parse
Grammar.new do #:nodoc:
## Hierarchical syntax
rule :file do
all(:space, zero_or_more(any(:require, :grammar))) {
captures[:require].each do |req|
file = req.value
begin
require file
rescue ::LoadError => e
begin
Citrus.require(file)
rescue LoadError
# Re-raise the original LoadError.
raise e
end
end
end
captures[:grammar].map {|g| g.value }
}
end
rule :grammar do
mod all(:grammar_keyword, :module_name, zero_or_more(any(:include, :root, :rule)), :end_keyword) do
include ModuleNameHelpers
def value
grammar = module_namespace.const_set(module_basename, Grammar.new)
captures[:include].each {|inc| grammar.include(inc.value) }
captures[:rule].each {|r| grammar.rule(r.rule_name.value, r.value) }
root = capture(:root)
grammar.root(root.value) if root
grammar
end
end
end
rule :rule do
mod all(:rule_keyword, :rule_name, zero_or_one(:expression), :end_keyword) do
def rule_name
capture(:rule_name)
end
def value
# An empty rule definition matches the empty string.
expr = capture(:expression)
expr ? expr.value : Rule.for('')
end
end
end
rule :expression do
all(:sequence, zero_or_more([['|', zero_or_one(:space)], :sequence])) {
rules = captures[:sequence].map {|s| s.value }
rules.length > 1 ? Choice.new(rules) : rules.first
}
end
rule :sequence do
one_or_more(:labelled) {
rules = captures[:labelled].map {|l| l.value }
rules.length > 1 ? Sequence.new(rules) : rules.first
}
end
rule :labelled do
all(zero_or_one(:label), :extended) {
label = capture(:label)
rule = capture(:extended).value
rule.label = label.value if label
rule
}
end
rule :extended do
all(:prefix, zero_or_one(:extension)) {
extension = capture(:extension)
rule = capture(:prefix).value
rule.extension = extension.value if extension
rule
}
end
rule :prefix do
all(zero_or_one(:predicate), :suffix) {
predicate = capture(:predicate)
rule = capture(:suffix).value
rule = predicate.value(rule) if predicate
rule
}
end
rule :suffix do
all(:primary, zero_or_one(:repeat)) {
repeat = capture(:repeat)
rule = capture(:primary).value
rule = repeat.value(rule) if repeat
rule
}
end
rule :primary do
any(:grouping, :proxy, :terminal)
end
rule :grouping do
all(['(', zero_or_one(:space)], :expression, [')', zero_or_one(:space)]) {
capture(:expression).value
}
end
## Lexical syntax
rule :require do
all(:require_keyword, :quoted_string) {
capture(:quoted_string).value
}
end
rule :include do
mod all(:include_keyword, :module_name) do
include ModuleNameHelpers
def value
module_namespace.const_get(module_basename)
end
end
end
rule :root do
all(:root_keyword, :rule_name) {
capture(:rule_name).value
}
end
# Rule names may contain letters, numbers, underscores, and dashes. They
# MUST start with a letter.
rule :rule_name do
all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) {
first.to_s
}
end
rule :proxy do
any(:super, :alias)
end
rule :super do
ext(:super_keyword) {
Super.new
}
end
rule :alias do
all(notp(:end_keyword), :rule_name) {
Alias.new(capture(:rule_name).value)
}
end
rule :terminal do
any(:quoted_string, :case_insensitive_string, :regular_expression, :character_class, :dot) {
primitive = super()
if String === primitive
StringTerminal.new(primitive, flags)
else
Terminal.new(primitive)
end
}
end
rule :quoted_string do
mod all(/(["'])(?:\\?.)*?\1/, :space) do
def value
eval(first.to_s)
end
def flags
0
end
end
end
rule :case_insensitive_string do
mod all(/`(?:\\?.)*?`/, :space) do
def value
eval(first.to_s.gsub(/^`|`$/, '"'))
end
def flags
Regexp::IGNORECASE
end
end
end
rule :regular_expression do
all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
eval(first.to_s)
}
end
rule :character_class do
all(/\[(?:\\?.)*?\]/, :space) {
eval("/#{first.to_s.gsub('/', '\\/')}/")
}
end
rule :dot do
all('.', :space) {
DOT
}
end
rule :label do
all(/[a-zA-Z0-9_]+/, :space, ':', :space) {
first.to_str.to_sym
}
end
rule :extension do
any(:tag, :block)
end
rule :tag do
mod all(
['<', zero_or_one(:space)],
:module_name,
['>', zero_or_one(:space)]
) do
include ModuleNameHelpers
def value
module_namespace.const_get(module_basename)
end
end
end
rule :block do
all(
'{',
zero_or_more(any(:block, /[^{}]+/)),
['}', zero_or_one(:space)]
) {
proc = eval("Proc.new #{to_s}", TOPLEVEL_BINDING)
# Attempt to detect if this is a module block using some
# extremely simple heuristics.
if to_s =~ /\b(def|include) /
Module.new(&proc)
else
proc
end
}
end
rule :predicate do
any(:and, :not, :but)
end
rule :and do
all('&', :space) { |rule|
AndPredicate.new(rule)
}
end
rule :not do
all('!', :space) { |rule|
NotPredicate.new(rule)
}
end
rule :but do
all('~', :space) { |rule|
ButPredicate.new(rule)
}
end
rule :repeat do
any(:question, :plus, :star)
end
rule :question do
all('?', :space) { |rule|
Repeat.new(rule, 0, 1)
}
end
rule :plus do
all('+', :space) { |rule|
Repeat.new(rule, 1, Infinity)
}
end
rule :star do
all(/[0-9]*/, '*', /[0-9]*/, :space) { |rule|
min = captures[1] == '' ? 0 : captures[1].to_str.to_i
max = captures[3] == '' ? Infinity : captures[3].to_str.to_i
Repeat.new(rule, min, max)
}
end
rule :module_name do
all(one_or_more([ zero_or_one('::'), :constant ]), :space) {
first.to_s
}
end
rule :require_keyword, [ /\brequire\b/, :space ]
rule :include_keyword, [ /\binclude\b/, :space ]
rule :grammar_keyword, [ /\bgrammar\b/, :space ]
rule :root_keyword, [ /\broot\b/, :space ]
rule :rule_keyword, [ /\brule\b/, :space ]
rule :super_keyword, [ /\bsuper\b/, :space ]
rule :end_keyword, [ /\bend\b/, :space ]
rule :constant, /[A-Z][a-zA-Z0-9_]*/
rule :white, /[ \t\n\r]/
rule :comment, /#.*/
rule :space, zero_or_more(any(:white, :comment))
end