Class: Walrus::Grammar
- Inherits:
-
Object
- Object
- Walrus::Grammar
- 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
-
#memoizing ⇒ Object
Returns the value of attribute memoizing.
-
#rules ⇒ Object
readonly
Returns the value of attribute rules.
-
#skipping_overrides ⇒ Object
readonly
Returns the value of attribute skipping_overrides.
Class Method Summary collapse
-
.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.
Instance Method Summary collapse
-
#initialize(&block) ⇒ Grammar
constructor
A new instance of Grammar.
-
#node(new_class_name, parent_class = nil, *attributes) ⇒ Object
Dynamically creates a Node subclass inside the namespace of the current grammar.
-
#parse(string, options = {}) ⇒ Object
Starts with starting_symbol.
-
#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.
-
#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.
-
#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.
-
#starting_symbol(symbol) ⇒ Object
Sets the starting symbol.
- #wrap(result, rule_name) ⇒ Object
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
#memoizing ⇒ Object
Returns the value of attribute memoizing.
20 21 22 |
# File 'lib/walrus/grammar.rb', line 20 def memoizing @memoizing end |
#rules ⇒ Object (readonly)
Returns the value of attribute rules.
21 22 23 |
# File 'lib/walrus/grammar.rb', line 21 def rules @rules end |
#skipping_overrides ⇒ Object (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.
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.
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.
52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/walrus/grammar.rb', line 52 def parse(string, = {}) raise ArgumentError if string.nil? raise StandardError if @starting_symbol.nil? [:grammar] = self [:rule_name] = @starting_symbol [:skipping] = @skipping [:line_start] = 0 # "richer" information (more human-friendly) than that provided in "location" [:column_start] = 0 # "richer" information (more human-friendly) than that provided in "location" [:memoizer] = MemoizingCache.new if @memoizing @starting_symbol.to_parseable.memoizing_parse(string, ) 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)
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.
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.
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 |