home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / ipl / progs / ipp.icn < prev    next >
Text File  |  2002-03-26  |  36KB  |  1,179 lines

  1. ############################################################################
  2. #
  3. #    File:     ipp.icn
  4. #
  5. #    Subject:  Program to preprocess Icon programs
  6. #
  7. #    Author:   Robert C. Wieland, revised by Frank J. Lhota
  8. #
  9. #    Date:     March 26, 2002
  10. #
  11. ############################################################################
  12. #
  13. #   This file is in the public domain.
  14. #
  15. ############################################################################
  16. #
  17. #     Ipp is a preprocessor for the Icon language.  Ipp has many operations and
  18. #  features that are unique to the Icon environment and should not be used as
  19. #  a generic preprocessor (such as m4).  Ipp produces output which when written
  20. #  to a file is designed to be the source for icont, the command processor for
  21. #  Icon programs.
  22. #
  23. ############################################################################
  24. #  
  25. #  Ipp may be invoked from the command line as:
  26. #
  27. #    ipp [option  ...] [ifile [ofile]]
  28. #  
  29. #     Two file names may be specified as arguments.  'ifile' and 'ofile' are 
  30. #  respectively the input and output files for the preprocessor.  By default
  31. #  these are standard input and standard output.  If the output file is to be
  32. #  specified while the input file should remain standard input a dash ('-')
  33. #  should be given as 'ifile'.  For example, 'ipp - test' makes test the output
  34. #  file while retaining standard input as the input file.
  35. #  
  36. #     The following special names are predefined by ipp and may not be
  37. #  redefined #  or undefined.  The name _LINE_ is defined as the line number
  38. #  (as an integer) of the line of the source file currently processed.  The
  39. #  name _FILE_ is defined as the name of the current source file
  40. #  (as a string).  If the source is standard input then it has the value
  41. #  'stdin'.
  42. #
  43. #     Ipp will also set _LINE_ and _FILE_ from the "#line" directives it
  44. #  encounters, and will insert line directives to indicate source origins.
  45. #  
  46. #     Also predefined are names corresponding to the features supported by the
  47. #  implementation of Icon at the location the preprocessor is run.  This allows
  48. #  conditional translations using the 'if' commands, depending on what features
  49. #  are available.  Given below is a list of the features on a 4.nbsd UNIX 
  50. #  implementation and the corresponding predefined names:
  51. #  
  52. #      Feature                Name
  53. #      -----------------------------------------------------
  54. #      UNIX                UNIX
  55. #      co-expressions            co_expressions
  56. #      overflow checking        overflow_checking
  57. #      direct execution        direct_execution
  58. #      environment variables        environment_variables
  59. #      error traceback            error_traceback
  60. #      executable images        executable_images
  61. #      string invocation        string_invocation
  62. #      expandable regions        expandable_regions
  63. #  
  64. #  
  65. #  Command-Line Options:
  66. #  ---------------------
  67. #  
  68. #    The following options to ipp are recognized:
  69. #  
  70. #   -C        By default ipp strips Icon-style comments.  If this option
  71. #         is specified all comments are passed along except those
  72. #         found on ipp command lines (lines starting with  a '$' 
  73. #         command).
  74. #   -D name    
  75. #   -D name=def    Allows the user to define a name on the command line instead
  76. #         of using a $define command in a source file.  In the first
  77. #         form the name is defined as '1'.  In the second form name is
  78. #         defined as the text following the equal sign.  This is less
  79. #         powerful than the $define command line since def can not
  80. #         contain any white space (spaces or tabs).
  81. #   -d depth    By default ipp allows include files to be nested to a depth
  82. #         of ten.  This allows the preprocessor to detect infinitely
  83. #         recursive include sequences.  If a different limit for the
  84. #         nesting depth is needed it may changed by using this option
  85. #         with an integer argument greater than zero. Also, if a file
  86. #         is found to already be in a nested include sequence an
  87. #         error message is written regardless of the limit.
  88. #   -I dir    The following algorithm is normally used in searching for
  89. #         $include files.  On a UNIX system names enclosed in "" are
  90. #         searched for by trying in order the directories specified by the
  91. #         PATH environment variable, and names enclosed in <> are always
  92. #         expected to be in the /usr/icon/src directory.  On other systems
  93. #         names enclosed in <> are searched for by trying in order the
  94. #         directories specified by the IPATH environment variable; names
  95. #         in "" are searched for in a similar fashion, except that the
  96. #         current directory is tried first.  If the -I option is given the
  97. #         directory specified is searched before the 'standard'
  98. #          directories.  If this option is specified more than once the
  99. #          directories specified are tried in the order that they appear on
  100. #          the command line, then followed by the 'standard' directories.
  101. #  
  102. #  Preprocessor commands:
  103. #  ----------------------
  104. #  
  105. #     All ipp commands start with a line that has '$' as its first non-space
  106. #  character.  The name of the command must follow the '$'.  White space
  107. #  (any number of spaces or tabs) may be used to separate the '$' and the
  108. #  command name.  Any line beginning with a '$' and not followed by a valid
  109. #  name will cause an error message to be sent to standard error and
  110. #  termination of the preprocessor.  If the command requires an argument then
  111. #  it must be separated from the command name by white space otherwise the
  112. #  argument will be considered part of the name and the result will likely
  113. #  produce an error.  In processing the $ commands ipp responds to exceptional
  114. #  conditions in one of two ways.  It may produce a warning and continue
  115. #  processing or produce an error message and terminate.  In both cases the
  116. #  message is sent to standard error.  With the exception of error conditions
  117. #  encountered during the processing of the command line, the messages normally
  118. #  include the name and line number of the source file at the point the
  119. #  condition was encountered.  Ipp was designed so that most exception
  120. #  conditions encountered will produce errors and terminate.  This protects the
  121. #  user since warnings could simply be overlooked or misinterpreted.
  122. #
  123. #     Many ipp command require names as arguments.  Names must begin with a
  124. #  letter or an underscore, which may be followed by any number of letters,
  125. #  underscores, and digits.  Icon-style comments may appear on ipp command
  126. #  lines, however they must be separated from the normal end of the command by
  127. #  white_space.  If any extraneous characters appear on a command line a
  128. #  warning is issued.  This occurs when characters other than white-space or a
  129. #  comment follow the normal end of a command.
  130. #  
  131. #     The following commands are implemented:
  132. #  
  133. #    $define:  This command may be used in one of two forms.  The first form
  134. #           only allows simple textual substitution.  It would be invoked as
  135. #          '$define name text'.  Subsequent occurrences of name are replaced 
  136. #          with text.  Name and text must be separated by one white space
  137. #          character which is not considered to be part of the replacement
  138. #          text.  Normally the replacement text ends at the end of the line.
  139. #          The text however may be continued on the next line if the backslash
  140. #          character '\' is the last character on the line.  If name occurs
  141. #          in the replacement text an error message (recursive textual substi-
  142. #          tution) is written.
  143. #  
  144. #          The second form is '$define name(arg,...,arg) text' which defines
  145. #          a macro with arguments.  There may be no white space between the 
  146. #          name and the '('.  Each occurrence of arg in the replacement text
  147. #          is replaced by the formal arg specified when the macro is 
  148. #          encountered.   When a macro with arguments is expanded the arguments
  149. #          are placed into the expanded replacement text unchanged.  After the
  150. #          entire replacement text is expanded, ipp restarts its scan for names
  151. #          to expand at the beginning of the newly formed replacement text.  
  152. #          As with the first form above, the replacement text may be continued
  153. #          on following lines.  The replacement text starts immediately after
  154. #          the ')'. 
  155. #          The names of arguments must comply with the convention for regular 
  156. #          names.  See the section below on Macro processing for more 
  157. #          information on the replacement process.
  158. #  
  159. #    $undef:   Invoked as '$undef name'.   Removes the definition of name.  If
  160. #          name is not a valid name or if name is one of the reserved names
  161. #          _FILE_ or _LINE_ a message is issued.
  162. #  
  163. #    $include: Invoked as '$include <filename>' or '$include "filename"'.  This
  164. #          causes the preprocessor to make filename the new source until
  165. #          end of file is reached upon which input is again taken from the
  166. #          original source.  See the -I option above for more detail.
  167. #  
  168. #    $dump:    This command, which has no arguments, causes the preprocessor to 
  169. #          write to standard error all names which are currently defined.
  170. #          See '$ifdef' below for a definition of 'defined'.
  171. #  
  172. #    $warning:
  173. #           This command issues a warning, with the text coming from the
  174. #        argument field of the command.
  175. #  
  176. #    $error:   This command issues a error, with the text coming from the
  177. #        argument field of the command.  As with all errors, processing
  178. #        is terminated.
  179. #  
  180. #    $ifdef:   Invoked as 'ifdef name'.  The lines following this command appear
  181. #          in the output only if the name given is defined.  'Defined' means
  182. #            1.  The name is a predefined name and was not undefined using
  183. #            $undef, or
  184. #            2.  The name was defined using $define and has not been undefined
  185. #            by an intervening $undef.
  186. #  
  187. #    $ifndef:  Invoked as 'ifndef name'.  The lines following this command do 
  188. #           not appear in the output if the name is not defined.
  189. #  
  190. #    $if:      Invoked as 'if constant-expression'.  Lines following this
  191. #           command are processed only if the constant-expression produces a
  192. #           result. The following arithmetic operators may be applied to
  193. #           integer arguments: + - * / % ^
  194. #
  195. #          If an argument to one of the above operators is not an integer an
  196. #          error is produced.
  197. #  
  198. #             The following functions are provided: def(name), ndef(name)
  199. #          This allows the utility of $ifdef and $ifndef in a $if command.
  200. #          def produces a result if name is defined and ndef produces a
  201. #          result if name is not defined.  
  202. #          
  203. #             The following comparison operators may be used on integer
  204. #           operands:
  205. #
  206. #          > >= = < <= ~=
  207. #
  208. #              Also provided are alternation (|), conjunction (&), and
  209. #           negation (not).  The following table lists all operators with
  210. #           regard to decreasing precedence:
  211. #  
  212. #        not + - (unary)
  213. #          ^ (associates right to left)
  214. #          * / %
  215. #          + - (binary)
  216. #               > >= = < <= ~=
  217. #          |
  218. #          &
  219. #  
  220. #          The precedence of '|' and '&' are the same as the corresponding
  221. #          Icon counterparts.  Parentheses may be used for grouping.
  222. #          Backtracking is performed, so that the expression
  223. #
  224. #          FOO = (1|2)
  225. #
  226. #          will produce a result precisely when FOO is either 1 or 2.
  227. #
  228. #    $elif:    Invoked as 'elif constant-expression'.  If the lines preceding
  229. #          this command were processed, this command and the lines following
  230. #           it up to the matching $endif command are ignored.  Otherwise,
  231. #           the constant-expression is evaluated, and the lines following this
  232. #          command are processed only if it produces a result.
  233. #  
  234. #    $else:    This command has no arguments and reverses the notion of the
  235. #           test command which matches this directive.  If the lines preceding
  236. #           this command where ignored the lines following are processed, and
  237. #           vice versa.
  238. #  
  239. #    $endif:   This command has no arguments and ends the section of lines
  240. #           begun by a test command ($ifdef, $ifndef, or $if).  Each test
  241. #           command must have a matching $endif.
  242. #  
  243. #  Macro Processing and Textual Substitution
  244. #  -----------------------------------------
  245. #     No substitution is performed on text inside single quotes (cset literals)
  246. #  and double quotes (strings) when a line is processed.   The preprocessor
  247. #  will #  detect unclosed cset literals or strings on a line and issue an
  248. #  error message unless the underscore character is the last character on the
  249. #  line.  The output from 
  250. #  
  251. #      $define foo bar
  252. #      write("foo")
  253. #  
  254. #  is
  255. #
  256. #       write("foo")
  257. #  
  258. #     Unless the -C option is specified comments are stripped from the source.
  259. #  Even if the option is given the text after the '#' is never expanded.
  260. #  
  261. #     Macro formal parameters are recognized in $define bodies even inside cset 
  262. #  constants and strings.  The output from
  263. #  
  264. #      $define test(a)        "a"
  265. #      test(processed)
  266. #  
  267. #  is the following sequence of characters: "processed".
  268. #  
  269. #     Macros are not expanded while processing a $define or $undef.  Thus:
  270. #  
  271. #      $define off invalid
  272. #      $define bar off
  273. #      $undef off
  274. #      bar
  275. #  
  276. #  produces off.  The name argument to $ifdef or $ifndef is also not expanded.
  277. #  
  278. #     Mismatches between the number of formal and actual parameters in a macro
  279. #  call are caught by ipp.  If the number of actual parameters is greater than
  280. #  the number of formal parameters is error is produced.  If the number of
  281. #  actual parameters is less than the number of formal parameters a warning is
  282. #  issued and the missing actual parameters are turned into null strings.
  283. #  
  284. ############################################################################
  285. #
  286. #    The records and global variables used by ipp are described below:
  287. #
  288. #  Src_desc:        Record which holds the 'file descriptor' and name
  289. #            of the corresponding file.  Used in a stack to keep
  290. #                track of the source files when $includes are used.
  291. #  Opt_rec         Record returned by the get_args() routine which returns
  292. #            the options and arguments on the command line.  options
  293. #            is a cset containing options that have no arguments.
  294. #            pairs is a list of [option,  argument] pairs. ifile and
  295. #            ofile are set if the input or output files have been
  296. #            specified.
  297. #  Defs_rec        Record stored in a table keyed by names.  Holds the
  298. #            names of formal arguments, if any, and the replacement
  299. #            text for that name.
  300. #  Expr_node        Node of a parse tree for $if / $elif expressions.
  301. #            Holds the operator, or a string representing the
  302. #            control structure.  Also, holds a list of the args for
  303. #            the operation / control structure, which are either
  304. #            scalars or other Expr_node records.
  305. #  Chars        Cset of all characters that may appear in the input.
  306. #  Defs            The table holding the definition data for each name.
  307. #  Depth        The maximum depth of the input source stack.
  308. #  Ifile        Descriptor for the input file.
  309. #  Ifile_name        Name of the input file.
  310. #  Init_name_char     Cset of valid initial characters for names.
  311. #  Line_no        The current line number.
  312. #  Name_char        Cset of valid characters for names.
  313. #  Non_name_char    The complement of the above cset.
  314. #  Ofile        The descriptor of the output file.
  315. #  Options        Cset of no-argument options specified on the command
  316. #            line.
  317. #  Path_list        List of directories to search in for "" include files.
  318. #  Src_stack        The stack of input source records.
  319. #  Std_include_paths    List of directories to search in for <> include files.
  320. #  White_space        Cset for white-space characters.
  321. #  TRUE            Defined as 1.
  322. #
  323. ############################################################################
  324.  
  325. record Src_desc(fd, fname, line)
  326. record Opt_rec(options, pairs, ifile, ofile)
  327. record Defs_rec(arg_list, text)
  328. record Expr_node(op, arg)
  329.  
  330. global Chars, Defs, Depth, Ifile, Ifile_name, Init_name_char, 
  331.   Line_no, Name_char, Non_name_char, Ofile, Options, Path_list, 
  332.   Src_stack, Std_include_paths, White_space, TRUE, DIR_SEP
  333.  
  334. procedure main(arg_list)
  335.   local line, source
  336.  
  337.   init(arg_list)
  338.  
  339.   repeat {
  340.     while line := get_line(Ifile) do
  341.       line ? process_cmd(get_cmd())
  342.  
  343.     # Get new source
  344.     close(Ifile)
  345.     if source := pop(Src_stack) then {
  346.       Ifile := source.fd
  347.       Ifile_name := source.fname
  348.       Line_no := source.line
  349.       }
  350.     else  break
  351.   }
  352. end
  353.  
  354. procedure conditional(expr)
  355.  
  356.   return if eval(expr) then
  357.       true_cond()
  358.     else
  359.       false_cond()
  360. end
  361.  
  362. #
  363. # In order to simplify the parsing the four operators that are longer
  364. # than one character (<= ~= >= not) are replaced by one character
  365. # 'aliases'.  Also, all white space is removed.
  366. #
  367.  
  368. procedure const_expr(expr)
  369.   local new
  370.  
  371.   static White_space_plus
  372.  
  373.   initial White_space_plus := White_space ++ '<>~n'
  374.  
  375.   new := ""
  376.   expr ? {
  377.     while new ||:= tab(upto(White_space_plus)) ||
  378.       if any(White_space) then {
  379.         tab(many(White_space))
  380.      ""
  381.     }
  382.       else if =">=" then "\x01"
  383.       else if ="<=" then "\x02"
  384.       else if ="~=" then "\x03"
  385.       else if not any(Name_char, ,&pos - 1) &
  386.               ="not" &
  387.               not any(Name_char) then "\x04"
  388.       else move (1)
  389.     new ||:= tab(0)
  390.     }
  391.   #
  392.   # Now recursively parse the transformed string.
  393.   #
  394.   return parse(new)
  395.  
  396. end
  397.  
  398. procedure decoded(op)
  399.   return case op of {
  400.     "\x01":     ">="
  401.     "\x02":     "<="
  402.     "\x03":     "~="
  403.     "\x04":     "not"
  404.     default:     op
  405.     }
  406. end
  407.  
  408. procedure def_opt(s)
  409.   local name, text
  410.  
  411.   s ? {
  412.     name := tab(find("=")) | tab(0)
  413.     text := (move(1) & tab(0)) | "1"
  414.     }
  415.   if name == ("_LINE_" | "_FILE_") then
  416.     error(name, " is a reserved name and can not be redefined by the -D option")
  417.   if not name ? (get_name() & pos(0)) then
  418.     error(name, " :  Illegal name argument to -D option")
  419.   if member(Defs, name) then
  420.     warning(name, " : redefined by -D option")
  421.   insert(Defs, name, Defs_rec(, text))
  422. end
  423.  
  424. procedure define()
  425.   local args, name, text
  426.  
  427.   get_opt_ws()
  428.   if name := get_name() & (any(White_space ++ '(') | pos(0)) then {
  429.     if name == ("_LINE_" | "_FILE_") then
  430.       error(name, " is a reserved name and can not be redefined")
  431.  
  432.     if match("(") then             # A macro
  433.       args := get_formals()
  434.     text := get_text(args)
  435.  
  436.     if member(Defs,name) then
  437.       warning(name, " redefined")
  438.     insert(Defs, name, Defs_rec(args, text))
  439.     }  
  440.   else
  441.     error("Illegal or missing name in define")
  442. end
  443.  
  444. procedure dump()
  445.   if not pos(0) then
  446.     warning("Extraneous characters after dump command")
  447.   every write(&errout, (!sort(Defs))[1])
  448. end
  449.  
  450. procedure error(s1, s2)
  451.   s1 ||:= \s2
  452.   stop(Ifile_name, ":  ", Line_no, ":  ", "Error  ", s1)
  453. end
  454.  
  455. procedure eval(node)
  456.   suspend case type(node) of {
  457.     "Expr_node": {
  458.       case node.op of {
  459.     "|"     : eval(node.arg[1]) | eval(node.arg[2])
  460.     "&"     : eval(node.arg[1]) & eval(node.arg[2])
  461.     "not"   : not eval(node.arg[1])
  462.     "def"   : member(Defs, node.arg[1])
  463.     "ndef"  : not member(Defs, node.arg[1])
  464.     default :
  465.       case *node.arg of {
  466.         1 : node.op(eval(node.arg[1]))
  467.         2 : node.op(eval(node.arg[1]), eval(node.arg[2]))
  468.         }
  469.      }
  470.        }
  471.     default: node
  472.     }       
  473. end
  474.  
  475. procedure false_cond()
  476.   local cmd, line
  477.  
  478.   # Skip to next $else / $elif branch, or $endif
  479.   cmd := skip_to("elif", "else", "endif")
  480.   case cmd of {
  481.     "elif" : return if_cond(cmd)
  482.     "else" : {
  483.        while line := get_line(Ifile) do
  484.       line ? {
  485.         cmd := get_cmd()
  486.         case cmd of {
  487.           "elif"  :
  488.         error("'elif' encountered after 'else'")
  489.           "else"  :
  490.         error("multiple 'else' sections")
  491.           "endif" : return
  492.           default : process_cmd(cmd)
  493.           }
  494.         }
  495.        error("'endif' not encountered before end of file")
  496.        }
  497.     "endif": return
  498.     }
  499. end
  500.     
  501. procedure find_file(fname, path_list)
  502.   local ifile, ifname, path 
  503.  
  504.   every path := !path_list do {
  505.     ifname :=
  506.       if path == ("" | ".") then
  507.     fname
  508.       else
  509.     path || DIR_SEP || fname
  510.  
  511.  
  512.     if ifile := open(ifname) then {
  513.       if *Src_stack >= Depth then {
  514.     close(ifile)
  515.     error("Possibly infinitely recursive file inclusion")
  516.     }
  517.       if ifname == (Ifile_name | (!Src_stack).fname) then
  518.     error("Infinitely recursive file inclusion")
  519.       push(Src_stack, Src_desc(Ifile, Ifile_name, Line_no))
  520.       Ifile := ifile
  521.       Ifile_name := ifname
  522.       Line_no := 0
  523.       return
  524.       }
  525.     }
  526.   error("Can not open include file ", fname)
  527. end
  528.  
  529. procedure func(expr)
  530.   local op, arg
  531.  
  532.   expr ? {
  533.     if op  := tab(find("(")) & move(1) &
  534.        arg := get_name() & =")" & pos(0) then {
  535.       if op == ("def" | "ndef") then
  536.     return Expr_node(op, [arg])
  537.       else    
  538.         error("Invalid function name") 
  539.       }
  540.     }
  541. end
  542.  
  543. procedure get_args(arg_list, simple_opts, arg_opts)
  544.   local arg, ch, get_ofile, i, opts, queue
  545.   opts := Opt_rec('', [])
  546.   queue := []
  547.  
  548.   every arg := arg_list[i := 1 to *arg_list] do
  549.     if arg == "-" then         # Next argument should be output file
  550.       get_ofile := (i = *arg_list - 1) | 
  551.     stop("Invalid position of '-' argument")
  552.     else if arg[1] == "-" then     # Get options
  553.       every ch := !arg[2: 0] do
  554.     if any(simple_opts, ch) then
  555.       opts.options ++:= ch
  556.     else if any(arg_opts, ch) then
  557.       put(queue, ch)
  558.     else
  559.       stop("Invalid option - ", ch)
  560.     else if ch := pop(queue) then     # Get argument for option
  561.       push(opts.pairs, [ch, arg])
  562.     else if \get_ofile then {     # Get output file
  563.       opts.ofile := arg
  564.       get_ofile := &null
  565.       }
  566.     else {            # Get input file
  567.       opts.ifile := arg
  568.       get_ofile := (i < *arg_list)
  569.       }
  570.  
  571.   if \get_ofile | *queue ~= 0 then
  572.     stop("Invalid number of arguments")
  573.  
  574.   return opts
  575. end
  576.  
  577. procedure get_cmd()
  578.   local cmd
  579.   static  no_arg_cmds
  580.   initial no_arg_cmds := set(["dump", "else", "endif"])
  581.  
  582.   if ="#" & cmd := ="line" then
  583.     get_opt_ws()
  584.   else if (get_opt_ws()) & ="$" then {
  585.     get_opt_ws()
  586.     (cmd := tab(many(Chars))) | error("Missing command")
  587.     get_opt_ws()
  588.     if not pos(0) & member(no_arg_cmds, cmd) then
  589.       warning("Extraneous characters after argument to '" || cmd || "'")
  590.     }
  591.   else
  592.     tab (1)
  593.   return cmd
  594. end
  595.  
  596. procedure get_formals()
  597.   local formal, arglist, ch
  598.  
  599.   arglist := []
  600.   ="("
  601.   get_opt_ws()
  602.   if not =")" then 
  603.     repeat {
  604.       if (formal := get_name()) & get_opt_ws() & any(',)') then
  605.     put(arglist, formal)
  606.       else    
  607.     error("Invalid formal argument in macro definition")
  608.       if =")" then break
  609.       =","
  610.       get_opt_ws()
  611.       }
  612.   get_opt_ws()
  613.   return arglist
  614. end
  615.  
  616. procedure get_line(Ifile)
  617.   return 1(read(Ifile), Line_no +:= 1)
  618. end
  619.  
  620. procedure get_name()
  621.   return tab(any(Init_name_char)) || (tab(many(Name_char)) | "")
  622. end
  623.  
  624. procedure get_opt_ws()
  625.   return (tab(many(White_space)) | "") || (="#" || tab(0) | "")
  626. end
  627.  
  628. procedure get_text(is_macro)
  629.   local text
  630.  
  631.   if \is_macro then
  632.     text := tab(0)
  633.   else
  634.     text := (tab(any(White_space)) & tab(0)) | ""
  635.   while (text[-1] == "\\") do
  636.     (text := text[1:-1] || get_line(Ifile)) |
  637.       error("Continuation line not found before end of file")
  638.   return text
  639. end
  640.  
  641. # if_cond is the procedure for $if or $elif.  
  642. #
  643. # Procedure true_cond is invoked if the evaluation of a previous $if, $ifdef, or
  644. # $ifndef causes subsequent lines to be processed.  Lines will be processed
  645. # upto an $elif, $else, or $endif.  If $elif or $else is encountered, lines
  646. # are skipped until the matching $endif is encountered.
  647. #
  648. # Procedure false_cond is invoked if the evaluation of a previous $if, $ifdef, 
  649. # or $ifndef causes subsequent lines to be skipped.  Lines will be skipped 
  650. # upto an $elif, $else, or, $endif.  If $else is encountered, lines are
  651. # processed until the $endif matching the $else is encountered.
  652.  
  653. procedure if_cond(cmd)
  654.   if pos(0) then
  655.     error("Constant expression argument to '" || cmd || "' missing")
  656.   else
  657.     return conditional(const_expr(tab(0)))
  658. end
  659.  
  660. procedure ifdef()
  661.   local name
  662.  
  663.   if name := get_name() then
  664.     {
  665.     get_opt_ws()
  666.     if not pos(0) then
  667.       warning("Extraneous characters after argument to 'ifdef'")
  668.     return conditional(Expr_node("def", [name]))
  669.     }
  670.   else
  671.     error("Argument to 'ifdef' is not a valid name")
  672. end
  673.   
  674. procedure ifndef()
  675.   local name
  676.  
  677.   if name := get_name() then {
  678.     get_opt_ws()
  679.     if not pos(0) then
  680.       warning("Extraneous characters after argument to 'ifndef'")
  681.     return conditional(Expr_node("ndef", [name]))
  682.     }
  683.   else
  684.     error("Argument to 'ifndef' is not a valid name")
  685. end
  686.  
  687. procedure in_text(name, text)
  688.   return text ? 
  689.     tab(find(name)) &
  690.     (if move(-1) then tab(any(Non_name_char)) else "") &
  691.     move(*name) &
  692.     (tab(any(Non_name_char)) | pos(0))
  693. end
  694.  
  695. procedure include()
  696.   local ch, fname 
  697.   static fname_chars, stopper
  698.  
  699.   initial {
  700.     fname_chars := Chars -- '<>"'
  701.     stopper := table()
  702.     insert(stopper, "\"", "\"")
  703.     insert(stopper, "<",  ">")
  704.     }
  705.  
  706.   if (ch    := tab(any('"<'))) &
  707.      (fname := tab(many(fname_chars))) &
  708.       =stopper[ch] then {
  709.     get_opt_ws()
  710.     if not pos(0) then
  711.       warning("Extraneous characters after include file name")
  712.     find_file(fname,
  713.       case ch of {
  714.     "\"" : Path_list
  715.     "<"  : Std_include_paths
  716.     }
  717.       )
  718.     }
  719.   else
  720.     error("Missing or invalid include file name")
  721. end
  722.  
  723. procedure init(arg_list)
  724.   local s
  725.  
  726.   TRUE := 1
  727.   Defs := table()
  728.   Init_name_char := &letters ++ '_'
  729.   Name_char := Init_name_char ++ &digits
  730.   Non_name_char := ~Name_char
  731.   White_space := ' \t\b'
  732.   Chars := &ascii -- White_space
  733.   Line_no := 0
  734.   Depth := 10
  735.  
  736.   # Predefine features
  737.   every s := &features do {
  738.     s := map(s, " -/", "___")
  739.     insert(Defs, s, Defs_rec(, "1"))
  740.     }
  741.  
  742.   # Set path list for $include files given in "", <>
  743.   if member(Defs, "UNIX") then {
  744.     Path_list := []
  745.     getenv("PATH") ? while put(Path_list, 1(tab(find(":")), move(1)))
  746.     Std_include_paths := ["/usr/icon/src"]
  747.     }
  748.   else {
  749.     Std_include_paths := []
  750.     (getenv("IPATH") || " ") ?
  751.        while put(Std_include_paths, tab(find(" "))) do move(1)
  752.     Path_list := [""] ||| Std_include_paths
  753.     }
  754.  
  755.   process_options(arg_list)
  756. end
  757.  
  758. procedure lassoc(expr, op)
  759.   local j, arg1, arg2
  760.  
  761.   expr ? {
  762.     every j := bal(op)
  763.     # Succeeds if op found.
  764.     if arg1 := tab(\j) & op := decoded(move(1)) & arg2 := tab(0) then {
  765.       op := proc(op, 2)        # Fails for control structures
  766.       return Expr_node(op, [parse(arg1), parse(arg2)])
  767.       }
  768.     }
  769. end
  770.  
  771. #
  772. # Programmer's note: Ifile_name and Line_no should not be assigned new
  773. # values until the very end, so that if there is an error, the error
  774. # message will include the file/line no of the current line directive,
  775. # instead of the file/line of the text that follows the directive.
  776. #
  777. procedure line()
  778.   local new_line, new_file
  779.  
  780.   new_line := tab(many(&digits)) | error("No line number in line directive")
  781.   get_opt_ws()
  782.   if ="\"" then {
  783.     new_file := ""
  784.     #
  785.     # Get escaped chars.  We assume that the only escaped chars
  786.     # appearing in a file name would be \\ or \", where the actual
  787.     # character to be used is simply the character following the slash.
  788.     # In the unlikely event that other escape sequences are encountered,
  789.     # this section would have to revised.
  790.     #
  791.     while new_file ||:= tab(find("\\")) || (move(1) & move(1))
  792.     new_file ||:= tab(find("\"")) |
  793.       error("Invalid file name in line directive")
  794.     }
  795.  
  796.   Line_no    := integer(new_line)
  797.   Ifile_name := \new_file
  798.   return
  799. end
  800.  
  801. procedure macro_call(entry, args)
  802.   local i, value, result, token
  803.  
  804.   value := table()
  805.   every i := 1 to *entry.arg_list do
  806.     insert(value, entry.arg_list[i], args[i] | "")
  807.  
  808.   entry.text ? {
  809.     result := tab(upto(Name_char) | 0)
  810.     while token := tab(many(Name_char)) do {
  811.       result ||:= \value[token] | token
  812.       result ||:= tab(many(Non_name_char))
  813.       }
  814.     }
  815.   return result
  816. end
  817.  
  818. procedure no_endif_error()
  819.   error("'endif' not encountered before end of file")
  820. end
  821.  
  822. procedure parse(expr)
  823.   # strip surrounding parens.
  824.   while expr ?:= 2(="(", tab(bal (')')), pos(-1))
  825.  
  826.   return lassoc(expr, '&' | '|') |
  827.     lassoc(expr, '<=>\x01\x02\x03' | '+-' | '*/%') |
  828.     rassoc(expr, '^') | 
  829.     unary(expr, '+-\x04') |
  830.     func(expr) |
  831.     integer(process_text(expr)) |
  832.     error(expr, " :  Integer expected")
  833. end
  834.  
  835. procedure process_cmd(cmd)
  836.   static last_cmd
  837.   initial last_cmd := ""
  838.  
  839.   case cmd of {
  840.     "dump"    : dump()
  841.     "define"  : define()
  842.     "undef"   : undefine()
  843.     "include" :    include()
  844.     "line"    : line()
  845.     "error"   :    error(tab(0))
  846.     "warning" :    warning(tab(0))
  847.     "if"      : if_cond( last_cmd := cmd )
  848.     "ifdef"   : ifdef(   last_cmd := cmd )
  849.     "ifndef"  : ifndef(  last_cmd := cmd )
  850.     "elif"   |
  851.     "else"   |
  852.     "endif"   :    error("No previous 'if' expression")
  853.     &null     : {
  854.       if \last_cmd then
  855.     put_linedir(Ofile, Line_no, Ifile_name)
  856.       write(Ofile, process_text(tab(0)))
  857.       }
  858.     default   :    error("Undefined command")
  859.     }
  860.   last_cmd := cmd
  861.   return
  862. end
  863.  
  864. procedure process_macro(name, entry, s)
  865.   local arg, args, new_entry, news, token
  866.  
  867.   s ? {
  868.     args := []
  869.     if ="(" then {
  870.       #
  871.       # Get args if list is not empty.
  872.       #
  873.       get_opt_ws ()
  874.       if not =")" then
  875.     repeat {
  876.       arg := get_opt_ws()
  877.       if token := tab(many(Chars -- '(,)')) then {
  878.         if /(new_entry := Defs[token]) then
  879.           arg ||:= token
  880.         else if /new_entry.arg_list then
  881.           arg ||:= new_entry.text
  882.         else {  # Macro with arguments
  883.           if news := tab(bal(White_space ++ ',)')) then
  884.         arg ||:= process_macro(token, new_entry, news)
  885.           else
  886.         error(token, ":  Error in arguments to macro call")
  887.           } # if
  888.         } # if
  889.       else if not any(',)') then
  890.         error(name, ":  Incomplete macro call")
  891.       arg ||:= tab(many(White_space))
  892.       put(args, arg)
  893.       if match(")") then
  894.         break
  895.       move(1)
  896.     } # repeat 
  897.       if *args > *entry.arg_list then
  898.     error(name, ":  Too many arguments in macro call")
  899.       else if *args < *entry.arg_list then
  900.     warning(name, ":  Missing arguments in macro call")
  901.       return macro_call(entry, args)
  902.       } # if
  903.     }
  904. end
  905.  
  906. procedure process_options(arg_list)
  907.   local args, arg_opts, pair, simple_opts, tmp_list, value
  908.  
  909.   simple_opts := 'C'
  910.   arg_opts := 'dDI'
  911.   Src_stack := []
  912.  
  913.   args := get_args(arg_list, simple_opts, arg_opts)
  914.   if \args.ifile then {
  915.     (Ifile := open(args.ifile)) | stop("Can not open input file ", args.ifile)
  916.     Ifile_name := args.ifile
  917.     }
  918.   else {
  919.     Ifile := &input
  920.     Ifile_name := "stdin"
  921.     }
  922.   if \args.ofile then 
  923.     (Ofile := open(args.ofile, "w")) | stop("Can not open output file",
  924.       args.ofile)
  925.   else 
  926.     Ofile := &output
  927.  
  928.   Options := args.options 
  929.   tmp_list := []
  930.   every pair := !args.pairs do
  931.     case pair[1] of {
  932.       "D":    def_opt(pair[2])
  933.       "d":    if (value := integer(pair[2])) > 0 then
  934.           Depth := value
  935.         else
  936.           stop("Invalid argument for depth")
  937.       "I":    push(tmp_list, pair[2])
  938.     }
  939.   Path_list := tmp_list ||| Path_list
  940. end
  941.  
  942. procedure process_text(line)
  943.   local add, entry, new, position, s, token
  944.   static in_string, in_cset
  945.  
  946.   new :=  ""
  947.   while *line > 0 do {
  948.     add := ""
  949.     line ?:= {
  950.       if \in_string then {
  951.     # Ignore escaped chars
  952.     while new ||:= tab(find("\\")) || move(2)
  953.     if new ||:= tab(find("\"")) || move(1) then
  954.       in_string := &null
  955.     else {
  956.       new ||:= tab(0)
  957.       if line[-1] ~== "_" then {
  958.         in_string := &null
  959.         warning("Unclosed double quote")
  960.         }
  961.       }
  962.     }        
  963.       else if \in_cset then {
  964.     # Ignore escaped chars.
  965.     while new ||:= tab(find("\\")) || move(2)
  966.     if new ||:= (tab(find("'")) || move(1)) then
  967.       in_cset := &null
  968.     else {
  969.       new ||:= tab(0)
  970.       if line[-1] ~== "_" then {
  971.         in_cset := &null
  972.         warning("Unclosed single quote")
  973.         }
  974.       }
  975.     }   
  976.  
  977.       new ||:= tab(many(White_space))
  978.       case token := tab(many(Name_char) | any(Non_name_char)) of {
  979.     "\"": {
  980.       new ||:= "\""
  981.       if \in_string then 
  982.         in_string := &null
  983.       else if not pos(0) then {
  984.         in_string := TRUE 
  985.         }
  986.       else {
  987.         warning("Unclosed double quote")
  988.         }
  989.       add ||:= tab(0)
  990.         }
  991.     "'": {
  992.       new ||:= "'"
  993.       if \in_cset then 
  994.         in_cset := &null
  995.       else if not pos(0) then {
  996.         in_cset := TRUE 
  997.         }
  998.       else {
  999.         warning("Unclosed double quote")
  1000.         }
  1001.       add ||:= tab(0)
  1002.         }
  1003.     "#": {
  1004.         new ||:= if any(Options, 'C') then token || tab(0)
  1005.         else tab(0) & token ? tab(find("#"))
  1006.         }
  1007.     "__LINE__":
  1008.       new ||:= Line_no
  1009.     "__FILE__":
  1010.       new ||:= Ifile_name
  1011.     default: {
  1012.       if /(entry := Defs[token]) then
  1013.         new ||:= token
  1014.       else if /entry.arg_list then
  1015.         if in_text(token, entry.text) then
  1016.         error("Recursive textual substitution")
  1017.         else
  1018.         add := entry.text
  1019.       else {  # Macro with arguments
  1020.         s := tab(bal(White_space) | 0)
  1021.         if not any('(', s) then
  1022.         error(token, ":  Incomplete macro call")
  1023.         add := process_macro(token, entry, s)
  1024.         }
  1025.       } # default
  1026.     } # case
  1027.       add || tab(0)
  1028.       } # ?:=
  1029.     } # while
  1030.   return new
  1031. end
  1032.  
  1033. procedure put_linedir(Ofile, Line_no, Ifile_name)
  1034.   static last_filename
  1035.   initial last_filename := ""
  1036.  
  1037.   writes(Ofile, "#line ", Line_no - 1)
  1038.   #
  1039.   # Output file name part only if the
  1040.   # filename differs from the last one used.
  1041.   #
  1042.   if last_filename ~==:= Ifile_name then
  1043.     writes(Ofile, " ", image(last_filename))
  1044.   write(Ofile)
  1045.   return
  1046. end
  1047.  
  1048. procedure rassoc(expr, op)
  1049.   local arg1, arg2
  1050.  
  1051.  
  1052.   # Succeeds if op found.
  1053.   expr ? if arg1 := tab(bal(op)) & op := move(1) & arg2 := tab(0) then {
  1054.       op := decoded(op)
  1055.       op := proc(op, 2)        # Fails for control structures
  1056.       return Expr_node(op, [parse(arg1), parse(arg2)])
  1057.       }
  1058. end
  1059.  
  1060. #
  1061. # skip_to is used to skip over parts of the an '$if' structure. targets
  1062. # are the $if - related commands to skip to, and should always include
  1063. # "endif".
  1064. #
  1065. # We do not, of course, wish to skip to a command in an $if structure
  1066. # that is embedded in the current one; also, we want to make sure that
  1067. # embedded $if structures, even in skipped lines, are well formed.  We
  1068. # therefore maintain a stack, if_sects, of the currently applicable $if
  1069. # structure commands encountered in the skipped lines.  For example, if
  1070. # we have skipped over the commands
  1071. #
  1072. #    $ifdef ...
  1073. #       $if ...
  1074. #       $elif ...
  1075. #           $if ...
  1076. #           $else
  1077. #
  1078. # if_sect would be ["else", "elif", "ifdef"].
  1079. #
  1080. procedure skip_to(targets[])
  1081.   local cmd, if_sects, line, argpos
  1082.  
  1083.   if_sects := []
  1084.   while line := get_line(Ifile) | no_endif_error () do
  1085.     line ? {
  1086.       cmd := get_cmd()
  1087.       if *if_sects = 0 & \cmd == !targets then {
  1088.     argpos := &pos
  1089.     break
  1090.     }
  1091.  
  1092.       case cmd of {
  1093.     "if"    |
  1094.     "ifdef" |
  1095.     "ifndef" : {
  1096.       if pos(0) then
  1097.         error("Argument to '" || cmd || "' missing")
  1098.       push(if_sects, cmd)
  1099.       }
  1100.     "elif"   : {
  1101.       if pos(0) then
  1102.         error("Argument to '" || cmd || "' missing")
  1103.       if if_sects[1] == "else" then
  1104.         error("'elif' encountered after 'else'")
  1105.       else
  1106.         if_sects[1] := cmd
  1107.       }
  1108.     "else"   : {
  1109.       if if_sects[1] == "else" then
  1110.         error("multiple 'else' sections")
  1111.       else
  1112.         if_sects[1] := cmd
  1113.       }
  1114.     "endif"  : pop(if_sects)
  1115.     }
  1116.       }
  1117.  
  1118.   #
  1119.   # Now reset the &subject to the current line, and &pos to the argument
  1120.   # field of the current line, so that if we skipped to a line which will
  1121.   # require further processing (such as $elif), the scanning functions can
  1122.   # be used.
  1123.   #
  1124.   &subject := line
  1125.   &pos     := argpos
  1126.   return cmd
  1127.  
  1128. end
  1129.  
  1130. procedure true_cond()
  1131.   local cmd, line
  1132.  
  1133.   while line := get_line(Ifile) | no_endif_error () do
  1134.     line ? {
  1135.       case cmd := get_cmd() of {
  1136.     "elif" |
  1137.     "else"  : return skip_to("endif")
  1138.     "endif" : return cmd
  1139.     default : process_cmd(cmd)
  1140.     }
  1141.       }
  1142.      
  1143. end
  1144.  
  1145. procedure unary(expr, op)
  1146.   local arg1
  1147.  
  1148.  
  1149.   # Succeeds if op found.
  1150.   expr ?
  1151.     if op := decoded(tab(any(op))) & arg1 := tab(0) then {
  1152.       op := proc(op, 1)        # fails for control structures
  1153.       return Expr_node(op, [parse(arg1)])
  1154.       }
  1155. end
  1156.  
  1157. procedure undefine()
  1158.   local name
  1159.  
  1160.   if name := get_name() then {
  1161.     get_opt_ws()
  1162.     if not pos(0) then
  1163.       warning("Extraneous characters after argument to undef")
  1164.     if name == ("_LINE_" | "_FILE_") then
  1165.       error(name, " is a reserved name that can not be undefined")
  1166.     delete(Defs, name)
  1167.     }
  1168.   else
  1169.     error("Name missing in undefine")
  1170. end
  1171.  
  1172. procedure warning(s1, s2)
  1173.   s1 ||:= \s2
  1174.   write(&errout, Ifile_name, ":  ", Line_no, ":  ", "Warning  " || s1)
  1175. end
  1176.