include :peg
include 'peg/ast'
#This is a PEG for parsing PEGs
peg_syntax = peg.new {
#A grammar is a list of rules
set :grammar action many(ref :line) { my.squish grammar }
set :line seq(any_ref(:rule :comment) any(ref(:line_break) no(anything)))
set :comment seq(str("#") kleene(seq no(str("\n")) anything))
#Optional spaces
set :opt_spaces reg /\s*/
#Required spaces
set :spaces reg /( )+/
#Multiple line breaks
set :line_break many(str("\n"))
#A rule looks like:
# name = list of rule items
set :rule action seq(ref(:rule_name) ref(:opt_spaces) str("=") ref(:opt_spaces) ref(:rule_list) reg(/( )*/)) { my.squish rule_def }
#A rule name can only contain word characters
set :rule_name reg /\w+/
#A rule list is a sequence of optional rules, separated by |
set :rule_list action seq(ref(:rule_seq) kleene(seq ref(:opt_spaces) str("|") ref(:opt_spaces) ref(:rule_seq))) { my.squish rule_list }
#Matches a sequence of basic matchers separated by spaces
set :rule_seq action seq(ref(:rule_exp) kleene(seq_ref :spaces :rule_exp) maybe(seq ref(:spaces) any_ref(:action :squish))) { my.squish rule_seq }
#A rule expression is an actual matcher or grouping
set :rule_exp any_ref(:maybe :many :kleene :not :and :simple_rule_exp)
#Simple rules (i.e., not suffixed rules like + or *)
set :simple_rule_exp any_ref(:label :str_literal :rule_ref :reg_literal :anything :paren_rule)
#Matches parentheses, which denote priority
set :paren_rule seq(str("(") ref(:rule_list) str(")"))
#Matches a string literal
set :str_literal action seq(str('"') label(:content kleene(any str('\\\\') str('\"') seq(no(str '"') anything))) str('"')) { my.squish str_lit }
#References the name of another rule
set :rule_ref action label(:name ref(:rule_name)) { my.squish rule_ref }
#Matches a regular expression
set :reg_literal action seq(str('/') label(:content kleene(any str('\\/') seq(no(str '/') anything))) str('/')) { my.squish regex_rule }
#Matches a period
set :anything action seq(str(".") maybe(ref :spaces)) { my.squish anything_rule }
#Matches a label
set :label action seq(label(:label_name ref(:rule_name)) str(":") label(:exp ref(:rule_exp))) { my.squish rule_label }
#Matches an optional rule:
# some_rule?
set :maybe action seq(ref(:simple_rule_exp) str("?")) { my.squish maybe_rule }
#Matches one or more of a rule:
# some_rule+
set :many action seq(ref(:simple_rule_exp) str("+")) { my.squish many_rule }
#Matches zero or more of a rule:
# some_rule*
set :kleene action seq(ref(:simple_rule_exp) str("*")) { my.squish kleene_rule }
#Matches a negative lookahead:
# !some_rule
set :not action seq(str("!") ref(:simple_rule_exp)) { my.squish not_rule }
#Matches positive lookahead:
# &some_rule
set :and action seq(str("&") ref(:simple_rule_exp)) { my.squish and_rule }
#Matches an action appended to a rule:
# some_rule { ... }
set :action action seq(str("{") kleene(any(reg(/[^{}]+/) ref(:action))) str("}")) { my.squish set_action }
#Matches an object squish-in appended to a rule:
# some_rule <cool_thing>
set :squish action reg(/<(\w+)>/) { my.squish set_squish }
}
#Generates a parser with the given name from the PEG syntax passed in
peg.make_parser = { name, grammar, *included |
result = peg_syntax.parse grammar
include_files = true? included.empty?
{ "" }
{ "includes #{included.map({ i | "'#{i}'" }).join(' ')}\n" }
true? result
{ true? result.matched_all?
{ include_files << result.output(name) }
{
last_line = result.text.split("\n").last
p "Error parsing around: #{last_line}"
}
}
{ p "Could not parse." }
}
export peg_syntax, :peg_parser