107 lines
2.8 KiB
Ruby
107 lines
2.8 KiB
Ruby
require "erb"
|
|
require "set"
|
|
require_relative "propane/cli"
|
|
require_relative "propane/code_point_range"
|
|
require_relative "propane/fa"
|
|
require_relative "propane/fa/state"
|
|
require_relative "propane/fa/state/transition"
|
|
require_relative "propane/grammar"
|
|
require_relative "propane/lexer"
|
|
require_relative "propane/lexer/dfa"
|
|
require_relative "propane/parser"
|
|
require_relative "propane/parser/item"
|
|
require_relative "propane/parser/item_set"
|
|
require_relative "propane/regex"
|
|
require_relative "propane/regex/nfa"
|
|
require_relative "propane/regex/unit"
|
|
require_relative "propane/rule_set"
|
|
require_relative "propane/rule"
|
|
require_relative "propane/token"
|
|
require_relative "propane/version"
|
|
|
|
class Propane
|
|
|
|
# EOF.
|
|
TOKEN_EOF = 0xFFFFFFFC
|
|
|
|
# Decoding error.
|
|
TOKEN_DECODE_ERROR = 0xFFFFFFFD
|
|
|
|
# Token ID for a "dropped" token.
|
|
TOKEN_DROP = 0xFFFFFFFE
|
|
|
|
# Invalid token ID.
|
|
TOKEN_NONE = 0xFFFFFFFF
|
|
|
|
class Error < RuntimeError
|
|
end
|
|
|
|
def initialize(input)
|
|
@grammar = Grammar.new(input)
|
|
@classname = @grammar.classname
|
|
@modulename = @grammar.modulename
|
|
@rule_sets = @grammar.rule_sets
|
|
end
|
|
|
|
def generate(output_file, log_file)
|
|
expand_rules
|
|
lexer = Lexer.new(@grammar.tokens, @grammar.drop_tokens)
|
|
parser = Parser.new(@rule_sets)
|
|
classname = @classname || File.basename(output_file).sub(%r{[^a-zA-Z0-9].*}, "").capitalize
|
|
erb = ERB.new(File.read(File.join(File.dirname(File.expand_path(__FILE__)), "../assets/parser.d.erb")), trim_mode: "<>")
|
|
result = erb.result(binding.clone)
|
|
File.open(output_file, "wb") do |fh|
|
|
fh.write(result)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def expand_rules
|
|
tokens_by_name = {}
|
|
@grammar.tokens.each do |token|
|
|
if tokens_by_name.include?(token.name)
|
|
raise Error.new("Duplicate token name #{token.name.inspect}")
|
|
end
|
|
tokens_by_name[token.name] = token
|
|
end
|
|
@rule_sets.each do |rule_name, rule_set|
|
|
if tokens_by_name.include?(rule_name)
|
|
raise Error.new("Rule name collides with token name #{rule_name.inspect}")
|
|
end
|
|
end
|
|
unless @rule_sets["Start"]
|
|
raise Error.new("Start rule not found")
|
|
end
|
|
@rule_sets.each do |rule_name, rule_set|
|
|
rule_set.rules.each do |rule|
|
|
rule.components.map! do |component|
|
|
if tokens_by_name[component]
|
|
tokens_by_name[component]
|
|
elsif @rule_sets[component]
|
|
@rule_sets[component]
|
|
else
|
|
raise Error.new("Symbol #{component} not found")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class << self
|
|
|
|
def run(input_file, output_file, log_file)
|
|
begin
|
|
propane = Propane.new(File.read(input_file))
|
|
propane.generate(output_file, log_file)
|
|
rescue Error => e
|
|
$stderr.puts e.message
|
|
return 2
|
|
end
|
|
return 0
|
|
end
|
|
|
|
end
|
|
|
|
end
|