diff --git a/lib/propane/grammar.rb b/lib/propane/grammar.rb index 94e4b47..8f3164e 100644 --- a/lib/propane/grammar.rb +++ b/lib/propane/grammar.rb @@ -28,15 +28,45 @@ class Propane def parse_statement! @next_line_number = @line_number - if consume!(/\A\s+/) - # Skip white space. - elsif consume!(/\A#.*\n/) - # Skip comment lines. - elsif md = consume!(/\Amodule\s+(\S+)\s*;/) + if parse_white_space! + elsif parse_comment_line! + elsif parse_module_statement! + elsif parse_class_statement! + elsif parse_token_statement! + elsif parse_tokenid_statement! + elsif parse_drop_statement! + elsif parse_rule_statement! + else + if @input.size > 25 + @input = @input.slice(0..20) + "..." + end + raise Error.new("Unexpected grammar input at line #{@line_number}: #{@input.chomp}") + end + @line_number = @next_line_number + end + + def parse_white_space! + consume!(/\A\s+/) + end + + def parse_comment_line! + consume!(/\A#.*\n/) + end + + def parse_module_statement! + if md = consume!(/\Amodule\s+(\S+)\s*;/) @modulename = md[1] - elsif md = consume!(/\Aclass\s+(\S+)\s*;/) + end + end + + def parse_class_statement! + if md = consume!(/\Aclass\s+(\S+)\s*;/) @classname = md[1] - elsif md = consume!(/\Atoken\s+(\S+?)(?:\s+([^\n]+?))?\s*(?:;|<<\n(.*?)^>>\n)/m) + end + end + + def parse_token_statement! + if md = consume!(/\Atoken\s+(\S+?)(?:\s+([^\n]+?))?\s*(?:;|<<\n(.*?)^>>\n)/m) name, pattern, code = *md[1, 3] if pattern.nil? pattern = name @@ -54,17 +84,29 @@ class Propane end pattern = Pattern.new(pattern: pattern, token: token, line_number: @line_number, code: code, code_id: code_id) @patterns << pattern - elsif md = consume!(/\Atokenid\s+(\S+?)\s*;/m) + end + end + + def parse_tokenid_statement! + if md = consume!(/\Atokenid\s+(\S+?)\s*;/m) name = md[1] unless name =~ /^[a-zA-Z_][a-zA-Z_0-9]*$/ raise Error.new("Invalid token name #{name.inspect}") end token = Token.new(name: name, id: @tokens.size, line_number: @line_number) @tokens << token - elsif md = consume!(/\Adrop\s+(\S+)\s*;/) + end + end + + def parse_drop_statement! + if md = consume!(/\Adrop\s+(\S+)\s*;/) pattern = md[1] @patterns << Pattern.new(pattern: pattern, line_number: @line_number, drop: true) - elsif md = consume!(/\A(\S+)\s*->\s*([^\n]*?)(?:;|<<\n(.*?)^>>\n)/m) + end + end + + def parse_rule_statement! + if md = consume!(/\A(\S+)\s*->\s*([^\n]*?)(?:;|<<\n(.*?)^>>\n)/m) rule_name, components, code = *md[1, 3] unless rule_name =~ /^[a-zA-Z_][a-zA-Z_0-9]*$/ raise Error.new("Invalid rule name #{name.inspect}") @@ -72,13 +114,7 @@ class Propane components = components.strip.split(/\s+/) # Reserve rule ID 0 for the "real" start rule. @rules << Rule.new(rule_name, components, code, @line_number, @rules.size + 1) - else - if @input.size > 25 - @input = @input.slice(0..20) + "..." - end - raise Error.new("Unexpected grammar input at line #{@line_number}: #{@input.chomp}") end - @line_number = @next_line_number end # Check if the input string matches the given regex.