├── .gitattributes
├── test
├── assets
│ ├── norule.y
│ ├── firstline.y
│ ├── noend.y
│ ├── unterm.y
│ ├── expect.y
│ ├── useless.y
│ ├── error_on_expect_mismatch.y
│ ├── rrconf.y
│ ├── conf.y
│ ├── nullbug2.y
│ ├── group.y
│ ├── many.y
│ ├── many1.y
│ ├── optional.y
│ ├── nullbug1.y
│ ├── ifelse.y
│ ├── digraph.y
│ ├── normal.y
│ ├── newsyn.y
│ ├── nonass.y
│ ├── yyerr.y
│ ├── error_recovery.y
│ ├── syntax.y
│ ├── percent.y
│ ├── err.y
│ ├── frozen.y
│ ├── journey.y
│ ├── recv.y
│ ├── scan.y
│ ├── ichk.y
│ ├── echk.y
│ ├── opt.y
│ ├── chk.y
│ ├── php_serialization.y
│ ├── cadenza.y
│ ├── nokogiri-css.y
│ ├── namae.y
│ ├── twowaysql.y
│ └── liquor.y
├── scandata
│ ├── gvar
│ ├── normal
│ ├── brace
│ ├── slash
│ └── percent
├── infini.y
├── lib
│ └── helper.rb
├── start.y
├── test_parser_text.rb
├── regress
│ ├── README.txt
│ ├── optional
│ ├── group
│ ├── many
│ ├── many1
│ ├── journey
│ ├── frozen
│ └── php_serialization
├── test_grammar_file_parser.rb
├── bench.y
├── src.intp
├── testscanner.rb
├── test_scan_y.rb
├── test_chk_y.rb
├── test_sample.rb
├── test_grammar.rb
└── case.rb
├── rakelib
└── epoch.rake
├── TODO
├── lib
├── racc
│ ├── static.rb
│ ├── exception.rb
│ ├── info.rb
│ ├── compat.rb
│ ├── sourcetext.rb
│ ├── debugflags.rb
│ ├── iset.rb
│ ├── logfilegenerator.rb
│ └── statetransitiontable.rb
└── racc.rb
├── ext
└── racc
│ └── cparse
│ └── extconf.rb
├── .gitignore
├── sample
├── lalr.y
├── conflict.y
├── yyerr.y
├── syntax.y
├── array2.y
├── array.y
├── lists.y
├── hash.y
├── calc.y
└── calc-ja.y
├── Gemfile
├── .github
├── dependabot.yml
└── workflows
│ ├── gh-pages.yml
│ ├── push_gem.yml
│ └── test.yml
├── doc
├── ja
│ ├── index.ja.html
│ ├── debug.ja.rdoc
│ ├── racc.ja.rhtml
│ ├── command.ja.html
│ ├── parser.ja.rdoc
│ └── grammar.ja.rdoc
└── en
│ ├── racc.en.rhtml
│ ├── grammar.en.rdoc
│ └── grammar2.en.rdoc
├── README.ja.rdoc
├── BSDL
├── README.rdoc
├── racc.gemspec
├── COPYING
├── Rakefile
└── bin
└── racc
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.rb ident
2 | *.rdoc ident
3 |
--------------------------------------------------------------------------------
/test/assets/norule.y:
--------------------------------------------------------------------------------
1 |
2 | class A
3 | rule
4 | end
5 |
--------------------------------------------------------------------------------
/test/assets/firstline.y:
--------------------------------------------------------------------------------
1 | class T
2 | rule
3 | a: A B C
4 | end
5 |
--------------------------------------------------------------------------------
/test/scandata/gvar:
--------------------------------------------------------------------------------
1 | { $' $" $& $-a $/ $\ $( $1 $2 $3 $? $-i }
2 |
--------------------------------------------------------------------------------
/test/assets/noend.y:
--------------------------------------------------------------------------------
1 | class MyParser
2 | rule
3 | input: A B C
4 | end
5 |
--------------------------------------------------------------------------------
/test/infini.y:
--------------------------------------------------------------------------------
1 |
2 | class I
3 |
4 | rule
5 |
6 | list: list X
7 |
8 | end
9 |
--------------------------------------------------------------------------------
/test/assets/unterm.y:
--------------------------------------------------------------------------------
1 | # unterminated action
2 |
3 | class A
4 | rule
5 | a: A {
6 |
--------------------------------------------------------------------------------
/test/scandata/normal:
--------------------------------------------------------------------------------
1 | {
2 | # comment
3 | result = "string".match(/regexp/)[0]
4 | }
5 |
--------------------------------------------------------------------------------
/test/assets/expect.y:
--------------------------------------------------------------------------------
1 | class E
2 | expect 1
3 | rule
4 | list: inlist inlist
5 | inlist:
6 | | A
7 | end
8 |
--------------------------------------------------------------------------------
/test/scandata/brace:
--------------------------------------------------------------------------------
1 | { {
2 | } { } {
3 | { { { } } }
4 | { { { {} } } }
5 | {} {} {}
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/lib/helper.rb:
--------------------------------------------------------------------------------
1 | require_relative '../case'
2 | require 'core_assertions'
3 |
4 | Racc::TestCase.include Test::Unit::CoreAssertions
5 |
--------------------------------------------------------------------------------
/test/assets/useless.y:
--------------------------------------------------------------------------------
1 |
2 |
3 | class A
4 | token A B C X
5 | rule
6 |
7 | targ : A list B
8 | | A B C
9 |
10 | list: list X
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/test/assets/error_on_expect_mismatch.y:
--------------------------------------------------------------------------------
1 | class E
2 | expect 0
3 | error_on_expect_mismatch
4 | rule
5 | list: inlist inlist
6 | inlist:
7 | | A
8 | end
9 |
--------------------------------------------------------------------------------
/rakelib/epoch.rake:
--------------------------------------------------------------------------------
1 | task "build" => "date_epoch"
2 |
3 | task "date_epoch" do
4 | ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp
5 | end
6 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | * check 'error' token handling.
2 | * interactive transition table monitor.
3 | * support backtracking.
4 | * output Ruby extension library?
5 | * LL(k)? (But it should not be called Racc)
6 |
--------------------------------------------------------------------------------
/lib/racc/static.rb:
--------------------------------------------------------------------------------
1 | require_relative '../racc'
2 | require_relative 'parser'
3 | require_relative 'grammarfileparser'
4 | require_relative 'parserfilegenerator'
5 | require_relative 'logfilegenerator'
6 |
--------------------------------------------------------------------------------
/test/assets/rrconf.y:
--------------------------------------------------------------------------------
1 | # 1 s/r conflict and 1 r/r conflict
2 |
3 | class A
4 | rule
5 |
6 | target: a
7 |
8 | a :
9 | | a list
10 |
11 | list :
12 | | list ITEM
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/test/assets/conf.y:
--------------------------------------------------------------------------------
1 |
2 | class A
3 | rule
4 |
5 | a: A c C expr;
6 |
7 | b: A B; # useless
8 |
9 | c: A;
10 | c: A;
11 |
12 | expr: expr '+' expr
13 | expr: expr '-' expr
14 | expr: NUMBER
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/lib/racc.rb:
--------------------------------------------------------------------------------
1 | require_relative 'racc/compat'
2 | require_relative 'racc/debugflags'
3 | require_relative 'racc/grammar'
4 | require_relative 'racc/state'
5 | require_relative 'racc/exception'
6 | require_relative 'racc/info'
7 |
--------------------------------------------------------------------------------
/ext/racc/cparse/extconf.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | #
3 |
4 | require 'mkmf'
5 | require_relative '../../../lib/racc/info'
6 |
7 | $defs << "-D""RACC_INFO_VERSION=#{Racc::VERSION}"
8 | create_makefile 'racc/cparse'
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bundle
2 | *.o
3 | *.so
4 | *.swp
5 | /pkg
6 | Makefile
7 | lib/racc/parser-text.rb
8 | tags
9 | tmp
10 | target
11 | lib/java/racc/cparse-jruby.jar
12 | lib/racc/cparse-jruby.jar
13 | Gemfile.lock
14 | _site/
15 |
--------------------------------------------------------------------------------
/sample/lalr.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # This is LALR grammar, and not LL/SLR.
4 |
5 | class A
6 | rule
7 | A : L '=' E
8 |
9 | L : i
10 | | R '^' i
11 |
12 | E : E '+' R
13 | | R
14 | | '@' L
15 |
16 | R : i
17 | end
18 |
--------------------------------------------------------------------------------
/test/assets/nullbug2.y:
--------------------------------------------------------------------------------
1 | #
2 | # number of conflicts must be ZERO.
3 | #
4 |
5 | class A
6 | rule
7 | targ: operation voidhead
8 | | variable
9 |
10 | voidhead : void B
11 | void:
12 |
13 | operation: A
14 | variable : A
15 | end
16 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | group :development do
4 | gem "racc"
5 | gem "rake", "13.3.0"
6 | gem "rake-compiler", "1.3.0"
7 | gem "rdoc", "6.5.1.1"
8 | gem "test-unit", "3.7.0"
9 | gem "test-unit-ruby-core", "1.0.10"
10 | end
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'github-actions'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 | - package-ecosystem: 'bundler'
8 | directory: '/'
9 | schedule:
10 | interval: 'weekly'
11 |
--------------------------------------------------------------------------------
/test/start.y:
--------------------------------------------------------------------------------
1 | class S
2 |
3 | start st
4 |
5 | rule
6 |
7 | n: D { result = 'no' }
8 | st : A B C n { result = 'ok' }
9 |
10 | end
11 |
12 | ---- inner
13 |
14 | def parse
15 | do_parse
16 | end
17 |
18 | ---- footer
19 |
20 | S.new.parse == 'ok' or raise 'start stmt not worked'
21 |
--------------------------------------------------------------------------------
/sample/conflict.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Example of conflicted grammar.
4 | # This grammar contains 1 Shift/Reduce conflict and 1 Reduce/Reduce conflict.
5 |
6 | class A
7 | rule
8 | target : outer
9 |
10 | outer :
11 | | outer inner
12 |
13 | inner :
14 | | inner ITEM
15 | end
16 |
--------------------------------------------------------------------------------
/test/scandata/slash:
--------------------------------------------------------------------------------
1 | {
2 | # here's many '/'s
3 | i = 5/1 # div
4 | re = /regex/ # regexp
5 | i /= 5 # div
6 | result = 5 / 1 # div
7 | result = 5/ 1 # div
8 | call(/regex/) # regexp
9 | call /regex/ # regexp
10 | }
11 |
--------------------------------------------------------------------------------
/test/assets/group.y:
--------------------------------------------------------------------------------
1 | class MyParser
2 | rule
3 | stmt: ('a')
4 | end
5 | ---- header
6 | require 'strscan'
7 | ---- inner
8 | def parse(str)
9 | @ss = StringScanner.new(str)
10 | do_parse
11 | end
12 | def next_token
13 | @ss.skip(/\\s+/)
14 | token = @ss.scan(/\\S+/) and [token, token]
15 | end
16 |
--------------------------------------------------------------------------------
/test/assets/many.y:
--------------------------------------------------------------------------------
1 | class MyParser
2 | rule
3 | stmt: 'abc'*
4 | end
5 | ---- header
6 | require 'strscan'
7 | ---- inner
8 | def parse(str)
9 | @ss = StringScanner.new(str)
10 | do_parse
11 | end
12 | def next_token
13 | @ss.skip(/\\s+/)
14 | token = @ss.scan(/\\S+/) and [token, token]
15 | end
16 |
--------------------------------------------------------------------------------
/test/assets/many1.y:
--------------------------------------------------------------------------------
1 | class MyParser
2 | rule
3 | stmt: 'abc'+
4 | end
5 | ---- header
6 | require 'strscan'
7 | ---- inner
8 | def parse(str)
9 | @ss = StringScanner.new(str)
10 | do_parse
11 | end
12 | def next_token
13 | @ss.skip(/\\s+/)
14 | token = @ss.scan(/\\S+/) and [token, token]
15 | end
16 |
--------------------------------------------------------------------------------
/test/assets/optional.y:
--------------------------------------------------------------------------------
1 | class MyParser
2 | rule
3 | stmt: 'abc'?
4 | end
5 | ---- header
6 | require 'strscan'
7 | ---- inner
8 | def parse(str)
9 | @ss = StringScanner.new(str)
10 | do_parse
11 | end
12 | def next_token
13 | @ss.skip(/\\s+/)
14 | token = @ss.scan(/\\S+/) and [token, token]
15 | end
16 |
--------------------------------------------------------------------------------
/test/assets/nullbug1.y:
--------------------------------------------------------------------------------
1 | #
2 | # number of conflicts must be ZERO.
3 | #
4 |
5 | class T
6 |
7 | rule
8 |
9 | targ : dummy
10 | | a b c
11 |
12 | dummy : V v
13 |
14 | V : E e
15 | | F f
16 | |
17 | ;
18 |
19 | E :
20 | ;
21 |
22 | F :
23 | ;
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/test/assets/ifelse.y:
--------------------------------------------------------------------------------
1 | class C::Parser
2 | token tSOMETHING
3 | rule
4 | statement
5 | : tSOMETHING
6 | | 'if' statement 'then' statement
7 | | 'if' statement 'then' statement 'else' statement
8 | ;
9 |
10 | dummy
11 | : tSOMETHING '+' tSOMETHING
12 | | tSOMETHING '-' tSOMETHING
13 | ;
14 |
15 |
--------------------------------------------------------------------------------
/lib/racc/exception.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | module Racc
14 | class Error < StandardError; end
15 | class CompileError < Error; end
16 | end
17 |
--------------------------------------------------------------------------------
/test/test_parser_text.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(__dir__, 'case'))
2 |
3 | module Racc
4 | class TestRaccParserText < TestCase
5 | def test_parser_text_require
6 | assert_not_match(/^require/, Racc::PARSER_TEXT)
7 | assert_in_out_err(%W[-I#{LIB_DIR} -rracc/parser-text -e$:.clear -eeval(Racc::PARSER_TEXT)])
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/doc/ja/index.ja.html:
--------------------------------------------------------------------------------
1 |
Racc ユーザマニュアル
2 | バージョン 1.4 対応
3 |
11 |
--------------------------------------------------------------------------------
/lib/racc/info.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | #--
3 | #
4 | #
5 | #
6 | # Copyright (c) 1999-2006 Minero Aoki
7 | #
8 | # This program is free software.
9 | # You can distribute/modify this program under the same terms of ruby.
10 | # see the file "COPYING".
11 | #
12 | #++
13 |
14 | module Racc
15 | VERSION = '1.8.1'
16 | Version = VERSION
17 | Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
18 | end
19 |
--------------------------------------------------------------------------------
/test/assets/digraph.y:
--------------------------------------------------------------------------------
1 | # ? detect digraph bug
2 |
3 | class P
4 | token A B C D
5 | rule
6 | target : a b c d
7 | a : A
8 | |
9 | b : B
10 | |
11 | c : C
12 | |
13 | d : D
14 | |
15 | end
16 |
17 | ---- inner
18 |
19 | def parse
20 | do_parse
21 | end
22 |
23 | def next_token
24 | [false, '$']
25 | end
26 |
27 | ---- footer
28 |
29 | P.new.parse
30 |
--------------------------------------------------------------------------------
/test/assets/normal.y:
--------------------------------------------------------------------------------
1 |
2 | class Testp
3 |
4 | convert
5 | A '2'
6 | B '3'
7 | end
8 |
9 | prechigh
10 | left B
11 | preclow
12 |
13 | rule
14 |
15 | /* comment */
16 | target: A B C nonterminal { action "string" == /regexp/o
17 | 1 /= 3 }
18 | ; # comment
19 |
20 | nonterminal: A '+' B = A;
21 |
22 | /* end */
23 | end
24 |
25 | ---- driver
26 |
27 | # driver is old name
28 |
--------------------------------------------------------------------------------
/test/regress/README.txt:
--------------------------------------------------------------------------------
1 | These files are "known-good" compiler output, generated from a stable version of
2 | Racc. Whenever Racc is refactored, or changes are made which should not affect the
3 | compiler output, running "rake test" checks that the compiler output is exactly
4 | the same as these files.
5 |
6 | If a change is made which *should* change the compiler output, these files will
7 | have to be regenerated from the source in test/assets, and the results committed.
8 |
--------------------------------------------------------------------------------
/test/assets/newsyn.y:
--------------------------------------------------------------------------------
1 |
2 | class A
3 |
4 | preclow
5 | left preclow prechigh right left nonassoc token
6 | right preclow prechigh right left nonassoc token
7 | nonassoc preclow prechigh right left nonassoc token
8 | prechigh
9 |
10 | convert
11 | left 'a'
12 | right 'b'
13 | preclow 'c'
14 | nonassoc 'd'
15 | preclow 'e'
16 | prechigh 'f'
17 | end
18 |
19 | rule
20 |
21 | left: right nonassoc preclow prechigh
22 |
23 | right: A B C
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/test/test_grammar_file_parser.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(__dir__, 'case'))
2 |
3 | module Racc
4 | class TestGrammarFileParser < TestCase
5 | def test_parse
6 | file = File.join(ASSET_DIR, 'yyerr.y')
7 |
8 | debug_flags = Racc::DebugFlags.parse_option_string('o')
9 | assert debug_flags.status_logging
10 |
11 | parser = Racc::GrammarFileParser.new(debug_flags)
12 | parser.parse(File.read(file), File.basename(file))
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/scandata/percent:
--------------------------------------------------------------------------------
1 | {
2 | 3 % 5 # mod
3 | 3%5 # mod
4 | 3% 5 # mod
5 | i % 5 # mod
6 | i%5 # mod
7 | i% 5 # mod
8 | call %{str} # string
9 | call(%{str}) # string
10 | %q{string} # string
11 | %Q{string} # string
12 | %r{string} # string
13 | %w(array) # array
14 | %x{array} # command string
15 | %{string} # string
16 | %_string_ # string
17 | %/string/ # regexp
18 | }
19 |
--------------------------------------------------------------------------------
/test/assets/nonass.y:
--------------------------------------------------------------------------------
1 | #
2 | # nonassoc test
3 | #
4 |
5 | class P
6 |
7 | preclow
8 | nonassoc N
9 | left P
10 | prechigh
11 |
12 | rule
13 |
14 | target : exp
15 | exp : exp N exp
16 | | exp P exp
17 | | T
18 |
19 | end
20 |
21 | ---- inner
22 |
23 | def parse
24 | @src = [[:T,'T'], [:N,'N'], [:T,'T'], [:N,'N'], [:T,'T']]
25 | do_parse
26 | end
27 |
28 | def next_token
29 | @src.shift
30 | end
31 |
32 | ---- footer
33 |
34 | begin
35 | P.new.parse
36 | rescue ParseError
37 | exit 0
38 | else
39 | $stderr.puts 'parse error not raised: nonassoc not work'
40 | exit 1
41 | end
42 |
--------------------------------------------------------------------------------
/test/bench.y:
--------------------------------------------------------------------------------
1 | class BenchmarkParser
2 |
3 | rule
4 |
5 | target: a a a a a a a a a a;
6 | a: b b b b b b b b b b;
7 | b: c c c c c c c c c c;
8 | c: d d d d d d d d d d;
9 | d: e e e e e e e e e e;
10 |
11 | end
12 |
13 | ---- inner
14 |
15 | def initialize
16 | @old = [ :e, 'e' ]
17 | @i = 0
18 | end
19 |
20 | def next_token
21 | return [false, '$'] if @i >= 10_0000
22 | @i += 1
23 | @old
24 | end
25 |
26 | def parse
27 | do_parse
28 | end
29 |
30 | ---- footer
31 |
32 | require 'benchmark'
33 |
34 | Benchmark.bm do |x|
35 | x.report { BenchmarkParser.new.parse }
36 | end
37 |
--------------------------------------------------------------------------------
/test/assets/yyerr.y:
--------------------------------------------------------------------------------
1 | #
2 | # yyerror/yyerrok/yyaccept test
3 | #
4 |
5 | class A
6 | rule
7 |
8 | target: a b c
9 |
10 | a:
11 | {
12 | yyerror
13 | raise ArgumentError, "yyerror failed"
14 | }
15 | | error
16 |
17 | b:
18 | {
19 | yyerrok
20 | }
21 |
22 | c:
23 | {
24 | yyaccept
25 | raise ArgumentError, "yyaccept failed"
26 | }
27 |
28 | end
29 |
30 | ---- inner
31 |
32 | def parse
33 | do_parse
34 | end
35 |
36 | def next_token
37 | [false, '$end']
38 | end
39 |
40 | def on_error( *args )
41 | $stderr.puts "on_error called: args=#{args.inspect}"
42 | end
43 |
44 | ---- footer
45 |
46 | A.new.parse
47 |
--------------------------------------------------------------------------------
/lib/racc/compat.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | unless Object.method_defined?(:__send)
14 | class Object
15 | alias __send __send__
16 | end
17 | end
18 |
19 | unless Object.method_defined?(:__send!)
20 | class Object
21 | alias __send! __send__
22 | end
23 | end
24 |
25 | unless Array.method_defined?(:map!)
26 | class Array
27 | if Array.method_defined?(:collect!)
28 | alias map! collect!
29 | else
30 | alias map! filter
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/sample/yyerr.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Test grammar file for error handling.
4 |
5 | class A
6 | rule
7 |
8 | target: a b c
9 |
10 | a :
11 | {
12 | yyerror
13 | raise ArgumentError, "yyerror failed"
14 | }
15 | | error
16 |
17 | b :
18 | {
19 | yyerrok
20 | }
21 |
22 | c :
23 | {
24 | yyaccept
25 | raise "yyaccept failed"
26 | }
27 |
28 | end
29 |
30 | ---- inner
31 |
32 | def parse
33 | do_parse
34 | end
35 |
36 | def next_token
37 | [false, '$end']
38 | end
39 |
40 | def on_error(*args)
41 | $stderr.puts "on_error called: args=#{args.inspect}"
42 | end
43 |
44 | ---- footer
45 |
46 | A.new.parse
47 |
--------------------------------------------------------------------------------
/test/src.intp:
--------------------------------------------------------------------------------
1 | def assert( no, cond )
2 | if cond then
3 | else
4 | raise( 'assert ' + to_s(no) + ' failed' )
5 | end
6 | end
7 |
8 | assert( 1, concat(concat(concat('str=', 'a'), "b"), 'c') == 'str=abc' )
9 | assert( 2, 'operator' + ' ok' == 'operator ok' )
10 | assert( 3, 1 + 1 == 2 )
11 | assert( 4, 4 * 1 + 10 * 1 == 14 )
12 |
13 | if true then
14 | assert( 5, true )
15 | else
16 | assert( 6, false )
17 | end
18 |
19 | i = 1
20 | while i == 1 do
21 | i = false
22 | end
23 | assert( 7, i == false )
24 | assert( 8, nil == nil )
25 |
26 | def func
27 | assert( 9, true )
28 | end
29 | func
30 |
31 | def argfunc( str )
32 | assert( 10, str == 'ok' )
33 | end
34 | argfunc 'ok'
35 |
--------------------------------------------------------------------------------
/lib/racc/sourcetext.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | module Racc
14 |
15 | class SourceText
16 | def initialize(text, filename, lineno)
17 | @text = text
18 | @filename = filename
19 | @lineno = lineno
20 | end
21 |
22 | attr_reader :text
23 | attr_reader :filename
24 | attr_reader :lineno
25 |
26 | def to_s
27 | "#"
28 | end
29 |
30 | def location
31 | "#{@filename}:#{@lineno}"
32 | end
33 | end
34 |
35 | end
36 |
--------------------------------------------------------------------------------
/test/assets/error_recovery.y:
--------------------------------------------------------------------------------
1 | # Regression test case for the bug discussed here:
2 | # https://github.com/whitequark/parser/issues/93
3 | # In short, a Racc-generated parser could go into an infinite loop when
4 | # attempting error recovery at EOF
5 |
6 | class InfiniteLoop
7 |
8 | rule
9 |
10 | stmts: stmt
11 | | error stmt
12 |
13 | stmt: '%' stmt
14 |
15 | end
16 |
17 | ---- inner
18 |
19 | def parse
20 | @errors = []
21 | do_parse
22 | end
23 |
24 | def next_token
25 | nil
26 | end
27 |
28 | def on_error(error_token, error_value, value_stack)
29 | # oh my, an error
30 | @errors << [error_token, error_value]
31 | end
32 |
33 | ---- footer
34 |
35 | InfiniteLoop.new.parse
36 |
--------------------------------------------------------------------------------
/test/assets/syntax.y:
--------------------------------------------------------------------------------
1 | #
2 | # racc syntax checker
3 | #
4 |
5 | class M1::M2::ParserClass < S1::S2::SuperClass
6 |
7 | token A
8 | | B C
9 |
10 | convert
11 | A '5'
12 | end
13 |
14 | prechigh
15 | left B
16 | preclow
17 |
18 | start target
19 |
20 | expect 0
21 |
22 | rule
23 |
24 | target: A B C
25 | {
26 | print 'abc'
27 | }
28 | | B C A
29 | | C B A
30 | {
31 | print 'cba'
32 | }
33 | | cont
34 |
35 | cont : A c2 B c2 C
36 |
37 | c2 : C C C C C
38 |
39 | end
40 |
41 | ---- inner
42 |
43 | junk code !!!!
44 |
45 | kjaljlajrlaolanbla /// %%% (*((( token rule
46 | akiurtlajluealjflaj @@@@ end end end end __END__
47 | laieu2o879urkq96ga(Q#*&%Q#
48 | #&lkji END
49 |
50 | q395q?/// liutjqlkr7
51 |
--------------------------------------------------------------------------------
/test/assets/percent.y:
--------------------------------------------------------------------------------
1 | class ScannerChecker
2 | rule
3 | target: A
4 | {
5 | i = 7
6 | i %= 4
7 | raise 'assert failed' unless i == 3
8 | tmp = %-This is percent string.-
9 | raise 'assert failed' unless tmp == 'This is percent string.'
10 | a = 5; b = 3
11 | assert_equal(2,(a%b)) #A
12 | # assert_equal(2,(a %b)) # is %-string
13 | assert_equal(2,(a% b)) #B
14 | assert_equal(2,(a % b)) #C
15 | }
16 | end
17 |
18 | ---- inner ----
19 |
20 | def parse
21 | @q = [[:A, 'A'], [false, '$']]
22 | do_parse
23 | end
24 |
25 | def next_token
26 | @q.shift
27 | end
28 |
29 | def assert_equal( expect, real )
30 | raise "expect #{expect.inspect} but #{real.inspect}" unless expect == real
31 | end
32 |
33 | ---- footer ----
34 |
35 | parser = ScannerChecker.new.parse
36 |
--------------------------------------------------------------------------------
/sample/syntax.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Racc syntax checker. This grammar file generates
4 | # invalid ruby program, you cannot run this parser.
5 |
6 | class P
7 | token A B C
8 |
9 | convert
10 | A '5'
11 | end
12 |
13 | prechigh
14 | left B
15 | preclow
16 |
17 | options omit_action_call
18 |
19 | start target
20 | rule
21 | target: A B C
22 | {
23 | print 'abc'
24 | }
25 | | B C A
26 | | C B A
27 | {
28 | print 'cba'
29 | }
30 | | cont
31 |
32 | cont : A c2 B c2 C
33 |
34 | c2 : C C C C C
35 | end
36 |
37 | ---- inner
38 |
39 | junk code !!!!
40 |
41 | kjaljlajrlaolanbla /// %%% (*((( token rule
42 | akiurtlajluealjflaj @@@@ end end end end __END__
43 | laieu2o879urkq96ga(Q#*&%Q#
44 | #&lkji END
45 |
46 | q395q?/// liutjqlkr7
47 |
--------------------------------------------------------------------------------
/test/assets/err.y:
--------------------------------------------------------------------------------
1 |
2 | class ErrTestp
3 |
4 | rule
5 |
6 | target: lines
7 | ;
8 |
9 | lines: line
10 | | lines line
11 | ;
12 |
13 | line: A B C D E
14 | | error E
15 | ;
16 |
17 | end
18 |
19 | ---- inner
20 |
21 | def initialize
22 | @yydebug = false
23 | @q = [
24 | [:A, 'a'],
25 | # [:B, 'b'],
26 | [:C, 'c'],
27 | [:D, 'd'],
28 | [:E, 'e'],
29 |
30 | [:A, 'a'],
31 | [:B, 'b'],
32 | [:C, 'c'],
33 | [:D, 'd'],
34 | [:E, 'e'],
35 |
36 | [:A, 'a'],
37 | [:B, 'b'],
38 | # [:C, 'c'],
39 | [:D, 'd'],
40 | [:E, 'e'],
41 | [false, nil]
42 | ]
43 | end
44 |
45 | def next_token
46 | @q.shift
47 | end
48 |
49 | def on_error( t, val, values )
50 | $stderr.puts "error on token '#{val}'(#{t})"
51 | end
52 |
53 | def parse
54 | do_parse
55 | end
56 |
57 | ---- footer
58 |
59 | p = ErrTestp.new
60 | p.parse
61 |
--------------------------------------------------------------------------------
/doc/ja/debug.ja.rdoc:
--------------------------------------------------------------------------------
1 | = パーサのデバッグ
2 |
3 | ここでは、Racc を使っていくうえで遭遇しそうな問題について書きます。
4 |
5 | == 文法ファイルがパースエラーになる
6 |
7 | エラーメッセージに出ている行番号のあたりを見て間違いを
8 | 探してください。ブロックを閉じる行でエラーになる場合は、
9 | どこかで開き括弧などを増やしてしまっている可能性が高いです。
10 |
11 | == なんたら conflict って言われた
12 |
13 | 一番ありがちで一番面倒な問題は衝突 (conflict) でしょう。
14 | 文法中に衝突があると、racc はコンパイル後に
15 | 「5 shift/reduce conflict」のようなメッセージを表示します。
16 | -v をつけると出力される .output ファイルからはさらに詳しい情報が得られます。
17 | それをどう使うか、とかそういうことに関しては、それなりの本を読んでください。
18 | とてもここに書けるような単純な話ではありません。
19 | 当然ながら『Ruby を 256 倍使うための本 無道編』(青木峰郎著)がお勧めです。
20 |
21 | == パーサは問題なく生成できたけど予想どおりに動かない
22 |
23 | racc に -g オプションをつけてパーサを出力すると、デバッグ用のコードが
24 | 付加されます。ここで、パーサクラスのインスタンス変数 @yydebug を true に
25 | しておいてから do_parse/yyparse を呼ぶと、デバッグ用メッセージが出力
26 | されます。パーサが動作する様子が直接見えますので、完全に現在の状態を
27 | 把握できます。これを見てどこがおかしいのかわかったらあとは直すだけ。
28 |
29 | == next_token に関して
30 |
31 | いまだ自分でも忘れることが多いのが
32 | 「送るトークンが尽きたら [false,なにか] を送る」ということです。
33 | ちなみに Racc 0.10.2 以降では一度 [false,なにか] を受け取ったら
34 | それ以上 next_token は呼ばないことが保証されています。
35 |
36 | 追記: 最近は [false,なにか] ではなく nil でもよいことになった。
37 |
--------------------------------------------------------------------------------
/doc/ja/racc.ja.rhtml:
--------------------------------------------------------------------------------
1 | % require 'makefile'
2 | % version = Makefile.get_parameter('Makefile', 'version')
3 | Racc
4 |
5 | 最新版 <%= version %>
6 | 種別 parser generator
7 | 形式 ruby script, ruby extension
8 | 必要環境 ruby (>=1.6)
9 | 配布条件 LGPL
10 |
11 |
16 |
17 | Ruby 用の LALR(1) パーザジェネレータです。
18 | 生成したパーサはそれなりに高速に動作します。
19 |
20 |
21 | Racc で生成したパーサは動作時にランタイムモジュールが必要です。
22 | Ruby 1.8 にはこのランタイムが最初から添付されているので
23 | 何も考えなくて大丈夫ですが、Ruby 1.6 以前を対象にするときは
24 | racc -E でパーサを生成する必要があります。
25 |
26 |
27 | なお、Racc 1.4.x のランタイムと Ruby 1.8 添付の Racc ランタイムは、
28 | ソースコード上では微妙に違いがありますが、完全に互換性があります。
29 |
30 |
31 | 状況
32 |
33 | もう基本的な部分は枯れているはずです。
34 | TODO はまだいくつかありますが、気持ちが他にいってるので
35 | 当分は大きく変更するつもりはありません。
36 |
37 |
--------------------------------------------------------------------------------
/test/assets/frozen.y:
--------------------------------------------------------------------------------
1 | class Journey::Parser
2 |
3 | token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
4 |
5 | rule
6 | expressions
7 | : expressions expression { result = Cat.new(val.first, val.last) }
8 | | expression { result = val.first }
9 | | or
10 | ;
11 | expression
12 | : terminal
13 | | group
14 | | star
15 | ;
16 | group
17 | : LPAREN expressions RPAREN { result = Group.new(val[1]) }
18 | ;
19 | or
20 | : expressions OR expression { result = Or.new([val.first, val.last]) }
21 | ;
22 | star
23 | : STAR { result = Star.new(Symbol.new(val.last)) }
24 | ;
25 | terminal
26 | : symbol
27 | | literal
28 | | slash
29 | | dot
30 | ;
31 | slash
32 | : SLASH { result = Slash.new('/') }
33 | ;
34 | symbol
35 | : SYMBOL { result = Symbol.new(val.first) }
36 | ;
37 | literal
38 | : LITERAL { result = Literal.new(val.first) }
39 | dot
40 | : DOT { result = Dot.new(val.first) }
41 | ;
42 |
43 | end
44 |
45 | ---- header
46 |
47 | require 'journey/parser_extras'
48 |
--------------------------------------------------------------------------------
/test/assets/journey.y:
--------------------------------------------------------------------------------
1 | class Journey::Parser
2 |
3 | token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
4 |
5 | rule
6 | expressions
7 | : expressions expression { result = Cat.new(val.first, val.last) }
8 | | expression { result = val.first }
9 | | or
10 | ;
11 | expression
12 | : terminal
13 | | group
14 | | star
15 | ;
16 | group
17 | : LPAREN expressions RPAREN { result = Group.new(val[1]) }
18 | ;
19 | or
20 | : expressions OR expression { result = Or.new([val.first, val.last]) }
21 | ;
22 | star
23 | : STAR { result = Star.new(Symbol.new(val.last)) }
24 | ;
25 | terminal
26 | : symbol
27 | | literal
28 | | slash
29 | | dot
30 | ;
31 | slash
32 | : SLASH { result = Slash.new('/') }
33 | ;
34 | symbol
35 | : SYMBOL { result = Symbol.new(val.first) }
36 | ;
37 | literal
38 | : LITERAL { result = Literal.new(val.first) }
39 | dot
40 | : DOT { result = Dot.new(val.first) }
41 | ;
42 |
43 | end
44 |
45 | ---- header
46 |
47 | require 'journey/parser_extras'
48 |
--------------------------------------------------------------------------------
/sample/array2.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Converting Array-like string into Ruby's Array, version 2.
4 | # This grammar uses no_result_var.
5 |
6 | class ArrayParser2
7 | options no_result_var
8 | rule
9 | array : '[' contents ']' { val[1] }
10 | | '[' ']' { [] }
11 |
12 | contents: ITEM { val }
13 | | contents ',' ITEM { val[0].push val[2]; val[0] }
14 | end
15 |
16 | ---- inner
17 |
18 | def parse(str)
19 | @str = str
20 | yyparse self, :scan
21 | end
22 |
23 | def scan
24 | str = @str.strip
25 | until str.empty?
26 | case str
27 | when /\A\s+/
28 | str = $'
29 | when /\A\w+/
30 | yield [:ITEM, $&]
31 | str = $'
32 | else
33 | c = str[0,1]
34 | yield [c, c]
35 | str = str[1..-1]
36 | end
37 | end
38 | yield [false, '$'] # is optional from Racc 1.3.7
39 | end
40 |
41 | def next_token
42 | @q.shift
43 | end
44 |
45 | ---- footer
46 |
47 | if $0 == __FILE__
48 | src = <Racc
4 |
5 |
6 | Version <%= version %>
7 | Type Parser Generator
8 | Format Ruby script + Ruby extension
9 | Requirement ruby (>=1.6)
10 | License LGPL
11 |
12 |
13 | -- Download (.tar.gz)
14 | -- Old Versions
15 | -- Online Manual
16 | --
17 |
18 |
19 |
20 | Racc (Ruby yACC) is an LALR(1) parser generator for Ruby.
21 | Version 1.4.x is stable release.
22 |
23 |
24 | Parsers generated by Racc requires "Racc Runtime Module".
25 | Ruby 1.8.x comes with this runtime.
26 | If you want to run your parsers with ruby 1.6.x,
27 | use "racc -E" command. For details, see online manual .
28 |
29 |
30 | Git
31 |
32 | You can use the latest version of Racc using git.
33 | To clone out a working copy, type:
34 |
35 |
36 | $ git clone https://github.com/ruby/racc.git
37 |
38 |
--------------------------------------------------------------------------------
/test/testscanner.rb:
--------------------------------------------------------------------------------
1 | #
2 | # racc scanner tester
3 | #
4 |
5 | require 'racc/raccs'
6 |
7 |
8 | class ScanError < StandardError; end
9 |
10 | def testdata( dir, argv )
11 | if argv.empty? then
12 | Dir.glob( dir + '/*' ) -
13 | Dir.glob( dir + '/*.swp' ) -
14 | [ dir + '/CVS' ]
15 | else
16 | argv.collect {|i| dir + '/' + i }
17 | end
18 | end
19 |
20 |
21 | if ARGV.delete '--print' then
22 | $raccs_print_type = true
23 | printonly = true
24 | else
25 | printonly = false
26 | end
27 |
28 | testdata( File.dirname($0) + '/scandata', ARGV ).each do |file|
29 | $stderr.print File.basename(file) + ': '
30 | begin
31 | ok = File.read(file)
32 | s = Racc::GrammarFileScanner.new( ok )
33 | sym, (val, _lineno) = s.scan
34 | if printonly then
35 | $stderr.puts
36 | $stderr.puts val
37 | next
38 | end
39 |
40 | val = '{' + val + "}\n"
41 | sym == :ACTION or raise ScanError, 'is not action!'
42 | val == ok or raise ScanError, "\n>>>\n#{ok}----\n#{val}<<<"
43 |
44 | $stderr.puts 'ok'
45 | rescue => err
46 | $stderr.puts 'fail (' + err.type.to_s + ')'
47 | $stderr.puts err.message
48 | $stderr.puts err.backtrace
49 | $stderr.puts
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/sample/array.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # convert Array-like string into Ruby's Array.
4 |
5 | class ArrayParser
6 |
7 | rule
8 |
9 | array : '[' contents ']'
10 | {
11 | result = val[1]
12 | }
13 | | '[' ']'
14 | {
15 | result = []
16 | }
17 |
18 | contents: ITEM
19 | {
20 | result = val
21 | }
22 | | contents ',' ITEM
23 | {
24 | result.push val[2]
25 | }
26 |
27 | ---- inner
28 |
29 | def parse(str)
30 | str = str.strip
31 | @q = []
32 | until str.empty?
33 | case str
34 | when /\A\s+/
35 | str = $'
36 | when /\A\w+/
37 | @q.push [:ITEM, $&]
38 | str = $'
39 | else
40 | c = str[0,1]
41 | @q.push [c, c]
42 | str = str[1..-1]
43 | end
44 | end
45 | @q.push [false, '$'] # is optional from Racc 1.3.7
46 | do_parse
47 | end
48 |
49 | def next_token
50 | @q.shift
51 | end
52 |
53 | ---- footer
54 |
55 | if $0 == __FILE__
56 | src = <" item
51 | {
52 | result = { val[0] => val[2] }
53 | }
54 | | hash_contents ',' item "=>" item
55 | {
56 | result[val[2]] = val[4]
57 | }
58 |
--------------------------------------------------------------------------------
/sample/hash.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Converting Hash-like string into Ruby's Hash.
4 |
5 | class HashParser
6 | options no_result_var
7 | rule
8 | hash : '{' contents '}' { val[1] }
9 | | '{' '}' { Hash.new }
10 |
11 | # Racc can handle string over 2 bytes.
12 | contents: IDENT '=>' IDENT { {val[0] => val[2]} }
13 | | contents ',' IDENT '=>' IDENT { val[0][val[2]] = val[4]; val[0] }
14 | end
15 |
16 | ---- inner
17 |
18 | def parse(str)
19 | @str = str
20 | yyparse self, :scan
21 | end
22 |
23 | private
24 |
25 | def scan
26 | str = @str
27 | until str.empty?
28 | case str
29 | when /\A\s+/
30 | str = $'
31 | when /\A\w+/
32 | yield [:IDENT, $&]
33 | str = $'
34 | when /\A=>/
35 | yield ['=>', '=>']
36 | str = $'
37 | else
38 | c = str[0,1]
39 | yield [c, c]
40 | str = str[1..-1]
41 | end
42 | end
43 | yield [false, '$'] # is optional from Racc 1.3.7
44 | end
45 |
46 | ---- footer
47 |
48 | if $0 == __FILE__
49 | src = < MyName,
52 | id => MyIdent
53 | }
54 | EOS
55 | puts 'Parsing (String):'
56 | print src
57 | puts
58 | puts 'Result (Ruby Object):'
59 | p HashParser.new.parse(src)
60 | end
61 |
--------------------------------------------------------------------------------
/BSDL:
--------------------------------------------------------------------------------
1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 | 1. Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22 | SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/sample/calc.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # Very simple calculator.
4 |
5 | class Calcp
6 | prechigh
7 | nonassoc UMINUS
8 | left '*' '/'
9 | left '+' '-'
10 | preclow
11 | rule
12 | target: exp
13 | | /* none */ { result = 0 }
14 |
15 | exp: exp '+' exp { result += val[2] }
16 | | exp '-' exp { result -= val[2] }
17 | | exp '*' exp { result *= val[2] }
18 | | exp '/' exp { result /= val[2] }
19 | | '(' exp ')' { result = val[1] }
20 | | '-' NUMBER =UMINUS { result = -val[1] }
21 | | NUMBER
22 | end
23 |
24 | ---- header
25 | #
26 | ---- inner
27 |
28 | def parse(str)
29 | @q = []
30 | until str.empty?
31 | case str
32 | when /\A\s+/
33 | when /\A\d+/
34 | @q.push [:NUMBER, $&.to_i]
35 | when /\A.|\n/o
36 | s = $&
37 | @q.push [s, s]
38 | end
39 | str = $'
40 | end
41 | @q.push [false, '$end']
42 | do_parse
43 | end
44 |
45 | def next_token
46 | @q.shift
47 | end
48 |
49 | ---- footer
50 |
51 | if $0 == __FILE__
52 | parser = Calcp.new
53 | puts
54 | puts 'type "Q" to quit.'
55 | puts
56 | while true
57 | puts
58 | print '? '
59 | str = gets.chop!
60 | break if /q/i =~ str
61 | begin
62 | puts "= #{parser.parse(str)}"
63 | rescue ParseError
64 | puts $!
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Racc documentation to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | workflow_dispatch:
7 |
8 | permissions:
9 | contents: read
10 | pages: write
11 | id-token: write
12 |
13 | concurrency:
14 | group: "pages"
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | build:
19 | runs-on: ubuntu-latest
20 | if: ${{ github.repository == 'ruby/racc' && !startsWith(github.event_name, 'pull') }}
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v6.0.1
24 | - name: Setup Ruby
25 | uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
26 | with:
27 | ruby-version: "3.3" # TODO: Temporarily avoids the error if 3.4 is specified. (https://github.com/ruby/racc/actions/runs/18798977070/job/53643662504)
28 | bundler-cache: true
29 | - name: Setup Pages
30 | id: pages
31 | uses: actions/configure-pages@v5
32 | - name: Build with Racc
33 | run: bundle exec rake docs
34 | - name: Upload artifact
35 | uses: actions/upload-pages-artifact@v4
36 |
37 | deploy:
38 | environment:
39 | name: github-pages
40 | url: ${{ steps.deployment.outputs.page_url }}
41 | runs-on: ubuntu-latest
42 | needs: build
43 | steps:
44 | - name: Deploy to GitHub Pages
45 | id: deployment
46 | uses: actions/deploy-pages@v4
47 |
--------------------------------------------------------------------------------
/sample/calc-ja.y:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # A simple calculator, version 2.
4 | # This file contains Japanese characters.
5 |
6 | class Calculator2
7 | prechigh
8 | nonassoc UMINUS
9 | left '*' '/'
10 | left '+' '-'
11 | preclow
12 | options no_result_var
13 | rule
14 | target : exp
15 | | /* none */ { 0 }
16 |
17 | exp : exp '+' exp { val[0] + val[2] }
18 | | exp '-' exp { val[0] - val[2] }
19 | | exp '*' exp { val[0] * val[2] }
20 | | exp '/' exp { val[0] / val[2] }
21 | | '(' exp ')' { val[1] }
22 | | '-' NUMBER =UMINUS { -(val[1]) }
23 | | NUMBER
24 | end
25 |
26 | ---- header
27 | #
28 | ---- inner
29 |
30 | def evaluate(str)
31 | @tokens = []
32 | until str.empty?
33 | case str
34 | when /\A\s+/
35 | ;
36 | when /\A\d+/
37 | @tokens.push [:NUMBER, $&.to_i]
38 | when /\A.|\n/
39 | s = $&
40 | @tokens.push [s, s]
41 | end
42 | str = $'
43 | end
44 | @tokens.push [false, '$']
45 | do_parse
46 | end
47 |
48 | def next_token
49 | @tokens.shift
50 | end
51 |
52 | ---- footer
53 |
54 | puts '超豪華電卓 2 号機'
55 | puts 'Q で終了します'
56 | calc = Calculator2.new
57 | while true
58 | print '>>> '; $stdout.flush
59 | str = $stdin.gets.strip
60 | break if /q/i =~ str
61 | begin
62 | p calc.evaluate(str)
63 | rescue ParseError
64 | puts 'parse error'
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/.github/workflows/push_gem.yml:
--------------------------------------------------------------------------------
1 | name: Publish gem to rubygems.org
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | push:
13 | if: github.repository == 'ruby/racc'
14 | runs-on: ubuntu-latest
15 |
16 | environment:
17 | name: rubygems.org
18 | url: https://rubygems.org/gems/racc
19 |
20 | permissions:
21 | contents: write
22 | id-token: write
23 |
24 | strategy:
25 | matrix:
26 | ruby: ["ruby", "jruby"]
27 |
28 | steps:
29 | - name: Harden Runner
30 | uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
31 | with:
32 | egress-policy: audit
33 |
34 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1
35 |
36 | - name: Set up Ruby
37 | uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
38 | with:
39 | bundler-cache: true
40 | ruby-version: ${{ matrix.ruby }}
41 |
42 | - name: Publish to RubyGems
43 | uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2
44 |
45 | - name: Create GitHub release
46 | run: |
47 | tag_name="$(git describe --tags --abbrev=0)"
48 | gh release create "${tag_name}" --verify-tag --generate-notes
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | if: matrix.ruby == 'ruby'
52 |
--------------------------------------------------------------------------------
/test/assets/recv.y:
--------------------------------------------------------------------------------
1 | # s/r 5, r/r 10
2 | class A
3 | rule
4 |
5 | content: RecvH received
6 | ;
7 |
8 | datetime: day
9 | ;
10 |
11 | msgid: '<' spec '>';
12 |
13 | day:
14 | | ATOM ','
15 | ;
16 |
17 | received: recvitem_list recvdatetime
18 | ;
19 |
20 | recvitem_list:
21 | | recvitem_list recvitem
22 | ;
23 |
24 | recvitem: by | via | with | for ;
25 |
26 | by:
27 | | BY domain
28 | ;
29 |
30 | via:
31 | | VIA ATOM
32 | ;
33 |
34 | with: WITH ATOM
35 | ;
36 |
37 | for:
38 | | FOR addr
39 | ;
40 |
41 | recvdatetime:
42 | | ';' datetime
43 | ;
44 |
45 | addr: mbox | group ;
46 |
47 | mboxes: mbox
48 | | mboxes ',' mbox
49 | ;
50 |
51 | mbox: spec
52 | | routeaddr
53 | | phrase routeaddr
54 | ;
55 |
56 | group: phrase ':' mboxes ';'
57 | ;
58 |
59 | routeaddr: '<' route spec '>'
60 | | '<' spec '>'
61 | ;
62 |
63 | route: at_domains ':' ;
64 |
65 | at_domains: '@' domain
66 | | at_domains ',' '@' domain
67 | ;
68 |
69 | spec: local '@' domain
70 | | local
71 | ;
72 |
73 | local: word
74 | | local '.' word
75 | ;
76 |
77 | domain: domword
78 | | domain '.' domword
79 | ;
80 |
81 | domword: atom
82 | | DOMLIT
83 | | DIGIT
84 | ;
85 |
86 | phrase: word
87 | | phrase word
88 | ;
89 |
90 | word: atom
91 | | QUOTED
92 | | DIGIT
93 | ;
94 |
95 | atom: ATOM | FROM | BY | VIA | WITH | ID | FOR ;
96 |
97 | end
98 |
--------------------------------------------------------------------------------
/lib/racc/debugflags.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | module Racc
14 |
15 | class DebugFlags
16 | def DebugFlags.parse_option_string(s)
17 | parse = rule = token = state = la = prec = conf = false
18 | s.split(//).each do |ch|
19 | case ch
20 | when 'p' then parse = true
21 | when 'r' then rule = true
22 | when 't' then token = true
23 | when 's' then state = true
24 | when 'l' then la = true
25 | when 'c' then prec = true
26 | when 'o' then conf = true
27 | else
28 | raise "unknown debug flag char: #{ch.inspect}"
29 | end
30 | end
31 | new(parse, rule, token, state, la, prec, conf)
32 | end
33 |
34 | def initialize(parse = false, rule = false, token = false, state = false,
35 | la = false, prec = false, conf = false)
36 | @parse = parse
37 | @rule = rule
38 | @token = token
39 | @state = state
40 | @la = la
41 | @prec = prec
42 | @any = (parse || rule || token || state || la || prec)
43 | @status_logging = conf
44 | end
45 |
46 | attr_reader :parse
47 | attr_reader :rule
48 | attr_reader :token
49 | attr_reader :state
50 | attr_reader :la
51 | attr_reader :prec
52 |
53 | def any?
54 | @any
55 | end
56 |
57 | attr_reader :status_logging
58 | end
59 |
60 | end
61 |
--------------------------------------------------------------------------------
/test/assets/scan.y:
--------------------------------------------------------------------------------
1 | class P
2 |
3 | rule
4 |
5 | a: A
6 | {
7 | # comment test
8 |
9 | # comment test
10 |
11 | # string
12 | @sstring = 'squote string'
13 | @dstring = 'dquote string'
14 |
15 | # regexp
16 | @regexp = /some regexp with spaces/
17 |
18 | # gvar
19 | /regexp/ === 'some regexp matches to this string'
20 | @pre_match = $`
21 | @matched = $&
22 | @post_match = $'
23 | @m = $~
24 |
25 | # braces
26 | @array = []
27 | [1,2,3].each {|i|
28 | @array.push i
29 | }
30 | 3.times { @array.push 10 }
31 | }
32 |
33 | end
34 |
35 | ---- inner
36 |
37 | def parse
38 | @sstring = @dstring = nil
39 | @regexp = nil
40 | @pre_match = @matched = @post_match = @m = nil
41 |
42 | @src = [[:A, 'A'], [false, '$']]
43 | do_parse
44 |
45 | assert_equal 'squote string', @sstring
46 | assert_equal 'dquote string', @dstring
47 | assert_equal(/some regexp with spaces/, @regexp)
48 | assert_equal 'some ', @pre_match
49 | assert_equal 'regexp', @matched
50 | assert_equal ' matches to this string', @post_match
51 | assert_instance_of MatchData, @m
52 | end
53 |
54 | def assert_equal(ok, data)
55 | unless ok == data
56 | raise "expected <#{ok.inspect}> but is <#{data.inspect}>"
57 | end
58 | end
59 |
60 | def assert_instance_of(klass, obj)
61 | unless obj.instance_of?(klass)
62 | raise "expected #{klass} but is #{obj.class}"
63 | end
64 | end
65 |
66 | def next_token
67 | @src.shift
68 | end
69 |
70 | ---- footer
71 |
72 | P.new.parse
73 |
--------------------------------------------------------------------------------
/lib/racc/iset.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | module Racc
14 |
15 | # An "indexed" set. All items must respond to :ident.
16 | class ISet
17 |
18 | def initialize(a = [])
19 | @set = a
20 | end
21 |
22 | attr_reader :set
23 |
24 | def add(i)
25 | @set[i.ident] = i
26 | end
27 |
28 | def [](key)
29 | @set[key.ident]
30 | end
31 |
32 | def []=(key, val)
33 | @set[key.ident] = val
34 | end
35 |
36 | alias include? []
37 | alias key? []
38 |
39 | def update(other)
40 | s = @set
41 | o = other.set
42 | o.each_index do |idx|
43 | if t = o[idx]
44 | s[idx] = t
45 | end
46 | end
47 | end
48 |
49 | def update_a(a)
50 | s = @set
51 | a.each {|i| s[i.ident] = i }
52 | end
53 |
54 | def delete(key)
55 | i = @set[key.ident]
56 | @set[key.ident] = nil
57 | i
58 | end
59 |
60 | def each(&block)
61 | @set.compact.each(&block)
62 | end
63 |
64 | def to_a
65 | @set.compact
66 | end
67 |
68 | def to_s
69 | "[#{@set.compact.join(' ')}]"
70 | end
71 |
72 | alias inspect to_s
73 |
74 | def size
75 | @set.nitems
76 | end
77 |
78 | def empty?
79 | @set.nitems == 0
80 | end
81 |
82 | def clear
83 | @set.clear
84 | end
85 |
86 | def dup
87 | ISet.new(@set.dup)
88 | end
89 |
90 | end # class ISet
91 |
92 | end # module Racc
93 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | concurrency:
3 | group: "${{github.workflow}}-${{github.ref}}"
4 | cancel-in-progress: true
5 | on:
6 | workflow_dispatch:
7 | pull_request:
8 | types: [opened, synchronize]
9 | branches:
10 | - '*'
11 | push:
12 | schedule:
13 | - cron: "0 8 * * 3" # At 08:00 on Wednesday # https://crontab.guru/#0_8_*_*_3
14 |
15 | jobs:
16 | ruby-versions:
17 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master
18 | with:
19 | min_version: 2.5
20 | test:
21 | needs: ruby-versions
22 | runs-on: ${{ matrix.os }}
23 | strategy:
24 | fail-fast: false
25 | matrix:
26 | ruby: ${{fromJson(needs.ruby-versions.outputs.versions)}}
27 | os: [ubuntu-latest, macos-latest, windows-latest]
28 | exclude:
29 | - {os: windows-latest, ruby: truffleruby-head}
30 | - {os: windows-latest, ruby: truffleruby}
31 | - {os: windows-latest, ruby: jruby-head}
32 | - {os: windows-latest, ruby: jruby }
33 | - {os: macos-latest, ruby: '2.5' }
34 | - {os: macos-latest, ruby: truffleruby }
35 | - {os: macos-latest, ruby: truffleruby-head }
36 | - {os: macos-latest, ruby: jruby }
37 | - {os: macos-latest, ruby: jruby-head }
38 | steps:
39 | - uses: actions/checkout@v6.0.1
40 | - uses: ruby/setup-ruby@v1
41 | with:
42 | ruby-version: ${{matrix.ruby}}
43 | - uses: actions/setup-java@v5
44 | with:
45 | distribution: zulu
46 | java-version: 21
47 | if: >-
48 | startsWith(matrix.ruby, 'jruby')
49 | - run: bundle install --jobs 4 --retry 3
50 | - run: rake test
51 | - run: rake build
52 |
--------------------------------------------------------------------------------
/test/test_scan_y.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(__dir__, 'case'))
2 |
3 | module Racc
4 | class TestScanY < TestCase
5 | def setup
6 | super
7 | file = File.join(ASSET_DIR, 'scan.y')
8 | @debug_flags = Racc::DebugFlags.parse_option_string('o')
9 | parser = Racc::GrammarFileParser.new(@debug_flags)
10 | @result = parser.parse(File.read(file), File.basename(file))
11 | @states = Racc::States.new(@result.grammar).nfa
12 | @states.dfa
13 | end
14 |
15 | def test_compile
16 | generator = Racc::ParserFileGenerator.new(@states, @result.params.dup)
17 |
18 | # it generates valid ruby
19 | assert Module.new {
20 | self.class_eval(generator.generate_parser)
21 | }
22 |
23 | grammar = @states.grammar
24 |
25 | assert_equal 0, @states.n_srconflicts
26 | assert_equal 0, @states.n_rrconflicts
27 | assert_equal 0, grammar.n_useless_nonterminals
28 | assert_equal 0, grammar.n_useless_rules
29 | assert_nil grammar.n_expected_srconflicts
30 | end
31 |
32 | def test_compile_line_convert
33 | params = @result.params.dup
34 | params.convert_line_all = true
35 |
36 | generator = Racc::ParserFileGenerator.new(@states, @result.params.dup)
37 |
38 | # it generates valid ruby
39 | assert Module.new {
40 | self.class_eval(generator.generate_parser)
41 | }
42 |
43 | grammar = @states.grammar
44 |
45 | assert_equal 0, @states.n_srconflicts
46 | assert_equal 0, @states.n_rrconflicts
47 | assert_equal 0, grammar.n_useless_nonterminals
48 | assert_equal 0, grammar.n_useless_rules
49 | assert_nil grammar.n_expected_srconflicts
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/test/test_chk_y.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(__dir__, 'case'))
2 |
3 | module Racc
4 | class TestChkY < TestCase
5 | def setup
6 | super
7 | file = File.join(ASSET_DIR, 'chk.y')
8 | @debug_flags = Racc::DebugFlags.parse_option_string('o')
9 | parser = Racc::GrammarFileParser.new(@debug_flags)
10 | @result = parser.parse(File.read(file), File.basename(file))
11 | @states = Racc::States.new(@result.grammar).nfa
12 | @states.dfa
13 | end
14 |
15 | def test_compile_chk_y
16 | generator = Racc::ParserFileGenerator.new(@states, @result.params.dup)
17 |
18 | # it generates valid ruby
19 | assert Module.new {
20 | self.instance_eval(generator.generate_parser, __FILE__, __LINE__)
21 | }
22 |
23 | grammar = @states.grammar
24 |
25 | assert_equal 0, @states.n_srconflicts
26 | assert_equal 0, @states.n_rrconflicts
27 | assert_equal 0, grammar.n_useless_nonterminals
28 | assert_equal 0, grammar.n_useless_rules
29 | assert_nil grammar.n_expected_srconflicts
30 | end
31 |
32 | def test_compile_chk_y_line_convert
33 | params = @result.params.dup
34 | params.convert_line_all = true
35 |
36 | generator = Racc::ParserFileGenerator.new(@states, @result.params.dup)
37 |
38 | # it generates valid ruby
39 | assert Module.new {
40 | self.instance_eval(generator.generate_parser, __FILE__, __LINE__)
41 | }
42 |
43 | grammar = @states.grammar
44 |
45 | assert_equal 0, @states.n_srconflicts
46 | assert_equal 0, @states.n_rrconflicts
47 | assert_equal 0, grammar.n_useless_nonterminals
48 | assert_equal 0, grammar.n_useless_rules
49 | assert_nil grammar.n_expected_srconflicts
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Racc
2 |
3 | * https://github.com/ruby/racc
4 |
5 | == DESCRIPTION:
6 |
7 | Racc is an LALR(1) parser generator.
8 | It is written in Ruby itself, and generates Ruby program.
9 |
10 | == Requirement
11 |
12 | * Ruby 2.5 or later.
13 |
14 | == Installation
15 |
16 | gem install:
17 |
18 | $ gem install racc
19 |
20 | == Testing Racc
21 |
22 | Racc comes with simple calculator. To compile this, on shell:
23 |
24 | $ racc -o calc calc.y
25 |
26 | This process costs few seconds (or less). Then type:
27 |
28 | $ ruby calc
29 |
30 | ... Does it work?
31 | For details of Racc, see HTML documents placed under 'doc/en/'
32 | and sample grammar files under 'sample/'.
33 |
34 | == Release flow
35 |
36 | * Update VERSION number of these files
37 | * RACC_VERSION in "ext/racc/com/headius/racc/Cparse.java"
38 | * VERSION in "lib/racc/info.rb"
39 | * Release as a gem by rake release with CRuby and JRuby because Racc gem provides 2 packages
40 | * Create new release on {GitHub}[https://github.com/ruby/racc/releases]
41 |
42 | == License
43 |
44 | Racc is distributed under the same terms of ruby.
45 | (see the file COPYING). Note that you do NOT need to follow
46 | ruby license for your own parser (racc outputs).
47 | You can distribute those files under any licenses you want.
48 |
49 |
50 | == Bug Reports
51 |
52 | Any kind of bug report is welcome.
53 | If you find a bug of Racc, please report an issue at
54 | https://github.com/ruby/racc/issues. Your grammar file,
55 | debug output generated by "racc -t", are helpful.
56 |
57 |
58 | Minero Aoki
59 | aamine@loveruby.net
60 | http://i.loveruby.net
61 |
--------------------------------------------------------------------------------
/test/assets/ichk.y:
--------------------------------------------------------------------------------
1 | class Calculator
2 |
3 | prechigh
4 | left '*' '/'
5 | left '+' '-'
6 | preclow
7 |
8 | convert
9 | NUMBER 'Number'
10 | end
11 |
12 | rule
13 |
14 | target : exp
15 | | /* none */ { result = 0 }
16 |
17 | exp : exp '+' exp { result += val[2]; a = 'plus' }
18 | | exp '-' exp { result -= val[2]; a = "string test" }
19 | | exp '*' exp { result *= val[2] }
20 | | exp '/' exp { result /= val[2] }
21 | | '(' { $emb = true } exp ')'
22 | {
23 | raise 'must not happen' unless $emb
24 | result = val[2]
25 | }
26 | | '-' NUMBER { result = -val[1] }
27 | | NUMBER
28 |
29 | ----header
30 |
31 | class Number
32 | end
33 |
34 | ----inner
35 |
36 | def initialize
37 | @racc_debug_out = $stdout
38 | @yydebug = false
39 | end
40 |
41 | def validate(expected, src)
42 | result = parse(src)
43 | unless result == expected
44 | raise "test #{@test_number} fail"
45 | end
46 | @test_number += 1
47 | end
48 |
49 | def parse(src)
50 | @src = src
51 | @test_number = 1
52 | yyparse self, :scan
53 | end
54 |
55 | def scan(&block)
56 | @src.each(&block)
57 | end
58 |
59 | ----footer
60 |
61 | calc = Calculator.new
62 |
63 | calc.validate(9, [[Number, 9], nil])
64 |
65 | calc.validate(-3,
66 | [[Number, 5],
67 | ['*', '*'],
68 | [Number, 1],
69 | ['-', '*'],
70 | [Number, 1],
71 | ['*', '*'],
72 | [Number, 8],
73 | nil])
74 |
75 | calc.validate(-1,
76 | [[Number, 5],
77 | ['+', '+'],
78 | [Number, 2],
79 | ['-', '-'],
80 | [Number, 5],
81 | ['+', '+'],
82 | [Number, 2],
83 | ['-', '-'],
84 | [Number, 5],
85 | nil])
86 |
87 | calc.validate(-4,
88 | [['-', 'UMINUS'],
89 | [Number, 4],
90 | nil])
91 |
92 | calc.validate(40,
93 | [[Number, 7],
94 | ['*', '*'],
95 | ['(', '('],
96 | [Number, 4],
97 | ['+', '+'],
98 | [Number, 3],
99 | [')', ')'],
100 | ['-', '-'],
101 | [Number, 9],
102 | nil])
103 |
--------------------------------------------------------------------------------
/test/assets/echk.y:
--------------------------------------------------------------------------------
1 | #
2 | # racc tester
3 | #
4 |
5 | class Calcp
6 |
7 | prechigh
8 | left '*' '/'
9 | left '+' '-'
10 | preclow
11 |
12 | convert
13 | NUMBER 'Number'
14 | end
15 |
16 | rule
17 |
18 | target : exp | /* none */ { result = 0 } ;
19 |
20 | exp : exp '+' exp { result += val[2]; a = 'plus' }
21 | | exp '-' exp { result -= val[2]; "string test" }
22 | | exp '*' exp { result *= val[2] }
23 | | exp '/' exp { result /= val[2] }
24 | | '(' { $emb = true } exp ')'
25 | {
26 | raise 'must not happen' unless $emb
27 | result = val[2]
28 | }
29 | | '-' NUMBER { result = -val[1] }
30 | | NUMBER
31 | ;
32 |
33 | end
34 |
35 | ----header
36 |
37 | class Number ; end
38 |
39 | ----inner
40 |
41 | def parse( src )
42 | @src = src
43 | do_parse
44 | end
45 |
46 | def next_token
47 | @src.shift
48 | end
49 |
50 | def initialize
51 | @yydebug = true
52 | end
53 |
54 | ----footer
55 |
56 | $parser = Calcp.new
57 | $tidx = 1
58 |
59 | def chk( src, ans )
60 | ret = $parser.parse( src )
61 | unless ret == ans then
62 | bug! "test #{$tidx} fail"
63 | end
64 | $tidx += 1
65 | end
66 |
67 | chk(
68 | [ [Number, 9],
69 | [false, false],
70 | [false, false] ], 9
71 | )
72 |
73 | chk(
74 | [ [Number, 5],
75 | ['*', nil],
76 | [Number, 1],
77 | ['-', nil],
78 | [Number, 1],
79 | ['*', nil],
80 | [Number, 8],
81 | [false, false],
82 | [false, false] ], -3
83 | )
84 |
85 | chk(
86 | [ [Number, 5],
87 | ['+', nil],
88 | [Number, 2],
89 | ['-', nil],
90 | [Number, 5],
91 | ['+', nil],
92 | [Number, 2],
93 | ['-', nil],
94 | [Number, 5],
95 | [false, false],
96 | [false, false] ], -1
97 | )
98 |
99 | chk(
100 | [ ['-', nil],
101 | [Number, 4],
102 | [false, false],
103 | [false, false] ], -4
104 | )
105 |
106 | chk(
107 | [ [Number, 7],
108 | ['*', nil],
109 | ['(', nil],
110 | [Number, 4],
111 | ['+', nil],
112 | [Number, 3],
113 | [')', nil],
114 | ['-', nil],
115 | [Number, 9],
116 | [false, false],
117 | [false, false] ], 40
118 | )
119 |
--------------------------------------------------------------------------------
/racc.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | begin
4 | require_relative "lib/racc/info"
5 | rescue LoadError # Fallback to load version file in ruby core repository
6 | require_relative "info"
7 | end
8 |
9 | Gem::Specification.new do |s|
10 | s.name = "racc"
11 | s.version = Racc::VERSION
12 | s.summary = "Racc is an LALR(1) parser generator"
13 | s.description = < 0,
60 | :error => 1,
61 | "abc" => 2 }
62 |
63 | racc_nt_base = 3
64 |
65 | racc_use_result_var = true
66 |
67 | Racc_arg = [
68 | racc_action_table,
69 | racc_action_check,
70 | racc_action_default,
71 | racc_action_pointer,
72 | racc_goto_table,
73 | racc_goto_check,
74 | racc_goto_default,
75 | racc_goto_pointer,
76 | racc_nt_base,
77 | racc_reduce_table,
78 | racc_token_table,
79 | racc_shift_n,
80 | racc_reduce_n,
81 | racc_use_result_var ]
82 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
83 |
84 | Racc_token_to_s_table = [
85 | "$end",
86 | "error",
87 | "\"abc\"",
88 | "$start",
89 | "stmt",
90 | "\"-option@abc\"" ]
91 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
92 |
93 | Racc_debug_parser = false
94 |
95 | ##### State transition tables end #####
96 |
97 | # reduce 0 omitted
98 |
99 | # reduce 1 omitted
100 |
101 | # reduce 2 omitted
102 |
103 | # reduce 3 omitted
104 |
105 | def _reduce_none(val, _values, result)
106 | val[0]
107 | end
108 |
109 | end # class MyParser
110 |
--------------------------------------------------------------------------------
/test/regress/group:
--------------------------------------------------------------------------------
1 | #
2 | # DO NOT MODIFY!!!!
3 | # This file is automatically generated by Racc 1.8.0
4 | # from Racc grammar file "group.y".
5 | #
6 |
7 | require 'racc/parser.rb'
8 |
9 | require 'strscan'
10 | class MyParser < Racc::Parser
11 |
12 | module_eval(<<'...end group.y/module_eval...', 'group.y', 8)
13 | def parse(str)
14 | @ss = StringScanner.new(str)
15 | do_parse
16 | end
17 | def next_token
18 | @ss.skip(/\\s+/)
19 | token = @ss.scan(/\\S+/) and [token, token]
20 | end
21 | ...end group.y/module_eval...
22 | ##### State transition tables begin ###
23 |
24 | racc_action_table = [
25 | 2, 4, 5 ]
26 |
27 | racc_action_check = [
28 | 0, 1, 4 ]
29 |
30 | racc_action_pointer = [
31 | -2, 1, nil, nil, 2, nil ]
32 |
33 | racc_action_default = [
34 | -3, -3, -1, -2, -3, 6 ]
35 |
36 | racc_goto_table = [
37 | 1, 3 ]
38 |
39 | racc_goto_check = [
40 | 1, 2 ]
41 |
42 | racc_goto_pointer = [
43 | nil, 0, 1 ]
44 |
45 | racc_goto_default = [
46 | nil, nil, nil ]
47 |
48 | racc_reduce_table = [
49 | 0, 0, :racc_error,
50 | 1, 6, :_reduce_1,
51 | 1, 5, :_reduce_none ]
52 |
53 | racc_reduce_n = 3
54 |
55 | racc_shift_n = 6
56 |
57 | racc_token_table = {
58 | false => 0,
59 | :error => 1,
60 | "a" => 2,
61 | "-temp-group" => 3 }
62 |
63 | racc_nt_base = 4
64 |
65 | racc_use_result_var = true
66 |
67 | Racc_arg = [
68 | racc_action_table,
69 | racc_action_check,
70 | racc_action_default,
71 | racc_action_pointer,
72 | racc_goto_table,
73 | racc_goto_check,
74 | racc_goto_default,
75 | racc_goto_pointer,
76 | racc_nt_base,
77 | racc_reduce_table,
78 | racc_token_table,
79 | racc_shift_n,
80 | racc_reduce_n,
81 | racc_use_result_var ]
82 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
83 |
84 | Racc_token_to_s_table = [
85 | "$end",
86 | "error",
87 | "\"a\"",
88 | "\"-temp-group\"",
89 | "$start",
90 | "stmt",
91 | "\"-group@\\\"a\\\"\"" ]
92 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
93 |
94 | Racc_debug_parser = false
95 |
96 | ##### State transition tables end #####
97 |
98 | # reduce 0 omitted
99 |
100 | module_eval(<<'.,.,', 'group.y', 4)
101 | def _reduce_1(val, _values, result)
102 | result = val
103 | result
104 | end
105 | .,.,
106 |
107 | # reduce 2 omitted
108 |
109 | def _reduce_none(val, _values, result)
110 | val[0]
111 | end
112 |
113 | end # class MyParser
114 |
--------------------------------------------------------------------------------
/test/assets/chk.y:
--------------------------------------------------------------------------------
1 | #
2 | # racc tester
3 | #
4 |
5 | class Calcp
6 |
7 | prechigh
8 | left '*' '/'
9 | left '+' '-'
10 | preclow
11 |
12 | convert
13 | NUMBER 'Number'
14 | end
15 |
16 | rule
17 |
18 | target : exp | /* none */ { result = 0 } ;
19 |
20 | exp : exp '+' exp { result += val[2]; @plus = 'plus' }
21 | | exp '-' exp { result -= val[2]; @str = "string test" }
22 | | exp '*' exp { result *= val[2] }
23 | | exp '/' exp { result /= val[2] }
24 | | '(' { $emb = true } exp ')'
25 | {
26 | raise 'must not happen' unless $emb
27 | result = val[2]
28 | }
29 | | '-' NUMBER { result = -val[1] }
30 | | NUMBER
31 | ;
32 |
33 | end
34 |
35 | ----header
36 |
37 | class Number; end
38 |
39 | ----inner
40 |
41 | def parse( src )
42 | $emb = false
43 | @plus = nil
44 | @str = nil
45 | @src = src
46 | result = do_parse
47 | if @plus
48 | raise 'string parse failed' unless @plus == 'plus'
49 | end
50 | if @str
51 | raise 'string parse failed' unless @str == 'string test'
52 | end
53 | result
54 | end
55 |
56 | def next_token
57 | @src.shift
58 | end
59 |
60 | def initialize
61 | @yydebug = true
62 | end
63 |
64 | ----footer
65 |
66 | $parser = Calcp.new
67 | $test_number = 1
68 |
69 | def chk( src, ans )
70 | result = $parser.parse(src)
71 | raise "test #{$test_number} fail" unless result == ans
72 | $test_number += 1
73 | end
74 |
75 | chk(
76 | [ [Number, 9],
77 | [false, false],
78 | [false, false] ], 9
79 | )
80 |
81 | chk(
82 | [ [Number, 5],
83 | ['*', nil],
84 | [Number, 1],
85 | ['-', nil],
86 | [Number, 1],
87 | ['*', nil],
88 | [Number, 8],
89 | [false, false],
90 | [false, false] ], -3
91 | )
92 |
93 | chk(
94 | [ [Number, 5],
95 | ['+', nil],
96 | [Number, 2],
97 | ['-', nil],
98 | [Number, 5],
99 | ['+', nil],
100 | [Number, 2],
101 | ['-', nil],
102 | [Number, 5],
103 | [false, false],
104 | [false, false] ], -1
105 | )
106 |
107 | chk(
108 | [ ['-', nil],
109 | [Number, 4],
110 | [false, false],
111 | [false, false] ], -4
112 | )
113 |
114 | chk(
115 | [ [Number, 7],
116 | ['*', nil],
117 | ['(', nil],
118 | [Number, 4],
119 | ['+', nil],
120 | [Number, 3],
121 | [')', nil],
122 | ['-', nil],
123 | [Number, 9],
124 | [false, false],
125 | [false, false] ], 40
126 | )
127 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Ruby is copyrighted free software by Yukihiro Matsumoto .
2 | You can redistribute it and/or modify it under either the terms of the
3 | 2-clause BSDL (see the file BSDL), or the conditions below:
4 |
5 | 1. You may make and give away verbatim copies of the source form of the
6 | software without restriction, provided that you duplicate all of the
7 | original copyright notices and associated disclaimers.
8 |
9 | 2. You may modify your copy of the software in any way, provided that
10 | you do at least ONE of the following:
11 |
12 | a. place your modifications in the Public Domain or otherwise
13 | make them Freely Available, such as by posting said
14 | modifications to Usenet or an equivalent medium, or by allowing
15 | the author to include your modifications in the software.
16 |
17 | b. use the modified software only within your corporation or
18 | organization.
19 |
20 | c. give non-standard binaries non-standard names, with
21 | instructions on where to get the original software distribution.
22 |
23 | d. make other distribution arrangements with the author.
24 |
25 | 3. You may distribute the software in object code or binary form,
26 | provided that you do at least ONE of the following:
27 |
28 | a. distribute the binaries and library files of the software,
29 | together with instructions (in the manual page or equivalent)
30 | on where to get the original distribution.
31 |
32 | b. accompany the distribution with the machine-readable source of
33 | the software.
34 |
35 | c. give non-standard binaries non-standard names, with
36 | instructions on where to get the original software distribution.
37 |
38 | d. make other distribution arrangements with the author.
39 |
40 | 4. You may modify and include the part of the software into any other
41 | software (possibly commercial). But some files in the distribution
42 | are not written by the author, so that they are not under these terms.
43 |
44 | For the list of those files and their copying conditions, see the
45 | file LEGAL.
46 |
47 | 5. The scripts and library files supplied as input to or produced as
48 | output from the software do not automatically fall under the
49 | copyright of the software, but belong to whomever generated them,
50 | and may be sold commercially, and may be aggregated with this
51 | software.
52 |
53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56 | PURPOSE.
57 |
--------------------------------------------------------------------------------
/test/test_sample.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('lib/helper', __dir__)
2 |
3 | module Racc
4 | class TestSample < TestCase
5 | # working samples
6 | [
7 | {
8 | grammar_file: "array.y",
9 | parser_class: :ArrayParser,
10 | testcases: [
11 | { input: "[1]", expected: ["1"] },
12 | { input: "[1, 2]", expected: ["1", "2"] },
13 | ]
14 | },
15 | {
16 | grammar_file: "array2.y",
17 | parser_class: :ArrayParser2,
18 | testcases: [
19 | { input: "[1]", expected: ["1"] },
20 | { input: "[1, 2]", expected: ["1", "2"] },
21 | ]
22 | },
23 | {
24 | grammar_file: "calc.y",
25 | parser_class: :Calcp,
26 | testcases: [
27 | { input: "1", expected: 1 },
28 | { input: "10", expected: 10 },
29 | { input: "2 + 1", expected: 3 },
30 | { input: "2 - 1", expected: 1 },
31 | { input: "3 * 4", expected: 12 },
32 | { input: "4 / 2", expected: 2 },
33 | { input: "3 / 2", expected: 1 },
34 | { input: "2 + 3 * 4", expected: 14 },
35 | { input: "(2 + 3) * 4", expected: 20 },
36 | { input: "2 + (3 * 4)", expected: 14 },
37 | ]
38 | },
39 | {
40 | grammar_file: "hash.y",
41 | parser_class: :HashParser,
42 | testcases: [
43 | { input: "{}", expected: {} },
44 | { input: "{ a => b }", expected: { "a" => "b" } },
45 | { input: "{ a => b, 1 => 2 }", expected: { "a" => "b", "1" => "2" } },
46 | ]
47 | },
48 | ].each do |data|
49 | define_method "test_#{data[:grammar_file]}" do
50 | outfile = compile_sample(data[:grammar_file])
51 |
52 | load(outfile)
53 |
54 | parser_class = Object.const_get(data[:parser_class])
55 | data[:testcases].each do |testcase|
56 | input = testcase[:input]
57 | actual = parser_class.new.parse(input)
58 | expected = testcase[:expected]
59 | assert_equal(expected, actual, "expected #{expected} but got #{actual} when input is #{input}")
60 | end
61 | ensure
62 | remove_const_f(data[:parser_class])
63 | end
64 | end
65 |
66 | private
67 |
68 | # returns the generated file's path
69 | def compile_sample(yfile)
70 | file = File.basename(yfile, '.y')
71 | out = File.join(@OUT_DIR, file)
72 | ruby "-I#{LIB_DIR}", RACC, File.join(SAMPLE_DIR, yfile), "-o#{out}"
73 | out
74 | end
75 |
76 | def remove_const_f(const_name)
77 | Object.send(:remove_const, const_name) if Object.const_defined?(const_name, false)
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/doc/ja/command.ja.html:
--------------------------------------------------------------------------------
1 | Raccコマンドリファレンス
2 |
3 | racc [-ofilename ] [--output-file=filename ]
4 | [-erubypath ] [--executable=rubypath ]
5 | [-v] [--verbose]
6 | [-Ofilename ] [--log-file=filename ]
7 | [-g] [--debug]
8 | [-E] [--embedded]
9 | [-F] [--frozen]
10 | [-l] [--no-line-convert]
11 | [-c] [--line-convert-all]
12 | [-a] [--no-omit-actions]
13 | [-C] [--check-only]
14 | [-S] [--output-status]
15 | [--version] [--copyright] [--help] grammarfile
16 |
17 |
18 |
19 | filename
20 |
21 | Raccの文法ファイルを指定します。拡張子には特に制限はありません。
22 |
23 | -ooutfile , --output-file=outfile
24 |
25 | 作成するクラスをかきこむファイル名を指定します。デフォルトは.tab.rbです。
26 |
27 | -Ofilename , --log-file=filename
28 |
29 | -v オプションをつけた時に生成するログファイルの名前を
30 | filename に変更します。
31 | デフォルトは filename .output です。
32 |
33 | -erubypath , --executable=rubypath
34 |
35 | 実行可能ファイルを生成します。rubypath は Ruby 本体のパスです。
36 | rubypath を単に 'ruby' にした時には Racc が動作している
37 | Ruby のパスを使用します。
38 |
39 | -v, --verbose
40 |
41 | ファイル "filename".output に詳細な解析情報を出力します。
42 |
43 | -g, --debug
44 |
45 | 出力するコードにデバッグ用コードを加えます。-g をつけて生成したパーサで
46 | @yydebug を true にセットすると、デバッグ用のコードが出力されます。
47 | -g をつけるだけでは何もおこりませんので注意してください。
48 |
49 | -E, --embedded
50 |
51 | ランタイムルーチンをすべて含んだコードを生成します。
52 | つまり、このオプションをつけて生成したコードは Ruby さえあれば動きます。
53 |
54 | -F, --frozen
55 |
56 | Add frozen_string_literals: true.
57 |
58 | -C, --check-only
59 |
60 | (文法ファイルの) 文法のチェックだけをして終了します。
61 |
62 | -S, --output-status
63 |
64 | 進行状況を逐一報告します。
65 |
66 | -l, --no-line-convert
67 |
68 |
69 | Ruby では例外が発生した時のファイル名や行番号を表示してくれますが、
70 | Racc の生成したパーサは、デフォルトではこの場合のファイル名・行番号を
71 | 文法ファイルでのものに置きかえます。このフラグはその機能をオフにします。
72 |
73 |
74 | ruby 1.4.3 以前のバージョンではバグのために定数の参照に失敗する
75 | 場合があるので、定数参照に関してなにかおかしいことがおこったらこのフラグを
76 | 試してみてください。
77 |
78 |
79 | -c, --line-convert-all
80 |
81 | アクションと inner に加え header footer の行番号も変換します。
82 | header と footer がつながっているような場合には使わないでください。
83 | -a, --no-omit-actions
84 |
85 |
86 | 全てのアクションに対応するメソッド定義と呼び出しを行います。
87 | 例えアクションが省略されていても空のメソッドを生成します。
88 |
89 | --version
90 |
91 | Racc のバージョンを出力して終了します。
92 |
93 | --copyright
94 |
95 | 著作権表示を出力して終了します。
96 | --help
97 |
98 |
99 | オプションの簡単な説明を出力して終了します。
100 |
101 |
102 |
--------------------------------------------------------------------------------
/test/regress/many:
--------------------------------------------------------------------------------
1 | #
2 | # DO NOT MODIFY!!!!
3 | # This file is automatically generated by Racc 1.8.0
4 | # from Racc grammar file "many.y".
5 | #
6 |
7 | require 'racc/parser.rb'
8 |
9 | require 'strscan'
10 | class MyParser < Racc::Parser
11 |
12 | module_eval(<<'...end many.y/module_eval...', 'many.y', 8)
13 | def parse(str)
14 | @ss = StringScanner.new(str)
15 | do_parse
16 | end
17 | def next_token
18 | @ss.skip(/\\s+/)
19 | token = @ss.scan(/\\S+/) and [token, token]
20 | end
21 | ...end many.y/module_eval...
22 | ##### State transition tables begin ###
23 |
24 | racc_action_table = [
25 | 2, 4, 2, 6 ]
26 |
27 | racc_action_check = [
28 | 0, 1, 2, 4 ]
29 |
30 | racc_action_pointer = [
31 | -2, 1, 0, nil, 3, nil, nil ]
32 |
33 | racc_action_default = [
34 | -1, -4, -1, -3, -4, -2, 7 ]
35 |
36 | racc_goto_table = [
37 | 3, 1, 5 ]
38 |
39 | racc_goto_check = [
40 | 2, 1, 2 ]
41 |
42 | racc_goto_pointer = [
43 | nil, 1, 0 ]
44 |
45 | racc_goto_default = [
46 | nil, nil, nil ]
47 |
48 | racc_reduce_table = [
49 | 0, 0, :racc_error,
50 | 0, 5, :_reduce_1,
51 | 2, 5, :_reduce_2,
52 | 1, 4, :_reduce_none ]
53 |
54 | racc_reduce_n = 4
55 |
56 | racc_shift_n = 7
57 |
58 | racc_token_table = {
59 | false => 0,
60 | :error => 1,
61 | "abc" => 2 }
62 |
63 | racc_nt_base = 3
64 |
65 | racc_use_result_var = true
66 |
67 | Racc_arg = [
68 | racc_action_table,
69 | racc_action_check,
70 | racc_action_default,
71 | racc_action_pointer,
72 | racc_goto_table,
73 | racc_goto_check,
74 | racc_goto_default,
75 | racc_goto_pointer,
76 | racc_nt_base,
77 | racc_reduce_table,
78 | racc_token_table,
79 | racc_shift_n,
80 | racc_reduce_n,
81 | racc_use_result_var ]
82 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
83 |
84 | Racc_token_to_s_table = [
85 | "$end",
86 | "error",
87 | "\"abc\"",
88 | "$start",
89 | "stmt",
90 | "\"-many@abc\"" ]
91 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
92 |
93 | Racc_debug_parser = false
94 |
95 | ##### State transition tables end #####
96 |
97 | # reduce 0 omitted
98 |
99 | module_eval(<<'.,.,', 'many.y', 4)
100 | def _reduce_1(val, _values, result)
101 | result = val[1] ? val[1].unshift(val[0]) : val
102 | result
103 | end
104 | .,.,
105 |
106 | module_eval(<<'.,.,', 'many.y', 4)
107 | def _reduce_2(val, _values, result)
108 | result = val[1] ? val[1].unshift(val[0]) : val
109 | result
110 | end
111 | .,.,
112 |
113 | # reduce 3 omitted
114 |
115 | def _reduce_none(val, _values, result)
116 | val[0]
117 | end
118 |
119 | end # class MyParser
120 |
--------------------------------------------------------------------------------
/test/regress/many1:
--------------------------------------------------------------------------------
1 | #
2 | # DO NOT MODIFY!!!!
3 | # This file is automatically generated by Racc 1.8.0
4 | # from Racc grammar file "many1.y".
5 | #
6 |
7 | require 'racc/parser.rb'
8 |
9 | require 'strscan'
10 | class MyParser < Racc::Parser
11 |
12 | module_eval(<<'...end many1.y/module_eval...', 'many1.y', 8)
13 | def parse(str)
14 | @ss = StringScanner.new(str)
15 | do_parse
16 | end
17 | def next_token
18 | @ss.skip(/\\s+/)
19 | token = @ss.scan(/\\S+/) and [token, token]
20 | end
21 | ...end many1.y/module_eval...
22 | ##### State transition tables begin ###
23 |
24 | racc_action_table = [
25 | 2, 4, 2, 6 ]
26 |
27 | racc_action_check = [
28 | 0, 1, 2, 4 ]
29 |
30 | racc_action_pointer = [
31 | -2, 1, 0, nil, 3, nil, nil ]
32 |
33 | racc_action_default = [
34 | -4, -4, -1, -3, -4, -2, 7 ]
35 |
36 | racc_goto_table = [
37 | 3, 1, 5 ]
38 |
39 | racc_goto_check = [
40 | 2, 1, 2 ]
41 |
42 | racc_goto_pointer = [
43 | nil, 1, 0 ]
44 |
45 | racc_goto_default = [
46 | nil, nil, nil ]
47 |
48 | racc_reduce_table = [
49 | 0, 0, :racc_error,
50 | 1, 5, :_reduce_1,
51 | 2, 5, :_reduce_2,
52 | 1, 4, :_reduce_none ]
53 |
54 | racc_reduce_n = 4
55 |
56 | racc_shift_n = 7
57 |
58 | racc_token_table = {
59 | false => 0,
60 | :error => 1,
61 | "abc" => 2 }
62 |
63 | racc_nt_base = 3
64 |
65 | racc_use_result_var = true
66 |
67 | Racc_arg = [
68 | racc_action_table,
69 | racc_action_check,
70 | racc_action_default,
71 | racc_action_pointer,
72 | racc_goto_table,
73 | racc_goto_check,
74 | racc_goto_default,
75 | racc_goto_pointer,
76 | racc_nt_base,
77 | racc_reduce_table,
78 | racc_token_table,
79 | racc_shift_n,
80 | racc_reduce_n,
81 | racc_use_result_var ]
82 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
83 |
84 | Racc_token_to_s_table = [
85 | "$end",
86 | "error",
87 | "\"abc\"",
88 | "$start",
89 | "stmt",
90 | "\"-many1@abc\"" ]
91 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
92 |
93 | Racc_debug_parser = false
94 |
95 | ##### State transition tables end #####
96 |
97 | # reduce 0 omitted
98 |
99 | module_eval(<<'.,.,', 'many1.y', 4)
100 | def _reduce_1(val, _values, result)
101 | result = val[1] ? val[1].unshift(val[0]) : val
102 | result
103 | end
104 | .,.,
105 |
106 | module_eval(<<'.,.,', 'many1.y', 4)
107 | def _reduce_2(val, _values, result)
108 | result = val[1] ? val[1].unshift(val[0]) : val
109 | result
110 | end
111 | .,.,
112 |
113 | # reduce 3 omitted
114 |
115 | def _reduce_none(val, _values, result)
116 | val[0]
117 | end
118 |
119 | end # class MyParser
120 |
--------------------------------------------------------------------------------
/doc/ja/parser.ja.rdoc:
--------------------------------------------------------------------------------
1 | = class Racc::Parser
2 | Racc の生成するパーサはすべて Racc::Parser クラスを継承します。
3 | Racc::Parser クラスにはパース中に使用するメソッドがいくつかあり、
4 | そのようなメソッドをオーバーロードすると、パーサを初期化したり
5 | することができます。
6 |
7 | == Super Class
8 |
9 | Object
10 |
11 | == Constants
12 |
13 | プリフィクス "Racc_" がついた定数はパーサの予約定数です。
14 | そのような定数は使わないでください。動作不可能になります。
15 | == Instance Methods
16 | ここに載っているもののほか、プリフィクス "racc_" および "_racc_" が
17 | ついたメソッドはパーサの予約名です。そのようなメソッドは使わないで
18 | ください。
19 |
20 | : do_parse -> Object
21 | パースを開始します。
22 | また、トークンが必要になった時は #next_token を呼び出します。
23 |
24 | --
25 | # Example
26 | ---- inner
27 | def parse
28 | @q = [[1,1],
29 | [2,2],
30 | [3,3],
31 | [false, '$']]
32 | do_parse
33 | end
34 |
35 | def next_token
36 | @q.shift
37 | end
38 | --
39 |
40 | : next_token -> [Symbol, Object]
41 | [abstract method]
42 |
43 | パーサが次のトークンを読みこむ時に使います。
44 | [記号, その値] の形式の配列を返してください。
45 | 記号はデフォルトでは
46 |
47 | * 文法中、引用符でかこまれていないもの
48 | → その名前の文字列のシンボル (例えば :ATOM )
49 | * 引用符でかこまれているもの
50 | → その文字列そのまま (例えば '=' )
51 |
52 | で表します。これを変更する方法については、
53 | 文法リファレンスを参照してください。
54 |
55 | また、もう送るシンボルがなくなったときには
56 | [false, なにか] または nil を返してください。
57 |
58 | このメソッドは抽象メソッドなので、#do_parse を使う場合は
59 | 必ずパーサクラス中で再定義する必要があります。
60 | 定義しないままパースを始めると例外 NotImplementedError が
61 | 発生します。
62 |
63 | : yyparse( receiver, method_id )
64 | パースを開始します。このメソッドでは始めてトークンが
65 | 必要になった時点で receiver に対して method_id メソッドを
66 | 呼び出してトークンを得ます。
67 |
68 | receiver の method_id メソッドはトークンを yield しなければ
69 | なりません。形式は #next_token と同じで [記号, 値] です。
70 | つまり、receiver の method_id メソッドの概形は以下のように
71 | なるはずです。
72 | --
73 | def method_id
74 | until end_of_file
75 | :
76 | yield 記号, 値
77 | :
78 | end
79 | end
80 | --
81 | 少し注意が必要なのは、method_id が呼び出されるのは始めて
82 | トークンが必要になった時点であるということです。method_id
83 | メソッドが呼び出されたときは既にパースが進行中なので、
84 | アクション中で使う変数を method_id の冒頭で初期化すると
85 | まず失敗します。
86 |
87 | トークンの終端を示す [false, なにか] を渡したらそれ以上は
88 | yield しないでください。その場合には例外が発生します。
89 |
90 | 最後に、method_id メソッドからは必ず yield してください。
91 | しない場合は何が起きるかわかりません。
92 |
93 | : on_error( error_token_id, error_value, value_stack )
94 | パーサコアが文法エラーを検出すると呼び出します (yacc の yyerror)。
95 | エラーメッセージを出すなり、例外を発生するなりしてください。
96 | このメソッドから正常に戻った場合、パーサはエラー回復モード
97 | に移行します。
98 |
99 | error_token_id はパースエラーを起こした記号の内部表現 (整数) です。
100 | #token_to_str で文法ファイル上の文字列表現に直せます。
101 |
102 | error_value はその値です。
103 |
104 | value_stack はエラーの時点での値スタックです。
105 | value_stack を変更してはいけません。
106 |
107 | on_error のデフォルトの実装は例外 ParseError を発生します。
108 |
109 | : token_to_str( t ) -> String
110 | Racc トークンの内部表現 (整数)
111 | を文法ファイル上の記号表現の文字列に変換します。
112 |
113 | t が整数でない場合は TypeError を発生します。
114 | t が範囲外の整数だった場合は nil を返します。
115 |
116 | : yyerror
117 | エラー回復モードに入ります。このとき #on_error は呼ばれません。
118 | アクション以外からは呼び出さないでください。
119 |
120 | : yyerrok
121 | エラー回復モードから復帰します。
122 | アクション以外からは呼び出さないでください。
123 |
124 | : yyaccept
125 | すぐに値スタックの先頭の値を返して #do_parse、#yyparse を抜けます。
126 |
--------------------------------------------------------------------------------
/test/test_grammar.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 | require 'racc/static'
3 | require 'tempfile'
4 |
5 | class TestGrammar < Test::Unit::TestCase
6 | private def with_parser(rule)
7 | parser = Racc::GrammarFileParser.new
8 | result = parser.parse(<<"eom", "foo.y")
9 | class MyParser
10 | rule
11 | #{rule}
12 | end
13 | ---- header
14 | require 'strscan'
15 | ---- inner
16 | def parse(str)
17 | @ss = StringScanner.new(str)
18 | do_parse
19 | end
20 | def next_token
21 | @ss.skip(/\\s+/)
22 | token = @ss.scan(/\\S+/) and [token, token]
23 | end
24 | eom
25 | states = Racc::States.new(result.grammar).nfa
26 | params = result.params.dup
27 | generator = Racc::ParserFileGenerator.new(states, params)
28 | Tempfile.create(%w[y .tab.rb]) do |f|
29 | generator.generate_parser_file(f.path)
30 | require f.path
31 | parser = MyParser.new
32 | yield parser
33 | end
34 | Object.__send__(:remove_const, :MyParser)
35 | end
36 |
37 | def test_optional
38 | with_parser("stmt: 'abc'?") do |parser|
39 | assert_equal "abc", parser.parse("abc")
40 | assert_equal nil, parser.parse("")
41 | end
42 | end
43 |
44 | def test_many
45 | with_parser("stmt: 'abc'*") do |parser|
46 | assert_equal [], parser.parse("")
47 | assert_equal ["abc"], parser.parse("abc")
48 | assert_equal ["abc", "abc"], parser.parse("abc abc")
49 | assert_equal ["abc", "abc", "abc"], parser.parse("abc abc abc")
50 | end
51 | end
52 |
53 | def test_many1
54 | with_parser("stmt: 'abc'+") do |parser|
55 | assert_raise(Racc::ParseError){ parser.parse("") }
56 | assert_equal ["abc"], parser.parse("abc")
57 | assert_equal ["abc", "abc"], parser.parse("abc abc")
58 | assert_equal ["abc", "abc", "abc"], parser.parse("abc abc abc")
59 | end
60 | end
61 |
62 | def test_group
63 | with_parser("stmt: ('a')") do |parser|
64 | assert_raise(Racc::ParseError){ parser.parse("") }
65 | assert_equal ["a"], parser.parse("a")
66 | end
67 |
68 | with_parser("stmt: ('a' 'b')") do |parser|
69 | assert_raise(Racc::ParseError){ parser.parse("") }
70 | assert_raise(Racc::ParseError){ parser.parse("a") }
71 | assert_equal ["a", "b"], parser.parse("a b")
72 | end
73 | end
74 |
75 | def test_group_or
76 | with_parser("stmt: ('a' | 'b')") do |parser|
77 | assert_raise(Racc::ParseError){ parser.parse("") }
78 | assert_equal ["a"], parser.parse("a")
79 | assert_equal ["b"], parser.parse("b")
80 | end
81 | end
82 |
83 | def test_group_many
84 | with_parser("stmt: ('a')*") do |parser|
85 | assert_equal [], parser.parse("")
86 | assert_equal [["a"]], parser.parse("a")
87 | assert_equal [["a"], ["a"]], parser.parse("a a")
88 | end
89 |
90 | with_parser("start: stmt\n stmt: ('a' 'b')*") do |parser|
91 | assert_equal [], parser.parse("")
92 | assert_equal [["a", "b"]], parser.parse("a b")
93 | assert_equal [["a", "b"], ["a", "b"]], parser.parse("a b a b")
94 | end
95 | end
96 |
97 | def test_group_or_many
98 | with_parser("stmt: ('a' | 'b')*") do |parser|
99 | assert_equal [], parser.parse("")
100 | assert_equal [["a"], ["a"]], parser.parse("a a")
101 | assert_equal [["a"], ["b"]], parser.parse("a b")
102 | assert_equal [["a"], ["b"], ["b"], ["a"]], parser.parse("a b b a")
103 | end
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/test/assets/php_serialization.y:
--------------------------------------------------------------------------------
1 | # MIT License
2 | # See https://github.com/divoxx/ruby-php-serialization/blob/master/LICENSE.txt
3 |
4 | class PhpSerialization::Unserializer
5 | rule
6 |
7 | data : null ';' { @object = val[0] }
8 | | bool ';' { @object = val[0] }
9 | | integer ';' { @object = val[0] }
10 | | double ';' { @object = val[0] }
11 | | string ';' { @object = val[0] }
12 | | assoc_array { @object = val[0] }
13 | | object { @object = val[0] }
14 | ;
15 |
16 | null : 'N' { result = nil }
17 | ;
18 |
19 | bool : 'b' ':' NUMBER { result = Integer(val[2]) > 0 }
20 | ;
21 |
22 | integer : 'i' ':' NUMBER { result = Integer(val[2]) }
23 | ;
24 |
25 | double : 'd' ':' NUMBER { result = Float(val[2]) }
26 | ;
27 |
28 | string : 's' ':' NUMBER ':' STRING { result = val[4] }
29 | ;
30 |
31 | object : 'O' ':' NUMBER ':' STRING ':' NUMBER ':' '{' attribute_list '}'
32 | {
33 | if eval("defined?(#{val[4]})")
34 | result = Object.const_get(val[4]).new
35 |
36 | val[9].each do |(attr_name, value)|
37 | # Protected and private attributes will have a \0..\0 prefix
38 | attr_name = attr_name.gsub(/\A\\0[^\\]+\\0/, '')
39 | result.instance_variable_set("@#{attr_name}", value)
40 | end
41 | else
42 | klass_name = val[4].gsub(/^Struct::/, '')
43 | attr_names, values = [], []
44 |
45 | val[9].each do |(attr_name, value)|
46 | # Protected and private attributes will have a \0..\0 prefix
47 | attr_names << attr_name.gsub(/\A\\0[^\\]+\\0/, '')
48 | values << value
49 | end
50 |
51 | result = Struct.new(klass_name, *attr_names).new(*values)
52 | result.instance_variable_set("@_php_class", klass_name)
53 | end
54 | }
55 | ;
56 |
57 | attribute_list : attribute_list attribute { result = val[0] << val[1] }
58 | | { result = [] }
59 | ;
60 |
61 | attribute : data data { result = val }
62 | ;
63 |
64 | assoc_array : 'a' ':' NUMBER ':' '{' attribute_list '}'
65 | {
66 | # Checks if the keys are a sequence of integers
67 | idx = -1
68 | arr = val[5].all? { |(k,v)| k == (idx += 1) }
69 |
70 | if arr
71 | result = val[5].map { |(k,v)| v }
72 | else
73 | result = Hash[val[5]]
74 | end
75 | }
76 | ;
77 |
78 | end
79 |
80 | ---- header ----
81 | require 'php_serialization/tokenizer'
82 |
83 | ---- inner ----
84 | def initialize(tokenizer_klass = Tokenizer)
85 | @tokenizer_klass = tokenizer_klass
86 | end
87 |
88 | def run(string)
89 | @tokenizer = @tokenizer_klass.new(string)
90 | yyparse(@tokenizer, :each)
91 | return @object
92 | ensure
93 | @tokenizer = nil
94 | end
95 |
96 | def next_token
97 | @tokenizer.next_token
98 | end
99 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # -*- ruby -*-
2 |
3 | require "bundler/gem_tasks"
4 |
5 | require 'rdoc/task'
6 |
7 | spec = Gem::Specification.load("racc.gemspec")
8 |
9 | RDoc::Task.new(:docs) do |rd|
10 | rd.main = "README.rdoc"
11 | rd.rdoc_files.include(spec.files.find_all { |file_name|
12 | file_name =~ /^(bin|lib|ext)/ || file_name !~ /\//
13 | })
14 |
15 | title = "#{spec.name}-#{spec.version} Documentation"
16 | rd.options << "-t #{title}"
17 | rd.rdoc_dir = "_site"
18 | end
19 |
20 | def java?
21 | /java/ === RUBY_PLATFORM
22 | end
23 | def jruby?
24 | Object.const_defined?(:RUBY_ENGINE) and 'jruby' == RUBY_ENGINE
25 | end
26 |
27 | file 'lib/racc/parser-text.rb' => ['lib/racc/parser.rb', 'lib/racc/info.rb', __FILE__] do |t|
28 | source = 'lib/racc/parser.rb'
29 |
30 | text = File.read(source)
31 | text.sub!(/\A# *frozen[-_]string[-_]literal:.*\n/, '')
32 | text.gsub!(/(^#.*\n)+(?=module )/, "#--\n")
33 | text.gsub!(/^require '(.*)'$/) do
34 | lib = $1
35 | code = File.read("lib/#{lib}.rb")
36 | code.sub!(/\A(?:#.*\n)+/, '')
37 | %[unless $".find {|p| p.end_with?('/#{lib}.rb')}\n$".push "\#{__dir__}/#{lib}.rb"\n#{code}\nend\n]
38 | rescue
39 | $&
40 | end
41 | text << "#++\n"
42 | File.open(t.name, 'wb') { |io|
43 | io.write(<<-eorb)
44 | # :stopdoc:
45 | module Racc
46 | PARSER_TEXT = <<'__end_of_file__'
47 | #{text}
48 | __end_of_file__
49 | end
50 | eorb
51 | }
52 | end
53 |
54 | javasrc, = Dir.glob('ext/racc/**/Cparse.java')
55 | task :compile => javasrc do
56 | code = File.binread(javasrc)
57 | if code.sub!(/RACC_VERSION\s*=\s*"\K([^"]*)(?=")/) {|v| break if v == spec.version; spec.version}
58 | File.binwrite(javasrc, code)
59 | end
60 | end
61 |
62 | lib_dir = nil # for dummy rake/extensiontask.rb at ruby test-bundled-gems
63 | if jruby?
64 | # JRUBY
65 | require "rake/javaextensiontask"
66 | extask = Rake::JavaExtensionTask.new("cparse") do |ext|
67 | jruby_home = RbConfig::CONFIG['prefix']
68 | lib_dir = ext.lib_dir += "/#{ext.platform}/racc"
69 | ext.ext_dir = 'ext/racc'
70 | # source/target jvm
71 | if defined?(JRUBY_VERSION) && Gem::Version.new(JRUBY_VERSION) >= Gem::Version.new('10.0.0.0')
72 | # Use Java 21 for JRuby 10.0.0.0 or higher
73 | ext.source_version = '21'
74 | ext.target_version = '21'
75 | else
76 | # Use Java 8 if its lower than JRuby 10.0.0.0
77 | ext.source_version = '1.8'
78 | ext.target_version = '1.8'
79 | end
80 | jars = ["#{jruby_home}/lib/jruby.jar"] + FileList['lib/*.jar']
81 | ext.classpath = jars.map { |x| File.expand_path x }.join( ':' )
82 | ext.name = 'cparse-jruby'
83 | end
84 |
85 | task :build => "#{extask.lib_dir}/#{extask.name}.jar"
86 | else
87 | # MRI
88 | require "rake/extensiontask"
89 | extask = Rake::ExtensionTask.new "cparse" do |ext|
90 | lib_dir = ext.lib_dir += "/#{RUBY_VERSION}/#{ext.platform}/racc"
91 | ext.ext_dir = 'ext/racc/cparse'
92 | end
93 | end
94 |
95 | desc 'Make autogenerated sources'
96 | task :srcs => ['lib/racc/parser-text.rb']
97 |
98 | task :compile => :srcs
99 | task :build => :srcs
100 |
101 | task :test => :compile
102 |
103 | require 'rake/testtask'
104 |
105 | Rake::TestTask.new(:test) do |t|
106 | t.libs << lib_dir if lib_dir
107 | t.libs << "test/lib"
108 | ENV["RUBYOPT"] = "-I" + t.libs.join(File::PATH_SEPARATOR)
109 | t.ruby_opts << "-rhelper"
110 | t.test_files = FileList["test/**/test_*.rb"]
111 | if RUBY_VERSION >= "2.6"
112 | t.ruby_opts << "--enable-frozen-string-literal"
113 | t.ruby_opts << "--debug=frozen-string-literal" if RUBY_ENGINE != "truffleruby"
114 | end
115 | end
116 |
--------------------------------------------------------------------------------
/test/case.rb:
--------------------------------------------------------------------------------
1 | verbose = $VERBOSE
2 | $VERBOSE = true
3 | begin
4 |
5 | require 'test/unit'
6 | require 'racc/static'
7 | require 'fileutils'
8 | require 'tempfile'
9 | require 'timeout'
10 |
11 | module Racc
12 | class TestCase < Test::Unit::TestCase
13 | PROJECT_DIR = File.expand_path(File.join(__dir__, '..'))
14 | SAMPLE_DIR = File.join(PROJECT_DIR, 'sample')
15 |
16 | test_dir = File.join(PROJECT_DIR, 'test')
17 | test_dir = File.join(PROJECT_DIR, 'racc') unless File.exist?(test_dir)
18 | TEST_DIR = test_dir
19 | racc = File.join(PROJECT_DIR, 'bin', 'racc')
20 | racc = File.join(PROJECT_DIR, '..', 'libexec', 'racc') unless File.exist?(racc)
21 | racc = 'racc' unless File.exist?(racc)
22 |
23 | RACC = racc
24 | ASSET_DIR = File.join(TEST_DIR, 'assets') # test grammars
25 | REGRESS_DIR = File.join(TEST_DIR, 'regress') # known-good generated outputs
26 |
27 | LIB_DIR = File.expand_path("../../lib", __FILE__)
28 |
29 | INC = [
30 | File.join(PROJECT_DIR, 'lib'),
31 | File.join(PROJECT_DIR, 'ext'),
32 | ].join(':')
33 |
34 | def setup
35 | @TEMP_DIR = Dir.mktmpdir("racc")
36 | @OUT_DIR = File.join(@TEMP_DIR, 'out')
37 | @TAB_DIR = File.join(@TEMP_DIR, 'tab') # generated parsers go here
38 | @LOG_DIR = File.join(@TEMP_DIR, 'log')
39 | @ERR_DIR = File.join(@TEMP_DIR, 'err')
40 | FileUtils.mkdir_p([@OUT_DIR, @TAB_DIR, @LOG_DIR, @ERR_DIR])
41 | FileUtils.cp File.join(TEST_DIR, "src.intp"), @TEMP_DIR
42 | end
43 |
44 | def teardown
45 | FileUtils.rm_f(File.join(@TEMP_DIR, "src.intp"))
46 | FileUtils.rm_rf([@OUT_DIR, @TAB_DIR, @LOG_DIR, @ERR_DIR, @TEMP_DIR])
47 | end
48 |
49 | def assert_compile(asset, args = [], **opt)
50 | file = File.basename(asset, '.y')
51 | args = ([args].flatten) + [
52 | "#{ASSET_DIR}/#{file}.y",
53 | '-Do',
54 | "-O#{@OUT_DIR}/#{file}",
55 | "-o#{@TAB_DIR}/#{file}",
56 | ]
57 | racc(*args, **opt)
58 | end
59 |
60 | def assert_debugfile(asset, ok)
61 | file = File.basename(asset, '.y')
62 | Dir.chdir(@LOG_DIR) do
63 | File.foreach("#{file}.y") do |line|
64 | line.strip!
65 | case line
66 | when /sr/ then assert_equal "sr#{ok[0]}", line
67 | when /rr/ then assert_equal "rr#{ok[1]}", line
68 | when /un/ then assert_equal "un#{ok[2]}", line
69 | when /ur/ then assert_equal "ur#{ok[3]}", line
70 | when /ex/ then assert_equal "ex#{ok[4]}", line
71 | else
72 | raise TestFailed, 'racc outputs unknown debug report???'
73 | end
74 | end
75 | end
76 | end
77 |
78 | def assert_exec(asset, **opts)
79 | file = File.basename(asset, '.y')
80 | ruby "-I#{LIB_DIR}", "-rracc/parser", "#{@TAB_DIR}/#{file}", **opts
81 | end
82 |
83 | def strip_version(source)
84 | source.sub(/This file is automatically generated by Racc \d+\.\d+\.\d+(?:\.\S+)?/, '')
85 | end
86 |
87 | def assert_output_unchanged(asset)
88 | file = File.basename(asset, '.y')
89 |
90 | # Code to re-generate the expectation files
91 | # File.write("#{REGRESS_DIR}/#{file}", File.read("#{@TAB_DIR}/#{file}"))
92 |
93 | expected = File.read("#{REGRESS_DIR}/#{file}")
94 | actual = File.read("#{@TAB_DIR}/#{file}")
95 | result = (strip_version(expected) == strip_version(actual))
96 |
97 | assert(result, proc {`diff -u #{REGRESS_DIR}/#{file} #{@TAB_DIR}/#{file}`})
98 | end
99 |
100 | def racc(*arg, **opt)
101 | ruby "-I#{LIB_DIR}", "-S", RACC, *arg, **opt
102 | end
103 |
104 | def ruby(*arg, quiet: false, **opt)
105 | if quiet
106 | assert_in_out_err(["-C", @TEMP_DIR, *arg], **opt)
107 | else
108 | assert_ruby_status(["-C", @TEMP_DIR, *arg], **opt)
109 | end
110 | end
111 | end
112 | end
113 |
114 | ensure
115 | $VERBOSE = verbose
116 | end
117 |
--------------------------------------------------------------------------------
/test/regress/journey:
--------------------------------------------------------------------------------
1 | #
2 | # DO NOT MODIFY!!!!
3 | # This file is automatically generated by Racc 1.5.2
4 | # from Racc grammar file "journey.y".
5 | #
6 |
7 | require 'racc/parser.rb'
8 |
9 |
10 | require 'journey/parser_extras'
11 | module Journey
12 | class Parser < Racc::Parser
13 | ##### State transition tables begin ###
14 |
15 | racc_action_table = [
16 | 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
17 | 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
18 | 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
19 | 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
20 |
21 | racc_action_check = [
22 | 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
23 | 20, 20, 20, 20, 20, 20, 20, 20, 0, 0,
24 | 0, 0, nil, 0, 0, 7, 7, 7, 7, nil,
25 | 7, 7, 19, 19, 19, 19, nil, 19, 19 ]
26 |
27 | racc_action_pointer = [
28 | 16, 0, nil, nil, nil, nil, nil, 23, nil, nil,
29 | nil, nil, nil, nil, nil, nil, nil, 1, nil, 30,
30 | 8, nil, nil, nil ]
31 |
32 | racc_action_default = [
33 | -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
34 | -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
35 | -18, 24, -8, -7 ]
36 |
37 | racc_goto_table = [
38 | 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
39 | nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
40 |
41 | racc_goto_check = [
42 | 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
43 | nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
44 |
45 | racc_goto_pointer = [
46 | nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
47 | nil ]
48 |
49 | racc_goto_default = [
50 | nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
51 | 12 ]
52 |
53 | racc_reduce_table = [
54 | 0, 0, :racc_error,
55 | 2, 11, :_reduce_1,
56 | 1, 11, :_reduce_2,
57 | 1, 11, :_reduce_none,
58 | 1, 12, :_reduce_none,
59 | 1, 12, :_reduce_none,
60 | 1, 12, :_reduce_none,
61 | 3, 15, :_reduce_7,
62 | 3, 13, :_reduce_8,
63 | 1, 16, :_reduce_9,
64 | 1, 14, :_reduce_none,
65 | 1, 14, :_reduce_none,
66 | 1, 14, :_reduce_none,
67 | 1, 14, :_reduce_none,
68 | 1, 19, :_reduce_14,
69 | 1, 17, :_reduce_15,
70 | 1, 18, :_reduce_16,
71 | 1, 20, :_reduce_17 ]
72 |
73 | racc_reduce_n = 18
74 |
75 | racc_shift_n = 24
76 |
77 | racc_token_table = {
78 | false => 0,
79 | :error => 1,
80 | :SLASH => 2,
81 | :LITERAL => 3,
82 | :SYMBOL => 4,
83 | :LPAREN => 5,
84 | :RPAREN => 6,
85 | :DOT => 7,
86 | :STAR => 8,
87 | :OR => 9 }
88 |
89 | racc_nt_base = 10
90 |
91 | racc_use_result_var = true
92 |
93 | Racc_arg = [
94 | racc_action_table,
95 | racc_action_check,
96 | racc_action_default,
97 | racc_action_pointer,
98 | racc_goto_table,
99 | racc_goto_check,
100 | racc_goto_default,
101 | racc_goto_pointer,
102 | racc_nt_base,
103 | racc_reduce_table,
104 | racc_token_table,
105 | racc_shift_n,
106 | racc_reduce_n,
107 | racc_use_result_var ]
108 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
109 |
110 | Racc_token_to_s_table = [
111 | "$end",
112 | "error",
113 | "SLASH",
114 | "LITERAL",
115 | "SYMBOL",
116 | "LPAREN",
117 | "RPAREN",
118 | "DOT",
119 | "STAR",
120 | "OR",
121 | "$start",
122 | "expressions",
123 | "expression",
124 | "or",
125 | "terminal",
126 | "group",
127 | "star",
128 | "symbol",
129 | "literal",
130 | "slash",
131 | "dot" ]
132 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
133 |
134 | Racc_debug_parser = false
135 |
136 | ##### State transition tables end #####
137 |
138 | # reduce 0 omitted
139 |
140 | module_eval(<<'.,.,', 'journey.y', 6)
141 | def _reduce_1(val, _values, result)
142 | result = Cat.new(val.first, val.last)
143 | result
144 | end
145 | .,.,
146 |
147 | module_eval(<<'.,.,', 'journey.y', 7)
148 | def _reduce_2(val, _values, result)
149 | result = val.first
150 | result
151 | end
152 | .,.,
153 |
154 | # reduce 3 omitted
155 |
156 | # reduce 4 omitted
157 |
158 | # reduce 5 omitted
159 |
160 | # reduce 6 omitted
161 |
162 | module_eval(<<'.,.,', 'journey.y', 16)
163 | def _reduce_7(val, _values, result)
164 | result = Group.new(val[1])
165 | result
166 | end
167 | .,.,
168 |
169 | module_eval(<<'.,.,', 'journey.y', 19)
170 | def _reduce_8(val, _values, result)
171 | result = Or.new([val.first, val.last])
172 | result
173 | end
174 | .,.,
175 |
176 | module_eval(<<'.,.,', 'journey.y', 22)
177 | def _reduce_9(val, _values, result)
178 | result = Star.new(Symbol.new(val.last))
179 | result
180 | end
181 | .,.,
182 |
183 | # reduce 10 omitted
184 |
185 | # reduce 11 omitted
186 |
187 | # reduce 12 omitted
188 |
189 | # reduce 13 omitted
190 |
191 | module_eval(<<'.,.,', 'journey.y', 31)
192 | def _reduce_14(val, _values, result)
193 | result = Slash.new('/')
194 | result
195 | end
196 | .,.,
197 |
198 | module_eval(<<'.,.,', 'journey.y', 34)
199 | def _reduce_15(val, _values, result)
200 | result = Symbol.new(val.first)
201 | result
202 | end
203 | .,.,
204 |
205 | module_eval(<<'.,.,', 'journey.y', 37)
206 | def _reduce_16(val, _values, result)
207 | result = Literal.new(val.first)
208 | result
209 | end
210 | .,.,
211 |
212 | module_eval(<<'.,.,', 'journey.y', 39)
213 | def _reduce_17(val, _values, result)
214 | result = Dot.new(val.first)
215 | result
216 | end
217 | .,.,
218 |
219 | def _reduce_none(val, _values, result)
220 | val[0]
221 | end
222 |
223 | end # class Parser
224 | end # module Journey
225 |
--------------------------------------------------------------------------------
/test/regress/frozen:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | #
3 | # DO NOT MODIFY!!!!
4 | # This file is automatically generated by Racc 1.5.2
5 | # from Racc grammar file "frozen.y".
6 | #
7 |
8 | require 'racc/parser.rb'
9 |
10 |
11 | require 'journey/parser_extras'
12 | module Journey
13 | class Parser < Racc::Parser
14 | ##### State transition tables begin ###
15 |
16 | racc_action_table = [
17 | 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
18 | 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
19 | 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
20 | 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
21 |
22 | racc_action_check = [
23 | 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
24 | 20, 20, 20, 20, 20, 20, 20, 20, 0, 0,
25 | 0, 0, nil, 0, 0, 7, 7, 7, 7, nil,
26 | 7, 7, 19, 19, 19, 19, nil, 19, 19 ]
27 |
28 | racc_action_pointer = [
29 | 16, 0, nil, nil, nil, nil, nil, 23, nil, nil,
30 | nil, nil, nil, nil, nil, nil, nil, 1, nil, 30,
31 | 8, nil, nil, nil ]
32 |
33 | racc_action_default = [
34 | -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
35 | -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
36 | -18, 24, -8, -7 ]
37 |
38 | racc_goto_table = [
39 | 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
40 | nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
41 |
42 | racc_goto_check = [
43 | 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
44 | nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
45 |
46 | racc_goto_pointer = [
47 | nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
48 | nil ]
49 |
50 | racc_goto_default = [
51 | nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
52 | 12 ]
53 |
54 | racc_reduce_table = [
55 | 0, 0, :racc_error,
56 | 2, 11, :_reduce_1,
57 | 1, 11, :_reduce_2,
58 | 1, 11, :_reduce_none,
59 | 1, 12, :_reduce_none,
60 | 1, 12, :_reduce_none,
61 | 1, 12, :_reduce_none,
62 | 3, 15, :_reduce_7,
63 | 3, 13, :_reduce_8,
64 | 1, 16, :_reduce_9,
65 | 1, 14, :_reduce_none,
66 | 1, 14, :_reduce_none,
67 | 1, 14, :_reduce_none,
68 | 1, 14, :_reduce_none,
69 | 1, 19, :_reduce_14,
70 | 1, 17, :_reduce_15,
71 | 1, 18, :_reduce_16,
72 | 1, 20, :_reduce_17 ]
73 |
74 | racc_reduce_n = 18
75 |
76 | racc_shift_n = 24
77 |
78 | racc_token_table = {
79 | false => 0,
80 | :error => 1,
81 | :SLASH => 2,
82 | :LITERAL => 3,
83 | :SYMBOL => 4,
84 | :LPAREN => 5,
85 | :RPAREN => 6,
86 | :DOT => 7,
87 | :STAR => 8,
88 | :OR => 9 }
89 |
90 | racc_nt_base = 10
91 |
92 | racc_use_result_var = true
93 |
94 | Racc_arg = [
95 | racc_action_table,
96 | racc_action_check,
97 | racc_action_default,
98 | racc_action_pointer,
99 | racc_goto_table,
100 | racc_goto_check,
101 | racc_goto_default,
102 | racc_goto_pointer,
103 | racc_nt_base,
104 | racc_reduce_table,
105 | racc_token_table,
106 | racc_shift_n,
107 | racc_reduce_n,
108 | racc_use_result_var ]
109 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
110 |
111 | Racc_token_to_s_table = [
112 | "$end",
113 | "error",
114 | "SLASH",
115 | "LITERAL",
116 | "SYMBOL",
117 | "LPAREN",
118 | "RPAREN",
119 | "DOT",
120 | "STAR",
121 | "OR",
122 | "$start",
123 | "expressions",
124 | "expression",
125 | "or",
126 | "terminal",
127 | "group",
128 | "star",
129 | "symbol",
130 | "literal",
131 | "slash",
132 | "dot" ]
133 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
134 |
135 | Racc_debug_parser = false
136 |
137 | ##### State transition tables end #####
138 |
139 | # reduce 0 omitted
140 |
141 | module_eval(<<'.,.,', 'frozen.y', 6)
142 | def _reduce_1(val, _values, result)
143 | result = Cat.new(val.first, val.last)
144 | result
145 | end
146 | .,.,
147 |
148 | module_eval(<<'.,.,', 'frozen.y', 7)
149 | def _reduce_2(val, _values, result)
150 | result = val.first
151 | result
152 | end
153 | .,.,
154 |
155 | # reduce 3 omitted
156 |
157 | # reduce 4 omitted
158 |
159 | # reduce 5 omitted
160 |
161 | # reduce 6 omitted
162 |
163 | module_eval(<<'.,.,', 'frozen.y', 16)
164 | def _reduce_7(val, _values, result)
165 | result = Group.new(val[1])
166 | result
167 | end
168 | .,.,
169 |
170 | module_eval(<<'.,.,', 'frozen.y', 19)
171 | def _reduce_8(val, _values, result)
172 | result = Or.new([val.first, val.last])
173 | result
174 | end
175 | .,.,
176 |
177 | module_eval(<<'.,.,', 'frozen.y', 22)
178 | def _reduce_9(val, _values, result)
179 | result = Star.new(Symbol.new(val.last))
180 | result
181 | end
182 | .,.,
183 |
184 | # reduce 10 omitted
185 |
186 | # reduce 11 omitted
187 |
188 | # reduce 12 omitted
189 |
190 | # reduce 13 omitted
191 |
192 | module_eval(<<'.,.,', 'frozen.y', 31)
193 | def _reduce_14(val, _values, result)
194 | result = Slash.new('/')
195 | result
196 | end
197 | .,.,
198 |
199 | module_eval(<<'.,.,', 'frozen.y', 34)
200 | def _reduce_15(val, _values, result)
201 | result = Symbol.new(val.first)
202 | result
203 | end
204 | .,.,
205 |
206 | module_eval(<<'.,.,', 'frozen.y', 37)
207 | def _reduce_16(val, _values, result)
208 | result = Literal.new(val.first)
209 | result
210 | end
211 | .,.,
212 |
213 | module_eval(<<'.,.,', 'frozen.y', 39)
214 | def _reduce_17(val, _values, result)
215 | result = Dot.new(val.first)
216 | result
217 | end
218 | .,.,
219 |
220 | def _reduce_none(val, _values, result)
221 | val[0]
222 | end
223 |
224 | end # class Parser
225 | end # module Journey
226 |
--------------------------------------------------------------------------------
/doc/en/grammar.en.rdoc:
--------------------------------------------------------------------------------
1 | = Racc Grammar File Reference
2 |
3 | == Global Structure
4 |
5 | == Class Block and User Code Block
6 |
7 | There are two top-level blocks: the 'class' block, and the 'user code'
8 | block. The 'user code' block MUST be after the 'class' block.
9 |
10 | == Comment
11 |
12 | Comments can be added about everywhere. Two comment styles are
13 | supported: Ruby style (`# ...`) and C style (`/* ... */`).
14 |
15 | == Class Block
16 |
17 | The class block is formed like this:
18 |
19 | class CLASS_NAME
20 | [precedence table]
21 | [token declarations]
22 | [expected number of S/R conflict]
23 | [options]
24 | [semantic value conversion]
25 | [start rule]
26 | rule
27 | GRAMMARS
28 |
29 | CLASS_NAME is a name of parser class.
30 | This is the name of generating parser class.
31 |
32 | If CLASS_NAME includes '::', Racc outputs module clause.
33 | For example, writing "class M::C" causes creating the code below:
34 |
35 | module M
36 | class C
37 | :
38 | :
39 | end
40 | end
41 |
42 | == Grammar Block
43 |
44 | The grammar block describes the grammar
45 | to be understood by parser. Syntax is:
46 |
47 | (token): (token) (token) (token).... (action)
48 |
49 | (token): (token) (token) (token).... (action)
50 | | (token) (token) (token).... (action)
51 | | (token) (token) (token).... (action)
52 |
53 | (action) is an action which is executed when its (token)s are found.
54 | (action) is a ruby code block, which is surrounded by braces:
55 |
56 | { print val[0]
57 | puts val[1] }
58 |
59 | Note that you cannot use '%' string, here document, '%r' regexp in action.
60 |
61 | Actions can be omitted.
62 | When it is omitted, '' (empty string) is used.
63 |
64 | A return value of action is a value of left side value ($$).
65 | It is value of result, or returned value by "return" statement.
66 |
67 | Here is an example of whole grammar block.
68 |
69 | rule
70 | goal: definition rules source { result = val }
71 |
72 | definition: /* none */ { result = [] }
73 | | definition startdesig { result[0] = val[1] }
74 | | definition
75 | precrule # this line continue from upper line
76 | {
77 | result[1] = val[1]
78 | }
79 |
80 | startdesig: START TOKEN
81 |
82 | You can use following special local variables in action.
83 |
84 | * result ($$)
85 |
86 | The value of left-hand side (lhs). A default value is val[0].
87 |
88 | * val ($1,$2,$3...)
89 |
90 | An array of value of right-hand side (rhs).
91 |
92 | * _values (...$-2,$-1,$0)
93 |
94 | A stack of values.
95 | DO NOT MODIFY this stack unless you know what you are doing.
96 |
97 | == Operator Precedence
98 |
99 | This function is equal to '%prec' in yacc.
100 | To designate this block:
101 |
102 | prechigh
103 | nonassoc '++'
104 | left '*' '/'
105 | left '+' '-'
106 | right '='
107 | preclow
108 |
109 | `right' is yacc's %right, `left' is yacc's %left.
110 |
111 | `=' + (symbol) means yacc's %prec:
112 |
113 | prechigh
114 | nonassoc UMINUS
115 | left '*' '/'
116 | left '+' '-'
117 | preclow
118 |
119 | rule
120 | exp: exp '*' exp
121 | | exp '-' exp
122 | | '-' exp =UMINUS # equals to "%prec UMINUS"
123 | :
124 | :
125 |
126 | == expect
127 |
128 | Racc supports Bison's "expect" directive to declare the expected
129 | number of shift/reduce conflicts.
130 |
131 | class MyParser
132 | expect 3
133 | rule
134 | :
135 | :
136 |
137 | Then warnings are issued only when the effective number of conflicts differs.
138 |
139 | == Declaring Tokens
140 |
141 | Declaring tokens avoids many bugs.
142 |
143 | Racc outputs warnings for declared tokens that do not exist, or existing tokens not declared.
144 | The syntax is:
145 |
146 | token TOKEN_NAME AND_IS_THIS
147 | ALSO_THIS_IS AGAIN_AND_AGAIN THIS_IS_LAST
148 |
149 | == Options
150 |
151 | You can write options for racc command in your racc file.
152 |
153 | options OPTION OPTION ...
154 |
155 | Options are:
156 |
157 | * omit_action_call
158 |
159 | omit empty action call or not.
160 |
161 | * result_var
162 |
163 | use/does not use local variable "result"
164 |
165 | You can use 'no_' prefix to invert its meanings.
166 |
167 | == Converting Token Symbol
168 |
169 | Token symbols are, as default,
170 |
171 | * naked token strings in racc file (TOK, XFILE, this_is_token, ...)
172 | --> symbol (:TOK, :XFILE, :this_is_token, ...)
173 | * quoted strings (':', '.', '(', ...)
174 | --> same string (':', '.', '(', ...)
175 |
176 | You can change this default using a "convert" block.
177 | Here is an example:
178 |
179 | convert
180 | PLUS 'PlusClass' # We use PlusClass for symbol of `PLUS'
181 | MIN 'MinusClass' # We use MinusClass for symbol of `MIN'
182 | end
183 |
184 | We can use almost all ruby value can be used by token symbol,
185 | except 'false' and 'nil'. These are causes unexpected parse error.
186 |
187 | If you want to use String as token symbol, special care is required.
188 | For example:
189 |
190 | convert
191 | class '"cls"' # in code, "cls"
192 | PLUS '"plus\n"' # in code, "plus\n"
193 | MIN "\"minus#{val}\"" # in code, \"minus#{val}\"
194 | end
195 |
196 | == Start Rule
197 |
198 | '%start' in yacc. This changes the start symbol.
199 |
200 | start real_target
201 |
202 | == User Code Block
203 |
204 | A "User Code Block" is a piece of Ruby source code copied in the output.
205 | There are three user code blocks, "header" "inner" and "footer".
206 |
207 | User code blocks are introduced by four '-' at the beginning of a line,
208 | followed by a single-word name:
209 |
210 | ---- header
211 | ruby statement
212 | ruby statement
213 | ruby statement
214 |
215 | ---- inner
216 | ruby statement
217 | :
218 | :
219 |
--------------------------------------------------------------------------------
/doc/en/grammar2.en.rdoc:
--------------------------------------------------------------------------------
1 | = Racc Grammar File Reference
2 |
3 | == Global Structure
4 |
5 | == Class Block and User Code Block
6 |
7 | There are two blocks on the toplevel. One is the 'class' block, the other is the 'user code'
8 | block. The 'user code' block MUST be placed after the 'class' block.
9 |
10 | == Comments
11 |
12 | You can insert comments about all places. Two styles of comments can be used, Ruby style '#.....' and C style '/\*......*\/'.
13 |
14 | == Class Block
15 |
16 | The class block is formed like this:
17 |
18 | class CLASS_NAME
19 | [precedence table]
20 | [token declarations]
21 | [expected number of S/R conflicts]
22 | [options]
23 | [semantic value conversion]
24 | [start rule]
25 | rule
26 | GRAMMARS
27 |
28 | CLASS_NAME is a name of the parser class. This is the name of the generating parser
29 | class.
30 |
31 | If CLASS_NAME includes '::', Racc outputs the module clause. For example, writing
32 | "class M::C" causes the code below to be created:
33 |
34 | module M
35 | class C
36 | :
37 | :
38 | end
39 | end
40 |
41 | == Grammar Block
42 |
43 | The grammar block describes grammar which is able to be understood by the parser.
44 | Syntax is:
45 |
46 | (token): (token) (token) (token).... (action)
47 |
48 | (token): (token) (token) (token).... (action)
49 | | (token) (token) (token).... (action)
50 | | (token) (token) (token).... (action)
51 |
52 | (action) is an action which is executed when its (token)s are found.
53 | (action) is a ruby code block, which is surrounded by braces:
54 |
55 | { print val[0]
56 | puts val[1] }
57 |
58 | Note that you cannot use '%' string, here document, '%r' regexp in action.
59 |
60 | Actions can be omitted. When it is omitted, '' (empty string) is used.
61 |
62 | A return value of action is a value of the left side value ($$). It is the value of the
63 | result, or the returned value by `return` statement.
64 |
65 | Here is an example of the whole grammar block.
66 |
67 | rule
68 | goal: definition rules source { result = val }
69 |
70 | definition: /* none */ { result = [] }
71 | | definition startdesig { result[0] = val[1] }
72 | | definition
73 | precrule # this line continues from upper line
74 | {
75 | result[1] = val[1]
76 | }
77 |
78 | startdesig: START TOKEN
79 |
80 | You can use the following special local variables in action:
81 |
82 | * result ($$)
83 |
84 | The value of the left-hand side (lhs). A default value is val[0].
85 |
86 | * val ($1,$2,$3...)
87 |
88 | An array of value of the right-hand side (rhs).
89 |
90 | * _values (...$-2,$-1,$0)
91 |
92 | A stack of values. DO NOT MODIFY this stack unless you know what you are doing.
93 |
94 | == Operator Precedence
95 |
96 | This function is equal to '%prec' in yacc.
97 | To designate this block:
98 |
99 | prechigh
100 | nonassoc '++'
101 | left '*' '/'
102 | left '+' '-'
103 | right '='
104 | preclow
105 |
106 | `right` is yacc's %right, `left` is yacc's %left.
107 |
108 | `=` + (symbol) means yacc's %prec:
109 |
110 | prechigh
111 | nonassoc UMINUS
112 | left '*' '/'
113 | left '+' '-'
114 | preclow
115 |
116 | rule
117 | exp: exp '*' exp
118 | | exp '-' exp
119 | | '-' exp =UMINUS # equals to "%prec UMINUS"
120 | :
121 | :
122 |
123 | == expect
124 |
125 | Racc has bison's "expect" directive.
126 |
127 | # Example
128 |
129 | class MyParser
130 | expect 3
131 | rule
132 | :
133 | :
134 |
135 | This directive declares "expected" number of shift/reduce conflicts. If
136 | "expected" number is equal to real number of conflicts, Racc does not print
137 | conflict warning message.
138 |
139 | == Declaring Tokens
140 |
141 | By declaring tokens, you can avoid many meaningless bugs. If declared token
142 | does not exist or existing token is not declared, Racc output warnings.
143 | Declaration syntax is:
144 |
145 | token TOKEN_NAME AND_IS_THIS
146 | ALSO_THIS_IS AGAIN_AND_AGAIN THIS_IS_LAST
147 |
148 | == Options
149 |
150 | You can write options for Racc command in your Racc file.
151 |
152 | options OPTION OPTION ...
153 |
154 | Options are:
155 |
156 | * omit_action_call
157 |
158 | omits empty action call or not.
159 |
160 | * result_var
161 |
162 | uses local variable "result" or not.
163 |
164 | You can use 'no_' prefix to invert their meanings.
165 |
166 | == Converting Token Symbol
167 |
168 | Token symbols are, as default,
169 |
170 | * naked token string in Racc file (TOK, XFILE, this_is_token, ...)
171 | --> symbol (:TOK, :XFILE, :this_is_token, ...)
172 | * quoted string (':', '.', '(', ...)
173 | --> same string (':', '.', '(', ...)
174 |
175 | You can change this default by "convert" block.
176 | Here is an example:
177 |
178 | convert
179 | PLUS 'PlusClass' # We use PlusClass for symbol of `PLUS'
180 | MIN 'MinusClass' # We use MinusClass for symbol of `MIN'
181 | end
182 |
183 | We can use almost all ruby value can be used by token symbol,
184 | except 'false' and 'nil'. These cause unexpected parse error.
185 |
186 | If you want to use String as token symbol, special care is required.
187 | For example:
188 |
189 | convert
190 | class '"cls"' # in code, "cls"
191 | PLUS '"plus\n"' # in code, "plus\n"
192 | MIN "\"minus#{val}\"" # in code, \"minus#{val}\"
193 | end
194 |
195 | == Start Rule
196 |
197 | '%start' in yacc. This changes start rule.
198 |
199 | start real_target
200 |
201 | == User Code Block
202 |
203 | "User Code Block" is a Ruby source code which is copied to output. There are
204 | three user code blocks, "header" "inner" and "footer".
205 |
206 | Format of user code is like this:
207 |
208 | ---- header
209 | ruby statement
210 | ruby statement
211 | ruby statement
212 |
213 | ---- inner
214 | ruby statement
215 | :
216 | :
217 |
218 | If four '-' exist on the line head, Racc treats it as the beginning of the
219 | user code block. The name of the user code block must be one word.
220 |
--------------------------------------------------------------------------------
/lib/racc/logfilegenerator.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | module Racc
14 |
15 | class LogFileGenerator
16 |
17 | def initialize(states, debug_flags = DebugFlags.new)
18 | @states = states
19 | @grammar = states.grammar
20 | @debug_flags = debug_flags
21 | end
22 |
23 | def output(out)
24 | output_conflict out; out.puts
25 | output_useless out; out.puts
26 | output_rule out; out.puts
27 | output_token out; out.puts
28 | output_state out
29 | end
30 |
31 | #
32 | # Warnings
33 | #
34 |
35 | def output_conflict(out)
36 | @states.each do |state|
37 | if state.srconf
38 | out.printf "state %d contains %d shift/reduce conflicts\n",
39 | state.stateid, state.srconf.size
40 | end
41 | if state.rrconf
42 | out.printf "state %d contains %d reduce/reduce conflicts\n",
43 | state.stateid, state.rrconf.size
44 | end
45 | end
46 | end
47 |
48 | def output_useless(out)
49 | @grammar.each do |rl|
50 | if rl.useless?
51 | out.printf "rule %d (%s) never reduced\n",
52 | rl.ident, rl.target.to_s
53 | end
54 | end
55 | @grammar.each_nonterminal do |t|
56 | if t.useless?
57 | out.printf "useless nonterminal %s\n", t.to_s
58 | end
59 | end
60 | end
61 |
62 | #
63 | # States
64 | #
65 |
66 | def output_state(out)
67 | out << "--------- State ---------\n"
68 |
69 | showall = @debug_flags.la || @debug_flags.state
70 | @states.each do |state|
71 | out << "\nstate #{state.ident}\n\n"
72 |
73 | (showall ? state.closure : state.core).each do |ptr|
74 | pointer_out(out, ptr) if ptr.rule.ident != 0 or showall
75 | end
76 | out << "\n"
77 |
78 | action_out out, state
79 | end
80 | end
81 |
82 | def pointer_out(out, ptr)
83 | buf = sprintf("%4d) %s :", ptr.rule.ident, ptr.rule.target.to_s)
84 | ptr.rule.symbols.each_with_index do |tok, idx|
85 | buf << ' _' if idx == ptr.index
86 | buf << ' ' << tok.to_s
87 | end
88 | buf << ' _' if ptr.reduce?
89 | out.puts buf
90 | end
91 |
92 | def action_out(f, state)
93 | sr = state.srconf && state.srconf.dup
94 | rr = state.rrconf && state.rrconf.dup
95 | acts = state.action
96 | keys = acts.keys
97 | keys.sort! {|a,b| a.ident <=> b.ident }
98 |
99 | [ Shift, Reduce, Error, Accept ].each do |klass|
100 | keys.delete_if do |tok|
101 | act = acts[tok]
102 | if act.kind_of?(klass)
103 | outact f, tok, act
104 | if sr and c = sr.delete(tok)
105 | outsrconf f, c
106 | end
107 | if rr and c = rr.delete(tok)
108 | outrrconf f, c
109 | end
110 |
111 | true
112 | else
113 | false
114 | end
115 | end
116 | end
117 | sr.each {|tok, c| outsrconf f, c } if sr
118 | rr.each {|tok, c| outrrconf f, c } if rr
119 |
120 | act = state.defact
121 | if not act.kind_of?(Error) or @debug_flags.any?
122 | outact f, '$default', act
123 | end
124 |
125 | f.puts
126 | state.goto_table.each do |t, st|
127 | if t.nonterminal?
128 | f.printf " %-12s go to state %d\n", t.to_s, st.ident
129 | end
130 | end
131 | end
132 |
133 | def outact(f, t, act)
134 | case act
135 | when Shift
136 | f.printf " %-12s shift, and go to state %d\n",
137 | t.to_s, act.goto_id
138 | when Reduce
139 | f.printf " %-12s reduce using rule %d (%s)\n",
140 | t.to_s, act.ruleid, act.rule.target.to_s
141 | when Accept
142 | f.printf " %-12s accept\n", t.to_s
143 | when Error
144 | f.printf " %-12s error\n", t.to_s
145 | else
146 | raise "racc: fatal: wrong act for outact: act=#{act}(#{act.class})"
147 | end
148 | end
149 |
150 | def outsrconf(f, confs)
151 | confs.each do |c|
152 | r = c.reduce
153 | f.printf " %-12s [reduce using rule %d (%s)]\n",
154 | c.shift.to_s, r.ident, r.target.to_s
155 | end
156 | end
157 |
158 | def outrrconf(f, confs)
159 | confs.each do |c|
160 | r = c.low_prec
161 | f.printf " %-12s [reduce using rule %d (%s)]\n",
162 | c.token.to_s, r.ident, r.target.to_s
163 | end
164 | end
165 |
166 | #
167 | # Rules
168 | #
169 |
170 | def output_rule(out)
171 | out.print "-------- Grammar --------\n\n"
172 | @grammar.each do |rl|
173 | if @debug_flags.any? or rl.ident != 0
174 | out.printf "rule %d %s: %s\n",
175 | rl.ident, rl.target.to_s, rl.symbols.join(' ')
176 | end
177 | end
178 | end
179 |
180 | #
181 | # Tokens
182 | #
183 |
184 | def output_token(out)
185 | out.print "------- Symbols -------\n\n"
186 |
187 | out.print "**Nonterminals, with rules where they appear\n\n"
188 | @grammar.each_nonterminal do |t|
189 | tmp = <=", val[2]) }
49 | | boolean_expression '>' additive_expression { result = OperationNode.new(val[0], ">", val[2]) }
50 | | boolean_expression '<' additive_expression { result = OperationNode.new(val[0], "<", val[2]) }
51 | ;
52 |
53 | inverse_expression
54 | : boolean_expression
55 | | NOT boolean_expression { result = BooleanInverseNode.new(val[1]) }
56 | ;
57 |
58 | logical_expression
59 | : inverse_expression
60 | | logical_expression AND inverse_expression { result = OperationNode.new(val[0], "and", val[2]) }
61 | | logical_expression OR inverse_expression { result = OperationNode.new(val[0], "or", val[2]) }
62 | ;
63 |
64 | filter
65 | : IDENTIFIER { result = FilterNode.new(val[0].value) }
66 | | IDENTIFIER ':' parameter_list { result = FilterNode.new(val[0].value, val[2]) }
67 | ;
68 |
69 | filter_list
70 | : filter { result = [val[0]] }
71 | | filter_list '|' filter { result = val[0].push(val[2]) }
72 | ;
73 |
74 | filtered_expression
75 | : logical_expression
76 | | logical_expression '|' filter_list { result = FilteredValueNode.new(val[0], val[2]) }
77 | ;
78 |
79 | inject_statement
80 | : VAR_OPEN filtered_expression VAR_CLOSE { result = val[1] }
81 | ;
82 |
83 | if_tag
84 | : STMT_OPEN IF logical_expression STMT_CLOSE { open_scope!; result = val[2] }
85 | | STMT_OPEN UNLESS logical_expression STMT_CLOSE { open_scope!; result = BooleanInverseNode.new(val[2]) }
86 | ;
87 |
88 | else_tag
89 | : STMT_OPEN ELSE STMT_CLOSE { result = close_scope!; open_scope! }
90 | ;
91 |
92 | end_if_tag
93 | : STMT_OPEN ENDIF STMT_CLOSE { result = close_scope! }
94 | | STMT_OPEN ENDUNLESS STMT_CLOSE { result = close_scope! }
95 | ;
96 |
97 | if_block
98 | : if_tag end_if_tag { result = IfNode.new(val[0], val[1]) }
99 | | if_tag document end_if_tag { result = IfNode.new(val[0], val[2]) }
100 | | if_tag else_tag document end_if_tag { result = IfNode.new(val[0], val[1], val[3]) }
101 | | if_tag document else_tag end_if_tag { result = IfNode.new(val[0], val[2], val[3]) }
102 | | if_tag document else_tag document end_if_tag { result = IfNode.new(val[0], val[2], val[4]) }
103 | ;
104 |
105 | for_tag
106 | : STMT_OPEN FOR IDENTIFIER IN filtered_expression STMT_CLOSE { open_scope!; result = [val[2].value, val[4]] }
107 | ;
108 |
109 | end_for_tag
110 | : STMT_OPEN ENDFOR STMT_CLOSE { result = close_scope! }
111 | ;
112 |
113 | /* this has a shift/reduce conflict but since Racc will shift in this case it is the correct behavior */
114 | for_block
115 | : for_tag end_for_tag { result = ForNode.new(VariableNode.new(val[0].first), val[0].last, val[1]) }
116 | | for_tag document end_for_tag { result = ForNode.new(VariableNode.new(val[0].first), val[0].last, val[2]) }
117 | ;
118 |
119 | block_tag
120 | : STMT_OPEN BLOCK IDENTIFIER STMT_CLOSE { result = open_block_scope!(val[2].value) }
121 | ;
122 |
123 | end_block_tag
124 | : STMT_OPEN ENDBLOCK STMT_CLOSE { result = close_block_scope! }
125 | ;
126 |
127 | /* this has a shift/reduce conflict but since Racc will shift in this case it is the correct behavior */
128 | block_block
129 | : block_tag end_block_tag { result = BlockNode.new(val[0], val[1]) }
130 | | block_tag document end_block_tag { result = BlockNode.new(val[0], val[2]) }
131 | ;
132 |
133 | generic_block_tag
134 | : STMT_OPEN IDENTIFIER STMT_CLOSE { open_scope!; result = [val[1].value, []] }
135 | | STMT_OPEN IDENTIFIER parameter_list STMT_CLOSE { open_scope!; result = [val[1].value, val[2]] }
136 | ;
137 |
138 | end_generic_block_tag
139 | : STMT_OPEN END STMT_CLOSE { result = close_scope! }
140 | ;
141 |
142 | generic_block
143 | : generic_block_tag document end_generic_block_tag { result = GenericBlockNode.new(val[0].first, val[2], val[0].last) }
144 | ;
145 |
146 | extends_statement
147 | : STMT_OPEN EXTENDS STRING STMT_CLOSE { result = val[2].value }
148 | | STMT_OPEN EXTENDS IDENTIFIER STMT_CLOSE { result = VariableNode.new(val[2].value) }
149 | ;
150 |
151 | document_component
152 | : TEXT_BLOCK { result = TextNode.new(val[0].value) }
153 | | inject_statement
154 | | if_block
155 | | for_block
156 | | generic_block
157 | | block_block
158 | ;
159 |
160 | document
161 | : document_component { push val[0] }
162 | | document document_component { push val[1] }
163 | | extends_statement { document.extends = val[0] }
164 | | document extends_statement { document.extends = val[1] }
165 | ;
166 |
167 | ---- header ----
168 | # racc_parser.rb : generated by racc
169 |
170 | ---- inner ----
171 |
--------------------------------------------------------------------------------
/test/assets/nokogiri-css.y:
--------------------------------------------------------------------------------
1 | class Nokogiri::CSS::Parser
2 |
3 | token FUNCTION INCLUDES DASHMATCH LBRACE HASH PLUS GREATER S STRING IDENT
4 | token COMMA NUMBER PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH TILDE NOT_EQUAL
5 | token SLASH DOUBLESLASH NOT EQUAL RPAREN LSQUARE RSQUARE HAS
6 |
7 | rule
8 | selector
9 | : selector COMMA simple_selector_1toN {
10 | result = [val.first, val.last].flatten
11 | }
12 | | prefixless_combinator_selector { result = val.flatten }
13 | | optional_S simple_selector_1toN { result = [val.last].flatten }
14 | ;
15 | combinator
16 | : PLUS { result = :DIRECT_ADJACENT_SELECTOR }
17 | | GREATER { result = :CHILD_SELECTOR }
18 | | TILDE { result = :FOLLOWING_SELECTOR }
19 | | DOUBLESLASH { result = :DESCENDANT_SELECTOR }
20 | | SLASH { result = :CHILD_SELECTOR }
21 | ;
22 | simple_selector
23 | : element_name hcap_0toN {
24 | result = if val[1].nil?
25 | val.first
26 | else
27 | Node.new(:CONDITIONAL_SELECTOR, [val.first, val[1]])
28 | end
29 | }
30 | | function
31 | | function pseudo {
32 | result = Node.new(:CONDITIONAL_SELECTOR, val)
33 | }
34 | | function attrib {
35 | result = Node.new(:CONDITIONAL_SELECTOR, val)
36 | }
37 | | hcap_1toN {
38 | result = Node.new(:CONDITIONAL_SELECTOR,
39 | [Node.new(:ELEMENT_NAME, ['*']), val.first]
40 | )
41 | }
42 | ;
43 | prefixless_combinator_selector
44 | : combinator simple_selector_1toN {
45 | result = Node.new(val.first, [nil, val.last])
46 | }
47 | ;
48 | simple_selector_1toN
49 | : simple_selector combinator simple_selector_1toN {
50 | result = Node.new(val[1], [val.first, val.last])
51 | }
52 | | simple_selector S simple_selector_1toN {
53 | result = Node.new(:DESCENDANT_SELECTOR, [val.first, val.last])
54 | }
55 | | simple_selector
56 | ;
57 | class
58 | : '.' IDENT { result = Node.new(:CLASS_CONDITION, [val[1]]) }
59 | ;
60 | element_name
61 | : namespaced_ident
62 | | '*' { result = Node.new(:ELEMENT_NAME, val) }
63 | ;
64 | namespaced_ident
65 | : namespace '|' IDENT {
66 | result = Node.new(:ELEMENT_NAME,
67 | [[val.first, val.last].compact.join(':')]
68 | )
69 | }
70 | | IDENT {
71 | name = @namespaces.key?('xmlns') ? "xmlns:#{val.first}" : val.first
72 | result = Node.new(:ELEMENT_NAME, [name])
73 | }
74 | ;
75 | namespace
76 | : IDENT { result = val[0] }
77 | |
78 | ;
79 | attrib
80 | : LSQUARE attrib_name attrib_val_0or1 RSQUARE {
81 | result = Node.new(:ATTRIBUTE_CONDITION,
82 | [val[1]] + (val[2] || [])
83 | )
84 | }
85 | | LSQUARE function attrib_val_0or1 RSQUARE {
86 | result = Node.new(:ATTRIBUTE_CONDITION,
87 | [val[1]] + (val[2] || [])
88 | )
89 | }
90 | | LSQUARE NUMBER RSQUARE {
91 | # Non standard, but hpricot supports it.
92 | result = Node.new(:PSEUDO_CLASS,
93 | [Node.new(:FUNCTION, ['nth-child(', val[1]])]
94 | )
95 | }
96 | ;
97 | attrib_name
98 | : namespace '|' IDENT {
99 | result = Node.new(:ELEMENT_NAME,
100 | [[val.first, val.last].compact.join(':')]
101 | )
102 | }
103 | | IDENT {
104 | # Default namespace is not applied to attributes.
105 | # So we don't add prefix "xmlns:" as in namespaced_ident.
106 | result = Node.new(:ELEMENT_NAME, [val.first])
107 | }
108 | ;
109 | function
110 | : FUNCTION RPAREN {
111 | result = Node.new(:FUNCTION, [val.first.strip])
112 | }
113 | | FUNCTION expr RPAREN {
114 | result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
115 | }
116 | | FUNCTION nth RPAREN {
117 | result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
118 | }
119 | | NOT expr RPAREN {
120 | result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
121 | }
122 | | HAS selector RPAREN {
123 | result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
124 | }
125 | ;
126 | expr
127 | : NUMBER COMMA expr { result = [val.first, val.last] }
128 | | STRING COMMA expr { result = [val.first, val.last] }
129 | | IDENT COMMA expr { result = [val.first, val.last] }
130 | | NUMBER
131 | | STRING
132 | | IDENT # even, odd
133 | {
134 | case val[0]
135 | when 'even'
136 | result = Node.new(:NTH, ['2','n','+','0'])
137 | when 'odd'
138 | result = Node.new(:NTH, ['2','n','+','1'])
139 | when 'n'
140 | result = Node.new(:NTH, ['1','n','+','0'])
141 | else
142 | # This is not CSS standard. It allows us to support this:
143 | # assert_xpath("//a[foo(., @href)]", @parser.parse('a:foo(@href)'))
144 | # assert_xpath("//a[foo(., @a, b)]", @parser.parse('a:foo(@a, b)'))
145 | # assert_xpath("//a[foo(., a, 10)]", @parser.parse('a:foo(a, 10)'))
146 | result = val
147 | end
148 | }
149 | ;
150 | nth
151 | : NUMBER IDENT PLUS NUMBER # 5n+3 -5n+3
152 | {
153 | if val[1] == 'n'
154 | result = Node.new(:NTH, val)
155 | else
156 | raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
157 | end
158 | }
159 | | IDENT PLUS NUMBER { # n+3, -n+3
160 | if val[0] == 'n'
161 | val.unshift("1")
162 | result = Node.new(:NTH, val)
163 | elsif val[0] == '-n'
164 | val[0] = 'n'
165 | val.unshift("-1")
166 | result = Node.new(:NTH, val)
167 | else
168 | raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
169 | end
170 | }
171 | | NUMBER IDENT { # 5n, -5n, 10n-1
172 | n = val[1]
173 | if n[0, 2] == 'n-'
174 | val[1] = 'n'
175 | val << "-"
176 | # b is contained in n as n is the string "n-b"
177 | val << n[2, n.size]
178 | result = Node.new(:NTH, val)
179 | elsif n == 'n'
180 | val << "+"
181 | val << "0"
182 | result = Node.new(:NTH, val)
183 | else
184 | raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
185 | end
186 | }
187 | ;
188 | pseudo
189 | : ':' function {
190 | result = Node.new(:PSEUDO_CLASS, [val[1]])
191 | }
192 | | ':' IDENT { result = Node.new(:PSEUDO_CLASS, [val[1]]) }
193 | ;
194 | hcap_0toN
195 | : hcap_1toN
196 | |
197 | ;
198 | hcap_1toN
199 | : attribute_id hcap_1toN {
200 | result = Node.new(:COMBINATOR, val)
201 | }
202 | | class hcap_1toN {
203 | result = Node.new(:COMBINATOR, val)
204 | }
205 | | attrib hcap_1toN {
206 | result = Node.new(:COMBINATOR, val)
207 | }
208 | | pseudo hcap_1toN {
209 | result = Node.new(:COMBINATOR, val)
210 | }
211 | | negation hcap_1toN {
212 | result = Node.new(:COMBINATOR, val)
213 | }
214 | | attribute_id
215 | | class
216 | | attrib
217 | | pseudo
218 | | negation
219 | ;
220 | attribute_id
221 | : HASH { result = Node.new(:ID, val) }
222 | ;
223 | attrib_val_0or1
224 | : eql_incl_dash IDENT { result = [val.first, val[1]] }
225 | | eql_incl_dash STRING { result = [val.first, val[1]] }
226 | |
227 | ;
228 | eql_incl_dash
229 | : EQUAL { result = :equal }
230 | | PREFIXMATCH { result = :prefix_match }
231 | | SUFFIXMATCH { result = :suffix_match }
232 | | SUBSTRINGMATCH { result = :substring_match }
233 | | NOT_EQUAL { result = :not_equal }
234 | | INCLUDES { result = :includes }
235 | | DASHMATCH { result = :dash_match }
236 | ;
237 | negation
238 | : NOT negation_arg RPAREN {
239 | result = Node.new(:NOT, [val[1]])
240 | }
241 | ;
242 | negation_arg
243 | : element_name
244 | | element_name hcap_1toN
245 | | hcap_1toN
246 | ;
247 | optional_S
248 | : S
249 | |
250 | ;
251 | end
252 |
253 | ---- header
254 |
255 | require 'nokogiri/css/parser_extras'
256 |
--------------------------------------------------------------------------------
/doc/ja/grammar.ja.rdoc:
--------------------------------------------------------------------------------
1 | = 規則ファイル文法リファレンス
2 |
3 | == 文法に関する前バージョンとの非互換
4 |
5 | * (1.2.5) ユーザーコードを連結する時、外部ファイルよりも
6 | 埋めこんであるコードを先に連結します。
7 | * (1.1.6) 新しいディレクティブ options が追加されました。
8 | * (1.1.5) 予約語 token の意味が変更になりました。
9 | * (0.14) ルールの最後のセミコロンが省略可能になりました。
10 | また、token prechigh などが予約語でなくなりました。
11 | * (10.2) prepare が header に driver が footer になりました。
12 | 今はそのままでも使えますが、2.0 からは対応しません。
13 | * (0.10) class に対応する end がなくなりました。
14 | * (0.9) ダサダサのピリオド方式をやめて { と } で囲むようにしました。
15 |
16 | == 全体の構造
17 |
18 | トップレベルは、規則部とユーザーコード部に分けられます。
19 | ユーザーコード部はクラス定義の後に来なければいけません。
20 |
21 | === コメント
22 |
23 | 文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを
24 | 書くことができます。コメントは、Rubyの #.....(行末) スタイルと、
25 | Cの /*......*/ スタイルを使うことができます。
26 |
27 | === 規則部
28 |
29 | 規則部は以下のような形をしています。
30 | --
31 | class クラス名 [< スーパークラス]
32 | [演算子順位]
33 | [トークン宣言]
34 | [オプション]
35 | [expect]
36 | [トークンシンボル値おきかえ]
37 | [スタート規則]
38 | rule
39 | 文法記述
40 | --
41 | "クラス名"はここで定義するパーサクラスの名前です。
42 | これはそのままRubyのクラス名になります。
43 |
44 | また M::C のように「::」を使った名前を使うと、クラス定義を
45 | モジュール M の中にネストさせます。つまり class M::C ならば
46 | --
47 | module M
48 | class C < Racc::Parser
49 | いろいろ
50 | end
51 | end
52 | --
53 | のように出力します。
54 |
55 | さらに、Ruby と同じ構文でスーパークラスを指定できます。
56 | ただしこの指定をするとパーサの動作に重大な影響を与えるので、
57 | 特に必要がない限り指定してはいけません。これは将来の拡張の
58 | ために用意したもので、現在指定する必然性はあまりありません。
59 |
60 | === 文法の記述
61 |
62 | racc で生成するパーサが理解できる文法を記述します。
63 | 文法は、予約語 rule と end の間に、以下のような書式で書きます。
64 | --
65 | トークン: トークンの並び アクション
66 |
67 | トークン: トークンの並び アクション
68 | | トークンの並び アクション
69 | | トークンの並び アクション
70 | (必要なだけ同じようにつづける)
71 | --
72 | アクションは { } で囲みます。アクションでは Ruby の文はほとんど
73 | 使えますが、一部だけは非対応です。対応していないものは以下のとおり。
74 |
75 | * ヒアドキュメント
76 | * =begin ... =end 型コメント
77 | * スペースで始まる正規表現
78 | * ごくまれに % の演算。普通に演算子のまわりにスペースを入れていれば問題なし
79 |
80 | このあたりに関しては完全な対応はまず無理です。あきらめてください。
81 |
82 | 左辺の値($$)は、オプションによって返し方がかわります。まずデフォルトでは
83 | ローカル変数 result (そのデフォルト値は val[0])が 左辺値を表し、アクション
84 | ブロックを抜けた時の result の値が左辺値になります。または明示的に return
85 | で返した場合もこの値になります。一方、options で no_result_var を指定した
86 | 場合、左辺値はアクションブロックの最後の文の値になります (Ruby のメソッドと
87 | 同じ)。
88 |
89 | どちらの場合でもアクションは省略でき、省略した場合の左辺値は常に val[0] です。
90 |
91 | 以下に文法記述の全体の例をしめします。
92 | --
93 | rule
94 | goal: def ruls source
95 | {
96 | result = val
97 | }
98 |
99 | def : /* none */
100 | {
101 | result = []
102 | }
103 | | def startdesig
104 | {
105 | result[0] = val[1]
106 | }
107 | | def
108 | precrule # これは上の行の続き
109 | {
110 | result[1] = val[1]
111 | }
112 | (略)
113 | --
114 | アクション内では特別な意味をもった変数がいくつか使えます。
115 | そのような変数を以下に示します。括弧の中は yacc での表記です。
116 |
117 | * result ($$)
118 |
119 | 左辺の値。初期値は val[0] です。
120 |
121 | * val ($1,$2,$3…)
122 |
123 | 右辺の記号の値の配列。Ruby の配列なので当然インデックスはゼロから始まります。
124 | この配列は毎回作られるので自由に変更したり捨てたりして構いません。
125 |
126 | * _values (...,$-2,$-1,$0)
127 |
128 | 値スタック。Racc コアが使っているオブジェクトがそのまま渡されます。
129 | この変数の意味がわかる人以外は絶対に 変更してはいけません。
130 |
131 | またアクションの特別な形式に、埋めこみアクションというものがあります。
132 | これはトークン列の途中の好きなところに記述することができます。
133 | 以下に埋めこみアクションの例を示します。
134 | --
135 | target: A B { puts 'test test' } C D { normal action }
136 | --
137 | このように記述すると A B を検出した時点で puts が実行されます。
138 | また、埋めこみアクションはそれ自体が値を持ちます。つまり、以下の例において
139 | --
140 | target: A { result = 1 } B { p val[1] }
141 | --
142 | 最後にある p val[1] は埋めこみアクションの値 1 を表示します。
143 | B の値ではありません。
144 |
145 | 意味的には、埋めこみアクションは空の規則を持つ非終端記号を追加することと
146 | 全く同じ働きをします。つまり、上の例は次のコードと完全に同じ意味です。
147 | --
148 | target : A nonterm B { p val[1] }
149 | nonterm : /* 空の規則 */ { result = 1 }
150 | --
151 |
152 | === 演算子優先順位
153 |
154 | あるトークン上でシフト・還元衝突がおこったとき、そのトークンに
155 | 演算子優先順位が設定してあると衝突を解消できる場合があります。
156 | そのようなものとして特に有名なのは数式の演算子と if...else 構文です。
157 |
158 | 優先順位で解決できる文法は、うまく文法をくみかえてやれば
159 | 優先順位なしでも同じ効果を得ることができます。しかしたいていの
160 | 場合は優先順位を設定して解決するほうが文法を簡単にできます。
161 |
162 | シフト・還元衝突がおこったとき、Racc はまずその規則に順位が設定
163 | されているか調べます。規則の順位は、その規則で一番うしろにある
164 | 終端トークンの優先順位です。たとえば
165 | --
166 | target: TERM_A nonterm_a TERM_B nonterm_b
167 | --
168 | のような規則の順位はTERM_Bの優先順位になります。もしTERM_Bに
169 | 優先順位が設定されていなかったら、優先順位で衝突を解決することは
170 | できないと判断し、「Shift/Reduce conflict」を報告します。
171 |
172 | 演算子の優先順位はつぎのように書いて定義します。
173 | --
174 | prechigh
175 | nonassoc PLUSPLUS
176 | left MULTI DIVIDE
177 | left PLUS MINUS
178 | right '='
179 | preclow
180 | --
181 | prechigh に近い行にあるほど優先順位の高いトークンです。上下をまるごと
182 | さかさまにして preclow...prechigh の順番に書くこともできます。left
183 | などは必ず行の最初になければいけません。
184 |
185 | left right nonassoc はそれぞれ「結合性」を表します。結合性によって、
186 | 同じ順位の演算子の規則が衝突した場合にシフト還元のどちらをとるかが
187 | 決まります。たとえば
188 | --
189 | a - b - c
190 | --
191 | が
192 | --
193 | (a - b) - c
194 | --
195 | になるのが左結合 (left) です。四則演算は普通これです。
196 | 一方
197 | --
198 | a - (b - c)
199 | --
200 | になるのが右結合 (right) です。代入のクオートは普通 right です。
201 | またこのように演算子が重なるのはエラーである場合、非結合 (nonassoc) です。
202 | C 言語の ++ や単項のマイナスなどがこれにあたります。
203 |
204 | ところで、説明したとおり通常は還元する規則の最後のトークンが順位を
205 | 決めるのですが、ある規則に限ってそのトークンとは違う順位にしたいことも
206 | あります。例えば符号反転のマイナスは引き算のマイナスより順位を高く
207 | しないといけません。このような場合 yacc では %prec を使います。
208 | racc ではイコール記号を使って同じことをできます。
209 | --
210 | prechigh
211 | nonassoc UMINUS
212 | left '*' '/'
213 | left '+' '-'
214 | preclow
215 | (略)
216 | exp: exp '*' exp
217 | | exp '-' exp
218 | | '-' exp = UMINUS # ここだけ順位を上げる
219 | --
220 | このように記述すると、'-' exp の規則の順位が UMINUS の順位になります。
221 | こうすることで符号反転の '-' は '*' よりも順位が高くなるので、
222 | 意図どおりになります。
223 |
224 | === トークン宣言
225 |
226 | トークン(終端記号)のつづりを間違えるというのはよくあることですが、
227 | 発見するのはなかなか難しいものです。1.1.5 からはトークンを明示的に
228 | 宣言することで、宣言にないトークン / 宣言にだけあるトークンに対して
229 | 警告が出るようになりました。yacc の %token と似ていますが最大の違いは
230 | racc では必須ではなく、しかもエラーにならず警告だけ、という点です。
231 |
232 | トークン宣言は以下のように書きます。
233 | --
234 | token A B C D
235 | E F G H
236 | --
237 | トークンのリストを複数行にわたって書けることに注目してください。
238 | racc では一般に「予約語」は行の先頭に来た時だけ予約語とみなされるので
239 | prechigh などもシンボルとして使えます。ただし深淵な理由から end だけは
240 | どうやっても予約語になってしまいます。
241 |
242 | === オプション
243 |
244 | racc のコマンドラインオプションの一部をファイル中にデフォルト値
245 | として記述することができます。
246 | --
247 | options オプション オプション …
248 | --
249 | 現在ここで使えるのは
250 |
251 | * omit_action_call
252 |
253 | 空のアクション呼び出しを省略する
254 |
255 | * result_var
256 |
257 | 変数 result を使う
258 |
259 | です。
260 | それぞれ no_ を頭につけることで意味を反転できます。
261 |
262 | === expect
263 |
264 | 実用になるパーサはたいてい無害な shift/reduce conflict を含みます。
265 | しかし文法ファイルを書いた本人はそれを知っているからいいですが、
266 | ユーザが文法ファイルを処理した時に「conflict」と表示されたら
267 | 不安に思うでしょう。そのような場合、以下のように書いておくと
268 | shift/reduce conflict のメッセージを抑制できます。
269 | --
270 | expect 3
271 | --
272 | この場合 shift/reduce conflict はぴったり三つでなければいけません。
273 | 三つでない場合はやはり表示が出ます (ゼロでも出ます)。
274 | また reduce/reduce conflict の表示は抑制できません。
275 |
276 | === トークンシンボル値の変更
277 |
278 | トークンシンボルを表す値は、デフォルトでは
279 |
280 | * 文法中、引用符でかこまれていないもの (RULEとかXENDとか)
281 | →その名前の文字列を intern して得られるシンボル (1.4 では Fixnum)
282 | * 引用符でかこまれているもの(':'とか'.'とか)
283 | →その文字列そのまま
284 |
285 | となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、
286 | これにあわせなければならず、このままでは不便です。このような場合には、
287 | convert 節を加えることで、トークンシンボルを表す値を変えることができます。
288 | 以下がその例です。
289 | --
290 | convert
291 | PLUS 'PlusClass' #→ PlusClass
292 | MIN 'MinusClass' #→ MinusClass
293 | end
294 | --
295 | デフォルトではトークンシンボル PLUS に対してはトークンシンボル値は
296 | :PLUS ですが、上のような記述がある場合は PlusClass になります。
297 | 変換後の値は false・nil 以外ならなんでも使えます。
298 |
299 | 変換後の値として文字列を使うときは、次のように引用符を重ねる必要があります。
300 | --
301 | convert
302 | PLUS '"plus"' #→ "plus"
303 | end
304 | --
305 | また、「'」を使っても生成された Ruby のコード上では「"」になるので
306 | 注意してください。バックスラッシュによるクオートは有効ですが、バック
307 | スラッシュは消えずにそのまま残ります。
308 | --
309 | PLUS '"plus\n"' #→ "plus\n"
310 | MIN "\"minus#{val}\"" #→ \"minus#{val}\"
311 | --
312 |
313 | === スタート規則
314 |
315 | パーサをつくるためには、どの規則が「最初の」規則か、ということを Racc におしえて
316 | やらなければいけません。それを明示的に書くのがスタート規則です。スタート規則は
317 | 次のように書きます。
318 | --
319 | start real_target
320 | --
321 | start は行の最初にこなければいけません。このように書くと、ファイルで
322 | 一番最初に出てくる real_target の規則をスタート規則として使います。
323 | 省略した場合は、ファイルの最初の規則がスタート規則になります。普通は
324 | 最初の規則を一番上にかくほうが書きやすく、わかりやすくなりますから、
325 | この記法はあまりつかう必要はないでしょう。
326 |
327 | === ユーザーコード部
328 |
329 | ユーザーコードは、パーサクラスが書きこまれるファイルに、
330 | アクションの他にもコードを含めたい時に使います。このようなものは
331 | 書きこまれる場所に応じて三つ存在し、パーサクラスの定義の前が
332 | header、クラスの定義中(の冒頭)が inner、定義の後が footer です。
333 | ユーザコードとして書いたものは全く手を加えずにそのまま連結されます。
334 |
335 | ユーザーコード部の書式は以下の通りです。
336 | --
337 | ---- 識別子
338 | ruby の文
339 | ruby の文
340 | ruby の文
341 |
342 | ---- 識別子
343 | ruby の文
344 | :
345 | --
346 | 行の先頭から四つ以上連続した「-」(マイナス)があるとユーザーコードと
347 | みなされます。識別子は一つの単語で、そのあとには「=」以外なら何を
348 | 書いてもかまいません。
349 |
--------------------------------------------------------------------------------
/lib/racc/statetransitiontable.rb:
--------------------------------------------------------------------------------
1 | #--
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 | #
11 | #++
12 |
13 | require_relative 'parser'
14 |
15 | module Racc
16 |
17 | StateTransitionTable = Struct.new(:action_table,
18 | :action_check,
19 | :action_default,
20 | :action_pointer,
21 | :goto_table,
22 | :goto_check,
23 | :goto_default,
24 | :goto_pointer,
25 | :token_table,
26 | :reduce_table,
27 | :reduce_n,
28 | :shift_n,
29 | :nt_base,
30 | :token_to_s_table,
31 | :use_result_var,
32 | :debug_parser)
33 | class StateTransitionTable # reopen
34 | def StateTransitionTable.generate(states)
35 | StateTransitionTableGenerator.new(states).generate
36 | end
37 |
38 | def initialize(states)
39 | super()
40 | @states = states
41 | @grammar = states.grammar
42 | self.use_result_var = true
43 | self.debug_parser = true
44 | end
45 |
46 | attr_reader :states
47 | attr_reader :grammar
48 |
49 | def parser_class
50 | ParserClassGenerator.new(@states).generate
51 | end
52 |
53 | def token_value_table
54 | h = {}
55 | token_table().each do |sym, i|
56 | h[sym.value] = i
57 | end
58 | h
59 | end
60 | end
61 |
62 |
63 | class StateTransitionTableGenerator
64 |
65 | def initialize(states)
66 | @states = states
67 | @grammar = states.grammar
68 | end
69 |
70 | def generate
71 | t = StateTransitionTable.new(@states)
72 | gen_action_tables t, @states
73 | gen_goto_tables t, @grammar
74 | t.token_table = token_table(@grammar)
75 | t.reduce_table = reduce_table(@grammar)
76 | t.reduce_n = @states.reduce_n
77 | t.shift_n = @states.shift_n
78 | t.nt_base = @grammar.nonterminal_base
79 | t.token_to_s_table = @grammar.symbols.map {|sym| sym.to_s }
80 | t
81 | end
82 |
83 | def reduce_table(grammar)
84 | t = [0, 0, :racc_error]
85 | grammar.each_with_index do |rule, idx|
86 | next if idx == 0
87 | t.push rule.size
88 | t.push rule.target.ident
89 | t.push(if rule.action.empty? # and @params.omit_action_call?
90 | then :_reduce_none
91 | else "_reduce_#{idx}".intern
92 | end)
93 | end
94 | t
95 | end
96 |
97 | def token_table(grammar)
98 | h = {}
99 | grammar.symboltable.terminals.each do |t|
100 | h[t] = t.ident
101 | end
102 | h
103 | end
104 |
105 | def gen_action_tables(t, states)
106 | t.action_table = yytable = []
107 | t.action_check = yycheck = []
108 | t.action_default = yydefact = []
109 | t.action_pointer = yypact = []
110 | e1 = []
111 | e2 = []
112 | states.each do |state|
113 | yydefact.push act2actid(state.defact)
114 | if state.action.empty?
115 | yypact.push nil
116 | next
117 | end
118 | vector = []
119 | state.action.each do |tok, act|
120 | vector[tok.ident] = act2actid(act)
121 | end
122 | addent e1, vector, state.ident, yypact
123 | end
124 | set_table e1, e2, yytable, yycheck, yypact
125 | end
126 |
127 | def gen_goto_tables(t, grammar)
128 | t.goto_table = yytable2 = []
129 | t.goto_check = yycheck2 = []
130 | t.goto_pointer = yypgoto = []
131 | t.goto_default = yydefgoto = []
132 | e1 = []
133 | e2 = []
134 | grammar.each_nonterminal do |tok|
135 | tmp = []
136 |
137 | # decide default
138 | freq = Array.new(@states.size, 0)
139 | @states.each do |state|
140 | st = state.goto_table[tok]
141 | if st
142 | st = st.ident
143 | freq[st] += 1
144 | end
145 | tmp[state.ident] = st
146 | end
147 | max = freq.max
148 | if max > 1
149 | default = freq.index(max)
150 | tmp.map! {|i| default == i ? nil : i }
151 | else
152 | default = nil
153 | end
154 | yydefgoto.push default
155 |
156 | # delete default value
157 | tmp.pop until tmp.last or tmp.empty?
158 | if tmp.compact.empty?
159 | # only default
160 | yypgoto.push nil
161 | next
162 | end
163 |
164 | addent e1, tmp, (tok.ident - grammar.nonterminal_base), yypgoto
165 | end
166 | set_table e1, e2, yytable2, yycheck2, yypgoto
167 | end
168 |
169 | def addent(all, arr, chkval, ptr)
170 | max = arr.size
171 | min = nil
172 | arr.each_with_index do |item, idx|
173 | if item
174 | min ||= idx
175 | end
176 | end
177 | ptr.push(-7777) # mark
178 | arr = arr[min...max]
179 | all.push [arr, chkval, mkmapexp(arr), min, ptr.size - 1]
180 | end
181 |
182 | n = 2 ** 16
183 | begin
184 | Regexp.compile("a{#{n}}")
185 | RE_DUP_MAX = n
186 | rescue RegexpError
187 | n /= 2
188 | retry
189 | end
190 |
191 | def mkmapexp(arr)
192 | i = ii = 0
193 | as = arr.size
194 | map = String.new
195 | maxdup = RE_DUP_MAX
196 | curr = nil
197 | while i < as
198 | ii = i + 1
199 | if arr[i]
200 | ii += 1 while ii < as and arr[ii]
201 | curr = '-'
202 | else
203 | ii += 1 while ii < as and not arr[ii]
204 | curr = '.'
205 | end
206 |
207 | offset = ii - i
208 | if offset == 1
209 | map << curr
210 | else
211 | while offset > maxdup
212 | map << "#{curr}{#{maxdup}}"
213 | offset -= maxdup
214 | end
215 | map << "#{curr}{#{offset}}" if offset > 1
216 | end
217 | i = ii
218 | end
219 | Regexp.compile(map, Regexp::NOENCODING)
220 | end
221 |
222 | def set_table(entries, dummy, tbl, chk, ptr)
223 | upper = 0
224 | map = '-' * 10240
225 |
226 | # sort long to short
227 | entries.sort_by!.with_index {|a,i| [-a[0].size, i] }
228 |
229 | entries.each do |arr, chkval, expr, min, ptri|
230 | if upper + arr.size > map.size
231 | map << '-' * (arr.size + 1024)
232 | end
233 | idx = map.index(expr)
234 | ptr[ptri] = idx - min
235 | arr.each_with_index do |item, i|
236 | if item
237 | i += idx
238 | tbl[i] = item
239 | chk[i] = chkval
240 | map[i] = ?o
241 | end
242 | end
243 | upper = idx + arr.size
244 | end
245 | end
246 |
247 | def act2actid(act)
248 | case act
249 | when Shift then act.goto_id
250 | when Reduce then -act.ruleid
251 | when Accept then @states.shift_n
252 | when Error then @states.reduce_n * -1
253 | else
254 | raise "racc: fatal: wrong act type #{act.class} in action table"
255 | end
256 | end
257 |
258 | end
259 |
260 |
261 | class ParserClassGenerator
262 |
263 | def initialize(states)
264 | @states = states
265 | @grammar = states.grammar
266 | end
267 |
268 | def generate
269 | table = @states.state_transition_table
270 | c = Class.new(::Racc::Parser)
271 | c.const_set :Racc_arg, [table.action_table,
272 | table.action_check,
273 | table.action_default,
274 | table.action_pointer,
275 | table.goto_table,
276 | table.goto_check,
277 | table.goto_default,
278 | table.goto_pointer,
279 | table.nt_base,
280 | table.reduce_table,
281 | table.token_value_table,
282 | table.shift_n,
283 | table.reduce_n,
284 | false]
285 | c.const_set :Racc_token_to_s_table, table.token_to_s_table
286 | c.const_set :Racc_debug_parser, true
287 | define_actions c
288 | c
289 | end
290 |
291 | private
292 |
293 | def define_actions(c)
294 | c.module_eval "def _reduce_none(vals, vstack) vals[0] end"
295 | @grammar.each do |rule|
296 | if rule.action.empty?
297 | c.alias_method("_reduce_#{rule.ident}", :_reduce_none)
298 | else
299 | c.define_method("_racc_action_#{rule.ident}", &rule.action.proc)
300 | c.module_eval(<<-End, __FILE__, __LINE__ + 1)
301 | def _reduce_#{rule.ident}(vals, vstack)
302 | _racc_action_#{rule.ident}(*vals)
303 | end
304 | End
305 | end
306 | end
307 | end
308 |
309 | end
310 |
311 | end # module Racc
312 |
--------------------------------------------------------------------------------
/test/assets/namae.y:
--------------------------------------------------------------------------------
1 | # -*- ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | # Copyright (C) 2012 President and Fellows of Harvard College
5 | # Copyright (C) 2013-2014 Sylvester Keil
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | #
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | #
13 | # 2. Redistributions in binary form must reproduce the above copyright notice,
14 | # this list of conditions and the following disclaimer in the documentation
15 | # and/or other materials provided with the distribution.
16 | #
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
18 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20 | # EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 | # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 | # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | #
28 | # The views and conclusions contained in the software and documentation are
29 | # those of the authors and should not be interpreted as representing official
30 | # policies, either expressed or implied, of the copyright holder.
31 |
32 | class Namae::Parser
33 |
34 | token COMMA UWORD LWORD PWORD NICK AND APPELLATION TITLE SUFFIX
35 |
36 | expect 0
37 |
38 | rule
39 |
40 | names : { result = [] }
41 | | name { result = [val[0]] }
42 | | names AND name { result = val[0] << val[2] }
43 |
44 | name : word { result = Name.new(:given => val[0]) }
45 | | display_order
46 | | honorific word { result = val[0].merge(:family => val[1]) }
47 | | honorific display_order { result = val[1].merge(val[0]) }
48 | | sort_order
49 |
50 | honorific : APPELLATION { result = Name.new(:appellation => val[0]) }
51 | | TITLE { result = Name.new(:title => val[0]) }
52 |
53 | display_order : u_words word opt_suffices opt_titles
54 | {
55 | result = Name.new(:given => val[0], :family => val[1],
56 | :suffix => val[2], :title => val[3])
57 | }
58 | | u_words NICK last opt_suffices opt_titles
59 | {
60 | result = Name.new(:given => val[0], :nick => val[1],
61 | :family => val[2], :suffix => val[3], :title => val[4])
62 | }
63 | | u_words NICK von last opt_suffices opt_titles
64 | {
65 | result = Name.new(:given => val[0], :nick => val[1],
66 | :particle => val[2], :family => val[3],
67 | :suffix => val[4], :title => val[5])
68 | }
69 | | u_words von last
70 | {
71 | result = Name.new(:given => val[0], :particle => val[1],
72 | :family => val[2])
73 | }
74 | | von last
75 | {
76 | result = Name.new(:particle => val[0], :family => val[1])
77 | }
78 |
79 | sort_order : last COMMA first
80 | {
81 | result = Name.new({ :family => val[0], :suffix => val[2][0],
82 | :given => val[2][1] }, !!val[2][0])
83 | }
84 | | von last COMMA first
85 | {
86 | result = Name.new({ :particle => val[0], :family => val[1],
87 | :suffix => val[3][0], :given => val[3][1] }, !!val[3][0])
88 | }
89 | | u_words von last COMMA first
90 | {
91 | result = Name.new({ :particle => val[0,2].join(' '), :family => val[2],
92 | :suffix => val[4][0], :given => val[4][1] }, !!val[4][0])
93 | }
94 | ;
95 |
96 | von : LWORD
97 | | von LWORD { result = val.join(' ') }
98 | | von u_words LWORD { result = val.join(' ') }
99 |
100 | last : LWORD | u_words
101 |
102 | first : opt_words { result = [nil,val[0]] }
103 | | words opt_comma suffices { result = [val[2],val[0]] }
104 | | suffices { result = [val[0],nil] }
105 | | suffices COMMA words { result = [val[0],val[2]] }
106 |
107 | u_words : u_word
108 | | u_words u_word { result = val.join(' ') }
109 |
110 | u_word : UWORD | PWORD
111 |
112 | words : word
113 | | words word { result = val.join(' ') }
114 |
115 | opt_comma : /* empty */ | COMMA
116 | opt_words : /* empty */ | words
117 |
118 | word : LWORD | UWORD | PWORD
119 |
120 | opt_suffices : /* empty */ | suffices
121 |
122 | suffices : SUFFIX
123 | | suffices SUFFIX { result = val.join(' ') }
124 |
125 | opt_titles : /* empty */ | titles
126 |
127 | titles : TITLE
128 | | titles TITLE { result = val.join(' ') }
129 |
130 | ---- header
131 | require 'singleton'
132 | require 'strscan'
133 |
134 | ---- inner
135 |
136 | include Singleton
137 |
138 | attr_reader :options, :input
139 |
140 | def initialize
141 | @input, @options = StringScanner.new(''), {
142 | :debug => false,
143 | :prefer_comma_as_separator => false,
144 | :comma => ',',
145 | :stops => ',;',
146 | :separator => /\s*(\band\b|\&|;)\s*/i,
147 | :title => /\s*\b(sir|lord|count(ess)?|(gen|adm|col|maj|capt|cmdr|lt|sgt|cpl|pvt|prof|dr|md|ph\.?d)\.?)(\s+|$)/i,
148 | :suffix => /\s*\b(JR|Jr|jr|SR|Sr|sr|[IVX]{2,})(\.|\b)/,
149 | :appellation => /\s*\b((mrs?|ms|fr|hr)\.?|miss|herr|frau)(\s+|$)/i
150 | }
151 | end
152 |
153 | def debug?
154 | options[:debug] || ENV['DEBUG']
155 | end
156 |
157 | def separator
158 | options[:separator]
159 | end
160 |
161 | def comma
162 | options[:comma]
163 | end
164 |
165 | def stops
166 | options[:stops]
167 | end
168 |
169 | def title
170 | options[:title]
171 | end
172 |
173 | def suffix
174 | options[:suffix]
175 | end
176 |
177 | def appellation
178 | options[:appellation]
179 | end
180 |
181 | def prefer_comma_as_separator?
182 | options[:prefer_comma_as_separator]
183 | end
184 |
185 | def parse(input)
186 | parse!(input)
187 | rescue => e
188 | warn e.message if debug?
189 | []
190 | end
191 |
192 | def parse!(string)
193 | input.string = normalize(string)
194 | reset
195 | do_parse
196 | end
197 |
198 | def normalize(string)
199 | string = string.strip
200 | string
201 | end
202 |
203 | def reset
204 | @commas, @words, @initials, @suffices, @yydebug = 0, 0, 0, 0, debug?
205 | self
206 | end
207 |
208 | private
209 |
210 | def stack
211 | @vstack || @racc_vstack || []
212 | end
213 |
214 | def last_token
215 | stack[-1]
216 | end
217 |
218 | def consume_separator
219 | return next_token if seen_separator?
220 | @commas, @words, @initials, @suffices = 0, 0, 0, 0
221 | [:AND, :AND]
222 | end
223 |
224 | def consume_comma
225 | @commas += 1
226 | [:COMMA, :COMMA]
227 | end
228 |
229 | def consume_word(type, word)
230 | @words += 1
231 |
232 | case type
233 | when :UWORD
234 | @initials += 1 if word =~ /^[[:upper:]]+\b/
235 | when :SUFFIX
236 | @suffices += 1
237 | end
238 |
239 | [type, word]
240 | end
241 |
242 | def seen_separator?
243 | !stack.empty? && last_token == :AND
244 | end
245 |
246 | def suffix?
247 | !@suffices.zero? || will_see_suffix?
248 | end
249 |
250 | def will_see_suffix?
251 | input.peek(8).to_s.strip.split(/\s+/)[0] =~ suffix
252 | end
253 |
254 | def will_see_initial?
255 | input.peek(6).to_s.strip.split(/\s+/)[0] =~ /^[[:upper:]]+\b/
256 | end
257 |
258 | def seen_full_name?
259 | prefer_comma_as_separator? && @words > 1 &&
260 | (@initials > 0 || !will_see_initial?) && !will_see_suffix?
261 | end
262 |
263 | def next_token
264 | case
265 | when input.nil?, input.eos?
266 | nil
267 | when input.scan(separator)
268 | consume_separator
269 | when input.scan(/\s*#{comma}\s*/)
270 | if @commas.zero? && !seen_full_name? || @commas == 1 && suffix?
271 | consume_comma
272 | else
273 | consume_separator
274 | end
275 | when input.scan(/\s+/)
276 | next_token
277 | when input.scan(title)
278 | consume_word(:TITLE, input.matched.strip)
279 | when input.scan(suffix)
280 | consume_word(:SUFFIX, input.matched.strip)
281 | when input.scan(appellation)
282 | [:APPELLATION, input.matched.strip]
283 | when input.scan(/((\\\w+)?\{[^\}]*\})*[[:upper:]][^\s#{stops}]*/)
284 | consume_word(:UWORD, input.matched)
285 | when input.scan(/((\\\w+)?\{[^\}]*\})*[[:lower:]][^\s#{stops}]*/)
286 | consume_word(:LWORD, input.matched)
287 | when input.scan(/(\\\w+)?\{[^\}]*\}[^\s#{stops}]*/)
288 | consume_word(:PWORD, input.matched)
289 | when input.scan(/('[^'\n]+')|("[^"\n]+")/)
290 | consume_word(:NICK, input.matched[1...-1])
291 | else
292 | raise ArgumentError,
293 | "Failed to parse name #{input.string.inspect}: unmatched data at offset #{input.pos}"
294 | end
295 | end
296 |
297 | def on_error(tid, value, stack)
298 | raise ArgumentError,
299 | "Failed to parse name: unexpected '#{value}' at #{stack.inspect}"
300 | end
301 |
302 | # -*- racc -*-
303 |
--------------------------------------------------------------------------------
/test/assets/twowaysql.y:
--------------------------------------------------------------------------------
1 | # Copyright 2008-2015 Takuto Wada
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | class TwoWaySQL::Parser
16 |
17 | rule
18 |
19 | sql : stmt_list
20 | {
21 | result = RootNode.new( val[0] )
22 | }
23 |
24 | stmt_list :
25 | {
26 | result = []
27 | }
28 | | stmt_list stmt
29 | {
30 | result.push val[1]
31 | }
32 |
33 | stmt : primary
34 | | if_stmt
35 | | begin_stmt
36 |
37 | begin_stmt : BEGIN stmt_list END
38 | {
39 | result = BeginNode.new( val[1] )
40 | }
41 |
42 | if_stmt : IF sub_stmt else_stmt END
43 | {
44 | result = IfNode.new( val[0][1], val[1], val[2] )
45 | }
46 |
47 | else_stmt : ELSE sub_stmt
48 | {
49 | result = val[1]
50 | }
51 | |
52 | {
53 | result = nil
54 | }
55 |
56 | sub_stmt : and_stmt
57 | | or_stmt
58 | | stmt_list
59 |
60 | and_stmt : AND stmt_list
61 | {
62 | result = SubStatementNode.new( val[0][1], val[1] )
63 | }
64 |
65 | or_stmt : OR stmt_list
66 | {
67 | result = SubStatementNode.new( val[0][1], val[1] )
68 | }
69 |
70 | primary : IDENT
71 | {
72 | result = LiteralNode.new( val[0][1] )
73 | }
74 | | STRING_LITERAL
75 | {
76 | result = LiteralNode.new( val[0][1] )
77 | }
78 | | AND
79 | {
80 | result = LiteralNode.new( val[0][1] )
81 | }
82 | | OR
83 | {
84 | result = LiteralNode.new( val[0][1] )
85 | }
86 | | SPACES
87 | {
88 | result = WhiteSpaceNode.new( val[0][1], @preserve_space )
89 | }
90 | | COMMA
91 | {
92 | result = LiteralNode.new( val[0][1] )
93 | }
94 | | LPAREN
95 | {
96 | result = LiteralNode.new( val[0][1] )
97 | }
98 | | RPAREN
99 | {
100 | result = LiteralNode.new( val[0][1] )
101 | }
102 | | QUESTION
103 | {
104 | @num_questions += 1
105 | result = QuestionNode.new( @num_questions )
106 | }
107 | | ACTUAL_COMMENT
108 | {
109 | result = ActualCommentNode.new( val[0][1] , val[0][2] )
110 | }
111 | | bind_var
112 | | embed_var
113 |
114 | bind_var : BIND_VARIABLE STRING_LITERAL
115 | {
116 | result = BindVariableNode.new( val[0][1] )
117 | }
118 | | BIND_VARIABLE SPACES STRING_LITERAL
119 | {
120 | result = BindVariableNode.new( val[0][1] )
121 | }
122 | | BIND_VARIABLE IDENT
123 | {
124 | result = BindVariableNode.new( val[0][1] )
125 | }
126 | | BIND_VARIABLE SPACES IDENT
127 | {
128 | result = BindVariableNode.new( val[0][1] )
129 | }
130 | | PAREN_BIND_VARIABLE
131 | {
132 | result = ParenBindVariableNode.new( val[0][1] )
133 | }
134 |
135 | embed_var : EMBED_VARIABLE IDENT
136 | {
137 | result = EmbedVariableNode.new( val[0][1] )
138 | }
139 | | EMBED_VARIABLE SPACES IDENT
140 | {
141 | result = EmbedVariableNode.new( val[0][1] )
142 | }
143 |
144 | end
145 |
146 |
147 | ---- inner
148 |
149 | require 'strscan'
150 |
151 | def initialize(opts={})
152 | opts = {
153 | :debug => false,
154 | :preserve_space => true,
155 | :preserve_comment => false
156 | }.merge(opts)
157 | @yydebug = opts[:debug]
158 | @preserve_space = opts[:preserve_space]
159 | @preserve_comment = opts[:preserve_comment]
160 | @num_questions = 0
161 | end
162 |
163 |
164 | PAREN_EXAMPLE = '\([^\)]+\)'
165 | BEGIN_BIND_VARIABLE = '(\/|\#)\*([^\*]+)\*\1'
166 | BIND_VARIABLE_PATTERN = /\A#{BEGIN_BIND_VARIABLE}\s*/
167 | PAREN_BIND_VARIABLE_PATTERN = /\A#{BEGIN_BIND_VARIABLE}\s*#{PAREN_EXAMPLE}/
168 | EMBED_VARIABLE_PATTERN = /\A(\/|\#)\*\$([^\*]+)\*\1\s*/
169 |
170 | CONDITIONAL_PATTERN = /\A(\/|\#)\*(IF)\s+([^\*]+)\s*\*\1/
171 | BEGIN_END_PATTERN = /\A(\/|\#)\*(BEGIN|END)\s*\*\1/
172 | STRING_LITERAL_PATTERN = /\A(\'(?:[^\']+|\'\')*\')/ ## quoted string
173 | SPLIT_TOKEN_PATTERN = /\A(\S+?)(?=\s*(?:(?:\/|\#)\*|-{2,}|\(|\)|\,))/ ## stop on delimiters --,/*,#*,',',(,)
174 | LITERAL_PATTERN = /\A([^;\s]+)/
175 | SPACES_PATTERN = /\A(\s+)/
176 | QUESTION_PATTERN = /\A\?/
177 | COMMA_PATTERN = /\A\,/
178 | LPAREN_PATTERN = /\A\(/
179 | RPAREN_PATTERN = /\A\)/
180 | ACTUAL_COMMENT_PATTERN = /\A(\/|\#)\*(\s{1,}(?:.*?))\*\1/m ## start with spaces
181 | SEMICOLON_AT_INPUT_END_PATTERN = /\A\;\s*\Z/
182 | UNMATCHED_COMMENT_START_PATTERN = /\A(?:(?:\/|\#)\*)/
183 |
184 | #TODO: remove trailing spaces for S2Dao compatibility, but this spec sometimes causes SQL bugs...
185 | ELSE_PATTERN = /\A\-{2,}\s*ELSE\s*/
186 | AND_PATTERN = /\A(\ *AND)\b/i
187 | OR_PATTERN = /\A(\ *OR)\b/i
188 |
189 |
190 | def parse( io )
191 | @q = []
192 | io.each_line(nil) do |whole|
193 | @s = StringScanner.new(whole)
194 | end
195 | scan_str
196 |
197 | # @q.push [ false, nil ]
198 | @q.push [ false, [@s.pos, nil] ]
199 |
200 | ## call racc's private parse method
201 | do_parse
202 | end
203 |
204 |
205 | ## called by racc
206 | def next_token
207 | @q.shift
208 | end
209 |
210 |
211 | def scan_str
212 | until @s.eos? do
213 | case
214 | when @s.scan(AND_PATTERN)
215 | @q.push [ :AND, [@s.pos, @s[1]] ]
216 | when @s.scan(OR_PATTERN)
217 | @q.push [ :OR, [@s.pos, @s[1]] ]
218 | when @s.scan(SPACES_PATTERN)
219 | @q.push [ :SPACES, [@s.pos, @s[1]] ]
220 | when @s.scan(QUESTION_PATTERN)
221 | @q.push [ :QUESTION, [@s.pos, nil] ]
222 | when @s.scan(COMMA_PATTERN)
223 | @q.push [ :COMMA, [@s.pos, ','] ]
224 | when @s.scan(LPAREN_PATTERN)
225 | @q.push [ :LPAREN, [@s.pos, '('] ]
226 | when @s.scan(RPAREN_PATTERN)
227 | @q.push [ :RPAREN, [@s.pos, ')'] ]
228 | when @s.scan(ELSE_PATTERN)
229 | @q.push [ :ELSE, [@s.pos, nil] ]
230 | when @s.scan(ACTUAL_COMMENT_PATTERN)
231 | @q.push [ :ACTUAL_COMMENT, [@s.pos, @s[1], @s[2]] ] if @preserve_comment
232 | when @s.scan(BEGIN_END_PATTERN)
233 | @q.push [ @s[2].intern, [@s.pos, nil] ]
234 | when @s.scan(CONDITIONAL_PATTERN)
235 | @q.push [ @s[2].intern, [@s.pos, @s[3]] ]
236 | when @s.scan(EMBED_VARIABLE_PATTERN)
237 | @q.push [ :EMBED_VARIABLE, [@s.pos, @s[2]] ]
238 | when @s.scan(PAREN_BIND_VARIABLE_PATTERN)
239 | @q.push [ :PAREN_BIND_VARIABLE, [@s.pos, @s[2]] ]
240 | when @s.scan(BIND_VARIABLE_PATTERN)
241 | @q.push [ :BIND_VARIABLE, [@s.pos, @s[2]] ]
242 | when @s.scan(STRING_LITERAL_PATTERN)
243 | @q.push [ :STRING_LITERAL, [@s.pos, @s[1]] ]
244 | when @s.scan(SPLIT_TOKEN_PATTERN)
245 | @q.push [ :IDENT, [@s.pos, @s[1]] ]
246 | when @s.scan(UNMATCHED_COMMENT_START_PATTERN) ## unmatched comment start, '/*','#*'
247 | raise Racc::ParseError, "unmatched comment. line:[#{line_no(@s.pos)}], str:[#{@s.rest}]"
248 | when @s.scan(LITERAL_PATTERN) ## other string token
249 | @q.push [ :IDENT, [@s.pos, @s[1]] ]
250 | when @s.scan(SEMICOLON_AT_INPUT_END_PATTERN)
251 | #drop semicolon at input end
252 | else
253 | raise Racc::ParseError, "syntax error at or near line:[#{line_no(@s.pos)}], str:[#{@s.rest}]"
254 | end
255 | end
256 | end
257 |
258 |
259 | ## override racc's default on_error method
260 | def on_error(t, v, vstack)
261 | ## cursor in value-stack is an array of two items,
262 | ## that have position value as 0th item. like [731, "ctx[:limit] "]
263 | cursor = vstack.find do |tokens|
264 | tokens.size == 2 and tokens[0].kind_of?(Fixnum)
265 | end
266 | pos = cursor[0]
267 | line = line_no(pos)
268 | rest = @s.string[pos .. -1]
269 | raise Racc::ParseError, "syntax error at or near line:[#{line}], str:[#{rest}]"
270 | end
271 |
272 |
273 | def line_no(pos)
274 | lines = 0
275 | scanned = @s.string[0..(pos)]
276 | scanned.each_line { lines += 1 }
277 | lines
278 | end
279 |
--------------------------------------------------------------------------------
/test/regress/php_serialization:
--------------------------------------------------------------------------------
1 | #
2 | # DO NOT MODIFY!!!!
3 | # This file is automatically generated by Racc 1.5.2
4 | # from Racc grammar file "php_serialization.y".
5 | #
6 |
7 | require 'racc/parser.rb'
8 |
9 | require 'php_serialization/tokenizer'
10 |
11 | module PhpSerialization
12 | class Unserializer < Racc::Parser
13 |
14 | module_eval(<<'...end php_serialization.y/module_eval...', 'php_serialization.y', 84)
15 | def initialize(tokenizer_klass = Tokenizer)
16 | @tokenizer_klass = tokenizer_klass
17 | end
18 |
19 | def run(string)
20 | @tokenizer = @tokenizer_klass.new(string)
21 | yyparse(@tokenizer, :each)
22 | return @object
23 | ensure
24 | @tokenizer = nil
25 | end
26 |
27 | def next_token
28 | @tokenizer.next_token
29 | end
30 | ...end php_serialization.y/module_eval...
31 | ##### State transition tables begin ###
32 |
33 | racc_action_table = [
34 | 9, 10, 16, 17, 11, 12, 13, 18, 14, 9,
35 | 10, 15, 19, 11, 12, 13, 20, 14, 21, 46,
36 | 15, 9, 10, 22, 23, 11, 12, 13, 24, 14,
37 | 9, 10, 15, 25, 11, 12, 13, 26, 14, 27,
38 | 51, 15, 28, 29, 30, 31, 32, 33, 34, 35,
39 | 36, 37, 38, 39, 40, 41, 43, 47, 49 ]
40 |
41 | racc_action_check = [
42 | 0, 0, 1, 2, 0, 0, 0, 3, 0, 42,
43 | 42, 0, 4, 42, 42, 42, 5, 42, 6, 42,
44 | 42, 45, 45, 10, 11, 45, 45, 45, 12, 45,
45 | 50, 50, 45, 13, 50, 50, 50, 14, 50, 15,
46 | 50, 50, 16, 22, 23, 24, 25, 26, 27, 32,
47 | 33, 34, 35, 36, 37, 39, 41, 43, 47 ]
48 |
49 | racc_action_pointer = [
50 | -3, 2, 1, 5, 10, 14, 16, nil, nil, nil,
51 | 18, 19, 23, 28, 32, 34, 42, nil, nil, nil,
52 | nil, nil, 37, 38, 39, 40, 41, 42, nil, nil,
53 | nil, nil, 44, 45, 46, 42, 43, 42, nil, 50,
54 | nil, 50, 6, 52, nil, 18, nil, 46, nil, nil,
55 | 27, nil ]
56 |
57 | racc_action_default = [
58 | -18, -18, -18, -18, -18, -18, -18, -6, -7, -8,
59 | -18, -18, -18, -18, -18, -18, -18, -1, -2, -3,
60 | -4, -5, -18, -18, -18, -18, -18, -18, 52, -9,
61 | -10, -11, -18, -18, -18, -18, -18, -18, -12, -18,
62 | -15, -18, -18, -18, -14, -18, -17, -18, -16, -15,
63 | -18, -13 ]
64 |
65 | racc_goto_table = [
66 | 1, 42, nil, nil, nil, nil, nil, nil, nil, nil,
67 | 50, nil, nil, nil, nil, nil, nil, nil, nil, nil,
68 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
69 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
70 | nil, nil, nil, nil, nil, 48 ]
71 |
72 | racc_goto_check = [
73 | 1, 9, nil, nil, nil, nil, nil, nil, nil, nil,
74 | 9, nil, nil, nil, nil, nil, nil, nil, nil, nil,
75 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
76 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
77 | nil, nil, nil, nil, nil, 1 ]
78 |
79 | racc_goto_pointer = [
80 | nil, 0, nil, nil, nil, nil, nil, nil, nil, -39,
81 | nil ]
82 |
83 | racc_goto_default = [
84 | nil, 45, 2, 3, 4, 5, 6, 7, 8, nil,
85 | 44 ]
86 |
87 | racc_reduce_table = [
88 | 0, 0, :racc_error,
89 | 2, 16, :_reduce_1,
90 | 2, 16, :_reduce_2,
91 | 2, 16, :_reduce_3,
92 | 2, 16, :_reduce_4,
93 | 2, 16, :_reduce_5,
94 | 1, 16, :_reduce_6,
95 | 1, 16, :_reduce_7,
96 | 1, 17, :_reduce_8,
97 | 3, 18, :_reduce_9,
98 | 3, 19, :_reduce_10,
99 | 3, 20, :_reduce_11,
100 | 5, 21, :_reduce_12,
101 | 11, 23, :_reduce_13,
102 | 2, 24, :_reduce_14,
103 | 0, 24, :_reduce_15,
104 | 2, 25, :_reduce_16,
105 | 7, 22, :_reduce_17 ]
106 |
107 | racc_reduce_n = 18
108 |
109 | racc_shift_n = 52
110 |
111 | racc_token_table = {
112 | false => 0,
113 | :error => 1,
114 | ";" => 2,
115 | "N" => 3,
116 | "b" => 4,
117 | ":" => 5,
118 | :NUMBER => 6,
119 | "i" => 7,
120 | "d" => 8,
121 | "s" => 9,
122 | :STRING => 10,
123 | "O" => 11,
124 | "{" => 12,
125 | "}" => 13,
126 | "a" => 14 }
127 |
128 | racc_nt_base = 15
129 |
130 | racc_use_result_var = true
131 |
132 | Racc_arg = [
133 | racc_action_table,
134 | racc_action_check,
135 | racc_action_default,
136 | racc_action_pointer,
137 | racc_goto_table,
138 | racc_goto_check,
139 | racc_goto_default,
140 | racc_goto_pointer,
141 | racc_nt_base,
142 | racc_reduce_table,
143 | racc_token_table,
144 | racc_shift_n,
145 | racc_reduce_n,
146 | racc_use_result_var ]
147 | Ractor.make_shareable(Racc_arg) if defined?(Ractor)
148 |
149 | Racc_token_to_s_table = [
150 | "$end",
151 | "error",
152 | "\";\"",
153 | "\"N\"",
154 | "\"b\"",
155 | "\":\"",
156 | "NUMBER",
157 | "\"i\"",
158 | "\"d\"",
159 | "\"s\"",
160 | "STRING",
161 | "\"O\"",
162 | "\"{\"",
163 | "\"}\"",
164 | "\"a\"",
165 | "$start",
166 | "data",
167 | "null",
168 | "bool",
169 | "integer",
170 | "double",
171 | "string",
172 | "assoc_array",
173 | "object",
174 | "attribute_list",
175 | "attribute" ]
176 | Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
177 |
178 | Racc_debug_parser = false
179 |
180 | ##### State transition tables end #####
181 |
182 | # reduce 0 omitted
183 |
184 | module_eval(<<'.,.,', 'php_serialization.y', 6)
185 | def _reduce_1(val, _values, result)
186 | @object = val[0]
187 | result
188 | end
189 | .,.,
190 |
191 | module_eval(<<'.,.,', 'php_serialization.y', 7)
192 | def _reduce_2(val, _values, result)
193 | @object = val[0]
194 | result
195 | end
196 | .,.,
197 |
198 | module_eval(<<'.,.,', 'php_serialization.y', 8)
199 | def _reduce_3(val, _values, result)
200 | @object = val[0]
201 | result
202 | end
203 | .,.,
204 |
205 | module_eval(<<'.,.,', 'php_serialization.y', 9)
206 | def _reduce_4(val, _values, result)
207 | @object = val[0]
208 | result
209 | end
210 | .,.,
211 |
212 | module_eval(<<'.,.,', 'php_serialization.y', 10)
213 | def _reduce_5(val, _values, result)
214 | @object = val[0]
215 | result
216 | end
217 | .,.,
218 |
219 | module_eval(<<'.,.,', 'php_serialization.y', 11)
220 | def _reduce_6(val, _values, result)
221 | @object = val[0]
222 | result
223 | end
224 | .,.,
225 |
226 | module_eval(<<'.,.,', 'php_serialization.y', 12)
227 | def _reduce_7(val, _values, result)
228 | @object = val[0]
229 | result
230 | end
231 | .,.,
232 |
233 | module_eval(<<'.,.,', 'php_serialization.y', 15)
234 | def _reduce_8(val, _values, result)
235 | result = nil
236 | result
237 | end
238 | .,.,
239 |
240 | module_eval(<<'.,.,', 'php_serialization.y', 18)
241 | def _reduce_9(val, _values, result)
242 | result = Integer(val[2]) > 0
243 | result
244 | end
245 | .,.,
246 |
247 | module_eval(<<'.,.,', 'php_serialization.y', 21)
248 | def _reduce_10(val, _values, result)
249 | result = Integer(val[2])
250 | result
251 | end
252 | .,.,
253 |
254 | module_eval(<<'.,.,', 'php_serialization.y', 24)
255 | def _reduce_11(val, _values, result)
256 | result = Float(val[2])
257 | result
258 | end
259 | .,.,
260 |
261 | module_eval(<<'.,.,', 'php_serialization.y', 27)
262 | def _reduce_12(val, _values, result)
263 | result = val[4]
264 | result
265 | end
266 | .,.,
267 |
268 | module_eval(<<'.,.,', 'php_serialization.y', 32)
269 | def _reduce_13(val, _values, result)
270 | if eval("defined?(#{val[4]})")
271 | result = Object.const_get(val[4]).new
272 |
273 | val[9].each do |(attr_name, value)|
274 | # Protected and private attributes will have a \0..\0 prefix
275 | attr_name = attr_name.gsub(/\A\\0[^\\]+\\0/, '')
276 | result.instance_variable_set("@#{attr_name}", value)
277 | end
278 | else
279 | klass_name = val[4].gsub(/^Struct::/, '')
280 | attr_names, values = [], []
281 |
282 | val[9].each do |(attr_name, value)|
283 | # Protected and private attributes will have a \0..\0 prefix
284 | attr_names << attr_name.gsub(/\A\\0[^\\]+\\0/, '')
285 | values << value
286 | end
287 |
288 | result = Struct.new(klass_name, *attr_names).new(*values)
289 | result.instance_variable_set("@_php_class", klass_name)
290 | end
291 |
292 | result
293 | end
294 | .,.,
295 |
296 | module_eval(<<'.,.,', 'php_serialization.y', 56)
297 | def _reduce_14(val, _values, result)
298 | result = val[0] << val[1]
299 | result
300 | end
301 | .,.,
302 |
303 | module_eval(<<'.,.,', 'php_serialization.y', 57)
304 | def _reduce_15(val, _values, result)
305 | result = []
306 | result
307 | end
308 | .,.,
309 |
310 | module_eval(<<'.,.,', 'php_serialization.y', 60)
311 | def _reduce_16(val, _values, result)
312 | result = val
313 | result
314 | end
315 | .,.,
316 |
317 | module_eval(<<'.,.,', 'php_serialization.y', 65)
318 | def _reduce_17(val, _values, result)
319 | # Checks if the keys are a sequence of integers
320 | idx = -1
321 | arr = val[5].all? { |(k,v)| k == (idx += 1) }
322 |
323 | if arr
324 | result = val[5].map { |(k,v)| v }
325 | else
326 | result = Hash[val[5]]
327 | end
328 |
329 | result
330 | end
331 | .,.,
332 |
333 | def _reduce_none(val, _values, result)
334 | val[0]
335 | end
336 |
337 | end # class Unserializer
338 | end # module PhpSerialization
339 |
--------------------------------------------------------------------------------
/test/assets/liquor.y:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2012-2013 Peter Zotov
2 | # 2012 Yaroslav Markin
3 | # 2012 Nate Gadgibalaev
4 | #
5 | # MIT License
6 | #
7 | # Permission is hereby granted, free of charge, to any person obtaining
8 | # a copy of this software and associated documentation files (the
9 | # "Software"), to deal in the Software without restriction, including
10 | # without limitation the rights to use, copy, modify, merge, publish,
11 | # distribute, sublicense, and/or sell copies of the Software, and to
12 | # permit persons to whom the Software is furnished to do so, subject to
13 | # the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be
16 | # included in all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 |
26 | class Liquor::Parser
27 | token comma dot endtag ident integer keyword lblock lblock2 lbracket
28 | linterp lparen op_div op_eq op_gt op_geq op_lt op_leq op_minus
29 | op_mod op_mul op_neq op_not op_plus pipe plaintext rblock
30 | rbracket rinterp rparen string tag_ident
31 |
32 | prechigh
33 | left dot
34 | nonassoc op_uminus op_not
35 | left op_mul op_div op_mod
36 | left op_plus op_minus
37 | left op_eq op_neq op_lt op_leq op_gt op_geq
38 | left op_and
39 | left op_or
40 | preclow
41 |
42 | expect 15
43 |
44 | start block
45 |
46 | rule
47 | block: /* empty */
48 | { result = [] }
49 | | plaintext block
50 | { result = [ val[0], *val[1] ] }
51 | | interp block
52 | { result = [ val[0], *val[1] ] }
53 | | tag block
54 | { result = [ val[0], *val[1] ] }
55 |
56 | interp:
57 | linterp expr rinterp
58 | { result = [ :interp, retag(val), val[1] ] }
59 | | linterp filter_chain rinterp
60 | { result = [ :interp, retag(val), val[1] ] }
61 |
62 | primary_expr:
63 | ident
64 | | lparen expr rparen
65 | { result = [ val[1][0], retag(val), *val[1][2..-1] ] }
66 |
67 | expr:
68 | integer
69 | | string
70 | | tuple
71 | | ident function_args
72 | { result = [ :call, retag(val), val[0], val[1] ] }
73 | | expr lbracket expr rbracket
74 | { result = [ :index, retag(val), val[0], val[2] ] }
75 | | expr dot ident function_args
76 | { result = [ :external, retag(val), val[0], val[2], val[3] ] }
77 | | expr dot ident
78 | { result = [ :external, retag(val), val[0], val[2], nil ] }
79 | | op_minus expr =op_uminus
80 | { result = [ :uminus, retag(val), val[1] ] }
81 | | op_not expr
82 | { result = [ :not, retag(val), val[1] ] }
83 | | expr op_mul expr
84 | { result = [ :mul, retag(val), val[0], val[2] ] }
85 | | expr op_div expr
86 | { result = [ :div, retag(val), val[0], val[2] ] }
87 | | expr op_mod expr
88 | { result = [ :mod, retag(val), val[0], val[2] ] }
89 | | expr op_plus expr
90 | { result = [ :plus, retag(val), val[0], val[2] ] }
91 | | expr op_minus expr
92 | { result = [ :minus, retag(val), val[0], val[2] ] }
93 | | expr op_eq expr
94 | { result = [ :eq, retag(val), val[0], val[2] ] }
95 | | expr op_neq expr
96 | { result = [ :neq, retag(val), val[0], val[2] ] }
97 | | expr op_lt expr
98 | { result = [ :lt, retag(val), val[0], val[2] ] }
99 | | expr op_leq expr
100 | { result = [ :leq, retag(val), val[0], val[2] ] }
101 | | expr op_gt expr
102 | { result = [ :gt, retag(val), val[0], val[2] ] }
103 | | expr op_geq expr
104 | { result = [ :geq, retag(val), val[0], val[2] ] }
105 | | expr op_and expr
106 | { result = [ :and, retag(val), val[0], val[2] ] }
107 | | expr op_or expr
108 | { result = [ :or, retag(val), val[0], val[2] ] }
109 | | primary_expr
110 |
111 | tuple:
112 | lbracket tuple_content rbracket
113 | { result = [ :tuple, retag(val), val[1].compact ] }
114 |
115 | tuple_content:
116 | expr comma tuple_content
117 | { result = [ val[0], *val[2] ] }
118 | | expr
119 | { result = [ val[0] ] }
120 | | /* empty */
121 | { result = [ ] }
122 |
123 | function_args:
124 | lparen function_args_inside rparen
125 | { result = [ :args, retag(val), *val[1] ] }
126 |
127 | function_args_inside:
128 | expr function_keywords
129 | { result = [ val[0], val[1][2] ] }
130 | | function_keywords
131 | { result = [ nil, val[0][2] ] }
132 |
133 | function_keywords:
134 | keyword expr function_keywords
135 | { name = val[0][2].to_sym
136 | tail = val[2][2]
137 | loc = retag([ val[0], val[1] ])
138 |
139 | if tail.include? name
140 | @errors << SyntaxError.new("duplicate keyword argument `#{val[0][2]}'",
141 | tail[name][1])
142 | end
143 |
144 | hash = {
145 | name => [ val[1][0], loc, *val[1][2..-1] ]
146 | }.merge(tail)
147 |
148 | result = [ :keywords, retag([ loc, val[2] ]), hash ]
149 | }
150 | | /* empty */
151 | { result = [ :keywords, nil, {} ] }
152 |
153 | filter_chain:
154 | expr pipe filter_chain_cont
155 | { result = [ val[0], *val[2] ].
156 | reduce { |tree, node| node[3][2] = tree; node }
157 | }
158 |
159 | filter_chain_cont:
160 | filter_call pipe filter_chain_cont
161 | { result = [ val[0], *val[2] ] }
162 | | filter_call
163 | { result = [ val[0] ] }
164 |
165 | filter_call:
166 | ident function_keywords
167 | { ident_loc = val[0][1]
168 | empty_args_loc = { line: ident_loc[:line],
169 | start: ident_loc[:end] + 1,
170 | end: ident_loc[:end] + 1, }
171 | result = [ :call, val[0][1], val[0],
172 | [ :args, val[1][1] || empty_args_loc, nil, val[1][2] ] ]
173 | }
174 |
175 | tag:
176 | lblock ident expr tag_first_cont
177 | { result = [ :tag, retag(val), val[1], val[2], *reduce_tag_args(val[3][2]) ] }
178 | | lblock ident tag_first_cont
179 | { result = [ :tag, retag(val), val[1], nil, *reduce_tag_args(val[2][2]) ] }
180 |
181 | # Racc cannot do lookahead across rules. I had to add states
182 | # explicitly to avoid S/R conflicts. You are not expected to
183 | # understand this.
184 |
185 | tag_first_cont:
186 | rblock
187 | { result = [ :cont, retag(val), [] ] }
188 | | keyword tag_first_cont2
189 | { result = [ :cont, retag(val), [ val[0], *val[1][2] ] ] }
190 |
191 | tag_first_cont2:
192 | rblock block lblock2 tag_next_cont
193 | { result = [ :cont2, val[0][1], [ [:block, val[0][1], val[1] ], *val[3] ] ] }
194 | | expr tag_first_cont
195 | { result = [ :cont2, retag(val), [ val[0], *val[1][2] ] ] }
196 |
197 | tag_next_cont:
198 | endtag rblock
199 | { result = [] }
200 | | keyword tag_next_cont2
201 | { result = [ val[0], *val[1] ] }
202 |
203 | tag_next_cont2:
204 | rblock block lblock2 tag_next_cont
205 | { result = [ [:block, val[0][1], val[1] ], *val[3] ] }
206 | | expr keyword tag_next_cont3
207 | { result = [ val[0], val[1], *val[2] ] }
208 |
209 | tag_next_cont3:
210 | rblock block lblock2 tag_next_cont
211 | { result = [ [:block, val[0][1], val[1] ], *val[3] ] }
212 | | expr tag_next_cont
213 | { result = [ val[0], *val[1] ] }
214 |
215 | ---- inner
216 | attr_reader :errors, :ast
217 |
218 | def initialize(tags={})
219 | super()
220 |
221 | @errors = []
222 | @ast = nil
223 | @tags = tags
224 | end
225 |
226 | def success?
227 | @errors.empty?
228 | end
229 |
230 | def parse(string, name='(code)')
231 | @errors.clear
232 | @name = name
233 | @ast = nil
234 |
235 | begin
236 | @stream = Lexer.lex(string, @name, @tags)
237 | @ast = do_parse
238 | rescue Liquor::SyntaxError => e
239 | @errors << e
240 | end
241 |
242 | success?
243 | end
244 |
245 | def next_token
246 | tok = @stream.shift
247 | [ tok[0], tok ] if tok
248 | end
249 |
250 | TOKEN_NAME_MAP = {
251 | :comma => ',',
252 | :dot => '.',
253 | :lblock => '{%',
254 | :rblock => '%}',
255 | :linterp => '{{',
256 | :rinterp => '}}',
257 | :lbracket => '[',
258 | :rbracket => ']',
259 | :lparen => '(',
260 | :rparen => ')',
261 | :pipe => '|',
262 | :op_not => '!',
263 | :op_mul => '*',
264 | :op_div => '/',
265 | :op_mod => '%',
266 | :op_plus => '+',
267 | :op_minus => '-',
268 | :op_eq => '==',
269 | :op_neq => '!=',
270 | :op_lt => '<',
271 | :op_leq => '<=',
272 | :op_gt => '>',
273 | :op_geq => '>=',
274 | :keyword => 'keyword argument name',
275 | :kwarg => 'keyword argument',
276 | :ident => 'identifier',
277 | }
278 |
279 | def on_error(error_token_id, error_token, value_stack)
280 | if token_to_str(error_token_id) == "$end"
281 | raise Liquor::SyntaxError.new("unexpected end of program", {
282 | file: @name
283 | })
284 | else
285 | type, (loc, value) = error_token
286 | type = TOKEN_NAME_MAP[type] || type
287 |
288 | raise Liquor::SyntaxError.new("unexpected token `#{type}'", loc)
289 | end
290 | end
291 |
292 | def retag(nodes)
293 | loc = nodes.map { |node| node[1] }.compact
294 | first, *, last = loc
295 | return first if last.nil?
296 |
297 | {
298 | file: first[:file],
299 | line: first[:line],
300 | start: first[:start],
301 | end: last[:end],
302 | }
303 | end
304 |
305 | def reduce_tag_args(list)
306 | list.each_slice(2).reduce([]) { |args, (k, v)|
307 | if v[0] == :block
308 | args << [ :blockarg, retag([ k, v ]), k, v[2] || [] ]
309 | else
310 | args << [ :kwarg, retag([ k, v ]), k, v ]
311 | end
312 | }
313 | end
314 |
--------------------------------------------------------------------------------
/bin/racc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | #
4 | #
5 | # Copyright (c) 1999-2006 Minero Aoki
6 | #
7 | # This program is free software.
8 | # You can distribute/modify this program under the same terms of ruby.
9 | # see the file "COPYING".
10 |
11 | require 'racc/static'
12 | require 'optparse'
13 |
14 | def main
15 | output = nil
16 | debug_parser = false
17 | make_logfile = false
18 | logfilename = nil
19 | make_executable = false
20 | rubypath = nil
21 | embed_runtime = false
22 | frozen_strings = false
23 | debug_flags = Racc::DebugFlags.new
24 | line_convert = true
25 | line_convert_all = false
26 | omit_action_call = true
27 | superclass = nil
28 | check_only = false
29 | verbose = false
30 | profiler = RaccProfiler.new(false)
31 |
32 | parser = OptionParser.new
33 | parser.banner = "Usage: #{File.basename($0)} [options] [input]"
34 | parser.on('-o', '--output-file=PATH',
35 | 'output file name [ .tab.rb]') {|name|
36 | output = name
37 | }
38 | parser.on('-t', '--debug', 'Outputs debugging parser.') {|fl|
39 | debug_parser = fl
40 | }
41 | parser.on('-g', 'Equivalent to -t (obsolete).') {|fl|
42 | $stderr.puts "racc -g is obsolete. Use racc -t instead." if $VERBOSE
43 | debug_parser = fl
44 | }
45 | parser.on('-v', '--verbose',
46 | 'Creates .output log file.') {|fl|
47 | make_logfile = fl
48 | }
49 | parser.on('-O', '--log-file=PATH',
50 | 'Log file name [ .output]') {|path|
51 | make_logfile = true
52 | logfilename = path
53 | }
54 | parser.on('-e', '--executable [RUBYPATH]', 'Makes executable parser.') {|path|
55 | make_executable = true
56 | rubypath = (path == 'ruby' ? nil : path)
57 | }
58 | parser.on('-E', '--embedded', "Embeds Racc runtime in output.") {
59 | embed_runtime = true
60 | }
61 | parser.on('-F', '--frozen', "Add frozen_string_literals: true.") {
62 | frozen_strings = true
63 | }
64 | parser.on('--line-convert-all', 'Converts line numbers of user codes.') {
65 | line_convert_all = true
66 | }
67 | parser.on('-l', '--no-line-convert', 'Never convert line numbers.') {
68 | line_convert = false
69 | line_convert_all = false
70 | }
71 | parser.on('-a', '--no-omit-actions', 'Never omit actions.') {
72 | omit_action_call = false
73 | }
74 | parser.on('--superclass=CLASSNAME',
75 | 'Uses CLASSNAME instead of Racc::Parser.') {|name|
76 | superclass = name
77 | }
78 | parser.on('-C', '--check-only', 'Checks syntax and quit immediately.') {|fl|
79 | check_only = fl
80 | }
81 | parser.on('-S', '--output-status', 'Outputs internal status time to time.') {
82 | verbose = true
83 | }
84 | parser.on('-P', 'Enables generator profile') {
85 | profiler = RaccProfiler.new(true)
86 | }
87 | parser.on('-D flags', "Flags for Racc debugging (do not use).") {|flags|
88 | debug_flags = Racc::DebugFlags.parse_option_string(flags)
89 | }
90 | #parser.on('--no-extensions', 'Run Racc without any Ruby extension.') {
91 | # Racc.const_set :Racc_No_Extensions, true
92 | #}
93 | parser.on('--version', 'Prints version and quit.') {
94 | puts "racc version #{Racc::Version}"
95 | exit
96 | }
97 | parser.on('--runtime-version', 'Prints runtime version and quit.') {
98 | printf "racc runtime version %s; %s core version %s\n",
99 | Racc::Parser::Racc_Runtime_Version,
100 | Racc::Parser.racc_runtime_type,
101 | if Racc::Parser.racc_runtime_type == 'ruby'
102 | Racc::Parser::Racc_Runtime_Core_Version_R
103 | else
104 | Racc::Parser::Racc_Runtime_Core_Version_C
105 | end
106 | exit
107 | }
108 | parser.on('--copyright', 'Prints copyright and quit.') {
109 | puts Racc::Copyright
110 | exit
111 | }
112 | parser.on('--help', 'Prints this message and quit.') {
113 | puts parser.help
114 | exit
115 | }
116 | begin
117 | parser.parse!
118 | rescue OptionParser::ParseError => err
119 | abort [err.message, parser.help].join("\n")
120 | end
121 | if ARGV.size > 1
122 | abort 'too many input'
123 | end
124 |
125 | input = ARGV[0] || "stdin"
126 |
127 | if input == "stdin" && !output then
128 | abort 'You must specify a path to read or use -o for output.'
129 | end
130 |
131 | begin
132 | $stderr.puts 'Parsing grammar file...' if verbose
133 | result = profiler.section('parse') {
134 | parser = Racc::GrammarFileParser.new(debug_flags)
135 | content = input == "stdin" ? ARGF.read : File.read(input)
136 | parser.parse(content, File.basename(input))
137 | }
138 | if check_only
139 | $stderr.puts 'syntax ok'
140 | exit
141 | end
142 |
143 | $stderr.puts 'Generating LALR states...' if verbose
144 | states = profiler.section('nfa') {
145 | Racc::States.new(result.grammar).nfa
146 | }
147 |
148 | $stderr.puts "Resolving #{states.size} states..." if verbose
149 | profiler.section('dfa') {
150 | states.dfa
151 | }
152 |
153 | $stderr.puts 'Creating parser file...' if verbose
154 | params = result.params.dup
155 | params.filename = File.basename(input)
156 | # Overwrites parameters given by a grammar file with command line options.
157 | params.superclass = superclass if superclass
158 | params.omit_action_call = true if omit_action_call
159 | # From command line option
160 | if make_executable
161 | params.make_executable = true
162 | params.interpreter = rubypath
163 | end
164 | params.debug_parser = debug_parser
165 | params.convert_line = line_convert
166 | params.convert_line_all = line_convert_all
167 | params.embed_runtime = embed_runtime
168 | params.frozen_strings = frozen_strings
169 | profiler.section('generation') {
170 | generator = Racc::ParserFileGenerator.new(states, params)
171 | generator.generate_parser_file(output || make_filename(input, '.tab.rb'))
172 | }
173 |
174 | if make_logfile
175 | profiler.section('logging') {
176 | $stderr.puts 'Creating log file...' if verbose
177 | logfilename ||= make_filename(output || File.basename(input), '.output')
178 | File.open(logfilename, 'w') {|f|
179 | Racc::LogFileGenerator.new(states, debug_flags).output f
180 | }
181 | }
182 | end
183 | if debug_flags.status_logging
184 | log_useless states.grammar
185 | log_conflict states
186 | else
187 | has_useless = report_useless states.grammar
188 | has_conflicts = report_conflict states
189 | if has_useless || has_conflicts
190 | preamble = make_logfile ? 'C' : 'Turn on logging with "-v" and c'
191 | $stderr.puts %Q{#{preamble}heck ".output" file for details}
192 | end
193 | end
194 |
195 | profiler.report
196 | if states.should_error_on_expect_mismatch?
197 | raise Racc::CompileError, "#{states.grammar.n_expected_srconflicts} shift/reduce conflicts are expected but #{states.n_srconflicts} shift/reduce conflicts exist"
198 | end
199 | rescue Racc::Error, Errno::ENOENT, Errno::EPERM => err
200 | raise if $DEBUG or debug_flags.any?
201 | lineno = err.message.slice(/\A\d+:/).to_s
202 | abort "#{File.basename $0}: #{input}:#{lineno} #{err.message.strip}"
203 | end
204 | end
205 |
206 | def make_filename(path, suffix)
207 | path.sub(/(?:\..*?)?\z/, suffix)
208 | end
209 |
210 | LIST_LIMIT = 10
211 | def report_list(enum, label)
212 | c = enum.count
213 | if c > 0
214 | $stderr.puts "#{c} #{label}:"
215 | enum.first(LIST_LIMIT).each do |item|
216 | $stderr.puts " #{yield item}"
217 | end
218 | $stderr.puts " ..." if c > LIST_LIMIT
219 | end
220 | end
221 |
222 | # @return [Boolean] if anything was reported
223 | def report_conflict(states)
224 | if states.should_report_srconflict?
225 | reported = true
226 | $stderr.puts "#{states.n_srconflicts} shift/reduce conflicts"
227 | end
228 | if states.rrconflict_exist?
229 | reported = true
230 | $stderr.puts "#{states.n_rrconflicts} reduce/reduce conflicts"
231 | end
232 | reported
233 | end
234 |
235 | def log_conflict(states)
236 | logging('w') {|f|
237 | f.puts "ex#{states.grammar.n_expected_srconflicts}"
238 | if states.should_report_srconflict?
239 | f.puts "sr#{states.n_srconflicts}"
240 | end
241 | if states.rrconflict_exist?
242 | f.puts "rr#{states.n_rrconflicts}"
243 | end
244 | }
245 | end
246 |
247 | # @return [Boolean] if anything was reported
248 | def report_useless(grammar)
249 | reported = report_list(grammar.each_useless_nonterminal, 'useless nonterminals', &:to_s)
250 |
251 | reported ||= report_list(grammar.each_useless_rule, 'useless rules') { |r| "##{r.ident} (#{r.target})" }
252 |
253 | if grammar.start.useless?
254 | $stderr.puts 'fatal: start symbol does not derive any sentence'
255 | reported = true
256 | end
257 | reported
258 | end
259 |
260 | def log_useless(grammar)
261 | logging('a') {|f|
262 | if grammar.useless_nonterminal_exist?
263 | f.puts "un#{grammar.n_useless_nonterminals}"
264 | end
265 | if grammar.useless_rule_exist?
266 | f.puts "ur#{grammar.n_useless_rules}"
267 | end
268 | }
269 | end
270 |
271 | def logging(mode, &block)
272 | File.open("log/#{File.basename(ARGV[0])}", mode, &block)
273 | end
274 |
275 | class RaccProfiler
276 | def initialize(really)
277 | @really = really
278 | @log = []
279 | end
280 |
281 | def section(name)
282 | if @really
283 | t1 = ::Process.times.utime
284 | result = yield
285 | t2 = ::Process.times.utime
286 | @log.push [name, t2 - t1]
287 | result
288 | else
289 | yield
290 | end
291 | end
292 |
293 | def report
294 | return unless @really
295 | f = $stderr
296 | total = cumulative_time()
297 | f.puts '--task-----------+--sec------+---%-'
298 | @log.each do |name, time|
299 | f.printf "%-19s %s %3d%%\n", name, pjust(time,4,4), (time/total*100).to_i
300 | end
301 | f.puts '-----------------+-----------+-----'
302 | f.printf "%-20s%s\n", 'total', pjust(total,4,4)
303 | end
304 |
305 | private
306 |
307 | def cumulative_time
308 | t = @log.inject(0) {|sum, (name, time)| sum + time }
309 | t == 0 ? 0.01 : t
310 | end
311 |
312 | def pjust(num, i, j)
313 | m = /(\d+)(\.\d+)?/.match(num.to_s)
314 | str = m[1].rjust(i)
315 | str.concat m[2].ljust(j+1)[0,j+1] if m[2]
316 | str
317 | end
318 | end
319 |
320 | main
321 |
--------------------------------------------------------------------------------