gxg-web-client/gxg/core/peg.rb
2021-11-16 08:58:51 -08:00

347 lines
7 KiB
Ruby

module Rubinius
module PEG
class InvalidPatternError < Exception; end
# Class Pattern is the base class for all nodes used to represent the AST
# for a PEG. Following LPEG, the base is a pattern and not a grammar.
class Pattern
def graph
if defined? Rubinius
Rubinius::AST::AsciiGrapher.new(self, Pattern).print
else
inspect
end
nil
end
def visit(visitor)
end
# Pattern operators
def /(other)
Choice.new self, other
end
# TODO: remove after core transform for :/ is removed
alias_method :divide, :/
def +(other)
Concatenation.new self, other
end
def -(other)
Difference.new self, other
end
def *(other)
Product.new self, other
end
def +@
If.new self
end
def -@
Unless.new self
end
def =~(string)
end
def match(string)
end
end
class UnaryOp < Pattern
attr_accessor :pattern
def initialize(pattern)
@pattern = PEG.pattern pattern
end
private :initialize
end
class BinaryOp < Pattern
attr_accessor :first
attr_accessor :second
def initialize(first, second)
@first = PEG.pattern first
@second = PEG.pattern second
end
private :initialize
end
class Grammar < Pattern
attr_accessor :start
def initialize(variable=nil)
if variable
@start = variable.name
set_variable variable.name, variable.pattern
end
end
private :initialize
def set_variable(name, pattern)
variable = get_variable name
variable.pattern = PEG.pattern pattern
variable
end
def get_variable(name)
if singleton_class.method_defined? name
variable = send name
else
vname = name.to_sym
variable = Variable.new vname
singleton_class.thunk_method vname, variable
end
variable
end
def method_missing(sym, *args)
name = sym.to_s
if name[-1] == ?= and args.size == 1
set_variable name[0..-2], args.first
else
get_variable name
end
end
def visit(visitor)
visitor.grammar self
end
def =~(string)
end
def match(string)
end
end
class Always < Pattern
def visit(visitor)
visitor.always self
end
end
class AnyRange < Pattern
attr_accessor :range
def initialize(range)
@range = range
end
private :initialize
def visit(visitor)
visitor.any_range self
end
end
class Any < Pattern
attr_accessor :count
def initialize(count)
@count = count
end
private :initialize
def visit(visitor)
visitor.any self
end
end
class CharacterRange < Pattern
attr_accessor :range
def initialize(range)
@range = range
end
private :initialize
def visit(visitor)
visitor.character_range self
end
end
class Character < Pattern
attr_accessor :string
def initialize(string)
@string = string
end
private :initialize
def visit(visitor)
visitor.character self
end
end
class Choice < BinaryOp
def visit(visitor)
visitor.choice self
end
end
class Concatenation < BinaryOp
def visit(visitor)
visitor.concatenation self
end
end
class Difference < BinaryOp
def visit(visitor)
visitor.difference self
end
end
# Implements &patt in a PEG. The pattern succeeds if
# patt matches. Does not consume any input.
class If < UnaryOp
def visit(visitor)
visitor.if self
end
end
class Never < Pattern
def visit(visitor)
visitor.never self
end
end
class Product < Pattern
attr_accessor :pattern
attr_accessor :count
def initialize(pattern, count)
@pattern = PEG.pattern pattern
@count = Type.coerce_to(count, Integer, :to_int).abs
end
private :initialize
def visit(visitor)
visitor.product self
end
end
class Set < Pattern
attr_accessor :set
def initialize(set)
@set = set.uniq
end
private :initialize
def visit(visitor)
visitor.set self
end
end
# Implements !patt in a PEG. The pattern succeeds if
# patt does not match. Does not consume any input.
class Unless < UnaryOp
def visit(visitor)
visitor.unless self
end
end
class Variable < Pattern
attr_accessor :name
attr_accessor :pattern
def initialize(name, pattern=nil)
@name = name.to_sym
@pattern = PEG.pattern pattern
end
private :initialize
def visit(visitor)
visitor.variable self
end
end
# Accepts a Ruby object representation of simple PEG "atoms" and returns a
# PEG AST node.
def pattern(spec)
case spec
when Array
Set.new spec
when Integer
node = Any.new spec.abs
node = If.new node if spec < 0
node
when String
Character.new spec
when Pattern
spec
when true
Always.new
when false
Never.new
when nil
nil
else
raise InvalidPatternError, "unknown specification type for Pattern: #{spec.inspect}"
end
end
module_function :pattern
def grammar(spec=nil, pattern=nil)
case spec
when Symbol
Grammar.new Variable.new(spec, pattern)
when Variable
Grammar.new spec
when Pattern
Grammar.new Variable.new(:S, spec)
when nil
Grammar.new
else
Grammar.new Variable.new(:S, pattern)
end
end
module_function :grammar
def variable(name, pattern=nil)
PEG::Variable.new name, pattern
end
module_function :variable
def self.compile(pattern)
end
def self.peg
g = grammar = grammar(:grammar)
g.grammar = (g.nonterminal + "=" + g.sp + g.pattern) * 1
g.pattern = g.alternative + (pattern("/") + g.sp + g.alternative) * 0
g.alternative = ((+g.predicate + g.predicate + g.sp + g.suffix) /
(g.sp + g.suffix)) * 1
g.predicate = ["!&"]
g.suffix = g.primary + (pattern(["*+?"]) + g.sp) * 0
g.primary = (pattern("(") + g.sp + g.pattern + ")" + g.sp) / (pattern(1) + g.sp) /
g.literal / g.char_class / (g.nonterminal + -pattern("="))
g.literal = pattern("'") + (-pattern("'") + 1) * 0 + "'" + g.sp
g.char_class = (pattern("[") + (-pattern("]") +
((pattern(1) + "-" + 1) / 1)) * 0) + "]" + g.sp
g.nonterminal = (pattern("_") / ["a".."z"] / ["A".."Z"]) * 1 + g.sp
g.sp = pattern([" \t\n"]) * 0
grammar
end
end
end