#!/bin/env tclsh
#
# This script is used to amalgamate C source code files into a single
# unit.
#
# Usage example:
#
#     tclsh mkccode.tcl -DENABLE_FEATURE_XYZ demoapp.c.in >demoapp.c
#
# The demoapp.c.in file contains a mixture of C code, TCL script, and
# processing directives used by mkccode.tcl to build the final C-code
# output file.  Most lines of demoapp.c.in are copied straight through
# into the output.  The following control directives are recognized:
#
# BEGIN_STRING
#
#      This marks the beginning of large string literal - usually a TCL
#      script of some kind.  Subsequent lines of text through the first
#      line that begins with END_STRING are converted into a C-language
#      string literal.
#
# INCLUDE path
#
#      The path argument is the name of a file to be inserted in place of
#      the INCLUDE line.  The path can begin with $ROOT to signify the
#      root of the SQLite source tree, or $HOME to signify the directory
#      that contains the demoapp.c.in input script itself.  If the path does
#      not begin with either $ROOT or $HOME, then it is interpreted relative
#      to the current working directory.
#
#      If the INCLUDE occurs in the middle of BEGIN_STRING...END_STRING
#      then all of the text in the input file is converted into C-language
#      string literals.
#
# IFDEF macro
# IFNDEF macro
# ELSE
# ENDIF
#
#      The text from "IFDEF macro" down to the next ELSE or ENDIF is
#      included only if -Dmacro appears as a command-line argument.
#      The "IFNDEF macro" simply inverts the initial test.
#
# None of the control directives described above will nest.  Only the
# top-level input file ("demoapp.c.in" in the example) is interpreted.
# referenced files are copied verbatim.
#
proc usage {} {
  puts stderr "Usage: $::argv0 \[OPTIONS\] TEMPLATE >OUTPUT"
  exit 1
}
set infile {}
foreach ax $argv {
  if {[string match -D* $ax]} {
    if {[string match *=* $ax]} {
      regexp -- {-D([^=]+)=(.*)} $ax all name value
      set DEF($name) $value
    } else {
      set DEF([string range $ax 2 end]) 1
    }
    continue
  }
  if {[string match -* $ax]} {
    puts stderr "$::argv0:  Unknown option \"$ax\""
    usage
  }
  if {$infile!=""} {
    puts stderr "$::argv0:  Surplus argument: \"$ax\""
    usage
  }
  set infile $ax
}
set ROOT [file normalize [file dir $argv0]/..]
set HOME [file normalize [file dir $infile]]
set in [open $infile rb]
puts [subst {/* DO NOT EDIT
**
** This file was generated by \"$argv0 $argv\".
** To make changes, edit $infile then rerun the generator
** command.
*/}]
set instr 0
set omit {}
set nomit 0
set ln 0
while {1} {
  set line [gets $in]
  incr ln
  if {[eof $in]} break
  if {[regexp {^INCLUDE (.*)} $line all path]} {
    if {$nomit>0 && [string match *1* $omit]} continue
    if {0} {
      # https://github.com/msteveb/jimtcl/issues/320
      regsub {^\$ROOT\y} $path $ROOT path
      regsub {^\$HOME\y} $path $HOME path
    } else {
      set path [string map "\$ROOT $ROOT" $path]
      set path [string map "\$HOME $HOME" $path]
      # or: set path [string map "\$HOME $HOME \$ROOT $ROOT" $path]
    }
    set in2 [open $path rb]
    puts "/* INCLUDE $path */"
    if {$instr} {
      while {1} {
        set line [gets $in2]
        if {[eof $in2]} break
        set x [string map "\\\\ \\\\\\\\ \\\" \\\\\"" $line]
        puts "\"$x\\n\""
      }
    } else {
      puts [read $in2]
    }
    puts "/* END $path */"
    close $in2
    continue
  }
  if {[regexp {^BEGIN_STRING} $line]} {
    set instr 1
    puts "/* BEGIN_STRING */"
    continue
  }
  if {[regexp {^END_STRING} $line]} {
    set instr 0
    puts "/* END_STRING */"
    continue
  }
  if {[regexp {^IFNDEF +([A-Za-z_0-9]+)} $line all name]} {
    set omit $omit[info exists DEF($name)]
    incr nomit
    continue
  }
  if {[regexp {^IFDEF +([A-Za-z_0-9]+)} $line all name]} {
    set omit $omit[expr {![info exists DEF($name)]}]
    incr nomit
    continue
  }
  if {[regexp {^ELSE} $line]} {
    if {!$nomit} {
      puts stderr "$infile:$ln: ELSE without a prior IFDEF"
      exit 1
    }
    set omit [string range $omit 0 end-1][expr {![string index $omit end]}]
    continue
  }
  if {[regexp {^ENDIF} $line]} {
    if {!$nomit} {
      puts stderr "$infile:$ln: ENDIF without a prior IFDEF"
      exit 1
    }
    incr nomit -1
    set omit [string range $omit 0 [expr {$nomit-1}]]
    continue
  }
  if {$nomit>0 && [string match *1* $omit]} {
    # noop
  } elseif {$instr} {
    set x [string map "\\\\ \\\\\\\\ \\\" \\\\\"" $line]
    puts "\"$x\\n\""
  } else {
    puts $line
  }
}
if {$nomit} {
  puts stderr "$infile:$ln: One or more unterminated IFDEFs"
  exit 1
}
