├── .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 | 6 | 7 | 8 | 9 | 10 |
最新版<%= version %>
種別parser generator
形式ruby script, ruby extension
必要環境ruby (>=1.6)
配布条件LGPL
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 | 7 | 8 | 9 | 10 | 11 |
Version<%= version %>
TypeParser Generator
FormatRuby script + Ruby extension
Requirementruby (>=1.6)
LicenseLGPL
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 | --------------------------------------------------------------------------------