include :peg
#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.output = { name |
"include :peg\n#{name} = peg.new\n" <<
matched.map({ e | e.output }).join("\n ") <<
"\n\n" <<
"export #{name}, \"#{name}_parser\""
}
}
set :line action seq(any_ref(:rule :comment) any(ref(:line_break) no(anything))) {
my.output = matched.first.output
}
set :comment action seq(str("#") kleene(seq no(str("\n")) anything)) {
my.output = ""
}
set :error action seq(str("%") many(seq no(str("%")) anything) str("%")) {
my.output = { "parse_error(\"#{matched[1].text}\")" }
}
#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.output = { "brat.with_this { set(:#{matched.first.text}, #{matched[-2].output}) }" }
}
#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.output = {
first = matched.first
rest = matched.last
true? rest.elements.empty?
{
first.output
}
{
options = rest.elements.map { r |
r.elements.last.output
}
"any(#{first.output}, #{options.join ','})"
}
}
}
#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.output = {
first = matched.first
rest = matched[1]
act = false? matched[2].matched == ""
{ matched[2].matched.elements.last }
{ null }
out = true? rest.elements.empty?
{
first.output
}
{
"seq(#{first.output}, #{rest.elements.map({ e | e.elements.last.output }).join(',')})"
}
null? act
{ out }
{ "action(#{out}, #{act.output})" }
}
}
#A rule expression is an actual matcher or grouping
set :rule_exp any_ref(:maybe :many :kleene :not :and :simple_rule_exp :error)
#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 action seq(str("(") ref(:rule_list) str(")")) {
my.output = matched[1].output
}
#Matches a string literal
set :str_literal action seq(str('"') kleene(any str('\\\\') str('\"') seq(no(str '"') anything)) str('"')) {
my.output = "str(#{text})"
}
#References the name of another rule
set :rule_ref action label(:name ref(:rule_name)) {
my.output = "ref(:#{name.text})"
}
#Matches a regular expression
set :reg_literal action seq(str('/') label(:inner kleene(any str('\\/') seq(no(str '/') anything))) str('/')) {
my.output = "reg(/\\G#{inner.text}/)"
}
#Matches a period
set :anything action seq(str(".") maybe(ref :spaces)) {
my.output = "anything()"
}
#Matches a label (but labels aren't really working currently)
set :label action seq(label(:label_name ref(:rule_name)) str(":") label(:exp ref(:rule_exp))) {
my.output = "label(:#{label_name.text} #{exp.output})"
}
#Matches an optional rule:
# some_rule?
set :maybe action seq(ref(:simple_rule_exp) str("?")) {
my.output = "maybe(#{matched.first.output})"
}
#Matches one or more of a rule:
# some_rule+
set :many action seq(ref(:simple_rule_exp) str("+")) {
my.output = "many(#{matched.first.output})"
}
#Matches zero or more of a rule:
# some_rule*
set :kleene action seq(ref(:simple_rule_exp) str("*")) {
my.output = "kleene(#{matched.first.output})"
}
#Matches a negative lookahead:
# !some_rule
set :not action seq(str("!") ref(:simple_rule_exp)) {
my.output = "no(#{matched.last.output})"
}
#Matches positive lookahead:
# &some_rule
set :and action seq(str("&") ref(:simple_rule_exp)) {
my.output = {
"and(#{matched.last.output})"
}
}
#Matches an action appended to a rule:
# some_rule { ... }
set :action action seq(str("{") kleene(any(reg(/[^{}]+/) ref(:action))) str("}")) {
my.output = my.text
}
#Matches an object squish-in appended to a rule:
# some_rule <cool_thing>
set :squish action reg(/<(\w+)>/) {
my.output = "{ my.squish #{my.matched[1]} }"
}
}
#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, :peg