Class: Walrus::Grammar

Inherits:
Object
  • Object
show all
Defined in:
lib/walrus.rb,
lib/walrus/grammar.rb,
lib/walrus/grammar/node.rb,
lib/walrus/compile_error.rb,
lib/walrus/grammar/parslet.rb,
lib/walrus/grammar/memoizing.rb,
lib/walrus/grammar/predicate.rb,
lib/walrus/grammar/parse_error.rb,
lib/walrus/grammar/array_result.rb,
lib/walrus/grammar/parser_state.rb,
lib/walrus/grammar/proc_parslet.rb,
lib/walrus/grammar/and_predicate.rb,
lib/walrus/grammar/not_predicate.rb,
lib/walrus/grammar/parslet_merge.rb,
lib/walrus/grammar/string_result.rb,
lib/walrus/grammar/parslet_choice.rb,
lib/walrus/grammar/regexp_parslet.rb,
lib/walrus/grammar/string_parslet.rb,
lib/walrus/grammar/symbol_parslet.rb,
lib/walrus/grammar/memoizing_cache.rb,
lib/walrus/grammar/parslet_omission.rb,
lib/walrus/grammar/parslet_sequence.rb,
lib/walrus/grammar/location_tracking.rb,
lib/walrus/grammar/parslet_combining.rb,
lib/walrus/grammar/string_enumerator.rb,
lib/walrus/grammar/match_data_wrapper.rb,
lib/walrus/grammar/parslet_repetition.rb,
lib/walrus/grammar/parslet_combination.rb,
lib/walrus/grammar/left_recursion_exception.rb,
lib/walrus/grammar/parslet_repetition_default.rb,
lib/walrus/grammar/skipped_substring_exception.rb,
lib/walrus/grammar/continuation_wrapper_exception.rb

Defined Under Namespace

Modules: LocationTracking, Memoizing, ParsletCombining Classes: AndPredicate, ArrayResult, CompileError, ContinuationWrapperException, LeftRecursionException, MatchDataWrapper, MemoizingCache, Node, NotPredicate, ParseError, ParserState, Parslet, ParsletChoice, ParsletCombination, ParsletMerge, ParsletOmission, ParsletRepetition, ParsletRepetitionDefault, ParsletSequence, Predicate, ProcParslet, RegexpParslet, SkippedSubstringException, StringEnumerator, StringParslet, StringResult, SymbolParslet

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&block) ⇒ Grammar

Returns a new instance of Grammar.



38
39
40
41
42
43
44
# File 'lib/walrus/grammar.rb', line 38

def initialize(&block)
  @rules              = Hash.new { |hash, key| raise StandardError.new('no rule for key "%s"' % key.to_s) }
  @productions        = Hash.new { |hash, key| raise StandardError.new('no production for key "%s"' % key.to_s) }
  @skipping_overrides = Hash.new { |hash, key| raise StandardError.new('no skipping override for key "%s"' % key.to_s) }
  @memoizing          = true
  self.instance_eval(&block) if block_given?
end

Instance Attribute Details

#memoizingObject

Returns the value of attribute memoizing.



20
21
22
# File 'lib/walrus/grammar.rb', line 20

def memoizing
  @memoizing
end

#rulesObject (readonly)

Returns the value of attribute rules.



21
22
23
# File 'lib/walrus/grammar.rb', line 21

def rules
  @rules
end

#skipping_overridesObject (readonly)

Returns the value of attribute skipping_overrides.



22
23
24
# File 'lib/walrus/grammar.rb', line 22

def skipping_overrides
  @skipping_overrides
end

Class Method Details

.subclass(subclass_name, &block) ⇒ Object

Creates a Grammar subclass named according to subclass_name and instantiates an instance of the new class, returning it after evaluating the optional block in the context of the newly created instance. The advantage of working inside a new subclass is that any constants defined in the new grammar will be in a separate namespace. The subclass_name parameter should be a String.

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
35
36
# File 'lib/walrus/grammar.rb', line 26

def self.subclass(subclass_name, &block)
  raise ArgumentError if subclass_name.nil?
  raise ArgumentError if Walrus::const_defined?(subclass_name)
  Walrus::const_set(subclass_name, Class.new(Grammar))
  subclass = Walrus::module_eval(subclass_name)
  begin
    subclass.new(&block)
  rescue ContinuationWrapperException => c # a Symbol in a production rule wants to know what namespace its being used in
    c.continuation.call(subclass)
  end
end

Instance Method Details

#node(new_class_name, parent_class = nil, *attributes) ⇒ Object

Dynamically creates a Node subclass inside the namespace of the current grammar. If parent_class is nil, Node is assumed. new_class_name must not be nil.

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
84
85
# File 'lib/walrus/grammar.rb', line 76

def node(new_class_name, parent_class = nil, *attributes)
  raise ArgumentError if new_class_name.nil?
  new_class_name = new_class_name.to_s.to_class_name # camel-case
  if parent_class.nil?
    Node.subclass(new_class_name, self.class, *attributes)
  else
    # convert parent_class to string, then camel case, then back to Symbol, then lookup the constant
    self.class.const_get(parent_class.to_s.to_class_name.to_s).subclass(new_class_name, self.class, *attributes)
  end
end

#parse(string, options = {}) ⇒ Object

Starts with starting_symbol.

Raises:

  • (ArgumentError)


52
53
54
55
56
57
58
59
60
61
62
# File 'lib/walrus/grammar.rb', line 52

def parse(string, options = {})
  raise ArgumentError if string.nil?
  raise StandardError if @starting_symbol.nil?
  options[:grammar]       = self
  options[:rule_name]     = @starting_symbol
  options[:skipping]      = @skipping
  options[:line_start]    = 0 # "richer" information (more human-friendly) than that provided in "location"
  options[:column_start]  = 0 # "richer" information (more human-friendly) than that provided in "location"
  options[:memoizer]      = MemoizingCache.new if @memoizing
  @starting_symbol.to_parseable.memoizing_parse(string, options)
end

#production(rule_name, class_symbol = nil) ⇒ Object

Specifies the Node subclass that will be used to encapsulate results for the rule identified by the symbol, rule_name. class_symbol, if present, will be converted to camel-case and explicitly names the class to be used. If class_symbol is not specified then a camel-cased version of the rule_name itself is used. rule_name must not be nil.

Example; specifying that the results of rule “string_literal” should be encapsulated in a “StringLiteral” instance:

production :string_literal

Example; specifying that the results of the rule “numeric_literal” should be encapsulated into a “RawToken” instance:

production :numeric_literal, :raw_token

Example; using the “build” method to dynamically define an “AssigmentExpression” class with superclass “Expression” and assign the created class as the AST production class for the rule “assignment_expression”:

production :assignment_expression.build(:expression, :target, :value)

Raises:

  • (ArgumentError)


103
104
105
106
107
108
109
# File 'lib/walrus/grammar.rb', line 103

def production(rule_name, class_symbol = nil)
  raise ArgumentError if rule_name.nil?
  raise ArgumentError if @productions.has_key?(rule_name)
  raise ArgumentError unless @rules.has_key?(rule_name)
  class_symbol = rule_name if class_symbol.nil?
  @productions[rule_name] = class_symbol
end

#rule(symbol, parseable) ⇒ Object

Defines a rule and stores it Expects an object that responds to the parse message, such as a Parslet or ParsletCombination. As this is intended to work with Parsing Expression Grammars, each rule may only be defined once. Defining a rule more than once will raise an ArgumentError.

Raises:

  • (ArgumentError)


67
68
69
70
71
72
# File 'lib/walrus/grammar.rb', line 67

def rule(symbol, parseable)
  raise ArgumentError if symbol.nil?
  raise ArgumentError if parseable.nil?
  raise ArgumentError if @rules.has_key?(symbol)
  @rules[symbol] = parseable
end

#skipping(rule_or_parslet, parslet = NoParameterMarker.instance) ⇒ Object

Sets the default parslet that is used for skipping inter-token whitespace, and can be used to override the default on a rule-by-rule basis. This allows for simpler grammars which do not need to explicitly put optional whitespace parslets (or any other kind of parslet) between elements.

There are two modes of operation for this method. In the first mode (when only one parameter is passed) the rule_or_parslet parameter is used to define the default parslet for inter-token skipping. rule_or_parslet must refer to a rule which itself is a Parslet or ParsletCombination and which is responsible for skipping. Note that the ability to pass an arbitrary parslet means that the notion of what consitutes the “whitespace” that should be skipped is completely flexible. Raises if a default skipping parslet has already been set.

In the second mode of operation (when two parameters are passed) the rule_or_parslet parameter is interpreted to be the rule to which an override should be applied, where the parslet parameter specifies the parslet to be used in this case. If nil is explicitly passed then this overrides the default parslet; no parslet will be used for the purposes of inter-token skipping. Raises if an override has already been set for the named rule.

The inter-token parslet is passed inside the “options” hash when invoking the “parse” methods. Any parser which fails will retry after giving this inter-token parslet a chance to consume and discard intervening whitespace. The initial, conservative implementation only performs this fallback skipping for ParsletSequence and ParsletRepetition combinations.

Raises if rule_or_parslet is nil.

Raises:

  • (ArgumentError)


157
158
159
160
161
162
163
164
165
166
167
# File 'lib/walrus/grammar.rb', line 157

def skipping(rule_or_parslet, parslet = NoParameterMarker.instance)
  raise ArgumentError if rule_or_parslet.nil?
  if parslet == NoParameterMarker.instance  # first mode of operation: set default parslet
    raise if @skipping                      # should not set a default skipping parslet twice
    @skipping = rule_or_parslet
  else                                      # second mode of operation: override default case
    raise ArgumentError if @skipping_overrides.has_key?(rule_or_parslet)
    raise ArgumentError unless @rules.has_key?(rule_or_parslet)
    @skipping_overrides[rule_or_parslet] = parslet
  end
end

#starting_symbol(symbol) ⇒ Object

Sets the starting symbol. symbol must refer to a rule.



142
143
144
# File 'lib/walrus/grammar.rb', line 142

def starting_symbol(symbol)
  @starting_symbol = symbol
end

#wrap(result, rule_name) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/walrus/grammar.rb', line 111

def wrap(result, rule_name)
  if @productions.has_key?(rule_name.to_sym)    # figure out arity of "initialize" method and wrap results in AST node
    node_class  = self.class.const_get(@productions[rule_name.to_sym].to_s.to_class_name)
    param_count = node_class.instance_method(:initialize).arity
    raise if param_count < 1
    
    # dynamically build up a message send
    if param_count == 1
      params = 'result'
    else
      params = 'result[0]'
      for i in 1..(param_count - 1)
        params << ", result[#{i.to_s}]"
      end
    end
    
    node                = node_class.class_eval('new(%s)' % params)
    node.start          = (result.outer_start or result.start)              # propagate the start information
    node.end            = (result.outer_end or result.end)                  # and the end information
    node.source_text    = (result.outer_source_text or result.source_text)  # and the original source text
    node
  else
    result.start        = result.outer_start if result.outer_start
    result.end          = result.outer_end if result.outer_end
    result.source_text  = result.source_text if result.outer_source_text
    result
  end
end