diff --git a/tool/lrama/exe/lrama b/tool/lrama/exe/lrama index 5e1ee582cf5681..ba5fb06c82c7dd 100755 --- a/tool/lrama/exe/lrama +++ b/tool/lrama/exe/lrama @@ -3,4 +3,4 @@ $LOAD_PATH << File.join(__dir__, "../lib") require "lrama" -Lrama::Command.new(ARGV.dup).run +Lrama::Command.new.run(ARGV.dup) diff --git a/tool/lrama/lib/lrama.rb b/tool/lrama/lib/lrama.rb index 12e635d8b6ae73..880e64df5fa090 100644 --- a/tool/lrama/lib/lrama.rb +++ b/tool/lrama/lib/lrama.rb @@ -5,6 +5,8 @@ require "lrama/digraph" require "lrama/grammar" require "lrama/lexer" +require "lrama/option_parser" +require "lrama/options" require "lrama/output" require "lrama/parser" require "lrama/report" diff --git a/tool/lrama/lib/lrama/command.rb b/tool/lrama/lib/lrama/command.rb index 45670ae405c0c6..39e3c3665bbf8e 100644 --- a/tool/lrama/lib/lrama/command.rb +++ b/tool/lrama/lib/lrama/command.rb @@ -1,53 +1,34 @@ -require 'optparse' - module Lrama class Command - def initialize(argv) - @argv = argv - - @skeleton = "bison/yacc.c" - @header = false - @header_file = nil - @report = [] - @report_file = nil - @outfile = "y.tab.c" - @trace = [] - @error_recovery = false - @grammar_file = nil - @report_file = nil - @trace_opts = nil - @report_opts = nil - end + def run(argv) + options = OptionParser.new.parse(argv) - def run - parse_option - - Report::Duration.enable if @trace_opts[:time] + Report::Duration.enable if options.trace_opts[:time] warning = Lrama::Warning.new - grammar = Lrama::Parser.new(@y.read).parse - @y.close if @y != STDIN - states = Lrama::States.new(grammar, warning, trace_state: (@trace_opts[:automaton] || @trace_opts[:closure])) + grammar = Lrama::Parser.new(options.y.read).parse + options.y.close if options.y != STDIN + states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure])) states.compute context = Lrama::Context.new(states) - if @report_file + if options.report_file reporter = Lrama::StatesReporter.new(states) - File.open(@report_file, "w+") do |f| - reporter.report(f, **@report_opts) + File.open(options.report_file, "w+") do |f| + reporter.report(f, **options.report_opts) end end - File.open(@outfile, "w+") do |f| + File.open(options.outfile, "w+") do |f| Lrama::Output.new( out: f, - output_file_path: @outfile, - template_name: @skeleton, - grammar_file_path: @grammar_file, - header_file_path: @header_file, + output_file_path: options.outfile, + template_name: options.skeleton, + grammar_file_path: options.grammar_file, + header_file_path: options.header_file, context: context, grammar: grammar, - error_recovery: @error_recovery, + error_recovery: options.error_recovery, ).render end @@ -55,108 +36,5 @@ def run exit 1 end end - - private - - def validate_report(report) - bison_list = %w[states itemsets lookaheads solved counterexamples cex all none] - others = %w[verbose] - list = bison_list + others - not_supported = %w[cex none] - h = { grammar: true } - - report.each do |r| - if list.include?(r) && !not_supported.include?(r) - h[r.to_sym] = true - else - raise "Invalid report option \"#{r}\"." - end - end - - if h[:all] - (bison_list - not_supported).each do |r| - h[r.to_sym] = true - end - - h.delete(:all) - end - - return h - end - - def validate_trace(trace) - list = %w[ - none locations scan parse automaton bitsets - closure grammar resource sets muscles tools - m4-early m4 skeleton time ielr cex all - ] - h = {} - - trace.each do |t| - if list.include?(t) - h[t.to_sym] = true - else - raise "Invalid trace option \"#{t}\"." - end - end - - return h - end - - def parse_option - opt = OptionParser.new - - # opt.on('-h') {|v| p v } - opt.on('-V', '--version') {|v| puts "lrama #{Lrama::VERSION}"; exit 0 } - - # Tuning the Parser - opt.on('-S', '--skeleton=FILE') {|v| @skeleton = v } - opt.on('-t') { } # Do nothing - - # Output Files: - opt.on('-h', '--header=[FILE]') {|v| @header = true; @header_file = v } - opt.on('-d') { @header = true } - opt.on('-r', '--report=THINGS', Array) {|v| @report = v } - opt.on('--report-file=FILE') {|v| @report_file = v } - opt.on('-v') { } # Do nothing - opt.on('-o', '--output=FILE') {|v| @outfile = v } - - # Hidden - opt.on('--trace=THINGS', Array) {|v| @trace = v } - - # Error Recovery - opt.on('-e') {|v| @error_recovery = true } - - opt.parse!(@argv) - - @trace_opts = validate_trace(@trace) - @report_opts = validate_report(@report) - - @grammar_file = @argv.shift - - if !@grammar_file - abort "File should be specified\n" - end - - if @grammar_file == '-' - @grammar_file = @argv.shift or abort "File name for STDIN should be specified\n" - @y = STDIN - else - @y = File.open(@grammar_file, 'r') - end - - if !@report.empty? && @report_file.nil? && @grammar_file - @report_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".output" - end - - if !@header_file && @header - case - when @outfile - @header_file = File.dirname(@outfile) + "/" + File.basename(@outfile, ".*") + ".h" - when @grammar_file - @header_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".h" - end - end - end end end diff --git a/tool/lrama/lib/lrama/lexer.rb b/tool/lrama/lib/lrama/lexer.rb index c591684a05e621..72ce90195f0d9e 100644 --- a/tool/lrama/lib/lrama/lexer.rb +++ b/tool/lrama/lib/lrama/lexer.rb @@ -213,19 +213,33 @@ def lex_user_code(ss, line, column, lines) string, line = lex_string(ss, "'", line, lines) str << string next + + # $ references + # It need to wrap an identifier with brackets to use ".-" for identifiers when ss.scan(/\$(<[a-zA-Z0-9_]+>)?\$/) # $$, $$ tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil references << [:dollar, "$", tag, str.length, str.length + ss[0].length - 1] when ss.scan(/\$(<[a-zA-Z0-9_]+>)?(\d+)/) # $1, $2, $1 tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil references << [:dollar, Integer(ss[2]), tag, str.length, str.length + ss[0].length - 1] - when ss.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_.][-a-zA-Z0-9_.]*)/) # $foo, $expr, $program + when ss.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $program (named reference without brackets) + tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil + references << [:dollar, ss[2], tag, str.length, str.length + ss[0].length - 1] + when ss.scan(/\$(<[a-zA-Z0-9_]+>)?\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $expr.right, $expr-right, $program (named reference with brackets) tag = ss[1] ? create_token(Token::Tag, ss[1], line, str.length) : nil references << [:dollar, ss[2], tag, str.length, str.length + ss[0].length - 1] + + # @ references + # It need to wrap an identifier with brackets to use ".-" for identifiers when ss.scan(/@\$/) # @$ references << [:at, "$", nil, str.length, str.length + ss[0].length - 1] when ss.scan(/@(\d+)/) # @1 references << [:at, Integer(ss[1]), nil, str.length, str.length + ss[0].length - 1] + when ss.scan(/@([a-zA-Z][a-zA-Z0-9_]*)/) # @foo, @expr (named reference without brackets) + references << [:at, ss[1], nil, str.length, str.length + ss[0].length - 1] + when ss.scan(/@\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # @expr.right, @expr-right (named reference with brackets) + references << [:at, ss[1], nil, str.length, str.length + ss[0].length - 1] + when ss.scan(/{/) brace_count += 1 when ss.scan(/}/) diff --git a/tool/lrama/lib/lrama/lexer/token.rb b/tool/lrama/lib/lrama/lexer/token.rb index 4eca6f60077bbb..a6180746cc3f77 100644 --- a/tool/lrama/lib/lrama/lexer/token.rb +++ b/tool/lrama/lib/lrama/lexer/token.rb @@ -28,7 +28,13 @@ def numberize_references(lhs, rhs) if lhs.referred_by?(ref_name) '$' else - rhs.find_index {|token| token.referred_by?(ref_name) } + 1 + index = rhs.find_index {|token| token.referred_by?(ref_name) } + + if index + index + 1 + else + raise "'#{ref_name}' is invalid name." + end end [ref[0], value, ref[2], ref[3], ref[4]] else diff --git a/tool/lrama/lib/lrama/option_parser.rb b/tool/lrama/lib/lrama/option_parser.rb new file mode 100644 index 00000000000000..d8ed62ee07e398 --- /dev/null +++ b/tool/lrama/lib/lrama/option_parser.rb @@ -0,0 +1,124 @@ +require 'optparse' + +module Lrama + # Handle option parsing for the command line interface. + class OptionParser + def initialize + @options = Options.new + @trace = [] + @report = [] + end + + def parse(argv) + parse_by_option_parser(argv) + + @options.trace_opts = validate_trace(@trace) + @options.report_opts = validate_report(@report) + @options.grammar_file = argv.shift + + if !@options.grammar_file + abort "File should be specified\n" + end + + if @options.grammar_file == '-' + @options.grammar_file = argv.shift or abort "File name for STDIN should be specified\n" + else + @options.y = File.open(@options.grammar_file, 'r') + end + + if !@report.empty? && @options.report_file.nil? && @options.grammar_file + @options.report_file = File.dirname(@options.grammar_file) + "/" + File.basename(@options.grammar_file, ".*") + ".output" + end + + if !@options.header_file && @options.header + case + when @options.outfile + @options.header_file = File.dirname(@options.outfile) + "/" + File.basename(@options.outfile, ".*") + ".h" + when @options.grammar_file + @options.header_file = File.dirname(@options.grammar_file) + "/" + File.basename(@options.grammar_file, ".*") + ".h" + end + end + + @options + end + + private + + def parse_by_option_parser(argv) + ::OptionParser.new do |o| + o.banner = <<~BANNER + Lrama is LALR (1) parser generator written by Ruby. + + Usage: lrama [options] FILE + BANNER + o.separator '' + o.separator 'Tuning the Parser:' + o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v } + o.on('-t', 'reserved, do nothing') { } + o.separator '' + o.separator 'Output:' + o.on('-h', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v } + o.on('-d', 'also produce a header file') { @options.header = true } + o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v } + o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v } + o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v } + o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v } + o.on('-v', 'reserved, do nothing') { } + o.separator '' + o.separator 'Error Recovery:' + o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true } + o.separator '' + o.separator 'Other options:' + o.on('-V', '--version', "output version information and exit") {|v| puts "lrama #{Lrama::VERSION}"; exit 0 } + o.on('--help', "display this help and exit") {|v| puts o; exit 0 } + o.separator '' + o.parse!(argv) + end + end + + def validate_report(report) + bison_list = %w[states itemsets lookaheads solved counterexamples cex all none] + others = %w[verbose] + list = bison_list + others + not_supported = %w[cex none] + h = { grammar: true } + + report.each do |r| + if list.include?(r) && !not_supported.include?(r) + h[r.to_sym] = true + else + raise "Invalid report option \"#{r}\"." + end + end + + if h[:all] + (bison_list - not_supported).each do |r| + h[r.to_sym] = true + end + + h.delete(:all) + end + + return h + end + + def validate_trace(trace) + list = %w[ + none locations scan parse automaton bitsets + closure grammar resource sets muscles tools + m4-early m4 skeleton time ielr cex all + ] + h = {} + + trace.each do |t| + if list.include?(t) + h[t.to_sym] = true + else + raise "Invalid trace option \"#{t}\"." + end + end + + return h + end + end +end diff --git a/tool/lrama/lib/lrama/options.rb b/tool/lrama/lib/lrama/options.rb new file mode 100644 index 00000000000000..01b3e701d97f14 --- /dev/null +++ b/tool/lrama/lib/lrama/options.rb @@ -0,0 +1,23 @@ +module Lrama + # Command line options. + class Options + attr_accessor :skeleton, :header, :header_file, + :report_file, :outfile, + :error_recovery, :grammar_file, + :report_file, :trace_opts, :report_opts, :y + + def initialize + @skeleton = "bison/yacc.c" + @header = false + @header_file = nil + @report_file = nil + @outfile = "y.tab.c" + @error_recovery = false + @grammar_file = nil + @report_file = nil + @trace_opts = nil + @report_opts = nil + @y = STDIN + end + end +end diff --git a/tool/lrama/lib/lrama/version.rb b/tool/lrama/lib/lrama/version.rb index 41215c1aee8b18..fe695873e5f972 100644 --- a/tool/lrama/lib/lrama/version.rb +++ b/tool/lrama/lib/lrama/version.rb @@ -1,3 +1,3 @@ module Lrama - VERSION = "0.5.5".freeze + VERSION = "0.5.6".freeze end