Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+22
View File
@@ -0,0 +1,22 @@
Copyright (C) 1993-2013 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.
+56
View File
@@ -0,0 +1,56 @@
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
You can redistribute it and/or modify it under either the terms of the
2-clause BSDL (see the file BSDL), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or binary form,
provided that you do at least ONE of the following:
a. distribute the binaries and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under these terms.
For the list of those files and their copying conditions, see the
file LEGAL.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
+846
View File
@@ -0,0 +1,846 @@
Tue Feb 20 18:45:45 2007 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/grammar.rb (separated_by): return [] for empty list.
Tue Nov 7 07:13:47 2006 Minero Aoki <aamine@loveruby.net>
* lib/racc/grammar.rb (Rule#prec): rule.prec{...} should set
action.
Tue Nov 7 06:38:57 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/grammar.rb: #should_terminal should be called in
#check_terminals.
Fri Aug 4 00:44:56 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/parserfilegenerator.rb
(serialize_integer_list_compressed): fix typo.
Thu Aug 3 22:20:34 2006 Minero Aoki <aamine@loveruby.net>
* bin/y2racc: fix filename.
Thu Aug 3 21:10:48 2006 Minero Aoki <aamine@loveruby.net>
* bin/y2racc: getopts -> optparse.
Thu Aug 3 19:35:34 2006 Minero Aoki <aamine@loveruby.net>
* setup.rb: updated.
Thu Aug 3 19:34:55 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/pre-setup: We need not make grammarfileparser.rb.
Sat Jul 29 04:30:33 2006 Minero Aoki <aamine@loveruby.net>
* lib/racc/grammar.rb: allow '|' operation with meta rules
(many, option...).
Sat Jul 29 03:17:20 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/compat.rb: reduce warning.
Sun Jul 16 05:07:12 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* ext/racc/cparse/cparse.so: should mark VALUEs in cparse_params.
Tue Jul 4 02:24:27 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/debugflags.rb: remove unused class GenerationOptions.
Tue Jul 4 02:14:48 2006 Minero Aoki <aamine@loveruby.net>
* lib/racc/compat.rb: update coding style.
Mon Jul 3 04:34:32 2006 Minero Aoki <aamine@loveruby.net>
* 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 accept 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 accept 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 <aamine@loveruby.net>
* Makefile: new task bootstrap-force.
Sun Jul 2 19:46:58 2006 Minero Aoki <aamine@loveruby.net>
* test/ichk.y: update coding style.
Sun Jul 2 19:01:55 2006 Minero Aoki <aamine@loveruby.net>
* ext/racc/cparse/cparse.c: must require version.h to get
RUBY_VERSION_CODE.
Sun Jul 2 18:33:32 2006 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* ext/racc/cparse/cparse.c: K&R -> ANSI C.
Mon Nov 21 02:37:10 2005 Minero Aoki <aamine@loveruby.net>
* version 1.4.5 released.
Mon Nov 21 02:31:18 2005 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* bin/racc2y: refactoring.
* bin/y2racc: refactoring.
Sun Nov 20 23:46:42 2005 Minero Aoki <aamine@loveruby.net>
* lib/racc/pre-setup: new file.
Sun Nov 20 22:46:21 2005 Minero Aoki <aamine@loveruby.net>
* COPYING: new file.
Sun Nov 20 22:25:15 2005 Minero Aoki <aamine@loveruby.net>
* setup.rb: import setup.rb 3.4.1.
Thu Sep 29 02:51:56 2005 Minero Aoki <aamine@loveruby.net>
* Makefile (clean): invoke `make clean' in ext.
Thu Sep 29 02:50:56 2005 Minero Aoki <aamine@loveruby.net>
* lib/racc/.cvsignore: removed.
Thu Sep 29 02:46:30 2005 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* setup.rb: new file.
Tue Jul 26 23:37:46 2005 Minero Aoki <aamine@loveruby.net>
* bin/racc: --no-omit-actions did not work (This patch is
contributed by OHKUBO Takuya).
Sun Jan 2 11:48:19 2005 Minero Aoki <aamine@loveruby.net>
* lib/racc/grammar.rb (once_writer): bug! needs argument.
Mon Feb 16 16:14:16 2004 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* lib/racc/rubyloader.rb: imported rev1.6.
Fri Dec 12 01:57:47 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* Makefile (all): make cparse.so.
Mon Nov 3 17:19:26 2003 Minero Aoki <aamine@loveruby.net>
* lib/racc/parser.rb: update version.
* ext/racc/cparse/cparse.c: update version.
Mon Nov 3 17:19:01 2003 Minero Aoki <aamine@loveruby.net>
* Makefile: update version in parser.rb, cparse.c.
Sun Oct 12 23:49:58 2003 Minero Aoki <aamine@loveruby.net>
* version 1.4.4.
Sun Oct 12 23:49:40 2003 Minero Aoki <aamine@loveruby.net>
* bin/y2racc: did not work.
* bin/y2racc: -u options did not work.
Sun Oct 12 23:41:46 2003 Minero Aoki <aamine@loveruby.net>
* misc/dist.sh: cd before make.
Sun Oct 12 23:38:04 2003 Minero Aoki <aamine@loveruby.net>
* Makefile (site): create $siteroot/{ja,en}/man/racc/*.html.
Sun Oct 12 23:37:18 2003 Minero Aoki <aamine@loveruby.net>
* doc/parser.rrd.m: missing 'j'.
Sun Oct 12 23:29:11 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* web/racc.ja.html: update descriptions.
* web/racc.en.html: ditto.
Sun Oct 12 18:43:45 2003 Minero Aoki <aamine@loveruby.net>
* misc/dist.sh: remove web/ directory before distribute.
Sun Oct 12 18:37:29 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* misc/dist.sh: forgot to remove tmp comment out.
Sun Oct 12 18:12:09 2003 Minero Aoki <aamine@loveruby.net>
* lib/racc/info.rb: version 1.4.4.
Sun Oct 12 18:11:42 2003 Minero Aoki <aamine@loveruby.net>
* Makefile (dist): split out misc/dist.sh.
* misc/dist.sh: new file.
Sun Oct 12 17:18:47 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* sameple/calc-ja.y: simplify.
Sun Oct 12 16:24:16 2003 Minero Aoki <aamine@loveruby.net>
* misc/y2racc -> bin/y2racc
* misc/racc2y -> bin/racc2y
Sun Oct 12 15:56:30 2003 Minero Aoki <aamine@loveruby.net>
* bin/racc: follow method name change.
Sun Oct 12 15:34:14 2003 Minero Aoki <aamine@loveruby.net>
* Makefile: new target `test'.
* Makefile: missing $datadir.
Sun Oct 12 15:33:02 2003 Minero Aoki <aamine@loveruby.net>
* README.ja: update description.
* README.en: ditto.
Sun Oct 12 15:25:23 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* Makefile: new file.
Sun Oct 12 15:19:57 2003 Minero Aoki <aamine@loveruby.net>
* BUGS.en: removed.
* BUGS.ja: removed.
Sun Oct 12 15:10:38 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* test/test.rb: use /bin/rm if exists.
Sun Oct 12 14:33:29 2003 Minero Aoki <aamine@loveruby.net>
* rubyloader.rb: imported from amstd, rev 1.5.
Sun Oct 12 14:24:47 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* test/test.rb: refactoring.
Tue Jun 24 03:14:01 2003 Minero Aoki <aamine@as600.loveruby.net>
* ucodep.rb: typo: Grammer -> Grammar
Mon May 26 23:06:58 2003 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* raccs.rb: update coding style.
Fri Nov 15 17:53:12 2002 Minero Aoki <aamine@loveruby.net>
* racc: changes style.
* parser.rb: ditto.
Fri Nov 15 17:11:52 2002 Minero Aoki <aamine@loveruby.net>
version 1.4.3.
Fri Nov 15 17:08:01 2002 Minero Aoki <aamine@loveruby.net>
* 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 <aamine@loveruby.net>
* raccs.rb: explicit method call for VCALL.
Wed Oct 16 15:45:11 2002 Minero Aoki <aamine@loveruby.net>
* parser.rb: reformat.
Fri Aug 9 18:21:01 2002 Minero Aoki <aamine@loveruby.net>
* cparse/cparse.c: use better variable/macro names.
Wed Aug 7 08:39:19 2002 Minero Aoki <aamine@loveruby.net>
* cparse/cparse.c: goto label requires stmt.
Mon Aug 5 21:53:07 2002 Minero Aoki <aamine@loveruby.net>
* cparse/cparse.c: grand refine.
* cparse/depend: re-added from ruby/ext/racc/cparse.
Tue Jun 4 00:15:28 2002 Minero Aoki <aamine@loveruby.net>
* boot.rb: allow to omit last 'end'.
Mon Jun 3 23:29:45 2002 Minero Aoki <aamine@loveruby.net>
* racc (write_table_file): shebang must placed on first line.
(reported by Hiroyuki Sato)
+58
View File
@@ -0,0 +1,58 @@
= Racc
* https://github.com/ruby/racc
== DESCRIPTION:
Racc は LALR(1) パーサジェネレータです。
yacc の Ruby 版に相当します。
== 必要環境
* Ruby 2.5 以降
== インストール
gem インストール:
$ gem install racc
== テスト
sample/ 以下にいくつか Racc の文法ファイルのサンプルが用意
してあります。動くのも動かないのもありますが、少なくとも
calc-ja.y は動くのでこれを処理してみましょう。Racc をインス
トールしたあと
$ racc -ocalc.rb calc-ja.y
として下さい。処理は一瞬から数秒で終わるので、
$ ruby calc.rb
を実行してください。ちゃんと動いてますか?
Racc の文法など詳しいことは doc.ja/ ディレクトリ以下の HTML を
見てください。
== ライセンス
このパッケージに付属するファイルの著作権は青木峰郎が保持します。
ライセンスは Ruby ライセンスです。ただしユーザが書いた規則
ファイルや、Racc がそこから生成した Ruby スクリプトはその対象
外です。好きなライセンスで配布してください。
== バグなど
Racc を使っていてバグらしき現象に遭遇したら、下記のアドレスまで
メールをください。作者にはバグを修正する義務はありませんがその
意思はあります。また、そのときはできるだけバグを再現できる文法
ファイルを付けてください。
青木峰郎(あおきみねろう)
aamine@loveruby.net
http://i.loveruby.net
+60
View File
@@ -0,0 +1,60 @@
= Racc
* https://github.com/ruby/racc
== DESCRIPTION:
Racc is an LALR(1) parser generator.
It is written in Ruby itself, and generates Ruby program.
== Requirement
* Ruby 2.5 or later.
== Installation
gem install:
$ gem install racc
== Testing Racc
Racc comes with simple calculator. To compile this, on shell:
$ racc -o calc calc.y
This process costs few seconds (or less). Then type:
$ ruby calc
... Does it work?
For details of Racc, see HTML documents placed under 'doc/en/'
and sample grammar files under 'sample/'.
== Release flow
* Update VERSION number of these files
* <code>RACC_VERSION</code> in "ext/racc/com/headius/racc/Cparse.java"
* <code>VERSION</code> in "lib/racc/info.rb"
* Release as a gem by <code>rake release</code> with CRuby and JRuby because Racc gem provides 2 packages
* Create new release on {GitHub}[https://github.com/ruby/racc/releases]
== License
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 report is welcome.
If you find a bug of Racc, please report an issue at
https://github.com/ruby/racc/issues. Your grammar file,
debug output generated by "racc -g", are helpful.
Minero Aoki
aamine@loveruby.net
http://i.loveruby.net
+5
View File
@@ -0,0 +1,5 @@
* check 'error' token handling.
* interactive transition table monitor.
* support backtracking.
* output Ruby extension library?
* LL(k)? (But it should not be called Racc)
+320
View File
@@ -0,0 +1,320 @@
#!/usr/bin/env ruby
#
#
#
# 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/static'
require 'optparse'
def main
output = nil
debug_parser = false
make_logfile = false
logfilename = nil
make_executable = false
rubypath = nil
embed_runtime = false
frozen_strings = 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)
parser = OptionParser.new
parser.banner = "Usage: #{File.basename($0)} [options] [input]"
parser.on('-o', '--output-file=PATH',
'output file name [<input>.tab.rb]') {|name|
output = name
}
parser.on('-t', '--debug', 'Outputs debugging parser.') {|fl|
debug_parser = fl
}
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('-v', '--verbose',
'Creates <filename>.output log file.') {|fl|
make_logfile = fl
}
parser.on('-O', '--log-file=PATH',
'Log file name [<input>.output]') {|path|
make_logfile = true
logfilename = path
}
parser.on('-e', '--executable [RUBYPATH]', 'Makes executable parser.') {|path|
make_executable = true
rubypath = (path == 'ruby' ? nil : path)
}
parser.on('-E', '--embedded', "Embeds Racc runtime in output.") {
embed_runtime = true
}
parser.on('-F', '--frozen', "Add frozen_string_literals: true.") {
frozen_strings = 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('-C', '--check-only', 'Checks syntax and quit immediately.') {|fl|
check_only = fl
}
parser.on('-S', '--output-status', 'Outputs internal status time to time.') {
verbose = true
}
parser.on('-P', 'Enables generator profile') {
profiler = RaccProfiler.new(true)
}
parser.on('-D flags', "Flags for Racc debugging (do not use).") {|flags|
debug_flags = Racc::DebugFlags.parse_option_string(flags)
}
#parser.on('--no-extensions', 'Run Racc without any Ruby extension.') {
# Racc.const_set :Racc_No_Extensions, true
#}
parser.on('--version', 'Prints version and quit.') {
puts "racc version #{Racc::Version}"
exit
}
parser.on('--runtime-version', 'Prints runtime version and quit.') {
printf "racc runtime version %s; %s core version %s\n",
Racc::Parser::Racc_Runtime_Version,
Racc::Parser.racc_runtime_type,
if Racc::Parser.racc_runtime_type == 'ruby'
Racc::Parser::Racc_Runtime_Core_Version_R
else
Racc::Parser::Racc_Runtime_Core_Version_C
end
exit
}
parser.on('--copyright', 'Prints copyright and quit.') {
puts Racc::Copyright
exit
}
parser.on('--help', 'Prints this message and quit.') {
puts parser.help
exit
}
begin
parser.parse!
rescue OptionParser::ParseError => err
abort [err.message, parser.help].join("\n")
end
if ARGV.size > 1
abort 'too many input'
end
input = ARGV[0] || "stdin"
if input == "stdin" && !output then
abort 'You must specify a path to read or use -o <path> for output.'
end
begin
$stderr.puts 'Parsing grammar file...' if verbose
result = profiler.section('parse') {
parser = Racc::GrammarFileParser.new(debug_flags)
content = input == "stdin" ? ARGF.read : File.read(input)
parser.parse(content, File.basename(input))
}
if check_only
$stderr.puts 'syntax ok'
exit
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
params.filename = File.basename(input)
# 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
end
params.debug_parser = debug_parser
params.convert_line = line_convert
params.convert_line_all = line_convert_all
params.embed_runtime = embed_runtime
params.frozen_strings = frozen_strings
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
}
}
end
if debug_flags.status_logging
log_useless states.grammar
log_conflict states
else
has_useless = report_useless states.grammar
has_conflicts = report_conflict states
if has_useless || has_conflicts
preamble = make_logfile ? 'C' : 'Turn on logging with "-v" and c'
$stderr.puts %Q{#{preamble}heck ".output" file for details}
end
end
profiler.report
if states.should_error_on_expect_mismatch?
raise Racc::CompileError, "#{states.grammar.n_expected_srconflicts} shift/reduce conflicts are expected but #{states.n_srconflicts} shift/reduce conflicts exist"
end
rescue Racc::Error, Errno::ENOENT, Errno::EPERM => err
raise if $DEBUG or debug_flags.any?
lineno = err.message.slice(/\A\d+:/).to_s
abort "#{File.basename $0}: #{input}:#{lineno} #{err.message.strip}"
end
end
def make_filename(path, suffix)
path.sub(/(?:\..*?)?\z/, suffix)
end
LIST_LIMIT = 10
def report_list(enum, label)
c = enum.count
if c > 0
$stderr.puts "#{c} #{label}:"
enum.first(LIST_LIMIT).each do |item|
$stderr.puts " #{yield item}"
end
$stderr.puts " ..." if c > LIST_LIMIT
end
end
# @return [Boolean] if anything was reported
def report_conflict(states)
if states.should_report_srconflict?
reported = true
$stderr.puts "#{states.n_srconflicts} shift/reduce conflicts"
end
if states.rrconflict_exist?
reported = true
$stderr.puts "#{states.n_rrconflicts} reduce/reduce conflicts"
end
reported
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
}
end
# @return [Boolean] if anything was reported
def report_useless(grammar)
reported = report_list(grammar.each_useless_nonterminal, 'useless nonterminals', &:to_s)
reported ||= report_list(grammar.each_useless_rule, 'useless rules') { |r| "##{r.ident} (#{r.target})" }
if grammar.start.useless?
$stderr.puts 'fatal: start symbol does not derive any sentence'
reported = true
end
reported
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 = []
end
def section(name)
if @really
t1 = ::Process.times.utime
result = yield
t2 = ::Process.times.utime
@log.push [name, t2 - t1]
result
else
yield
end
end
def report
return unless @really
f = $stderr
total = cumulative_time()
f.puts '--task-----------+--sec------+---%-'
@log.each do |name, time|
f.printf "%-19s %s %3d%%\n", name, pjust(time,4,4), (time/total*100).to_i
end
f.puts '-----------------+-----------+-----'
f.printf "%-20s%s\n", 'total', pjust(total,4,4)
end
private
def cumulative_time
t = @log.inject(0) {|sum, (name, time)| sum + time }
t == 0 ? 0.01 : t
end
def pjust(num, i, j)
m = /(\d+)(\.\d+)?/.match(num.to_s)
str = m[1].rjust(i)
str.concat m[2].ljust(j+1)[0,j+1] if m[2]
str
end
end
main
+218
View File
@@ -0,0 +1,218 @@
= Racc Grammar File Reference
== Global Structure
== Class Block and User Code Block
There are two top-level blocks: the 'class' block, and the 'user code'
block. The 'user code' block MUST be after the 'class' block.
== Comment
Comments can be added about everywhere. Two comment styles are
supported: 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 conflict]
[options]
[semantic value conversion]
[start rule]
rule
GRAMMARS
CLASS_NAME is a name of parser class.
This is the name of generating parser class.
If CLASS_NAME includes '::', Racc outputs module clause.
For example, writing "class M::C" causes creating the code below:
module M
class C
:
:
end
end
== Grammar Block
The grammar block describes the grammar
to be understood by 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 left side value ($$).
It is value of result, or returned value by "return" statement.
Here is an example of whole grammar block.
rule
goal: definition rules source { result = val }
definition: /* none */ { result = [] }
| definition startdesig { result[0] = val[1] }
| definition
precrule # this line continue from upper line
{
result[1] = val[1]
}
startdesig: START TOKEN
You can use following special local variables in action.
* result ($$)
The value of left-hand side (lhs). A default value is val[0].
* val ($1,$2,$3...)
An array of value of 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 supports Bison's "expect" directive to declare the expected
number of shift/reduce conflicts.
class MyParser
expect 3
rule
:
:
Then warnings are issued only when the effective number of conflicts differs.
== Declaring Tokens
Declaring tokens avoids many bugs.
Racc outputs warnings for declared tokens that do not exist, or existing tokens not declared.
The 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
omit empty action call or not.
* result_var
use/does not use local variable "result"
You can use 'no_' prefix to invert its meanings.
== Converting Token Symbol
Token symbols are, as default,
* naked token strings in racc file (TOK, XFILE, this_is_token, ...)
--&gt; symbol (:TOK, :XFILE, :this_is_token, ...)
* quoted strings (':', '.', '(', ...)
--&gt; same string (':', '.', '(', ...)
You can change this default using a "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 are causes 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 the start symbol.
start real_target
== User Code Block
A "User Code Block" is a piece of Ruby source code copied in the output.
There are three user code blocks, "header" "inner" and "footer".
User code blocks are introduced by four '-' at the beginning of a line,
followed by a single-word name:
---- header
ruby statement
ruby statement
ruby statement
---- inner
ruby statement
:
:
+219
View File
@@ -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
expect 3
rule
:
:
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.
@@ -0,0 +1,99 @@
<h1>Raccコマンドリファレンス</h1>
<p>
racc [-o<var>filename</var>] [--output-file=<var>filename</var>]
[-e<var>rubypath</var>] [--executable=<var>rubypath</var>]
[-v] [--verbose]
[-O<var>filename</var>] [--log-file=<var>filename</var>]
[-g] [--debug]
[-E] [--embedded]
[-F] [--frozen]
[-l] [--no-line-convert]
[-c] [--line-convert-all]
[-a] [--no-omit-actions]
[-C] [--check-only]
[-S] [--output-status]
[--version] [--copyright] [--help] <var>grammarfile</var>
</p>
<dl>
<dt><var>filename</var>
<dd>
Raccの文法ファイルを指定します。拡張子には特に制限はありません。
</dd>
<dt>-o<var>outfile</var>, --output-file=<var>outfile</var>
<dd>
作成するクラスをかきこむファイル名を指定します。デフォルトは<filename>.tab.rbです。
</dd>
<dt>-O<var>filename</var>, --log-file=<var>filename</var>
<dd>
-v オプションをつけた時に生成するログファイルの名前を
<var>filename</var> に変更します。
デフォルトは <var>filename</var>.output です。
</dd>
<dt>-e<var>rubypath</var>, --executable=<var>rubypath</var>
<dd>
実行可能ファイルを生成します。<var>rubypath</var>は Ruby 本体のパスです。
<var>rubypath</var>を単に 'ruby' にした時には Racc が動作している
Ruby のパスを使用します。
</dd>
<dt>-v, --verbose
<dd>
ファイル "filename".output に詳細な解析情報を出力します。
</dd>
<dt>-g, --debug
<dd>
出力するコードにデバッグ用コードを加えます。-g をつけて生成したパーサで
@yydebug を true にセットすると、デバッグ用のコードが出力されます。<br>
-g をつけるだけでは何もおこりませんので注意してください。
</dd>
<dt>-E, --embedded
<dd>
ランタイムルーチンをすべて含んだコードを生成します。
つまり、このオプションをつけて生成したコードは Ruby さえあれば動きます。
</dd>
<dt>-F, --frozen
<dd>
Add frozen_string_literals: true.
</dd>
<dt>-C, --check-only
<dd>
(文法ファイルの) 文法のチェックだけをして終了します。
</dd>
<dt>-S, --output-status
<dd>
進行状況を逐一報告します。
</dd>
<dt>-l, --no-line-convert
<dd>
<p>
Ruby では例外が発生した時のファイル名や行番号を表示してくれますが、
Racc の生成したパーサは、デフォルトではこの場合のファイル名・行番号を
文法ファイルでのものに置きかえます。このフラグはその機能をオフにします。
</p>
<p>
ruby 1.4.3 以前のバージョンではバグのために定数の参照に失敗する
場合があるので、定数参照に関してなにかおかしいことがおこったらこのフラグを
試してみてください。
</p>
</dd>
<dt>-c, --line-convert-all
<dd>
アクションと inner に加え header footer の行番号も変換します。
header と footer がつながっているような場合には使わないでください。
<dt>-a, --no-omit-actions
<dd>
全てのアクションに対応するメソッド定義と呼び出しを行います。
例えアクションが省略されていても空のメソッドを生成します。
</dd>
<dt>--version
<dd>
Racc のバージョンを出力して終了します。
</dd>
<dt>--copyright
<dd>
著作権表示を出力して終了します。
<dt>--help
<dd>
オプションの簡単な説明を出力して終了します。
</dd>
</dl>
+36
View File
@@ -0,0 +1,36 @@
= パーサのデバッグ
ここでは、Racc を使っていくうえで遭遇しそうな問題について書きます。
== 文法ファイルがパースエラーになる
エラーメッセージに出ている行番号のあたりを見て間違いを
探してください。ブロックを閉じる行でエラーになる場合は、
どこかで開き括弧などを増やしてしまっている可能性が高いです。
== なんたら conflict って言われた
一番ありがちで一番面倒な問題は衝突 (conflict) でしょう。
文法中に衝突があると、racc はコンパイル後に
「5 shift/reduce conflict」のようなメッセージを表示します。
-v をつけると出力される .output ファイルからはさらに詳しい情報が得られます。
それをどう使うか、とかそういうことに関しては、それなりの本を読んでください。
とてもここに書けるような単純な話ではありません。
当然ながら『Ruby を 256 倍使うための本 無道編』(青木峰郎著)がお勧めです。
== パーサは問題なく生成できたけど予想どおりに動かない
racc に -g オプションをつけてパーサを出力すると、デバッグ用のコードが
付加されます。ここで、パーサクラスのインスタンス変数 @yydebug を true に
しておいてから do_parse/yyparse を呼ぶと、デバッグ用メッセージが出力
されます。パーサが動作する様子が直接見えますので、完全に現在の状態を
把握できます。これを見てどこがおかしいのかわかったらあとは直すだけ。
== next_token に関して
いまだ自分でも忘れることが多いのが
「送るトークンが尽きたら [false,なにか] を送る」ということです。
ちなみに Racc 0.10.2 以降では一度 [false,なにか] を受け取ったら
それ以上 next_token は呼ばないことが保証されています。
追記: 最近は [false,なにか] ではなく nil でもよいことになった。
+348
View File
@@ -0,0 +1,348 @@
= 規則ファイル文法リファレンス
== 文法に関する前バージョンとの非互換
* (1.2.5) ユーザーコードを連結する時、外部ファイルよりも
埋めこんであるコードを先に連結します。
* (1.1.6) 新しいディレクティブ options が追加されました。
* (1.1.5) 予約語 token の意味が変更になりました。
* (0.14) ルールの最後のセミコロンが省略可能になりました。
また、token prechigh などが予約語でなくなりました。
* (10.2) prepare が header に driver が footer になりました。
今はそのままでも使えますが、2.0 からは対応しません。
* (0.10) class に対応する end がなくなりました。
* (0.9) ダサダサのピリオド方式をやめて { と } で囲むようにしました。
== 全体の構造
トップレベルは、規則部とユーザーコード部に分けられます。
ユーザーコード部はクラス定義の後に来なければいけません。
=== コメント
文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを
書くことができます。コメントは、Rubyの #.....(行末) スタイルと、
Cの /*......*/ スタイルを使うことができます。
=== 規則部
規則部は以下のような形をしています。
--
class クラス名 [< スーパークラス]
[演算子順位]
[トークン宣言]
[オプション]
[expect]
[トークンシンボル値おきかえ]
[スタート規則]
rule
文法記述
--
"クラス名"はここで定義するパーサクラスの名前です。
これはそのままRubyのクラス名になります。
また M::C のように「::」を使った名前を使うと、クラス定義を
モジュール M の中にネストさせます。つまり class M::C ならば
--
module M
class C < Racc::Parser
いろいろ
end
end
--
のように出力します。
さらに、Ruby と同じ構文でスーパークラスを指定できます。
ただしこの指定をするとパーサの動作に重大な影響を与えるので、
特に必要がない限り指定してはいけません。これは将来の拡張の
ために用意したもので、現在指定する必然性はあまりありません。
=== 文法の記述
racc で生成するパーサが理解できる文法を記述します。
文法は、予約語 rule と end の間に、以下のような書式で書きます。
--
トークン: トークンの並び アクション
トークン: トークンの並び アクション
| トークンの並び アクション
| トークンの並び アクション
(必要なだけ同じようにつづける)
--
アクションは { } で囲みます。アクションでは Ruby の文はほとんど
使えますが、一部だけは非対応です。対応していないものは以下のとおり。
* ヒアドキュメント
* =begin ... =end 型コメント
* スペースで始まる正規表現
* ごくまれに % の演算。普通に演算子のまわりにスペースを入れていれば問題なし
このあたりに関しては完全な対応はまず無理です。あきらめてください。
左辺の値($$)は、オプションによって返し方がかわります。まずデフォルトでは
ローカル変数 result (そのデフォルト値は val[0])が 左辺値を表し、アクション
ブロックを抜けた時の result の値が左辺値になります。または明示的に return
で返した場合もこの値になります。一方、options で no_result_var を指定した
場合、左辺値はアクションブロックの最後の文の値になります (Ruby のメソッドと
同じ)。
どちらの場合でもアクションは省略でき、省略した場合の左辺値は常に val[0] です。
以下に文法記述の全体の例をしめします。
--
rule
goal: def ruls source
{
result = val
}
def : /* none */
{
result = []
}
| def startdesig
{
result[0] = val[1]
}
| def
precrule # これは上の行の続き
{
result[1] = val[1]
}
(略)
--
アクション内では特別な意味をもった変数がいくつか使えます。
そのような変数を以下に示します。括弧の中は yacc での表記です。
* result ($$)
左辺の値。初期値は val[0] です。
* val ($1,$2,$3…)
右辺の記号の値の配列。Ruby の配列なので当然インデックスはゼロから始まります。
この配列は毎回作られるので自由に変更したり捨てたりして構いません。
* _values (...,$-2,$-1,$0)
値スタック。Racc コアが使っているオブジェクトがそのまま渡されます。
この変数の意味がわかる人以外は<em>絶対に</em>変更してはいけません。
またアクションの特別な形式に、埋めこみアクションというものがあります。
これはトークン列の途中の好きなところに記述することができます。
以下に埋めこみアクションの例を示します。
--
target: A B { puts 'test test' } C D { normal action }
--
このように記述すると A B を検出した時点で puts が実行されます。
また、埋めこみアクションはそれ自体が値を持ちます。つまり、以下の例において
--
target: A { result = 1 } B { p val[1] }
--
最後にある p val[1] は埋めこみアクションの値 1 を表示します。
B の値ではありません。
意味的には、埋めこみアクションは空の規則を持つ非終端記号を追加することと
全く同じ働きをします。つまり、上の例は次のコードと完全に同じ意味です。
--
target : A nonterm B { p val[1] }
nonterm : /* 空の規則 */ { result = 1 }
--
=== 演算子優先順位
あるトークン上でシフト・還元衝突がおこったとき、そのトークンに
演算子優先順位が設定してあると衝突を解消できる場合があります。
そのようなものとして特に有名なのは数式の演算子と if...else 構文です。
優先順位で解決できる文法は、うまく文法をくみかえてやれば
優先順位なしでも同じ効果を得ることができます。しかしたいていの
場合は優先順位を設定して解決するほうが文法を簡単にできます。
シフト・還元衝突がおこったとき、Racc はまずその規則に順位が設定
されているか調べます。規則の順位は、その規則で一番うしろにある
終端トークンの優先順位です。たとえば
--
target: TERM_A nonterm_a TERM_B nonterm_b
--
のような規則の順位はTERM_Bの優先順位になります。もしTERM_Bに
優先順位が設定されていなかったら、優先順位で衝突を解決することは
できないと判断し、「Shift/Reduce conflict」を報告します。
演算子の優先順位はつぎのように書いて定義します。
--
prechigh
nonassoc PLUSPLUS
left MULTI DIVIDE
left PLUS MINUS
right '='
preclow
--
prechigh に近い行にあるほど優先順位の高いトークンです。上下をまるごと
さかさまにして preclow...prechigh の順番に書くこともできます。left
などは必ず行の最初になければいけません。
left right nonassoc はそれぞれ「結合性」を表します。結合性によって、
同じ順位の演算子の規則が衝突した場合にシフト還元のどちらをとるかが
決まります。たとえば
--
a - b - c
--
が
--
(a - b) - c
--
になるのが左結合 (left) です。四則演算は普通これです。
一方
--
a - (b - c)
--
になるのが右結合 (right) です。代入のクオートは普通 right です。
またこのように演算子が重なるのはエラーである場合、非結合 (nonassoc) です。
C 言語の ++ や単項のマイナスなどがこれにあたります。
ところで、説明したとおり通常は還元する規則の最後のトークンが順位を
決めるのですが、ある規則に限ってそのトークンとは違う順位にしたいことも
あります。例えば符号反転のマイナスは引き算のマイナスより順位を高く
しないといけません。このような場合 yacc では %prec を使います。
racc ではイコール記号を使って同じことをできます。
--
prechigh
nonassoc UMINUS
left '*' '/'
left '+' '-'
preclow
(略)
exp: exp '*' exp
| exp '-' exp
| '-' exp = UMINUS # ここだけ順位を上げる
--
このように記述すると、'-' exp の規則の順位が UMINUS の順位になります。
こうすることで符号反転の '-' は '*' よりも順位が高くなるので、
意図どおりになります。
=== トークン宣言
トークン(終端記号)のつづりを間違えるというのはよくあることですが、
発見するのはなかなか難しいものです。1.1.5 からはトークンを明示的に
宣言することで、宣言にないトークン / 宣言にだけあるトークンに対して
警告が出るようになりました。yacc の %token と似ていますが最大の違いは
racc では必須ではなく、しかもエラーにならず警告だけ、という点です。
トークン宣言は以下のように書きます。
--
token A B C D
E F G H
--
トークンのリストを複数行にわたって書けることに注目してください。
racc では一般に「予約語」は行の先頭に来た時だけ予約語とみなされるので
prechigh などもシンボルとして使えます。ただし深淵な理由から end だけは
どうやっても予約語になってしまいます。
=== オプション
racc のコマンドラインオプションの一部をファイル中にデフォルト値
として記述することができます。
--
options オプション オプション …
--
現在ここで使えるのは
* omit_action_call
空のアクション呼び出しを省略する
* result_var
変数 result を使う
です。
それぞれ no_ を頭につけることで意味を反転できます。
=== expect
実用になるパーサはたいてい無害な shift/reduce conflict を含みます。
しかし文法ファイルを書いた本人はそれを知っているからいいですが、
ユーザが文法ファイルを処理した時に「conflict」と表示されたら
不安に思うでしょう。そのような場合、以下のように書いておくと
shift/reduce conflict のメッセージを抑制できます。
--
expect 3
--
この場合 shift/reduce conflict はぴったり三つでなければいけません。
三つでない場合はやはり表示が出ます (ゼロでも出ます)。
また reduce/reduce conflict の表示は抑制できません。
=== トークンシンボル値の変更
トークンシンボルを表す値は、デフォルトでは
* 文法中、引用符でかこまれていないもの (RULEとかXENDとか)
→その名前の文字列を intern して得られるシンボル (1.4 では Fixnum)
* 引用符でかこまれているもの(':'とか'.'とか)
→その文字列そのまま
となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、
これにあわせなければならず、このままでは不便です。このような場合には、
convert 節を加えることで、トークンシンボルを表す値を変えることができます。
以下がその例です。
--
convert
PLUS 'PlusClass' #→ PlusClass
MIN 'MinusClass' #→ MinusClass
end
--
デフォルトではトークンシンボル PLUS に対してはトークンシンボル値は
:PLUS ですが、上のような記述がある場合は PlusClass になります。
変換後の値は false・nil 以外ならなんでも使えます。
変換後の値として文字列を使うときは、次のように引用符を重ねる必要があります。
--
convert
PLUS '"plus"' #→ "plus"
end
--
また、「'」を使っても生成された Ruby のコード上では「"」になるので
注意してください。バックスラッシュによるクオートは有効ですが、バック
スラッシュは消えずにそのまま残ります。
--
PLUS '"plus\n"' #→ "plus\n"
MIN "\"minus#{val}\"" #→ \"minus#{val}\"
--
=== スタート規則
パーサをつくるためには、どの規則が「最初の」規則か、ということを Racc におしえて
やらなければいけません。それを明示的に書くのがスタート規則です。スタート規則は
次のように書きます。
--
start real_target
--
start は行の最初にこなければいけません。このように書くと、ファイルで
一番最初に出てくる real_target の規則をスタート規則として使います。
省略した場合は、ファイルの最初の規則がスタート規則になります。普通は
最初の規則を一番上にかくほうが書きやすく、わかりやすくなりますから、
この記法はあまりつかう必要はないでしょう。
=== ユーザーコード部
ユーザーコードは、パーサクラスが書きこまれるファイルに、
アクションの他にもコードを含めたい時に使います。このようなものは
書きこまれる場所に応じて三つ存在し、パーサクラスの定義の前が
header、クラスの定義中(の冒頭)が inner、定義の後が footer です。
ユーザコードとして書いたものは全く手を加えずにそのまま連結されます。
ユーザーコード部の書式は以下の通りです。
--
---- 識別子
ruby の文
ruby の文
ruby の文
---- 識別子
ruby の文
:
--
行の先頭から四つ以上連続した「-」(マイナス)があるとユーザーコードと
みなされます。識別子は一つの単語で、そのあとには「=」以外なら何を
書いてもかまいません。
+10
View File
@@ -0,0 +1,10 @@
<h1>Racc ユーザマニュアル</h1>
<p>バージョン 1.4 対応</p>
<ul>
<li><a href="usage.html">Racc の使い方</a>
<li><a href="command.html">racc コマンドリファレンス</a>
<li><a href="grammar.html">規則ファイル文法リファレンス</a>
<li><a href="parser.html">Parser クラスリファレンス</a>
<li><a href="debug.html">パーサのデバッグ</a>
<li><a href="NEWS.html">リリースノート</a>
</ul>
+125
View File
@@ -0,0 +1,125 @@
= class Racc::Parser
Racc の生成するパーサはすべて Racc::Parser クラスを継承します。
Racc::Parser クラスにはパース中に使用するメソッドがいくつかあり、
そのようなメソッドをオーバーロードすると、パーサを初期化したり
することができます。
== Super Class
Object
== Constants
プリフィクス "Racc_" がついた定数はパーサの予約定数です。
そのような定数は使わないでください。動作不可能になります。
== Instance Methods
ここに載っているもののほか、プリフィクス "racc_" および "_racc_" が
ついたメソッドはパーサの予約名です。そのようなメソッドは使わないで
ください。
: do_parse -> Object
パースを開始します。
また、トークンが必要になった時は #next_token を呼び出します。
--
# Example
---- inner
def parse
@q = [[1,1],
[2,2],
[3,3],
[false, '$']]
do_parse
end
def next_token
@q.shift
end
--
: next_token -> [Symbol, Object]
[abstract method]
パーサが次のトークンを読みこむ時に使います。
[記号, その値] の形式の配列を返してください。
記号はデフォルトでは
* 文法中、引用符でかこまれていないもの
→ その名前の文字列のシンボル (例えば :ATOM )
* 引用符でかこまれているもの<br>
→ その文字列そのまま (例えば '=' )
で表します。これを変更する方法については、
文法リファレンスを参照してください。
また、もう送るシンボルがなくなったときには
[false, なにか] または nil を返してください。
このメソッドは抽象メソッドなので、#do_parse を使う場合は
必ずパーサクラス中で再定義する必要があります。
定義しないままパースを始めると例外 NotImplementedError が
発生します。
: yyparse( receiver, method_id )
パースを開始します。このメソッドでは始めてトークンが
必要になった時点で receiver に対して method_id メソッドを
呼び出してトークンを得ます。
receiver の method_id メソッドはトークンを yield しなければ
なりません。形式は #next_token と同じで [記号, 値] です。
つまり、receiver の method_id メソッドの概形は以下のように
なるはずです。
--
def method_id
until end_of_file
:
yield 記号, 値
:
end
end
--
少し注意が必要なのは、method_id が呼び出されるのは始めて
トークンが必要になった時点であるということです。method_id
メソッドが呼び出されたときは既にパースが進行中なので、
アクション中で使う変数を method_id の冒頭で初期化すると
まず失敗します。
トークンの終端を示す [false, なにか] を渡したらそれ以上は
yield しないでください。その場合には例外が発生します。
最後に、method_id メソッドからは必ず yield してください。
しない場合は何が起きるかわかりません。
: on_error( error_token_id, error_value, value_stack )
パーサコアが文法エラーを検出すると呼び出します (yacc の yyerror)。
エラーメッセージを出すなり、例外を発生するなりしてください。
このメソッドから正常に戻った場合、パーサはエラー回復モード
に移行します。
error_token_id はパースエラーを起こした記号の内部表現 (整数) です。
#token_to_str で文法ファイル上の文字列表現に直せます。
error_value はその値です。
value_stack はエラーの時点での値スタックです。
value_stack を変更してはいけません。
on_error のデフォルトの実装は例外 ParseError を発生します。
: token_to_str( t ) -> String
Racc トークンの内部表現 (整数)
を文法ファイル上の記号表現の文字列に変換します。
t が整数でない場合は TypeError を発生します。
t が範囲外の整数だった場合は nil を返します。
: yyerror
エラー回復モードに入ります。このとき #on_error は呼ばれません。
アクション以外からは呼び出さないでください。
: yyerrok
エラー回復モードから復帰します。
アクション以外からは呼び出さないでください。
: yyaccept
すぐに値スタックの先頭の値を返して #do_parse、#yyparse を抜けます。
+414
View File
@@ -0,0 +1,414 @@
<h1>Racc の使い方</h1>
<p>
Racc は文法規則から Ruby で書かれたパーサを生成するパーサジェネレータです。
パーサ生成アルゴリズムには yacc などと同じ LALR(1) を使用しています。
</p>
<p>
yacc を知っている人は記述法の違いだけわかれば使えると思います。
yacc を知らない人は
拙著『Ruby を 256 倍使うための本 無道編』(青木峰郎著、ASCII)
などを一読していただくのがよいかと思います。
他の UNIX コマンドなどとは異なり、
いきなり使うだけで Racc を理解するのはかなり困難です。
</p>
<h2>Racc とはなにか</h2>
<p>
Racc は文法を処理するツールです。
文字列はただの文字の列で、コンピュータにとっては意味を持ちません。
しかし人間はその文字の列の中になにか意味を見出すことができます。
コンピュータにもそのようなことを、部分的にでも、させられたら便利でしょう。
Racc はその手伝いをしてくれます。完全な自動化ではありませんが、
人間が全部やるよりも遥かに簡単になります。
</p>
<p>
Racc が自動化してくれる部分とは、文字列の含む「構造」の処理です。
たとえば Ruby の if 文を考えてみると、次のように定式化できます。
</p>
<pre>
if 条件式 [then]
文
:
[elsif 条件式 [then]
文
:]
[else
文
:]
end
</pre>
<p>
if 文では if という単語が最初になくてはならず、
elsif 節は else 節より前になくてはいけません。
このような配置の関係 (構造) が、Racc が処理する対象です。
</p>
<p>
一方、Racc で処理できないのはどういうことでしょうか。それは、たとえば
if の条件式にあたる部分が「なんであるか」ということです。つまり、条件
式が if の条件だということです。これは、こっちで条件として扱うコードを
書いてやらないといけません。
</p>
<p>
と言っても、わかりにくいでしょう。こういう抽象的なものは実際にいじって
みるのが一番です。
</p>
<h2>実際の話</h2>
<p>
実際に Racc をどのように使うかという話をします。Racc には独自のソース
コードみたいなものがあって、この中に処理したい「構造」を記述しておきま
す。このソースファイルを「文法ファイル」と呼ぶことにしましょう。この文
法ファイルの名前が parse.y と仮定すると、コマンドラインから以下のよう
に打ちこめば、その構造を処理するためのクラスを含んだファイルが得られま
す。
</p>
<pre>
$ racc parse.y
</pre>
<p>
生成されるファイルはデフォルトでは "ファイル名.tab.rb" です。他の名前
にしたいなら、-o オプションで変更できます。
</p>
<pre>
$ racc parse.y -o myparser.rb
</pre>
<p>
このようにして作ったクラス、またはそのような処理を担当するパート、
のことはパーサ (parser) と呼ぶことになっています。解析するヤツ、
というくらいに適当にとらえてください。
</p>
<h2>文法ファイルを書く</h2>
<p>
Racc は文法ファイルから Ruby のクラスを生成するツールだと言いました。
そのクラスは全て Racc::Parser の下位クラスで、名前は文法ファイル中で
指定します。以下、ここに書くべきことが「なんなのか」を説明します。
ここでは内容に重点を置くので、文法ファイル自体の文法の詳細は
<a href="grammar.html">文法リファレンス</a>を見てください。
</p>
<h3>文法</h3>
<p>
まずは、全体の概形です。
</p>
<pre>
class MyParser
rule
if_stmt: IF expr then stmt_list elsif else END
then : THEN
|
elsif :
| ELSIF stmt_list
else :
| ELSE stmt_list
expr : NUMBER
| IDENT
| STRING
stmt_list : ふにゃふにゃ
end
</pre>
<p>
Ruby スクリプトのように class でパーサクラス名を指定し、rule ... end
の間にパーサに解析させたい文法を記述します。
</p>
<p>
文法は、記号の並びでもって表します。rule ... end の間にあるコロンとバー
以外のもの、if_stmt IF expr then などが全て「記号」です。そしてコロン
が日本語で言う「〜は××だ」の「は」みたいなもんで、その左の記号が右の
記号の列と同じものを指す、というふうに定義します。また、バーは「または」
を意味します。それと、単純にコロンの左の記号のことを左辺、右を右辺とも
言います。以下はこちらのほうを使って説明しましょう。
</p>
<p>
少し注意が必要な点を述べます。まず、then の、バーのあとの定義 (規則) を
見てください。ここには何も書いていないので、これはその通り「無」であっ
てもいい、ということを表しています。つまり、then は記号 THEN 一個か、
またはなにもなし(省略する)でよい、ということです。記号 then は実際の
Ruby のソースコードにある then とは切り離して考えましょう
(それは実は大文字の記号 THEN が表しています)。
</p>
<p>
さて、そろそろ「記号」というものがなんなのか書きましょう。
ただし順番に話をしないといけないので、まずは聞いていてください。
この文章の最初に、パーサとは文字の列から構造を見出す部分だと言いました。
しかし文字の列からいきなり構造を探すのは面倒なので、実際にはまず
文字の列を単語の列に分割します。その時点でスペースやコメントは捨てて
しまい、以降は純粋にプログラムの一部をなす部分だけを相手にします。
たとえば文字列の入力が次のようだったとすると、
</p>
<pre>
if flag then # item found.
puts 'ok'
end
</pre>
<p>
単語の列は次のようになります。
</p>
<pre>
if flag then puts 'ok' end
</pre>
<p>
ここで、工夫が必要です。どうやら flag はローカル変数名だと思われますが、
変数名というのは他にもいろいろあります。しかし名前が i だろうが a だろ
うが vvvvvvvvvvvv だろうが、「構造」は同じです。つまり同じ扱いをされる
べきです。変数 a を書ける場所なら b も書けなくてはいけません。だったら
一時的に同じ名前で読んでもいいじゃん。ということで、この単語の列を以下
のように読みかえましょう。
</p>
<pre>
IF IDENT THEN IDENT STRING END
</pre>
<p>
これが「記号」の列です。パーサではこの記号列のほうを扱い、構造を見付け
ていきます。
</p>
<p>
さらに記号について見ていきましょう。
記号は二種類に分けられます。「左辺にある記号」と「ない記号」です。
左辺にある記号は「非終端」記号と言います。ないほうは「終端」記号と
言います。最初の例では終端記号はすべて大文字、非終端記号は小文字で
書いてあるので、もう一度戻って例の文法を見てください。
</p>
<p>
なぜこの区分が重要かと言うと、入力の記号列はすべて終端記号だからです。
一方、非終端記号はパーサの中でだけ、終端記号の列から「作りだす」ことに
よって始めて存在します。例えば次の規則をもう一度見てください。
</p>
<pre>
expr : NUMBER
| IDENT
| STRING
</pre>
<p>
expr は NUMBER か IDENT か STRING だと言っています。逆に言うと、
IDENT は expr に「なることができます」。文法上 expr が存在できる
場所に IDENT が来ると、それは expr になります。例えば if の条件式の
部分は expr ですから、ここに IDENT があると expr になります。その
ように文法的に「大きい」記号を作っていって、最終的に一個になると、
その入力は文法を満たしていることになります。実際にさっきの入力で
試してみましょう。入力はこうでした。
</p>
<pre>
IF IDENT THEN IDENT STRING END
</pre>
<p>
まず、IDENT が expr になります。
</p>
<pre>
IF expr THEN IDENT STRING END
</pre>
<p>
次に THEN が then になります。
</p>
<pre>
IF expr then IDENT STRING END
</pre>
<p>
IDENT STRING がメソッドコールになります。この定義はさきほどの例には
ないですが、実は省略されているんだと考えてください。そしていろいろな
過程を経て、最終的には stmt_list (文のリスト)になります。
</p>
<pre>
IF expr then stmt_list END
</pre>
<p>
elsif と else は省略できる、つまり無から生成できます。
</p>
<pre>
IF expr then stmt_list elsif else END
</pre>
<p>
最後に if_stmt を作ります。
</p>
<pre>
if_stmt
</pre>
<p>
ということでひとつになりました。
つまりこの入力は文法的に正しいということがわかりました。
</p>
<h3>アクション</h3>
<p>
ここまでで入力の文法が正しいかどうかを確認する方法はわかりましたが、
これだけではなんにもなりません。最初に説明したように、ここまででは
構造が見えただけで、プログラムは「意味」を理解できません。そしてその
部分は Racc では自動処理できないので、人間が書く、とも言いました。
それを書くのが以下に説明する「アクション」という部分です。
</p>
<p>
前項で、記号の列がだんだんと大きな単位にまとめられていく過程を見ました。
そのまとめる時に、同時になにかをやらせることができます。それが
アクションです。アクションは、文法ファイルで以下のように書きます。
</p>
<pre>
class MyParser
rule
if_stmt: IF expr then stmt_list elsif else END
{ puts 'if_stmt found' }
then : THEN
{ puts 'then found' }
|
{ puts 'then is omitted' }
elsif :
{ puts 'elsif is omitted' }
| ELSIF stmt_list
{ puts 'elsif found' }
else :
{ puts 'else omitted' }
| ELSE stmt_list
{ puts 'else found' }
expr : NUMBER
{ puts 'expr found (NUMBER)' }
| IDENT
{ puts 'expr found (IDENT)' }
| STRING
{ puts 'expr found (STRING)' }
stmt_list : ふにゃふにゃ
end
</pre>
<p>
見てのとおり、規則のあとに { と } で囲んで書きます。
アクションにはだいたい好きなように Ruby スクリプトが書けます。
</p>
<p>
(この節、未完)
</p>
<hr>
<p>
yacc での <code>$$</code> は Racc ではローカル変数 <code>result</code>
で、<code>$1,$2...</code> は配列 <var>val</var>です。
<code>result</code> は <code>val[0]</code> ($1) の値に初期化され、
アクションを抜けたときの <code>result</code> の値が左辺値になります。
Racc ではアクション中の <code>return</code> はアクションから抜けるだけで、
パース自体は終わりません。アクション中からパースを終了するには、
メソッド <code>yyaccept</code> を使ってください。
</p>
<p>
演算子の優先順位、スタートルールなどの yacc の一般的な機能も用意されて
います。ただしこちらも少し文法が違います。
</p>
<p>
yacc では生成されたコードに直接転写されるコードがありました。
Racc でも同じように、ユーザ指定のコードが書けます。
Racc ではクラスを生成するので、クラス定義の前/中/後の三個所があります。
Racc ではそれを上から順番に header inner footer と呼んでいます。
</p>
<h3>ユーザが用意すべきコード</h3>
<p>
パースのエントリポイントとなるメソッドは二つあります。ひとつは
<code>do_parse</code>で、こちらはトークンを
<code>Parser#next_token</code> から得ます。もうひとつは
<code>yyparse</code> で、こちらはスキャナから <code>yield</code> され
ることによってトークンを得ます。ユーザ側ではこのどちらか(両方でもいい
けど)を起動する簡単なメソッドを inner に書いてください。これらメソッド
の引数など、詳しいことはリファレンスを見てください。
</p>
<ul>
<li><a href="parser.html#Racc%3a%3aParser-do_parse">do_parse</a>
<li><a href="parser.html#Racc%3a%3aParser-yyparse">yyparse</a>
</ul>
<p>
どちらのメソッドにも共通なのはトークンの形式です。必ずトークンシンボル
とその値の二要素を持つ配列を返すようにします。またスキャンが終了して、
もう送るものがない場合は <code>[false,<var>なにか</var>]</code> を返し
てください。これは一回返せば十分です (逆に、<code>yyparse</code> を使
う場合は二回以上 <code>yield</code> してはいけない)。
</p>
<p>
パーサは別に文字列処理にだけ使われるものではありませんが、実際問題とし
て、パーサを作る場面ではたいてい文字列のスキャナとセットで使うことが多
いでしょう。Ruby ならスキャナくらい楽勝で作れますが、高速なスキャナと
なると実は難しかったりします。そこで高速なスキャナを作成するためのライ
ブラリも作っています。詳しくは
<a href="#WritingScanner">「スキャナを作る」の項</a>を見てください。
</p>
<p>
Racc には error トークンを使ったエラー回復機能もあります。yacc の
<code>yyerror()</code> は Racc では
<a href="parser.html#Racc%3a%3aParser-on_error"><code>Racc::Parser#on_error</code></a>
で、エラーが起きたトークンとその値、値スタック、の三つの引数をとります。
<code>on_error</code> のデフォルトの実装は例外
<code>Racc::ParseError</code> を発生します。
</p>
<p>
ユーザがアクション中でパースエラーを発見した場合は、メソッド
<a href="parser.html#Racc%3a%3aParser-yyerror"><code>yyerror</code></a>
を呼べばパーサがエラー回復モードに入ります。
ただしこのとき <code>on_error</code>は呼ばれません。
</p>
<h3>パーサを生成する</h3>
<p>
これだけあればだいたい書けると思います。あとは、最初に示した方法で文法
ファイルを処理し、Ruby スクリプトを得ます。
</p>
<p>
うまくいけばいいのですが、大きいものだと最初からはうまくいかないでしょ
う。racc に -g オプションをつけてコンパイルし、@yydebug を true にする
とデバッグ用の出力が得られます。デバッグ出力はパーサの @racc_debug_out
に出力されます(デフォルトは stderr)。また、racc に -v オプションをつけ
ると、状態遷移表を読みやすい形で出力したファイル(*.output)が得られます。
どちらもデバッグの参考になるでしょう。
</p>
<h2>作ったパーサを配布する</h2>
<p>
Racc の生成したパーサは動作時にランタイムルーチンが必要です。
具体的には parser.rb と cparse.so です。
ただし cparse.so は単にパースを高速化するためのライブラリなので
必須ではありません。なくても動きます。
</p>
<p>
まず Ruby 1.8.0 以降にはこのランタイムが標準添付されているので、
Ruby 1.8 がある環境ならばランタイムについて考慮する必要はありません。
Racc 1.4.x のランタイムと Ruby 1.8 に添付されているランタイムは
完全互換です。
</p>
<p>
問題は Ruby 1.8 を仮定できない場合です。
Racc をユーザみんなにインストールしてもらうのも一つの手ですが、
これでは不親切です。そこでRacc では回避策を用意しました。
</p>
<p>
racc に -E オプションをつけてコンパイルすると、
パーサと racc/parser.rb を合体したファイルを出力できます。
これならばファイルは一つだけなので簡単に扱えます。
racc/parser.rb は擬似的に require したような扱いになるので、
この形式のパーサが複数あったとしてもクラスやメソッドが衝突することもありません。
ただし -E を使った場合は cparse.so が使えませんので、
必然的にパーサの速度は落ちます。
</p>
<h2><a name="WritingScanner">おまけ: スキャナを書く</a></h2>
<p>
パーサを使うときは、たいてい文字列をトークンに切りわけてくれるスキャナ
が必要になります。しかし実は Ruby は文字列の最初からトークンに切りわけ
ていくという作業があまり得意ではありません。
正確に言うと、簡単にできるのですが、それなりのオーバーヘッドがかかります。
</p>
<p>
そのオーバーヘッドを回避しつつ、
手軽にスキャナを作れるように strscan というパッケージを作りました。
Ruby 1.8 以降には標準添付されていますし、
<a href="http://i.loveruby.net/ja/">筆者のホームページ</a>には
単体パッケージがあります。
</p>
@@ -0,0 +1,269 @@
SHELL = /bin/sh
# V=0 quiet, V=1 verbose. other values don't work.
V = 0
V0 = $(V:0=)
Q1 = $(V:1=)
Q = $(Q1:0=@)
ECHO1 = $(V:1=@ :)
ECHO = $(ECHO1:0=@ echo)
NULLCMD = :
#### Start of system configuration section. ####
srcdir = .
topdir = /usr/include/ruby-3.2.0
hdrdir = $(topdir)
arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0
PATH_SEPARATOR = :
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
prefix = $(DESTDIR)/usr
rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME)
rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME)
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
exec_prefix = $(prefix)
vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby
sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby
rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME)
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
sitehdrdir = $(rubyhdrdir)/site_ruby
rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version)
vendorlibdir = $(vendordir)/$(ruby_version)
vendordir = $(rubylibprefix)/vendor_ruby
sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby
sitelibdir = $(sitedir)/$(ruby_version)
sitedir = $(DESTDIR)/usr/local/lib/site_ruby
rubyarchdir = $(rubyarchprefix)/$(ruby_version)
rubylibdir = $(rubylibprefix)/$(ruby_version)
sitearchincludedir = $(includedir)/$(sitearch)
archincludedir = $(includedir)/$(arch)
sitearchlibdir = $(libdir)/$(sitearch)
archlibdir = $(libdir)/$(arch)
ridir = $(datarootdir)/$(RI_BASE_NAME)
mandir = $(datarootdir)/man
localedir = $(datarootdir)/locale
libdir = $(exec_prefix)/lib
psdir = $(docdir)
pdfdir = $(docdir)
dvidir = $(docdir)
htmldir = $(docdir)
infodir = $(datarootdir)/info
docdir = $(datarootdir)/doc/$(PACKAGE)
oldincludedir = $(DESTDIR)/usr/include
includedir = $(prefix)/include
runstatedir = $(DESTDIR)/var/run
localstatedir = $(DESTDIR)/var
sharedstatedir = $(prefix)/com
sysconfdir = $(DESTDIR)/etc
datadir = $(datarootdir)
datarootdir = $(prefix)/share
libexecdir = $(exec_prefix)/libexec
sbindir = $(exec_prefix)/sbin
bindir = $(exec_prefix)/bin
archdir = $(rubyarchdir)
CC_WRAPPER =
CC = x86_64-linux-gnu-gcc
CXX = x86_64-linux-gnu-g++
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS)
empty =
OUTFLAG = -o $(empty)
COUTFLAG = -o $(empty)
CSRCFLAG = $(empty)
RUBY_EXTCONF_H =
cflags = $(optflags) $(debugflags) $(warnflags)
cxxflags =
optflags = -O3 -fno-fast-math
debugflags = -ggdb3
warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef
cppflags =
CCDLFLAGS = -fPIC
CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 -fPIC $(ARCH_FLAG)
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
DEFS =
CPPFLAGS = -DRACC_INFO_VERSION=1.8.1 -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags)
CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.8 $(ARCH_FLAG)
ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed
dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now
ARCH_FLAG =
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
LDSHARED = $(CC) -shared
LDSHAREDXX = $(CXX) -shared
AR = x86_64-linux-gnu-gcc-ar
EXEEXT =
RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2
RUBY_SO_NAME = ruby-3.2
RUBYW_INSTALL_NAME =
RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
RUBYW_BASE_NAME = rubyw
RUBY_BASE_NAME = ruby
arch = x86_64-linux-gnu
sitearch = $(arch)
ruby_version = 3.2.0
ruby = $(bindir)/$(RUBY_BASE_NAME)3.2
RUBY = $(ruby)
BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2
ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
RM = rm -f
RM_RF = rm -fr
RMDIRS = rmdir --ignore-fail-on-non-empty -p
MAKEDIRS = /bin/mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
COPY = cp
TOUCH = exit >
#### End of system configuration section. ####
preload =
libpath = . $(archlibdir)
LIBPATH = -L. -L$(archlibdir)
DEFFILE =
CLEANFILES = mkmf.log
DISTCLEANFILES =
DISTCLEANDIRS =
extout =
extout_prefix =
target_prefix = /racc
LOCAL_LIBS =
LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc
ORIG_SRCS = cparse.c
SRCS = $(ORIG_SRCS)
OBJS = cparse.o
HDRS =
LOCAL_HDRS =
TARGET = cparse
TARGET_NAME = cparse
TARGET_ENTRY = Init_$(TARGET_NAME)
DLLIB = $(TARGET).so
EXTSTATIC =
STATIC_LIB =
TIMESTAMP_DIR = .
BINDIR = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
HDRDIR = $(sitehdrdir)$(target_prefix)
ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
TARGET_SO_DIR =
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
CLEANLIBS = $(TARGET_SO) false
CLEANOBJS = $(OBJS) *.bak
TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.racc.time
all: $(DLLIB)
static: $(STATIC_LIB)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
clean-static::
clean-rb-default::
clean-rb::
clean-so::
clean: clean-so clean-static clean-rb-default clean-rb
-$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
distclean-rb-default::
distclean-rb::
distclean-so::
distclean-static::
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
-$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
realclean: distclean
install: install-so install-rb
install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
clean-static::
-$(Q)$(RM) $(STATIC_LIB)
install-rb: pre-install-rb do-install-rb install-rb-default
install-rb-default: pre-install-rb-default do-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
do-install-rb:
do-install-rb-default:
pre-install-rb-default:
@$(NULLCMD)
$(TARGET_SO_DIR_TIMESTAMP):
$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
$(Q) $(TOUCH) $@
site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb
.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
.cc.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cc.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.mm.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.mm.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cxx.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cxx.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.cpp.o:
$(ECHO) compiling $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.cpp.S:
$(ECHO) translating $(<)
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.c.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.c.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
.m.o:
$(ECHO) compiling $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
.m.S:
$(ECHO) translating $(<)
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
$(TARGET_SO): $(OBJS) Makefile
$(ECHO) linking shared-object racc/$(DLLIB)
-$(Q)$(RM) $(@)
$(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
$(OBJS): $(HDRS) $(ruby_headers)
@@ -0,0 +1,840 @@
/*
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.
*/
#include <ruby.h>
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/* -----------------------------------------------------------------------
Important Constants
----------------------------------------------------------------------- */
#define RACC_VERSION STRINGIZE(RACC_INFO_VERSION)
#define DEFAULT_TOKEN -1
#define ERROR_TOKEN 1
#define FINAL_TOKEN 0
#define vDEFAULT_TOKEN INT2FIX(DEFAULT_TOKEN)
#define vERROR_TOKEN INT2FIX(ERROR_TOKEN)
#define vFINAL_TOKEN INT2FIX(FINAL_TOKEN)
/* -----------------------------------------------------------------------
File Local Variables
----------------------------------------------------------------------- */
static VALUE RaccBug;
static VALUE CparseParams;
static ID id_yydebug;
static ID id_nexttoken;
static ID id_onerror;
static ID id_noreduce;
static ID id_errstatus;
static ID id_d_shift;
static ID id_d_reduce;
static ID id_d_accept;
static ID id_d_read_token;
static ID id_d_next_state;
static ID id_d_e_pop;
/* -----------------------------------------------------------------------
Utils
----------------------------------------------------------------------- */
/* For backward compatibility */
#ifndef ID2SYM
# define ID2SYM(i) ULONG2NUM(i)
#endif
#ifndef SYM2ID
# define SYM2ID(v) ((ID)NUM2ULONG(v))
#endif
#ifndef SYMBOL_P
# define SYMBOL_P(v) FIXNUM_P(v)
#endif
#ifndef LONG2NUM
# define LONG2NUM(i) INT2NUM(i)
#endif
static ID value_to_id(VALUE v);
static inline long num_to_long(VALUE n);
static ID
value_to_id(VALUE v)
{
if (! SYMBOL_P(v)) {
rb_raise(rb_eTypeError, "not symbol");
}
return SYM2ID(v);
}
static inline long
num_to_long(VALUE n)
{
return NUM2LONG(n);
}
#define AREF(s, idx) \
((0 <= idx && idx < RARRAY_LEN(s)) ? rb_ary_entry(s, idx) : Qnil)
/* -----------------------------------------------------------------------
Parser Stack Interfaces
----------------------------------------------------------------------- */
static VALUE get_stack_tail(VALUE stack, long len);
static void cut_stack_tail(VALUE stack, long len);
static VALUE
get_stack_tail(VALUE stack, long len)
{
if (len < 0) return Qnil; /* system error */
if (len > RARRAY_LEN(stack)) len = RARRAY_LEN(stack);
return rb_ary_subseq(stack, RARRAY_LEN(stack) - len, len);
}
static void
cut_stack_tail(VALUE stack, long len)
{
while (len > 0) {
rb_ary_pop(stack);
len--;
}
}
#define STACK_INIT_LEN 64
#define NEW_STACK() rb_ary_new2(STACK_INIT_LEN)
#define PUSH(s, i) rb_ary_store(s, RARRAY_LEN(s), i)
#define POP(s) rb_ary_pop(s)
#define LAST_I(s) \
((RARRAY_LEN(s) > 0) ? rb_ary_entry(s, RARRAY_LEN(s) - 1) : Qnil)
#define GET_TAIL(s, len) get_stack_tail(s, len)
#define CUT_TAIL(s, len) cut_stack_tail(s, len)
/* -----------------------------------------------------------------------
struct cparse_params
----------------------------------------------------------------------- */
struct cparse_params {
VALUE value_v; /* VALUE version of this struct */
VALUE parser; /* parser object */
int lex_is_iterator;
VALUE lexer; /* scanner object */
ID lexmid; /* name of scanner method (must be an iterator) */
/* State transition tables (immutable)
Data structure is from Dragon Book 4.9 */
/* action table */
VALUE action_table;
VALUE action_check;
VALUE action_default;
VALUE action_pointer;
/* goto table */
VALUE goto_table;
VALUE goto_check;
VALUE goto_default;
VALUE goto_pointer;
long nt_base; /* NonTerminal BASE index */
VALUE reduce_table; /* reduce data table */
VALUE token_table; /* token conversion table */
/* parser stacks and parameters */
VALUE state;
long curstate;
VALUE vstack;
VALUE tstack;
VALUE t;
long shift_n;
long reduce_n;
long ruleno;
long errstatus; /* nonzero in error recovering mode */
long nerr; /* number of error */
int use_result_var;
VALUE retval; /* return value of parser routine */
long fin; /* parse result status */
#define CP_FIN_ACCEPT 1
#define CP_FIN_EOT 2
#define CP_FIN_CANTPOP 3
int debug; /* user level debug */
int sys_debug; /* system level debug */
long i; /* table index */
};
/* -----------------------------------------------------------------------
Parser Main Routines
----------------------------------------------------------------------- */
static VALUE racc_cparse(VALUE parser, VALUE arg, VALUE sysdebug);
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(RB_BLOCK_CALL_FUNC_ARGLIST(block_args, data));
static VALUE assert_array(VALUE a);
static long assert_integer(VALUE n);
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);
static void extract_user_token(struct cparse_params *v,
VALUE block_args, VALUE *tok, VALUE *val);
static void shift(struct cparse_params* v, long act, VALUE tok, VALUE val);
static int reduce(struct cparse_params* v, long act);
static rb_block_call_func reduce0;
#ifdef DEBUG
# define D_puts(msg) if (v->sys_debug) puts(msg)
# define D_printf(fmt,arg) if (v->sys_debug) printf(fmt,arg)
#else
# define D_puts(msg)
# 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)
{
VALUE vparams;
struct cparse_params *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 = 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)
{
VALUE vparams;
struct cparse_params *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 = TRUE;
D_puts("params initialized");
parse_main(v, Qnil, Qnil, 0);
call_lexer(v);
if (!v->fin) {
rb_raise(rb_eArgError, "%s() is finished before EndOfToken",
rb_id2name(v->lexmid));
}
RB_GC_GUARD(vparams);
return v->retval;
}
static void
call_lexer(struct cparse_params *v)
{
rb_block_call(v->lexer, v->lexmid, 0, NULL, lexer_i, v->value_v);
}
static VALUE
lexer_i(RB_BLOCK_CALL_FUNC_ARGLIST(block_args, data))
{
struct cparse_params *v = rb_check_typeddata(data, &cparse_params_type);
VALUE tok, val;
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();
return Qnil;
}
static VALUE
assert_array(VALUE a)
{
Check_Type(a, T_ARRAY);
return a;
}
static VALUE
assert_hash(VALUE h)
{
Check_Type(h, T_HASH);
return h;
}
static long
assert_integer(VALUE n)
{
return NUM2LONG(n);
}
static VALUE
initialize_params(VALUE vparams, VALUE parser, VALUE arg, VALUE lexer, VALUE lexmid)
{
struct cparse_params *v = rb_check_typeddata(vparams, &cparse_params_type);
v->value_v = vparams;
v->parser = parser;
v->lexer = lexer;
if (! NIL_P(lexmid))
v->lexmid = value_to_id(lexmid);
v->debug = RTEST(rb_ivar_get(parser, id_yydebug));
Check_Type(arg, T_ARRAY);
if (!(13 <= RARRAY_LEN(arg) && RARRAY_LEN(arg) <= 14))
rb_raise(RaccBug, "[Racc Bug] wrong arg.size %ld", RARRAY_LEN(arg));
v->action_table = assert_array (rb_ary_entry(arg, 0));
v->action_check = assert_array (rb_ary_entry(arg, 1));
v->action_default = assert_array (rb_ary_entry(arg, 2));
v->action_pointer = assert_array (rb_ary_entry(arg, 3));
v->goto_table = assert_array (rb_ary_entry(arg, 4));
v->goto_check = assert_array (rb_ary_entry(arg, 5));
v->goto_default = assert_array (rb_ary_entry(arg, 6));
v->goto_pointer = assert_array (rb_ary_entry(arg, 7));
v->nt_base = assert_integer(rb_ary_entry(arg, 8));
v->reduce_table = assert_array (rb_ary_entry(arg, 9));
v->token_table = assert_hash (rb_ary_entry(arg, 10));
v->shift_n = assert_integer(rb_ary_entry(arg, 11));
v->reduce_n = assert_integer(rb_ary_entry(arg, 12));
if (RARRAY_LEN(arg) > 13) {
v->use_result_var = RTEST(rb_ary_entry(arg, 13));
}
else {
v->use_result_var = TRUE;
}
v->tstack = v->debug ? NEW_STACK() : Qnil;
v->vstack = NEW_STACK();
v->state = NEW_STACK();
v->curstate = 0;
PUSH(v->state, INT2FIX(0));
v->t = INT2FIX(FINAL_TOKEN + 1); /* must not init to FINAL_TOKEN */
v->nerr = 0;
v->errstatus = 0;
rb_ivar_set(parser, id_errstatus, LONG2NUM(v->errstatus));
v->retval = Qnil;
v->fin = 0;
v->lex_is_iterator = FALSE;
rb_iv_set(parser, "@vstack", v->vstack);
if (v->debug) {
rb_iv_set(parser, "@tstack", v->tstack);
}
else {
rb_iv_set(parser, "@tstack", Qnil);
}
return vparams;
}
static void
cparse_params_mark(void *ptr)
{
struct cparse_params *v = (struct cparse_params*)ptr;
rb_gc_mark(v->value_v);
rb_gc_mark(v->parser);
rb_gc_mark(v->lexer);
rb_gc_mark(v->action_table);
rb_gc_mark(v->action_check);
rb_gc_mark(v->action_default);
rb_gc_mark(v->action_pointer);
rb_gc_mark(v->goto_table);
rb_gc_mark(v->goto_check);
rb_gc_mark(v->goto_default);
rb_gc_mark(v->goto_pointer);
rb_gc_mark(v->reduce_table);
rb_gc_mark(v->token_table);
rb_gc_mark(v->state);
rb_gc_mark(v->vstack);
rb_gc_mark(v->tstack);
rb_gc_mark(v->t);
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)
{
if (NIL_P(block_args)) {
/* EOF */
*tok = Qfalse;
*val = rb_str_new("$", 1);
return;
}
if (!RB_TYPE_P(block_args, T_ARRAY)) {
rb_raise(rb_eTypeError,
"%s() %s %"PRIsVALUE" (must be Array[2])",
v->lex_is_iterator ? rb_id2name(v->lexmid) : "next_token",
v->lex_is_iterator ? "yielded" : "returned",
rb_obj_class(block_args));
}
if (RARRAY_LEN(block_args) != 2) {
rb_raise(rb_eArgError,
"%s() %s wrong size of array (%ld for 2)",
v->lex_is_iterator ? rb_id2name(v->lexmid) : "next_token",
v->lex_is_iterator ? "yielded" : "returned",
RARRAY_LEN(block_args));
}
*tok = AREF(block_args, 0);
*val = AREF(block_args, 1);
}
#define SHIFT(v,act,tok,val) shift(v,act,tok,val)
#define REDUCE(v,act) do {\
switch (reduce(v,act)) { \
case 0: /* normal */ \
break; \
case 1: /* yyerror */ \
goto user_yyerror; \
case 2: /* yyaccept */ \
D_puts("u accept"); \
goto accept; \
default: \
break; \
} \
} while (0)
static void
parse_main(struct cparse_params *v, VALUE tok, VALUE val, int resume)
{
long i; /* table index */
long act; /* action type */
VALUE act_value; /* action type, VALUE version */
int read_next = 1; /* true if we need to read next token */
VALUE tmp;
if (resume)
goto resume;
while (1) {
D_puts("");
D_puts("---- enter new loop ----");
D_puts("");
D_printf("(act) k1=%ld\n", v->curstate);
tmp = AREF(v->action_pointer, v->curstate);
if (NIL_P(tmp)) goto notfound;
D_puts("(act) pointer[k1] ok");
i = NUM2LONG(tmp);
D_printf("read_next=%d\n", read_next);
if (read_next && (v->t != vFINAL_TOKEN)) {
if (v->lex_is_iterator) {
D_puts("resuming...");
if (v->fin) rb_raise(rb_eArgError, "token given after EOF");
v->i = i; /* save i */
return;
resume:
D_puts("resumed");
i = v->i; /* load i */
}
else {
D_puts("next_token");
tmp = rb_funcall(v->parser, id_nexttoken, 0);
extract_user_token(v, tmp, &tok, &val);
}
/* convert token */
v->t = rb_hash_aref(v->token_table, tok);
if (NIL_P(v->t)) {
v->t = vERROR_TOKEN;
}
D_printf("(act) t(k2)=%ld\n", NUM2LONG(v->t));
if (v->debug) {
rb_funcall(v->parser, id_d_read_token,
3, v->t, tok, val);
}
}
read_next = 0;
i += NUM2LONG(v->t);
D_printf("(act) i=%ld\n", i);
if (i < 0) goto notfound;
act_value = AREF(v->action_table, i);
if (NIL_P(act_value)) goto notfound;
act = NUM2LONG(act_value);
D_printf("(act) table[i]=%ld\n", act);
tmp = AREF(v->action_check, i);
if (NIL_P(tmp)) goto notfound;
if (NUM2LONG(tmp) != v->curstate) goto notfound;
D_printf("(act) check[i]=%ld\n", NUM2LONG(tmp));
D_puts("(act) found");
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);
act = NUM2LONG(act_value);
goto act_fixed;
handle_act:
if (act > 0 && act < v->shift_n) {
D_puts("shift");
if (v->errstatus > 0) {
v->errstatus--;
rb_ivar_set(v->parser, id_errstatus, LONG2NUM(v->errstatus));
}
SHIFT(v, act, v->t, val);
read_next = 1;
}
else if (act < 0 && act > -(v->reduce_n)) {
D_puts("reduce");
REDUCE(v, act);
}
else if (act == -(v->reduce_n)) {
goto error;
error_recovered:
; /* goto label requires stmt */
}
else if (act == v->shift_n) {
D_puts("accept");
goto accept;
}
else {
rb_raise(RaccBug, "[Racc Bug] unknown act value %ld", act);
}
if (v->debug) {
rb_funcall(v->parser, id_d_next_state,
2, LONG2NUM(v->curstate), v->state);
}
}
/* not reach */
accept:
if (v->debug) rb_funcall(v->parser, id_d_accept, 0);
v->retval = rb_ary_entry(v->vstack, 0);
v->fin = CP_FIN_ACCEPT;
return;
error:
D_printf("error detected, status=%ld\n", v->errstatus);
if (v->errstatus == 0) {
v->nerr++;
rb_funcall(v->parser, id_onerror,
3, v->t, val, v->vstack);
}
user_yyerror:
if (v->errstatus == 3) {
if (v->t == vFINAL_TOKEN) {
v->retval = Qnil;
v->fin = CP_FIN_EOT;
return;
}
read_next = 1;
}
v->errstatus = 3;
rb_ivar_set(v->parser, id_errstatus, LONG2NUM(v->errstatus));
/* check if we can shift/reduce error token */
D_printf("(err) k1=%ld\n", v->curstate);
D_printf("(err) k2=%d (error)\n", ERROR_TOKEN);
while (1) {
tmp = AREF(v->action_pointer, v->curstate);
if (NIL_P(tmp)) goto error_pop;
D_puts("(err) pointer[k1] ok");
i = NUM2LONG(tmp) + ERROR_TOKEN;
D_printf("(err) i=%ld\n", i);
if (i < 0) goto error_pop;
act_value = AREF(v->action_table, i);
if (NIL_P(act_value)) {
D_puts("(err) table[i] == nil");
goto error_pop;
}
act = NUM2LONG(act_value);
D_printf("(err) table[i]=%ld\n", act);
tmp = AREF(v->action_check, i);
if (NIL_P(tmp)) {
D_puts("(err) check[i] == nil");
goto error_pop;
}
if (NUM2LONG(tmp) != v->curstate) {
D_puts("(err) check[i] != k1");
goto error_pop;
}
D_puts("(err) found: can handle error token");
break;
error_pop:
D_puts("(err) act not found: can't handle error token; pop");
if (RARRAY_LEN(v->state) <= 1) {
v->retval = Qnil;
v->fin = CP_FIN_CANTPOP;
return;
}
POP(v->state);
POP(v->vstack);
v->curstate = num_to_long(LAST_I(v->state));
if (v->debug) {
POP(v->tstack);
rb_funcall(v->parser, id_d_e_pop,
3, v->state, v->tstack, v->vstack);
}
}
/* shift/reduce error token */
if (act > 0 && act < v->shift_n) {
D_puts("e shift");
SHIFT(v, act, ERROR_TOKEN, val);
}
else if (act < 0 && act > -(v->reduce_n)) {
D_puts("e reduce");
REDUCE(v, act);
}
else if (act == v->shift_n) {
D_puts("e accept");
goto accept;
}
else {
rb_raise(RaccBug, "[Racc Bug] unknown act value %ld", act);
}
goto error_recovered;
}
static void
shift(struct cparse_params *v, long act, VALUE tok, VALUE val)
{
PUSH(v->vstack, val);
if (v->debug) {
PUSH(v->tstack, tok);
rb_funcall(v->parser, id_d_shift,
3, tok, v->tstack, v->vstack);
}
v->curstate = act;
PUSH(v->state, LONG2NUM(v->curstate));
}
static int
reduce(struct cparse_params *v, long act)
{
VALUE code;
v->ruleno = -act * 3;
code = rb_catch("racc_jump", reduce0, v->value_v);
v->errstatus = num_to_long(rb_ivar_get(v->parser, id_errstatus));
return NUM2INT(code);
}
static VALUE
reduce0(RB_BLOCK_CALL_FUNC_ARGLIST(_, data))
{
struct cparse_params *v = rb_check_typeddata(data, &cparse_params_type);
VALUE reduce_to, reduce_len, method_id;
long len;
ID mid;
VALUE tmp, tmp_t = Qundef, tmp_v = Qundef;
long i, k1, k2;
VALUE goto_state;
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);
len = NUM2LONG(reduce_len);
mid = value_to_id(method_id);
/* call action */
if (len == 0) {
tmp = Qnil;
if (mid != id_noreduce)
tmp_v = rb_ary_new();
if (v->debug)
tmp_t = rb_ary_new();
}
else {
if (mid != id_noreduce) {
tmp_v = GET_TAIL(v->vstack, len);
tmp = rb_ary_entry(tmp_v, 0);
}
else {
tmp = rb_ary_entry(v->vstack, RARRAY_LEN(v->vstack) - len);
}
CUT_TAIL(v->vstack, len);
if (v->debug) {
tmp_t = GET_TAIL(v->tstack, len);
CUT_TAIL(v->tstack, len);
}
CUT_TAIL(v->state, len);
}
if (mid != id_noreduce) {
if (v->use_result_var) {
tmp = rb_funcall(v->parser, mid,
3, tmp_v, v->vstack, tmp);
}
else {
tmp = rb_funcall(v->parser, mid,
2, tmp_v, v->vstack);
}
}
/* then push result */
PUSH(v->vstack, tmp);
if (v->debug) {
PUSH(v->tstack, reduce_to);
rb_funcall(v->parser, id_d_reduce,
4, tmp_t, reduce_to, v->tstack, v->vstack);
}
/* calculate transition state */
if (RARRAY_LEN(v->state) == 0)
rb_raise(RaccBug, "state stack unexpectedly empty");
k2 = num_to_long(LAST_I(v->state));
k1 = num_to_long(reduce_to) - v->nt_base;
D_printf("(goto) k1=%ld\n", k1);
D_printf("(goto) k2=%ld\n", k2);
tmp = AREF(v->goto_pointer, k1);
if (NIL_P(tmp)) goto notfound;
i = NUM2LONG(tmp) + k2;
D_printf("(goto) i=%ld\n", i);
if (i < 0) goto notfound;
goto_state = AREF(v->goto_table, i);
if (NIL_P(goto_state)) {
D_puts("(goto) table[i] == nil");
goto notfound;
}
D_printf("(goto) table[i]=%ld (goto_state)\n", NUM2LONG(goto_state));
tmp = AREF(v->goto_check, i);
if (NIL_P(tmp)) {
D_puts("(goto) check[i] == nil");
goto notfound;
}
if (tmp != LONG2NUM(k1)) {
D_puts("(goto) check[i] != table[i]");
goto notfound;
}
D_printf("(goto) check[i]=%ld\n", NUM2LONG(tmp));
D_puts("(goto) found");
transit:
PUSH(v->state, goto_state);
v->curstate = NUM2LONG(goto_state);
return INT2FIX(0);
notfound:
D_puts("(goto) not found: use default");
/* overwrite `goto-state' by default value */
goto_state = AREF(v->goto_default, k1);
goto transit;
}
/* -----------------------------------------------------------------------
Ruby Interface
----------------------------------------------------------------------- */
void
Init_cparse(void)
{
#ifdef HAVE_RB_EXT_RACTOR_SAFE
rb_ext_ractor_safe(true);
#endif
VALUE Racc, Parser;
ID id_racc = rb_intern_const("Racc");
if (rb_const_defined(rb_cObject, id_racc)) {
Racc = rb_const_get(rb_cObject, id_racc);
Parser = rb_const_get_at(Racc, rb_intern_const("Parser"));
}
else {
Racc = rb_define_module("Racc");
Parser = rb_define_class_under(Racc, "Parser", rb_cObject);
}
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;
id_yydebug = rb_intern_const("@yydebug");
id_nexttoken = rb_intern_const("next_token");
id_onerror = rb_intern_const("on_error");
id_noreduce = rb_intern_const("_reduce_none");
id_errstatus = rb_intern_const("@racc_error_status");
id_d_shift = rb_intern_const("racc_shift");
id_d_reduce = rb_intern_const("racc_reduce");
id_d_accept = rb_intern_const("racc_accept");
id_d_read_token = rb_intern_const("racc_read_token");
id_d_next_state = rb_intern_const("racc_next_state");
id_d_e_pop = rb_intern_const("racc_e_pop");
}
@@ -0,0 +1,8 @@
# frozen_string_literal: true
#
require 'mkmf'
require_relative '../../../lib/racc/info'
$defs << "-D""RACC_INFO_VERSION=#{Racc::VERSION}"
create_makefile 'racc/cparse'
+6
View File
@@ -0,0 +1,6 @@
require_relative 'racc/compat'
require_relative 'racc/debugflags'
require_relative 'racc/grammar'
require_relative 'racc/state'
require_relative 'racc/exception'
require_relative 'racc/info'
+33
View File
@@ -0,0 +1,33 @@
#--
#
#
#
# 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".
#
#++
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
Binary file not shown.
@@ -0,0 +1,60 @@
#--
#
#
#
# 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
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
+16
View File
@@ -0,0 +1,16 @@
#--
#
#
#
# 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
class Error < StandardError; end
class CompileError < Error; end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,667 @@
#--
#
#
#
# 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_relative '../racc'
require_relative 'compat'
require_relative 'grammar'
require_relative 'parserfilegenerator'
require_relative '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
}\
| seq(:ERROR_ON_EXPECT_MISMATCH) {|*|
@grammar.error_on_expect_mismatch = true
}
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("?") {|*|
OptionMark.new(@scanner.lineno)
}\
| seq("*") {|*|
ManyMark.new(@scanner.lineno)
}\
| seq("+") {|*|
Many1Mark.new(@scanner.lineno)
}\
| seq("(") {|*|
GroupStartMark.new(@scanner.lineno)
}\
| seq(")") {|*|
GroupEndMark.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)
target = list.shift
case target
when OrMark, OptionMark, ManyMark, Many1Mark, GroupStartMark, GroupEndMark, UserAction, Prec
raise CompileError, "#{target.lineno}: unexpected symbol #{target.name}"
end
enum = list.each.with_index
_, sym, idx = _add_rule_block(target, enum)
if idx
# sym is Racc::GroupEndMark
raise "#{sym.lineno}: unexpected symbol ')' at pos=#{idx}"
end
end
def _add_rule_block(target, enum)
rules = [] # [ [seqs, sprec], .. ]
curr = []
sprec = nil
while (sym, idx = enum.next rescue nil)
case sym
when OrMark
rules << [curr, sprec]
curr = []
sprec = nil
when OptionMark
curr << _add_option_rule(curr.pop)
when ManyMark
curr << _add_many_rule(curr.pop)
when Many1Mark
curr << _add_many1_rule(curr.pop)
when GroupStartMark
curr << _add_group_rule(enum)
when GroupEndMark
rules << [curr, sprec]
return rules, sym, idx
when Prec
raise CompileError, "'=<prec>' used twice in one rule" if sprec
sprec = sym.symbol
else
curr.push sym
end
end
rules << [curr, sprec]
rules.each do |syms, sprec|
add_rule target, syms, sprec
end
nil
end
def _add_option_rule(prev)
@option_rule_registry ||= {}
target = @option_rule_registry[prev.to_s]
return target if target
target = _gen_target_name("option", prev)
@option_rule_registry[prev.to_s] = target
act = UserAction.empty
@grammar.add Rule.new(target, [], act)
@grammar.add Rule.new(target, [prev], act)
target
end
def _add_many_rule(prev)
@many_rule_registry ||= {}
target = @many_rule_registry[prev.to_s]
return target if target
target = _gen_target_name("many", prev)
@many_rule_registry[prev.to_s] = target
src = SourceText.new("result = val[1] ? val[1].unshift(val[0]) : val", @filename, @scanner.lineno + 1)
act = UserAction.source_text(src)
@grammar.add Rule.new(target, [], act)
@grammar.add Rule.new(target, [prev, target], act)
target
end
def _add_many1_rule(prev)
@many1_rule_registry ||= {}
target = @many1_rule_registry[prev.to_s]
return target if target
target = _gen_target_name("many1", prev)
@many1_rule_registry[prev.to_s] = target
src = SourceText.new("result = val[1] ? val[1].unshift(val[0]) : val", @filename, @scanner.lineno + 1)
act = UserAction.source_text(src)
@grammar.add Rule.new(target, [prev], act)
@grammar.add Rule.new(target, [prev, target], act)
target
end
def _add_group_rule(enum)
target = @grammar.intern("-temp-group", true)
rules, _ = _add_rule_block(target, enum)
target_name = rules.map{|syms, sprec| syms.join("-")}.join("|")
@group_rule_registry ||= {}
unless target = @group_rule_registry[target_name]
target = @grammar.intern("-group@#{target_name}", true)
@group_rule_registry[target_name] = target
src = SourceText.new("result = val", @filename, @scanner.lineno + 1)
act = UserAction.source_text(src)
rules.each do |syms, sprec|
rule = Rule.new(target, syms, act)
rule.specified_prec = sprec
@grammar.add rule
end
end
target
end
def _gen_target_name(type, sym)
@grammar.intern("-#{type}@#{sym.value}", true)
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, paths = *header.sub(/\A-+/, '').split('=', 2)
label = canonical_label(label0)
(paths ? paths.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.public_send(USER_CODE_LABELS[label]).push src
end
end
class GrammarFileScanner
def initialize(str, filename = '-')
@lines = str.b.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,
'error_on_expect_mismatch' => :ERROR_ON_EXPECT_MISMATCH,
'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 = String.new
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
buf.sub!(/[ \t\f]+\z/, '')
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
+18
View File
@@ -0,0 +1,18 @@
# frozen_string_literal: true
#--
#
#
#
# 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
VERSION = '1.8.1'
Version = VERSION
Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
end
+92
View File
@@ -0,0 +1,92 @@
#--
#
#
#
# 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
# 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
@@ -0,0 +1,212 @@
#--
#
#
#
# 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
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 = <<SRC
%s (%d)
on right: %s
on left : %s
SRC
out.printf tmp, t.to_s, t.ident,
symbol_locations(t.locate).join(' '),
symbol_locations(t.heads).join(' ')
end
out.print "\n**Terminals, with rules where they appear\n\n"
@grammar.each_terminal do |t|
out.printf " %s (%d) %s\n",
t.to_s, t.ident, symbol_locations(t.locate).join(' ')
end
end
def symbol_locations(locs)
locs.map {|loc| loc.rule.ident }.reject {|n| n == 0 }.uniq
end
end
end # module Racc
+644
View File
@@ -0,0 +1,644 @@
module Racc
PARSER_TEXT = <<'__end_of_file__'
#--
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
#
# As a special exception, when this code is copied by Racc
# into a Racc output file, you may use that output file
# without restriction.
#++
unless $".find {|p| p.end_with?('/racc/info.rb')}
$".push "#{__dir__}/racc/info.rb"
module Racc
VERSION = '1.8.1'
Version = VERSION
Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
end
end
module Racc
class ParseError < StandardError; end
end
unless defined?(::ParseError)
ParseError = Racc::ParseError # :nodoc:
end
# Racc is an LALR(1) parser generator.
# It is written in Ruby itself, and generates Ruby programs.
#
# == Command-line Reference
#
# racc [-o<var>filename</var>] [--output-file=<var>filename</var>]
# [-e<var>rubypath</var>] [--executable=<var>rubypath</var>]
# [-v] [--verbose]
# [-O<var>filename</var>] [--log-file=<var>filename</var>]
# [-g] [--debug]
# [-E] [--embedded]
# [-l] [--no-line-convert]
# [-c] [--line-convert-all]
# [-a] [--no-omit-actions]
# [-C] [--check-only]
# [-S] [--output-status]
# [--version] [--copyright] [--help] <var>grammarfile</var>
#
# [+grammarfile+]
# 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+]
# Place logging output in file +filename+.
# Default log file name is <+filename+>.output.
# [-e+rubypath+, --executable=+rubypath+]
# 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 debugging information,
# use this '-g' option and set @yydebug true in parser class.
# [-E, --embedded]
# Output parser which doesn't need runtime files (racc/parser.rb).
# [-F, --frozen]
# Output parser which declares frozen_string_literals: true
# [-C, --check-only]
# Check syntax of racc grammar 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.
# [--version]
# print Racc version and quit.
# [--copyright]
# Print copyright and quit.
# [--help]
# Print usage and quit.
#
# == Generating Parser Using Racc
#
# To compile Racc grammar file, simply type:
#
# $ racc parse.y
#
# This creates Ruby script file "parse.tab.y". The -o option can change the output filename.
#
# == Writing A Racc Grammar File
#
# If you want 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.
# 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.
#
# class Calcparser
# rule
# target: exp { print val[0] }
#
# exp: exp '+' exp
# | exp '*' exp
# | '(' exp ')'
# | NUMBER
# end
#
# Racc grammar files resemble yacc files.
# But (of course), this is Ruby code.
# yacc's $$ is the 'result', $0, $1... is
# an array called 'val', and $-1, $-2... is an array called '_values'.
#
# See the {Grammar File Reference}[rdoc-ref:lib/racc/rdoc/grammar.en.rdoc] for
# more information on grammar files.
#
# == Parser
#
# Then you must prepare the parse entry method. There are two types of
# parse methods in Racc, Racc::Parser#do_parse and Racc::Parser#yyparse
#
# 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].
# 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.
#
# Racc::Parser#yyparse is little complicated, but useful.
# It does not use Racc::Parser#next_token, instead it gets tokens from any iterator.
#
# For example, <code>yyparse(obj, :scan)</code> causes
# calling +obj#scan+, and you can return tokens by yielding them from +obj#scan+.
#
# == Debugging
#
# When debugging, "-v" or/and the "-g" option is helpful.
#
# "-v" creates verbose log file (.output).
# "-g" creates a "Verbose Parser".
# 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..
#
# === Racc reported "XXXX conflicts".
#
# Try "racc -v xxxx.y".
# It causes producing racc's internal log file, xxxx.output.
#
# === Generated parsers does not work correctly
#
# Try "racc -g xxxx.y".
# This command let racc generate "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;
# racc/parser.rb.
#
# Ruby 1.8.x comes with Racc runtime module,
# 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:
#
# $ racc -E -omyparser.rb myparser.y
#
# This command creates myparser.rb which `includes' Racc runtime.
# Only you must do is to distribute your parser file (myparser.rb).
#
# Note: parser.rb is ruby license, but your parser is not.
# Your own parser is completely yours.
module Racc
unless defined?(Racc_No_Extensions)
Racc_No_Extensions = false # :nodoc:
end
class Parser
Racc_Runtime_Version = ::Racc::VERSION
Racc_Runtime_Core_Version_R = ::Racc::VERSION
begin
if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
require 'jruby'
require 'racc/cparse-jruby.jar'
com.headius.racc.Cparse.new.load(JRuby.runtime, false)
else
require 'racc/cparse'
end
unless new.respond_to?(:_racc_do_parse_c, true)
raise LoadError, 'old cparse.so'
end
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_Type = 'c' # :nodoc:
rescue LoadError
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_Type = 'ruby'
end
def Parser.racc_runtime_type # :nodoc:
Racc_Runtime_Type
end
def _racc_setup
@yydebug = false unless self.class::Racc_debug_parser
@yydebug = false unless defined?(@yydebug)
if @yydebug
@racc_debug_out = $stderr unless defined?(@racc_debug_out)
@racc_debug_out ||= $stderr
end
arg = self.class::Racc_arg
arg[13] = true if arg.size < 14
arg
end
def _racc_init_sysvars
@racc_state = [0]
@racc_tstack = []
@racc_vstack = []
@racc_t = nil
@racc_val = nil
@racc_read_next = true
@racc_user_yyerror = false
@racc_error_status = 0
end
# The entry point of the parser. This method is used with #next_token.
# If Racc wants to get token (and its value), calls next_token.
#
# Example:
# def parse
# @q = [[1,1],
# [2,2],
# [3,3],
# [false, '$']]
# do_parse
# end
#
# def next_token
# @q.shift
# end
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def do_parse
#{Racc_Main_Parsing_Routine}(_racc_setup(), false)
end
RUBY
# The method to fetch next token.
# If you use #do_parse method, you must implement #next_token.
#
# The format of return value is [TOKEN_SYMBOL, VALUE].
# +token-symbol+ is represented by Ruby's symbol by default, e.g. :IDENT
# for 'IDENT'. ";" (String) for ';'.
#
# The final symbol (End of file) must be false.
def next_token
raise NotImplementedError, "#{self.class}\#next_token is not defined"
end
def _racc_do_parse_rb(arg, in_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
tok = act = i = nil
catch(:racc_end_parse) {
while true
if i = action_pointer[@racc_state[-1]]
if @racc_read_next
if @racc_t != 0 # not EOF
tok, @racc_val = next_token()
unless tok # EOF
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
racc_read_token(@racc_t, tok, @racc_val) if @yydebug
@racc_read_next = false
end
end
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
else
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
}
end
# Another entry point for the parser.
# If you use this method, you must implement RECEIVER#METHOD_ID method.
#
# RECEIVER#METHOD_ID is a method to get next token.
# It must 'yield' the token, which format is [TOKEN-SYMBOL, VALUE].
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def yyparse(recv, mid)
#{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false)
end
RUBY
def _racc_yyparse_rb(recv, mid, arg, c_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
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|
unless tok
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
@racc_val = val
@racc_read_next = false
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
while !(i = action_pointer[@racc_state[-1]]) ||
! @racc_read_next ||
@racc_t == 0 # $
unless i and i += @racc_t and
i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
end
}
end
###
### common
###
def _racc_evalact(act, arg)
action_table, action_check, _, action_pointer,
_, _, _, _,
_, _, _, 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 or EOF
end
@racc_vstack.push @racc_val
@racc_state.push act
@racc_read_next = true
if @yydebug
@racc_tstack.push @racc_t
racc_shift @racc_t, @racc_tstack, @racc_vstack
end
elsif act < 0 and act > -reduce_n
#
# reduce
#
code = catch(:racc_jump) {
@racc_state.push _racc_do_reduce(arg, act)
false
}
if code
case code
when 1 # yyerror
@racc_user_yyerror = true # user_yyerror
return -reduce_n
when 2 # yyaccept
return shift_n
else
raise '[Racc Bug] unknown jump code'
end
end
elsif act == shift_n
#
# accept
#
racc_accept if @yydebug
throw :racc_end_parse, @racc_vstack[0]
elsif act == -reduce_n
#
# error
#
case @racc_error_status
when 0
unless arg[21] # user_yyerror
nerr += 1
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
end
@racc_user_yyerror = false
@racc_error_status = 3
while true
if i = action_pointer[@racc_state[-1]]
i += 1 # error token
if i >= 0 and
(act = action_table[i]) and
action_check[i] == @racc_state[-1]
break
end
end
throw :racc_end_parse, nil if @racc_state.size <= 1
@racc_state.pop
@racc_vstack.pop
if @yydebug
@racc_tstack.pop
racc_e_pop @racc_state, @racc_tstack, @racc_vstack
end
end
return act
else
raise "[Racc Bug] unknown action #{act.inspect}"
end
racc_next_state(@racc_state[-1], @racc_state) if @yydebug
nil
end
def _racc_do_reduce(arg, act)
_, _, _, _,
goto_table, goto_check, goto_default, goto_pointer,
nt_base, reduce_table, _, _,
_, use_result, * = arg
state = @racc_state
vstack = @racc_vstack
tstack = @racc_tstack
i = act * -3
len = reduce_table[i]
reduce_to = reduce_table[i+1]
method_id = reduce_table[i+2]
void_array = []
tmp_t = tstack[-len, len] if @yydebug
tmp_v = vstack[-len, len]
tstack[-len, len] = void_array if @yydebug
vstack[-len, len] = void_array
state[-len, len] = void_array
# tstack must be updated AFTER method call
if use_result
vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
else
vstack.push __send__(method_id, tmp_v, vstack)
end
tstack.push reduce_to
racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
k1 = reduce_to - nt_base
if i = goto_pointer[k1]
i += state[-1]
if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
return curstate
end
end
goto_default[k1]
end
# This method is called when a parse error is found.
#
# ERROR_TOKEN_ID is an internal ID of token which caused error.
# You can get string representation of this ID by calling
# #token_to_str.
#
# ERROR_VALUE is a value of error token.
#
# value_stack is a stack of symbol values.
# DO NOT MODIFY this object.
#
# This method raises ParseError by default.
#
# If this method returns, parsers enter "error recovering mode".
def on_error(t, val, vstack)
raise ParseError, sprintf("parse error on value %s (%s)",
val.inspect, token_to_str(t) || '?')
end
# Enter error recovering mode.
# This method does not call #on_error.
def yyerror
throw :racc_jump, 1
end
# Exit parser.
# Return value is +Symbol_Value_Stack[0]+.
def yyaccept
throw :racc_jump, 2
end
# Leave error recovering mode.
def yyerrok
@racc_error_status = 0
end
# For debugging output
def racc_read_token(t, tok, val)
@racc_debug_out.print 'read '
@racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
@racc_debug_out.puts val.inspect
@racc_debug_out.puts
end
def racc_shift(tok, tstack, vstack)
@racc_debug_out.puts "shift #{racc_token2str tok}"
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_reduce(toks, sim, tstack, vstack)
out = @racc_debug_out
out.print 'reduce '
if toks.empty?
out.print ' <none>'
else
toks.each {|t| out.print ' ', racc_token2str(t) }
end
out.puts " --> #{racc_token2str(sim)}"
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_accept
@racc_debug_out.puts 'accept'
@racc_debug_out.puts
end
def racc_e_pop(state, tstack, vstack)
@racc_debug_out.puts 'error recovering mode: pop token'
racc_print_states state
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_next_state(curstate, state)
@racc_debug_out.puts "goto #{curstate}"
racc_print_states state
@racc_debug_out.puts
end
def racc_print_stacks(t, v)
out = @racc_debug_out
out.print ' ['
t.each_index do |i|
out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
end
out.puts ' ]'
end
def racc_print_states(s)
out = @racc_debug_out
out.print ' ['
s.each {|st| out.print ' ', st }
out.puts ' ]'
end
def racc_token2str(tok)
self.class::Racc_token_to_s_table[tok] or
raise "[Racc Bug] can't convert token #{tok} to string"
end
# Convert internal ID of token symbol to the string.
def token_to_str(t)
self.class::Racc_token_to_s_table[t]
end
end
end
__end_of_file__
end
+630
View File
@@ -0,0 +1,630 @@
# frozen_string_literal: true
#--
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
#
# As a special exception, when this code is copied by Racc
# into a Racc output file, you may use that output file
# without restriction.
#++
require 'racc/info'
module Racc
class ParseError < StandardError; end
end
unless defined?(::ParseError)
ParseError = Racc::ParseError # :nodoc:
end
# Racc is an LALR(1) parser generator.
# It is written in Ruby itself, and generates Ruby programs.
#
# == Command-line Reference
#
# racc [-o<var>filename</var>] [--output-file=<var>filename</var>]
# [-e<var>rubypath</var>] [--executable=<var>rubypath</var>]
# [-v] [--verbose]
# [-O<var>filename</var>] [--log-file=<var>filename</var>]
# [-g] [--debug]
# [-E] [--embedded]
# [-l] [--no-line-convert]
# [-c] [--line-convert-all]
# [-a] [--no-omit-actions]
# [-C] [--check-only]
# [-S] [--output-status]
# [--version] [--copyright] [--help] <var>grammarfile</var>
#
# [+grammarfile+]
# 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+]
# Place logging output in file +filename+.
# Default log file name is <+filename+>.output.
# [-e+rubypath+, --executable=+rubypath+]
# 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 debugging information,
# use this '-g' option and set @yydebug true in parser class.
# [-E, --embedded]
# Output parser which doesn't need runtime files (racc/parser.rb).
# [-F, --frozen]
# Output parser which declares frozen_string_literals: true
# [-C, --check-only]
# Check syntax of racc grammar 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.
# [--version]
# print Racc version and quit.
# [--copyright]
# Print copyright and quit.
# [--help]
# Print usage and quit.
#
# == Generating Parser Using Racc
#
# To compile Racc grammar file, simply type:
#
# $ racc parse.y
#
# This creates Ruby script file "parse.tab.y". The -o option can change the output filename.
#
# == Writing A Racc Grammar File
#
# If you want 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.
# 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.
#
# class Calcparser
# rule
# target: exp { print val[0] }
#
# exp: exp '+' exp
# | exp '*' exp
# | '(' exp ')'
# | NUMBER
# end
#
# Racc grammar files resemble yacc files.
# But (of course), this is Ruby code.
# yacc's $$ is the 'result', $0, $1... is
# an array called 'val', and $-1, $-2... is an array called '_values'.
#
# See the {Grammar File Reference}[rdoc-ref:lib/racc/rdoc/grammar.en.rdoc] for
# more information on grammar files.
#
# == Parser
#
# Then you must prepare the parse entry method. There are two types of
# parse methods in Racc, Racc::Parser#do_parse and Racc::Parser#yyparse
#
# 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].
# 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.
#
# Racc::Parser#yyparse is little complicated, but useful.
# It does not use Racc::Parser#next_token, instead it gets tokens from any iterator.
#
# For example, <code>yyparse(obj, :scan)</code> causes
# calling +obj#scan+, and you can return tokens by yielding them from +obj#scan+.
#
# == Debugging
#
# When debugging, "-v" or/and the "-g" option is helpful.
#
# "-v" creates verbose log file (.output).
# "-g" creates a "Verbose Parser".
# 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..
#
# === Racc reported "XXXX conflicts".
#
# Try "racc -v xxxx.y".
# It causes producing racc's internal log file, xxxx.output.
#
# === Generated parsers does not work correctly
#
# Try "racc -g xxxx.y".
# This command let racc generate "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;
# racc/parser.rb.
#
# Ruby 1.8.x comes with Racc runtime module,
# 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:
#
# $ racc -E -omyparser.rb myparser.y
#
# This command creates myparser.rb which `includes' Racc runtime.
# Only you must do is to distribute your parser file (myparser.rb).
#
# Note: parser.rb is ruby license, but your parser is not.
# Your own parser is completely yours.
module Racc
unless defined?(Racc_No_Extensions)
Racc_No_Extensions = false # :nodoc:
end
class Parser
Racc_Runtime_Version = ::Racc::VERSION
Racc_Runtime_Core_Version_R = ::Racc::VERSION
begin
if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
require 'jruby'
require 'racc/cparse-jruby.jar'
com.headius.racc.Cparse.new.load(JRuby.runtime, false)
else
require 'racc/cparse'
end
unless new.respond_to?(:_racc_do_parse_c, true)
raise LoadError, 'old cparse.so'
end
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_Type = 'c' # :nodoc:
rescue LoadError
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_Type = 'ruby'
end
def Parser.racc_runtime_type # :nodoc:
Racc_Runtime_Type
end
def _racc_setup
@yydebug = false unless self.class::Racc_debug_parser
@yydebug = false unless defined?(@yydebug)
if @yydebug
@racc_debug_out = $stderr unless defined?(@racc_debug_out)
@racc_debug_out ||= $stderr
end
arg = self.class::Racc_arg
arg[13] = true if arg.size < 14
arg
end
def _racc_init_sysvars
@racc_state = [0]
@racc_tstack = []
@racc_vstack = []
@racc_t = nil
@racc_val = nil
@racc_read_next = true
@racc_user_yyerror = false
@racc_error_status = 0
end
# The entry point of the parser. This method is used with #next_token.
# If Racc wants to get token (and its value), calls next_token.
#
# Example:
# def parse
# @q = [[1,1],
# [2,2],
# [3,3],
# [false, '$']]
# do_parse
# end
#
# def next_token
# @q.shift
# end
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def do_parse
#{Racc_Main_Parsing_Routine}(_racc_setup(), false)
end
RUBY
# The method to fetch next token.
# If you use #do_parse method, you must implement #next_token.
#
# The format of return value is [TOKEN_SYMBOL, VALUE].
# +token-symbol+ is represented by Ruby's symbol by default, e.g. :IDENT
# for 'IDENT'. ";" (String) for ';'.
#
# The final symbol (End of file) must be false.
def next_token
raise NotImplementedError, "#{self.class}\#next_token is not defined"
end
def _racc_do_parse_rb(arg, in_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
tok = act = i = nil
catch(:racc_end_parse) {
while true
if i = action_pointer[@racc_state[-1]]
if @racc_read_next
if @racc_t != 0 # not EOF
tok, @racc_val = next_token()
unless tok # EOF
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
racc_read_token(@racc_t, tok, @racc_val) if @yydebug
@racc_read_next = false
end
end
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
else
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
}
end
# Another entry point for the parser.
# If you use this method, you must implement RECEIVER#METHOD_ID method.
#
# RECEIVER#METHOD_ID is a method to get next token.
# It must 'yield' the token, which format is [TOKEN-SYMBOL, VALUE].
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def yyparse(recv, mid)
#{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false)
end
RUBY
def _racc_yyparse_rb(recv, mid, arg, c_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
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|
unless tok
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
@racc_val = val
@racc_read_next = false
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
while !(i = action_pointer[@racc_state[-1]]) ||
! @racc_read_next ||
@racc_t == 0 # $
unless i and i += @racc_t and
i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
end
}
end
###
### common
###
def _racc_evalact(act, arg)
action_table, action_check, _, action_pointer,
_, _, _, _,
_, _, _, 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 or EOF
end
@racc_vstack.push @racc_val
@racc_state.push act
@racc_read_next = true
if @yydebug
@racc_tstack.push @racc_t
racc_shift @racc_t, @racc_tstack, @racc_vstack
end
elsif act < 0 and act > -reduce_n
#
# reduce
#
code = catch(:racc_jump) {
@racc_state.push _racc_do_reduce(arg, act)
false
}
if code
case code
when 1 # yyerror
@racc_user_yyerror = true # user_yyerror
return -reduce_n
when 2 # yyaccept
return shift_n
else
raise '[Racc Bug] unknown jump code'
end
end
elsif act == shift_n
#
# accept
#
racc_accept if @yydebug
throw :racc_end_parse, @racc_vstack[0]
elsif act == -reduce_n
#
# error
#
case @racc_error_status
when 0
unless arg[21] # user_yyerror
nerr += 1
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
end
@racc_user_yyerror = false
@racc_error_status = 3
while true
if i = action_pointer[@racc_state[-1]]
i += 1 # error token
if i >= 0 and
(act = action_table[i]) and
action_check[i] == @racc_state[-1]
break
end
end
throw :racc_end_parse, nil if @racc_state.size <= 1
@racc_state.pop
@racc_vstack.pop
if @yydebug
@racc_tstack.pop
racc_e_pop @racc_state, @racc_tstack, @racc_vstack
end
end
return act
else
raise "[Racc Bug] unknown action #{act.inspect}"
end
racc_next_state(@racc_state[-1], @racc_state) if @yydebug
nil
end
def _racc_do_reduce(arg, act)
_, _, _, _,
goto_table, goto_check, goto_default, goto_pointer,
nt_base, reduce_table, _, _,
_, use_result, * = arg
state = @racc_state
vstack = @racc_vstack
tstack = @racc_tstack
i = act * -3
len = reduce_table[i]
reduce_to = reduce_table[i+1]
method_id = reduce_table[i+2]
void_array = []
tmp_t = tstack[-len, len] if @yydebug
tmp_v = vstack[-len, len]
tstack[-len, len] = void_array if @yydebug
vstack[-len, len] = void_array
state[-len, len] = void_array
# tstack must be updated AFTER method call
if use_result
vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
else
vstack.push __send__(method_id, tmp_v, vstack)
end
tstack.push reduce_to
racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
k1 = reduce_to - nt_base
if i = goto_pointer[k1]
i += state[-1]
if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
return curstate
end
end
goto_default[k1]
end
# This method is called when a parse error is found.
#
# ERROR_TOKEN_ID is an internal ID of token which caused error.
# You can get string representation of this ID by calling
# #token_to_str.
#
# ERROR_VALUE is a value of error token.
#
# value_stack is a stack of symbol values.
# DO NOT MODIFY this object.
#
# This method raises ParseError by default.
#
# If this method returns, parsers enter "error recovering mode".
def on_error(t, val, vstack)
raise ParseError, sprintf("parse error on value %s (%s)",
val.inspect, token_to_str(t) || '?')
end
# Enter error recovering mode.
# This method does not call #on_error.
def yyerror
throw :racc_jump, 1
end
# Exit parser.
# Return value is +Symbol_Value_Stack[0]+.
def yyaccept
throw :racc_jump, 2
end
# Leave error recovering mode.
def yyerrok
@racc_error_status = 0
end
# For debugging output
def racc_read_token(t, tok, val)
@racc_debug_out.print 'read '
@racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
@racc_debug_out.puts val.inspect
@racc_debug_out.puts
end
def racc_shift(tok, tstack, vstack)
@racc_debug_out.puts "shift #{racc_token2str tok}"
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_reduce(toks, sim, tstack, vstack)
out = @racc_debug_out
out.print 'reduce '
if toks.empty?
out.print ' <none>'
else
toks.each {|t| out.print ' ', racc_token2str(t) }
end
out.puts " --> #{racc_token2str(sim)}"
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_accept
@racc_debug_out.puts 'accept'
@racc_debug_out.puts
end
def racc_e_pop(state, tstack, vstack)
@racc_debug_out.puts 'error recovering mode: pop token'
racc_print_states state
racc_print_stacks tstack, vstack
@racc_debug_out.puts
end
def racc_next_state(curstate, state)
@racc_debug_out.puts "goto #{curstate}"
racc_print_states state
@racc_debug_out.puts
end
def racc_print_stacks(t, v)
out = @racc_debug_out
out.print ' ['
t.each_index do |i|
out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
end
out.puts ' ]'
end
def racc_print_states(s)
out = @racc_debug_out
out.print ' ['
s.each {|st| out.print ' ', st }
out.puts ' ]'
end
def racc_token2str(tok)
self.class::Racc_token_to_s_table[tok] or
raise "[Racc Bug] can't convert token #{tok} to string"
end
# Convert internal ID of token symbol to the string.
def token_to_str(t)
self.class::Racc_token_to_s_table[t]
end
end
end
@@ -0,0 +1,473 @@
#--
#
#
#
# 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_relative 'compat'
require_relative 'sourcetext'
require_relative 'parser-text'
require 'rbconfig'
module Racc
class ParserFileGenerator
class Params
def self.bool_attr(name)
module_eval(<<-End)
def #{name}?
@#{name}
end
def #{name}=(b)
@#{name} = b
end
End
end
attr_accessor :filename
attr_accessor :classname
attr_accessor :superclass
bool_attr :omit_action_call
bool_attr :result_var
attr_accessor :header
attr_accessor :inner
attr_accessor :footer
bool_attr :debug_parser
bool_attr :convert_line
bool_attr :convert_line_all
bool_attr :embed_runtime
bool_attr :frozen_strings
bool_attr :make_executable
attr_accessor :interpreter
def initialize
# Parameters derived from parser
self.filename = nil
self.classname = nil
self.superclass = 'Racc::Parser'
self.omit_action_call = true
self.result_var = true
self.header = []
self.inner = []
self.footer = []
# Parameters derived from command line options
self.debug_parser = false
self.convert_line = true
self.convert_line_all = false
self.embed_runtime = false
self.frozen_strings = false
self.make_executable = false
self.interpreter = nil
end
end
def initialize(states, params)
@states = states
@grammar = states.grammar
@params = params
end
def generate_parser
string_io = StringIO.new
init_line_conversion_system
@f = string_io
parser_file
string_io.rewind
string_io.read
end
def generate_parser_file(destpath)
init_line_conversion_system
File.open(destpath, 'w') {|f|
@f = f
parser_file
}
File.chmod 0755, destpath if @params.make_executable?
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'
end
header
parser_class(@params.classname, @params.superclass) {
inner
state_transition_table
}
footer
end
c = ::RbConfig::CONFIG
RUBY_PATH = "#{c['bindir']}/#{c['ruby_install_name']}#{c['EXEEXT']}"
def shebang(path)
line '#!' + (path == 'ruby' ? RUBY_PATH : path)
end
def notice
line %q[# frozen_string_literal: true] if @params.frozen_strings?
line %q[#]
line %q[# DO NOT MODIFY!!!!]
line %Q[# This file is automatically generated by Racc #{Racc::Version}]
line %Q[# from Racc grammar file "#{@params.filename}".]
line %q[#]
end
def runtime_source
SourceText.new(::Racc::PARSER_TEXT, 'racc/parser.rb', 1)
end
def embed_library(src)
line %[###### #{src.filename} begin]
line %[unless $".find {|p| p.end_with?('/#{src.filename}')}]
line %[$".push "\#{__dir__}/#{src.filename}"]
put src, @params.convert_line?
line %[end]
line %[###### #{src.filename} end]
end
def require(feature)
line "require '#{feature}'"
end
def parser_class(classname, superclass)
mods = classname.split('::')
classid = mods.pop
mods.each do |mod|
indent; line "module #{mod}"
cref_push mod
end
indent; line "class #{classid} < #{superclass}"
cref_push classid
yield
cref_pop
indent; line "end \# class #{classid}"
mods.reverse_each do |mod|
cref_pop
indent; line "end \# module #{mod}"
end
end
def header
@params.header.each do |src|
line
put src, @params.convert_line_all?
end
end
def inner
@params.inner.each do |src|
line
put src, @params.convert_line?
end
end
def footer
@params.footer.each do |src|
line
put src, @params.convert_line_all?
end
end
# Low Level Routines
def put(src, convert_line = false)
if convert_line
replace_location(src) {
@f.puts src.text
}
else
@f.puts src.text
end
end
def line(str = '')
@f.puts str
end
def init_line_conversion_system
@cref = []
@used_separator = {}
end
def cref_push(name)
@cref.push name
end
def cref_pop
@cref.pop
end
def indent
@f.print ' ' * @cref.size
end
def toplevel?
@cref.empty?
end
def replace_location(src)
sep = make_separator(src)
@f.print 'self.class.' if toplevel?
@f.puts "module_eval(<<'#{sep}', '#{src.filename}', #{src.lineno})"
yield
@f.puts sep
end
def make_separator(src)
sep = unique_separator(src.filename)
sep *= 2 while src.text.index(sep)
sep
end
def unique_separator(id)
sep = String.new "...end #{id}/module_eval..."
while @used_separator.key?(sep)
sep.concat sprintf('%02x', rand(255))
end
@used_separator[sep] = true
sep
end
#
# State Transition Table Serialization
#
public
def put_state_transition_table(f)
@f = f
state_transition_table
end
private
def state_transition_table
table = @states.state_transition_table
table.use_result_var = @params.result_var?
table.debug_parser = @params.debug_parser?
line "##### State transition tables begin ###"
line
integer_list 'racc_action_table', table.action_table
line
integer_list 'racc_action_check', table.action_check
line
integer_list 'racc_action_pointer', table.action_pointer
line
integer_list 'racc_action_default', table.action_default
line
integer_list 'racc_goto_table', table.goto_table
line
integer_list 'racc_goto_check', table.goto_check
line
integer_list 'racc_goto_pointer', table.goto_pointer
line
integer_list 'racc_goto_default', table.goto_default
line
i_i_sym_list 'racc_reduce_table', table.reduce_table
line
line "racc_reduce_n = #{table.reduce_n}"
line
line "racc_shift_n = #{table.shift_n}"
line
sym_int_hash 'racc_token_table', table.token_table
line
line "racc_nt_base = #{table.nt_base}"
line
line "racc_use_result_var = #{table.use_result_var}"
line
@f.print(unindent_auto(<<-End))
Racc_arg = [
racc_action_table,
racc_action_check,
racc_action_default,
racc_action_pointer,
racc_goto_table,
racc_goto_check,
racc_goto_default,
racc_goto_pointer,
racc_nt_base,
racc_reduce_table,
racc_token_table,
racc_shift_n,
racc_reduce_n,
racc_use_result_var ]
End
line "Ractor.make_shareable(Racc_arg) if defined?(Ractor)"
line
string_list 'Racc_token_to_s_table', table.token_to_s_table
line "Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)"
line
line "Racc_debug_parser = #{table.debug_parser}"
line
line '##### State transition tables end #####'
actions
end
def integer_list(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 ' ]'
end
def i_i_sym_list(name, table)
sep = ''
line "#{name} = ["
table.each_slice(3) do |len, target, mid|
@f.print sep; sep = ",\n"
@f.printf ' %d, %d, %s', len, target, mid.inspect
end
line " ]"
end
def sym_int_hash(name, h)
sep = "\n"
@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
end
line " }"
end
def string_list(name, list)
sep = " "
line "#{name} = ["
list.each do |s|
@f.print sep; sep = ",\n "
@f.print s.dump
end
line ' ]'
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
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?
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),
def _reduce_%d(val, _values%s)
%s%s
end
End
rule.ident, decl,
src.text, retval
end
end
end
line
@f.printf unindent_auto(<<-'End'), decl
def _reduce_none(val, _values%s)
val[0]
end
End
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)
delim *= 2
end
delim
end
def unindent_auto(str)
lines = str.lines.to_a
n = minimum_indent(lines)
lines.map {|line| detab(line).sub(indent_re(n), '').rstrip + "\n" }.join('')
end
def minimum_indent(lines)
lines.map {|line| n_indent(line) }.min
end
def n_indent(line)
line.slice(/\A\s+/).size
end
RE_CACHE = {}
def indent_re(n)
RE_CACHE[n] ||= /\A {#{n}}/
end
def detab(str, ts = 8)
add = 0
len = nil
str.gsub(/\t/) {
len = ts - ($`.size + add) % ts
add += len - 1
' ' * len
}
end
end
end
@@ -0,0 +1,35 @@
#--
#
#
#
# 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
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
"#<SourceText #{location()}>"
end
def location
"#{@filename}:#{@lineno}"
end
end
end
+976
View File
@@ -0,0 +1,976 @@
#--
#
#
#
# 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_relative 'iset'
require_relative 'statetransitiontable'
require_relative 'exception'
require 'forwardable'
module Racc
# A table of LALR states.
class States
include Enumerable
def initialize(grammar, debug_flags = DebugFlags.new)
@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
end
attr_reader :grammar
attr_reader :actions
def size
@states.size
end
def inspect
'#<state table>'
end
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 should_error_on_expect_mismatch?
should_report_srconflict? && @grammar.error_on_expect_mismatch
end
def srconflict_exist?
n_srconflicts() != 0
end
def n_srconflicts
@n_srconflicts ||= inject(0) {|sum, st| sum + st.n_srconflicts }
end
def rrconflict_exist?
n_rrconflicts() != 0
end
def n_rrconflicts
@n_rrconflicts ||= inject(0) {|sum, st| sum + st.n_rrconflicts }
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
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(state)
puts "dstate: #{state}" if @d_state
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
def addsym(table, sym, ptr)
unless s = table[sym]
table[sym] = s = ISet.new
end
s.add ptr
end
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(' ')}"
end
end
dest
end
def fingerprint(arr)
arr.map {|i| i.ident }.pack('L*')
end
#
# DFA (Deterministic Finite Automaton) Generation
#
public
def dfa
return self if @dfa_computed
nfa
compute_dfa
@dfa_computed = true
self
end
private
def compute_dfa
la = lookahead()
@states.each do |state|
state.la = la
resolve state
end
set_accept
@states.each do |state|
pack state
end
check_useless
end
def 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
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|
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?
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
### 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
def addrel(tbl, i, item)
if a = tbl[i]
a.push item
else
tbl[i] = [item]
end
end
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
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
end
end
end
new
end
def digraph(map, relation)
n = relation.size
index = Array.new(n, nil)
vertices = []
@infinity = n + 2
index.each_index do |i|
if not index[i] and relation[i]
traverse i, index, vertices, map, relation
end
end
end
def traverse(i, index, vertices, map, relation)
vertices.push i
index[i] = height = vertices.size
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
end
if index[i] == height
while true
proci = vertices.pop
index[proci] = @infinity
break if i == proci
map[proci] |= map[i]
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
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
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
# for debug
def printb(i)
each_t(@symboltable, i) do |t|
print t, ' '
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
end
end
end
#
# resolve
#
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])
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)
end
end
end
end
def resolve_sr(state, s)
s.each do |stok|
goto = state.goto_table[stok]
act = state.action[stok]
unless act
# no conflict
state.action[stok] = @actions.shift(goto)
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
rtok = act.rule.precedence
case do_resolve_sr(stok, rtok)
when :Reduce
# action is already set
when :Shift
# overwrite
act.decref
state.action[stok] = @actions.shift(goto)
when :Error
act.decref
state.action[stok] = @actions.error
when :CantResolve
# shift as default
act.decref
state.action[stok] = @actions.shift(goto)
state.sr_conflict stok, 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
ret = if rprec == sprec
ASSOC[rtok.assoc] or
raise "racc: fatal: #{rtok}.assoc is not Left/Right/Nonassoc"
else
(rprec > sprec) ? (:Reduce) : (:Shift)
end
puts "resolve_sr: resolved as #{ret.id2name}" if @d_prec
ret
end
#
# complete
#
def set_accept
anch = @symboltable.anchor
init_state = @states[0].goto_table[@grammar.start]
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
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
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
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
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
@closure = make_closure(@core)
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
attr_reader :action
attr_accessor :defact # default action
attr_reader :rrconf
attr_reader :srconf
def inspect
"<state #{@ident}>"
end
alias to_s inspect
def ==(oth)
@ident == oth.ident
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
end
else
r.push ptr.rule
end
end
unless r.empty?
if not s.empty? or r.size > 1
@conflict = true
end
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
def conflict?
@conflict
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
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
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]
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
end
def n_srconflicts
@srconf ? @srconf.size : 0
end
def n_rrconflicts
@rrconf ? @rrconf.size : 0
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
attr_reader :ident
attr_reader :symbol
attr_reader :from_state
attr_reader :to_state
def inspect
"(#{@from_state.ident}-#{@symbol}->#{@to_state.ident})"
end
end
# LALR item. A set of rule and its lookahead tokens.
class Item
def initialize(rule, la)
@rule = rule
@la = la
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
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
end
def init
@grammar.each do |rule|
@reduce.push Reduce.new(rule)
end
@statetable.each do |state|
@shift.push Shift.new(state)
end
@accept = Accept.new
@error = Error.new
end
def reduce_n
@reduce.size
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
end
def each_reduce(&block)
@reduce.each(&block)
end
def shift_n
@shift.size
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"
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
def inspect
"<shift #{@goto_state.ident}>"
end
end
class Reduce
def initialize(rule)
@rule = rule
@refn = 0
end
attr_reader :rule
attr_reader :refn
def ruleid
@rule.ident
end
def inspect
"<reduce #{@rule.ident}>"
end
def incref
@refn += 1
end
def decref
@refn -= 1
raise 'racc: fatal: act.refn < 0' if @refn < 0
end
end
class Accept
def inspect
"<accept>"
end
end
class Error
def inspect
"<error>"
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
def to_s
sprintf('state %d: S/R conflict rule %d reduce and shift %s',
@stateid, @reduce.ruleid, @shift.to_s)
end
end
class RRconflict
def initialize(sid, high, low, tok)
@stateid = sid
@high_prec = high
@low_prec = low
@token = tok
end
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)
end
end
end
@@ -0,0 +1,311 @@
#--
#
#
#
# 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_relative 'parser'
module Racc
StateTransitionTable = Struct.new(:action_table,
:action_check,
:action_default,
:action_pointer,
:goto_table,
:goto_check,
:goto_default,
:goto_pointer,
:token_table,
:reduce_table,
:reduce_n,
:shift_n,
:nt_base,
:token_to_s_table,
:use_result_var,
:debug_parser)
class StateTransitionTable # reopen
def StateTransitionTable.generate(states)
StateTransitionTableGenerator.new(states).generate
end
def initialize(states)
super()
@states = states
@grammar = states.grammar
self.use_result_var = true
self.debug_parser = true
end
attr_reader :states
attr_reader :grammar
def parser_class
ParserClassGenerator.new(@states).generate
end
def token_value_table
h = {}
token_table().each do |sym, i|
h[sym.value] = i
end
h
end
end
class StateTransitionTableGenerator
def initialize(states)
@states = states
@grammar = states.grammar
end
def generate
t = StateTransitionTable.new(@states)
gen_action_tables t, @states
gen_goto_tables t, @grammar
t.token_table = token_table(@grammar)
t.reduce_table = reduce_table(@grammar)
t.reduce_n = @states.reduce_n
t.shift_n = @states.shift_n
t.nt_base = @grammar.nonterminal_base
t.token_to_s_table = @grammar.symbols.map {|sym| sym.to_s }
t
end
def reduce_table(grammar)
t = [0, 0, :racc_error]
grammar.each_with_index do |rule, idx|
next if idx == 0
t.push rule.size
t.push rule.target.ident
t.push(if rule.action.empty? # and @params.omit_action_call?
then :_reduce_none
else "_reduce_#{idx}".intern
end)
end
t
end
def token_table(grammar)
h = {}
grammar.symboltable.terminals.each do |t|
h[t] = t.ident
end
h
end
def gen_action_tables(t, states)
t.action_table = yytable = []
t.action_check = yycheck = []
t.action_default = yydefact = []
t.action_pointer = yypact = []
e1 = []
e2 = []
states.each do |state|
yydefact.push act2actid(state.defact)
if state.action.empty?
yypact.push nil
next
end
vector = []
state.action.each do |tok, act|
vector[tok.ident] = act2actid(act)
end
addent e1, vector, state.ident, yypact
end
set_table e1, e2, yytable, yycheck, yypact
end
def gen_goto_tables(t, grammar)
t.goto_table = yytable2 = []
t.goto_check = yycheck2 = []
t.goto_pointer = yypgoto = []
t.goto_default = yydefgoto = []
e1 = []
e2 = []
grammar.each_nonterminal do |tok|
tmp = []
# decide default
freq = Array.new(@states.size, 0)
@states.each do |state|
st = state.goto_table[tok]
if st
st = st.ident
freq[st] += 1
end
tmp[state.ident] = st
end
max = freq.max
if max > 1
default = freq.index(max)
tmp.map! {|i| default == i ? nil : i }
else
default = nil
end
yydefgoto.push default
# delete default value
tmp.pop until tmp.last or tmp.empty?
if tmp.compact.empty?
# only default
yypgoto.push nil
next
end
addent e1, tmp, (tok.ident - grammar.nonterminal_base), yypgoto
end
set_table e1, e2, yytable2, yycheck2, yypgoto
end
def addent(all, arr, chkval, ptr)
max = arr.size
min = nil
arr.each_with_index do |item, idx|
if item
min ||= idx
end
end
ptr.push(-7777) # mark
arr = arr[min...max]
all.push [arr, chkval, mkmapexp(arr), min, ptr.size - 1]
end
n = 2 ** 16
begin
Regexp.compile("a{#{n}}")
RE_DUP_MAX = n
rescue RegexpError
n /= 2
retry
end
def mkmapexp(arr)
i = ii = 0
as = arr.size
map = String.new
maxdup = RE_DUP_MAX
curr = nil
while i < as
ii = i + 1
if arr[i]
ii += 1 while ii < as and arr[ii]
curr = '-'
else
ii += 1 while ii < as and not arr[ii]
curr = '.'
end
offset = ii - i
if offset == 1
map << curr
else
while offset > maxdup
map << "#{curr}{#{maxdup}}"
offset -= maxdup
end
map << "#{curr}{#{offset}}" if offset > 1
end
i = ii
end
Regexp.compile(map, Regexp::NOENCODING)
end
def set_table(entries, dummy, tbl, chk, ptr)
upper = 0
map = '-' * 10240
# sort long to short
entries.sort_by!.with_index {|a,i| [-a[0].size, i] }
entries.each do |arr, chkval, expr, min, ptri|
if upper + arr.size > map.size
map << '-' * (arr.size + 1024)
end
idx = map.index(expr)
ptr[ptri] = idx - min
arr.each_with_index do |item, i|
if item
i += idx
tbl[i] = item
chk[i] = chkval
map[i] = ?o
end
end
upper = idx + arr.size
end
end
def act2actid(act)
case act
when Shift then act.goto_id
when Reduce then -act.ruleid
when Accept then @states.shift_n
when Error then @states.reduce_n * -1
else
raise "racc: fatal: wrong act type #{act.class} in action table"
end
end
end
class ParserClassGenerator
def initialize(states)
@states = states
@grammar = states.grammar
end
def generate
table = @states.state_transition_table
c = Class.new(::Racc::Parser)
c.const_set :Racc_arg, [table.action_table,
table.action_check,
table.action_default,
table.action_pointer,
table.goto_table,
table.goto_check,
table.goto_default,
table.goto_pointer,
table.nt_base,
table.reduce_table,
table.token_value_table,
table.shift_n,
table.reduce_n,
false]
c.const_set :Racc_token_to_s_table, table.token_to_s_table
c.const_set :Racc_debug_parser, true
define_actions c
c
end
private
def define_actions(c)
c.module_eval "def _reduce_none(vals, vstack) vals[0] end"
@grammar.each do |rule|
if rule.action.empty?
c.alias_method("_reduce_#{rule.ident}", :_reduce_none)
else
c.define_method("_racc_action_#{rule.ident}", &rule.action.proc)
c.module_eval(<<-End, __FILE__, __LINE__ + 1)
def _reduce_#{rule.ident}(vals, vstack)
_racc_action_#{rule.ident}(*vals)
end
End
end
end
end
end
end # module Racc
+5
View File
@@ -0,0 +1,5 @@
require_relative '../racc'
require_relative 'parser'
require_relative 'grammarfileparser'
require_relative 'parserfilegenerator'
require_relative 'logfilegenerator'