Skip to content

Commit

Permalink
Lrama v0.5.6
Browse files Browse the repository at this point in the history
  • Loading branch information
yui-knk committed Sep 13, 2023
1 parent c75d54a commit 4655d21
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 141 deletions.
2 changes: 1 addition & 1 deletion tool/lrama/exe/lrama
Expand Up @@ -3,4 +3,4 @@
$LOAD_PATH << File.join(__dir__, "../lib")
require "lrama"

Lrama::Command.new(ARGV.dup).run
Lrama::Command.new.run(ARGV.dup)
2 changes: 2 additions & 0 deletions tool/lrama/lib/lrama.rb
Expand Up @@ -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"
Expand Down
152 changes: 15 additions & 137 deletions tool/lrama/lib/lrama/command.rb
@@ -1,162 +1,40 @@
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

if warning.has_error?
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
16 changes: 15 additions & 1 deletion tool/lrama/lib/lrama/lexer.rb
Expand Up @@ -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_]+>)?\$/) # $$, $<long>$
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, $<long>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, $<long>program
when ss.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $<long>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, $<long>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(/}/)
Expand Down
8 changes: 7 additions & 1 deletion tool/lrama/lib/lrama/lexer/token.rb
Expand Up @@ -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
Expand Down
124 changes: 124 additions & 0 deletions 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
23 changes: 23 additions & 0 deletions 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
2 changes: 1 addition & 1 deletion tool/lrama/lib/lrama/version.rb
@@ -1,3 +1,3 @@
module Lrama
VERSION = "0.5.5".freeze
VERSION = "0.5.6".freeze
end

0 comments on commit 4655d21

Please sign in to comment.