diff --git a/.gitignore b/.gitignore
index 085e3b71..fdbf6974 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,9 +4,15 @@
*.swp
/pkg
Makefile
-lib/racc/parser-text.rb
+lib/racc/grammar_file_scanner.rb
tags
tmp
target
lib/racc/cparse-jruby.jar
racc.gemspec
+TAGS
+
+# For Bundler
+.bundle
+Gemfile.lock
+Gemfile.local
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 00000000..d7d2d50d
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,12 @@
+AllCops:
+ Exclude:
+ - Gemfile
+
+Lint/AssignmentInCondition:
+ Enabled: false
+
+Style/Semicolon:
+ Enabled: false
+
+Style/TrivialAccessors:
+ AllowPredicates: true
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 442c46d1..100b0b3b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,25 @@
---
-after_script:
-- rake travis:after -t
-before_script:
-- gem install hoe-travis --no-rdoc --no-ri
-- rake travis:before -t
+dist: xenial
+
+cache:
+ - bundler
+ - directories:
+ - /home/travis/.rvm/
language: ruby
-notifications:
- email:
+
rvm:
-- 1.8.7
-- 1.9.3
-- 2.0.0
-- ruby-head
-script: rake travis
+ - 2.4.6
+ - 2.5.5
+ - 2.6.3
+ - jruby-9.2.7.0
+ - ruby-head
+
matrix:
+ fast_finish: true
allow_failures:
- rvm: ruby-head
+ - rvm: jruby-9.2.7.0
+
+install:
+ - bundle install --retry 3
+script: ./test/run_tests.sh
diff --git a/COPYING b/COPYING
index c4792dd2..00e3e0db 100644
--- a/COPYING
+++ b/COPYING
@@ -1,515 +1,22 @@
-
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 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.
-
-[This is the first released version of the Lesser GPL. It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
- This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it. You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations
-below.
-
- When we speak of free software, we are referring to freedom of use,
-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 and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
- To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights. These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
- For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you. You must make sure that they, too, receive or can get the source
-code. If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it. And you must show them these terms so they know their rights.
-
- We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
- To protect each distributor, we want to make it very clear that
-there is no warranty for the free library. Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-^L
- Finally, software patents pose a constant threat to the existence of
-any free program. We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder. Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
- Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License. This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License. We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
- When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library. The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom. The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
- We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License. It also provides other free software developers Less
-of an advantage over competing non-free programs. These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries. However, the Lesser license provides advantages in certain
-special circumstances.
-
- For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it
-becomes
-a de-facto standard. To achieve this, non-free programs must be
-allowed to use the library. A more frequent case is that a free
-library does the same job as widely used non-free libraries. In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
- In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software. For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
- Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
- The precise terms and conditions for copying, distribution and
-modification follow. Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library". The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-^L
- GNU LESSER GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
- A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
- The "Library", below, refers to any such software library or work
-which has been distributed under these terms. A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language. (Hereinafter, translation is
-included without limitation in the term "modification".)
-
- "Source code" for a work means the preferred form of the work for
-making modifications to it. For a library, 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 library.
-
- Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it). Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
- 1. You may copy and distribute verbatim copies of the Library's
-complete 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 distribute a copy of this License along with the
-Library.
-
- 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 Library or any portion
-of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
-
- b) You must cause the files modified to carry prominent notices
- stating that you changed the files and the date of any change.
-
- c) You must cause the whole of the work to be licensed at no
- charge to all third parties under the terms of this License.
-
- d) If a facility in the modified Library refers to a function or a
- table of data to be supplied by an application program that uses
- the facility, other than as an argument passed when the facility
- is invoked, then you must make a good faith effort to ensure that,
- in the event an application does not supply such function or
- table, the facility still operates, and performs whatever part of
- its purpose remains meaningful.
-
- (For example, a function in a library to compute square roots has
- a purpose that is entirely well-defined independent of the
- application. Therefore, Subsection 2d requires that any
- application-supplied function or table used by this function must
- be optional: if the application does not supply it, the square
- root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Library,
-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 Library, 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 Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library. To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License. (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.) Do not make any other change in
-these notices.
-^L
- Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
- This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
- 4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you 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.
-
- If distribution of 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 satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library". Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
- However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library". The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
- When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library. The
-threshold for this to be true is not precisely defined by law.
-
- If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work. (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
- Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-^L
- 6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
- You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License. You must supply a copy of this License. If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License. Also, you must do one
-of these things:
-
- a) Accompany the work with the complete corresponding
- machine-readable source code for the Library including whatever
- changes were used in the work (which must be distributed under
- Sections 1 and 2 above); and, if the work is an executable linked
- with the Library, with the complete machine-readable "work that
- uses the Library", as object code and/or source code, so that the
- user can modify the Library and then relink to produce a modified
- executable containing the modified Library. (It is understood
- that the user who changes the contents of definitions files in the
- Library will not necessarily be able to recompile the application
- to use the modified definitions.)
-
- b) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (1) uses at run time a
- copy of the library already present on the user's computer system,
- rather than copying library functions into the executable, and (2)
- will operate properly with a modified version of the library, if
- the user installs one, as long as the modified version is
- interface-compatible with the version that the work was made with.
-
- c) Accompany the work with a written offer, valid for at
- least three years, to give the same user the materials
- specified in Subsection 6a, above, for a charge no more
- than the cost of performing this distribution.
-
- d) If distribution of the work is made by offering access to copy
- from a designated place, offer equivalent access to copy the above
- specified materials from the same place.
-
- e) Verify that the user has already received a copy of these
- materials or that you have already sent this user a copy.
-
- For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it. However, as a special exception,
-the materials to be 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.
-
- It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system. Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-^L
- 7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
- a) Accompany the combined library with a copy of the same work
- based on the Library, uncombined with any other library
- facilities. This must be distributed under the terms of the
- Sections above.
-
- b) Give prominent notice with the combined library of the fact
- that part of it is a work based on the Library, and explaining
- where to find the accompanying uncombined form of the same work.
-
- 8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License. Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library 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.
-
- 9. 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 Library or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
- 10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-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 with
-this License.
-^L
- 11. 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 Library at all. For example, if a patent
-license would not permit royalty-free redistribution of the Library 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 Library.
-
-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.
-
- 12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library 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.
-
- 13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser 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 Library
-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 Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-^L
- 14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-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
-
- 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "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
-LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. 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 LIBRARY 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
-LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
- END OF TERMS AND CONDITIONS
-^L
- How to Apply These Terms to Your New Libraries
-
- If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change. You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms
-of the ordinary General Public License).
-
- To apply these terms, attach the following notices to the library.
-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.
-
-
-
- Copyright (C)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; 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.
-
-You should also get your employer (if you work as a programmer) or
-your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the
- library `Frob' (a library for tweaking knobs) written by James
-Random Hacker.
-
- , 1 April 1990
- Ty Coon, President of Vice
-
-That's all there is to it!
-
-
+Copyright (C) 2019 Yukihiro Matsumoto. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/ChangeLog b/ChangeLog
deleted file mode 100644
index 762b5ee7..00000000
--- a/ChangeLog
+++ /dev/null
@@ -1,846 +0,0 @@
-Tue Feb 20 18:45:45 2007 Minero Aoki
-
- * lib/racc/grammar.rb (separated_by): last commit was wrong. use
- optional default return value of #option.
-
-Tue Feb 20 18:27:48 2007 Minero Aoki
-
- * lib/racc/grammar.rb (separated_by): return [] for empty list.
-
-Tue Nov 7 07:13:47 2006 Minero Aoki
-
- * lib/racc/grammar.rb (Rule#prec): rule.prec{...} should set
- action.
-
-Tue Nov 7 06:38:57 2006 Minero Aoki
-
- * lib/racc/grammar.rb: system call error on writing log file
- should be ignored.
-
- * lib/racc/grammar.rb: never define lvar which have same name with
- block local variable.
-
- * lib/racc/iset.rb: ditto.
-
- * lib/racc/logfilegenerator.rb: ditto.
-
- * lib/racc/parser.rb: ditto.
-
- * lib/racc/state.rb: ditto.
-
- * lib/racc/statetransitiontable.rb: ditto.
-
- * test/test.rb: racc -c is obsolete, use --line-convert-all.
-
-Sun Oct 29 13:27:30 2006 Minero Aoki
-
- * lib/racc/grammarfileparser.rb: use String#lines instead of
- #to_a.
-
- * lib/racc/parserfilegenerator.rb: ditto.
-
- * lib/racc/compat.rb: provide Object#__send.
-
- * lib/racc/compat.rb: provide Object#__send!.
-
- * lib/racc/compat.rb: provide String#lines.
-
-Thu Aug 24 23:14:16 2006 Minero Aoki
-
- * lib/racc/grammar.rb: report conflicts/useless if $DEBUG.
-
- * lib/racc/statetransitiontable.rb: remove code for Ruby 1.4
- compatibility.
-
-Fri Aug 4 01:02:36 2006 Minero Aoki
-
- * lib/racc/grammar.rb: #should_terminal should be called in
- #check_terminals.
-
-Fri Aug 4 00:44:56 2006 Minero Aoki
-
- * bin/racc: getopts -> optparse.
-
- * lib/racc/grammar.rb: value of error symbol is :error.
-
- * lib/racc/grammar.rb (check_terminals): string symbols are
- terminal.
-
- * lib/racc/grammarfileparser.rb (add_rule_block): specified-prec
- did not work.
-
-Fri Aug 4 00:29:53 2006 Minero Aoki
-
- * lib/racc/parserfilegenerator.rb
- (serialize_integer_list_compressed): fix typo.
-
-Thu Aug 3 22:20:34 2006 Minero Aoki
-
- * bin/y2racc: fix filename.
-
-Thu Aug 3 21:10:48 2006 Minero Aoki
-
- * bin/y2racc: getopts -> optparse.
-
-Thu Aug 3 19:35:34 2006 Minero Aoki
-
- * setup.rb: updated.
-
-Thu Aug 3 19:34:55 2006 Minero Aoki
-
- * bin/racc2y: getopts -> optparse.
-
- * bin/racc2y: rewrite code for new generator.
-
- * lib/racc/grammar.rb (_regist): did not check @delayed rules (it
- causes registering same dummy rules many times).
-
- * lib/racc/grammarfileparser.rb: refactoring: simplify syntax.
-
- * lib/racc/grammarfileparser.rb: new method
- GrammarFileParser.parse.
-
- * lib/racc/grammarfileparser.rb: new method
- GrammarFileParser.parse_file.
-
-Sat Jul 29 04:51:42 2006 Minero Aoki
-
- * lib/racc/pre-setup: We need not make grammarfileparser.rb.
-
-Sat Jul 29 04:30:33 2006 Minero Aoki
-
- * lib/racc/grammar.rb: allow '|' operation with meta rules
- (many, option...).
-
-Sat Jul 29 03:17:20 2006 Minero Aoki
-
- * lib/racc/grammar.rb (Grammar#parser_class): write log file when
- $DEBUG=true.
-
- * lib/racc/grammar.rb (Grammar.define): run block on a
- Racc::Grammar::DefinitionEnv object, instead of a Racc::Grammar
- object.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #null.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #many.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #many1.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #option.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #seperated_by.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #seperated_by1.
-
- * lib/racc/grammar.rb (DefinitionEnv): new method #action.
-
-Sat Jul 29 03:13:22 2006 Minero Aoki
-
- * lib/racc/compat.rb: reduce warning.
-
-Sun Jul 16 05:07:12 2006 Minero Aoki
-
- * lib/racc/compat.rb: implement Enumerable#each_slice for Ruby
- 1.8.
-
- * lib/racc/parserfilegenerator.rb: better output.
-
- * ext/racc/cparse/cparse.c: always use VALUE instead of struct
- cparse_params.
-
- * ext/racc/cparse/cparse.c: mark params->value_v.
-
-Thu Jul 6 20:44:48 2006 Minero Aoki
-
- * lib/racc/grammar.rb: on-the-fly generator implemented.
-
- * lib/racc/generator.rb -> statetransitiontable.rb,
- parserfilegenerator.rb, logfilegenerator.rb.
-
- * lib/racc/statetransitiontable.rb: new file.
-
- * lib/racc/parserfilegenerator.rb: new file.
-
- * lib/racc/logfilegenerator.rb: new file.
-
- * lib/racc/grammarfileparser.rb.in: removed.
-
- * lib/racc/grammarfileparser.rb: new file. uses on-the-fly
- generator.
-
- * misc/boot.rb: removed.
-
- * lib/racc/static.rb: new file, to import static generator
- (lib/racc.rb provides dynamic generator).
-
- * lib/racc/grammar.rb: grand refactoring.
-
- * lib/racc/sourcetext.rb: new method #to_s, #location.
-
- * lib/racc/state.rb: compute NFA/DFA on demand.
-
- * bin/racc: follow these changes.
-
-Thu Jul 6 20:39:42 2006 Minero Aoki
-
- * ext/racc/cparse/cparse.so: should mark VALUEs in cparse_params.
-
-Tue Jul 4 02:24:27 2006 Minero Aoki
-
- * bin/racc: simplify report code.
-
- * lib/racc/grammar.rb: introduce new methods for racc command.
-
- * lib/racc/states.rb: ditto.
-
- * lib/racc/generator.rb: class CodeGenerator ->
- ParserFileGenerator.
-
- * lib/racc/generator.rb: new class ParserFileGenerator::Params.
-
- * bin/racc: ditto.
-
- * misc/boot.rb: ditto.
-
- * lib/racc/grammarfileparser.rb.in: ditto.
-
- * lib/racc/grammarfileparser.rb.in: merge grammarfilescanner.rb.
-
- * lib/racc/grammarfilescanner.rb: removed.
-
- * lib/racc/grammarfileparser.rb.in: parses user code blocks.
-
- * lib/racc/usercodeparser.rb: removed.
-
- * lib/racc/generator.rb: remove user code parsing code.
-
- * lib/racc/grammarfileparser.rb.in: passes user code block by a
- SourceText object.
-
- * lib/racc/generator.rb: ditto.
-
- * lib/racc/sourcetext.rb: new file.
-
- * lib/racc/generator.rb: introduce DSL to describe file contents.
-
-Tue Jul 4 02:15:36 2006 Minero Aoki
-
- * lib/racc/debugflags.rb: remove unused class GenerationOptions.
-
-Tue Jul 4 02:14:48 2006 Minero Aoki
-
- * lib/racc/compat.rb: update coding style.
-
-Mon Jul 3 04:34:32 2006 Minero Aoki
-
- * lib/racc/compiler.rb: do not export Grammar/SymbolTable/States.
-
- * lib/racc/compiler.rb: make a new class for debug flags
- (Racc::DebugFlags).
-
- * lib/racc/compiler.rb: removed.
-
- * bin/racc: eliminate Racc::Compiler class.
-
- * bin/racc: refactor profiling code.
-
- * bin/racc: move file generation code to racc/generator.rb.
-
- * misc/boot.rb: does not emulate Racc::Compiler interface.
-
- * lib/racc.rb: new file to require whole generator.
-
- * lib/racc/grammar.rb: class RuleTable -> Grammar.
-
- * lib/racc/grammar.rb: Grammar.new does not acccept a Compiler.
-
- * lib/racc/grammar.rb: refactoring.
-
- * lib/racc/grammarfileparser.rb.in: GrammarFileParser.new does not
- accept a Compiler.
-
- * lib/racc/grammarfileparser.rb.in: #parser takes more 2 args, a
- filename and a base line number.
-
- * lib/racc/grammarfileparser.rb.in: refactoring.
-
- * lib/racc/output.rb -> generate.rb
-
- * lib/racc/generate.rb: class Formatter -> CodeGenerator.
-
- * lib/racc/generate.rb: CodeGenerator.new does not accept a
- Compiler.
-
- * lib/racc/generate.rb: a CodeGenerator got many parameters via
- setter method.
-
- * lib/racc/generate.rb: class VerboseOutputter ->
- LogFileGenerator.
-
- * lib/racc/generate.rb: LogFileGenerator.new does not accept a
- Compiler.
-
- * lib/racc/generate.rb: refactoring.
-
- * lib/racc/state.rb: class StateTable -> States.
-
- * lib/racc/state.rb: States.new does not acccept a Compiler.
-
- * lib/racc/state.rb: refactoring.
-
- * test/test.rb: -Da is obsolete (I forgot what this flag is).
-
- * test/test.rb: allow replacing racc via environment variable
- $RACC.
-
-Mon Jul 3 04:18:49 2006 Minero Aoki
-
- * Makefile: new task bootstrap-force.
-
-Sun Jul 2 19:46:58 2006 Minero Aoki
-
- * test/ichk.y: update coding style.
-
-Sun Jul 2 19:01:55 2006 Minero Aoki
-
- * ext/racc/cparse/cparse.c: must require version.h to get
- RUBY_VERSION_CODE.
-
-Sun Jul 2 18:33:32 2006 Minero Aoki
-
- * ext/racc/cparse/cparse.c: do not use rb_iterate to give a block
- to the method, use rb_block_call instead. [ruby-dev:28445]
-
-Mon Jun 19 02:38:18 2006 Minero Aoki
-
- * bin/racc: -g option is now -t. -g option is obsolete and is an
- alias of -t.
-
-Mon Jun 19 02:35:59 2006 Minero Aoki
-
- * ext/racc/cparse/cparse.c: K&R -> ANSI C.
-
-Mon Nov 21 02:37:10 2005 Minero Aoki
-
- * version 1.4.5 released.
-
-Mon Nov 21 02:31:18 2005 Minero Aoki
-
- * bin/racc: shebang line should include file extension.
-
- * lib/racc/compat.rb: method removed: bug!.
-
- * lib/racc/*.rb: racc compiler should not depend on
- Racc::ParseError.
-
- * lib/racc/*.rb: update copyright year.
-
- * lib/racc/*.rb: update coding style.
-
- * lib/racc/exception.rb: new file.
-
-Mon Nov 21 00:49:18 2005 Minero Aoki
-
- * Makefile: remove useless target `import'.
-
- * Makefile: generate parser-text.rb.
-
- * misc/dist.sh: setup.rb and COPYING is now in repository.
-
- * misc/dist.sh: generate parser-text.rb.
-
-Mon Nov 21 00:14:21 2005 Minero Aoki
-
- * bin/racc: read racc/parser.rb from parser-text.rb.
-
- * lib/racc/rubyloader.rb: no longer needed.
-
- * lib/racc/pre-setup: new file.
-
- * lib/racc/pre-setup: generate parser-text.rb.
-
- * lib/racc/pre-setup: generate grammarfileparser.rb.
-
- * misc/boot.rb: new method BootstrapCompiler.main.
-
- * misc/boot.rb: new method BootstrapCompiler.generate, which is
- used from pre-setup.
-
-Mon Nov 21 00:09:04 2005 Minero Aoki
-
- * bin/racc2y: refactoring.
-
- * bin/y2racc: refactoring.
-
-Sun Nov 20 23:46:42 2005 Minero Aoki
-
- * lib/racc/pre-setup: new file.
-
-Sun Nov 20 22:46:21 2005 Minero Aoki
-
- * COPYING: new file.
-
-Sun Nov 20 22:25:15 2005 Minero Aoki
-
- * setup.rb: import setup.rb 3.4.1.
-
-Thu Sep 29 02:51:56 2005 Minero Aoki
-
- * Makefile (clean): invoke `make clean' in ext.
-
-Thu Sep 29 02:50:56 2005 Minero Aoki
-
- * lib/racc/.cvsignore: removed.
-
-Thu Sep 29 02:46:30 2005 Minero Aoki
-
- * Makefile: use .makeparams system.
-
- * Makefile: unify lib/racc/Makefile.
-
- * Makefile: new target lib/racc/grammarfileparser.rb.
-
- * lib/racc/Makefile: unified by ./Makefile.
-
- * lib/racc/boot: removed (moved under misc).
-
- * misc/boot.rb: new file.
-
-Thu Sep 29 02:43:30 2005 Minero Aoki
-
- * setup.rb: new file.
-
-Tue Jul 26 23:37:46 2005 Minero Aoki
-
- * bin/racc: --no-omit-actions did not work (This patch is
- contributed by OHKUBO Takuya).
-
-Sun Jan 2 11:48:19 2005 Minero Aoki
-
- * lib/racc/grammer.rb (once_writer): bug! needs argument.
-
-Mon Feb 16 16:14:16 2004 Minero Aoki
-
- * test/echk.y: fix typo.
-
- * test/ichk.y: does not use amstd.
-
- * test/opt.y: untabify.
-
-Mon Feb 16 16:10:46 2004 Minero Aoki
-
- * lib/racc/boot: update coding style.
-
- * lib/racc/compat.rb: ditto.
-
- * lib/racc/compiler.rb: ditto.
-
- * lib/racc/grammar.rb: ditto.
-
- * lib/racc/grammarfileparser.rb.in: ditto.
-
- * lib/racc/grammarfilescanner.rb: ditto.
-
- * lib/racc/info.rb: ditto.
-
- * lib/racc/iset.rb: ditto.
-
- * lib/racc/output.rb: ditto.
-
- * lib/racc/parser.rb: ditto.
-
- * lib/racc/state.rb: ditto.
-
- * lib/racc/usercodeparser.rb: ditto.
-
-Mon Feb 16 16:01:34 2004 Minero Aoki
-
- * lib/racc/rubyloader.rb: imported rev1.6.
-
-Fri Dec 12 01:57:47 2003 Minero Aoki
-
- * sample/hash.y: use no_result_var option.
-
- * sample/array.y: use latest (my) coding style.
-
- * sample/array2.y: ditto.
-
- * sample/hash.y: ditto.
-
- * sample/lists.y: ditto.
-
-Wed Nov 5 19:50:35 2003 Minero Aoki
-
- * test/bench.y: remove dependency on amstd.
-
- * test/chk.y: ditto.
-
- * test/echk.y: ditto.
-
- * test/ichk.y: ditto.
-
- * test/intp.y: ditto.
-
- * test/opt.y: ditto.
-
- * test/percent.y: ditto.
-
-Wed Nov 5 19:11:15 2003 Minero Aoki
-
- * bin/racc (get_options): remove --no-extensions option;
- racc/parser is preloaded, Racc_No_Extension does not work.
-
-Mon Nov 3 22:41:42 2003 Minero Aoki
-
- * bin/racc: apply latest coding style.
-
- * lib/racc/parser.rb: ditto.
-
- * lib/racc/compat.rb: add File.read.
-
-Mon Nov 3 21:20:25 2003 Minero Aoki
-
- * ext/racc/cparse/cparse.c (parse_main): abort if length of state
- stack <=1, not ==0.
-
- * lib/racc/parser.rb: use <=1, not <2.
-
- * ext/racc/cparse/cparse.c: check_*() -> assert_*()
-
- * ext/racc/cparse/cparse.c (racc_cparse): define lvar `v' for
- debugging.
-
- * ext/racc/cparse/cparse.c (racc_yyparse): ditto.
-
-Mon Nov 3 17:21:55 2003 Minero Aoki
-
- * Makefile (all): make cparse.so.
-
-Mon Nov 3 17:19:26 2003 Minero Aoki
-
- * lib/racc/parser.rb: update version.
-
- * ext/racc/cparse/cparse.c: update version.
-
-Mon Nov 3 17:19:01 2003 Minero Aoki
-
- * Makefile: update version in parser.rb, cparse.c.
-
-Sun Oct 12 23:49:58 2003 Minero Aoki
-
- * version 1.4.4.
-
-Sun Oct 12 23:49:40 2003 Minero Aoki
-
- * bin/y2racc: did not work.
-
- * bin/y2racc: -u options did not work.
-
-Sun Oct 12 23:41:46 2003 Minero Aoki
-
- * misc/dist.sh: cd before make.
-
-Sun Oct 12 23:38:04 2003 Minero Aoki
-
- * Makefile (site): create $siteroot/{ja,en}/man/racc/*.html.
-
-Sun Oct 12 23:37:18 2003 Minero Aoki
-
- * doc/parser.rrd.m: missing 'j'.
-
-Sun Oct 12 23:29:11 2003 Minero Aoki
-
- * Makefile: new target `doc'.
-
- * Makefile: new target `clean'.
-
- * lib/racc/Makefile: new target `clean'.
-
- * misc/dist.sh: create documents before pack.
-
-Sun Oct 12 23:27:58 2003 Minero Aoki
-
- * doc/debug.rd.m: junk char was inserted.
-
- * doc/index.html.m: en/ja text were mixed.
-
- * doc/parser.rrd.m: add return values.
-
- * doc/usage.html.m: fix hyper link.
-
-Sun Oct 12 22:57:28 2003 Minero Aoki
-
- * doc.en/changes.html, doc.ja/changes.html -> doc/NEWS.rd.m
-
- * doc.en/command.html, doc.ja/command.html -> doc/command.html.m
-
- * doc.en/debug.html, doc.ja/debug.html -> doc/debug.rd.m
-
- * doc.en/grammar.html, doc.ja/grammar.html -> doc/grammar.rd.m
-
- * doc.en/index.html, doc.ja/index.html -> doc/index.html.m
-
- * doc.en/parser.html, doc.ja/parser.html -> doc/parser.rrd.m
-
- * doc.en/usage.html, doc.ja/usage.html -> doc/usage.html.m
-
-Sun Oct 12 18:46:21 2003 Minero Aoki
-
- * web/racc.ja.html: update descriptions.
-
- * web/racc.en.html: ditto.
-
-Sun Oct 12 18:43:45 2003 Minero Aoki
-
- * misc/dist.sh: remove web/ directory before distribute.
-
-Sun Oct 12 18:37:29 2003 Minero Aoki
-
- * Makefile: new target `site'.
-
- * web/racc.ja.html: new file.
-
- * web/racc.en.html: new file.
-
-Sun Oct 12 18:30:55 2003 Minero Aoki
-
- * misc/dist.sh: forgot to remove tmp comment out.
-
-Sun Oct 12 18:12:09 2003 Minero Aoki
-
- * lib/racc/info.rb: version 1.4.4.
-
-Sun Oct 12 18:11:42 2003 Minero Aoki
-
- * Makefile (dist): split out misc/dist.sh.
-
- * misc/dist.sh: new file.
-
-Sun Oct 12 17:18:47 2003 Minero Aoki
-
- * README.en: update documents.
-
- * README.ja: ditto.
-
- * doc.en/changes.html: ditto.
-
- * doc.en/command.html: ditto.
-
- * doc.en/debug.html: ditto.
-
- * doc.en/grammar.html: ditto.
-
- * doc.en/index.html: ditto.
-
- * doc.en/parser.html: ditto.
-
- * doc.en/usage.html: ditto.
-
- * doc.ja/changes.html: ditto.
-
- * doc.ja/command.html: ditto.
-
- * doc.ja/debug.html: ditto.
-
- * doc.ja/index.html: ditto.
-
- * doc.ja/parser.html: ditto.
-
- * doc.ja/usage.html: ditto.
-
-Sun Oct 12 16:24:46 2003 Minero Aoki
-
- * sameple/calc-ja.y: simplify.
-
-Sun Oct 12 16:24:16 2003 Minero Aoki
-
- * misc/y2racc -> bin/y2racc
-
- * misc/racc2y -> bin/racc2y
-
-Sun Oct 12 15:56:30 2003 Minero Aoki
-
- * bin/racc: follow method name change.
-
-Sun Oct 12 15:34:14 2003 Minero Aoki
-
- * Makefile: new target `test'.
-
- * Makefile: missing $datadir.
-
-Sun Oct 12 15:33:02 2003 Minero Aoki
-
- * README.ja: update description.
-
- * README.en: ditto.
-
-Sun Oct 12 15:25:23 2003 Minero Aoki
-
- * lib/racc/compiler.rb: adjust file names.
-
- * lib/racc/grammarfileparser.rb.in: ditto.
-
- * lib/racc/grammarfilescanner.rb: ditto.
-
-Sun Oct 12 15:24:53 2003 Minero Aoki
-
- * Makefile: new file.
-
-Sun Oct 12 15:19:57 2003 Minero Aoki
-
- * BUGS.en: removed.
-
- * BUGS.ja: removed.
-
-Sun Oct 12 15:10:38 2003 Minero Aoki
-
- * racc -> bin/racc
-
- * .cvsignore -> lib/racc/.cvsignore
-
- * lib/racc/Makefile: new file.
-
- * boot.rb -> lib/racc/boot
-
- * compat.rb -> lib/racc/compat.rb
-
- * compiler.rb -> lib/racc/compiler.rb
-
- * grammar.rb -> lib/racc/grammar.rb
-
- * in.raccp.rb -> lib/racc/grammarfileparser.rb.in
-
- * raccs.rb -> lib/racc/grammarfilescanner.rb
-
- * info.rb -> lib/racc/info.rb
-
- * iset.rb -> lib/racc/iset.rb
-
- * outpur.rb -> lib/racc/output.rb
-
- * parser.rb -> lib/racc/parser.rb
-
- * rubyloader.rb -> lib/racc/rubyloader.rb
-
- * state.rb -> lib/racc/state.rb
-
- * ucodep.rb -> lib/racc/usercodeparser.rb
-
- * cparse/MANIFEST -> ext/racc/cparse/MANIFEST
-
- * cparse/cparse.c -> ext/racc/cparse/cparse.c
-
- * cparse/depend -> ext/racc/cparse/depend
-
- * cparse/extconf.rb -> ext/racc/cparse/extconf.rb
-
- * cparse/.cvsignore -> ext/racc/cparse/.cvsignore
-
-Sun Oct 12 15:10:13 2003 Minero Aoki
-
- * test/test.rb: use /bin/rm if exists.
-
-Sun Oct 12 14:33:29 2003 Minero Aoki
-
- * rubyloader.rb: imported from amstd, rev 1.5.
-
-Sun Oct 12 14:24:47 2003 Minero Aoki
-
- * boot.rb: reformat only.
-
- * compiler.rb: ditto.
-
- * grammar.rb: ditto.
-
- * in.raccp.rb: ditto.
-
- * iset.rb: ditto.
-
- * output.rb: ditto.
-
- * raccs.rb: ditto.
-
- * state.rb: ditto.
-
-Sun Oct 12 14:17:22 2003 Minero Aoki
-
- * test/test.rb: refactoring.
-
-Tue Jun 24 03:14:01 2003 Minero Aoki
-
- * ucodep.rb: typo: Grammer -> Grammar
-
-Mon May 26 23:06:58 2003 Minero Aoki
-
- * compiler.rb: update copyright year.
-
- * grammar.rb: ditto.
-
- * in.raccp.rb: ditto.
-
- * info.rb: ditto.
-
- * iset.rb: ditto.
-
- * output.rb: ditto.
-
- * parser.rb: ditto.
-
- * raccs.rb: ditto.
-
- * state.rb: ditto.
-
- * ucodep.rb: ditto.
-
-Sun May 25 13:21:27 2003 Minero Aoki
-
- * raccs.rb: update coding style.
-
-Fri Nov 15 17:53:12 2002 Minero Aoki
-
- * racc: changes style.
-
- * parser.rb: ditto.
-
-Fri Nov 15 17:11:52 2002 Minero Aoki
-
- version 1.4.3.
-
-Fri Nov 15 17:08:01 2002 Minero Aoki
-
- * boot.rb, compiler.rb, grammar.rb, in.raccp.rb, iset.rb,
- output.rb, parser.rb, racc, raccs.rb, state.rb, ucodep.rb,
- misc/racc2y, misc/y2racc: follows (my) latest coding styles.
-
-Thu Nov 14 14:39:53 2002 Minero Aoki
-
- * raccs.rb: explicit method call for VCALL.
-
-Wed Oct 16 15:45:11 2002 Minero Aoki
-
- * parser.rb: reformat.
-
-Fri Aug 9 18:21:01 2002 Minero Aoki
-
- * cparse/cparse.c: use better variable/macro names.
-
-Wed Aug 7 08:39:19 2002 Minero Aoki
-
- * cparse/cparse.c: goto label requires stmt.
-
-Mon Aug 5 21:53:07 2002 Minero Aoki
-
- * cparse/cparse.c: grand refine.
-
- * cparse/depend: re-added from ruby/ext/racc/cparse.
-
-Tue Jun 4 00:15:28 2002 Minero Aoki
-
- * boot.rb: allow to omit last 'end'.
-
-Mon Jun 3 23:29:45 2002 Minero Aoki
-
- * racc (write_table_file): shebang must placed on first line.
- (reported by Hiroyuki Sato)
-
diff --git a/DEPENDS b/DEPENDS
index 82d7516d..dbaafcd4 100644
--- a/DEPENDS
+++ b/DEPENDS
@@ -1,4 +1,2 @@
racc: raccruntime
raccruntime:
-y2racc: strscan
-racc2y: racc
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 00000000..b78ecc1b
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,21 @@
+# -*- ruby -*-
+
+# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
+
+source "https://rubygems.org/"
+
+
+gem "rake", "~>12.2", :group => [:development, :test]
+gem "rake-compiler", "~>1.0", :group => [:development, :test]
+gem "minitest", "~>5.10", :group => [:development, :test]
+gem "hoe", "~>3.16", :group => [:development, :test]
+gem "hoe-debugging", "~>1.4", :group => [:development, :test]
+gem "hoe-doofus", "~>1.0", :group => [:development, :test]
+gem "hoe-git", "~>1.6", :group => [:development, :test]
+gem "hoe-gemspec", "~>1.0", :group => [:development, :test]
+gem "hoe-bundler", "~>1.3", :group => [:development, :test]
+gem "rubocop", "~>0.51", :group => [:development, :test]
+gem "pry", "~>0.11", :group => [:development, :test]
+gem "rdoc", "~>4.0", :group => [:development, :test]
+
+# vim: syntax=ruby
diff --git a/Manifest.txt b/Manifest.txt
index d77dfcad..9a55836b 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -1,5 +1,4 @@
COPYING
-ChangeLog
DEPENDS
Manifest.txt
README.ja.rdoc
@@ -7,33 +6,30 @@ README.rdoc
Rakefile
TODO
bin/racc
-bin/racc2y
-bin/y2racc
ext/racc/MANIFEST
ext/racc/cparse.c
ext/racc/depend
ext/racc/extconf.rb
ext/racc/com/headius/racc/Cparse.java
-fastcache/extconf.rb
-fastcache/fastcache.c
lib/racc.rb
-lib/racc/compat.rb
-lib/racc/debugflags.rb
+lib/racc/color.rb
+lib/racc/directed_graph.rb
+lib/racc/dsl.rb
lib/racc/exception.rb
lib/racc/grammar.rb
-lib/racc/grammarfileparser.rb
+lib/racc/grammar_file_parser.rb
+lib/racc/grammar_file_scanner.rb
+lib/racc/grammar_file_scanner.rl
lib/racc/info.rb
-lib/racc/iset.rb
-lib/racc/logfilegenerator.rb
-lib/racc/parser-text.rb
lib/racc/parser.rb
-lib/racc/parserfilegenerator.rb
-lib/racc/pre-setup
-lib/racc/sourcetext.rb
+lib/racc/parser_file_generator.rb
+lib/racc/simulated_automaton.rb
+lib/racc/source.rb
lib/racc/state.rb
-lib/racc/statetransitiontable.rb
-lib/racc/static.rb
-misc/dist.sh
+lib/racc/state_summary_generator.rb
+lib/racc/state_transition_table.rb
+lib/racc/util.rb
+lib/racc/warning.rb
rdoc/en/NEWS.en.rdoc
rdoc/en/grammar.en.rdoc
rdoc/ja/NEWS.ja.rdoc
@@ -53,49 +49,134 @@ sample/lalr.y
sample/lists.y
sample/syntax.y
sample/yyerr.y
-setup.rb
-tasks/doc.rb
-tasks/email.rb
+test/assets/badprec1.y
+test/assets/badprec2.y
+test/assets/badrule1.y
+test/assets/badrule2.y
+test/assets/badrule3.y
+test/assets/badrule4.y
+test/assets/badsyntax.y
+test/assets/bench.y
+test/assets/cadenza.y
+test/assets/calc-ja.y
+test/assets/cast.y
test/assets/chk.y
test/assets/conf.y
+test/assets/csspool.y
test/assets/digraph.y
+test/assets/duplicate.y
test/assets/echk.y
+test/assets/edtf.y
+test/assets/empty.y
test/assets/err.y
+test/assets/error_recovery.y
test/assets/expect.y
+test/assets/eye-of-newt.y
test/assets/firstline.y
+test/assets/huia.y
test/assets/ichk.y
+test/assets/infini.y
test/assets/intp.y
+test/assets/journey.y
+test/assets/liquor.y
+test/assets/lr_not_lalr.y
+test/assets/lr_not_lalr2.y
+test/assets/machete.y
+test/assets/macruby.y
+test/assets/mediacloth.y
test/assets/mailp.y
+test/assets/mof.y
+test/assets/namae.y
+test/assets/nasl.y
test/assets/newsyn.y
test/assets/noend.y
+test/assets/nokogiri-css.y
test/assets/nonass.y
test/assets/normal.y
test/assets/norule.y
test/assets/nullbug1.y
test/assets/nullbug2.y
+test/assets/opal.y
test/assets/opt.y
test/assets/percent.y
+test/assets/php_serialization.y
test/assets/recv.y
+test/assets/riml.y
test/assets/rrconf.y
+test/assets/ruby18.y
+test/assets/ruby19.y
+test/assets/ruby20.y
+test/assets/ruby21.y
+test/assets/ruby22.y
test/assets/scan.y
+test/assets/src.intp
+test/assets/start.y
test/assets/syntax.y
+test/assets/tp_plus.y
+test/assets/twowaysql.y
test/assets/unterm.y
test/assets/useless.y
test/assets/yyerr.y
-test/bench.y
test/helper.rb
-test/infini.y
+test/regress.rb
+test/regress/README.txt
+test/regress/badprec1.out
+test/regress/badprec2.out
+test/regress/badrule1.out
+test/regress/badrule2.out
+test/regress/badrule3.out
+test/regress/badrule4.out
+test/regress/badsyntax.out
+test/regress/cadenza.rb
+test/regress/cast.rb
+test/regress/csspool.html
+test/regress/csspool.out
+test/regress/csspool.rb
+test/regress/duplicate.out
+test/regress/edtf.out
+test/regress/edtf.rb
+test/regress/empty.out
+test/regress/eye-of-newt.rb
+test/regress/journey.rb
+test/regress/huia.rb
+test/regress/liquor.rb
+test/regress/lr_not_lalr.out
+test/regress/lr_not_lalr2.out
+test/regress/machete.rb
+test/regress/mediacloth.rb
+test/regress/mof.out
+test/regress/mof.rb
+test/regress/namae.rb
+test/regress/nasl.rb
+test/regress/nokogiri-css.rb
+test/regress/normal.out
+test/regress/opal.out
+test/regress/opal.rb
+test/regress/php_serialization.rb
+test/regress/riml.rb
+test/regress/ruby18.rb
+test/regress/ruby22.rb
+test/regress/tp_plus.rb
+test/regress/twowaysql.html
+test/regress/twowaysql.rb
+test/scandata/blockcomment
test/scandata/brace
+test/scandata/comment_at_eof
+test/scandata/dynstr
+test/scandata/escapednl
test/scandata/gvar
+test/scandata/heredocs
+test/scandata/leading_whitespace
test/scandata/normal
+test/scandata/operators
test/scandata/percent
+test/scandata/regexp
test/scandata/slash
-test/src.intp
-test/start.y
+test/scandata/strings
+test/scandata/suffixes
test/test_chk_y.rb
+test/test_directed_graph.rb
test/test_grammar_file_parser.rb
test/test_racc_command.rb
test/test_scan_y.rb
-test/testscanner.rb
-web/racc.en.rhtml
-web/racc.ja.rhtml
+test/test_scanner.rb
diff --git a/README.ja.rdoc b/README.ja.rdoc
index ba1c1bf8..50ccc7b3 100644
--- a/README.ja.rdoc
+++ b/README.ja.rdoc
@@ -17,6 +17,7 @@
== 必要環境
* Ruby 1.8 以降
+ * Ragel
(*) C コンパイラと make
@@ -76,10 +77,9 @@
== ライセンス
このパッケージに付属するファイルの著作権は青木峰郎が保持します。
- ライセンスは GNU Lesser General Public License (LGPL) version 2
- です。ただしユーザが書いた規則ファイルや、Racc がそこから生成し
- た Ruby スクリプトはその対象外です。好きなライセンスで配布して
- ください。
+ ライセンスは Ruby ライセンスです。ただしユーザが書いた規則
+ ファイルや、Racc がそこから生成した Ruby スクリプトはその対象
+ 外です。好きなライセンスで配布してください。
== バグなど
diff --git a/README.rdoc b/README.rdoc
index 56bed808..32ed4ccd 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,21 +1,21 @@
= Racc
* http://i.loveruby.net/en/projects/racc/
-* http://racc.rubyforge.org/
== DESCRIPTION:
Racc is a LALR(1) parser generator.
- It is written in Ruby itself, and generates Ruby program.
+ It is written in Ruby itself, and generates Ruby programs.
- NOTE: Ruby 1.8.x comes with Racc runtime module. You
+ NOTE: Ruby 1.8+ comes with the Racc runtime module. You
can run your parsers generated by racc 1.4.x out of the
box.
-== Requirement
+== Requirements
- * Ruby 1.8.x or later.
+ * Ruby 1.8+
+ * Ragel
(*) make and C compiler.
@@ -25,60 +25,36 @@
$ gem install racc
- setup.rb install:
-
- Type this in the top directory of the extracted archive:
-
- $ ruby setup.rb config
- $ ruby setup.rb setup
- ($ su)
- # ruby setup.rb install
-
- You can install Racc into your favorite directory by giving
- options to setup.rb. e.g.
-
- $ ruby setup.rb config --prefix=/usr
-
- For details, try "ruby setup.rb --help".
-
-
- If you don't have C Compiler
- ----------------------------
-
- You can install Racc without C compilers. Type following
- command in config phase.
-
- $ ruby setup.rb config --without-ext
-
== Testing Racc
- Racc comes with simple calculator. To compile this, on shell:
+ Racc comes with a simple calculator. To compile this at the shell:
- $ racc -o calc calc.y
+ $ racc -o calc sample/calc.y
- This process costs few seconds (or less). Then type:
+ This process takes few seconds (or less). Then type:
$ ruby calc
- ... Does it works?
- For details of Racc, see HTML documents placed under 'doc.en/'
- and sample grammer files under 'sample/'.
+ ... Does it work?
+
+ For details about Racc, see the HTML documents under 'doc.en/'
+ and sample grammar files under 'sample/'.
== License
- Racc is distributed under the terms of the GNU Lesser General
- Public License version 2. Note that you do NOT need to follow
- LGPL for your own parser (racc outputs). You can provide those
- files under any licenses you want.
+ Racc is distributed under the same terms of ruby.
+ (see the file COPYING). Note that you do NOT need to follow
+ ruby license for your own parser (racc outputs).
+ You can distribute those files under any licenses you want.
== Bug Reports
Any kind of bug reports are welcome.
- If you find a bug of Racc, please email me. Your grammer file,
- debug output genereted by "racc -g", are helpful.
+ If you find a bug in Racc, please email me. Your grammar file,
+ and debug output generated by "racc -g", will be helpful.
Minero Aoki
diff --git a/Rakefile b/Rakefile
index d6c6b050..b2fb529d 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,9 +3,11 @@
require 'rubygems'
require 'hoe'
-gem 'rake-compiler', '>= 0.4.1'
+gem 'rake-compiler'
-Hoe.plugin :debugging, :doofus, :git, :isolate, :gemspec
+Hoe.plugin :debugging, :doofus, :git, :gemspec, :bundler
+
+$:.unshift(File.dirname(__FILE__) + '/lib')
def java?
/java/ === RUBY_PLATFORM
@@ -19,11 +21,21 @@ HOE = Hoe.spec 'racc' do
license "MIT"
self.extra_rdoc_files = Dir['*.rdoc']
- self.history_file = 'ChangeLog'
self.readme_file = 'README.rdoc'
- dependency 'rake-compiler', '>= 0.4.1', :developer
- dependency 'minitest', '~> 4.7', :developer # stick to stdlib's version
+ dependency 'rake', '~> 12.2', :developer
+ dependency 'rake-compiler', '~> 1.0', :developer
+ dependency 'minitest', '~> 5.10', :developer
+
+ dependency 'hoe', '~> 3.16', :developer
+ dependency 'hoe-debugging', '~> 1.4', :developer
+ dependency 'hoe-doofus', '~> 1.0', :developer
+ dependency 'hoe-git', '~> 1.6', :developer
+ dependency 'hoe-gemspec', '~> 1.0', :developer
+ dependency 'hoe-bundler', '~> 1.3', :developer
+
+ dependency 'rubocop', '~> 0.51', :developer
+ dependency 'pry', '~> 0.11', :developer
if java?
self.spec_extras[:platform] = 'java'
@@ -33,6 +45,7 @@ HOE = Hoe.spec 'racc' do
self.clean_globs << "lib/#{self.name}/*.{so,bundle,dll,jar}" # from hoe/compiler
+ require_ruby_version '2.2.8'
end
def add_file_to_gem relative_path
@@ -48,20 +61,6 @@ def gem_build_path
File.join 'pkg', HOE.spec.full_name
end
-file 'lib/racc/parser-text.rb' => ['lib/racc/parser.rb'] do |t|
- source = 'lib/racc/parser.rb'
-
- open(t.name, 'wb') { |io|
- io.write(<<-eorb)
-module Racc
- PARSER_TEXT = <<'__end_of_file__'
-#{File.read(source)}
-__end_of_file__
-end
- eorb
- }
-end
-
unless jruby?
# MRI
require "rake/extensiontask"
@@ -69,9 +68,6 @@ unless jruby?
ext.lib_dir = File.join 'lib', 'racc'
ext.ext_dir = File.join 'ext', 'racc'
end
-
- task :compile => 'lib/racc/parser-text.rb'
- #
else
# JRUBY
require "rake/javaextensiontask"
@@ -87,14 +83,34 @@ else
ext.name = 'cparse-jruby'
end
- task :compile => ['lib/racc/parser-text.rb']
-
task gem_build_path => [:compile] do
add_file_to_gem 'lib/racc/cparse-jruby.jar'
end
+end
+task :test_pure do
+ ENV["PURERUBY"] = "1"
+ Rake.application[:test].invoke
end
task :test => :compile
+require 'rubocop/rake_task'
+RuboCop::RakeTask.new(:rubocop) do |task|
+ task.options = ['-a', '-D']
+end
+
+task :pry, [:grammar_file] do |t, args|
+ require 'pry'
+ require 'racc'
+ require 'racc/grammar_file_parser'
+ puts "Parsing #{args[:grammar_file]}"
+ grammar = Racc::GrammarFileParser.parse_file(args[:grammar_file]).grammar
+ grammar.pry
+end
+
Hoe.add_include_dirs('.:lib/racc')
+
+rule '.rb' => '.rl' do |t|
+ sh "ragel -F1 -R #{t.source} -o #{t.name}"
+end
diff --git a/TODO b/TODO
index 8e68e6e4..ca25bfad 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,46 @@
-* check 'error' token handling.
-* interactive transition table monitor.
-* support backtracking.
-* output Ruby extention library?
-* LL(k)? (But it should not be called Racc)
+TO CHECK:
+
+- Try grammars which contain non-ASCII chars.
+
+DIAGNOSTICS:
+
+- On conflict, try to find a series of tokens which can be parsed BOTH by
+ an automaton which resolves the conflict in one way, and by an automaton
+ which resolves the conflict in the other way.
+
+ This problem is not solvable in the general case. Just do a recursive search,
+ limiting the search space enough to terminate quickly.
+
+ First though, we need to abstract the classes which represent LR states and
+ automatons a bit more, making them amenable to use for general-purpose
+ analysis and manipulation, not just solely doing what Racc needs to generate
+ a parser.
+
+- If we can do that, show alternative derivation trees for the same tokens.
+
+- Any other compiler checks or warnings which we should add?
+
+PARSER CORE:
+
+- Generate LR(1) rather than LALR(1) parsers.
+
+- Add a directive to manually resolve one specific conflict.
+ This will also be used internally for the next point:
+
+- Add *, ?, +, as well as parentheses for grouping, to the understood grammar
+ file format.
+
+RUNTIME:
+
+- Generate more efficient parsers!
+
+OTHER:
+
+- Documentation
+
+- Full code review and cleanup
+
+- Make the code RuboCop-compliant
+- Then add RC to the build process
+
+- Any further tests which can be added to verify correctness?
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 00000000..11adeee9
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,36 @@
+---
+image: Visual Studio 2017
+
+clone_depth: 10
+
+build: off
+deploy: off
+
+install:
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
+ - ps: |
+ $rv = $env:ruby_version
+ if ( $rv -lt '24' ) {
+ gem install minitest hoe rake-compiler rubocop:0.68.1 --no-document --conservative
+ } else {
+ gem install minitest hoe rake-compiler rubocop --no-document --conservative
+ }
+ if ( $rv.EndsWith('-x64') ) {
+ C:/msys64/usr/bin/pacman -S --noconfirm --needed --noprogressbar mingw-w64-x86_64-ragel
+ $env:PATH = "C:/Ruby$rv/bin;$env:PATH;C:/msys64/mingw64/bin"
+ } else {
+ C:/msys64/usr/bin/pacman -S --noconfirm --needed --noprogressbar mingw-w64-i686-ragel
+ $env:PATH = "C:/Ruby$rv/bin;$env:PATH;C:/msys64/mingw32/bin"
+ }
+
+test_script:
+ - rake -rdevkit test
+
+environment:
+ matrix:
+ - ruby_version: "24"
+ - ruby_version: "24-x64"
+ - ruby_version: "25"
+ - ruby_version: "25-x64"
+ - ruby_version: "26"
+ - ruby_version: "26-x64"
diff --git a/bin/racc b/bin/racc
index d8764bce..beefa394 100755
--- a/bin/racc
+++ b/bin/racc
@@ -1,203 +1,173 @@
#!/usr/bin/env ruby
#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of LGPL, see the file "COPYING".
-#
-
-require 'racc/static'
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
+
+require 'racc'
+require 'racc/parser'
+require 'racc/grammar_file_parser'
+require 'racc/parser_file_generator'
+require 'racc/state_summary_generator'
+require 'racc/color'
require 'optparse'
+include Racc::Color
+
def main
- output = nil
- debug_parser = false
- make_logfile = false
- logfilename = nil
+ output_file = nil
+ check_only = false
+ verbose = false
+ output_format = :ruby
+
+ debug_parser = false
make_executable = false
- rubypath = nil
- embed_runtime = false
- debug_flags = Racc::DebugFlags.new
- line_convert = true
- line_convert_all = false
- omit_action_call = true
- superclass = nil
- check_only = false
- verbose = false
- profiler = RaccProfiler.new(false)
+ rubypath = nil
+ embed_runtime = false
+ superclass = nil
+
+ profiler = RaccProfiler.new(!!ENV['PROFILE'])
+
+ Racc::Color.enabled = $stderr.tty?
parser = OptionParser.new
parser.banner = "Usage: #{File.basename($0)} [options] "
+ parser.separator 'General:'
parser.on('-o', '--output-file=PATH',
- 'output file name [.tab.rb]') {|name|
- output = name
- }
- parser.on('-t', '--debug', 'Outputs debugging parser.') {|fl|
- debug_parser = fl
+ 'Output file name [.rb / .html]') { |name|
+ output_file = name
}
- parser.on('-g', 'Equivalent to -t (obsolete).') {|fl|
- $stderr.puts "racc -g is obsolete. Use racc -t instead." if $VERBOSE
- debug_parser = fl
+ parser.on('-C', '--check-only', 'Check syntax and quit immediately') {
+ check_only = true
}
- parser.on('-v', '--verbose',
- 'Creates .output log file.') {|fl|
- make_logfile = fl
+ parser.on('-S', '--states', 'Output summary of parser states as HTML') {
+ output_format = :html
}
- parser.on('-O', '--log-file=PATH',
- 'Log file name [.output]') {|path|
- make_logfile = true
- logfilename = path
- }
- parser.on('-e', '--executable [RUBYPATH]', 'Makes executable parser.') {|path|
- executable = true
- rubypath = (path == 'ruby' ? nil : path)
- }
- parser.on('-E', '--embedded', "Embeds Racc runtime in output.") {
- embed_runtime = true
- }
- parser.on('--line-convert-all', 'Converts line numbers of user codes.') {
- line_convert_all = true
- }
- parser.on('-l', '--no-line-convert', 'Never convert line numbers.') {
- line_convert = false
- line_convert_all = false
- }
- parser.on('-a', '--no-omit-actions', 'Never omit actions.') {
- omit_action_call = false
- }
- parser.on('--superclass=CLASSNAME',
- 'Uses CLASSNAME instead of Racc::Parser.') {|name|
- superclass = name
+ parser.on('--version', 'Print version and quit') {
+ core = Racc::Parser.racc_runtime_type == 'ruby' ? '(ruby core)' : '(c core)'
+ puts "racc #{Racc::VERSION}, codename: #{Racc::CODENAME} #{core}"
+ exit 0
}
- parser.on('--runtime=FEATURE',
- "Uses FEATURE instead of 'racc/parser'") {|feat|
- runtime = feature
+ parser.on('--copyright', 'Print copyright and quit') {
+ puts Racc::COPYRIGHT
+ exit 0
}
- parser.on('-C', '--check-only', 'Checks syntax and quit immediately.') {|fl|
- check_only = fl
+ parser.on('--help', 'Print this message and quit') {
+ puts parser.help
+ exit 1
}
- parser.on('-S', '--output-status', 'Outputs internal status time to time.') {
+ parser.separator ''
+ parser.separator 'Console output:'
+ parser.on('-v', '--verbose', 'Display extra diagnostic information') {
verbose = true
}
- parser.on('-P', 'Enables generator profile') {
- profiler = RaccProfiler.new(true)
+ parser.on('--[no-]color', 'Force colored output on or off') { |fl|
+ Racc::Color.enabled = fl
}
- parser.on('-D flags', "Flags for Racc debugging (do not use).") {|flags|
- debug_flags = Racc::DebugFlags.parse_option_string(flags)
+ parser.separator ''
+ parser.separator 'Ruby parser code output:'
+ parser.on('-t', '--debug', 'Output debugging parser') {
+ debug_parser = true
}
- #parser.on('--no-extensions', 'Run Racc without any Ruby extension.') {
- # Racc.const_set :Racc_No_Extentions, true
- #}
- parser.on('--version', 'Prints version and quit.') {
- puts "racc version #{Racc::Version}"
- exit 0
+ parser.on('-e', '--executable [RUBYPATH]', 'Output executable parser') { |path|
+ make_executable = true
+ rubypath = path || 'ruby'
}
- parser.on('--runtime-version', 'Prints runtime version and quit.') {
- printf "racc runtime version %s (rev. %s); %s\n",
- Racc::Parser::Racc_Runtime_Version,
- Racc::Parser::Racc_Runtime_Revision,
- if Racc::Parser.racc_runtime_type == 'ruby'
- sprintf('ruby core version %s (rev. %s)',
- Racc::Parser::Racc_Runtime_Core_Version_R,
- Racc::Parser::Racc_Runtime_Core_Revision_R)
- else
- sprintf('c core version %s (rev. %s)',
- Racc::Parser::Racc_Runtime_Core_Version_C,
- Racc::Parser::Racc_Runtime_Core_Revision_C)
- end
- exit 0
+ parser.on('-E', '--embedded', "Embed Racc runtime in output") {
+ embed_runtime = true
}
- parser.on('--copyright', 'Prints copyright and quit.') {
- puts Racc::Copyright
- exit 0
+ parser.on('--superclass=CLASSNAME',
+ 'Use CLASSNAME as superclass instead of Racc::Parser') { |name|
+ superclass = name
}
- parser.on('--help', 'Prints this message and quit.') {
- puts parser.help
+
+ fail = proc do |err|
+ $stderr.puts "#{red('Error: ')}#{err}"
+ $stderr.puts
+ $stderr.puts parser.help
exit 1
- }
+ end
+
begin
parser.parse!
rescue OptionParser::ParseError => err
- $stderr.puts err.message
- $stderr.puts parser.help
- exit 1
+ fail[err.message]
end
+
if ARGV.empty?
- $stderr.puts 'no input'
- exit 1
- end
- if ARGV.size > 1
- $stderr.puts 'too many input'
- exit 1
+ fail['no input file']
+ elsif ARGV.size > 1
+ fail['too many input files']
end
input = ARGV[0]
begin
+ if output_file.nil?
+ output_file = make_filename(input, '.rb')
+ if File.exist?(output_file)
+ $stderr.puts "#{red('Error: ')}'#{output_file}' already exists. " \
+ "Please use '-o #{output_file}' if you are sure that you want to " \
+ "overwrite this file."
+ exit 1
+ end
+ end
+
$stderr.puts 'Parsing grammar file...' if verbose
- result = profiler.section('parse') {
- parser = Racc::GrammarFileParser.new(debug_flags)
+ result = profiler.section('parse') do
+ parser = Racc::GrammarFileParser.new
parser.parse(File.read(input), File.basename(input))
- }
+ end
if check_only
- $stderr.puts 'syntax ok'
+ $stderr.puts "Syntax is OK for #{input}"
exit 0
end
- $stderr.puts 'Generating LALR states...' if verbose
- states = profiler.section('nfa') {
- Racc::States.new(result.grammar).nfa
- }
-
- $stderr.puts "Resolving #{states.size} states..." if verbose
- profiler.section('dfa') {
- states.dfa
- }
-
- $stderr.puts 'Creating parser file...' if verbose
- params = result.params.dup
- # Overwrites parameters given by a grammar file with command line options.
- params.superclass = superclass if superclass
- params.omit_action_call = true if omit_action_call
- # From command line option
- if make_executable
- params.make_executable = true
- params.interpreter = rubypath
+ $stderr.puts 'Generating and resolving LALR states...' if verbose
+ states = profiler.section('nfa') do
+ result.grammar.states
end
- params.debug_parser = debug_parser
- params.convert_line = line_convert
- params.convert_line_all = line_convert_all
- params.embed_runtime = embed_runtime
- profiler.section('generation') {
- generator = Racc::ParserFileGenerator.new(states, params)
- generator.generate_parser_file(output || make_filename(input, '.tab.rb'))
- }
- if make_logfile
- profiler.section('logging') {
- $stderr.puts 'Creating log file...' if verbose
- logfilename ||= make_filename(output || File.basename(input), '.output')
- File.open(logfilename, 'w') {|f|
- Racc::LogFileGenerator.new(states, debug_flags).output f
- }
- }
+ if output_format == :ruby
+ $stderr.puts 'Creating parser file...' if verbose
+ params = result.params.dup
+ # Overwrite parameters from grammar file with command line options
+ params.superclass = superclass if superclass
+ if make_executable
+ params.make_executable = true
+ params.interpreter = rubypath
+ end
+ params.debug_parser = debug_parser
+ params.embed_runtime = embed_runtime
+ profiler.section('generation') do
+ generator = Racc::ParserFileGenerator.new(states, params)
+ generator.generate_parser_file(output_file)
+ end
+ elsif output_format == :html
+ $stderr.puts 'Creating HTML summary of parser states...' if verbose
+ generator = Racc::StateSummaryGenerator.new(states, input)
+ generator.generate_summary_file(output_file)
end
- if debug_flags.status_logging
- log_useless states.grammar
- log_conflict states
- else
- report_useless states.grammar
- report_conflict states
+
+ warnings = result.grammar.warnings(verbose)
+ if warnings.any?
+ $stderr.puts
+ $stderr.puts warnings.map(&:to_s).join("\n\n")
+ $stderr.puts
+ $stderr.puts warning_summary(warnings)
+ if !verbose && warnings.any?(&:verbose_details?)
+ $stderr.puts "Run with --verbose for more details."
+ end
end
profiler.report
- rescue Racc::Error, Errno::ENOENT, Errno::EPERM => err
- raise if $DEBUG or debug_flags.any?
- lineno = err.message.slice(/\A\d+:/).to_s
- $stderr.puts "#{File.basename $0}: #{input}:#{lineno} #{err.message.strip}"
+ rescue Racc::CompileError, Errno::ENOENT, Errno::EPERM => err
+ raise if $DEBUG
+ message = err.message.dup # string may be frozen literal
+ lineno = message.slice!(/\A\d+:/).to_s
+ location = lineno.empty? ? bright("#{input}:") : bright("#{input}:#{lineno}")
+ $stderr.puts "#{red('Error: ')}#{location} #{message.strip}"
exit 1
end
end
@@ -206,71 +176,42 @@ def make_filename(path, suffix)
path.sub(/(?:\..*?)?\z/, suffix)
end
-def report_conflict(states)
- if states.should_report_srconflict?
- $stderr.puts "#{states.n_srconflicts} shift/reduce conflicts"
- end
- if states.rrconflict_exist?
- $stderr.puts "#{states.n_rrconflicts} reduce/reduce conflicts"
- end
-end
-
-def log_conflict(states)
- logging('w') {|f|
- f.puts "ex#{states.grammar.n_expected_srconflicts}"
- if states.should_report_srconflict?
- f.puts "sr#{states.n_srconflicts}"
- end
- if states.rrconflict_exist?
- f.puts "rr#{states.n_rrconflicts}"
- end
- }
+def warning_summary(warnings)
+ freq = Hash.new(0)
+ warnings.each { |w| freq[w.type] += 1 }
+ types = {
+ useless_terminal: 'useless terminal',
+ useless_nonterminal: 'useless nonterminal',
+ useless_prec: "useless '=' precedence declaration",
+ useless_rule: 'useless rule',
+ sr_conflict: 'shift/reduce conflict',
+ rr_conflict: 'reduce/reduce conflict'
+ }
+ order = [:useless_terminal, :useless_nonterminal, :useless_prec,
+ :useless_rule, :sr_conflict, :rr_conflict]
+
+ freq.sort_by { |k, v| order.index(k) }.map { |k, v| pluralize(v, types[k]) }.join(', ')
end
-def report_useless(grammar)
- if grammar.useless_nonterminal_exist?
- $stderr.puts "#{grammar.n_useless_nonterminals} useless nonterminals"
- end
- if grammar.useless_rule_exist?
- $stderr.puts "#{grammar.n_useless_rules} useless rules"
+def pluralize(count, noun)
+ if count == 1
+ "#{count} #{noun}"
+ else
+ "#{count} #{noun}s"
end
- if grammar.start.useless?
- $stderr.puts 'fatal: start symbol does not derive any sentence'
- end
-end
-
-def log_useless(grammar)
- logging('a') {|f|
- if grammar.useless_nonterminal_exist?
- f.puts "un#{grammar.n_useless_nonterminals}"
- end
- if grammar.useless_rule_exist?
- f.puts "ur#{grammar.n_useless_rules}"
- end
- }
-end
-
-def logging(mode, &block)
- File.open("log/#{File.basename(ARGV[0])}", mode, &block)
end
class RaccProfiler
def initialize(really)
@really = really
@log = []
- unless ::Process.respond_to?(:times)
- # Ruby 1.6
- @class = ::Time
- else
- @class = ::Process
- end
end
def section(name)
if @really
- t1 = @class.times.utime
+ t1 = Process.times.utime
result = yield
- t2 = @class.times.utime
+ t2 = Process.times.utime
@log.push [name, t2 - t1]
result
else
diff --git a/bin/racc2y b/bin/racc2y
deleted file mode 100755
index f88d73ed..00000000
--- a/bin/racc2y
+++ /dev/null
@@ -1,195 +0,0 @@
-#!/usr/local/bin/ruby
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is feee software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the LGPL, see the file "COPYING".
-#
-
-require 'racc/grammarfileparser'
-require 'racc/info'
-require 'optparse'
-
-def main
- @with_action = true
- with_header = false
- with_inner = false
- with_footer = false
- output = nil
- parser = OptionParser.new
- parser.banner = "Usage: #{File.basename($0)} [-AHIF] [-oFILENAME] GRAMMARFILE"
- parser.on('-o', '--output=FILENAME', 'output file name [.yacc]') {|name|
- output = name
- }
- parser.on('-A', '--without-action', 'Does not include actions.') {
- @with_action = false
- }
- parser.on('-H', '--with-header', 'Includes header part.') {
- with_header = true
- }
- parser.on('-I', '--with-inner', 'Includes inner part.') {
- with_inner = true
- }
- parser.on('-F', '--with-footer', 'Includes footer part.') {
- with_footer = true
- }
- parser.on('--version', 'Prints version and quit.') {
- puts "racc2y version #{Racc::Version}"
- exit 0
- }
- parser.on('--copyright', 'Prints copyright and quit.') {
- puts Racc::Copyright
- exit 0
- }
- parser.on('--help', 'Prints this message and quit.') {
- puts parser.help
- exit 1
- }
- begin
- parser.parse!
- rescue OptionParser::ParseError => err
- $stderr.puts err.message
- $stderr.puts parser.help
- exit 1
- end
- if ARGV.empty?
- $stderr.puts "no input file"
- exit 1
- end
- unless ARGV.size == 1
- $stderr.puts "too many inputs"
- exit 1
- end
- input = ARGV[0]
-
- begin
- result = Racc::GrammarFileParser.parse_file(input)
- result.grammar.init
- File.open(output || "#{input}.yacc", 'w') {|f|
- f.puts "/* generated from #{input} */"
- if with_header
- f.puts
- f.puts '%{'
- print_user_codes f, result.params.header
- f.puts '%}'
- end
- f.puts
- print_terminals f, result.grammar
- f.puts
- print_precedence_table f, precedence_table(result.grammar)
- f.puts
- f.puts '%%'
- print_grammar f, result.grammar
- f.puts '%%'
- if with_inner
- f.puts '/*---- inner ----*/'
- print_user_codes f, result.params.inner
- end
- if with_footer
- f.puts '/*---- footer ----*/'
- print_user_codes f, result.params.footer
- end
- }
- rescue SystemCallError => err
- $stderr.puts err.message
- exit 1
- end
-end
-
-def print_terminals(f, grammar)
- init_indent = '%token'.size
- f.print '%token'
- columns = init_indent
- grammar.symboltable.each_terminal do |t|
- next unless t.terminal?
- next if t.dummy?
- next if t == grammar.symboltable.anchor
- next if t == grammar.symboltable.error
- unless t.value.kind_of?(String)
- if columns > 60
- f.puts
- f.print ' ' * init_indent
- columns = init_indent
- end
- columns += f.write(" #{yacc_symbol(t)}")
- end
- end
- f.puts
-end
-
-def precedence_table(grammar)
- table = []
- grammar.symboltable.select {|sym| sym.precedence }.each do |sym|
- (table[sym.prec] ||= [sym.assoc]).push sym
- end
- table.compact
-end
-
-def print_precedence_table(f, table)
- return if table.empty?
- f.puts '/* precedance table */'
- table.each do |syms|
- assoc = syms.shift
- f.printf '%%%-8s ', assoc.to_s.downcase
- f.puts syms.map {|s| yacc_symbol(s) }.join(' ')
- end
- f.puts
-end
-
-def print_grammar(f, grammar)
- prev_target = nil
- indent = 10
- embactions = []
- grammar.each do |rule|
- if rule.target.dummy?
- embactions.push rule.action unless rule.action.empty?
- next
- end
- if rule.target == prev_target
- f.print ' ' * indent, '|'
- else
- prev_target = rule.target
- f.printf "\n%-10s:", yacc_symbol(prev_target)
- end
- rule.symbols.each do |s|
- if s.dummy? # target of dummy rule for embedded action
- f.puts
- print_action f, embactions.shift, indent
- f.print ' ' * (indent + 1)
- else
- f.print ' ', yacc_symbol(s)
- end
- end
- if rule.specified_prec
- f.print ' %prec ', yacc_symbol(rule.specified_prec)
- end
- f.puts
- unless rule.action.empty?
- print_action f, rule.action, indent
- end
- end
-end
-
-def print_action(f, action, indent)
- return unless @with_action
- f.print ' ' * (indent + 4), "{\n"
- f.print ' ' * (indent + 6), action.source.text.strip, "\n"
- f.print ' ' * (indent + 4) , "}\n"
-end
-
-def print_user_codes(f, srcs)
- return if srcs.empty?
- srcs.each do |src|
- f.puts src.text
- end
-end
-
-def yacc_symbol(s)
- s.to_s.gsub('"', "'")
-end
-
-main
diff --git a/bin/y2racc b/bin/y2racc
deleted file mode 100755
index 38bd3669..00000000
--- a/bin/y2racc
+++ /dev/null
@@ -1,339 +0,0 @@
-#!/usr/local/bin/ruby
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public Lisence version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-
-require 'racc/info'
-require 'strscan'
-require 'forwardable'
-require 'optparse'
-
-def main
- @with_action = true
- @with_header = false
- @with_usercode = false
- cname = 'MyParser'
- input = nil
- output = nil
- parser = OptionParser.new
- parser.banner = "Usage: #{File.basename($0)} [-Ahu] [-c ] [-o ] "
- parser.on('-o', '--output=FILENAME', 'output file name [.racc]') {|name|
- output = name
- }
- parser.on('-c', '--classname=NAME', "Name of the parser class. [#{cname}]") {|name|
- cname = name
- }
- parser.on('-A', '--without-action', 'Does not include actions.') {
- @with_action = false
- }
- parser.on('-h', '--with-header', 'Includes header (%{...%}).') {
- @with_header = true
- }
- parser.on('-u', '--with-user-code', 'Includes user code.') {
- @with_usercode = true
- }
- parser.on('--version', 'Prints version and quit.') {
- puts "y2racc version #{Racc::Version}"
- exit 0
- }
- parser.on('--copyright', 'Prints copyright and quit.') {
- puts Racc::Copyright
- exit 0
- }
- parser.on('--help', 'Prints this message and quit.') {
- puts parser.help
- exit 1
- }
- begin
- parser.parse!
- rescue OptionParser::ParseError => err
- $stderr.puts err.message
- $stderr.puts parser.help
- exit 1
- end
- if ARGV.empty?
- $stderr.puts 'no input'
- exit 1
- end
- if ARGV.size > 1
- $stderr.puts 'too many input'
- exit 1
- end
- input = ARGV[0]
-
- begin
- result = YaccFileParser.parse_file(input)
- File.open(output || "#{input}.racc", 'w') {|f|
- convert cname, result, f
- }
- rescue SystemCallError => err
- $stderr.puts err.message
- exit 1
- end
-end
-
-def convert(classname, result, f)
- init_indent = 'token'.size
- f.puts %<# Converted from "#{result.filename}" by y2racc version #{Racc::Version}>
- f.puts
- f.puts "class #{classname}"
- unless result.terminals.empty?
- f.puts
- f.print 'token'
- columns = init_indent
- result.terminals.each do |t|
- if columns > 60
- f.puts
- f.print ' ' * init_indent
- columns = init_indent
- end
- columns += f.write(" #{t}")
- end
- f.puts
- end
- unless result.precedence_table.empty?
- f.puts
- f.puts 'preclow'
- result.precedence_table.each do |assoc, toks|
- f.printf " %-8s %s\n", assoc, toks.join(' ') unless toks.empty?
- end
- f.puts 'prechigh'
- end
- if result.start
- f.puts
- f.puts "start #{@start}"
- end
-
- f.puts
- f.puts 'rule'
- texts = @with_action ? result.grammar : result.grammar_without_actions
- texts.each do |text|
- f.print text
- end
-
- if @with_header and result.header
- f.puts
- f.puts '---- header'
- f.puts result.header
- end
- if @with_usercode and result.usercode
- f.puts
- f.puts '---- footer'
- f.puts result.usercode
- end
-end
-
-class ParseError < StandardError; end
-
-class StringScanner_withlineno
- def initialize(src)
- @s = StringScanner.new(src)
- @lineno = 1
- end
-
- extend Forwardable
- def_delegator "@s", :eos?
- def_delegator "@s", :rest
-
- attr_reader :lineno
-
- def scan(re)
- advance_lineno(@s.scan(re))
- end
-
- def scan_until(re)
- advance_lineno(@s.scan_until(re))
- end
-
- def skip(re)
- str = advance_lineno(@s.scan(re))
- str ? str.size : nil
- end
-
- def getch
- advance_lineno(@s.getch)
- end
-
- private
-
- def advance_lineno(str)
- @lineno += str.count("\n") if str
- str
- end
-end
-
-class YaccFileParser
-
- Result = Struct.new(:terminals, :precedence_table, :start,
- :header, :grammar, :usercode, :filename)
- class Result # reopen
- def initialize
- super
- self.terminals = []
- self.precedence_table = []
- self.start = nil
- self.grammar = []
- self.header = nil
- self.usercode = nil
- self.filename = nil
- end
-
- def grammar_without_actions
- grammar().map {|text| text[0,1] == '{' ? '{}' : text }
- end
- end
-
- def YaccFileParser.parse_file(filename)
- new().parse(File.read(filename), filename)
- end
-
- def parse(src, filename = '-')
- @result = Result.new
- @filename = filename
- @result.filename = filename
- s = StringScanner_withlineno.new(src)
- parse_header s
- parse_grammar s
- @result
- end
-
- private
-
- COMMENT = %r\*[^*]*\*+(?:[^/*][^*]*\*+)*/>
- CHAR = /'((?:[^'\\]+|\\.)*)'/
- STRING = /"((?:[^"\\]+|\\.)*)"/
-
- def parse_header(s)
- skip_until_percent s
- until s.eos?
- case
- when t = s.scan(/left/)
- @result.precedence_table.push ['left', scan_symbols(s)]
- when t = s.scan(/right/)
- @result.precedence_table.push ['right', scan_symbols(s)]
- when t = s.scan(/nonassoc/)
- @result.precedence_table.push ['nonassoc', scan_symbols(s)]
- when t = s.scan(/token/)
- list = scan_symbols(s)
- list.shift if /\A<(.*)>\z/ =~ list[0]
- @result.terminals.concat list
- when t = s.scan(/start/)
- @result.start = scan_symbols(s)[0]
- when s.skip(%r<(?:
- type | union | expect | thong | binary |
- semantic_parser | pure_parser | no_lines |
- raw | token_table
- )\b>x)
- skip_until_percent s
- when s.skip(/\{/) # header (%{...%})
- str = s.scan_until(/\%\}/)
- str.chop!
- str.chop!
- @result.header = str
- skip_until_percent s
- when s.skip(/\%/) # grammar (%%...)
- return
- else
- raise ParseError, "#{@filename}:#{s.lineno}: scan error"
- end
- end
- end
-
- def skip_until_percent(s)
- until s.eos?
- s.skip /[^\%\/]+/
- next if s.skip(COMMENT)
- return if s.getch == '%'
- end
- end
-
- def scan_symbols(s)
- list = []
- until s.eos?
- s.skip /\s+/
- if s.skip(COMMENT)
- ;
- elsif t = s.scan(CHAR)
- list.push t
- elsif t = s.scan(STRING)
- list.push t
- elsif s.skip(/\%/)
- break
- elsif t = s.scan(/\S+/)
- list.push t
- else
- raise ParseError, "#{@filename}:#{@lineno}: scan error"
- end
- end
- list
- end
-
- def parse_grammar(s)
- buf = []
- until s.eos?
- if t = s.scan(/[^%'"{\/]+/)
- buf.push t
- break if s.eos?
- end
- if s.skip(/\{/)
- buf.push scan_action(s)
- elsif t = s.scan(/'(?:[^'\\]+|\\.)*'/) then buf.push t
- elsif t = s.scan(/"(?:[^"\\]+|\\.)*"/) then buf.push t
- elsif t = s.scan(COMMENT) then buf.push t
- elsif s.skip(/%prec\b/) then buf.push '='
- elsif s.skip(/%%/)
- @result.usercode = s.rest
- break
- else
- buf.push s.getch
- end
- end
- @result.grammar = buf
- end
-
- def scan_action(s)
- buf = '{'
- nest = 1
- until s.eos?
- if t = s.scan(%r<[^/{}'"]+>)
- buf << t
- break if s.eos?
- elsif t = s.scan(COMMENT)
- buf << t
- elsif t = s.scan(CHAR)
- buf << t
- elsif t = s.scan(STRING)
- buf << t
- else
- c = s.getch
- buf << c
- case c
- when '{'
- nest += 1
- when '}'
- nest -= 1
- return buf if nest == 0
- end
- end
- end
- $stderr.puts "warning: unterminated action in #{@filename}"
- buf
- end
-
-end
-
-unless Object.method_defined?(:funcall)
- class Object
- alias funcall __send__
- end
-end
-
-
-main
diff --git a/ext/racc/com/headius/racc/Cparse.java b/ext/racc/com/headius/racc/Cparse.java
index 1d740656..459f3f69 100644
--- a/ext/racc/com/headius/racc/Cparse.java
+++ b/ext/racc/com/headius/racc/Cparse.java
@@ -1,3 +1,17 @@
+/*
+ Cparse.java -- Racc Runtime Core for JRuby
+
+ Copyright (c) 2016 Charles Oliver Nutter
+
+ Ported from and distributed under the same licence as cparse.c
+
+ cparse.c -- Racc Runtime Core
+
+ Copyright (c) 1999-2006 Minero Aoki
+
+ This library is free software.
+ You can distribute/modify this program under the same terms of ruby.
+*/
package com.headius.racc;
import org.jruby.Ruby;
@@ -7,6 +21,7 @@
import org.jruby.RubyContinuation;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
+import org.jruby.RubyKernel;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
@@ -24,13 +39,14 @@
import org.jruby.runtime.Helpers;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
public class Cparse implements Library {
- public static final String RACC_VERSION = "1.4.13"; // TODO: parse from Cparse.c
+ public static final String RACC_VERSION = "2.0.0-dev"; // TODO: parse from Cparse.c
public enum TokenType {
DEFAULT(-1),
@@ -233,10 +249,6 @@ private void SHIFT(ThreadContext context, int act, IRubyObject tok, IRubyObject
shift(context, act, tok, val);
}
- private int REDUCE(ThreadContext context, int act) {
- return reduce(context, act);
- }
-
public void parse_main(ThreadContext context, IRubyObject tok, IRubyObject val, boolean resume) {
Ruby runtime = context.runtime;
@@ -267,7 +279,7 @@ public void parse_main(ThreadContext context, IRubyObject tok, IRubyObject val,
i = assert_integer(tmp);
D_printf("read_next=%d\n", read_next);
- if (read_next && (this.t != vFINAL_TOKEN)) {
+ if (read_next && (!this.t.equals(vFINAL_TOKEN))) {
if (this.lex_is_iterator) {
D_puts("resuming...");
if (this.fin != 0) throw runtime.newArgumentError("token given after EOF");
@@ -354,7 +366,21 @@ public void parse_main(ThreadContext context, IRubyObject tok, IRubyObject val,
}
else if (act < 0 && act > -(this.reduce_n)) {
D_puts("reduce");
- REDUCE(context, act);
+ { // macro REDUCE
+ switch (reduce(context, act)) {
+ case 0: /* normal */
+ break;
+ case 1: /* yyerror */
+ branch = USER_YYERROR;
+ continue BRANCH;
+ case 2: /* yyaccept */
+ D_puts("u accept");
+ branch = ACCEPT;
+ continue BRANCH;
+ default:
+ break;
+ }
+ }
}
else if (act == -(this.reduce_n)) {
branch = ERROR; continue BRANCH;
@@ -367,6 +393,7 @@ else if (act == this.shift_n) {
throw runtime.newRaiseException(RaccBug, "[Cparse Bug] unknown act value " + act);
}
+ // fall through
case ERROR_RECOVERED:
if (this.debug) {
@@ -389,9 +416,10 @@ else if (act == this.shift_n) {
call_onerror.call(context, this.parser, this.parser, this.t, val, this.vstack);
}
+ // fall through
case USER_YYERROR:
if (this.errstatus == 3) {
- if (this.t == vFINAL_TOKEN) {
+ if (this.t.equals(vFINAL_TOKEN)) {
this.retval = runtime.getFalse();
this.fin = CP_FIN_EOT;
return;
@@ -464,7 +492,21 @@ else if (act == this.shift_n) {
}
else if (act < 0 && act > -(this.reduce_n)) {
D_puts("e reduce");
- REDUCE(context, act);
+ { // macro REDUCE
+ switch (reduce(context, act)) {
+ case 0: /* normal */
+ break;
+ case 1: /* yyerror */
+ branch = USER_YYERROR;
+ continue BRANCH;
+ case 2: /* yyaccept */
+ D_puts("u accept");
+ branch = ACCEPT;
+ continue BRANCH;
+ default:
+ break;
+ }
+ }
}
else if (act == this.shift_n) {
D_puts("e accept");
@@ -489,17 +531,18 @@ private void shift(ThreadContext context, int act, IRubyObject tok, IRubyObject
}
private int reduce(ThreadContext context, int act) {
- IRubyObject code;
ruleno = -act * 3;
IRubyObject tag = context.runtime.newSymbol("racc_jump");
- RubyContinuation rbContinuation = new RubyContinuation(context.runtime, context.runtime.newSymbol("racc_jump"));
- try {
- context.pushCatch(rbContinuation.getContinuation());
- code = reduce0(context);
- errstatus = assert_integer(parser.getInstanceVariable(ID_ERRSTATUS));
- } finally {
- context.popCatch();
- }
+ IRubyObject code = RubyKernel.rbCatch19(context, this,
+ tag,
+ CallBlock19.newCallClosure(this, getMetaClass(), Signature.NO_ARGUMENTS,
+ new BlockCallback() {
+ @Override
+ public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
+ return reduce0(context);
+ }
+ }, context));
+ errstatus = assert_integer(parser.getInstanceVariable(ID_ERRSTATUS));
return assert_integer(code);
}
@@ -527,13 +570,13 @@ private IRubyObject reduce0(ThreadContext context) {
/* call action */
if (len == 0) {
tmp = context.nil;
- if (mid != sym_noreduce)
+ if (!mid.equals(sym_noreduce))
tmp_v = runtime.newArray();
if (this.debug)
tmp_t = runtime.newArray();
}
else {
- if (mid != sym_noreduce) {
+ if (!mid.equals(sym_noreduce)) {
tmp_v = GET_TAIL(context, this.vstack, len);
tmp = ((RubyArray)tmp_v).entry(0);
}
@@ -547,7 +590,7 @@ private IRubyObject reduce0(ThreadContext context) {
}
CUT_TAIL(context, this.state, len);
}
- if (mid != sym_noreduce) {
+ if (!mid.equals(sym_noreduce)) {
if (this.use_result_var) {
tmp = Helpers.invoke(context, this.parser, mid.toString(), tmp_v, this.vstack, tmp);
}
@@ -590,7 +633,7 @@ private IRubyObject reduce0(ThreadContext context) {
D_puts("(goto) check[i] == nil");
branch = NOTFOUND; continue BRANCH;
}
- if (tmp != runtime.newFixnum(k1)) {
+ if (!tmp.equals(runtime.newFixnum(k1))) {
D_puts("(goto) check[i] != table[i]");
branch = NOTFOUND; continue BRANCH;
}
@@ -759,7 +802,7 @@ public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block)
}
}, context));
} catch (LexerUnroll maybeOurs) {
- if (maybeOurs == lexerUnroll) {
+ if (maybeOurs.equals(lexerUnroll)) {
return;
}
}
diff --git a/ext/racc/cparse.c b/ext/racc/cparse.c
index ee9afe1f..d5e367c0 100644
--- a/ext/racc/cparse.c
+++ b/ext/racc/cparse.c
@@ -1,9 +1,9 @@
/*
cparse.c -- Racc Runtime Core
-
+
Copyright (c) 1999-2006 Minero Aoki
-
+
This library is free software.
You can distribute/modify this program under the same terms of ruby.
@@ -13,12 +13,17 @@
#include
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
/* -----------------------------------------------------------------------
Important Constants
----------------------------------------------------------------------- */
-#define RACC_VERSION "1.4.13"
-
#define DEFAULT_TOKEN -1
#define ERROR_TOKEN 1
#define FINAL_TOKEN 0
@@ -189,7 +194,7 @@ static VALUE racc_yyparse _((VALUE parser, VALUE lexer, VALUE lexmid,
VALUE arg, VALUE sysdebug));
static void call_lexer _((struct cparse_params *v));
-static VALUE lexer_i _((VALUE block_args, VALUE data, VALUE self));
+static VALUE lexer_i _((RB_BLOCK_CALL_FUNC_ARGLIST(block_args, data)));
static VALUE assert_array _((VALUE a));
static long assert_integer _((VALUE n));
@@ -197,6 +202,7 @@ static VALUE assert_hash _((VALUE h));
static VALUE initialize_params _((VALUE vparams, VALUE parser, VALUE arg,
VALUE lexer, VALUE lexmid));
static void cparse_params_mark _((void *ptr));
+static size_t cparse_params_memsize _((const void *ptr));
static void parse_main _((struct cparse_params *v,
VALUE tok, VALUE val, int resume));
@@ -214,35 +220,52 @@ static VALUE reduce0 _((VALUE block_args, VALUE data, VALUE self));
# define D_printf(fmt,arg)
#endif
+#undef RUBY_UNTYPED_DATA_WARNING
+#define RUBY_UNTYPED_DATA_WARNING 1
+
+static const rb_data_type_t cparse_params_type = {
+ "racc/cparse",
+ {
+ cparse_params_mark,
+ RUBY_TYPED_DEFAULT_FREE,
+ cparse_params_memsize,
+ },
+#ifdef RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0,
+ RUBY_TYPED_FREE_IMMEDIATELY,
+#endif
+};
+
static VALUE
racc_cparse(VALUE parser, VALUE arg, VALUE sysdebug)
{
- volatile VALUE vparams;
+ VALUE vparams;
struct cparse_params *v;
- vparams = Data_Make_Struct(CparseParams, struct cparse_params,
- cparse_params_mark, -1, v);
+ vparams = TypedData_Make_Struct(CparseParams, struct cparse_params,
+ &cparse_params_type, v);
D_puts("starting cparse");
v->sys_debug = RTEST(sysdebug);
vparams = initialize_params(vparams, parser, arg, Qnil, Qnil);
- v->lex_is_iterator = Qfalse;
+ v->lex_is_iterator = FALSE;
parse_main(v, Qnil, Qnil, 0);
+ RB_GC_GUARD(vparams);
return v->retval;
}
static VALUE
racc_yyparse(VALUE parser, VALUE lexer, VALUE lexmid, VALUE arg, VALUE sysdebug)
{
- volatile VALUE vparams;
+ VALUE vparams;
struct cparse_params *v;
- vparams = Data_Make_Struct(CparseParams, struct cparse_params,
- cparse_params_mark, -1, v);
+ vparams = TypedData_Make_Struct(CparseParams, struct cparse_params,
+ &cparse_params_type, v);
v->sys_debug = RTEST(sysdebug);
D_puts("start C yyparse");
vparams = initialize_params(vparams, parser, arg, lexer, lexmid);
- v->lex_is_iterator = Qtrue;
+ v->lex_is_iterator = TRUE;
D_puts("params initialized");
parse_main(v, Qnil, Qnil, 0);
call_lexer(v);
@@ -251,6 +274,7 @@ racc_yyparse(VALUE parser, VALUE lexer, VALUE lexmid, VALUE arg, VALUE sysdebug)
rb_id2name(v->lexmid));
}
+ RB_GC_GUARD(vparams);
return v->retval;
}
@@ -264,9 +288,8 @@ call_lexer(struct cparse_params *v)
static VALUE
lexer_iter(VALUE data)
{
- struct cparse_params *v;
+ struct cparse_params *v = rb_check_typeddata(data, &cparse_params_type);
- Data_Get_Struct(data, struct cparse_params, v);
rb_funcall(v->lexer, v->lexmid, 0);
return Qnil;
}
@@ -279,18 +302,17 @@ call_lexer(struct cparse_params *v)
#endif
static VALUE
-lexer_i(VALUE block_args, VALUE data, VALUE self)
+lexer_i(RB_BLOCK_CALL_FUNC_ARGLIST(block_args, data))
{
- struct cparse_params *v;
+ struct cparse_params *v = rb_check_typeddata(data, &cparse_params_type);
VALUE tok, val;
- Data_Get_Struct(data, struct cparse_params, v);
if (v->fin)
rb_raise(rb_eArgError, "extra token after EndOfToken");
extract_user_token(v, block_args, &tok, &val);
parse_main(v, tok, val, 1);
if (v->fin && v->fin != CP_FIN_ACCEPT)
- rb_iter_break();
+ rb_iter_break();
return Qnil;
}
@@ -317,9 +339,8 @@ assert_integer(VALUE n)
static VALUE
initialize_params(VALUE vparams, VALUE parser, VALUE arg, VALUE lexer, VALUE lexmid)
{
- struct cparse_params *v;
+ struct cparse_params *v = rb_check_typeddata(vparams, &cparse_params_type);
- Data_Get_Struct(vparams, struct cparse_params, v);
v->value_v = vparams;
v->parser = parser;
v->lexer = lexer;
@@ -348,7 +369,7 @@ initialize_params(VALUE vparams, VALUE parser, VALUE arg, VALUE lexer, VALUE lex
v->use_result_var = RTEST(rb_ary_entry(arg, 13));
}
else {
- v->use_result_var = Qtrue;
+ v->use_result_var = TRUE;
}
v->tstack = v->debug ? NEW_STACK() : Qnil;
@@ -364,7 +385,7 @@ initialize_params(VALUE vparams, VALUE parser, VALUE arg, VALUE lexer, VALUE lex
v->retval = Qnil;
v->fin = 0;
- v->lex_is_iterator = Qfalse;
+ v->lex_is_iterator = FALSE;
rb_iv_set(parser, "@vstack", v->vstack);
if (v->debug) {
@@ -402,6 +423,12 @@ cparse_params_mark(void *ptr)
rb_gc_mark(v->retval);
}
+static size_t
+cparse_params_memsize(const void *ptr)
+{
+ return sizeof(struct cparse_params);
+}
+
static void
extract_user_token(struct cparse_params *v, VALUE block_args,
VALUE *tok, VALUE *val)
@@ -413,12 +440,12 @@ extract_user_token(struct cparse_params *v, VALUE block_args,
return;
}
- if (TYPE(block_args) != T_ARRAY) {
+ if (!RB_TYPE_P(block_args, T_ARRAY)) {
rb_raise(rb_eTypeError,
- "%s() %s %s (must be Array[2])",
+ "%s() %s %"PRIsVALUE" (must be Array[2])",
v->lex_is_iterator ? rb_id2name(v->lexmid) : "next_token",
v->lex_is_iterator ? "yielded" : "returned",
- rb_class2name(CLASS_OF(block_args)));
+ rb_obj_class(block_args));
}
if (RARRAY_LEN(block_args) != 2) {
rb_raise(rb_eArgError,
@@ -457,7 +484,7 @@ parse_main(struct cparse_params *v, VALUE tok, VALUE val, int resume)
if (resume)
goto resume;
-
+
while (1) {
D_puts("");
D_puts("---- enter new loop ----");
@@ -516,7 +543,7 @@ parse_main(struct cparse_params *v, VALUE tok, VALUE val, int resume)
act_fixed:
D_printf("act=%ld\n", act);
goto handle_act;
-
+
notfound:
D_puts("(act) not found: use default");
act_value = AREF(v->action_default, v->curstate);
@@ -617,7 +644,7 @@ parse_main(struct cparse_params *v, VALUE tok, VALUE val, int resume)
D_puts("(err) found: can handle error token");
break;
-
+
error_pop:
D_puts("(err) act not found: can't handle error token; pop");
@@ -681,7 +708,7 @@ reduce(struct cparse_params *v, long act)
static VALUE
reduce0(VALUE val, VALUE data, VALUE self)
{
- struct cparse_params *v;
+ struct cparse_params *v = rb_check_typeddata(data, &cparse_params_type);
VALUE reduce_to, reduce_len, method_id;
long len;
ID mid;
@@ -689,7 +716,6 @@ reduce0(VALUE val, VALUE data, VALUE self)
long i, k1, k2;
VALUE goto_state;
- Data_Get_Struct(data, struct cparse_params, v);
reduce_len = rb_ary_entry(v->reduce_table, v->ruleno);
reduce_to = rb_ary_entry(v->reduce_table, v->ruleno+1);
method_id = rb_ary_entry(v->reduce_table, v->ruleno+2);
@@ -791,6 +817,8 @@ reduce0(VALUE val, VALUE data, VALUE self)
void
Init_cparse(void)
{
+#undef rb_intern
+#define rb_intern(str) rb_intern_const(str)
VALUE Racc, Parser;
ID id_racc = rb_intern("Racc");
@@ -804,12 +832,11 @@ Init_cparse(void)
}
rb_define_private_method(Parser, "_racc_do_parse_c", racc_cparse, 2);
rb_define_private_method(Parser, "_racc_yyparse_c", racc_yyparse, 4);
- rb_define_const(Parser, "Racc_Runtime_Core_Version_C",
- rb_str_new2(RACC_VERSION));
- rb_define_const(Parser, "Racc_Runtime_Core_Id_C",
- rb_str_new2("$originalId: cparse.c,v 1.8 2006/07/06 11:39:46 aamine Exp $"));
CparseParams = rb_define_class_under(Racc, "CparseParams", rb_cObject);
+ rb_undef_alloc_func(CparseParams);
+ rb_undef_method(CparseParams, "initialize");
+ rb_undef_method(CparseParams, "initialize_copy");
RaccBug = rb_eRuntimeError;
diff --git a/ext/racc/depend b/ext/racc/depend
index 7b06a880..441d4df0 100644
--- a/ext/racc/depend
+++ b/ext/racc/depend
@@ -1 +1,12 @@
-cparse.o: cparse.c $(hdrdir)/ruby.h $(topdir)/config.h $(hdrdir)/defines.h
+# AUTOGENERATED DEPENDENCIES START
+cparse.o: $(RUBY_EXTCONF_H)
+cparse.o: $(arch_hdrdir)/ruby/config.h
+cparse.o: $(hdrdir)/ruby/backward.h
+cparse.o: $(hdrdir)/ruby/defines.h
+cparse.o: $(hdrdir)/ruby/intern.h
+cparse.o: $(hdrdir)/ruby/missing.h
+cparse.o: $(hdrdir)/ruby/ruby.h
+cparse.o: $(hdrdir)/ruby/st.h
+cparse.o: $(hdrdir)/ruby/subst.h
+cparse.o: cparse.c
+# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/racc/extconf.rb b/ext/racc/extconf.rb
index 1e30abed..35bf7b17 100644
--- a/ext/racc/extconf.rb
+++ b/ext/racc/extconf.rb
@@ -1,7 +1,8 @@
-# $Id$
-
require 'mkmf'
+load File.join(File.dirname(__FILE__), '..', '..', 'lib', 'racc', 'info.rb')
have_func('rb_ary_subseq')
+$defs.push("-DRACC_VERSION=\"#{Racc::VERSION}\"")
+
create_makefile 'racc/cparse'
diff --git a/fastcache/extconf.rb b/fastcache/extconf.rb
deleted file mode 100644
index e010aa79..00000000
--- a/fastcache/extconf.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'mkmf'
-create_makefile 'corecache'
diff --git a/fastcache/fastcache.c b/fastcache/fastcache.c
deleted file mode 100644
index 2656ce8d..00000000
--- a/fastcache/fastcache.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- $Id$
-
- Copyright (C) 2005 Minero Aoki
-
- This program is free software.
- You can distribute/modify this program under the terms of
- the GNU LGPL, Lesser General Public Licese version 2.1.
-*/
-
-#include "ruby.h"
-
-static VALUE LALRcoreCache;
-
-struct item_holder {
- unsigned long hashval;
- VALUE core;
- VALUE state;
- struct item_holder *next;
-};
-
-struct lalr_state_cache {
- struct item_holder **bin;
- long size;
- long num;
-};
-
-static void
-lalrc_free(struct lalr_state_cache *p)
-{
- struct item_holder *tmp;
- long i;
-
- for (i = 0; i < p->size; i++) {
- while (tmp = p->bin[i]) {
- p->bin[i] = tmp->next;
- free(tmp);
- }
- }
- free(p->bin);
- free(p);
-}
-
-#define INIT_BIN 256
-
-static VALUE
-lalrc_s_new(VALUE self)
-{
- struct lalr_state_cache *cache;
-
- cache = ALLOC_N(struct lalr_state_cache, 1);
- cache->bin = ALLOC_N(struct item_holder*, INIT_BIN);
- cache->size = INIT_BIN;
- cache->num = 0;
- return Data_Wrap_Struct(LALRcoreCache, 0, lalrc_free, cache);
-}
-
-#define GET_LALRC(self, p) Data_Get_Struct(self, struct lalr_state_cache, p)
-
-static void
-lalrc_rehash(struct lalr_state_cache *p)
-{
- struct item_holder *top = 0, *tmp = 0;
- long i;
-
- for (i = p->size / 2; i < p->size; i++) {
- p->bin[i] = 0;
- }
- for (i = 0; i < p->size / 2; i++) {
- if (!p->bin[i])
- continue;
-
- tmp = p->bin[i];
- while (tmp->next)
- tmp = tmp->next;
- tmp->next = top;
- top = p->bin[i];
- p->bin[i] = 0;
- }
-
- while (top) {
- tmp = top;
- top = tmp->next;
- tmp->next = 0;
-
- i = tmp->hashval % p->size;
- if (p->bin[i]) {
- tmp->next = p->bin[i];
- p->bin[i] = tmp;
- }
- else {
- p->bin[i] = tmp;
- }
- }
-}
-
-static int
-coreeql(VALUE a, VALUE b)
-{
- long i;
-
- /* Check_Type(a, T_ARRAY);
- Check_Type(b, T_ARRAY); */
- if (RARRAY(a)->len != RARRAY(b)->len)
- return 0;
- for (i = 0; i < RARRAY(a)->len; i++)
- if (RARRAY(a)->ptr[i] != RARRAY(b)->ptr[i])
- return 0;
-
- return 1;
-}
-
-static unsigned long
-hashval(VALUE core)
-{
- unsigned long v = 0;
- long i, j;
-
- for (i = 0; i < RARRAY(core)->len; i++) {
- v *= RARRAY(core)->ptr[i];
- v ^= RARRAY(core)->ptr[i];
- }
- return v;
-}
-
-static VALUE
-lalrc_aref(VALUE self, VALUE core)
-{
- struct lalr_state_cache *p;
- unsigned long v;
- long i;
- struct item_holder *ad;
-
- /* Check_Type(core, T_ARRAY); */
- GET_LALRC(self, p);
- v = hashval(core);
- i = v % p->size;
- ad = p->bin[i];
- while (ad) {
- if (ad->hashval == v) {
- if (coreeql(core, ad->core)) {
- return ad->state;
- }
- else {
-printf(".");
- }
- }
- ad = ad->next;
- }
- return Qnil;
-}
-
-static VALUE
-lalrc_add_direct(VALUE self, VALUE core, VALUE state)
-{
- struct lalr_state_cache *p;
- struct item_holder *ad;
- long i;
-
- GET_LALRC(self, p);
- ad = ALLOC_N(struct item_holder, 1);
- ad->hashval = hashval(core);
- ad->core = core;
- ad->state = state;
-
- i = ad->hashval % p->size;
- ad->next = p->bin[i];
- p->bin[i] = ad;
- p->num++;
- if ((p->num / p->size) >= 1) {
- REALLOC_N(p->bin, struct item_holder*, p->size * 2);
- p->size *= 2;
- lalrc_rehash(p);
- }
- return Qnil;
-}
-
-void
-Init_corecache(void)
-{
- LALRcoreCache = rb_define_class("LALRcoreCache", rb_cObject);
- rb_define_singleton_method(LALRcoreCache, "new", lalrc_s_new, 0);
- rb_define_method(LALRcoreCache, "[]", lalrc_aref, 1);
- rb_define_method(LALRcoreCache, "[]=", lalrc_add_direct, 2);
-}
diff --git a/lib/racc.rb b/lib/racc.rb
index f6e4ac03..eefd0327 100644
--- a/lib/racc.rb
+++ b/lib/racc.rb
@@ -1,5 +1,3 @@
-require 'racc/compat'
-require 'racc/debugflags'
require 'racc/grammar'
require 'racc/state'
require 'racc/exception'
diff --git a/lib/racc/color.rb b/lib/racc/color.rb
new file mode 100644
index 00000000..27194f73
--- /dev/null
+++ b/lib/racc/color.rb
@@ -0,0 +1,65 @@
+module Racc
+ # Support module for printing colored text to an ANSI terminal
+ module Color
+ extend self
+ @color_enabled = false
+
+ def self.enabled=(enabled)
+ @color_enabled = enabled
+ end
+
+ def self.enabled?
+ @color_enabled
+ end
+
+ def self.without_color
+ saved = @color_enabled
+ @color_enabled = false
+ yield
+ ensure
+ @color_enabled = saved
+ end
+
+ def bright(text)
+ return text unless Color.enabled?
+ text = text.gsub(/\e\[.*?m[^\e]*\e\[0m/, "\e[0m\\0\e[1m")
+ String.new "\e[1m#{text}\e[0m"
+ end
+
+ def red(text)
+ return text unless Color.enabled?
+ String.new "\e[31m#{text}\e[0m"
+ end
+
+ def green(text)
+ return text unless Color.enabled?
+ String.new "\e[32m#{text}\e[0m"
+ end
+
+ def violet(text)
+ return text unless Color.enabled?
+ String.new "\e[1;35m#{text}\e[0m"
+ end
+
+ # Syntax highlighting for various types of symbols...
+ def nonterminal(text)
+ return text unless Color.enabled?
+ String.new "\e[1;34m#{text}\e[0m" # blue
+ end
+
+ def terminal(text)
+ return text unless Color.enabled?
+ String.new "\e[1;36m\e[4m#{text}\e[0m" # cyan, with underline
+ end
+
+ def string(text)
+ return text unless Color.enabled?
+ String.new "\e[1;33m#{text}\e[0m" # bright yellow
+ end
+
+ def explicit_prec(text)
+ return text unless Color.enabled?
+ String.new "\e[1;31m#{text}\e[0m" # bright reddish orange
+ end
+ end
+end
diff --git a/lib/racc/compat.rb b/lib/racc/compat.rb
deleted file mode 100644
index 14fa1118..00000000
--- a/lib/racc/compat.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-
-unless Object.method_defined?(:__send)
- class Object
- alias __send __send__
- end
-end
-
-unless Object.method_defined?(:__send!)
- class Object
- alias __send! __send__
- end
-end
-
-unless Array.method_defined?(:map!)
- class Array
- if Array.method_defined?(:collect!)
- alias map! collect!
- else
- alias map! filter
- end
- end
-end
diff --git a/lib/racc/debugflags.rb b/lib/racc/debugflags.rb
deleted file mode 100644
index 74ff4369..00000000
--- a/lib/racc/debugflags.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of LGPL, see the file "COPYING".
-#
-
-module Racc
-
- class DebugFlags
- def DebugFlags.parse_option_string(s)
- parse = rule = token = state = la = prec = conf = false
- s.split(//).each do |ch|
- case ch
- when 'p' then parse = true
- when 'r' then rule = true
- when 't' then token = true
- when 's' then state = true
- when 'l' then la = true
- when 'c' then prec = true
- when 'o' then conf = true
- else
- raise "unknown debug flag char: #{ch.inspect}"
- end
- end
- new(parse, rule, token, state, la, prec, conf)
- end
-
- def initialize(parse = false, rule = false, token = false, state = false,
- la = false, prec = false, conf = false)
- @parse = parse
- @rule = rule
- @token = token
- @state = state
- @la = la
- @prec = prec
- @any = (parse || rule || token || state || la || prec)
- @status_logging = conf
- end
-
- attr_reader :parse
- attr_reader :rule
- attr_reader :token
- attr_reader :state
- attr_reader :la
- attr_reader :prec
-
- def any?
- @any
- end
-
- attr_reader :status_logging
- end
-
-end
diff --git a/lib/racc/directed_graph.rb b/lib/racc/directed_graph.rb
new file mode 100644
index 00000000..df0c2566
--- /dev/null
+++ b/lib/racc/directed_graph.rb
@@ -0,0 +1,383 @@
+require 'racc/util'
+require 'racc/color'
+require 'tempfile'
+require 'set'
+
+module Racc
+ module Graph
+ # Algorithms which work on any of the graph implementations below
+ module Algorithms
+ # shortest path between nodes; both start and end points are included
+ def shortest_path(start, dest)
+ # Dijkstra's algorithm
+ return [start] if start == dest
+
+ visited = Set[start]
+ worklist = [start]
+ paths = {start => [start]}
+
+ until visited.include?(dest)
+ return nil if worklist.empty?
+ node = worklist.shift
+ node_path = paths[node]
+
+ children(node) do |child|
+ child_path = paths[child]
+ if child_path.nil? || child_path.size > node_path.size + 1
+ paths[child] = node_path.dup << child
+ end
+ worklist << child unless visited.include?(child)
+ end
+
+ visited << node
+ end
+
+ paths[dest]
+ end
+
+ # { node -> shortest path to it from start node }
+ # if a block is provided, it is a 'cost function'
+ # all paths include both start and end points
+ def shortest_paths
+ # again, Dijkstra's algorithm
+ paths = {@start => [0, @start]} # cache total path cost
+
+ Racc.set_closure([@start]) do |node|
+ node_path = paths[node]
+ children(node) do |child|
+ child_path = paths[child]
+ cost = block_given? ? yield(node, child) : 1
+ if child_path.nil? || child_path[0] > node_path[0] + cost
+ paths[child] = node_path.dup.tap { |p| p[0] += cost } << child
+ end
+ end
+ end
+
+ paths.each_value { |p| p.shift } # drop cached total costs
+ paths
+ end
+
+ # only paths with no loops will be found
+ # start and end points are included
+ # this can be very slow on graphs with a lot of transitions!
+ def all_paths(src, dest, current=src, traversed=[src], result=[])
+ children(current) do |child|
+ if child == dest
+ result << (traversed.dup << child)
+ elsif !traversed.include?(child)
+ traversed.push(child)
+ all_paths(src, dest, child, traversed, result)
+ traversed.pop
+ end
+ end
+ result
+ end
+
+ def to_gif(options={})
+ filename = options[:filename] || "graph.gif"
+ filename <<= ".gif" unless filename.end_with?(".gif")
+ Tempfile.open("graph") do |f|
+ f.write(self.to_dot(options))
+ f.flush
+ `dot -Tgif #{f.path} -o "#{filename}"`
+ end
+ end
+
+ def to_dot(options={})
+ s = "digraph {\n"
+ s <<= "graph [label=\"#{options[:title]}\"]\n" if options[:title]
+ (options[:highlight] || []).each do |node|
+ s <<= "\"#{node.hash}\" [style=filled fillcolor=gold]\n"
+ end
+ s <<= nodes.map do |node|
+ %{"#{node.hash}" [label="#{node_caption(node)}"]} <<
+ children(node).map { |child| %{"#{node.hash}" -> "#{child.hash}"} }.join("\n")
+ end.join("\n")
+ s << "}"
+ end
+ end
+
+ # An implementation which is fast when the exact number of nodes is known
+ # in advance, and each one can be identified by an integer
+ class Finite < Array
+ include Algorithms
+
+ def initialize(size)
+ super(size) { Set.new }
+ @start = nil
+ end
+
+ def start=(idx)
+ @start = idx
+ end
+
+ def add_child(from, to)
+ self[from] << to
+ to
+ end
+
+ def remove_child(from, to)
+ self[from].delete(to)
+ end
+
+ alias nodes each_index
+
+ def children(node, &block)
+ result = self[node]
+ result.each(&block) if block_given?
+ result
+ end
+
+ def reachable
+ reachable_from([@start])
+ end
+
+ def reachable_from(nodes)
+ Racc.set_closure(nodes) { |node| self[node] }
+ end
+
+ def leaves
+ reachable.select { |node| self[node].empty? }
+ end
+
+ def dup
+ super.map!(&:dup)
+ end
+
+ def freeze
+ each(&:freeze)
+ end
+ end
+
+ # Like Graph::Finite, but with backpointers from children to parents as well
+ class Reversible < Finite
+ def initialize(size)
+ super(size * 2)
+ @offset = size
+ end
+
+ def add_child(from, to)
+ self[from] << to
+ self[@offset + to] << from
+ to
+ end
+
+ def remove_child(from, to)
+ self[from].delete(to)
+ self[@offset + to].delete(from)
+ end
+
+ def remove_node(node)
+ self[node].each { |child| self[@offset + child].delete(node) }.clear
+ self[@offset + node].each { |parent| self[parent].delete(node) }.clear
+ end
+
+ def nodes(&block)
+ result = 0...@offset
+ result.each(&block) if block_given?
+ result
+ end
+
+ def parents(node, &block)
+ result = self[@offset + node]
+ result.each(&block) if block_given?
+ result
+ end
+
+ # All nodes which can reach a node in `dests` (and `dests` themselves)
+ def can_reach(dests)
+ Racc.set_closure(dests) { |node| self[@offset + node] }
+ end
+ end
+
+ # Each vector has a label; labels are unique for any source node
+ # There can be multiple vectors from one node to another, as long as
+ # each vector has a different label
+ class Labeled < Array
+ include Algorithms
+
+ def initialize(size)
+ super(size) { {} }
+ concat(map { Set.new }) # backpointers
+ @start = nil
+ @offset = size
+ end
+
+ attr_reader :start
+
+ def start=(idx)
+ @start = idx
+ end
+
+ def add_vector(from, to, label)
+ if self[from].key?(label)
+ raise "Vector #{label.inspect} from node #{from} already exists"
+ end
+ self[from][label] = to
+ self[to + @offset] << from
+ to
+ end
+
+ def remove_vector(from, to, label)
+ self[from].delete(label)
+ self[to + @offset].delete(from)
+ end
+
+ def nodes(&block)
+ result = 0...@offset
+ result.each(&block) if block_given?
+ result
+ end
+
+ def children(node, &block)
+ result = self[node].values
+ result.each(&block) if block_given?
+ result
+ end
+
+ def vectors(node, &block)
+ result = self[node]
+ result.each(&block) if block_given?
+ result
+ end
+
+ def parents(node, &block)
+ result = self[@offset + node]
+ result.each(&block) if block_given?
+ result
+ end
+
+ def reachable
+ reachable_from([@start])
+ end
+
+ def reachable_from(nodes)
+ Racc.set_closure(nodes) { |node| self[node].values }
+ end
+
+ def leaves
+ reachable.select { |node| self[node].empty? }
+ end
+
+ # like #all_paths, but return sequences of vector labels, not sequences
+ # of nodes
+ def all_vector_paths(src, dest, current=src, kill=0, path=[], result=[])
+ vectors(current) do |label, child|
+ if child == dest
+ result << (path.dup << label)
+ elsif kill[child] == 0
+ path.push(label)
+ all_vector_paths(src, dest, child, kill | (1 << child), path, result)
+ path.pop
+ end
+ end
+ result
+ end
+
+ # like #shortest_paths, but return sequences of vector labels
+ def shortest_vector_paths
+ paths = {@start => [0]} # cache total path cost
+
+ Racc.set_closure([@start]) do |node|
+ node_path = paths[node]
+ vectors(node) do |label, child|
+ child_path = paths[child]
+ cost = block_given? ? yield(label) : 1
+ if child_path.nil? || child_path[0] > node_path[0] + cost
+ paths[child] = node_path.dup.tap { |p| p[0] += cost } << label
+ end
+ end
+ children(node)
+ end
+
+ paths.each_value { |p| p.shift } # drop cached total costs
+ paths
+ end
+
+ def dup
+ super.map!(&:dup)
+ end
+
+ def freeze
+ each(&:freeze)
+ end
+ end
+
+ # This implementation uses an object for each node, rather than identifying
+ # nodes by integers
+ # this means we can add as many nodes as we want
+ # Graph::Node can also be subclassed and have extra methods added
+ class Generic
+ include Algorithms
+
+ def initialize
+ @nodes = Set.new
+ @start = nil
+ end
+
+ attr_reader :nodes, :start
+
+ def start=(node)
+ @nodes << node
+ @start = node
+ end
+
+ def remove_node(node)
+ @start = nil if node == @start
+ @nodes.delete(node)
+ node.out.each { |other| other.in.delete?(node) }
+ node.in.each { |other| other.out.delete?(node) }
+ end
+
+ def add_child(from, to)
+ @nodes << to
+ from.out << to
+ to.in << from
+ to
+ end
+
+ def remove_child(from, to)
+ from.out.delete(to)
+ to.in.delete(from)
+ end
+
+ def children(node, &block)
+ node.out.each(&block) if block_given?
+ node.out
+ end
+
+ def reachable
+ reachable_from([@start])
+ end
+
+ def reachable_from(nodes)
+ Racc.set_closure(nodes) { |node| node.out }
+ end
+
+ def can_reach(dests)
+ Racc.set_closure(dests) { |node| node.in }
+ end
+
+ def leaves
+ @nodes.select { |node| node.out.empty? }
+ end
+
+ def node_caption(node)
+ Color.without_color { node.ptr.to_s }
+ end
+
+ def freeze
+ super
+ @nodes.each(&:freeze)
+ end
+ end
+
+ class Node
+ def initialize
+ @out, @in = Set.new, Set.new
+ end
+
+ attr_reader :out, :in
+ end
+ end
+end
diff --git a/lib/racc/dsl.rb b/lib/racc/dsl.rb
new file mode 100644
index 00000000..d79623be
--- /dev/null
+++ b/lib/racc/dsl.rb
@@ -0,0 +1,193 @@
+module Racc
+ # DSL for defining a grammar in code, rather than using a grammar file
+ module DSL
+ def self.define_grammar(&block)
+ env = DefinitionEnv.new
+ env.instance_eval(&block)
+ env.grammar
+ end
+
+ # Methods are DSL 'keywords' which can be used in a `define_grammar` block
+ #
+ # Key method is `#seq`, which creates a `Rule`
+ # (`Rule` objects can be combined using `#|`, similar to how alternative
+ # derivations for a non-terminal are separated by | in a BNF grammar)
+ #
+ # Other key method is `#method_missing`, which is used to register rules:
+ #
+ # self.nonterminal_name = seq(:token, :another_token) | seq(:something_else)
+ #
+ class DefinitionEnv
+ def initialize
+ @grammar = Grammar.new
+ @seqs = Hash.new(0)
+ @delayed = []
+ end
+
+ def grammar
+ flush_delayed
+ @grammar.finished!
+ @grammar
+ end
+
+ # Intercept calls to `self.non_terminal = ...`, and use them to register
+ # a new rule
+ def method_missing(mid, *args, &block)
+ unless mid.to_s[-1,1] == '='
+ super # raises NoMethodError
+ end
+ target = @grammar.intern(mid.to_s.chop.intern)
+ unless args.size == 1
+ raise ArgumentError, "too many arguments for #{mid} (#{args.size} for 1)"
+ end
+ _add(target, args.first)
+ end
+
+ # We just received a call to `self.nonterminal = definition`
+ # But when we were executing that "definition", we didn't know what the
+ # nonterminal on the LHS would be
+ # Depending on the DSL method(s) which were used in the "definition",
+ # `rhs` may be:
+ # - A "placeholder" target symbol, which should be replaced with the
+ # "real" target in all the rules which the definition created
+ # - A `Rule`, whose target we didn't know at the time of definition.
+ # Its target will be `nil` right now; fix that up.
+ def _add(target, rhs)
+ case rhs
+ when Sym
+ @delayed.each do |rule|
+ rule.replace(rhs, target) if rule.target == rhs
+ end
+ @grammar.delete_symbol(rhs)
+ else
+ rhs.each_rule do |rule|
+ rule.target = target
+ @grammar.add(rule)
+ end
+ end
+ flush_delayed
+ end
+
+ def _delayed_add(rule)
+ @delayed.push(rule)
+ end
+
+ def _added?(sym)
+ @grammar.added?(sym) or @delayed.detect {|r| r.target == sym }
+ end
+
+ def flush_delayed
+ return if @delayed.empty?
+ @delayed.each do |rule|
+ @grammar.add rule
+ end
+ @delayed.clear
+ end
+
+ # Basic method for creating a new `Rule`.
+ def seq(*list, &block)
+ Rule.new(nil, list.map {|x| _intern(x) }, UserAction.proc(block))
+ end
+
+ # Create a null `Rule` (one with an empty RHS)
+ def null(&block)
+ seq(&block)
+ end
+
+ # Create a `Rule` which can either be null (like an empty RHS in a BNF grammar),
+ # in which case the action will return `default`, or which can match a single
+ # `sym`.
+ def option(sym, default = nil, &block)
+ _defmetasyntax("option", _intern(sym), block) {|target|
+ seq() { default } | seq(sym)
+ }
+ end
+
+ # Create a `Rule` which matches 0 or more instance of `sym` in a row.
+ def many(sym, &block)
+ _defmetasyntax("many", _intern(sym), block) {|target|
+ seq() { [] }\
+ | seq(target, sym) {|list, x| list.push x; list }
+ }
+ end
+
+ # Create a `Rule` which matches 1 or more instances of `sym` in a row.
+ def many1(sym, &block)
+ _defmetasyntax("many1", _intern(sym), block) {|target|
+ seq(sym) {|x| [x] }\
+ | seq(target, sym) {|list, x| list.push x; list }
+ }
+ end
+
+ # Create a `Rule` which matches 0 or more instances of `sym`, separated
+ # by `sep`.
+ def separated_by(sep, sym, &block)
+ option(separated_by1(sep, sym), [], &block)
+ end
+
+ # Create a `Rule` which matches 1 or more instances of `sym`, separated
+ # by `sep`.
+ def separated_by1(sep, sym, &block)
+ _defmetasyntax("separated_by1", _intern(sym), block) {|target|
+ seq(sym) {|x| [x] }\
+ | seq(target, sep, sym) {|list, _, x| list.push x; list }
+ }
+ end
+
+ def _intern(x)
+ case x
+ when Symbol, String
+ @grammar.intern(x)
+ when Racc::Sym
+ x
+ else
+ raise TypeError, "wrong type #{x.class} (expected Symbol/String/Racc::Sym)"
+ end
+ end
+
+ private
+
+ # the passed block will define a `Rule` (which may be chained with
+ # 'alternative' `Rule`s)
+ # make all of those rules reduce to a placeholder nonterminal,
+ # executing `action` when they do so,
+ # and return the newly generated placeholder
+ #
+ # (when the placeholder is associated with a "real" nonterminal using the
+ # `self.non_terminal = ...` syntax, we will go through all the generated
+ # rules and rewrite the placeholder to the "real" nonterminal)
+ #
+ def _defmetasyntax(type, id, action, &block)
+ if action
+ idbase = :"#{type}@#{id}-#{@seqs[type] += 1}"
+ _regist(:"#{idbase}-core", &block)
+ _wrap(idbase, :"#{idbase}-core", action)
+ else
+ _regist(:"#{type}@#{id}", &block)
+ end
+ end
+
+ def _regist(target)
+ sym = @grammar.intern(target)
+ unless _added?(sym)
+ yield(target).each_rule do |rule|
+ rule.target = sym
+ _delayed_add(rule)
+ end
+ end
+ sym
+ end
+
+ # create a rule which reduces wrapped -> wrapper and executes an
+ # action at the same time
+ # (this is a way to make sure an action is executed everytime a
+ # reduction is done using a particular generated rule)
+ def _wrap(wrapper, wrapped, block)
+ wrapped = @grammar.intern(wrapped)
+ wrapper = @grammar.intern(wrapper)
+ _delayed_add Rule.new(wrapper, [wrapped], UserAction.proc(block))
+ wrapper
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/racc/exception.rb b/lib/racc/exception.rb
index a26517cc..d6ef6fd5 100644
--- a/lib/racc/exception.rb
+++ b/lib/racc/exception.rb
@@ -1,15 +1,11 @@
-#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
module Racc
- class Error < StandardError; end
- class CompileError < Error; end
+ class CompileError < StandardError; end
+ class ScanError < CompileError; end
+ class ParseError < CompileError; end
end
diff --git a/lib/racc/grammar.rb b/lib/racc/grammar.rb
index 733f7084..f1f16287 100644
--- a/lib/racc/grammar.rb
+++ b/lib/racc/grammar.rb
@@ -1,59 +1,63 @@
-#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
-require 'racc/compat'
-require 'racc/iset'
-require 'racc/sourcetext'
-require 'racc/logfilegenerator'
+require 'racc/source'
require 'racc/exception'
-require 'forwardable'
+require 'racc/color'
+require 'racc/warning'
+require 'racc/util'
+require 'set'
module Racc
-
class Grammar
+ include Enumerable
+
+ def initialize
+ @symbols = [] # all Syms used in a grammar
+ @cache = {} # map of String/Symbol name -> Sym
+ @rules = []
+ @start = nil
- def initialize(debug_flags = DebugFlags.new)
- @symboltable = SymbolTable.new
- @debug_symbol = debug_flags.token
- @rules = [] # :: [Rule]
- @start = nil
@n_expected_srconflicts = nil
+
@prec_table = []
@prec_table_closed = false
+
@closed = false
- @states = nil
+
+ # 'dummy' and 'anchor' are used to make sure the parser runs over ALL the
+ # input tokens before concluding that the parse was successful
+ # an 'anchor' token is appended to the end of the token stream, and a
+ # 'dummy rule' is automatically added which looks like:
+ # dummy : start anchor anchor
+ # We never actually reduce to the dummy symbol; instead, we manually set
+ # 'dummy : start anchor . anchor' to be an 'accept state'
+
+ @dummy = intern(:$start, true)
+ @anchor = intern(false, true) # Symbol ID = 0
+ @error = intern(:error, true) # Symbol ID = 1
end
attr_reader :start
- attr_reader :symboltable
- attr_accessor :n_expected_srconflicts
+ attr_reader :n_expected_srconflicts
+ attr_reader :terminals
+ attr_reader :nonterminals
+ attr_reader :symbols
+ attr_reader :dummy
+ attr_reader :anchor
+ attr_reader :error
def [](x)
@rules[x]
end
- def each_rule(&block)
+ def each(&block)
@rules.each(&block)
end
- alias each each_rule
-
- def each_index(&block)
- @rules.each_index(&block)
- end
-
- def each_with_index(&block)
- @rules.each_with_index(&block)
- end
-
def size
@rules.size
end
@@ -62,106 +66,88 @@ def to_s
""
end
- extend Forwardable
-
- def_delegator "@symboltable", :each, :each_symbol
- def_delegator "@symboltable", :each_terminal
- def_delegator "@symboltable", :each_nonterminal
-
- def intern(value, dummy = false)
- @symboltable.intern(value, dummy)
+ def intern(val, generated = false)
+ if @closed
+ @cache[val] || (raise "No such symbol: #{val}")
+ else
+ @cache[val] ||= Sym.new(val, self, generated).tap { |sym| @symbols.push(sym) }
+ end
end
- def symbols
- @symboltable.symbols
+ def delete_symbol(sym)
+ @symbols.delete(sym)
+ @cache.delete(sym.value)
end
- def nonterminal_base
- @symboltable.nt_base
+ def states
+ raise 'Grammar not yet closed' unless @closed
+ @states ||= States.new(self)
end
- def useless_nonterminal_exist?
- n_useless_nonterminals() != 0
+ def sr_conflicts
+ states.sr_conflicts
end
- def n_useless_nonterminals
- @n_useless_nonterminals ||=
- begin
- n = 0
- @symboltable.each_nonterminal do |sym|
- n += 1 if sym.useless?
- end
- n
- end
+ def rr_conflicts
+ states.rr_conflicts
end
- def useless_rule_exist?
- n_useless_rules() != 0
- end
-
- def n_useless_rules
- @n_useless_rules ||=
- begin
- n = 0
- each do |r|
- n += 1 if r.useless?
- end
- n
- end
+ def nonterminal_base
+ @terminals.size
end
- def nfa
- (@states ||= States.new(self)).nfa
+ def locations
+ raise 'Grammar not yet closed' unless @closed
+ @locations ||= @rules.flat_map(&:ptrs)
end
- def dfa
- (@states ||= States.new(self)).dfa
+ def n_expected_srconflicts=(value)
+ raise CompileError, "'expect' seen twice" if @n_expected_srconflicts
+ @n_expected_srconflicts = value
end
- alias states dfa
-
def state_transition_table
- states().state_transition_table
+ states.state_transition_table
end
def parser_class
- states = states() # cache
- if $DEBUG
- srcfilename = caller(1).first.slice(/\A(.*?):/, 1)
- begin
- write_log srcfilename + ".output"
- rescue SystemCallError
- end
- report = lambda {|s| $stderr.puts "racc: #{srcfilename}: #{s}" }
- if states.should_report_srconflict?
- report["#{states.n_srconflicts} shift/reduce conflicts"]
- end
- if states.rrconflict_exist?
- report["#{states.n_rrconflicts} reduce/reduce conflicts"]
- end
- g = states.grammar
- if g.useless_nonterminal_exist?
- report["#{g.n_useless_nonterminals} useless nonterminals"]
- end
- if g.useless_rule_exist?
- report["#{g.n_useless_rules} useless rules"]
+ state_transition_table.parser_class
+ end
+
+ def warnings(verbose)
+ warnings = Warnings.new
+
+ useless_symbols.each do |sym|
+ if sym.locate.empty?
+ if sym.terminal?
+ warnings.add_for_symbol(sym, Warning::UnusedTerminal.new(sym))
+ else
+ warnings.add_for_symbol(sym, Warning::UnusedNonterminal.new(sym))
+ end
+ elsif !sym.reachable.include?(@start) && sym.reachable.include?(sym)
+ if sym.reachable.one?
+ warnings.add_for_symbol(sym, Warning.new(:useless_nonterminal, 'Useless ' \
+ "nonterminal #{sym} only appears on the right side of its " \
+ 'own rules.'))
+ else
+ warnings.add_for_symbol(sym, Warning::UnreachableNonterminal.new(sym))
+ end
+ elsif !productive_symbols.include?(sym)
+ warnings.add_for_symbol(sym, Warning::InfiniteLoop.new(sym))
end
end
- states.state_transition_table.parser_class
- end
- def write_log(path)
- File.open(path, 'w') {|f|
- LogFileGenerator.new(states()).output f
- }
+ select { |r| r.explicit_precedence && !r.explicit_precedence_used? }.each do |rule|
+ warnings.add_for_rule(rule, Warning::UselessPrecedence.new(rule))
+ end
+
+ states.warnings(warnings, verbose)
end
- #
# Grammar Definition Interface
- #
def add(rule)
- raise ArgumentError, "rule added after the Grammar closed" if @closed
+ raise ArgumentError, "rule added after Grammar closed" if @closed
@rules.push rule
end
@@ -170,7 +156,7 @@ def added?(sym)
end
def start_symbol=(s)
- raise CompileError, "start symbol set twice'" if @start
+ raise CompileError, "start symbol set twice" if @start
@start = s
end
@@ -191,435 +177,218 @@ def end_precedence_declaration(reverse)
end
end
- #
- # Dynamic Generation Interface
- #
-
- def Grammar.define(&block)
- env = DefinitionEnv.new
- env.instance_eval(&block)
- env.grammar
- end
-
- class DefinitionEnv
- def initialize
- @grammar = Grammar.new
- @seqs = Hash.new(0)
- @delayed = []
- end
-
- def grammar
- flush_delayed
- @grammar.each do |rule|
- if rule.specified_prec
- rule.specified_prec = @grammar.intern(rule.specified_prec)
- end
- end
- @grammar.init
- @grammar
- end
-
- def precedence_table(&block)
- env = PrecedenceDefinitionEnv.new(@grammar)
- env.instance_eval(&block)
- @grammar.end_precedence_declaration env.reverse
- end
-
- def method_missing(mid, *args, &block)
- unless mid.to_s[-1,1] == '='
- super # raises NoMethodError
- end
- target = @grammar.intern(mid.to_s.chop.intern)
- unless args.size == 1
- raise ArgumentError, "too many arguments for #{mid} (#{args.size} for 1)"
- end
- _add target, args.first
- end
-
- def _add(target, x)
- case x
- when Sym
- @delayed.each do |rule|
- rule.replace x, target if rule.target == x
- end
- @grammar.symboltable.delete x
- else
- x.each_rule do |r|
- r.target = target
- @grammar.add r
- end
- end
- flush_delayed
- end
-
- def _delayed_add(rule)
- @delayed.push rule
- end
-
- def _added?(sym)
- @grammar.added?(sym) or @delayed.detect {|r| r.target == sym }
- end
-
- def flush_delayed
- return if @delayed.empty?
- @delayed.each do |rule|
- @grammar.add rule
- end
- @delayed.clear
- end
-
- def seq(*list, &block)
- Rule.new(nil, list.map {|x| _intern(x) }, UserAction.proc(block))
- end
-
- def null(&block)
- seq(&block)
- end
-
- def action(&block)
- id = "@#{@seqs["action"] += 1}".intern
- _delayed_add Rule.new(@grammar.intern(id), [], UserAction.proc(block))
- id
- end
-
- alias _ action
-
- def option(sym, default = nil, &block)
- _defmetasyntax("option", _intern(sym), block) {|target|
- seq() { default } | seq(sym)
- }
- end
-
- def many(sym, &block)
- _defmetasyntax("many", _intern(sym), block) {|target|
- seq() { [] }\
- | seq(target, sym) {|list, x| list.push x; list }
- }
- end
-
- def many1(sym, &block)
- _defmetasyntax("many1", _intern(sym), block) {|target|
- seq(sym) {|x| [x] }\
- | seq(target, sym) {|list, x| list.push x; list }
- }
- end
-
- def separated_by(sep, sym, &block)
- option(separated_by1(sep, sym), [], &block)
- end
-
- def separated_by1(sep, sym, &block)
- _defmetasyntax("separated_by1", _intern(sym), block) {|target|
- seq(sym) {|x| [x] }\
- | seq(target, sep, sym) {|list, _, x| list.push x; list }
- }
- end
-
- def _intern(x)
- case x
- when Symbol, String
- @grammar.intern(x)
- when Racc::Sym
- x
- else
- raise TypeError, "wrong type #{x.class} (expected Symbol/String/Racc::Sym)"
- end
- end
+ # Computation
- private
+ def finished!
+ return if @closed
+ @closed = true
- def _defmetasyntax(type, id, action, &block)
- if action
- idbase = "#{type}@#{id}-#{@seqs[type] += 1}"
- target = _wrap(idbase, "#{idbase}-core", action)
- _regist("#{idbase}-core", &block)
- else
- target = _regist("#{type}@#{id}", &block)
- end
- @grammar.intern(target)
- end
+ # if 'start' nonterminal was not explicitly set, just take the first one
+ @start ||= map(&:target).detect { |sym| !sym.generated? }
+ fail CompileError, 'no rules in input' if @rules.empty?
+ add_start_rule
- def _regist(target_name)
- target = target_name.intern
- unless _added?(@grammar.intern(target))
- yield(target).each_rule do |rule|
- rule.target = @grammar.intern(target)
- _delayed_add rule
- end
- end
- target
+ @rules.freeze
+ @symbols.each do |sym|
+ sym.heads.freeze
+ sym.locate.freeze
end
+ @cache.freeze
- def _wrap(target_name, sym, block)
- target = target_name.intern
- _delayed_add Rule.new(@grammar.intern(target),
- [@grammar.intern(sym.intern)],
- UserAction.proc(block))
- target
- end
+ fix_ident
+ check_terminals
+ check_rules
end
- class PrecedenceDefinitionEnv
- def initialize(g)
- @grammar = g
- @prechigh_seen = false
- @preclow_seen = false
- @reverse = false
- end
-
- attr_reader :reverse
-
- def higher
- if @prechigh_seen
- raise CompileError, "prechigh used twice"
- end
- @prechigh_seen = true
- end
-
- def lower
- if @preclow_seen
- raise CompileError, "preclow used twice"
- end
- if @prechigh_seen
- @reverse = true
- end
- @preclow_seen = true
- end
-
- def left(*syms)
- @grammar.declare_precedence :Left, syms.map {|s| @grammar.intern(s) }
- end
-
- def right(*syms)
- @grammar.declare_precedence :Right, syms.map {|s| @grammar.intern(s) }
- end
-
- def nonassoc(*syms)
- @grammar.declare_precedence :Nonassoc, syms.map {|s| @grammar.intern(s)}
+ # A useless symbol can never be a part of any valid parse tree, and is not
+ # used for a = precedence declaration either
+ def useless_symbols
+ raise 'Grammar not yet closed' unless @closed
+ @useless_symbols ||= begin
+ @symbols.select do |sym|
+ !sym.generated? &&
+ sym != @start &&
+ (!sym.reachable.include?(@start) || !productive_symbols.include?(sym)) &&
+ none? { |rule| rule.explicit_precedence == sym }
+ end.freeze
end
end
+ # A 'nonproductive' Sym, if taken as a starting point and then converted
+ # into a series of tokens by repeated substitution, would get stuck in an
+ # infinite loop and never reach a point where only terminals were left
+ # A 'productive' Sym, on the other hand, is not 'stuck' in an infinite loop
#
- # Computation
- #
-
- def init
- return if @closed
- @closed = true
- @start ||= @rules.map {|r| r.target }.detect {|sym| not sym.dummy? }
- raise CompileError, 'no rule in input' if @rules.empty?
- add_start_rule
- @rules.freeze
- fix_ident
- compute_hash
- compute_heads
- determine_terminals
- compute_nullable_0
- @symboltable.fix
- compute_locate
- @symboltable.each_nonterminal {|t| compute_expand t }
- compute_nullable
- compute_useless
+ # (Even if it can be converted to an empty sequence of tokens; in other
+ # words, if it is nullable, then it is considered 'productive')
+ def productive_symbols
+ raise 'Grammar not yet closed' unless @closed
+ @productive_symbols ||= begin
+ Sym.set_closure(@terminals + nullable_symbols.to_a).freeze
+ end
+ end
+
+ # Can an empty sequence of tokens reduce to this nonterminal?
+ # (Can it be produced out of "nothing"?)
+ def nullable_symbols
+ raise 'Grammar not yet closed' unless @closed
+ @nullable_symbols ||= Sym.set_closure(
+ @symbols.select { |nt| nt.heads.any?(&:reduce?) }).freeze
+ end
+
+ # What is the shortest series of terminals which can reduce to each NT?
+ def shortest_productions
+ raise 'Grammar not yet closed' unless @closed
+ @shortest_productions ||= begin
+ # nullable symbols can expand to... nothing
+ # terminals just map to themselves
+ result = Hash[nullable_symbols.map { |sym| [sym, []] } +
+ terminals.map { |t| [t, [t]] }]
+
+ worklist = result.keys
+ while sym = worklist.shift
+ sym.locate.each do |ptr|
+ target = ptr.target
+ rules = target.heads.map(&:rule)
+ rules.reject! { |r| r.symbols.any? { |rs| !result.key?(rs) }}
+ next if rules.empty?
+ best = rules.map { |r| r.symbols.flat_map { |rs| result[rs] }}.min_by(&:size)
+ if !result.key?(target) || best.size < result[target].size
+ result[target] = best
+ worklist.push(target)
+ end
+ end
+ end
+ result.freeze
+ end
end
private
def add_start_rule
- r = Rule.new(@symboltable.dummy,
- [@start, @symboltable.anchor, @symboltable.anchor],
- UserAction.empty)
- r.ident = 0
- r.hash = 0
- r.precedence = nil
- @rules.unshift r
+ # We don't ever actually reduce to the dummy symbol; it is just there
+ # because every rule must have a target
+ # When building the parser states, we manually set the state where the
+ # first 'anchor' symbol is shifted to an 'accept state' -- one which
+ # successfully ends the parse
+ @rules.unshift(Rule.new(@dummy, [@start, @anchor, @anchor], UserAction.empty))
end
- # Rule#ident
- # LocationPointer#ident
def fix_ident
- @rules.each_with_index do |rule, idx|
- rule.ident = idx
- end
- end
-
- # Rule#hash
- def compute_hash
- hash = 4 # size of dummy rule
- @rules.each do |rule|
- rule.hash = hash
- hash += (rule.size + 1)
- end
- end
+ @rules.each_with_index(&:ident=)
+ @rules.flat_map(&:ptrs).each_with_index(&:ident=)
- # Sym#heads
- def compute_heads
- @rules.each do |rule|
- rule.target.heads.push rule.ptrs[0]
- end
+ @terminals, @nonterminals = @symbols.partition(&:terminal?)
+ @symbols = @terminals + @nonterminals
+ # number Syms so terminals have the lower numbers
+ @symbols.each_with_index(&:ident=)
end
- # Sym#terminal?
- def determine_terminals
- @symboltable.each do |s|
- s.term = s.heads.empty?
- end
- end
-
- # Sym#self_null?
- def compute_nullable_0
- @symboltable.each do |s|
- if s.terminal?
- s.snull = false
- else
- s.snull = s.heads.any? {|loc| loc.reduce? }
+ def check_terminals
+ # token declarations in Racc are optional
+ # however, if you declare some tokens, you must declare them all
+ if @symbols.any?(&:declared_as_terminal?)
+ # any symbol which has no derivation rules is a terminal
+ undeclared = terminals.reject do |t|
+ t.declared_as_terminal? || t.string_symbol? || t.locate.empty?
+ end
+ undeclared -= [@anchor, @error]
+ unless undeclared.empty?
+ locations = undeclared.flat_map(&:locate).map(&:rule).uniq
+ raise CompileError, "terminal#{'s' unless undeclared.one?} " \
+ "#{Racc.to_sentence(undeclared)} #{undeclared.one? ? 'was' : 'were'} " \
+ "not declared in a 'token' block:\n" +
+ Source::SparseLines.render(locations.map(&:source))
end
- end
- end
- # Sym#locate
- def compute_locate
- @rules.each do |rule|
- t = nil
- rule.ptrs.each do |ptr|
- unless ptr.reduce?
- tok = ptr.dereference
- tok.locate.push ptr
- t = tok if tok.terminal?
- end
+ wrongly_declared = nonterminals.select(&:declared_as_terminal?)
+ unless wrongly_declared.empty?
+ bad_rules = wrongly_declared.flat_map(&:heads).map(&:rule)
+ raise CompileError, "token#{'s' unless wrongly_declared.one?} " \
+ "#{Racc.to_sentence(wrongly_declared)} were declared in a 'token'" \
+ " block, but #{wrongly_declared.one? ? 'it also has' : 'they also have'}" \
+ " derivation rules:\n" + Source::SparseLines.render(bad_rules.map(&:source))
end
- rule.precedence = t
end
- end
-
- # Sym#expand
- def compute_expand(t)
- puts "expand> #{t.to_s}" if @debug_symbol
- t.expand = _compute_expand(t, ISet.new, [])
- puts "expand< #{t.to_s}: #{t.expand.to_s}" if @debug_symbol
- end
- def _compute_expand(t, set, lock)
- if tmp = t.expand
- set.update tmp
- return set
+ bad_strings = @symbols.select { |s| s.string_symbol? && s.nonterminal? }
+ unless bad_strings.empty?
+ bad_rules = bad_strings.flat_map(&:heads).map(&:rule)
+ raise CompileError, 'you may not create derivation rules for a ' \
+ "string literal:\n" + Source::SparseLines.render(bad_rules.map(&:source))
end
- tok = nil
- set.update_a t.heads
- t.heads.each do |ptr|
- tok = ptr.dereference
- if tok and tok.nonterminal?
- unless lock[tok.ident]
- lock[tok.ident] = true
- _compute_expand tok, set, lock
- end
- end
- end
- set
- end
-
- # Sym#nullable?, Rule#nullable?
- def compute_nullable
- @rules.each {|r| r.null = false }
- @symboltable.each {|t| t.null = false }
- r = @rules.dup
- s = @symboltable.nonterminals
- begin
- rs = r.size
- ss = s.size
- check_rules_nullable r
- check_symbols_nullable s
- end until rs == r.size and ss == s.size
- end
-
- def check_rules_nullable(rules)
- rules.delete_if do |rule|
- rule.null = true
- rule.symbols.each do |t|
- unless t.nullable?
- rule.null = false
- break
- end
- end
- rule.nullable?
+
+ bad_prec = @symbols.select { |s| s.assoc && s.nonterminal? }
+ unless bad_prec.empty?
+ bad_rules = bad_prec.flat_map(&:heads).map(&:rule)
+ raise CompileError, "token#{'s' unless bad_prec.one?} " \
+ "#{Racc.to_sentence(bad_prec)} appeared in a prechigh/preclow " \
+ "block, but #{bad_prec.one? ? 'it is not a' : 'they are not'} " \
+ "terminal#{'s' unless bad_prec.one?}:\n" +
+ Source::SparseLines.render(bad_rules.map(&:source))
end
- end
- def check_symbols_nullable(symbols)
- symbols.delete_if do |sym|
- sym.heads.each do |ptr|
- if ptr.rule.nullable?
- sym.null = true
- break
- end
- end
- sym.nullable?
+ bad_prec = @rules.select do |rule|
+ rule.explicit_precedence && rule.explicit_precedence.nonterminal?
+ end
+ unless bad_prec.empty?
+ raise CompileError, "The following rule#{'s' unless bad_prec.one?} " \
+ "use#{'s' if bad_prec.one?} nonterminals for explicit precedence, " \
+ "which is not allowed:\n" +
+ Source::SparseLines.render(bad_prec.map(&:source))
end
end
- # Sym#useless?, Rule#useless?
- # FIXME: what means "useless"?
- def compute_useless
- @symboltable.each_terminal {|sym| sym.useless = false }
- @symboltable.each_nonterminal {|sym| sym.useless = true }
- @rules.each {|rule| rule.useless = true }
- r = @rules.dup
- s = @symboltable.nonterminals
- begin
- rs = r.size
- ss = s.size
- check_rules_useless r
- check_symbols_useless s
- end until r.size == rs and s.size == ss
- end
-
- def check_rules_useless(rules)
- rules.delete_if do |rule|
- rule.useless = false
- rule.symbols.each do |sym|
- if sym.useless?
- rule.useless = true
- break
- end
+ def check_rules
+ @rules.group_by(&:target).each_value do |same_lhs|
+ same_lhs.group_by { |r| r.symbols.reject(&:hidden?) }.each_value do |same_rhs|
+ next unless same_rhs.size > 1
+ raise CompileError, "The following rules are duplicates:\n" +
+ Source::SparseLines.render(same_rhs.map(&:source))
end
- not rule.useless?
end
- end
- def check_symbols_useless(s)
- s.delete_if do |t|
- t.heads.each do |ptr|
- unless ptr.rule.useless?
- t.useless = false
- break
- end
- end
- not t.useless?
+ unless @error.heads.empty?
+ raise CompileError, "You cannot create rules for the error symbol:\n" +
+ Source::SparseLines.render(@error.heads.map { |ptr| ptr.rule.source} )
end
end
-
- end # class Grammar
-
+ end
class Rule
-
- def initialize(target, syms, act)
- @target = target
- @symbols = syms
- @action = act
+ def initialize(target, syms, act, source = nil, precedence = nil)
+ @target = target # LHS of rule (may be `nil` if not yet known)
+ @symbols = syms # RHS of rule
+ @action = act # run this code when reducing
@alternatives = []
+ @source = source
@ident = nil
- @hash = nil
- @ptrs = nil
- @precedence = nil
- @specified_prec = nil
- @null = nil
- @useless = nil
+ @precedence = precedence
+ @precedence_used = false # does explicit precedence actually resolve conflicts?
+
+ @ptrs = (0..@symbols.size).map { |idx| LocationPointer.new(self, idx) }
+ @ptrs.freeze
+
+ # reverse lookup from each Sym in RHS to location in rule where it appears
+ @symbols.each_with_index { |sym, idx| sym.locate << @ptrs[idx] }
+
+ # reverse lookup from LHS of rule to starting location in rule
+ @target.heads << @ptrs[0] if @target
end
- attr_accessor :target
+ attr_accessor :ident
+ attr_reader :source
attr_reader :symbols
attr_reader :action
+ attr_reader :target
+ attr_reader :ptrs
+
+ def target=(target)
+ raise 'target already set' if @target
+ @target = target
+ @target.heads << @ptrs[0]
+ end
def |(x)
@alternatives.push x.rule
@@ -635,54 +404,50 @@ def each_rule(&block)
@alternatives.each(&block)
end
- attr_accessor :ident
-
- attr_reader :hash
- attr_reader :ptrs
-
- def hash=(n)
- @hash = n
- ptrs = []
- @symbols.each_with_index do |sym, idx|
- ptrs.push LocationPointer.new(self, idx, sym)
- end
- ptrs.push LocationPointer.new(self, @symbols.size, nil)
- @ptrs = ptrs
+ def precedence
+ @precedence || @symbols.select(&:terminal?).last
end
- def precedence
- @specified_prec || @precedence
+ def explicit_precedence
+ @precedence
end
- def precedence=(sym)
- @precedence ||= sym
+ def explicit_precedence_used!
+ @precedence_used = true
end
- def prec(sym, &block)
- @specified_prec = sym
- if block
- unless @action.empty?
- raise CompileError, 'both of rule action block and prec block given'
- end
- @action = UserAction.proc(block)
- end
- self
+ def explicit_precedence_used?
+ @precedence_used
end
- attr_accessor :specified_prec
+ # higher-priority rules which prevent this one from reducing
+ # (keys are lookahead tokens at point where this rule is overridden)
+ def overridden_by
+ @overridden_by ||= Hash.new { |h,k| h[k] = Set.new }
+ end
- def nullable?() @null end
- def null=(n) @null = n end
+ def inspect
+ "#"
+ end
- def useless?() @useless end
- def useless=(u) @useless = u end
+ def to_s
+ if @source
+ @source.spifferific
+ else
+ display
+ end
+ end
- def inspect
- "#"
+ def display
+ rule = "#{@target} : #{@symbols.reject(&:hidden?).map(&:to_s).join(' ')}"
+ if @precedence
+ rule << ' ' << Color.explicit_prec('=' << @precedence.display_name)
+ end
+ rule
end
- def ==(other)
- other.kind_of?(Rule) and @ident == other.ident
+ def each(&block)
+ @symbols.each(&block)
end
def [](idx)
@@ -693,38 +458,26 @@ def size
@symbols.size
end
- def empty?
- @symbols.empty?
- end
-
- def to_s
- "#"
- end
-
+ # is this the 'end' rule which is applied last in a successful parse?
def accept?
- if tok = @symbols[-1]
- tok.anchor?
- else
- false
- end
- end
-
- def each(&block)
- @symbols.each(&block)
+ @symbols.last && @symbols.last.anchor?
end
- def replace(src, dest)
- @target = dest
- @symbols = @symbols.map {|s| s == src ? dest : s }
+ # sometimes a Rule is instantiated before the target is actually known
+ # it may be given a "placeholder" target first, which is later replaced
+ # with the real one
+ def replace(placeholder, actual)
+ raise 'wrong placeholder' if placeholder != @target
+ @target.heads.delete(ptrs[0]) if @target
+ @target = actual
+ @target.heads << @ptrs[0]
+ @symbols.map! { |s| s == placeholder ? actual : s }
end
-
- end # class Rule
-
+ end
class UserAction
-
- def UserAction.source_text(src)
- new(src, nil)
+ def UserAction.source_text(src, lineno)
+ new(src, nil).tap { |act| act.lineno = lineno }
end
def UserAction.proc(pr = nil, &block)
@@ -747,98 +500,89 @@ def initialize(src, proc)
attr_reader :source
attr_reader :proc
+ attr_accessor :lineno
def source?
not @proc
end
- def proc?
- not @source
- end
-
def empty?
not @proc and not @source
end
- def name
+ def to_s
"{action type=#{@source || @proc || 'nil'}}"
end
- alias inspect name
-
+ alias inspect to_s
end
-
- class OrMark
- def initialize(lineno)
- @lineno = lineno
- end
-
- def name
+ class OrMark < Struct.new(:lineno)
+ def to_s
'|'
end
-
- alias inspect name
-
- attr_reader :lineno
end
-
- class Prec
- def initialize(symbol, lineno)
- @symbol = symbol
- @lineno = lineno
+ class Prec < Struct.new(:symbol, :range)
+ def to_s
+ Color.explicit_prec(range.text)
end
- def name
- "=#{@symbol}"
+ def lineno
+ range.lineno
end
-
- alias inspect name
-
- attr_reader :symbol
- attr_reader :lineno
end
-
- #
- # A set of rule and position in it's RHS.
- # Note that the number of pointers is more than rule's RHS array,
- # because pointer points right edge of the final symbol when reducing.
+ # A combination of a rule and a position in its RHS
+ # Note that the number of pointers is more than the rule's RHS array,
+ # because it points to the right edge of the final symbol when reducing
#
class LocationPointer
-
- def initialize(rule, i, sym)
- @rule = rule
- @index = i
- @symbol = sym
- @ident = @rule.hash + i
- @reduce = sym.nil?
+ def initialize(rule, i)
+ @rule = rule
+ @index = i
+ @ident = nil # canonical ordering for all LocationPointers
end
attr_reader :rule
attr_reader :index
- attr_reader :symbol
+ attr_accessor :ident
- alias dereference symbol
+ # Sym which immediately follows this position in RHS
+ # or nil if it points to the end of RHS
+ def symbol
+ @rule.symbols[@index]
+ end
- attr_reader :ident
- alias hash ident
- attr_reader :reduce
- alias reduce? reduce
+ def target
+ @rule.target
+ end
- def to_s
- sprintf('(%d,%d %s)',
- @rule.ident, @index, (reduce?() ? '#' : @symbol.to_s))
+ def preceding
+ @rule.symbols[0...@index]
end
- alias inspect to_s
+ def following
+ @rule.symbols[@index..-1]
+ end
- def eql?(ot)
- @hash == ot.hash
+ def to_s
+ result = String.new "#{@rule.target} : "
+ if @index > 0
+ result << "#{preceding.reject(&:hidden?).map(&:to_s).join(' ')} ."
+ else
+ result << '.'
+ end
+ unless reduce?
+ result << " #{following.reject(&:hidden?).map(&:to_s).join(' ')}"
+ end
+ if sym = @rule.explicit_precedence
+ result << ' ' << Color.explicit_prec('=' << sym.display_name)
+ end
+ result
end
- alias == eql?
+ alias inspect to_s
def head?
@index == 0
@@ -848,227 +592,120 @@ def next
@rule.ptrs[@index + 1] or ptr_bug!
end
- alias increment next
-
- def before(len)
- @rule.ptrs[@index - len] or ptr_bug!
+ def reduce?
+ symbol.nil?
end
private
-
- def ptr_bug!
- raise "racc: fatal: pointer not exist: self: #{to_s}"
- end
-
- end # class LocationPointer
-
-
- class SymbolTable
-
- include Enumerable
-
- def initialize
- @symbols = [] # :: [Racc::Sym]
- @cache = {} # :: {(String|Symbol) => Racc::Sym}
- @dummy = intern(:$start, true)
- @anchor = intern(false, true) # Symbol ID = 0
- @error = intern(:error, false) # Symbol ID = 1
- end
-
- attr_reader :dummy
- attr_reader :anchor
- attr_reader :error
-
- def [](id)
- @symbols[id]
- end
-
- def intern(val, dummy = false)
- @cache[val] ||=
- begin
- sym = Sym.new(val, dummy)
- @symbols.push sym
- sym
- end
- end
- attr_reader :symbols
- alias to_a symbols
-
- def delete(sym)
- @symbols.delete sym
- @cache.delete sym.value
- end
-
- attr_reader :nt_base
-
- def nt_max
- @symbols.size
- end
-
- def each(&block)
- @symbols.each(&block)
- end
-
- def terminals(&block)
- @symbols[0, @nt_base]
- end
-
- def each_terminal(&block)
- @terms.each(&block)
- end
-
- def nonterminals
- @symbols[@nt_base, @symbols.size - @nt_base]
- end
-
- def each_nonterminal(&block)
- @nterms.each(&block)
- end
-
- def fix
- terms, nterms = @symbols.partition {|s| s.terminal? }
- @symbols = terms + nterms
- @terms = terms
- @nterms = nterms
- @nt_base = terms.size
- fix_ident
- check_terminals
- end
-
- private
-
- def fix_ident
- @symbols.each_with_index do |t, i|
- t.ident = i
- end
- end
-
- def check_terminals
- return unless @symbols.any? {|s| s.should_terminal? }
- @anchor.should_terminal
- @error.should_terminal
- each_terminal do |t|
- t.should_terminal if t.string_symbol?
- end
- each do |s|
- s.should_terminal if s.assoc
- end
- terminals().reject {|t| t.should_terminal? }.each do |t|
- raise CompileError, "terminal #{t} not declared as terminal"
- end
- nonterminals().select {|n| n.should_terminal? }.each do |n|
- raise CompileError, "symbol #{n} declared as terminal but is not terminal"
- end
+ def ptr_bug!
+ raise "racc: fatal: pointer doesn't exist: self: #{to_s}"
end
+ end
- end # class SymbolTable
-
-
- # Stands terminal and nonterminal symbols.
+ # A terminal or nonterminal symbol
class Sym
+ def initialize(value, grammar, generated)
+ @ident = nil
+ @value = value
+ @genned = generated
+ @grammar = grammar
- def initialize(value, dummyp)
- @ident = nil
- @value = value
- @dummyp = dummyp
-
- @term = nil
- @nterm = nil
- @should_terminal = false
+ @declared_terminal = false
@precedence = nil
+
case value
when Symbol
- @to_s = value.to_s
+ @display_name = value.to_s
@serialized = value.inspect
@string = false
when String
- @to_s = value.inspect
- @serialized = value.dump
+ @display_name = @serialized = value.inspect
@string = true
when false
- @to_s = '$end'
+ @display_name = '$end'
@serialized = 'false'
@string = false
- when ErrorSymbolValue
- @to_s = 'error'
- @serialized = 'Object.new'
- @string = false
else
- raise ArgumentError, "unknown symbol value: #{value.class}"
+ raise ArgumentError, "illegal symbol value: #{value.class}"
end
- @heads = []
- @locate = []
- @snull = nil
- @null = nil
- @expand = nil
- @useless = nil
- end
-
- class << self
- def once_writer(nm)
- nm = nm.id2name
- module_eval(<<-EOS)
- def #{nm}=(v)
- raise 'racc: fatal: @#{nm} != nil' unless @#{nm}.nil?
- @#{nm} = v
- end
- EOS
- end
+ @heads = [] # RHS of rules which can reduce to this Sym
+ @locate = [] # all locations where this Sym appears on RHS of a rule
end
- once_writer :ident
- attr_reader :ident
-
+ attr_reader :value
+ attr_accessor :ident
alias hash ident
- attr_reader :value
+ attr_accessor :display_name
+ attr_accessor :precedence
+ attr_accessor :assoc
- def dummy?
- @dummyp
- end
+ # some tokens are written one way in the grammar, but the actual value
+ # expected from the lexer is different
+ # you can set this up using a 'convert' block
+ attr_accessor :serialized
- def terminal?
- @term
- end
+ attr_reader :heads
+ attr_reader :locate
- def nonterminal?
- @nterm
+ # Find a set of Syms with a common property
+ # The property extends to any Sym, which has a derivation rule whose RHS
+ # consists entirely of Syms with the property
+ def self.set_closure(seed)
+ Racc.set_closure(seed) do |sym, set|
+ rules = sym.locate.map(&:rule)
+ rules.select { |r| r.symbols.all? { |s| set.include?(s) }}.map(&:target)
+ end
end
- def term=(t)
- raise 'racc: fatal: term= called twice' unless @term.nil?
- @term = t
- @nterm = !t
+ def generated?
+ @genned
end
- def should_terminal
- @should_terminal = true
+ # Don't show in diagnostic messages... EXCEPT very technical diagnostics
+ # which show the internal parser states
+ def hidden?
+ @genned && @value != :$start && @value != false && @value != :error
end
- def should_terminal?
- @should_terminal
+ def terminal?
+ heads.empty?
end
- def string_symbol?
- @string
+ def nonterminal?
+ !heads.empty?
end
- def serialize
- @serialized
+ def declared_as_terminal!
+ @declared_terminal = true
end
- attr_writer :serialized
+ def declared_as_terminal?
+ @declared_terminal
+ end
- attr_accessor :precedence
- attr_accessor :assoc
+ # is this a terminal which is written as a string literal in the grammar?
+ # (if so, it shouldn't appear on the LHS of any rule)
+ def string_symbol?
+ @string
+ end
def to_s
- @to_s.dup
+ return @display_name.dup unless Color.enabled?
+ if string_symbol?
+ Color.string(@display_name)
+ elsif terminal?
+ Color.terminal(@display_name)
+ else
+ Color.nonterminal(@display_name)
+ end
end
- alias inspect to_s
+ def inspect
+ ""
+ end
def |(x)
rule() | x.rule
@@ -1078,38 +715,44 @@ def rule
Rule.new(nil, [self], UserAction.empty)
end
- #
- # cache
- #
-
- attr_reader :heads
- attr_reader :locate
-
- def self_null?
- @snull
- end
-
- once_writer :snull
-
def nullable?
- @null
+ @nullable ||= @grammar.nullable_symbols.include?(self)
end
- def null=(n)
- @null = n
+ def shortest_production
+ @shortest_prod ||= @grammar.shortest_productions[self]
end
- attr_reader :expand
- once_writer :expand
-
- def useless?
- @useless
+ # What NTs can be reached from this symbol, by traversing from the RHS of
+ # a rule where the symbol appears, to the target of the rule, then to the
+ # RHS of its rules, and so on?
+ def reachable
+ @reachable ||= Racc.set_closure(@locate.map(&:target)) do |sym|
+ sym.locate.map(&:target)
+ end.freeze
end
- def useless=(f)
- @useless = f
+ # If an instance of this NT comes next, then what rules could we be
+ # starting?
+ def expand
+ @expand ||= Racc.set_closure(@heads.dup) do |ptr|
+ if (sym = ptr.symbol) && sym.nonterminal?
+ sym.heads
+ end
+ end.freeze
end
- end # class Sym
-
-end # module Racc
+ # What terminals/NT could appear first in a series of terminals/NTs which
+ # reduce to this symbol?
+ def first_set
+ @first_set ||= Racc.set_closure([self]) do |sym|
+ sym.heads.each_with_object([]) do |ptr, next_syms|
+ while !ptr.reduce?
+ next_syms << ptr.symbol
+ ptr.symbol.nullable? ? ptr = ptr.next : break
+ end
+ end
+ end.freeze
+ end
+ end
+end
diff --git a/lib/racc/grammar_file_parser.rb b/lib/racc/grammar_file_parser.rb
new file mode 100644
index 00000000..b0075d69
--- /dev/null
+++ b/lib/racc/grammar_file_parser.rb
@@ -0,0 +1,278 @@
+# Copyright (c) 1999-2006 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
+
+require 'racc'
+require 'racc/grammar'
+require 'racc/grammar_file_scanner'
+require 'racc/parser_file_generator'
+require 'racc/source'
+require 'racc/dsl'
+
+module Racc
+
+ grammar = DSL.define_grammar do
+ g = self
+
+ g.class = seq(:CLASS, :cname, many(:param), option(:rules), option(:END))
+
+ g.cname = seq(:rubyconst) { |name|
+ @result.params.classname = name
+ } \
+ | seq(:rubyconst, "<", :rubyconst) { |c, _, s|
+ @result.params.classname = c
+ @result.params.superclass = s
+ }
+
+ g.rubyconst = separated_by1(:colon2, :SYMBOL) { |syms|
+ syms.map(&:first).map(&:to_s).join('::')
+ }
+
+ g.colon2 = seq(':', ':')
+
+ g.param = seq(:CONV, many1(:convdef), :END) \
+ | seq(:PRECHIGH, many1(:precdef), :PRECLOW) { |*|
+ @grammar.end_precedence_declaration(true)
+ } \
+ | seq(:PRECLOW, many1(:precdef), :PRECHIGH) { |*|
+ @grammar.end_precedence_declaration(false)
+ } \
+ | seq(:START, :symbol) { |_, (sym)|
+ @grammar.start_symbol = sym
+ } \
+ | seq(:TOKEN, :symbols) { |_, syms|
+ syms.each(&:declared_as_terminal!)
+ } \
+ | seq(:OPTION, :options) { |_, syms|
+ # TODO: pull setting of options into a separate methods
+ syms.each do |opt|
+ case opt
+ when 'result_var'
+ @result.params.result_var = true
+ when 'no_result_var'
+ @result.params.result_var = false
+ else
+ raise CompileError, "unknown option: #{opt}"
+ end
+ end
+ } \
+ | seq(:EXPECT, :DIGIT) { |_, (num)|
+ @grammar.n_expected_srconflicts = num
+ }
+
+ g.convdef = seq(:symbol, :STRING) { |(sym), (code)|
+ sym.serialized = code
+ }
+
+ g.precdef = seq(:LEFT, :symbols) { |_, syms|
+ @grammar.declare_precedence :Left, syms
+ } \
+ | seq(:RIGHT, :symbols) { |_, syms|
+ @grammar.declare_precedence :Right, syms
+ } \
+ | seq(:NONASSOC, :symbols) { |_, syms|
+ @grammar.declare_precedence :Nonassoc, syms
+ }
+
+ g.symbols = seq(:symbol) { |(sym)| [sym] } \
+ | seq(:symbols, :symbol) { |list, (sym)| list << sym } \
+ | seq(:symbols, "|")
+
+ g.symbol = seq(:SYMBOL) { |(sym, range)| [@grammar.intern(sym, false), range] } \
+ | seq(:STRING) { |(str, range)| [@grammar.intern(str, false), range] }
+
+ g.options = many(:SYMBOL) { |syms| syms.map(&:first).map(&:to_s) }
+
+ g.rules = seq(:RULE, option(:rules_core) { |list| add_rule_block(list) })
+
+ # a set of grammar rules with the same LHS, like:
+ # nonterminal: token1 token2 | token3 token4;
+ # the terminating ; is optional
+ g.rules_core = seq(:symbol) { |sym| [sym] } \
+ | seq(:rules_core, :rule_item) { |list, i| list << i } \
+ | seq(:rules_core, ';') { |list, _|
+ add_rule_block(list)
+ list.clear
+ } \
+ | seq(:rules_core, ':') { |list, (_, colon_range)|
+ # terminating ; may have been missing, in which case the
+ # previous token was actually a new LHS
+ # if it wasn't missing, we will just call add_rule_block
+ # with an empty list, which won't do anything
+ next_target = list.pop
+ add_rule_block(list)
+ [next_target, [':', colon_range]]
+ }
+
+ g.rule_item = seq(:symbol) \
+ | seq("|") { |(_, range)|
+ [OrMark.new(range.lineno), range]
+ } \
+ | seq("=", :symbol) { |(_, range1), (sym, range2)|
+ range = Source::Range.new(@file, range1.from, range2.to)
+ [Prec.new(sym, range), range]
+ } \
+ | seq(:ACTION) { |(range)|
+ [UserAction.source_text(range, range.lineno), range]
+ }
+ end
+
+ GrammarFileParser = grammar.parser_class
+
+ if grammar.sr_conflicts.any?
+ raise 'Racc boot script fatal: S/R conflict in build'
+ end
+ if grammar.rr_conflicts.any?
+ raise 'Racc boot script fatal: R/R conflict in build'
+ end
+
+ class GrammarFileParser # reopen
+ class Result
+ def initialize(grammar, file, encoding = nil)
+ @grammar = grammar
+ @params = ParserFileGenerator::Params.new
+ @params.file = file
+ @params.encoding = encoding
+ end
+
+ attr_reader :grammar, :params
+ end
+
+ def GrammarFileParser.parse_file(filename)
+ new.parse(File.read(filename), filename)
+ end
+
+ def parse(src, filename = '-')
+ @file = Source::Buffer.new(filename, src)
+ @scanner = GrammarFileScanner.new(@file)
+ @grammar = Grammar.new
+ encoding = src[/\A\s*#\s*encoding:\s*(\S+)/, 1]
+ @result = Result.new(@grammar, @file, encoding)
+ @embedded_action_seq = 0
+
+ yyparse @scanner, :yylex
+ parse_user_code
+
+ @grammar.finished!
+ @result
+ end
+
+ private
+
+ def on_error(_tok, val, _values)
+ fail(CompileError, "#{@scanner.lineno}: unexpected token #{val[0].inspect}")
+ end
+
+ def add_rule_block(list)
+ return if list.empty?
+ target, target_range = *list.shift
+ target_range.highlights << Source::Highlight.new(target, 0,
+ target_range.to - target_range.from)
+
+ if target.is_a?(OrMark) || target.is_a?(UserAction) || target.is_a?(Prec)
+ fail(CompileError, "#{target.lineno}: unexpected symbol #{target.name}")
+ end
+
+ if list.empty? # only derivation rule is null
+ add_rule(target, [], target_range)
+ return
+ end
+
+ # record highlights which will be used when printing out rules
+ block_end = list.last[1].to
+ block_end += 1 if list.last[0].is_a?(UserAction) # show terminating }
+ block_range = Source::Range.new(@file, target_range.from, block_end)
+ highlights = list
+ .select { |obj, r| obj.is_a?(Sym) || obj.is_a?(Prec) }
+ .map { |obj, r| Source::Highlight.new(obj,
+ r.from - block_range.from,
+ r.to - block_range.from) }
+ block_range.highlights = highlights.unshift(target_range.highlights[0])
+
+ groups = split_array(list) { |obj, r| obj.is_a?(OrMark) }
+ groups.each do |rule_items|
+ sprec, rule_items = rule_items.partition { |obj, r| obj.is_a?(Prec) }
+ items, ranges = *rule_items.transpose
+
+ if items
+ items.shift # drop OrMark or ':'
+ ranges.shift unless ranges.one?
+ end
+
+ if ranges
+ range = block_range.slice(ranges.map(&:from).min - block_range.from,
+ ranges.map(&:to).max - block_range.from)
+ range = Source::SparseLines.new(block_range, [target_range.lines, range.lines])
+ end
+
+ if sprec.empty?
+ add_rule(target, items || [], range)
+ elsif sprec.one?
+ add_rule(target, items || [], range, sprec[0][0].symbol)
+ else
+ fail(CompileError, "'=' used twice in one rule")
+ end
+ end
+ end
+
+ def split_array(array)
+ chunk, index = [], 0
+ results = [chunk]
+ while index < array.size
+ obj = array[index]
+ if yield obj
+ chunk = [obj]
+ results << chunk
+ else
+ chunk << obj
+ end
+ index += 1
+ end
+ results
+ end
+
+ def add_rule(target, list, range, prec = nil)
+ if list.last.kind_of?(UserAction)
+ act = list.pop
+ else
+ act = UserAction.empty
+ end
+ list.map! { |s| s.kind_of?(UserAction) ? embedded_action(s, target) : s }
+ @grammar.add(Rule.new(target, list, act, range, prec))
+ end
+
+ def embedded_action(act, target)
+ sym = @grammar.intern("@action#{@embedded_action_seq += 1}".to_sym, true)
+ @grammar.add(Rule.new(sym, [], act))
+ sym
+ end
+
+ # User Code Block
+
+ def parse_user_code
+ epilogue = @scanner.epilogue
+ return unless epilogue.text
+ epilogue.text.scan(/^----([^\n\r]*)(?:\n|\r\n|\r)(.*?)(?=^----|\Z)/m) do
+ label = canonical_label($~[1])
+ range = epilogue.slice($~.begin(2), $~.end(2))
+ add_user_code(label, range)
+ end
+ end
+
+ USER_CODE_LABELS = %w(header inner footer)
+
+ def canonical_label(src)
+ label = src.to_s.strip.downcase.slice(/\w+/)
+ unless USER_CODE_LABELS.include?(label)
+ raise CompileError, "unknown user code type: #{label.inspect}"
+ end
+ label
+ end
+
+ def add_user_code(label, src)
+ @result.params.send(label.to_sym).push(src)
+ end
+ end
+end
diff --git a/lib/racc/grammar_file_scanner.rl b/lib/racc/grammar_file_scanner.rl
new file mode 100644
index 00000000..607b125f
--- /dev/null
+++ b/lib/racc/grammar_file_scanner.rl
@@ -0,0 +1,228 @@
+%%machine lex;
+
+require 'ripper'
+require 'racc/exception'
+require 'racc/source'
+
+class Racc::GrammarFileScanner
+ ReservedWords = {
+ 'right' => :RIGHT,
+ 'left' => :LEFT,
+ 'nonassoc' => :NONASSOC,
+ 'preclow' => :PRECLOW,
+ 'prechigh' => :PRECHIGH,
+ 'token' => :TOKEN,
+ 'convert' => :CONV,
+ 'options' => :OPTION,
+ 'start' => :START,
+ 'expect' => :EXPECT,
+ 'class' => :CLASS,
+ 'rule' => :RULE,
+ 'end' => :END
+ }
+
+ Delimiters = {
+ '{' => '}',
+ '<' => '>',
+ '[' => ']',
+ '(' => ')'
+ }
+
+ attr_accessor :file
+ attr_accessor :lineno
+ attr_accessor :epilogue
+
+ def initialize(file)
+ @file = file
+ @source = file.text.force_encoding(Encoding::ASCII_8BIT)
+ @eof = @source.length
+ @lineno = 1
+ @linehead = true # are we at the beginning of a line?
+ @in_block = false # are we in a 'rule' or 'convert' block?
+ @epilogue = range(@eof, @eof) # empty by default
+
+ # To make the parser generator output exactly match the legacy generator,
+ # collapse excess trailing newlines into just one
+ @source.sub!(/\n+\Z/, "\n")
+
+ # Used by Ragel-generated code:
+ @data = @source.bytes.to_a
+
+ %%write data;
+ end
+
+ def yylex(&block)
+ %%write init;
+ eof = @eof
+ %%write exec;
+ yield nil
+ end
+
+ def tok_src
+ @source[@ts...@te]
+ end
+
+ def range(from, to)
+ Racc::Source::Range.new(@file, from, to)
+ end
+
+ def token(type = nil, value = nil)
+ src_text = tok_src
+ next_line = @lineno + src_text.scan(/\n|\r\n|\r/).size
+ type ||= src_text
+ value ||= block_given? ? yield(src_text) : src_text
+ result = [type, [value, range(@ts, @te)]]
+ @lineno = next_line
+ result
+ end
+
+ def scan_error!(message)
+ raise "Error in grammar: #{message}"
+ end
+
+ %%{
+ access @;
+ getkey (@data[p]);
+
+ c_nl = '\n' | '\r\n' | '\r';
+ c_space = [ \t\r\f\v];
+ c_space_nl = c_space | c_nl;
+
+ c_eof = 0x04 | 0x1a | 0 | zlen;
+ c_eol = c_nl | c_eof;
+ c_any = any - c_eof;
+
+ c_nl_zlen = c_nl | zlen;
+ c_line = any - c_nl_zlen;
+
+ c_unicode = c_any - 0x00..0x7f;
+ c_upper = [A-Z];
+ c_lower = [a-z_] | c_unicode;
+ c_alpha = c_lower | c_upper;
+ c_alnum = c_alpha | [0-9];
+
+ ws_space = (c_space | '\\' c_nl)+;
+ ws_rbcomment = '#' c_line*;
+ ws_ccomment = '/*' (^'*' | '*' ^'/')* '*/';
+
+ ws = ws_space | ws_rbcomment | ws_ccomment;
+ symbol = /[a-zA-Z_][a-zA-Z0-9_]*/;
+ integer = digit+;
+ string = ("'" (^("'" | '\\') | '\\' c_any)* "'") |
+ ('"' (^('"' | '\\') | '\\' c_any)* '"');
+
+ #==========================
+ # main grammar file scanner
+ #==========================
+
+ main := |*
+ ws => { @lineno += @source[@ts...@te].scan(/\n|\r\n|\r/).size };
+ c_nl => { @lineno += 1; @linehead = true };
+
+ # start of user code sections
+ '----' => {
+ yield token(:END, :end) if @in_block # pretend block was closed properly
+ @epilogue = range(@ts, @eof) # save the remainder of the file
+ fbreak; # return from yylex
+ };
+
+ symbol => {
+ symbol_src = tok_src
+ if @linehead # reserved words are only meaningful at line head
+ if @in_block # in rule/convert block, 'end' is the only special word
+ if symbol_src == 'end'
+ yield token(:END, :end)
+ @in_block = false
+ else
+ yield token(:SYMBOL, &:to_sym)
+ end
+ elsif symbol_src == 'rule' || symbol_src == 'convert'
+ yield token(ReservedWords[symbol_src]) { @in_block = symbol_src.to_sym }
+ else
+ yield token(ReservedWords.fetch(symbol_src, :SYMBOL), &:to_sym)
+ end
+ else
+ yield token(:SYMBOL, &:to_sym)
+ end
+ @linehead = false
+ };
+
+ integer => {
+ yield token(:DIGIT, &:to_i)
+ @linehead = false
+ };
+ string => {
+ yield token(:STRING) { |str_content| eval(str_content) }
+ @linehead = false
+ };
+
+ '{' => {
+ # an action block can only occur inside rule block
+ if @in_block == :rule
+ rl = RubyLexer.new(@source, p + 1)
+ code = range(p + 1, rl.position)
+ yield token(:ACTION, code)
+ @lineno += code.text.scan(/\n|\r\n|\r/).size
+ fexec rl.position + 1; # jump past the concluding '}'
+ else
+ yield token
+ end
+ };
+
+ c_any => {
+ @linehead = false if tok_src == '|'
+ yield token
+ };
+ *|;
+ }%%
+
+ class RubyLexer < Ripper
+ def initialize(src, position)
+ super(src[position..-1])
+
+ @source = src
+ @start = @position = position
+ @nesting = 0
+
+ catch(:please_stop) { parse }
+
+ while @position < @source.length && @source[@position] =~ /\s/
+ @position += 1
+ end
+
+ if @source[@position] != '}'
+ raise Racc::ScanError, "scan error in action block (expected closing brace, found #{@source[@position].inspect})"
+ end
+ end
+
+ attr_reader :position
+
+ (SCANNER_EVENTS - [:embexpr_beg, :embexpr_end, :lbrace, :rbrace]).each do |event|
+ class_eval("def on_#{event}(tok); @position += tok.bytesize; end")
+ end
+
+ # On Ruby 2.0+, Ripper emits an embexpr_end token for the concluding }
+ # On Ruby 1.9, it's an rbrace
+ def on_embexpr_beg(tok)
+ @nesting += 1
+ @position += tok.size
+ end
+
+ def on_lbrace(tok)
+ @nesting += 1
+ @position += tok.size
+ end
+
+ def on_embexpr_end(tok)
+ @nesting -= 1
+ throw :please_stop if @nesting < 0
+ @position += tok.size
+ end
+
+ def on_rbrace(tok)
+ @nesting -= 1
+ throw :please_stop if @nesting < 0
+ @position += tok.size
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/racc/grammarfileparser.rb b/lib/racc/grammarfileparser.rb
deleted file mode 100644
index 5e1871de..00000000
--- a/lib/racc/grammarfileparser.rb
+++ /dev/null
@@ -1,559 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-
-require 'racc'
-require 'racc/compat'
-require 'racc/grammar'
-require 'racc/parserfilegenerator'
-require 'racc/sourcetext'
-require 'stringio'
-
-module Racc
-
- grammar = Grammar.define {
- g = self
-
- g.class = seq(:CLASS, :cname, many(:param), :RULE, :rules, option(:END))
-
- g.cname = seq(:rubyconst) {|name|
- @result.params.classname = name
- }\
- | seq(:rubyconst, "<", :rubyconst) {|c, _, s|
- @result.params.classname = c
- @result.params.superclass = s
- }
-
- g.rubyconst = separated_by1(:colon2, :SYMBOL) {|syms|
- syms.map {|s| s.to_s }.join('::')
- }
-
- g.colon2 = seq(':', ':')
-
- g.param = seq(:CONV, many1(:convdef), :END) {|*|
- #@grammar.end_convert_block # FIXME
- }\
- | seq(:PRECHIGH, many1(:precdef), :PRECLOW) {|*|
- @grammar.end_precedence_declaration true
- }\
- | seq(:PRECLOW, many1(:precdef), :PRECHIGH) {|*|
- @grammar.end_precedence_declaration false
- }\
- | seq(:START, :symbol) {|_, sym|
- @grammar.start_symbol = sym
- }\
- | seq(:TOKEN, :symbols) {|_, syms|
- syms.each do |s|
- s.should_terminal
- end
- }\
- | seq(:OPTION, :options) {|_, syms|
- syms.each do |opt|
- case opt
- when 'result_var'
- @result.params.result_var = true
- when 'no_result_var'
- @result.params.result_var = false
- when 'omit_action_call'
- @result.params.omit_action_call = true
- when 'no_omit_action_call'
- @result.params.omit_action_call = false
- else
- raise CompileError, "unknown option: #{opt}"
- end
- end
- }\
- | seq(:EXPECT, :DIGIT) {|_, num|
- if @grammar.n_expected_srconflicts
- raise CompileError, "`expect' seen twice"
- end
- @grammar.n_expected_srconflicts = num
- }
-
- g.convdef = seq(:symbol, :STRING) {|sym, code|
- sym.serialized = code
- }
-
- g.precdef = seq(:LEFT, :symbols) {|_, syms|
- @grammar.declare_precedence :Left, syms
- }\
- | seq(:RIGHT, :symbols) {|_, syms|
- @grammar.declare_precedence :Right, syms
- }\
- | seq(:NONASSOC, :symbols) {|_, syms|
- @grammar.declare_precedence :Nonassoc, syms
- }
-
- g.symbols = seq(:symbol) {|sym|
- [sym]
- }\
- | seq(:symbols, :symbol) {|list, sym|
- list.push sym
- list
- }\
- | seq(:symbols, "|")
-
- g.symbol = seq(:SYMBOL) {|sym| @grammar.intern(sym) }\
- | seq(:STRING) {|str| @grammar.intern(str) }
-
- g.options = many(:SYMBOL) {|syms| syms.map {|s| s.to_s } }
-
- g.rules = option(:rules_core) {|list|
- add_rule_block list unless list.empty?
- nil
- }
-
- g.rules_core = seq(:symbol) {|sym|
- [sym]
- }\
- | seq(:rules_core, :rule_item) {|list, i|
- list.push i
- list
- }\
- | seq(:rules_core, ';') {|list, *|
- add_rule_block list unless list.empty?
- list.clear
- list
- }\
- | seq(:rules_core, ':') {|list, *|
- next_target = list.pop
- add_rule_block list unless list.empty?
- [next_target]
- }
-
- g.rule_item = seq(:symbol)\
- | seq("|") {|*|
- OrMark.new(@scanner.lineno)
- }\
- | seq("=", :symbol) {|_, sym|
- Prec.new(sym, @scanner.lineno)
- }\
- | seq(:ACTION) {|src|
- UserAction.source_text(src)
- }
- }
-
- GrammarFileParser = grammar.parser_class
-
- if grammar.states.srconflict_exist?
- raise 'Racc boot script fatal: S/R conflict in build'
- end
- if grammar.states.rrconflict_exist?
- raise 'Racc boot script fatal: R/R conflict in build'
- end
-
- class GrammarFileParser # reopen
-
- class Result
- def initialize(grammar)
- @grammar = grammar
- @params = ParserFileGenerator::Params.new
- end
-
- attr_reader :grammar
- attr_reader :params
- end
-
- def GrammarFileParser.parse_file(filename)
- parse(File.read(filename), filename, 1)
- end
-
- def GrammarFileParser.parse(src, filename = '-', lineno = 1)
- new().parse(src, filename, lineno)
- end
-
- def initialize(debug_flags = DebugFlags.new)
- @yydebug = debug_flags.parse
- end
-
- def parse(src, filename = '-', lineno = 1)
- @filename = filename
- @lineno = lineno
- @scanner = GrammarFileScanner.new(src, @filename)
- @scanner.debug = @yydebug
- @grammar = Grammar.new
- @result = Result.new(@grammar)
- @embedded_action_seq = 0
- yyparse @scanner, :yylex
- parse_user_code
- @result.grammar.init
- @result
- end
-
- private
-
- def next_token
- @scanner.scan
- end
-
- def on_error(tok, val, _values)
- if val.respond_to?(:id2name)
- v = val.id2name
- elsif val.kind_of?(String)
- v = val
- else
- v = val.inspect
- end
- raise CompileError, "#{location()}: unexpected token '#{v}'"
- end
-
- def location
- "#{@filename}:#{@lineno - 1 + @scanner.lineno}"
- end
-
- def add_rule_block(list)
- sprec = nil
- target = list.shift
- case target
- when OrMark, UserAction, Prec
- raise CompileError, "#{target.lineno}: unexpected symbol #{target.name}"
- end
- curr = []
- list.each do |i|
- case i
- when OrMark
- add_rule target, curr, sprec
- curr = []
- sprec = nil
- when Prec
- raise CompileError, "'=' used twice in one rule" if sprec
- sprec = i.symbol
- else
- curr.push i
- end
- end
- add_rule target, curr, sprec
- end
-
- def add_rule(target, list, sprec)
- if list.last.kind_of?(UserAction)
- act = list.pop
- else
- act = UserAction.empty
- end
- list.map! {|s| s.kind_of?(UserAction) ? embedded_action(s) : s }
- rule = Rule.new(target, list, act)
- rule.specified_prec = sprec
- @grammar.add rule
- end
-
- def embedded_action(act)
- sym = @grammar.intern("@#{@embedded_action_seq += 1}".intern, true)
- @grammar.add Rule.new(sym, [], act)
- sym
- end
-
- #
- # User Code Block
- #
-
- def parse_user_code
- line = @scanner.lineno
- _, *blocks = *@scanner.epilogue.split(/^----/)
- blocks.each do |block|
- header, *body = block.lines.to_a
- label0, pathes = *header.sub(/\A-+/, '').split('=', 2)
- label = canonical_label(label0)
- (pathes ? pathes.strip.split(' ') : []).each do |path|
- add_user_code label, SourceText.new(File.read(path), path, 1)
- end
- add_user_code label, SourceText.new(body.join(''), @filename, line + 1)
- line += (1 + body.size)
- end
- end
-
- USER_CODE_LABELS = {
- 'header' => :header,
- 'prepare' => :header, # obsolete
- 'inner' => :inner,
- 'footer' => :footer,
- 'driver' => :footer # obsolete
- }
-
- def canonical_label(src)
- label = src.to_s.strip.downcase.slice(/\w+/)
- unless USER_CODE_LABELS.key?(label)
- raise CompileError, "unknown user code type: #{label.inspect}"
- end
- label
- end
-
- def add_user_code(label, src)
- @result.params.send(USER_CODE_LABELS[label]).push src
- end
-
- end
-
-
- class GrammarFileScanner
-
- def initialize(str, filename = '-')
- @lines = str.split(/\n|\r\n|\r/)
- @filename = filename
- @lineno = -1
- @line_head = true
- @in_rule_blk = false
- @in_conv_blk = false
- @in_block = nil
- @epilogue = ''
- @debug = false
- next_line
- end
-
- attr_reader :epilogue
-
- def lineno
- @lineno + 1
- end
-
- attr_accessor :debug
-
- def yylex(&block)
- unless @debug
- yylex0(&block)
- else
- yylex0 do |sym, tok|
- $stderr.printf "%7d %-10s %s\n", lineno(), sym.inspect, tok.inspect
- yield [sym, tok]
- end
- end
- end
-
- private
-
- def yylex0
- begin
- until @line.empty?
- @line.sub!(/\A\s+/, '')
- if /\A\#/ =~ @line
- break
- elsif /\A\/\*/ =~ @line
- skip_comment
- elsif s = reads(/\A[a-zA-Z_]\w*/)
- yield [atom_symbol(s), s.intern]
- elsif s = reads(/\A\d+/)
- yield [:DIGIT, s.to_i]
- elsif ch = reads(/\A./)
- case ch
- when '"', "'"
- yield [:STRING, eval(scan_quoted(ch))]
- when '{'
- lineno = lineno()
- yield [:ACTION, SourceText.new(scan_action(), @filename, lineno)]
- else
- if ch == '|'
- @line_head = false
- end
- yield [ch, ch]
- end
- else
- end
- end
- end while next_line()
- yield nil
- end
-
- def next_line
- @lineno += 1
- @line = @lines[@lineno]
- if not @line or /\A----/ =~ @line
- @epilogue = @lines.join("\n")
- @lines.clear
- @line = nil
- if @in_block
- @lineno -= 1
- scan_error! sprintf('unterminated %s', @in_block)
- end
- false
- else
- @line.sub!(/(?:\n|\r\n|\r)\z/, '')
- @line_head = true
- true
- end
- end
-
- ReservedWord = {
- 'right' => :RIGHT,
- 'left' => :LEFT,
- 'nonassoc' => :NONASSOC,
- 'preclow' => :PRECLOW,
- 'prechigh' => :PRECHIGH,
- 'token' => :TOKEN,
- 'convert' => :CONV,
- 'options' => :OPTION,
- 'start' => :START,
- 'expect' => :EXPECT,
- 'class' => :CLASS,
- 'rule' => :RULE,
- 'end' => :END
- }
-
- def atom_symbol(token)
- if token == 'end'
- symbol = :END
- @in_conv_blk = false
- @in_rule_blk = false
- else
- if @line_head and not @in_conv_blk and not @in_rule_blk
- symbol = ReservedWord[token] || :SYMBOL
- else
- symbol = :SYMBOL
- end
- case symbol
- when :RULE then @in_rule_blk = true
- when :CONV then @in_conv_blk = true
- end
- end
- @line_head = false
- symbol
- end
-
- def skip_comment
- @in_block = 'comment'
- until m = /\*\//.match(@line)
- next_line
- end
- @line = m.post_match
- @in_block = nil
- end
-
- $raccs_print_type = false
-
- def scan_action
- buf = ''
- nest = 1
- pre = nil
- @in_block = 'action'
- begin
- pre = nil
- if s = reads(/\A\s+/)
- # does not set 'pre'
- buf << s
- end
- until @line.empty?
- if s = reads(/\A[^'"`{}%#\/\$]+/)
- buf << (pre = s)
- next
- end
- case ch = read(1)
- when '{'
- nest += 1
- buf << (pre = ch)
- when '}'
- nest -= 1
- if nest == 0
- @in_block = nil
- return buf
- end
- buf << (pre = ch)
- when '#' # comment
- buf << ch << @line
- break
- when "'", '"', '`'
- buf << (pre = scan_quoted(ch))
- when '%'
- if literal_head? pre, @line
- # % string, regexp, array
- buf << ch
- case ch = read(1)
- when /[qQx]/n
- buf << ch << (pre = scan_quoted(read(1), '%string'))
- when /wW/n
- buf << ch << (pre = scan_quoted(read(1), '%array'))
- when /s/n
- buf << ch << (pre = scan_quoted(read(1), '%symbol'))
- when /r/n
- buf << ch << (pre = scan_quoted(read(1), '%regexp'))
- when /[a-zA-Z0-9= ]/n # does not include "_"
- scan_error! "unknown type of % literal '%#{ch}'"
- else
- buf << (pre = scan_quoted(ch, '%string'))
- end
- else
- # operator
- buf << '||op->' if $raccs_print_type
- buf << (pre = ch)
- end
- when '/'
- if literal_head? pre, @line
- # regexp
- buf << (pre = scan_quoted(ch, 'regexp'))
- else
- # operator
- buf << '||op->' if $raccs_print_type
- buf << (pre = ch)
- end
- when '$' # gvar
- buf << ch << (pre = read(1))
- else
- raise 'racc: fatal: must not happen'
- end
- end
- buf << "\n"
- end while next_line()
- raise 'racc: fatal: scan finished before parser finished'
- end
-
- def literal_head?(pre, post)
- (!pre || /[a-zA-Z_0-9]/n !~ pre[-1,1]) &&
- !post.empty? && /\A[\s\=]/n !~ post
- end
-
- def read(len)
- s = @line[0, len]
- @line = @line[len .. -1]
- s
- end
-
- def reads(re)
- m = re.match(@line) or return nil
- @line = m.post_match
- m[0]
- end
-
- def scan_quoted(left, tag = 'string')
- buf = left.dup
- buf = "||#{tag}->" + buf if $raccs_print_type
- re = get_quoted_re(left)
- sv, @in_block = @in_block, tag
- begin
- if s = reads(re)
- buf << s
- break
- else
- buf << @line
- end
- end while next_line()
- @in_block = sv
- buf << "<-#{tag}||" if $raccs_print_type
- buf
- end
-
- LEFT_TO_RIGHT = {
- '(' => ')',
- '{' => '}',
- '[' => ']',
- '<' => '>'
- }
-
- CACHE = {}
-
- def get_quoted_re(left)
- term = Regexp.quote(LEFT_TO_RIGHT[left] || left)
- CACHE[left] ||= /\A[^#{term}\\]*(?:\\.[^\\#{term}]*)*#{term}/
- end
-
- def scan_error!(msg)
- raise CompileError, "#{lineno()}: #{msg}"
- end
-
- end
-
-end # module Racc
diff --git a/lib/racc/info.rb b/lib/racc/info.rb
index 22e49836..8fe4d32f 100644
--- a/lib/racc/info.rb
+++ b/lib/racc/info.rb
@@ -1,16 +1,11 @@
-#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
module Racc
- VERSION = '1.4.13'
- Version = VERSION
- Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
+ VERSION = "2.0.0.dev"
+ CODENAME = 'Mecha Oishii'
+ COPYRIGHT = 'Copyright (c) 1999-2006 Minero Aoki'
end
diff --git a/lib/racc/iset.rb b/lib/racc/iset.rb
deleted file mode 100644
index de638608..00000000
--- a/lib/racc/iset.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-
-module Racc
-
- # An "indexed" set. All items must respond to :ident.
- class ISet
-
- def initialize(a = [])
- @set = a
- end
-
- attr_reader :set
-
- def add(i)
- @set[i.ident] = i
- end
-
- def [](key)
- @set[key.ident]
- end
-
- def []=(key, val)
- @set[key.ident] = val
- end
-
- alias include? []
- alias key? []
-
- def update(other)
- s = @set
- o = other.set
- o.each_index do |idx|
- if t = o[idx]
- s[idx] = t
- end
- end
- end
-
- def update_a(a)
- s = @set
- a.each {|i| s[i.ident] = i }
- end
-
- def delete(key)
- i = @set[key.ident]
- @set[key.ident] = nil
- i
- end
-
- def each(&block)
- @set.compact.each(&block)
- end
-
- def to_a
- @set.compact
- end
-
- def to_s
- "[#{@set.compact.join(' ')}]"
- end
-
- alias inspect to_s
-
- def size
- @set.nitems
- end
-
- def empty?
- @set.nitems == 0
- end
-
- def clear
- @set.clear
- end
-
- def dup
- ISet.new(@set.dup)
- end
-
- end # class ISet
-
-end # module Racc
diff --git a/lib/racc/logfilegenerator.rb b/lib/racc/logfilegenerator.rb
deleted file mode 100644
index a7e96636..00000000
--- a/lib/racc/logfilegenerator.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-
-module Racc
-
- class LogFileGenerator
-
- def initialize(states, debug_flags = DebugFlags.new)
- @states = states
- @grammar = states.grammar
- @debug_flags = debug_flags
- end
-
- def output(out)
- output_conflict out; out.puts
- output_useless out; out.puts
- output_rule out; out.puts
- output_token out; out.puts
- output_state out
- end
-
- #
- # Warnings
- #
-
- def output_conflict(out)
- @states.each do |state|
- if state.srconf
- out.printf "state %d contains %d shift/reduce conflicts\n",
- state.stateid, state.srconf.size
- end
- if state.rrconf
- out.printf "state %d contains %d reduce/reduce conflicts\n",
- state.stateid, state.rrconf.size
- end
- end
- end
-
- def output_useless(out)
- @grammar.each do |rl|
- if rl.useless?
- out.printf "rule %d (%s) never reduced\n",
- rl.ident, rl.target.to_s
- end
- end
- @grammar.each_nonterminal do |t|
- if t.useless?
- out.printf "useless nonterminal %s\n", t.to_s
- end
- end
- end
-
- #
- # States
- #
-
- def output_state(out)
- out << "--------- State ---------\n"
-
- showall = @debug_flags.la || @debug_flags.state
- @states.each do |state|
- out << "\nstate #{state.ident}\n\n"
-
- (showall ? state.closure : state.core).each do |ptr|
- pointer_out(out, ptr) if ptr.rule.ident != 0 or showall
- end
- out << "\n"
-
- action_out out, state
- end
- end
-
- def pointer_out(out, ptr)
- buf = sprintf("%4d) %s :", ptr.rule.ident, ptr.rule.target.to_s)
- ptr.rule.symbols.each_with_index do |tok, idx|
- buf << ' _' if idx == ptr.index
- buf << ' ' << tok.to_s
- end
- buf << ' _' if ptr.reduce?
- out.puts buf
- end
-
- def action_out(f, state)
- sr = state.srconf && state.srconf.dup
- rr = state.rrconf && state.rrconf.dup
- acts = state.action
- keys = acts.keys
- keys.sort! {|a,b| a.ident <=> b.ident }
-
- [ Shift, Reduce, Error, Accept ].each do |klass|
- keys.delete_if do |tok|
- act = acts[tok]
- if act.kind_of?(klass)
- outact f, tok, act
- if sr and c = sr.delete(tok)
- outsrconf f, c
- end
- if rr and c = rr.delete(tok)
- outrrconf f, c
- end
-
- true
- else
- false
- end
- end
- end
- sr.each {|tok, c| outsrconf f, c } if sr
- rr.each {|tok, c| outrrconf f, c } if rr
-
- act = state.defact
- if not act.kind_of?(Error) or @debug_flags.any?
- outact f, '$default', act
- end
-
- f.puts
- state.goto_table.each do |t, st|
- if t.nonterminal?
- f.printf " %-12s go to state %d\n", t.to_s, st.ident
- end
- end
- end
-
- def outact(f, t, act)
- case act
- when Shift
- f.printf " %-12s shift, and go to state %d\n",
- t.to_s, act.goto_id
- when Reduce
- f.printf " %-12s reduce using rule %d (%s)\n",
- t.to_s, act.ruleid, act.rule.target.to_s
- when Accept
- f.printf " %-12s accept\n", t.to_s
- when Error
- f.printf " %-12s error\n", t.to_s
- else
- raise "racc: fatal: wrong act for outact: act=#{act}(#{act.class})"
- end
- end
-
- def outsrconf(f, confs)
- confs.each do |c|
- r = c.reduce
- f.printf " %-12s [reduce using rule %d (%s)]\n",
- c.shift.to_s, r.ident, r.target.to_s
- end
- end
-
- def outrrconf(f, confs)
- confs.each do |c|
- r = c.low_prec
- f.printf " %-12s [reduce using rule %d (%s)]\n",
- c.token.to_s, r.ident, r.target.to_s
- end
- end
-
- #
- # Rules
- #
-
- def output_rule(out)
- out.print "-------- Grammar --------\n\n"
- @grammar.each do |rl|
- if @debug_flags.any? or rl.ident != 0
- out.printf "rule %d %s: %s\n",
- rl.ident, rl.target.to_s, rl.symbols.join(' ')
- end
- end
- end
-
- #
- # Tokens
- #
-
- def output_token(out)
- out.print "------- Symbols -------\n\n"
-
- out.print "**Nonterminals, with rules where they appear\n\n"
- @grammar.each_nonterminal do |t|
- tmp = <rubypath] [--embedded=rubypath]
# [-v] [--verbose]
# [-Ofilename] [--log-file=filename]
-# [-g] [--debug]
+# [-t] [--debug]
# [-E] [--embedded]
-# [-l] [--no-line-convert]
-# [-c] [--line-convert-all]
-# [-a] [--no-omit-actions]
# [-C] [--check-only]
-# [-S] [--output-status]
# [--version] [--copyright] [--help] grammarfile
#
# [+filename+]
-# Racc grammar file. Any extention is permitted.
+# Racc grammar file. Any extension is permitted.
# [-o+outfile+, --output-file=+outfile+]
# A filename for output. default is <+filename+>.tab.rb
# [-O+filename+, --log-file=+filename+]
@@ -53,21 +42,13 @@ class ParseError < StandardError; end
# output executable file(mode 755). where +path+ is the Ruby interpreter.
# [-v, --verbose]
# verbose mode. create +filename+.output file, like yacc's y.output file.
-# [-g, --debug]
-# add debug code to parser class. To display debuggin information,
-# use this '-g' option and set @yydebug true in parser class.
+# [-t, --debug]
+# add debug code to parser class. To display debugging information,
+# use this '-t' option and set @yydebug to true in parser class.
# [-E, --embedded]
# Output parser which doesn't need runtime files (racc/parser.rb).
# [-C, --check-only]
-# Check syntax of racc grammer file and quit.
-# [-S, --output-status]
-# Print messages time to time while compiling.
-# [-l, --no-line-convert]
-# turns off line number converting.
-# [-c, --line-convert-all]
-# Convert line number of actions, inner, header and footer.
-# [-a, --no-omit-actions]
-# Call all actions, even if an action is empty.
+# Check syntax of racc grammar file and quit.
# [--version]
# print Racc version and quit.
# [--copyright]
@@ -75,9 +56,9 @@ class ParseError < StandardError; end
# [--help]
# Print usage and quit.
#
-# == Generating Parser Using Racc
+# == Generating a Parser Using Racc
#
-# To compile Racc grammar file, simply type:
+# To compile a Racc grammar file, simply type:
#
# $ racc parse.y
#
@@ -85,13 +66,13 @@ class ParseError < StandardError; end
#
# == Writing A Racc Grammar File
#
-# If you want your own parser, you have to write a grammar file.
+# To generate your own parser, you have to write a grammar file.
# A grammar file contains the name of your parser class, grammar for the parser,
-# user code, and anything else.
+# and user code.
# When writing a grammar file, yacc's knowledge is helpful.
# If you have not used yacc before, Racc is not too difficult.
#
-# Here's an example Racc grammar file.
+# Here's an example Racc grammar file:
#
# class Calcparser
# rule
@@ -118,13 +99,13 @@ class ParseError < StandardError; end
#
# Racc::Parser#do_parse is simple.
#
-# It's yyparse() of yacc, and Racc::Parser#next_token is yylex().
-# This method must returns an array like [TOKENSYMBOL, ITS_VALUE].
+# It's the `yyparse` of yacc, and Racc::Parser#next_token is `yylex`.
+# This method must return an array like [TOKENSYMBOL, ITS_VALUE].
# EOF is [false, false].
-# (TOKENSYMBOL is a Ruby symbol (taken from String#intern) by default.
-# If you want to change this, see the grammar reference.
+# (TOKENSYMBOL is a Ruby Symbol by default.
+# If you want to change this, see the grammar reference.)
#
-# Racc::Parser#yyparse is little complicated, but useful.
+# Racc::Parser#yyparse is a little complicated, but useful.
# It does not use Racc::Parser#next_token, instead it gets tokens from any iterator.
#
# For example, yyparse(obj, :scan) causes
@@ -132,21 +113,14 @@ class ParseError < StandardError; end
#
# == Debugging
#
-# When debugging, "-v" or/and the "-g" option is helpful.
+# When debugging, "-v" or/and the "-t" option is helpful.
#
# "-v" creates verbose log file (.output).
-# "-g" creates a "Verbose Parser".
-# Verbose Parser prints the internal status when parsing.
+# "-t" creates a "Verbose Parser".
+# A verbose parser prints the internal status when parsing.
# But it's _not_ automatic.
-# You must use -g option and set +@yydebug+ to +true+ in order to get output.
-# -g option only creates the verbose parser.
-#
-# === Racc reported syntax error.
-#
-# Isn't there too many "end"?
-# grammar of racc file is changed in v0.10.
-#
-# Racc does not use '%' mark, while yacc uses huge number of '%' marks..
+# You must use -t option and set +@yydebug+ to +true+ in order to get output.
+# -t option only creates a verbose parser.
#
# === Racc reported "XXXX conflicts".
#
@@ -155,70 +129,58 @@ class ParseError < StandardError; end
#
# === Generated parsers does not work correctly
#
-# Try "racc -g xxxx.y".
-# This command let racc generate "debugging parser".
+# Try "racc -t xxxx.y".
+# This command makes racc generate a "debugging parser".
# Then set @yydebug=true in your parser.
# It produces a working log of your parser.
#
# == Re-distributing Racc runtime
#
-# A parser, which is created by Racc, requires the Racc runtime module;
+# A parser, which is created by Racc, requires the Racc runtime module:
# racc/parser.rb.
#
-# Ruby 1.8.x comes with Racc runtime module,
-# you need NOT distribute Racc runtime files.
+# Ruby 1.8+ comes with the Racc runtime module,
+# so you need NOT distribute Racc runtime files.
#
-# If you want to include the Racc runtime module with your parser.
-# This can be done by using '-E' option:
+# If you want to include the Racc runtime module with your parser,
+# this can be done by using the '-E' option:
#
-# $ racc -E -omyparser.rb myparser.y
+# $ racc -E -o myparser.rb myparser.y
#
-# This command creates myparser.rb which `includes' Racc runtime.
-# Only you must do is to distribute your parser file (myparser.rb).
+# This command creates a parser with the Racc runtime embedded right in the
+# parser source file, so it can run even if racc/parser.rb is not present.
#
-# Note: parser.rb is LGPL, but your parser is not.
+# Note: parser.rb is ruby license, but your parser is not.
# Your own parser is completely yours.
module Racc
-
- unless defined?(Racc_No_Extentions)
- Racc_No_Extentions = false # :nodoc:
- end
+ Racc_No_Extensions = ENV['PURERUBY'] # :nodoc:
class Parser
-
- Racc_Runtime_Version = ::Racc::VERSION
- Racc_Runtime_Revision = '$Id$'
-
- Racc_Runtime_Core_Version_R = ::Racc::VERSION
- Racc_Runtime_Core_Revision_R = '$Id$'.split[1]
begin
if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
require 'racc/cparse-jruby.jar'
- com.headius.racc.Cparse.new.load(JRuby.runtime, false)
+ if JRuby::Util.respond_to?(:load_ext) # JRuby 9.2
+ JRuby::Util.load_ext('com.headius.racc.Cparse')
+ else; require 'jruby'
+ com.headius.racc.Cparse.new.load(JRuby.runtime, false)
+ end
else
require 'racc/cparse'
end
- # Racc_Runtime_Core_Version_C = (defined in extention)
- Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2]
+
unless new.respond_to?(:_racc_do_parse_c, true)
raise LoadError, 'old cparse.so'
end
- if Racc_No_Extentions
+ if Racc_No_Extensions
raise LoadError, 'selecting ruby version of racc runtime core'
end
Racc_Main_Parsing_Routine = :_racc_do_parse_c # :nodoc:
Racc_YY_Parse_Method = :_racc_yyparse_c # :nodoc:
- Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C # :nodoc:
- Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C # :nodoc:
Racc_Runtime_Type = 'c' # :nodoc:
rescue LoadError
-puts $!
-puts $!.backtrace
Racc_Main_Parsing_Routine = :_racc_do_parse_rb
Racc_YY_Parse_Method = :_racc_yyparse_rb
- Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
- Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
Racc_Runtime_Type = 'ruby'
end
@@ -267,9 +229,11 @@ def _racc_init_sysvars
# def next_token
# @q.shift
# end
+ class_eval %{
def do_parse
- __send__(Racc_Main_Parsing_Routine, _racc_setup(), false)
+ #{Racc_Main_Parsing_Routine}(_racc_setup(), false)
end
+ }
# The method to fetch next token.
# If you use #do_parse method, you must implement #next_token.
@@ -316,7 +280,6 @@ def _racc_do_parse_rb(arg, in_debug)
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
- ;
end
end
}
@@ -327,9 +290,11 @@ def _racc_do_parse_rb(arg, in_debug)
#
# RECEIVER#METHOD_ID is a method to get next token.
# It must 'yield' the token, which format is [TOKEN-SYMBOL, VALUE].
+ class_eval %{
def yyparse(recv, mid)
- __send__(Racc_YY_Parse_Method, recv, mid, _racc_setup(), false)
+ #{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), true)
end
+ }
def _racc_yyparse_rb(recv, mid, arg, c_debug)
action_table, action_check, action_default, action_pointer,
@@ -341,7 +306,6 @@ def _racc_yyparse_rb(recv, mid, arg, c_debug)
catch(:racc_end_parse) {
until i = action_pointer[@racc_state[-1]]
while act = _racc_evalact(action_default[@racc_state[-1]], arg)
- ;
end
end
recv.__send__(mid) do |tok, val|
@@ -360,7 +324,6 @@ def _racc_yyparse_rb(recv, mid, arg, c_debug)
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
- ;
end
while !(i = action_pointer[@racc_state[-1]]) ||
@@ -373,7 +336,6 @@ def _racc_yyparse_rb(recv, mid, arg, c_debug)
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
- ;
end
end
end
@@ -389,14 +351,13 @@ def _racc_evalact(act, arg)
_, _, _, _,
_, _, _, shift_n,
reduce_n, * = arg
- nerr = 0 # tmp
if act > 0 and act < shift_n
#
# shift
#
if @racc_error_status > 0
- @racc_error_status -= 1 unless @racc_t == 1 # error token
+ @racc_error_status -= 1 unless @racc_t <= 1 # error token or EOF
end
@racc_vstack.push @racc_val
@racc_state.push act
@@ -437,14 +398,30 @@ def _racc_evalact(act, arg)
#
# error
#
+
+ # Like yacc, Racc calls `on_error` when an error occurs, and then starts
+ # to attempt auto-recovery by discarding states on the stack until it
+ # gets to a state where the 'error' token is acceptable
+ #
+ # Often, this leads to another error, and another, until all the tokens
+ # in the erroneous portion of input have been shifted (and then
+ # discarded by the auto-recovery code)
+ #
+ # So to avoid an outpouring of error messages, @racc_error_status is
+ # used to suppress 3 calls to `on_error` after each error is detected
+ # Each successful shift decrements @racc_error_status, so after the
+ # erroneous portion of input is cleared, it quickly returns to zero,
+ # and then Racc is ready to report another error again
+
case @racc_error_status
when 0
- unless arg[21] # user_yyerror
- nerr += 1
- on_error @racc_t, @racc_val, @racc_vstack
+ unless arg[21] # user_yyerror
+ on_error(@racc_t, @racc_val, @racc_vstack)
end
when 3
if @racc_t == 0 # is $
+ # We're at EOF, and another error occurred immediately after
+ # attempting auto-recovery
throw :racc_end_parse, nil
end
@racc_read_next = true
@@ -541,7 +518,7 @@ def on_error(t, val, vstack)
end
# Enter error recovering mode.
- # This method does not call #on_error.
+ # This method does not call `on_error`.
def yyerror
throw :racc_jump, 1
end
@@ -552,7 +529,15 @@ def yyaccept
throw :racc_jump, 2
end
- # Leave error recovering mode.
+ # Leave error recovery mode.
+ #
+ # When in error recovery mode, Racc suppresses 3 errors after each error
+ # which is reported (by calling `on_error`).
+ # To get out of error recovery mode, normally Racc must successfully shift
+ # 3 tokens.
+ # You can call this from `on_error`, or from an action block, to immediately
+ # get out of error recovery and stop suppressing the reporting of further
+ # errors.
def yyerrok
@racc_error_status = 0
end
@@ -580,6 +565,7 @@ def racc_reduce(toks, sim, tstack, vstack)
toks.each {|t| out.print ' ', racc_token2str(t) }
end
out.puts " --> #{racc_token2str(sim)}"
+
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
@@ -627,7 +613,5 @@ def racc_token2str(tok)
def token_to_str(t)
self.class::Racc_token_to_s_table[t]
end
-
end
-
end
diff --git a/lib/racc/parserfilegenerator.rb b/lib/racc/parser_file_generator.rb
similarity index 61%
rename from lib/racc/parserfilegenerator.rb
rename to lib/racc/parser_file_generator.rb
index f2d2788a..1d7d7dd6 100644
--- a/lib/racc/parserfilegenerator.rb
+++ b/lib/racc/parser_file_generator.rb
@@ -1,24 +1,14 @@
-#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
-require 'enumerator'
-require 'racc/compat'
-require 'racc/sourcetext'
-require 'racc/parser-text'
+require 'racc/source'
require 'rbconfig'
module Racc
-
class ParserFileGenerator
-
class Params
def self.bool_attr(name)
module_eval(<<-End)
@@ -32,28 +22,20 @@ def #{name}=(b)
End
end
- attr_accessor :filename
- attr_accessor :classname
- attr_accessor :superclass
- bool_attr :omit_action_call
+ attr_accessor :file, :encoding, :classname, :superclass
bool_attr :result_var
- attr_accessor :header
- attr_accessor :inner
- attr_accessor :footer
+ attr_accessor :header, :inner, :footer
bool_attr :debug_parser
- bool_attr :convert_line
- bool_attr :convert_line_all
bool_attr :embed_runtime
bool_attr :make_executable
attr_accessor :interpreter
def initialize
# Parameters derived from parser
- self.filename = nil
+ self.file = nil
self.classname = nil
self.superclass = 'Racc::Parser'
- self.omit_action_call = true
self.result_var = true
self.header = []
self.inner = []
@@ -61,8 +43,6 @@ def initialize
# Parameters derived from command line options
self.debug_parser = false
- self.convert_line = true
- self.convert_line_all = false
self.embed_runtime = false
self.make_executable = false
self.interpreter = nil
@@ -88,30 +68,40 @@ def generate_parser
def generate_parser_file(destpath)
init_line_conversion_system
- File.open(destpath, 'w') {|f|
- @f = f
+ if destpath == '-'
+ @f = $stdout
parser_file
- }
- File.chmod 0755, destpath if @params.make_executable?
+ else
+ File.open(destpath, 'w') do |f|
+ @f = f
+ parser_file
+ end
+ File.chmod(0755, destpath) if @params.make_executable?
+ end
end
private
def parser_file
- shebang @params.interpreter if @params.make_executable?
- notice
- line
- if @params.embed_runtime?
- embed_library runtime_source()
- else
- require 'racc/parser.rb'
+ Color.without_color do
+ shebang(@params.interpreter) if @params.make_executable?
+ encoding_comment(@params.encoding) if @params.encoding
+ notice
+ line
+ if @params.embed_runtime?
+ embed_library(read_source('info.rb'))
+ embed_library(read_source('exception.rb'))
+ embed_library(read_source('parser.rb'))
+ else
+ require 'racc/parser.rb'
+ end
+ header
+ parser_class(@params.classname, @params.superclass) do
+ inner
+ state_transition_table
+ end
+ footer
end
- header
- parser_class(@params.classname, @params.superclass) {
- inner
- state_transition_table
- }
- footer
end
c = ::RbConfig::CONFIG
@@ -121,25 +111,30 @@ def shebang(path)
line '#!' + (path == 'ruby' ? RUBY_PATH : path)
end
+ def encoding_comment(encoding)
+ line "# encoding: #{encoding}"
+ end
+
def notice
line %q[#]
line %q[# DO NOT MODIFY!!!!]
- line %Q[# This file is automatically generated by Racc #{Racc::Version}]
- line %Q[# from Racc grammer file "#{@params.filename}".]
+ line %Q[# This file was automatically generated by Racc #{Racc::VERSION}]
+ line %Q[# (codename: #{Racc::CODENAME}) from Racc grammar file "#{@params.file.name}".]
line %q[#]
end
- def runtime_source
- SourceText.new(::Racc::PARSER_TEXT, 'racc/parser.rb', 1)
+ def read_source(filename)
+ path = File.join(File.dirname(__FILE__), filename)
+ Source::Buffer.new("racc/#{filename}", File.read(path))
end
def embed_library(src)
- line %[###### #{src.filename} begin]
- line %[unless $".index '#{src.filename}']
- line %[$".push '#{src.filename}']
- put src, @params.convert_line?
+ line %[###### #{src.name} begin]
+ line %[unless $".index '#{src.name}']
+ line %[$".push '#{src.name}']
+ put src
line %[end]
- line %[###### #{src.filename} end]
+ line %[###### #{src.name} end]
end
def require(feature)
@@ -167,34 +162,30 @@ def parser_class(classname, superclass)
def header
@params.header.each do |src|
line
- put src, @params.convert_line_all?
+ put src
end
end
def inner
@params.inner.each do |src|
line
- put src, @params.convert_line?
+ put src
end
end
def footer
@params.footer.each do |src|
line
- put src, @params.convert_line_all?
+ put src
end
end
# Low Level Routines
- def put(src, convert_line = false)
- if convert_line
- replace_location(src) {
- @f.puts src.text
- }
- else
+ def put(src)
+ replace_location(src) {
@f.puts src.text
- end
+ }
end
def line(str = '')
@@ -224,30 +215,34 @@ def toplevel?
def replace_location(src)
sep = make_separator(src)
- @f.print 'self.class.' if toplevel?
- @f.puts "module_eval(<<'#{sep}', '#{src.filename}', #{src.lineno})"
+ @f.print 'Object.' if toplevel?
+ @f.puts "module_eval(<<'#{sep}', '#{src.name}', #{src.lineno})"
yield
@f.puts sep
end
def make_separator(src)
- sep = unique_separator(src.filename)
+ sep = unique_separator(src.name)
sep *= 2 while src.text.index(sep)
sep
end
def unique_separator(id)
sep = "...end #{id}/module_eval..."
- while @used_separator.key?(sep)
- sep.concat sprintf('%02x', rand(255))
+
+ if @used_separator.key?(sep)
+ suffix = 2
+ while @used_separator.key?("#{sep}#{suffix}")
+ suffix += 1
+ end
+ sep = "#{sep}#{suffix}"
end
+
@used_separator[sep] = true
sep
end
- #
# State Transition Table Serialization
- #
public
@@ -320,57 +315,8 @@ def state_transition_table
end
def integer_list(name, table)
- if table.size > 2000
- serialize_integer_list_compressed name, table
- else
- serialize_integer_list_std name, table
- end
- end
-
- def serialize_integer_list_compressed(name, table)
- # TODO: this can be made a LOT more clean with a simple split/map
- sep = "\n"
- nsep = ",\n"
- buf = ''
- com = ''
- ncom = ','
- co = com
- @f.print 'clist = ['
- table.each do |i|
- buf << co << i.to_s; co = ncom
- if buf.size > 66
- @f.print sep; sep = nsep
- @f.print "'", buf, "'"
- buf = ''
- co = com
- end
- end
- unless buf.empty?
- @f.print sep
- @f.print "'", buf, "'"
- end
- line ' ]'
-
- @f.print(<<-End)
- #{name} = arr = ::Array.new(#{table.size}, nil)
- idx = 0
- clist.each do |str|
- str.split(',', -1).each do |i|
- arr[idx] = i.to_i unless i.empty?
- idx += 1
- end
- end
- End
- end
-
- def serialize_integer_list_std(name, table)
- sep = ''
- line "#{name} = ["
- table.each_slice(10) do |ns|
- @f.print sep; sep = ",\n"
- @f.print ns.map {|n| sprintf('%6s', n ? n.to_s : 'nil') }.join(',')
- end
- line ' ]'
+ lines = table.inspect.split(/((?:\w+, ){15})/).reject { |s| s.empty? }
+ line "#{name} = #{lines.join("\n")}"
end
def i_i_sym_list(name, table)
@@ -388,7 +334,7 @@ def sym_int_hash(name, h)
@f.print "#{name} = {"
h.to_a.sort_by {|sym, i| i }.each do |sym, i|
@f.print sep; sep = ",\n"
- @f.printf " %s => %d", sym.serialize, i
+ @f.printf " %s => %d", sym.serialized, i
end
line " }"
end
@@ -404,51 +350,36 @@ def string_list(name, list)
end
def actions
- @grammar.each do |rule|
- unless rule.action.source?
- raise "racc: fatal: cannot generate parser file when any action is a Proc"
- end
+ if @grammar.any? { |rule| !rule.action.source? }
+ raise "racc: fatal: cannot generate parser file when any action is a Proc"
end
if @params.result_var?
decl = ', result'
retval = "\n result"
- default_body = ''
else
decl = ''
retval = ''
- default_body = 'val[0]'
end
@grammar.each do |rule|
line
- if rule.action.empty? and @params.omit_action_call?
+ if rule.action.empty?
line "# reduce #{rule.ident} omitted"
else
- src0 = rule.action.source || SourceText.new(default_body, __FILE__, 0)
- if @params.convert_line?
- src = remove_blank_lines(src0)
- delim = make_delimiter(src.text)
- @f.printf unindent_auto(<<-End),
- module_eval(<<'%s', '%s', %d)
- def _reduce_%d(val, _values%s)
- %s%s
- end
- %s
- End
- delim, src.filename, src.lineno - 1,
- rule.ident, decl,
- src.text, retval,
- delim
- else
- src = remove_blank_lines(src0)
- @f.printf unindent_auto(<<-End),
+ src0 = rule.action.source
+ src = src0.drop_leading_blank_lines
+ delim = make_delimiter(src.text)
+ @f.printf unindent_auto(<<-End),
+ module_eval(<<'%s', '%s', %d)
def _reduce_%d(val, _values%s)
- %s%s
+ %s%s
end
- End
+ %s
+ End
+ delim, src.name, src.lineno - 1,
rule.ident, decl,
- src.text, retval
- end
+ src.text, retval,
+ delim
end
end
line
@@ -460,15 +391,6 @@ def _reduce_none(val, _values%s)
line
end
- def remove_blank_lines(src)
- body = src.text.dup
- line = src.lineno
- while body.slice!(/\A[ \t\f]*(?:\n|\r\n|\r)/)
- line += 1
- end
- SourceText.new(body, src.filename, line)
- end
-
def make_delimiter(body)
delim = '.,.,'
while body.index(delim)
@@ -506,7 +428,5 @@ def detab(str, ts = 8)
' ' * len
}
end
-
end
-
end
diff --git a/lib/racc/pre-setup b/lib/racc/pre-setup
deleted file mode 100644
index 5027d865..00000000
--- a/lib/racc/pre-setup
+++ /dev/null
@@ -1,13 +0,0 @@
-def generate_parser_text_rb(target)
- return if File.exist?(srcfile(target))
- $stderr.puts "generating #{target}..."
- File.open(target, 'w') {|f|
- f.puts "module Racc"
- f.puts " PARSER_TEXT = <<'__end_of_file__'"
- f.puts File.read(srcfile('parser.rb'))
- f.puts "__end_of_file__"
- f.puts "end"
- }
-end
-
-generate_parser_text_rb 'parser-text.rb'
diff --git a/lib/racc/rdoc/grammar.en.rdoc b/lib/racc/rdoc/grammar.en.rdoc
new file mode 100644
index 00000000..af10803f
--- /dev/null
+++ b/lib/racc/rdoc/grammar.en.rdoc
@@ -0,0 +1,219 @@
+= Racc Grammar File Reference
+
+== Global Structure
+
+== Class Block and User Code Block
+
+There are two blocks on the toplevel. One is the 'class' block, the other is the 'user code'
+block. The 'user code' block MUST be placed after the 'class' block.
+
+== Comments
+
+You can insert comments about all places. Two styles of comments can be used, Ruby style '#.....' and C style '/\*......*\/'.
+
+== Class Block
+
+The class block is formed like this:
+
+ class CLASS_NAME
+ [precedence table]
+ [token declarations]
+ [expected number of S/R conflicts]
+ [options]
+ [semantic value conversion]
+ [start rule]
+ rule
+ GRAMMARS
+
+CLASS_NAME is a name of the parser class. This is the name of the generating parser
+class.
+
+If CLASS_NAME includes '::', Racc outputs the module clause. For example, writing
+"class M::C" causes the code below to be created:
+
+ module M
+ class C
+ :
+ :
+ end
+ end
+
+== Grammar Block
+
+The grammar block describes grammar which is able to be understood by the parser.
+Syntax is:
+
+ (token): (token) (token) (token).... (action)
+
+ (token): (token) (token) (token).... (action)
+ | (token) (token) (token).... (action)
+ | (token) (token) (token).... (action)
+
+(action) is an action which is executed when its (token)s are found.
+(action) is a ruby code block, which is surrounded by braces:
+
+ { print val[0]
+ puts val[1] }
+
+Note that you cannot use '%' string, here document, '%r' regexp in action.
+
+Actions can be omitted. When it is omitted, '' (empty string) is used.
+
+A return value of action is a value of the left side value ($$). It is the value of the
+result, or the returned value by `return` statement.
+
+Here is an example of the whole grammar block.
+
+ rule
+ goal: definition rules source { result = val }
+
+ definition: /* none */ { result = [] }
+ | definition startdesig { result[0] = val[1] }
+ | definition
+ precrule # this line continues from upper line
+ {
+ result[1] = val[1]
+ }
+
+ startdesig: START TOKEN
+
+You can use the following special local variables in action:
+
+* result ($$)
+
+The value of the left-hand side (lhs). A default value is val[0].
+
+* val ($1,$2,$3...)
+
+An array of value of the right-hand side (rhs).
+
+* _values (...$-2,$-1,$0)
+
+A stack of values. DO NOT MODIFY this stack unless you know what you are doing.
+
+== Operator Precedence
+
+This function is equal to '%prec' in yacc.
+To designate this block:
+
+ prechigh
+ nonassoc '++'
+ left '*' '/'
+ left '+' '-'
+ right '='
+ preclow
+
+`right` is yacc's %right, `left` is yacc's %left.
+
+`=` + (symbol) means yacc's %prec:
+
+ prechigh
+ nonassoc UMINUS
+ left '*' '/'
+ left '+' '-'
+ preclow
+
+ rule
+ exp: exp '*' exp
+ | exp '-' exp
+ | '-' exp =UMINUS # equals to "%prec UMINUS"
+ :
+ :
+
+== expect
+
+Racc has bison's "expect" directive.
+
+ # Example
+
+ class MyParser
+ rule
+ expect 3
+ :
+ :
+
+This directive declares "expected" number of shift/reduce conflicts. If
+"expected" number is equal to real number of conflicts, Racc does not print
+conflict warning message.
+
+== Declaring Tokens
+
+By declaring tokens, you can avoid many meaningless bugs. If declared token
+does not exist or existing token does not decleared, Racc output warnings.
+Declaration syntax is:
+
+ token TOKEN_NAME AND_IS_THIS
+ ALSO_THIS_IS AGAIN_AND_AGAIN THIS_IS_LAST
+
+== Options
+
+You can write options for Racc command in your Racc file.
+
+ options OPTION OPTION ...
+
+Options are:
+
+* omit_action_call
+
+omits empty action call or not.
+
+* result_var
+
+uses local variable "result" or not.
+
+You can use 'no_' prefix to invert their meanings.
+
+== Converting Token Symbol
+
+Token symbols are, as default,
+
+ * naked token string in Racc file (TOK, XFILE, this_is_token, ...)
+ --> symbol (:TOK, :XFILE, :this_is_token, ...)
+ * quoted string (':', '.', '(', ...)
+ --> same string (':', '.', '(', ...)
+
+You can change this default by "convert" block.
+Here is an example:
+
+ convert
+ PLUS 'PlusClass' # We use PlusClass for symbol of `PLUS'
+ MIN 'MinusClass' # We use MinusClass for symbol of `MIN'
+ end
+
+We can use almost all ruby value can be used by token symbol,
+except 'false' and 'nil'. These cause unexpected parse error.
+
+If you want to use String as token symbol, special care is required.
+For example:
+
+ convert
+ class '"cls"' # in code, "cls"
+ PLUS '"plus\n"' # in code, "plus\n"
+ MIN "\"minus#{val}\"" # in code, \"minus#{val}\"
+ end
+
+== Start Rule
+
+'%start' in yacc. This changes start rule.
+
+ start real_target
+
+== User Code Block
+
+"User Code Block" is a Ruby source code which is copied to output. There are
+three user code blocks, "header" "inner" and "footer".
+
+Format of user code is like this:
+
+ ---- header
+ ruby statement
+ ruby statement
+ ruby statement
+
+ ---- inner
+ ruby statement
+ :
+ :
+
+If four '-' exist on the line head, Racc treats it as the beginning of the
+user code block. The name of the user code block must be one word.
diff --git a/lib/racc/simulated_automaton.rb b/lib/racc/simulated_automaton.rb
new file mode 100644
index 00000000..2c29ab92
--- /dev/null
+++ b/lib/racc/simulated_automaton.rb
@@ -0,0 +1,151 @@
+require 'racc/state'
+require 'racc/directed_graph'
+require 'set'
+
+module Racc
+ class SimulatedAutomaton
+ def self.from_path(grammar, path)
+ path.each_with_object(self.new(grammar.states)) do |sym, automaton|
+ if sym.terminal?
+ automaton.consume!(sym)
+ else
+ automaton.goto!(sym)
+ end
+ end
+ end
+
+ def initialize(states)
+ @states = states
+ @state = states.first
+ @sstack = [] # state stack
+ @error = false
+ end
+
+ attr_reader :state
+
+ def stack
+ @sstack
+ end
+
+ def error?
+ @error
+ end
+
+ # consuming a terminal may set off a series of reduces before the terminal
+ # is shifted
+ def consume!(token)
+ return self if @error
+
+ action = @state.action[token] || @state.defact
+ case action
+ when Shift
+ @sstack.push(@state)
+ @state = action.goto_state
+ shifted(token)
+ when Reduce
+ reduce_by!(action.rule)
+ consume!(token)
+ when Accept
+ done
+ when Error
+ @error = true
+ error
+ else
+ raise "Illegal action type: #{action.class}"
+ end
+
+ self
+ end
+
+ def goto!(nt)
+ @sstack.push(@state)
+ @state = @state.gotos[nt].to_state
+ goto(nt)
+ self
+ end
+
+ def reduce_by!(rule)
+ rule.symbols.size.times { @state = @sstack.pop }
+ reduced(rule.target)
+ goto!(rule.target)
+ end
+
+ # Callbacks; can be overridden
+
+ def shifted(symbol)
+ end
+
+ def reduced(nt)
+ end
+
+ def goto(nt)
+ end
+
+ def done
+ end
+
+ def error
+ end
+
+ def path_to_success(traversed = Set.new)
+ # Find the shortest series of terminals/reduce operations which will take
+ # us to the accept state
+ return [] if @state.ident == 1
+ return nil if @error
+
+ # Don't go into an infinite loop exploring the same states
+ return unless traversed.add?(@sstack + [@state])
+
+ # The state stack will guide us
+ # How many symbols could we reduce the stack size by for each reduce
+ # reachable from this state?
+ core = @state.core.group_by { |ptr| [ptr.target, ptr.index] }
+
+ core.map do |_, ptrs|
+ ptr = ptrs.min_by do |p|
+ # how many terminals will it take to reach reduce, if we try to
+ # follow this rule?
+ p.following.flat_map(&:shortest_production).size
+ end
+
+ automaton = self.dup
+ path1 = automaton.follow_rule(ptr)
+ next if automaton.error?
+
+ path2 = automaton.path_to_success(traversed)
+ # If that led to an infinite loop, `path2` will be `nil`.
+ path2 && path1.concat(path2)
+ end.compact.min_by(&:size)
+ end
+
+ def follow_shortest_rule_for(sym)
+ rule = sym.heads.map(&:rule).min_by do |r|
+ r.symbols.flat_map(&:shortest_production).size
+ end
+ follow_rule(rule.ptrs[0])
+ end
+
+ def follow_rule(ptr)
+ actions = []
+ initial_state = @state
+
+ ptr.following.each do |sym|
+ if sym.terminal?
+ actions << sym
+ consume!(sym)
+ else
+ actions.concat(follow_shortest_rule_for(sym))
+ end
+ end
+
+ reduce_by!(ptr.rule)
+ actions << ReduceStep.new(initial_state, @state, ptr.rule, ptr.target)
+ end
+
+ def dup
+ result = super
+ @sstack = @sstack.dup
+ result
+ end
+ end
+end
diff --git a/lib/racc/source.rb b/lib/racc/source.rb
new file mode 100644
index 00000000..09163845
--- /dev/null
+++ b/lib/racc/source.rb
@@ -0,0 +1,265 @@
+# Copyright (c) 1999-2006 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
+
+module Racc
+ module Source
+ NL = /\n|\r\n|\r/
+ TAB_WIDTH = 8
+ TAB_TO_SPACE = (' ' * TAB_WIDTH).freeze
+ TERM_WIDTH = 80
+
+ # methods which can be used on any object which represents source text
+ module Text
+ def drop_leading_blank_lines
+ if text =~ /\A(?:[ \t\f\v]*(?:#{NL}))+/
+ slice($~.end(0), text.size)
+ else
+ self
+ end
+ end
+
+ def location
+ "#{name}:#{lineno}"
+ end
+
+ # display with color highlights...
+ def spiffy
+ highlights.sort_by!(&:from)
+
+ raw = text
+ cooked = String.new
+ offset = 0
+
+ highlights.each do |hilite|
+ cooked << raw[offset...hilite.from]
+ cooked << hilite.to_s
+ offset = hilite.to
+ end
+ cooked << raw[offset...raw.size]
+ end
+
+ # ...and with corrected indention...
+ def spiffier
+ cooked = spiffy
+ # convert leading tabs to spaces so we get indentation right
+ cooked.gsub!(/^ *(\t+)/) { TAB_TO_SPACE * $1.size }
+ cooked = (' ' * column) << cooked if column > 0
+ cooked.gsub!(/^ {#{min_indent}}/, '')
+ cooked
+ end
+
+ # ...and with location
+ def spifferific
+ loc = location << ': '
+ # add extra indentation at every line start EXCEPT the first
+ # (to make everything line up)
+ cooked = spiffier
+ cooked.gsub!(/(?= from && t.to <= to }
+ .map { |h| Highlight.new(h.object, h.from - from, h.to - from ) }
+ end
+ end
+
+ if Array.method_defined?(:bsearch)
+ def line_for(offset)
+ line, _ = line_offsets.bsearch { |lineno, start| start <= offset }
+ line
+ end
+ def column_for(offset)
+ _, column = line_offsets.bsearch { |lineno, start| start <= offset }
+ offset - column
+ end
+ else
+ def line_for(offset)
+ line, _ = line_offsets.find { |lineno, start| start <= offset }
+ line
+ end
+ def column_for(offset)
+ _, column = line_offsets.find { |lineno, start| start <= offset }
+ offset - column
+ end
+ end
+
+ # line N starts at...
+ # (a newline is part of the preceding line)
+ def line_offsets
+ @line_offsets ||= begin
+ offsets = [[1, 0]]
+ index = 1
+ text.scan(NL) { offsets.unshift([index += 1, $~.end(0)]) }
+ offsets
+ end
+ end
+
+ def min_indent
+ @min_indent ||= begin
+ lines = text.lines.reject { |line| line =~ /\A\s*\Z/ }
+ lines.map { |line| line[/\A\s*/].gsub("\t", TAB_TO_SPACE).size }.min || 0
+ end
+ end
+ end
+
+ class Range
+ include Text
+
+ def initialize(buffer, from, to)
+ @buffer = buffer
+ @from = from
+ @to = to
+ @highlights = []
+ end
+
+ attr_reader :from, :to
+ attr_accessor :highlights
+
+ def text
+ @text ||= @buffer.text[@from...@to]
+ end
+
+ def name
+ @buffer.name
+ end
+
+ def lineno
+ @buffer.line_for(@from)
+ end
+
+ def lines
+ (@buffer.line_for(@from))..(@buffer.line_for(@to))
+ end
+
+ def column
+ @buffer.column_for(@from)
+ end
+
+ def slice(from, to)
+ raise 'slice end must be >= start' if from > to
+ max = @to - @from
+ to = max if to > max
+ from = max if from > max
+ Range.new(@buffer, @from + from, @from + to).tap do |range|
+ range.highlights =
+ @highlights
+ .select { |h| h.from >= from && h.to <= to }
+ .map { |h| Highlight.new(h.object, h.from - from, h.to - from) }
+ end
+ end
+
+ def min_indent
+ @min_indent ||= begin
+ lines = text.lines.reject { |line| line =~ /\A\s*\Z/ }
+ widths = lines.map { |line| line[/\A\s*/].gsub("\t", TAB_TO_SPACE).size }
+ widths[0] += @buffer.column_for(@from)
+ widths.min || 0
+ end
+ end
+ end
+
+ class Highlight
+ def initialize(object, from, to)
+ @object = object # model object which this text represents
+ @from = from # offset WITHIN parent Buffer/Range
+ @to = to
+ freeze
+ end
+
+ attr_reader :from, :to, :object
+
+ def to_s
+ object.to_s # code objects print themselves with color highlighting
+ end
+ end
+
+ # A (sparse) set of lines from a Buffer or Range
+ class SparseLines
+ def initialize(textobj, line_ranges)
+ @textobj = textobj
+ @lines = line_ranges.sort_by(&:begin)
+ freeze
+ end
+
+ attr_reader :textobj, :lines
+
+ def self.merge(sparse)
+ sparse.group_by(&:textobj).map do |textobj, slines|
+ SparseLines.new(textobj, slines.flat_map(&:lines))
+ end
+ end
+
+ def self.render(sparse)
+ merge(sparse).map(&:spifferific).join("\n\n")
+ end
+
+ def spifferific
+ cooked = @textobj.spiffier.lines.map(&:chomp)
+ base_line = @textobj.lineno
+ ranges = canonicalize_ranges(@lines)
+ groups = ranges.map { |r| cooked[(r.begin - base_line)..(r.end - base_line)] }
+ groups = groups.map! { |g| g.join("\n") }
+
+ loc_width = "#{@textobj.name}:#{ranges.last.begin}: ".length
+
+ groups.zip(ranges).map! do |g, range|
+ g.gsub!(/(? last.end + (last.exclude_end? ? 0 : 1)
+ result << (last = range)
+ else
+ range_end = (range.end - (range.exclude_end? ? 1 : 0))
+ last_end = (last.end - (last.exclude_end? ? 1 : 0))
+ combined = last.begin..(range_end > last_end ? range_end : last_end)
+ result[-1] = last = combined
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/racc/sourcetext.rb b/lib/racc/sourcetext.rb
deleted file mode 100644
index 3b2d89d9..00000000
--- a/lib/racc/sourcetext.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# $Id$
-#
-# Copyright (c) 1999-2006 Minero Aoki
-#
-# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of LGPL, see the file "COPYING".
-#
-
-module Racc
-
- class SourceText
- def initialize(text, filename, lineno)
- @text = text
- @filename = filename
- @lineno = lineno
- end
-
- attr_reader :text
- attr_reader :filename
- attr_reader :lineno
-
- def to_s
- "#"
- end
-
- def location
- "#{@filename}:#{@lineno}"
- end
- end
-
-end
diff --git a/lib/racc/state.rb b/lib/racc/state.rb
index 735e622b..599f6fd7 100644
--- a/lib/racc/state.rb
+++ b/lib/racc/state.rb
@@ -1,42 +1,46 @@
-#
-# $Id$
-#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
-# You can distribute/modify this program under the terms of
-# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of the GNU LGPL, see the file "COPYING".
-#
+# You can distribute/modify this program under the same terms of ruby.
+# see the file "COPYING".
-require 'racc/iset'
-require 'racc/statetransitiontable'
+require 'racc/state_transition_table'
require 'racc/exception'
-require 'forwardable'
+require 'racc/util'
+require 'racc/directed_graph'
-module Racc
+require 'set'
- # A table of LALR states.
+module Racc
class States
-
include Enumerable
- def initialize(grammar, debug_flags = DebugFlags.new)
+ def initialize(grammar)
@grammar = grammar
- @symboltable = grammar.symboltable
- @d_state = debug_flags.state
- @d_la = debug_flags.la
- @d_prec = debug_flags.prec
- @states = []
- @statecache = {}
- @actions = ActionTable.new(@grammar, self)
- @nfa_computed = false
- @dfa_computed = false
+ @states = []
+ @gotos = [] # all state transitions performed when reducing
+ # Goto is also used for state transitions when shifting,
+ # but those objects don't go in this array
+
+ generate_states
+ compute_lookahead
+
+ @states.each { |state| resolve(state) }
+ set_accept
+ @states.each { |state| pack(state) }
end
attr_reader :grammar
attr_reader :actions
+ def each(&block)
+ @states.each(&block)
+ end
+
+ def [](idx)
+ @states[idx]
+ end
+
def size
@states.size
end
@@ -47,876 +51,588 @@ def inspect
alias to_s inspect
- def [](i)
- @states[i]
- end
-
- def each_state(&block)
- @states.each(&block)
- end
-
- alias each each_state
-
- def each_index(&block)
- @states.each_index(&block)
- end
-
- extend Forwardable
-
- def_delegator "@actions", :shift_n
- def_delegator "@actions", :reduce_n
- def_delegator "@actions", :nt_base
-
def should_report_srconflict?
- srconflict_exist? and
- (n_srconflicts() != @grammar.n_expected_srconflicts)
- end
-
- def srconflict_exist?
- n_srconflicts() != 0
+ sr_conflicts.any? && (sr_conflicts.size != @grammar.n_expected_srconflicts)
end
- def n_srconflicts
- @n_srconflicts ||= inject(0) {|sum, st| sum + st.n_srconflicts }
+ def sr_conflicts
+ flat_map { |state| state.sr_conflicts.values }
end
- def rrconflict_exist?
- n_rrconflicts() != 0
- end
-
- def n_rrconflicts
- @n_rrconflicts ||= inject(0) {|sum, st| sum + st.n_rrconflicts }
+ def rr_conflicts
+ flat_map { |state| state.rr_conflicts.values }
end
def state_transition_table
- @state_transition_table ||= StateTransitionTable.generate(self.dfa)
- end
-
- #
- # NFA (Non-deterministic Finite Automaton) Computation
- #
-
- public
-
- def nfa
- return self if @nfa_computed
- compute_nfa
- @nfa_computed = true
- self
+ @state_transition_table ||= StateTransitionTable.generate(self)
end
private
- def compute_nfa
- @grammar.init
- # add state 0
- core_to_state [ @grammar[0].ptrs[0] ]
- # generate LALR states
- cur = 0
- @gotos = []
- while cur < @states.size
- generate_states @states[cur] # state is added here
- cur += 1
- end
- @actions.init
- end
+ def generate_states
+ # create start state
+ start = State.new(0, Set[@grammar[0].ptrs[0]], self)
- def generate_states(state)
- puts "dstate: #{state}" if @d_state
+ @states << start
+ states = {start.core => start}
+ worklist = [start]
- table = {}
- state.closure.each do |ptr|
- if sym = ptr.dereference
- addsym table, sym, ptr.next
- end
- end
- table.each do |sym, core|
- puts "dstate: sym=#{sym} ncore=#{core}" if @d_state
-
- dest = core_to_state(core.to_a)
- state.goto_table[sym] = dest
- id = sym.nonterminal?() ? @gotos.size : nil
- g = Goto.new(id, sym, state, dest)
- @gotos.push g if sym.nonterminal?
- state.gotos[sym] = g
- puts "dstate: #{state.ident} --#{sym}--> #{dest.ident}" if @d_state
-
- # check infinite recursion
- if state.ident == dest.ident and state.closure.size == 1
- raise CompileError,
- sprintf("Infinite recursion: state %d, with rule %d",
- state.ident, state.ptrs[0].rule.ident)
- end
- end
- end
+ until worklist.empty?
+ state = worklist.shift
- def addsym(table, sym, ptr)
- unless s = table[sym]
- table[sym] = s = ISet.new
- end
- s.add ptr
- end
+ # build table of what the 'core' of the following state will be, if the
+ # next token appearing in the input was 'sym'
+ #
+ # a 'core' is a set of LocationPointers, indicating all the possible
+ # positions within the RHS of a rule where we could be right now
+ # convert core to a State object; if state does not exist, create it
- def core_to_state(core)
- #
- # convert CORE to a State object.
- # If matching state does not exist, create it and add to the table.
- #
-
- k = fingerprint(core)
- unless dest = @statecache[k]
- # not registered yet
- dest = State.new(@states.size, core)
- @states.push dest
-
- @statecache[k] = dest
-
- puts "core_to_state: create state ID #{dest.ident}" if @d_state
- else
- if @d_state
- puts "core_to_state: dest is cached ID #{dest.ident}"
- puts "core_to_state: dest core #{dest.core.join(' ')}"
+ table = Hash.new { |h,k| h[k] = Set.new }
+ state.closure.each do |ptr|
+ table[ptr.symbol].add(ptr.next) unless ptr.reduce?
end
- end
-
- dest
- end
- def fingerprint(arr)
- arr.map {|i| i.ident }.pack('L*')
- end
-
- #
- # DFA (Deterministic Finite Automaton) Generation
- #
-
- public
+ table.each do |sym, core|
+ # each possible 'core' corresponds to one LALR state
- def dfa
- return self if @dfa_computed
- nfa
- compute_dfa
- @dfa_computed = true
- self
- end
+ unless dest = states[core]
+ # not registered yet
+ dest = State.new(@states.size, core, self)
+ @states << dest
+ worklist << dest
+ states[core] = dest
+ end
- private
+ goto = Goto.new(sym.nonterminal? && @gotos.size, sym, state, dest)
+ @gotos << goto if sym.nonterminal?
+ state.gotos[sym] = goto
- def compute_dfa
- la = lookahead()
- @states.each do |state|
- state.la = la
- resolve state
- end
- set_accept
- @states.each do |state|
- pack state
+ if state.ident == dest.ident and state.closure.size == 1
+ rule = state.ptrs[0].rule
+ raise CompileError, "Infinite recursion in rule: #{rule}"
+ end
+ end
end
- check_useless
end
- def lookahead
- #
+ def compute_lookahead
# lookahead algorithm ver.3 -- from bison 1.26
- #
-
- gotos = @gotos
- if @d_la
- puts "\n--- goto ---"
- gotos.each_with_index {|g, i| print i, ' '; p g }
- end
- ### initialize_LA()
- ### set_goto_map()
- la_rules = []
- @states.each do |state|
- state.check_la la_rules
- end
-
- ### initialize_F()
- f = create_tmap(gotos.size)
- reads = []
- edge = []
- gotos.each do |goto|
- goto.to_state.goto_table.each do |t, st|
- if t.terminal?
- f[goto.ident] |= (1 << t.ident)
- elsif t.nullable?
- edge.push goto.to_state.gotos[t].ident
+ # build a bitmap which shows which terminals could possibly appear next
+ # after each reduction in the grammar
+ # (we will use this information to decide which reduction to perform if
+ # two of them are possible, or whether to do a shift or a reduce if both
+ # are possible)
+ # (if both reductions A and B are possible, but we see that the next token
+ # can only validly appear after reduction A and not B, then we will choose
+ # to perform reduction A)
+ following_terminals = create_bitmap(@gotos.size)
+ look_past = Graph::Finite.new(@gotos.size)
+ @gotos.each do |goto|
+ goto.to_state.gotos.each do |tok, next_goto|
+ if tok.terminal?
+ # set bit for terminal which could be shifted after this reduction
+ following_terminals[goto.ident] |= (1 << tok.ident)
+ elsif tok.nullable?
+ # if a nullable NT could come next, then we have to look past it
+ # to see which terminals could appear next
+ look_past.add_child(goto.ident, next_goto.ident)
end
end
- if edge.empty?
- reads.push nil
- else
- reads.push edge
- edge = []
- end
- end
- digraph f, reads
- if @d_la
- puts "\n--- F1 (reads) ---"
- print_tab gotos, reads, f
end
-
- ### build_relations()
- ### compute_FOLLOWS
- path = nil
- edge = []
- lookback = Array.new(la_rules.size, nil)
- includes = []
- gotos.each do |goto|
+ look_past.freeze
+
+ # traverse graph with arrows connecting reductions which could occur
+ # directly after another without shifting any terminal first (because the
+ # reduced nonterminal is null)
+ walk_graph(following_terminals, look_past)
+
+ # there is another case we have to consider to get the full set of tokens
+ # which can validly appear after each reduction...
+ # what if we do 2 reductions in a row? or 3, 4, 5...?
+ # if terminal T1 can appear after nonterminal A, and we can reduce to A
+ # immediately after reducing to B, that means terminal T1 could also
+ # appear after B
+
+ # but that's not all! think about this:
+ # what if we have a rule like "A = BC", and we know terminal T1 can appear
+ # after A, *and C is nullable*?
+ # that means T1 can also appear after B, not just after C
+
+ includes = Graph::Finite.new(@gotos.size)
+ # look at the state transition triggered by each reduction in the grammar
+ # (at each place in the state graph where that reduction can occur)
+ @gotos.each do |goto|
+ # look at RHS of each rule which could have lead to this reduction
goto.symbol.heads.each do |ptr|
- path = record_path(goto.from_state, ptr.rule)
- lastgoto = path.last
- st = lastgoto ? lastgoto.to_state : goto.from_state
- if st.conflict?
- addrel lookback, st.rruleid(ptr.rule), goto
- end
- path.reverse_each do |g|
- break if g.symbol.terminal?
- edge.push g.ident
- break unless g.symbol.nullable?
+ # what sequence of state transitions would we have made to reach
+ # this reduction, if this is the rule that was used?
+ path(goto.from_state, ptr.rule).reverse_each do |preceding_goto|
+ break if preceding_goto.symbol.terminal?
+ includes.add_child(preceding_goto.ident, goto.ident)
+ break unless preceding_goto.symbol.nullable?
end
end
- if edge.empty?
- includes.push nil
- else
- includes.push edge
- edge = []
- end
- end
- includes = transpose(includes)
- digraph f, includes
- if @d_la
- puts "\n--- F2 (includes) ---"
- print_tab gotos, includes, f
end
+ includes.freeze
- ### compute_lookaheads
- la = create_tmap(la_rules.size)
- lookback.each_with_index do |arr, i|
- if arr
- arr.each do |g|
- la[i] |= f[g.ident]
- end
- end
- end
- if @d_la
- puts "\n--- LA (lookback) ---"
- print_tab la_rules, lookback, la
- end
-
- la
- end
-
- def create_tmap(size)
- Array.new(size, 0) # use Integer as bitmap
- end
+ walk_graph(following_terminals, includes)
- def addrel(tbl, i, item)
- if a = tbl[i]
- a.push item
- else
- tbl[i] = [item]
- end
- end
+ # Now we know which terminals can follow each reduction
+ # But this lookahead information is only needed when there would otherwise
+ # be a S/R or R/R conflict
- def record_path(begst, rule)
- st = begst
- path = []
- rule.symbols.each do |t|
- goto = st.gotos[t]
- path.push goto
- st = goto.to_state
- end
- path
- end
+ # So, find all the states leading to a possible reduce, where there is a
+ # S/R or R/R conflict, and copy the lookahead set for each reduce to the
+ # preceding state which has the conflict
- def transpose(rel)
- new = Array.new(rel.size, nil)
- rel.each_with_index do |arr, idx|
- if arr
- arr.each do |i|
- addrel new, i, idx
+ @gotos.each do |goto|
+ goto.symbol.heads.each do |ptr|
+ path = path(goto.from_state, ptr.rule)
+ prev_state = (path.last && path.last.to_state) || goto.from_state
+ if prev_state.conflict?
+ ritem = prev_state.ritems.find { |item| item.rule == ptr.rule }
+ ritem.lookahead |= following_terminals[goto.ident]
end
end
end
- new
end
- def digraph(map, relation)
- n = relation.size
- index = Array.new(n, nil)
- vertices = []
- @infinity = n + 2
+ def create_bitmap(size)
+ Array.new(size, 0) # use Integer as bitmap
+ end
- index.each_index do |i|
- if not index[i] and relation[i]
- traverse i, index, vertices, map, relation
- end
+ # Sequence of state transitions which would be taken when starting
+ # from 'state', then following the RHS of 'rule' right to the end
+ def path(state, rule)
+ rule.symbols.each_with_object([]) do |tok, path|
+ goto = state.gotos[tok]
+ path << goto
+ state = goto.to_state
end
end
- def traverse(i, index, vertices, map, relation)
- vertices.push i
- index[i] = height = vertices.size
+ # traverse a directed graph
+ # each entry in 'bitmap' corresponds to a graph node
+ # after the traversal, the bitmap for each node will be the union of its
+ # original value, and ALL the values for all the nodes which are reachable
+ # from it
+ def walk_graph(bitmap, graph)
+ index = Array.new(graph.size, nil)
+ traversed = Set.new
- if rp = relation[i]
- rp.each do |proci|
- unless index[proci]
- traverse proci, index, vertices, map, relation
- end
- if index[i] > index[proci]
- # circulative recursion !!!
- index[i] = index[proci]
- end
- map[i] |= map[proci]
- end
+ graph.nodes do |node|
+ next if traversed.include?(node)
+ traverse(node, traversed, index, [], bitmap, graph)
end
+ end
- if index[i] == height
- while true
- proci = vertices.pop
- index[proci] = @infinity
- break if i == proci
+ def traverse(node, traversed, index, stack, bitmap, graph)
+ traversed.add(node)
+ stack.push(node)
+ index[node] = stack_depth = stack.size
- map[proci] |= map[i]
+ graph.children(node) do |next_node|
+ unless index[next_node]
+ traverse(next_node, traversed, index, stack, bitmap, graph)
end
- end
- end
- # for debug
- def print_atab(idx, tab)
- tab.each_with_index do |i,ii|
- printf '%-20s', idx[ii].inspect
- p i
- end
- end
+ if index[node] > index[next_node]
+ # there is a cycle in the graph
+ # we already passed through 'next_node' to reach here
+ index[node] = index[next_node]
+ end
- def print_tab(idx, rel, tab)
- tab.each_with_index do |bin,i|
- print i, ' ', idx[i].inspect, ' << '; p rel[i]
- print ' '
- each_t(@symboltable, bin) {|t| print ' ', t }
- puts
+ bitmap[node] |= bitmap[next_node]
end
- end
- # for debug
- def print_tab_i(idx, rel, tab, i)
- bin = tab[i]
- print i, ' ', idx[i].inspect, ' << '; p rel[i]
- print ' '
- each_t(@symboltable, bin) {|t| print ' ', t }
- end
+ if index[node] == stack_depth
+ while true
+ next_node = stack.pop
+ index[next_node] = graph.size + 2
+ break if node == next_node
- # for debug
- def printb(i)
- each_t(@symboltable, i) do |t|
- print t, ' '
+ bitmap[next_node] |= bitmap[node]
+ end
end
- puts
end
- def each_t(tbl, set)
- 0.upto( set.size ) do |i|
- (0..7).each do |ii|
- if set[idx = i * 8 + ii] == 1
- yield tbl[idx]
- end
+ def resolve(state)
+ if state.conflict?
+ resolve_rr(state, state.ritems)
+ resolve_sr(state, state.stokens)
+ elsif state.rrules.empty?
+ # shift
+ state.stokens.each do |t|
+ state.action[t] = Shift.new(state.gotos[t].to_state)
end
+ else
+ # only reduce is possible; we won't even bother looking at the next
+ # token in this state
+ state.defact = Reduce.new(state.rrules[0])
end
end
- #
- # resolve
- #
+ def resolve_rr(state, ritems)
+ rrules = Hash.new { |h,k| h[k] = [] }
- def resolve(state)
- if state.conflict?
- resolve_rr state, state.ritems
- resolve_sr state, state.stokens
- else
- if state.rrules.empty?
- # shift
- state.stokens.each do |t|
- state.action[t] = @actions.shift(state.goto_table[t])
- end
- else
- # reduce
- state.defact = @actions.reduce(state.rrules[0])
+ ritems.each do |item|
+ item.each_lookahead_token(@grammar.symbols) do |sym|
+ rrules[sym] << item.rule
end
end
- end
- def resolve_rr(state, r)
- r.each do |item|
- item.each_la(@symboltable) do |t|
- act = state.action[t]
- if act
- unless act.kind_of?(Reduce)
- raise "racc: fatal: #{act.class} in action table"
- end
- # Cannot resolve R/R conflict (on t).
- # Reduce with upper rule as default.
- state.rr_conflict act.rule, item.rule, t
- else
- # No conflict.
- state.action[t] = @actions.reduce(item.rule)
+ rrules.each do |sym, rules|
+ # If there is a conflict, reduce with the rule which appeared
+ # first in the source
+ state.action[sym] = Reduce.new(rules[0])
+ if rules.size > 1
+ state.rr_conflict!(sym, rules)
+ rules.drop(1).each do |rule|
+ rule.overridden_by[sym] << rules[0]
end
end
end
end
- def resolve_sr(state, s)
- s.each do |stok|
- goto = state.goto_table[stok]
+ def resolve_sr(state, stokens)
+ stokens.each do |stok|
+ goto = state.gotos[stok]
act = state.action[stok]
unless act
# no conflict
- state.action[stok] = @actions.shift(goto)
+ state.action[stok] = Shift.new(goto.to_state)
else
- unless act.kind_of?(Reduce)
- puts 'DEBUG -------------------------------'
- p stok
- p act
- state.action.each do |k,v|
- print k.inspect, ' ', v.inspect, "\n"
- end
- raise "racc: fatal: #{act.class} in action table"
- end
-
- # conflict on stok
-
+ # conflict
rtok = act.rule.precedence
- case do_resolve_sr(stok, rtok)
+ case do_resolve_sr(stok, rtok, act.rule)
when :Reduce
# action is already set
when :Shift
# overwrite
- act.decref
- state.action[stok] = @actions.shift(goto)
+ act.rule.overridden_by[stok].merge(state.srules[stok].map(&:rule))
+ state.action[stok] = Shift.new(goto.to_state)
when :Error
- act.decref
- state.action[stok] = @actions.error
+ state.action[stok] = Error.new
when :CantResolve
# shift as default
- act.decref
- state.action[stok] = @actions.shift(goto)
- state.sr_conflict stok, act.rule
+ srules = state.srules[stok]
+ act.rule.overridden_by[stok].merge(srules.map(&:rule))
+ state.action[stok] = Shift.new(goto.to_state)
+ state.sr_conflict!(stok, srules, act.rule)
end
end
end
end
-
+
ASSOC = {
:Left => :Reduce,
:Right => :Shift,
:Nonassoc => :Error
}
-
- def do_resolve_sr(stok, rtok)
- puts "resolve_sr: s/r conflict: rtok=#{rtok}, stok=#{stok}" if @d_prec
-
- unless rtok and rtok.precedence
- puts "resolve_sr: no prec for #{rtok}(R)" if @d_prec
- return :CantResolve
- end
- rprec = rtok.precedence
- unless stok and stok.precedence
- puts "resolve_sr: no prec for #{stok}(S)" if @d_prec
- return :CantResolve
- end
- sprec = stok.precedence
+ def do_resolve_sr(stok, rtok, rrule)
+ return :CantResolve unless rtok && (rprec = rtok.precedence)
+ return :CantResolve unless stok && (sprec = stok.precedence)
- ret = if rprec == sprec
- ASSOC[rtok.assoc] or
- raise "racc: fatal: #{rtok}.assoc is not Left/Right/Nonassoc"
- else
- (rprec > sprec) ? (:Reduce) : (:Shift)
- end
+ rrule.explicit_precedence_used!
- puts "resolve_sr: resolved as #{ret.id2name}" if @d_prec
- ret
+ if rprec == sprec
+ ASSOC[rtok.assoc] ||
+ (raise "racc: fatal: #{rtok}.assoc is not Left/Right/Nonassoc")
+ else
+ (rprec > sprec) ? :Reduce : :Shift
+ end
end
- #
- # complete
- #
-
def set_accept
- anch = @symboltable.anchor
- init_state = @states[0].goto_table[@grammar.start]
+ anch = @grammar.anchor
+ init_state = @states[0].gotos[@grammar.start].to_state
targ_state = init_state.action[anch].goto_state
acc_state = targ_state.action[anch].goto_state
acc_state.action.clear
- acc_state.goto_table.clear
- acc_state.defact = @actions.accept
+ acc_state.defact = Accept.new
end
def pack(state)
- ### find most frequently used reduce rule
- act = state.action
- arr = Array.new(@grammar.size, 0)
- act.each do |t, a|
- arr[a.ruleid] += 1 if a.kind_of?(Reduce)
- end
- i = arr.max
- s = (i > 0) ? arr.index(i) : nil
-
- ### set & delete default action
- if s
- r = @actions.reduce(s)
- if not state.defact or state.defact == r
- act.delete_if {|t, a| a == r }
- state.defact = r
+ # find most frequently used reduce rule, and make it the default action
+ state.defact ||= begin
+ freq = Hash.new(0)
+ state.action.each do |tok, act|
+ freq[act.rule] += 1 if act.is_a?(Reduce)
end
- else
- state.defact ||= @actions.error
- end
- end
- def check_useless
- used = []
- @actions.each_reduce do |act|
- if not act or act.refn == 0
- act.rule.useless = true
+ if freq.empty?
+ Error.new
else
- t = act.rule.target
- used[t.ident] = t
- end
- end
- @symboltable.nt_base.upto(@symboltable.nt_max - 1) do |n|
- unless used[n]
- @symboltable[n].useless = true
+ most_common = freq.keys.max_by { |rule| freq[rule] }
+ reduce = Reduce.new(most_common)
+ state.action.delete_if { |tok, act| act == reduce }
+ reduce
end
end
end
- end # class StateTable
-
-
- # A LALR state.
- class State
-
- def initialize(ident, core)
- @ident = ident
- @core = core
- @goto_table = {}
- @gotos = {}
- @stokens = nil
- @ritems = nil
- @action = {}
- @defact = nil
- @rrconf = nil
- @srconf = nil
+ public
- @closure = make_closure(@core)
+ def useless_rules
+ used_rules = Set.new
+ @states.each do |state|
+ state.action.each do |tok, act|
+ used_rules << act.rule if act.is_a?(Reduce)
+ end
+ used_rules << state.defact.rule if state.defact.is_a?(Reduce)
+ end
+ Set.new(@grammar) - used_rules
end
- attr_reader :ident
- alias stateid ident
- alias hash ident
-
- attr_reader :core
- attr_reader :closure
-
- attr_reader :goto_table
- attr_reader :gotos
-
- attr_reader :stokens
- attr_reader :ritems
- attr_reader :rrules
+ def warnings(warnings, verbose = false)
+ useless_rules.each do |rule|
+ next unless warnings.for_rule(rule).empty? &&
+ !rule.overridden_by.empty?
+ warnings.add_for_rule(rule, Warning::RuleAlwaysOverridden.new(rule))
+ end
- attr_reader :action
- attr_accessor :defact # default action
+ if should_report_srconflict?
+ sr_conflicts.each do |sr|
+ warnings.add_for_state(sr.state,
+ Warning::SRConflict.new(sr, @grammar, verbose))
+ end
+ end
- attr_reader :rrconf
- attr_reader :srconf
+ rr_conflicts.each do |rr|
+ warnings.add_for_state(rr.state,
+ Warning::RRConflict.new(rr, @grammar, verbose))
+ end
- def inspect
- ""
+ warnings
end
- alias to_s inspect
-
- def ==(oth)
- @ident == oth.ident
+ def transition_graph
+ # this graph does not have vectors for reduce operations -- rather,
+ # the nodes where the reduces go to have vectors for the reduced NTs
+ @tgraph ||= each_with_object(Graph::Labeled.new(size)) do |s, graph|
+ s.gotos.each do |tok, goto|
+ graph.add_vector(s.ident, goto.to_state.ident, tok)
+ end
+ end.tap { |graph| graph.start = 0 }.freeze
end
- alias eql? ==
-
- def make_closure(core)
- set = ISet.new
- core.each do |ptr|
- set.add ptr
- if t = ptr.dereference and t.nonterminal?
- set.update_a t.expand
- end
- end
- set.to_a
- end
-
- def check_la(la_rules)
- @conflict = false
- s = []
- r = []
- @closure.each do |ptr|
- if t = ptr.dereference
- if t.terminal?
- s[t.ident] = t
- if t.ident == 1 # $error
- @conflict = true
- end
+ # Like `transition_graph`, but rather than vectors labeled with NTs, we
+ # have vectors labeled with the shortest series of terminals and reduce
+ # operations which could take us through the same transition
+ def detailed_transition_graph
+ @dtgraph ||= each_with_object(Graph::Labeled.new(size)) do |s, graph|
+ s.gotos.each do |tok, goto|
+ path = if tok.terminal?
+ [tok]
+ else
+ actions_to_reach_reduce(s.ident, tok)
end
- else
- r.push ptr.rule
+ graph.add_vector(s.ident, goto.to_state.ident, path)
end
+ end.tap { |graph| graph.start = 0 }.freeze
+ end
+
+ # What series of shifts/reduces can produce `target`, starting from state
+ # `state_idx`?
+ def actions_to_reach_reduce(state_idx, target)
+ rule = target.heads.map(&:rule).min_by do |r|
+ r.symbols.flat_map(&:shortest_production).size
end
- unless r.empty?
- if not s.empty? or r.size > 1
- @conflict = true
+
+ actions, cur_state = [], state_idx
+ rule.symbols.each do |sym|
+ if sym.terminal?
+ actions << sym
+ else
+ actions.concat(actions_to_reach_reduce(cur_state, sym))
end
+ cur_state = transition_graph[cur_state][sym]
end
- s.compact!
- @stokens = s
- @rrules = r
-
- if @conflict
- @la_rules_i = la_rules.size
- @la_rules = r.map {|i| i.ident }
- la_rules.concat r
- else
- @la_rules_i = @la_rules = nil
- end
- end
+ cur_state = transition_graph[state_idx][target]
- def conflict?
- @conflict
+ actions << ReduceStep.new(state_idx, cur_state, rule, target)
end
- def rruleid(rule)
- if i = @la_rules.index(rule.ident)
- @la_rules_i + i
- else
- puts '/// rruleid'
- p self
- p rule
- p @rrules
- p @la_rules_i
- raise 'racc: fatal: cannot get reduce rule id'
- end
+ def shortest_summarized_paths
+ @shortest_spaths ||= transition_graph.shortest_vector_paths
end
- def la=(la)
- return unless @conflict
- i = @la_rules_i
- @ritems = r = []
- @rrules.each do |rule|
- r.push Item.new(rule, la[i])
- i += 1
+ def shortest_detailed_paths
+ @shortest_dpaths ||= begin
+ paths = detailed_transition_graph.shortest_vector_paths(&:size)
+ Hash[paths.map { |state, steps| [state, steps.flatten] }]
end
end
- def rr_conflict(high, low, ctok)
- c = RRconflict.new(@ident, high, low, ctok)
-
- @rrconf ||= {}
- if a = @rrconf[ctok]
- a.push c
- else
- @rrconf[ctok] = [c]
+ def possible_reduce_destinations(state, rule)
+ # after popping states off the stack and following the goto,
+ # what states might we end up in?
+ graph = transition_graph
+ steps_back = rule.symbols.size
+ dest_indices = steps_back.times.reduce([state.ident]) do |dsts, _|
+ dsts.map { |dst| graph.parents(dst) }.reduce(Set.new, &:merge)
end
+ dest_indices.map { |idx| self[idx].gotos[rule.target].to_state }.uniq
end
+ end
- def sr_conflict(shift, reduce)
- c = SRconflict.new(@ident, shift, reduce)
-
- @srconf ||= {}
- if a = @srconf[shift]
- a.push c
- else
- @srconf[shift] = [c]
- end
+ class State
+ def initialize(ident, core, states)
+ @ident = ident # ID number used to provide a canonical ordering
+ @core = core # LocationPointers to all the possible positions within the
+ # RHS of a rule where we could be when in this state
+
+ @gotos = {} # Sym -> Goto describing state transition if we encounter
+ # that Sym next
+ @action = {} # Sym -> Shift/Reduce/Accept/Error describing what we will
+ # do if we encounter that Sym next
+ @defact = nil # if action table has no entry for a certain lookahead
+ # token, perform this action instead
+ # (if action table is empty, just perform this action
+ # without even checking the lookahead token)
+ @rr_conflicts = {}
+ @sr_conflicts = {}
+ @states = states
+
+ @core.freeze
end
- def n_srconflicts
- @srconf ? @srconf.size : 0
- end
+ attr_reader :ident
+ attr_reader :core
+ attr_reader :gotos
+ attr_reader :action
+ attr_accessor :defact # default action
+ attr_reader :rr_conflicts
+ attr_reader :sr_conflicts
- def n_rrconflicts
- @rrconf ? @rrconf.size : 0
+ def inspect
+ ""
end
- end # class State
-
-
- #
- # Represents a transition on the grammar.
- # "Real goto" means a transition by nonterminal,
- # but this class treats also terminal's.
- # If one is a terminal transition, .ident returns nil.
- #
- class Goto
- def initialize(ident, sym, from, to)
- @ident = ident
- @symbol = sym
- @from_state = from
- @to_state = to
- end
+ alias to_s inspect
- attr_reader :ident
- attr_reader :symbol
- attr_reader :from_state
- attr_reader :to_state
-
- def inspect
- "(#{@from_state.ident}-#{@symbol}->#{@to_state.ident})"
+ def closure
+ # Say we know that we are at "A = B . C" right now; in other words,
+ # we know that we are parsing an "A", we have already finished the "B",
+ # and the "C" should be coming next
+ # If "C" is a non-terminal, then that means the RHS of one of the rules
+ # for C should come next (but we don't know which one)
+ # So we could possibly be beginning ANY of the rules for C here
+ # But if one of the rules for C itself starts with non-terminal "D"...
+ # well, to find all the possible positions where we could be in each
+ # rule, we have to recurse down into all the rules for D (and so on)
+ # This recursion has already been done and the result cached in Sym#expand
+ @closure ||= @core.each_with_object(Set.new) do |ptr, set|
+ set.add(ptr)
+ if sym = ptr.symbol and sym.nonterminal?
+ set.merge(sym.expand)
+ end
+ end.sort_by(&:ident)
end
- end
-
- # LALR item. A set of rule and its lookahead tokens.
- class Item
- def initialize(rule, la)
- @rule = rule
- @la = la
+ def stokens
+ @stokens ||= closure.reject(&:reduce?).map(&:symbol).select(&:terminal?)
+ .uniq.sort_by(&:ident)
end
- attr_reader :rule
- attr_reader :la
-
- def each_la(tbl)
- la = @la
- 0.upto(la.size - 1) do |i|
- (0..7).each do |ii|
- if la[idx = i * 8 + ii] == 1
- yield tbl[idx]
- end
+ # {Sym -> LocationPointers within rules which direct us to shift that Sym}
+ def srules
+ @srules ||= begin
+ closure.each_with_object(Hash.new { |h,k| h[k] = [] }) do |ptr, table|
+ next if ptr.reduce? || ptr.symbol.nonterminal?
+ table[ptr.symbol] << ptr
end
end
end
- end
-
-
- # The table of LALR actions. Actions are either of
- # Shift, Reduce, Accept and Error.
- class ActionTable
-
- def initialize(rt, st)
- @grammar = rt
- @statetable = st
- @reduce = []
- @shift = []
- @accept = nil
- @error = nil
+ def rrules
+ @rrules ||= closure.select(&:reduce?).map(&:rule)
end
- def init
- @grammar.each do |rule|
- @reduce.push Reduce.new(rule)
- end
- @statetable.each do |state|
- @shift.push Shift.new(state)
+ # would there be a S/R or R/R conflict IF lookahead was not used?
+ def conflict?
+ @conflict ||= begin
+ (rrules.size > 1) ||
+ (stokens.any? { |tok| tok.ident == 1 }) || # $error symbol
+ (stokens.any? && rrules.any?)
end
- @accept = Accept.new
- @error = Error.new
end
- def reduce_n
- @reduce.size
+ # rules for which we need a lookahead set (to disambiguate which of them we
+ # should apply next)
+ def ritems
+ @ritems ||= conflict? ? rrules.map { |rule| Item.new(rule) } : []
end
- def reduce(i)
- case i
- when Rule then i = i.ident
- when Integer then ;
- else
- raise "racc: fatal: wrong class #{i.class} for reduce"
- end
-
- r = @reduce[i] or raise "racc: fatal: reduce action #{i.inspect} not exist"
- r.incref
- r
+ def rr_conflict!(sym, rules)
+ @rr_conflicts[sym] = RRConflict.new(self, sym, rules)
end
- def each_reduce(&block)
- @reduce.each(&block)
+ def sr_conflict!(token, srules, rrule)
+ @sr_conflicts[token] = SRConflict.new(self, token, srules, rrule)
end
- def shift_n
- @shift.size
+ def shortest_summarized_path
+ # samples sequence of terminals/NTs which would lead here
+ # from start state (for diagnostics)
+ @sspath ||= @states.shortest_summarized_paths[@ident]
end
- def shift(i)
- case i
- when State then i = i.ident
- when Integer then ;
- else
- raise "racc: fatal: wrong class #{i.class} for shift"
+ def shortest_detailed_path
+ # sample sequence of terminals/reduces which would lead here
+ # from start state (for diagnostics)
+ @sdpath ||= @states.shortest_detailed_paths[@ident].reject do |step|
+ step.is_a?(ReduceStep) && step.symbol.hidden?
end
-
- @shift[i] or raise "racc: fatal: shift action #{i} does not exist"
end
-
- def each_shift(&block)
- @shift.each(&block)
- end
-
- attr_reader :accept
- attr_reader :error
-
end
-
- class Shift
- def initialize(goto)
- @goto_state = goto
- end
-
- attr_reader :goto_state
-
- def goto_id
- @goto_state.ident
- end
-
+ # Represents a transition between states in the grammar
+ # Descriptions of the LR algorithm only talk about doing a "goto" after
+ # reducing, but this class can also represent a state transition which occurs
+ # after shifting
+ # If 'symbol' is a terminal, then ident will be nil (there is no global
+ # ordering of such Gotos).
+ #
+ class Goto < Struct.new(:ident, :symbol, :from_state, :to_state)
def inspect
- ""
+ "(#{from_state.ident}-#{symbol}->#{to_state.ident})"
end
end
-
- class Reduce
+ # LALR item: a rule and its lookahead tokens
+ class Item
def initialize(rule)
@rule = rule
- @refn = 0
+ @lookahead = 0 # bitmap of terminal ID numbers (Sym#ident)
end
attr_reader :rule
- attr_reader :refn
+ attr_accessor :lookahead
- def ruleid
- @rule.ident
+ def each_lookahead_token(tbl)
+ 0.upto((@lookahead.size * 8) - 1) do |idx|
+ yield tbl[idx] if @lookahead[idx] == 1
+ end
end
+ end
+ class Shift < Struct.new(:goto_state)
def inspect
- ""
- end
-
- def incref
- @refn += 1
+ ""
end
+ end
- def decref
- @refn -= 1
- raise 'racc: fatal: act.refn < 0' if @refn < 0
+ class Reduce < Struct.new(:rule)
+ def inspect
+ ""
end
end
@@ -932,40 +648,33 @@ def inspect
end
end
- class SRconflict
- def initialize(sid, shift, reduce)
- @stateid = sid
- @shift = shift
- @reduce = reduce
- end
-
- attr_reader :stateid
- attr_reader :shift
- attr_reader :reduce
-
+ class SRConflict < Struct.new(:state, :symbol, :srules, :rrule)
def to_s
- sprintf('state %d: S/R conflict rule %d reduce and shift %s',
- @stateid, @reduce.ruleid, @shift.to_s)
+ "state #{state.ident}: S/R conflict on #{symbol} between shift rules " \
+ "#{srules} and reduce rule #{rrule}"
end
end
- class RRconflict
- def initialize(sid, high, low, tok)
- @stateid = sid
- @high_prec = high
- @low_prec = low
- @token = tok
+ class RRConflict < Struct.new(:state, :symbol, :rules)
+ def to_s
+ "state #{state.ident}: R/R conflict on #{symbol} between reduce rules " \
+ "#{rrules}"
end
+ end
+
+ # Specifically for detailed_transition_graph
+ class ReduceStep
+ def initialize(from, to, rule, symbol)
+ @from = from
+ @to = to
+ @rule = rule
+ @symbol = symbol
+ end
+
+ attr_reader :from, :to, :rule, :symbol
- attr_reader :stateid
- attr_reader :high_prec
- attr_reader :low_prec
- attr_reader :token
-
def to_s
- sprintf('state %d: R/R conflict with rule %d and %d on %s',
- @stateid, @high_prec.ident, @low_prec.ident, @token.to_s)
+ "(reduce to #{@symbol})"
end
end
-
end
diff --git a/lib/racc/state_summary_generator.rb b/lib/racc/state_summary_generator.rb
new file mode 100644
index 00000000..cc05d3bb
--- /dev/null
+++ b/lib/racc/state_summary_generator.rb
@@ -0,0 +1,254 @@
+require 'racc/state'
+require 'racc/directed_graph'
+require 'racc/util'
+
+require 'erb'
+
+module Racc
+ class StateSummaryGenerator
+ def initialize(states, filename)
+ @states = states
+ @filename = filename
+ end
+
+ def generate_summary_file(destpath)
+ if destpath == '-'
+ puts render
+ else
+ File.open(destpath, 'w') do |f|
+ f.write(render)
+ end
+ end
+ end
+
+ def render
+ ERB.new(TEMPLATE, nil, nil, '@output').result(binding)
+ end
+
+ private
+
+ def print_state_title(state)
+ @output << "State #{state.ident}"
+ @output << ' (end state)' if state.defact.is_a?(Accept)
+ @output << ' (start state)' if state.ident == 0
+ end
+
+ def print_loc_ptr_as_tr(ptr)
+ rule = ptr.rule
+
+ @output << '
'
+ print_symbol(rule.target)
+ @output << '
:
'
+ ptr.preceding.reject(&:hidden?).each do |sym|
+ print_symbol(sym)
+ @output << ' '
+ end
+ @output << '. '
+ ptr.following.reject(&:hidden?).each do |sym|
+ print_symbol(sym)
+ @output << ' '
+ end
+ if sym = rule.explicit_precedence
+ print_explicit_prec(sym)
+ end
+ @output << '