Class: Walrat::Grammar
- Inherits:
-
Object
- Object
- Walrat::Grammar
- Defined in:
- lib/walrat/grammar.rb
Instance Attribute Summary collapse
-
#memoizing ⇒ Object
Returns the value of attribute memoizing.
Class Method Summary collapse
-
.default_skipping_rule ⇒ Object
Returns the default skipping rule.
-
.node(new_class_name, parent_class = Node) ⇒ Object
Dynamically creates a Node subclass inside the namespace of the current grammar.
-
.production(rule_name, *results) ⇒ Object
Specifies that a Node subclass will be used to encapsulate results for the rule identified by the symbol, rule_name.
-
.productions ⇒ Object
Lazy reader for the productions hash.
-
.rule(symbol, parseable) ⇒ Object
Defines a rule and stores it.
-
.rules ⇒ Object
Lazy reader for the rules hash.
-
.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.
-
.skipping_overrides ⇒ Object
Lazy reader for the skipping overrides hash.
-
.start_rule ⇒ Object
Returns the starting symbol.
-
.starting_symbol(symbol) ⇒ Object
Sets the starting symbol.
-
.wrap(result, rule_name) ⇒ Object
This method is called by the ParsletSequence and SymbolParslet classes to possibly wrap a parse result in a production node.
Instance Method Summary collapse
-
#initialize ⇒ Grammar
constructor
A new instance of Grammar.
-
#parse(string, options = {}) ⇒ Object
Starts with starting_symbol.
Constructor Details
#initialize ⇒ Grammar
Returns a new instance of Grammar.
235 236 237 |
# File 'lib/walrat/grammar.rb', line 235 def initialize @memoizing = true end |
Instance Attribute Details
#memoizing ⇒ Object
Returns the value of attribute memoizing.
233 234 235 |
# File 'lib/walrat/grammar.rb', line 233 def memoizing @memoizing end |
Class Method Details
.default_skipping_rule ⇒ Object
Returns the default skipping rule.
Note that we can’t use “skipping” as the accessor method here because it is already used as part of the grammar-definition DSL.
128 129 130 |
# File 'lib/walrat/grammar.rb', line 128 def default_skipping_rule @skipping end |
.node(new_class_name, parent_class = Node) ⇒ Object
Dynamically creates a Node subclass inside the namespace of the current grammar.
This is used to create classes in a class hierarchy where no custom behavior is required and therefore no actual file with an impementation need be provided; an example from the Walrus grammar:
module Walrus
class Grammar < Walrat::Grammar
class Literal < Walrat::Node
class StringLiteral < Literal
class DoubleQuotedStringLiteral < StringLiteral
In this example hiearchy the “Literal” class has custom behavior which is shared by all subclasses, and the custom behavior is implemented in the file “walrus/grammar/literal”. The subclasses, however, have no custom behavior and no associated file. They are dynamically synthesized when the Walrus::Grammar class is first evaluated.
164 165 166 167 168 169 170 171 |
# File 'lib/walrat/grammar.rb', line 164 def node new_class_name, parent_class = Node raise ArgumentError, 'nil new_class_name' if new_class_name.nil? new_class_name = new_class_name.to_s.to_class_name # camel-case unless parent_class.kind_of? Class parent_class = const_get parent_class.to_s.to_class_name end const_set new_class_name, Class.new(parent_class) end |
.production(rule_name, *results) ⇒ Object
Specifies that a Node subclass will be used to encapsulate results for the rule identified by the symbol, rule_name. The class name is derived by converting the rule_name to camel-case.
If no additional params are supplied then the class is assumed to accept a single parameter named “lexeme” in its initialize method.
If additional params are supplied then the class is expected to accept the named params in its initialize method.
As a convenience, the params will be sent to the specified class using the “production” method, which sets up an appropriate initializer.
For example:
# accepts a single parameter, "lexeme"
production :symbol_literal
# accepts a single parameter, "content"
production :multiline_comment, :content
# accepts three parameters, "identifier", "params" and "content"
production :block_directive, :identifier, :params, :content
197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/walrat/grammar.rb', line 197 def production rule_name, *results raise ArgumentError, 'nil rule_name' if rule_name.nil? raise ArgumentError, "production already defined for rule '#{rule_name}'" if productions.has_key?(rule_name) raise ArgumentError, "non-existent rule '#{rule_name}'" unless rules.has_key?(rule_name) results = results.empty? ? [:lexeme] : results const_get(rule_name.to_s.to_class_name).production *results productions[rule_name] = results end |
.productions ⇒ Object
Lazy reader for the productions hash.
Initializes the hash the first time it is accessed.
41 42 43 44 45 |
# File 'lib/walrat/grammar.rb', line 41 def productions @productions or @productions = Hash.new do |hash, key| raise "no production for key '#{key}'" end 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.
138 139 140 141 142 143 144 |
# File 'lib/walrat/grammar.rb', line 138 def rule symbol, parseable raise ArgumentError, 'nil symbol' if symbol.nil? raise ArgumentError, 'nil parseable' if parseable.nil? raise ArgumentError, "rule '#{symbol}' already defined" if rules.has_key? symbol rules[symbol] = parseable end |
.rules ⇒ Object
Lazy reader for the rules hash.
Initializes the hash the first time it is accessed.
32 33 34 35 36 |
# File 'lib/walrat/grammar.rb', line 32 def rules @rules or @rules = Hash.new do |hash, key| raise "no rule for key '#{key}'" end 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.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/walrat/grammar.rb', line 106 def skipping rule_or_parslet, parslet = NoParameterMarker.instance raise ArgumentError, 'nil rule_or_parslet' if rule_or_parslet.nil? if parslet == NoParameterMarker.instance # first mode of operation: set default parslet raise 'default skipping parslet already set' if @skipping @skipping = rule_or_parslet else # second mode of operation: override default case raise ArgumentError, "skipping override already set for rule '#{rule_or_parslet}'" if skipping_overrides.has_key? rule_or_parslet raise ArgumentError, "non-existent rule '#{rule_or_parslet}'" unless rules.has_key? rule_or_parslet skipping_overrides[rule_or_parslet] = parslet end end |
.skipping_overrides ⇒ Object
Lazy reader for the skipping overrides hash.
Initializes the hash the first time it is accessed.
50 51 52 53 54 |
# File 'lib/walrat/grammar.rb', line 50 def skipping_overrides @skipping_overrides or @skipping_overrides = Hash.new do |hash, key| raise "no skipping override for key '#{key}'" end end |
.start_rule ⇒ Object
Returns the starting symbol.
Note that the “starting_symbol” method can’t be used as an accessor because it is already used as part of the grammar-definition DSL.
68 69 70 |
# File 'lib/walrat/grammar.rb', line 68 def start_rule @starting_symbol end |
.starting_symbol(symbol) ⇒ Object
Sets the starting symbol.
59 60 61 62 |
# File 'lib/walrat/grammar.rb', line 59 def starting_symbol symbol raise ArgumentError, 'starting symbol already set' if @starting_symbol @starting_symbol = symbol end |
.wrap(result, rule_name) ⇒ Object
This method is called by the ParsletSequence and SymbolParslet classes to possibly wrap a parse result in a production node.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/walrat/grammar.rb', line 211 def wrap result, rule_name if productions.has_key? rule_name.to_sym node_class = const_get rule_name.to_s.to_class_name param_count = productions[rule_name.to_sym].length if param_count == 1 node = node_class.new result else node = node_class.new *result end 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 |
Instance Method Details
#parse(string, options = {}) ⇒ Object
Starts with starting_symbol.
245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/walrat/grammar.rb', line 245 def parse string, = {} raise ArgumentError, 'nil string' if string.nil? raise 'starting symbol not defined' if self.class.start_rule.nil? [:grammar] = self.class [:rule_name] = self.class.start_rule [:skipping] = self.class.default_skipping_rule [: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 self.class.start_rule.to_parseable.memoizing_parse string, end |