summaryrefslogtreecommitdiffstats
path: root/fickle
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2017-05-29 20:32:37 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2017-05-29 20:32:37 (GMT)
commit79b861f6cc5eee9985b08a9b3581580cf02a6965 (patch)
tree9c55c002e06068f2dc7765168f6fb813a8ec3c9e /fickle
parent76859fcafcbd024cb2ea5a200fbf361e906d1fac (diff)
downloadblt-79b861f6cc5eee9985b08a9b3581580cf02a6965.zip
blt-79b861f6cc5eee9985b08a9b3581580cf02a6965.tar.gz
blt-79b861f6cc5eee9985b08a9b3581580cf02a6965.tar.bz2
add fickle/taccle
Diffstat (limited to 'fickle')
-rwxr-xr-xfickle/COPYING340
-rwxr-xr-xfickle/ChangeLog49
-rwxr-xr-xfickle/README.md166
-rwxr-xr-xfickle/examples/Makefile20
-rwxr-xr-xfickle/examples/README102
-rwxr-xr-xfickle/examples/cat.fcl4
-rwxr-xr-xfickle/examples/cl.fcl76
-rwxr-xr-xfickle/examples/csa.fcl65
-rwxr-xr-xfickle/examples/tsa.fcl79
-rwxr-xr-xfickle/examples/verbs.fcl34
-rwxr-xr-xfickle/examples/wc.fcl40
-rwxr-xr-xfickle/examples/wc2.fcl102
-rwxr-xr-xfickle/fickle.tcl906
13 files changed, 1983 insertions, 0 deletions
diff --git a/fickle/COPYING b/fickle/COPYING
new file mode 100755
index 0000000..d60c31a
--- /dev/null
+++ b/fickle/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/fickle/ChangeLog b/fickle/ChangeLog
new file mode 100755
index 0000000..2b26507
--- /dev/null
+++ b/fickle/ChangeLog
@@ -0,0 +1,49 @@
+2004-11-13 J. Tang <tang@jtang.org>
+
+ * Released as fickle version 2.04.
+
+ * Reworded parts of README; included explanation of change to
+ [unput].
+
+ * Changes some internal variable names. With the exception of
+ yy_scan_string, all other global variables beginning with 'yy_'
+ are used to keep track of internal fickle states. (Of course,
+ this is all changed if %option prefix is given.)
+
+ * Fixed a missing parameter within yyless function. Thanks to Jon
+ Harrison for reporting this.
+
+2004-09-30 J. Tang <tang@jtang.org>
+
+ * Found some problems when both %option debug and %option prefix
+ enabled.
+
+ * Found a problem with patterns that use vertical bars.
+
+2004-09-30 J. Tang <tang@jtang.org>
+
+ * Added some more comments; reformatted code slightly.
+
+2004-09-27 J. Tang <tang@jtang.org>
+
+ * Removed errornous space in "%buffersize" within handle_defs
+ switch. Thanks to jcw for discovering this.
+
+2004-08-19 J. Tang <tang@jtang.org>
+
+ * definitions containing backslashses correctly maintain those
+ backslashes during substitution. thanks to Matt Newman for
+ discovering this bug.
+
+2004-08-18 J. Tang <tang@jtang.org>
+
+ * added interactive mode (-I option)
+
+2004-08-05 J. Tang <tang@jtang.org>
+
+ * fixed yy_scan_string; moved comments about to be Tcldoc-friendly
+
+2002-06-24 J. Tang <tang@jtang.org>
+
+ * fixed spelling and grammar mistakes within README
+
diff --git a/fickle/README.md b/fickle/README.md
new file mode 100755
index 0000000..399bc26
--- /dev/null
+++ b/fickle/README.md
@@ -0,0 +1,166 @@
+$Id: README,v 1.3 2004/11/14 02:36:28 tang Exp $
+
+fickle 2.04 by Jason Tang (tang@jtang.org)
+
+This is a scanner generator program much like flex(1) is to C. If you
+have no desire to author Tcl programs, particularly those that
+manipulate text, fickle is not for you. A passing knowledge of flex
+or some other lex-like program would be useful as that fickle uses
+nearly identical syntax and commands as flex. Two good references are
+the flex(1) man page and the O'Reilly book 'lex & yacc' by Levine,
+Mason, and Brown.
+
+Examples of working fickle code may be found in the 'examples'
+directory. See the examples' README for further details.
+
+fickle is protected by the GNU general public license. See the file
+COPYING for details.
+
+
+USAGE
+-----
+fickle is to be used as a command-line utility that translates 'fickle
+specification files' into valid Tcl code. Invoke fickle like so:
+
+$ tclsh fickle.tcl some_spec_file.f
+
+and it will generate a file 'some_spec_file.tcl' containing the
+resultant scanner. fickle supports the more popular of flex's
+options:
+
+ Usage: fickle [options] [FILE]
+ FILE a fickle specification file
+
+ Options:
+ -h print this help message and quit
+ -v be verbose while generating scanner
+ -o FILE specify name to write scanner
+ -d enable debug mode while running scanner
+ -i generate a case-insensitive scanner
+ -l keep track of line numbers in global variable yylineno
+ -s suppress default rule; unmatched input aborts with errors
+ -t write scanner to standard output
+ -I read input interactively
+ -P PREFIX change default yy prefix to PREFIX
+ --version print fickle version and quit
+
+If no input files are given fickle reads from standard input. Also
+like flex fickle supports the following '%option' directives (and
+their "no" counterparts):
+
+ caseful or case-sensitive opposite of -i option (default)
+ caseless or case-insensitive -i option
+ debug -d option
+ default opposite of -s option
+ interactive -I option
+ verbose -v option
+ stack enables start states
+ yylineno enables tracking of line numbers
+ yywrap call [yywrap] upon end-of-file
+
+In addition fickle has two additional directives:
+
+ %buffersize NUM set size of internal input buffer (default 1024)
+ %option noheaders strips fickle-generated comments from output file
+
+
+CAPABILITIES
+------------
+fickle is capable of most of flex's functionality. In addition to the
+options listed above the following functions work how one would expect
+within a Tcl environment:
+
+ input, unput, yy_scan_string, yyless, yylex, yyrestart, yywrap,
+ ECHO, YY_FLUSH_BUFFER, and YY_INPUT
+
+as well as these global variables:
+
+ ::yytext, ::yyleng, ::yyin, and ::yyout
+
+With debug mode enabled (either -d flag or %option debug) fickle
+adds a global variable ::yy_flex_debug. Set this to non-zero to
+display to standard error every time the scanner matches a pattern as
+well as when it reaches the end of a file.
+
+With start states enabled (%option stack) one now can call the
+functions yy_push_state, yy_pop_state, yy_top_state, and BEGIN. Like
+flex fickle allows for both inclusionary (%s directive) and
+exclusionary (%x) states.
+
+With line numbers enabled (%option yylineno) fickle will keep track of
+newlines within the input file. The line number may be accessed
+through the global variable ::yylineno.
+
+See a generated file for full documentation of these fickle-supplied
+functions, assuming that one did not call '%option noheaders'.
+
+
+DIFFERENCES
+-----------
+fickle does its best to emulate flex but there are some important
+differences to note. The following functions/macros are not supported
+by fickle:
+
+ output, yymore, yy_*_buffer, REJECT, YY_CURRENT_BUFFER, or YY_DECL
+
+nor does it support the declarations %T, %unused, or %used. Unlike
+flex, unput() is a procedure that takes accepts any string not just a
+character at a time.
+
+Textual substitutions of definitions is kind of blind, and will ignore
+backslashes preceding opening braces. For example, if there exists a
+definition 'foo' then it would be substituted into the patterns
+"{foo}" as well as "\{foo}". Substitutions are performed by order of
+appearance. Thus if the result of one substitution creates a pattern
+that looks like a second definition then a second substitution occurs.
+To prevent this behavior place definitions that might result in
+creating a valid name higher up in the file. Furthermore fickle will
+not issue any warnings whenever a pattern has an undefined name.
+
+Interactive mode differs somewhat from flex. fickle reads from
+$::yyin a block of bytes at a time; by default this block is 1024
+bytes though it may be changed with %buffersize. This is akin to
+flex's batch processing mode. However this behavior is very
+undesirable for interactive programs; fickle would block until a user
+types in 1024 characters. Instead when in interactive mode, set by
+either the -I command line option or %interactive directive, fickle
+reads a line at a time from $::yyin through the [gets] procedure.
+Unlike flex, fickle defaults to batch mode and not interactive mode.
+
+The start state INITIAL is exactly that -- the literal "INITIAL" and
+not the value zero. In addition fickle does not support start
+condition scopes.
+
+fickle calls Tcl's [regexp] to handle pattern matching, so any valid
+Tcl regexp is valid under fickle. This does lead to some
+incompatiblities with flex-style regexps. <<EOF>> is unsupported.
+Circumflexes (^) may behave oddly; fickle tries to handle ^ sanely by
+modifying its internal buffer whenever it matches newlines. Finally,
+Tcl regexps do not treat double quotation marks as metacharacters.
+For example, given the regular expression "/*" the call:
+
+ regexp -- {"/*"} $string
+
+attempts to match any number of forward slashes rather than a C-style
+comment token. fickle rewrites patterns containing double quotes to
+explicitly escape metacharacters within. Therefore fickle instead
+interprets the above pattern as:
+
+ regexp -- {\/\*} $string
+
+
+MISCELLANY
+----------
+fickle, like flex, allows the user to change all 'yy' prefaces through
+the -P flag. The argument to -P will automagically be downcased.
+However, the pre-defined macro 'BEGIN' does not have a prefix. To get
+around this limitation it takes an optional second parameter which
+will direct it to the correct parameter. For example suppose one
+invokes fickle with '-P zz'. All internal calls to 'BEGIN' will set
+"zz" as the second parameter. Any of your code which calls 'BEGIN'
+will need to pass "zz" as well, otherwise 'BEGIN' will default to
+using the "yy" internally.
+
+Finally, fickle will exhaust its internal buffer prior to calling
+yywrap. That means regular expressions cannot match across file
+boundaries.
diff --git a/fickle/examples/Makefile b/fickle/examples/Makefile
new file mode 100755
index 0000000..4a40f68
--- /dev/null
+++ b/fickle/examples/Makefile
@@ -0,0 +1,20 @@
+# $Id: Makefile,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+# A simple Makefile that calls fickle upon the example specification
+# files.
+
+TCL=/usr/bin/tclsh
+FICKLE=../fickle.tcl
+FCL_EXS=cat.tcl verbs.tcl wc.tcl wc2.tcl cl.tcl csa.tcl tsa.tcl
+
+all: fcl_exs
+
+fcl_exs: $(FCL_EXS)
+
+%.tcl: %.fcl
+ -$(TCL) $(FICKLE) $<
+
+clean:
+ -rm -f $(FCL_EXS:.fcl=.tcl)
+
+.PHONY: clean
diff --git a/fickle/examples/README b/fickle/examples/README
new file mode 100755
index 0000000..117e566
--- /dev/null
+++ b/fickle/examples/README
@@ -0,0 +1,102 @@
+$Id: README,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+The example fickle code programs are based upon the lex examples found
+within "lex & yacc" by John R. Levine, Tony Mason, and Doug Brown (by
+O'Reilly & Associates, ISBN 1-56592-000-7). For more information on
+using lex and yacc, see http://www.oreilly.com/catalog/lex/.
+
+Run the Makefile to generate resulting Tcl code. Descriptions of
+individual files are below. The reader is assumed to have a familiarity
+with flex; if not consider purchasing the aforementioned book.
+
+
+cat.fcl
+-------
+This is the simplest fickle example possible. It copies its input (from
+stdin) to output (stdout), much like the cat(1) program does without any
+arguments. Note that one must explicitly call yylex to run the lexer.
+
+
+verbs.fcl
+---------
+This examples demonstrates a verbatim section (the text between '%{'
+and '%}'). Also note how fickle specification files may have
+Tcl-style comments embedded within. This program searches its input
+for various English verbs and copies it to the output. The program
+makes use of the variable $yytext (really just an upvar'ed version of
+the global ::yytext variable) to print out the text that matched.
+
+
+wc.fcl
+------
+This program will count the number of characters, words, and lines in
+its input, much like wc(1). Called without any arguments, wc.tcl reads
+from stdin; it reads from a file if given an argument. Unless otherwise
+specified, 'yyin' points to stdin and 'yyout' to stdout. Overriding
+these variables forces the lexer to read from another stream. This
+program also uses $yyleng (an upvar'ed version of ::yyleng) which is
+set to [string length $::yytext].
+
+
+wc2.fcl
+-------
+This example supports multiple filenames. With more than one argument,
+wc2 will print a report for each line and a summary line totalling all
+metrics. No summary is displayed given zero or one argument.
+
+wc2 handles multiple files by overriding the definition for yywrap.
+yywrap returns 0 when additional files need to be processed.
+
+The directive `%option noheaders' causes fickle to not include
+comments about the autogenerated functions within wc2.tcl. Note the
+difference in size between wc2.tcl and wc.tcl.
+
+
+cl.fcl
+------
+This example demonstrates how to feed the lexer input from a source
+other than a file -- in this case, the command line. One must rewrite
+YY_INPUT to use the alternative source. The first parameter should be
+'upvar'ed; it holds the next string to scan. 'result' should be the
+size of the buffer, or zero to indicate an end of file; this too needs
+to be 'upvar'ed. The final parameter indicates the maximum allowed
+buffer size. By default this is 1024; use the `%option buffersize'
+directive to change this amount.
+
+Also note the use of `%option nodefault'. By default fickle will
+write to yyout any unmatched input through the ECHO function. Use
+`%option nodefault' to abort the program upon unmatched input; this is
+useful during debugging sessions. One can also invoke this
+suppression behavior with the `-s' flag on the fickle command line.
+
+
+csa.fcl
+-------
+The next example is a C source analyzer. It takes a single C source
+file as a parameter; it then counts the lines of code, comments, and
+whitespace within.
+
+This example demonstrates the start state feature of fickle, enabled
+through the directive `%option stack'. fickle supports both exclusive
+start states (as indicated by '%x') as well as regular start states
+('%s', though not featured in this program). Start states specify
+when a pattern is allowed. Switch states through calls to 'BEGIN',
+'yy_push_state', and 'yy_pop_state'.
+
+The initial state is called, not surprisingly, 'INITIAL'. Unlike flex,
+'BEGIN 0' and 'BEGIN INITIAL' are not identical. To match all states,
+prepend the pattern with '<*>'. Patterns that have no state listed are
+defaulted to matching only INITIAL and any regular start state.[*]
+
+Note that if multiple patterns match the input, the largest match takes
+precedence. In case of a tie the pattern appearing earlier within the
+specification file wins.
+
+[*] Regular start states are a source of much confusion and are rarely
+useful. Avoid them like the plague.
+
+
+tsa.fcl
+-------
+In comparison to the above this program instead analyzes Tcl code.
+It's not particularly foolproof but does get the job done.
diff --git a/fickle/examples/cat.fcl b/fickle/examples/cat.fcl
new file mode 100755
index 0000000..ebbd8e9
--- /dev/null
+++ b/fickle/examples/cat.fcl
@@ -0,0 +1,4 @@
+%%
+.|\n ECHO;
+%%
+yylex
diff --git a/fickle/examples/cl.fcl b/fickle/examples/cl.fcl
new file mode 100755
index 0000000..0949843
--- /dev/null
+++ b/fickle/examples/cl.fcl
@@ -0,0 +1,76 @@
+# $Id: cl.fcl,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+# Scans its command line for various arguments.
+
+# This is based upon example 'ape-05.l' (which is the flex version of
+# 'ch2-05.l') from "lex & yacc" by John R. Levine, Tony Mason, and
+# Doug Brown (by O'Reilly & Associates, ISBN 1-56592-000-7). For more
+# information on using lex and yacc, see
+# http://www.oreilly.com/catalog/lex/.
+
+# myinput() could have been written much more efficiently because Tcl
+# handles command line arguments as a list. For the sake of porting
+# the original example to Tcl, I used the same logic found within the
+# original flex code.
+
+%{
+#!/usr/bin/tclsh
+%}
+
+%buffersize 1024
+%option nodefault
+
+%%
+
+-h |
+-\? |
+-help {
+ puts "usage is: $::progName \[-help | -h | -? \] \[-verbose | -v \] \[(-file | -f) filename\]"
+ # actually, the -f option is not handled by this program.
+ # that is left as an exercise to the reader.
+ }
+-v |
+-verbose {
+ puts "verbose mode is on"
+ set ::verbose 1
+ }
+
+%%
+
+proc YY_INPUT {buf result max} {
+ upvar $result ret_val
+ upvar $buf buf_data
+ set ret_val [myinput buf_data $max]
+}
+
+set ::offset 0
+proc myinput {buf max} {
+ upvar $buf buf_data
+ if {[llength $::targv] == 0} {
+ # no arguments left, so return an EOF
+ return 0
+ }
+ set len [string length [lindex $::targv 0]]
+ if {$len >= $max} {
+ set copylen [expr {$max - 1}]
+ } else {
+ set copylen $len
+ }
+ if {$len > 0} {
+ set buf_data [string range [lindex $::targv 0] $::offset $copylen]
+ }
+ if {[string length [lindex $::targv 0]] >= $::offset + $copylen} {
+ append buf " "
+ incr copylen
+ set ::offset 0
+ set ::targv [lrange $::targv 1 end]
+ } else {
+ incr ::offset $copylen
+ }
+ return $copylen
+}
+
+set progName $argv0
+set verbose 0
+set ::targv $argv ;# holds remainder of argument list
+yylex
diff --git a/fickle/examples/csa.fcl b/fickle/examples/csa.fcl
new file mode 100755
index 0000000..3cd6dc3
--- /dev/null
+++ b/fickle/examples/csa.fcl
@@ -0,0 +1,65 @@
+# $Id: csa.fcl,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+# Counts the lines of comments, code, and whitespace within a C
+# program.
+
+# This is based upon example 'ch2-09.l' from "lex & yacc" by John
+# R. Levine, Tony Mason, and Doug Brown (by O'Reilly & Associates, ISBN
+# 1-56592-000-7). For more information on using lex and yacc, see
+# http://www.oreilly.com/catalog/lex/.
+
+%{
+#!/usr/bin/tclsh
+
+set comments 0
+set code 0
+set whitespace 0
+
+proc update_count { a b c } {
+ incr ::comments $a
+ incr ::code $b
+ incr ::whitespace $c
+ puts -nonewline "code: $::code, comments: $::comments, whitespace: $::whitespace\r"
+ flush stdout
+}
+
+%}
+
+%option noheaders stack nodefault
+%x COMMENT
+
+%%
+
+^[ \t]*"/*" { BEGIN COMMENT }
+^[ \t]*"/*".*"*/"[ \t]*\n { update_count 1 0 0 }
+<COMMENT>"*/"[ \t]*\n { BEGIN INITIAL; update_count 1 0 0 }
+<COMMENT>"*/" { BEGIN INITIAL }
+<COMMENT>\n { update_count 1 0 0 }
+<COMMENT>.\n { update_count 1 0 0 }
+
+^[ \t]*\n { update_count 0 0 1 }
+
+.+"/*".*"*/".*\n { update_count 0 1 0 }
+.*"/*".*"*/".+\n { update_count 0 1 0 }
+.+"/*".*\n { BEGIN COMMENT; update_count 0 1 0 }
+.\n { update_count 0 1 0 }
+
+<*>. # do nothing
+
+%%
+
+if {[llength $argv] == 0} {
+ puts stderr "C source analyzer needs a filename."
+ exit 0
+}
+
+if {[catch {open [lindex $argv 0] r} yyin]} {
+ puts stderr "Could not open [lindex $argv 0]"
+ exit 0
+}
+
+yylex
+
+close $yyin
+
+puts ""
diff --git a/fickle/examples/tsa.fcl b/fickle/examples/tsa.fcl
new file mode 100755
index 0000000..86a3733
--- /dev/null
+++ b/fickle/examples/tsa.fcl
@@ -0,0 +1,79 @@
+#$Id: tsa.fcl,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+# Counts lines of comments, logical lines of code, and function
+# invocations in Tcl code.
+
+# The patterns can handle most "normal" Tcl code. There are some
+# instances where it will not correctly detect a function call.
+
+%{
+#!/usr/bin/tclsh
+
+proc found_func {funcname} {
+ if [info exist ::func($funcname)] {
+ incr ::func($funcname)
+ } else {
+ set ::func($funcname) 1
+ }
+}
+
+proc spin {} {
+ if {$::numlines % 8 == 0} {
+ puts -nonewline "."
+ flush stdout
+ }
+}
+
+set comments 0
+set numlines 0
+set spinner_count 0
+
+%}
+
+%option stack
+%x ARG
+
+%%
+
+<*>^\s*\n { incr ::numlines; spin }
+<*>;?\s*#.*\n { incr ::comments; incr ::numlines; spin }
+<*>\n { yy_pop_state; incr ::numlines; spin }
+<*>\s # ignore whitespace
+<*>\\(.|\n) # ignore escaped characters
+<*>\d+ # numbers are ignored
+<INITIAL>\w+ { found_func $yytext; yy_push_state ARG }
+<ARG>\w+ # ignore arguments
+<*>\[\s*\w+ { set start [string first "\[" $yytext]
+ set func [string range $yytext [expr {$start + 1}] end]
+ found_func [string trim $func]
+ yy_push_state ARG }
+<ARG>\] { yy_pop_state }
+<*>; { yy_pop_state }
+<*>. # unknown character; ignore it
+
+%%
+
+# start of main
+if {[llength $argv] > 0} {
+ if {[catch {open [lindex $argv 0]} yyin]} {
+ puts stderr "could not open file"
+ exit 0
+ }
+}
+
+yylex
+
+if {[llength $argv] > 0} {
+ close $yyin
+}
+
+puts ""
+puts "Comments: $comments"
+puts "Num lines: $numlines"
+puts "Function calls:"
+parray func
+set totalcalls 0
+foreach {name calls} [array get func] {
+ incr totalcalls $calls
+}
+puts "Total calls: $totalcalls"
diff --git a/fickle/examples/verbs.fcl b/fickle/examples/verbs.fcl
new file mode 100755
index 0000000..c9317ae
--- /dev/null
+++ b/fickle/examples/verbs.fcl
@@ -0,0 +1,34 @@
+# $Id: verbs.fcl,v 1.1.1.1 2004/07/23 19:22:41 tang Exp $
+
+# Recognizes various English verbs in sentences.
+
+# This is based upon example 'ch1-02.l' from "lex & yacc" by John
+# R. Levine, Tony Mason, and Doug Brown (by O'Reilly & Associates, ISBN
+# 1-56592-000-7). For more information on using lex and yacc, see
+# http://www.oreilly.com/catalog/lex/.
+
+%{
+#!/usr/bin/tclsh
+%}
+
+%%
+[\t ]+ # ignore whitespace
+is |
+am |
+are |
+were |
+was |
+be |
+being |
+been |
+do |
+does |
+did |
+will puts "$yytext: is a verb"
+[a-zA-Z]+ puts "$yytext: is not a verb"
+
+.|\n ECHO ;# normal default anyway
+
+%%
+
+yylex
diff --git a/fickle/examples/wc.fcl b/fickle/examples/wc.fcl
new file mode 100755
index 0000000..871b3ad
--- /dev/null
+++ b/fickle/examples/wc.fcl
@@ -0,0 +1,40 @@
+%{
+#!/usr/bin/tclsh
+
+# Counts characters, words, and lines within its input.
+
+# This is based upon example 'ch2-02.l' from "lex & yacc" by John
+# R. Levine, Tony Mason, and Doug Brown (by O'Reilly & Associates, ISBN
+# 1-56592-000-7). For more information on using lex and yacc, see
+# http://www.oreilly.com/catalog/lex/.
+
+set charCount 0
+set wordCount 0
+set lineCount 0
+
+%}
+
+word [^ \t\n]+
+eol \n
+
+%%
+
+{word} { incr ::wordCount; incr ::charCount $yyleng }
+{eol} { incr ::charCount; incr ::lineCount }
+. { incr ::charCount }
+
+%%
+
+if {[llength $argv] > 0} {
+ if {[catch {open [lindex $argv 0]} f]} {
+ puts stderr "could not open file [lindex $argv 0]"
+ exit 1
+ }
+ set yyin $f
+}
+
+yylex
+
+puts "$charCount $wordCount $lineCount"
+
+return 0
diff --git a/fickle/examples/wc2.fcl b/fickle/examples/wc2.fcl
new file mode 100755
index 0000000..e887003
--- /dev/null
+++ b/fickle/examples/wc2.fcl
@@ -0,0 +1,102 @@
+%{
+#!/usr/bin/tclsh
+
+# Counts characters, words, and lines, with support for multiple
+# filenames.
+
+# This is based upon example 'ch2-03.l' from "lex & yacc" by John
+# R. Levine, Tony Mason, and Doug Brown (by O'Reilly & Associates, ISBN
+# 1-56592-000-7). For more information on using lex and yacc, see
+# http://www.oreilly.com/catalog/lex/.
+
+set charCount 0
+set wordCount 0
+set lineCount 0
+
+%}
+
+%option noheaders
+
+word [^ \t\n]+
+eol \n
+
+%%
+
+{word} { incr ::wordCount; incr ::charCount $yyleng }
+{eol} { incr ::charCount; incr ::lineCount }
+. { incr ::charCount }
+
+%%
+
+# lexer calls yywrap to handle EOF conditions (e.g., to
+# connect to a new file, as we do in this case.)
+proc yywrap {} {
+ set file ""
+ if {$::currentFile != 0 && $::nFiles > 1 && $::currentFile < $::nFiles} {
+ # print out statstics for previous file
+ puts [format "%8u %8u %8u %s" $::lineCount $::wordCount $::charCount \
+ [lindex $::fileList [expr {$::currentFile - 1}]]]
+ incr ::totalCC $::charCount
+ incr ::totalWC $::wordCount
+ incr ::totalLC $::lineCount
+ set ::charCount 0
+ set ::wordCount 0
+ set ::lineCount 0
+ close $::yyin
+ }
+ while {$::currentFile < $::nFiles} {
+ if {[catch {open [lindex $::fileList $::currentFile] r} file]} {
+ puts stderr "could not open [lindex $::fileList $::currentFile]"
+ incr ::currentFile
+ } else {
+ set ::yyin $file
+ incr ::currentFile
+ break
+ }
+ }
+ if {$file != ""} {
+ return 0 ;# 0 means there's more input
+ } else {
+ return 1
+ }
+}
+
+set fileList ""
+set currentFile 0
+set nFiles 0
+set totalCC 0
+set totalWC 0
+set totalLC 0
+
+set fileList $argv
+set nFiles [llength $argv]
+
+if {[llength $argv] == 1} {
+ # handle single file case differenly since we don't need to print a
+ # summary line
+ set currentFile 1
+ if {[catch {open [lindex $argv 0] r} file]} {
+ puts stderr "could not open file [lindex $argv 0]"
+ exit 1
+ }
+ set yyin $file
+}
+if {[llength $argv] > 1} {
+ yywrap
+}
+
+yylex
+
+# handle zero or one file differently from multiple files
+if {[llength $argv] > 1} {
+ puts [format "%8u %8u %8u %s" $lineCount $wordCount $charCount \
+ [lindex $argv [expr {$currentFile - 1}]]]
+ incr totalCC $charCount
+ incr totalWC $wordCount
+ incr totalLC $lineCount
+ puts [format "%8u %8u %8u total" $totalLC $totalWC $totalCC]
+} else {
+ puts [format "%8u %8u %8u" $lineCount $wordCount $charCount]
+}
+
+return 0
diff --git a/fickle/fickle.tcl b/fickle/fickle.tcl
new file mode 100755
index 0000000..8321451
--- /dev/null
+++ b/fickle/fickle.tcl
@@ -0,0 +1,906 @@
+#!/usr/bin/tclsh
+
+# $Id: fickle.tcl,v 1.6 2004/11/14 02:36:28 tang Exp $
+
+set FICKLE_VERSION 2.04
+
+#//#
+# Fickle is a lexical analyzer generator written in pure Tcl. It
+# reads a <em>fickle specification file</em> to generate pure Tcl code
+# that implements a scanner. See the {@link README} file for complete
+# instructions. Additional information may be found at {@link
+# http://mini.net/tcl/fickle}.
+#
+# @author Jason Tang (tang@jtang.org)
+# @version 2.04
+#//#
+
+# Process a definition / directive on a single line.
+proc handle_defs {line} {
+ # trim whitespace and remove any comments
+ set line [strip_comments [string trim $line]]
+ if {$line == ""} {
+ return
+ }
+ if {$line == "%\{"} {
+ handle_literal_block
+ } else {
+ # extract the keyword to the left of the first space and the
+ # arguments (if any) to the right
+ if {[regexp -line {^(\S+)\s+(.*)} $line foo keyword args] == 0} {
+ set keyword $line
+ set args ""
+ }
+ switch -- $keyword {
+ "%s" {
+ foreach state_name [split $args] {
+ if {$state_name != ""} {
+ set ::state_table($state_name) $::INCLUSIVE
+ }
+ }
+ }
+ "%x" {
+ foreach state_name [split $args] {
+ if {$state_name != ""} {
+ set ::state_table($state_name) $::EXCLUSIVE
+ }
+ }
+ }
+ "%option" {
+ handle_options $args
+ }
+ "%buffersize" {
+ if {$args == ""} {
+ fickle_error "%buffersize must have an integer parameter" $::PARAM_ERROR
+ } elseif {[string is digit $args] && $args > 0} {
+ set ::BUFFER_SIZE $args
+ } else {
+ fickle_error "%buffersize parameter must be positive integer" $::PARAM_ERROR
+ }
+ }
+ default {
+ # check if the directive is an option or a substitution
+ if {[string index $keyword 0] == "%"} {
+ fickle_error "Unknown directive \"$keyword\"" $::SYNTAX_ERROR
+ } else {
+ add_definition $line
+ }
+ }
+ }
+ }
+}
+
+# Copy everything between ^%\{$ to ^%\}$ to the destination file.
+proc handle_literal_block {} {
+ set end_defs 0
+ while {$end_defs == 0} {
+ if {[gets $::src line] < 0} {
+ fickle_error "No terminator to verbatim section found " $::SYNTAX_ERROR
+ } elseif {[string trim $line] == "%\}"} {
+ set end_defs 1
+ } else {
+ puts $::dest $line
+ }
+ incr ::line_count
+ }
+}
+
+# Examine each option (given by a %option directive) and set/unset
+# flags as necessary.
+proc handle_options {optargs} {
+ foreach option [split $optargs] {
+ if {$option == ""} {
+ continue
+ }
+ if {$option == "default"} {
+ # special construct to handle %option default (because I
+ # can't match this in the switch statement below
+ set ::suppress 0
+ continue
+ }
+ switch -- $option {
+ "caseful" - "case-sensitive" -
+ "nocaseless" - "nocase-insensitive" { set ::nocase 0 }
+ "caseless" - "case-insensitive" -
+ "nocaseful" - "nocase-sensitive" { set ::nocase 1 }
+ "debug" { set ::debugmode 1 }
+ "nodebug" { set ::debugmode 0 }
+ "nodefault" { set ::suppress 1 }
+ "interactive" { set ::interactive 1 }
+ "nointeractive" { set ::interactive 0 }
+ "verbose" { set ::verbose 1 }
+ "noverbose" { set ::verbose 0 }
+ "stack" { set ::startstates 1 }
+ "nostack" { set ::startstates 0 }
+ "yylineno" { set ::linenums 1 }
+ "noyylineno" { set ::linenums 0 }
+ "yywrap" { set ::callyywrap 1 }
+ "noyywrap" { set ::callyywrap 0 }
+ "headers" { set ::headers 1 }
+ "noheaders" { set ::headers 0 }
+ default {
+ # note this is /not/ the same as %option default (see above)
+ fickle_error "Unknown %option $option" $::PARAM_ERROR
+ }
+
+ }
+ }
+}
+
+# Adds a definition to the substition table.
+proc add_definition {line} {
+ if {![regexp -line -- {\A\s*([a-zA-Z_]\S*)\s+(.+)} $line foo name pattern]} {
+ fickle_error "Malformed definition" $::SYNTAX_ERROR
+ }
+ # make any substitutions within the pattern now
+ foreach {sub_rule sub_pat} [array get ::sub_table] {
+ # the quotes around the regexp below is necessary, to
+ # allow for substitution of the sub_rule
+ regsub -all -- "\{$sub_rule\}" $pattern "\($sub_pat\)" pattern
+ }
+ # double the backslashes (during the next round of substitution
+ # the extras will go away)
+ regsub -all -- {\\} $pattern {\\\\} pattern
+ set ::sub_table($name) $pattern
+}
+
+# Actually build the scanner given a set of pattern / action pairs.
+proc build_scanner {rules_buf} {
+ # step 0: parse the rules buffer into individual rules and actions
+ handle_rules_buf $rules_buf
+
+ if $::interactive {
+ set ::BUFFER_SIZE 1
+ }
+
+ # step 1: write scanner support functions
+ write_scanner_utils
+
+ # step 2: write the scanner to the destination file
+ write_scanner
+}
+
+# Scan though the rules buffer, pulling out each pattern / action pair.
+proc handle_rules_buf {rules_buf} {
+ set regexp_list ""
+ set num_rules 0
+ while {[string length $rules_buf] > 0} {
+ set line_start $::line_count
+ # remove the next line from the buffer
+ regexp -line -- {\A(.*)\n?} $rules_buf foo line
+ set rules_buf [string range $rules_buf [string length $foo] end]
+ # consume blank lines
+ if {[string trim $line] == ""} {
+ incr ::line_count
+ continue
+ }
+ # extract the left hand side
+ if {![regexp -line -- {\A\s*(\S+)(.*)} $line foo pattern line]} {
+ fickle_error "No pattern found" $::SYNTAX_ERROR
+ }
+ # the pattern may contain spaces; use [info complete] to keep
+ # appending to it
+ set pattern_done 0
+ while {!$pattern_done && $line != ""} {
+ if [info complete $pattern] {
+ set pattern_done 1
+ } else {
+ regexp -- {\A(\S*\s?)(.*)} $line foo p line
+ append pattern $p
+ }
+ }
+ if {!$pattern_done} {
+ fickle_error "Pattern appears to be unterminated" $::SYNTAX_ERROR
+ }
+ set pattern [rewrite_pattern [string trim $pattern]]
+ set orig_pattern $pattern
+
+ # check the pattern to see if it has a start state
+ set state_name ""
+ if [regexp -- {\A<([^>]+)>} $pattern foo state_name] {
+ if {!$::startstates} {
+ fickle_error "Start state specified, but states were not enabled with `%option stack'" $::GRAMMAR_ERROR
+ }
+ # a state was found; remove it from the pattern
+ regsub -- {\A<[^>]+>} $pattern "" pattern
+ # check that the state was declared
+ if {$state_name != "*" && ![info exists ::state_table($state_name)]} {
+ fickle_error "Undeclared start state $state_name" $::GRAMMAR_ERROR
+ }
+ }
+ # check if any textual substitutions are needed
+ foreach sub_rule [array names ::sub_table] {
+ # the quotes around the regexp below is necessary, to
+ # allow for substitution of the sub_rule
+ regsub -all -- "\{$sub_rule\}" $pattern "\($::sub_table($sub_rule)\)" pattern
+ }
+
+ # now determine the action; an action of just a vertical bar
+ # means to use the subsequent action
+ set action [string trimleft $line]
+ if {[string trim $action] == ""} {
+ fickle_error "Rule has no associated action" $::SYNTAX_ERROR
+ } elseif {[string trim $action] == "|"} {
+ # blank action means to use next one
+ set action ""
+ } else {
+ # keep scanning through buffer until action is complete
+ set num_lines 0
+ set action_done 0
+ while {!$action_done && $rules_buf != ""} {
+ if [info complete $action] {
+ set action_done 1
+ } else {
+ regexp -line -- {\A(.*)\n?} $rules_buf foo line
+ set rules_buf [string range $rules_buf [string length $foo] end]
+ append action "\n$line"
+ incr num_lines
+ }
+ }
+ if {!$action_done && ![info complete $action]} {
+ fickle_error "Unterminated action" $::SYNTAX_ERROR
+ }
+ # clean up the action, especially if it had curly braces
+ # around the ends
+ set action [string trim $action]
+ if {[string index $action 0] == "{" && \
+ [string index $action end] == "}"} {
+ set action [string trim [string range $action 1 end-1]]
+ }
+ incr ::line_count $num_lines
+ }
+ lappend ::rule_table [list $orig_pattern $state_name $pattern $action $line_start]
+ incr ::line_count
+ if $::verbose {
+ if {$state_name == ""} {
+ set state "default state"
+ } else {
+ set state "state $state_name"
+ }
+ if {$action == ""} {
+ set action "<fallthrough>"
+ }
+ puts stderr "Rule $num_rules: \[$pattern\] ($state) -> $action"
+ incr num_rules
+ }
+ }
+}
+
+# Tcl style regexps are not 100% compatible with flex, so rewrite them
+# here.
+proc rewrite_pattern {pattern} {
+ set in_quotes 0
+ set in_brackets 0
+ set in_escape 0
+ foreach c [split $pattern {}] {
+ if $in_escape {
+ append newpattern $c
+ set in_escape 0
+ continue
+ }
+ if $in_quotes {
+ if {$c == "\""} {
+ set in_quotes 0
+ } else {
+ # metacharacters lose their meaning within quotes
+ if [regexp -- {[.*\[\]^$\{\}+?|/\(\)]} $c foo] {
+ append newpattern "\\"
+ }
+ append newpattern $c
+ }
+ continue
+ }
+ switch -- $c {
+ "\\" { append newpattern "\\"; set in_escape 1 }
+ "\[" { append newpattern "\["; incr in_brackets }
+ "\]" { append newpattern "\]"; incr in_brackets -1 }
+ "\"" {
+ if $in_brackets {
+ append newpattern "\\\""
+ } else {
+ set in_quotes 1
+ }
+ }
+ default {
+ append newpattern $c
+ }
+ }
+ }
+ return $newpattern
+}
+
+######################################################################
+# procedure to write scanner
+
+# Writes all of the support procedures needed by the scanner during
+# run time.
+proc write_scanner_utils {} {
+ puts $::dest "
+######
+# Begin autogenerated fickle (version $::FICKLE_VERSION) routines.
+# Although fickle itself is protected by the GNU Public License (GPL)
+# all user-supplied functions are protected by their respective
+# author's license. See http://mini.net/tcl/fickle for other details.
+######
+"
+ if $::callyywrap {
+ if $::headers {
+ puts $::dest "# If ${::p}wrap() returns false (zero), then it is assumed that the
+# function has gone ahead and set up ${::p}in to point to another input
+# file, and scanning continues. If it returns true (non-zero), then
+# the scanner terminates, returning 0 to its caller. Note that in
+# either case, the start condition remains unchanged; it does not
+# revert to INITIAL.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}wrap \{\} \{
+ return 1
+\}
+"
+ }
+ if $::headers {
+ puts $::dest "# ECHO copies ${::p}text to the scanner's output if no arguments are
+# given. The scanner writes its ECHO output to the ${::p}out global
+# (default, stdout), which may be redefined by the user simply by
+# assigning it to some other channel.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ECHO \{\{s \"\"\}\} \{
+ if \{\$s == \"\"\} \{
+ puts -nonewline \$::${::p}out \$::${::p}text
+ \} else \{
+ puts -nonewline \$::${::p}out \$s
+ \}
+\}
+"
+ if $::headers {
+ puts $::dest "# ${::P}_FLUSH_BUFFER flushes the scanner's internal buffer so that the
+# next time the scanner attempts to match a token, it will first
+# refill the buffer using ${::P}_INPUT.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::P}_FLUSH_BUFFER \{\} \{
+ set ::${::p}_buffer \"\"
+ set ::${::p}_index 0
+ set ::${::p}_done 0
+\}
+"
+ if $::headers {
+ puts $::dest "# ${::p}restart(new_file) may be called to point ${::p}in at the new input
+# file. The switch-over to the new file is immediate (any previously
+# buffered-up input is lost). Note that calling ${::p}restart with ${::p}in
+# as an argument thus throws away the current input buffer and
+# continues scanning the same input file.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}restart \{new_file\} \{
+ set ::${::p}in \$new_file
+ ${::P}_FLUSH_BUFFER
+\}
+"
+ if $::headers {
+ puts $::dest "# The nature of how it gets its input can be controlled by defining
+# the ${::P}_INPUT macro. ${::P}_INPUT's calling sequence is
+# \"${::P}_INPUT(buf,result,max_size)\". Its action is to place up to
+# max_size characters in the character array buf and return in the
+# integer variable result either the number of characters read or the
+# constant ${::P}_NULL (0 on Unix systems) to indicate EOF. The default
+# ${::P}_INPUT reads from the global file-pointer \"${::p}in\".
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::P}_INPUT \{buf result max_size\} \{
+ upvar \$result ret_val
+ upvar \$buf new_data
+ if \{\$::${::p}in != \"\"\} \{"
+ if $::interactive {
+ puts $::dest " gets \$::${::p}in new_data
+ if \{!\[eof \$::${::p}in\]\} \{
+ append new_data \\n
+ \}"
+ } else {
+ puts $::dest " set new_data \[read \$::${::p}in \$max_size\]"
+ }
+ puts $::dest " set ret_val \[string length \$new_data\]
+ \} else \{
+ set new_data \"\"
+ set ret_val 0
+ \}
+\}
+"
+ if $::headers {
+ puts $::dest "# yy_scan_string sets up input buffers for scanning in-memory
+# strings instead of files. Note that switching input sources does
+# not change the start condition.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}_scan_string \{str\} \{
+ append ::${::p}_buffer \$str
+ set ::${::p}in \"\"
+\}
+"
+ if $::headers {
+ puts $::dest "# unput(c) puts the character c back onto the input stream. It will
+# be the next character scanned.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc unput \{c\} \{
+ set s \[string range \$::${::p}_buffer 0 \[expr \{\$::${::p}_index - 1\}\]\]
+ append s \$c
+ set ::${::p}_buffer \[append s \[string range \$::${::p}_buffer \$::${::p}_index end\]\]
+\}
+"
+ if $::headers {
+ puts $::dest "# Returns all but the first n characters of the current token back to
+# the input stream, where they will be rescanned when the scanner
+# looks for the next match. ${::p}text and ${::p}leng are adjusted
+# appropriately.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}less \{n\} \{
+ set s \[string range \$::${::p}_buffer 0 \[expr \{\$::${::p}_index - 1\}\]\]
+ append s \[string range \$::${::p}text \$n end\]
+ set ::${::p}_buffer \[append s \[string range \$::${::p}_buffer \$::${::p}_index end\]\]
+ set ::${::p}text \[string range \$::${::p}text 0 \[expr \{\$n - 1\}\]\]
+ set ::${::p}leng \[string length \$::${::p}text\]
+\}
+"
+ if $::headers {
+ puts $::dest "# input() reads the next character from the input stream.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc input \{\} \{
+ if \{\[string length \$::${::p}_buffer\] - \$::${::p}_index < $::BUFFER_SIZE\} \{
+ set new_buffer_size 0
+ if \{\$::${::p}_done == 0\} \{
+ ${::P}_INPUT new_buffer new_buffer_size $::BUFFER_SIZE
+ append ::${::p}_buffer \$new_buffer
+ if \{\$new_buffer_size == 0\} \{
+ set ::${::p}_done 1
+ \}
+ \}
+ if \$::${::p}_done \{"
+ if $::callyywrap {
+ puts -nonewline $::dest " if \{\[${::p}wrap\] == 0\} \{
+ return \[input\]
+ \} else"
+ } else {
+ puts -nonewline $::dest " "
+ }
+ puts $::dest "if \{\[string length \$::${::p}_buffer\] - \$::${::p}_index == 0\} \{
+ return \{\}
+ \}
+ \}
+ \}
+ set c \[string index \$::${::p}_buffer \$::${::p}_index\]
+ incr ::${::p}_index
+ return \$c
+\}
+"
+ if $::startstates {
+ if $::headers {
+ puts $::dest "# Pushes the current start condition onto the top of the start
+# condition stack and switches to new_state as though you had used
+# BEGIN new_state.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}_push_state \{new_state\} \{
+ lappend ::${::p}_state_stack \$new_state
+\}
+"
+ if $::headers {
+ puts $::dest "# Pops off the top of the state stack; if the stack is now empty, then
+# pushes the state \"INITIAL\".
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}_pop_state \{\} \{
+ set ::${::p}_state_stack \[lrange \$::${::p}_state_stack 0 end-1\]
+ if \{\$::${::p}_state_stack == \"\"\} \{
+ ${::p}_push_state INITIAL
+ \}
+\}
+"
+ if $::headers {
+ puts $::dest "# Returns the top of the stack without altering the stack's contents.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc ${::p}_top_state \{\} \{
+ return \[lindex \$::${::p}_state_stack end\]
+\}
+"
+ if $::headers {
+ puts $::dest "# BEGIN followed by the name of a start condition places the scanner
+# in the corresponding start condition. . . .Until the next BEGIN
+# action is executed, rules with the given start condition will be
+# active and rules with other start conditions will be inactive. If
+# the start condition is inclusive, then rules with no start
+# conditions at all will also be active. If it is exclusive, then
+# only rules qualified with the start condition will be active.
+# -- from the flex(1) man page"
+ }
+ puts $::dest "proc BEGIN \{new_state\ \{prefix $::p\}\} \{
+ eval set ::\${prefix}_state_stack \[lrange \\\$::\${prefix}_state_stack 0 end-1\]
+ eval lappend ::\${prefix}_state_stack \$new_state
+\}
+"
+ }
+
+ puts $::dest "# initialize values used by the lexer
+set ::${::p}text {}
+set ::${::p}leng 0
+set ::${::p}_buffer \{\}
+set ::${::p}_index 0
+set ::${::p}_done 0"
+ if $::startstates {
+ puts $::dest "set ::${::p}_state_stack \{\}
+BEGIN INITIAL
+array set ::${::p}_state_table \{[array get ::state_table]\}"
+ }
+ if $::linenums {
+ puts $::dest "set ::${::p}lineno 1"
+ }
+ if $::debugmode {
+ puts $::dest "set ::${::p}_flex_debug 1"
+ }
+ puts $::dest "if \{!\[info exists ::${::p}in\]\} \{
+ set ::${::p}in \"stdin\"
+\}
+if \{!\[info exists ::${::p}out\]\} \{
+ set ::${::p}out \"stdout\"
+\}
+"
+}
+
+
+# Writes the actual scanner as a function called <code>yylex</code>.
+# Note that this function may be renamed if the <code>-P</code> flag
+# was given at the command line.
+proc write_scanner {} {
+ puts $::dest "######
+# autogenerated ${::p}lex function created by fickle
+######
+
+# Whenever yylex() is called, it scans tokens from the global input
+# file yyin (which defaults to stdin). It continues until it either
+# reaches an end-of-file (at which point it returns the value 0) or
+# one of its actions executes a return statement.
+# -- from the flex(1) man page
+proc ${::p}lex \{\} \{
+ upvar #0 ::${::p}text ${::p}text
+ upvar #0 ::${::p}leng ${::p}leng
+ while \{1\} \{"
+ if $::startstates {
+ puts $::dest " set ${::p}_current_state \[${::p}_top_state\]"
+ }
+ puts $::dest " if \{\[string length \$::${::p}_buffer\] - \$::${::p}_index < $::BUFFER_SIZE\} \{
+ if \{\$::${::p}_done == 0\} \{
+ set ${::p}_new_buffer \"\"
+ ${::P}_INPUT ${::p}_new_buffer ${::p}_buffer_size $::BUFFER_SIZE
+ append ::${::p}_buffer \$${::p}_new_buffer
+ if \{\$${::p}_buffer_size == 0 && \\
+ \[string length \$::${::p}_buffer\] - \$::${::p}_index == 0\} \{
+ set ::${::p}_done 1
+ \}
+ \}
+ if \$::${::p}_done \{"
+ if $::debugmode {
+ puts $::dest " if \$::${::p}_flex_debug \{
+ puts stderr \" --reached end of input buffer\"
+ \}"
+ }
+ if $::callyywrap {
+ puts -nonewline $::dest " if \{\[${::p}wrap\] == 0\} \{
+ set ::${::p}_done 0
+ continue
+ \} else"
+ } else {
+ puts -nonewline $::dest " "
+ }
+ puts $::dest "if \{\[string length \$::${::p}_buffer\] - \$::${::p}_index == 0\} \{
+ break
+ \}
+ \}
+ \}
+ set ::${::p}leng 0
+ set ${::p}_matched_rule -1"
+
+ # build up the if statements to determine which rule to execute;
+ # lex is greedy and will use the rule that matches the most
+ # strings
+ if {$::nocase} {
+ set scan_args "-nocase"
+ } else {
+ set scan_args ""
+ }
+ set rule_num 0
+ foreach rule $::rule_table {
+ foreach {orig_pattern state_name pattern action rule_line} $rule {}
+ puts $::dest " # rule $rule_num: $orig_pattern"
+ puts -nonewline $::dest " if \{"
+ if $::startstates {
+ if {$state_name == ""} {
+ puts -nonewline $::dest "\$::${::p}_state_table(\$${::p}_current_state) && \\\n "
+ } elseif {$state_name != "*"} {
+ puts -nonewline $::dest "\$${::p}_current_state == \"$state_name\" && \\\n "
+ }
+ }
+ puts $::dest "\[regexp -start \$::${::p}_index -indices -line $scan_args -- \{\\A($pattern)\} \$::${::p}_buffer ${::p}_match\] > 0\ && \\
+ \[lindex \$${::p}_match 1\] - \$::${::p}_index + 1 > \$::${::p}leng\} \{
+ set ::${::p}text \[string range \$::${::p}_buffer \$::${::p}_index \[lindex \$${::p}_match 1\]\]
+ set ::${::p}leng \[string length \$::${::p}text\]
+ set ${::p}_matched_rule $rule_num"
+ if $::debugmode {
+ puts $::dest " set ${::p}rule_num \"rule at line $rule_line\""
+ }
+ puts $::dest " \}"
+ incr rule_num
+ }
+ # now add the default case
+ puts $::dest " if \{\$${::p}_matched_rule == -1\} \{
+ set ::${::p}text \[string index \$::${::p}_buffer \$::${::p}_index\]
+ set ::${::p}leng 1"
+ if $::debugmode {
+ puts $::dest " set ${::p}rule_num \"default rule\""
+ }
+ puts $::dest " \}
+ incr ::${::p}_index \$::${::p}leng
+ # workaround for Tcl's circumflex behavior
+ if \{\[string index \$::${::p}text end\] == \"\\n\"\} \{
+ set ::${::p}_buffer \[string range \$::${::p}_buffer \$::${::p}_index end\]
+ set ::${::p}_index 0
+ \}"
+ if $::debugmode {
+ puts $::dest " if \$::${::p}_flex_debug \{
+ puts stderr \" --accepting \$${::p}rule_num (\\\"\$::${::p}text\\\")\"
+ \}"
+ }
+ if $::linenums {
+ puts $::dest " set numlines \[expr \{\[llength \[split \$::${::p}text \"\\n\"\]\] - 1\}\]"
+ }
+ puts $::dest " switch -- \$${::p}_matched_rule \{"
+ set rule_num 0
+ foreach rule $::rule_table {
+ puts -nonewline $::dest " $rule_num "
+ if {[string length [lindex $rule 3]] == 0} {
+ # action is empty, so use next pattern's action
+ puts $::dest "-"
+ } else {
+ puts $::dest "\{\n[lindex $rule 3]\n \}"
+ }
+ incr rule_num
+ }
+ puts $::dest " default"
+ if {$::suppress == 0} {
+ puts $::dest " \{ ECHO \}"
+ } else {
+ puts -nonewline $::dest " \{ puts stderr \"unmatched token: \$::${::p}text"
+ if $::startstates {
+ puts -nonewline $::dest " in state `\$${::p}_current_state'"
+ }
+ puts $::dest "\"; exit -1 \}"
+ }
+ puts $::dest " \}"
+ if $::linenums {
+ puts $::dest " incr ::${::p}lineno \$numlines"
+ }
+ puts $::dest " \}
+ return 0
+\}
+######
+# end autogenerated fickle functions
+######
+"
+}
+
+######################################################################
+# utility functions
+
+# Given a line, returns a new line with any comments removed.
+proc strip_comments {line} {
+ regexp -- {\A([^\#]*)} $line foo line
+ return $line
+}
+
+# If the first non-whitespace character on a line is a hash, then
+# return an empty string; otherwise return the entire line.
+proc strip_only_comments {line} {
+ if [regexp -- {\A\s*\#} $line] {
+ return ""
+ } else {
+ return $line
+ }
+}
+
+# Retrives a parameter from the options list. If no parameter exists
+# then abort with an error very reminisicent of C's
+# <code>getopt</code> function; otherwise increment
+# <code>param_num</code> by one.
+#
+# @param param_list list of parameters from the command line
+# @param param_num index into <code>param_list</code> to retrieve
+# @param param_name name of the parameter, used when reporting an error
+# @return the <code>$param_num</code>'th element into <code>$param_list</code>
+proc get_param {param_list param_num param_name} {
+ upvar $param_num pn
+ incr pn
+ if {$pn >= [llength $param_list]} {
+ puts stderr "fickle: option requires an argument -- $param_name"
+ exit $::PARAM_ERROR
+ }
+ return [lindex $param_list $pn]
+}
+
+# Display an error message to standard error along with where within
+# the specification file it occurred. Then abort this program.
+proc fickle_error {message returnvalue} {
+ puts -nonewline stderr $message
+ puts stderr " (line $::line_count)"
+ exit $returnvalue
+}
+
+# Display to a channel a brief summary of fickle command line options.
+proc print_fickle_help {chan} {
+ puts $chan "fickle: a Tcl lexical anaylzer generator
+Usage: fickle \[options\] \[FILE\]
+ FILE a fickle specification file
+
+Options:
+ -h print this help message and quit
+ -v be verbose while generating scanner
+ -o FILE specify name to write scanner
+ -d enable debug mode while running scanner
+ -i generate a case-insensitive scanner
+ -l keep track of line numbers in global variable yylineno
+ -s suppress default rule; unmatched input aborts with errors
+ -t write scanner to standard output
+ -I read input interactively
+ -P PREFIX change default yy prefix to PREFIX
+ --version print fickle version and quit
+
+For more information see http://mini.net/tcl/fickle"
+}
+
+# Displays to standard out the fickle version, then exits program.
+proc print_fickle_version {} {
+ puts "fickle version $::FICKLE_VERSION"
+ exit 0
+}
+
+######################################################################
+# other fickle functions
+
+# Parse the command line and set all global options.
+proc fickle_args {argv} {
+ set argvp 0
+ set out_filename ""
+ set write_to_stdout 0
+ set ::callyywrap 1
+ set ::debugmode 0
+ set ::headers 1
+ set ::interactive 0
+ set ::nocase 0
+ set ::linenums 0
+ set ::startstates 0
+ set ::suppress 0
+ set ::BUFFER_SIZE 1024
+ set ::p "yy"
+ set ::P "YY"
+ set ::verbose 0
+ while {$argvp < [llength $argv]} {
+ set arg [lindex $argv $argvp]
+ switch -- $arg {
+ "-d" { set ::debugmode 1 }
+ "-h" - "--help" { print_fickle_help stdout; exit 0 }
+ "-i" { set ::nocase 1 }
+ "-l" { set ::linenums 1 }
+ "-o" { set out_filename [get_param $argv argvp "o"] }
+ "-s" { set ::suppress 1 }
+ "-t" { set write_to_stdout 1 }
+ "-v" { set ::verbose 1 }
+ "-I" { set ::interactive 1 }
+ "-P" {
+ set prefix [get_param $argv argvp "P"]
+ set ::p [string tolower $prefix]
+ set ::P [string toupper $prefix]
+ }
+ "--version" { print_fickle_version }
+ default {
+ if {[string index $arg 0] != "-"} {
+ break
+ } else {
+ puts stderr "fickle: unknown option $arg"
+ print_fickle_help stderr
+ exit $::PARAM_ERROR
+ }
+ }
+ }
+ incr argvp
+ }
+ if {$argvp >= [llength $argv]} {
+ # read from stdin
+ set ::src stdin
+ set out_filename "lex.yy.tcl"
+ } else {
+ set in_filename [lindex $argv $argvp]
+ if {$out_filename == ""} {
+ set out_filename [file rootname $in_filename]
+ append out_filename ".tcl"
+ }
+ if [catch {open $in_filename r} ::src] {
+ puts stderr "Could not open specification file '$in_filename'."
+ exit $::IO_ERROR
+ }
+ }
+ if $write_to_stdout {
+ set ::dest stdout
+ } else {
+ if [catch {open $out_filename w} ::dest] {
+ puts stderr "Could not create output file '$out_filename'."
+ exit $::IO_ERROR
+ }
+ }
+}
+
+# Actually do the scanner generation.
+proc fickle_main {} {
+ set ::line_count 0
+
+ # keep track of all rules found
+ set ::rule_table ""
+
+ # set up the INITIAL start state to be a normal inclusionary state
+ set ::state_table(INITIAL) $::INCLUSIVE
+
+ # keep track of where within the file I am:
+ # definitions, rules, or subroutines
+ set file_state definitions
+
+ while {[gets $::src line] >= 0} {
+ incr ::line_count
+
+ if {$line == "%%"} {
+ if {$file_state == "definitions"} {
+ set file_state "rules"
+ } elseif {$file_state == "rules"} {
+ set file_state "subroutines"
+ } else {
+ fickle_error "Syntax error." $::SYNTAX_ERROR
+ }
+ } else {
+ if {$file_state == "definitions"} {
+ handle_defs $line
+ } elseif {$file_state == "rules"} {
+ # keep reading the rest of the file until EOF or
+ # another '%%' appears
+ set rules_buf [strip_only_comments $line]
+ while {[gets $::src line] >= 0 && $file_state == "rules"} {
+ if {$line == "%%"} {
+ set file_state "subroutines"
+ break
+ } else {
+ append rules_buf "\n" [strip_only_comments $line]
+ }
+ }
+ build_scanner $rules_buf
+ set file_state "subroutines"
+ } else {
+ # file_state is subroutines -- copy verbatim to output file
+ puts $::dest $line
+ }
+ }
+ }
+}
+
+######################################################################
+# start of actual script
+
+set IO_ERROR 1
+set SYNTAX_ERROR 2
+set PARAM_ERROR 3
+set GRAMMAR_ERROR 4
+
+# two types of start states allowed:
+set INCLUSIVE 1
+set EXCLUSIVE 0
+
+fickle_args $argv
+fickle_main