Class: Spectre::Grammar
- Includes:
- DynVarMixin, GrammarInspectMixin, Parser, ShortcutsMixin
- Defined in:
- lib/spectre/base/grammar.rb
Overview
Chains several Parsers together to form a reusable unit and allows for recursion in Parser definition.
To define a Grammar, you may use the generator methods to create Parsers and chain them together. You will then have to store the created top-level Parser in the Grammar by calling start_with(parser).
To do so, you first have to call Grammar.new
, passing it a block that describes the Grammar’s behaviour. The returned Grammar object can be supplied with arguments on runtime in order to customize it’s behaviour, e.g:
mayor = Grammar.new do |city, klass|
start_with 'Mayor ' >> ( ~blank ).+ >> ", class #{klass}" >>
' from ' >> city.to_p
end
The thus created Grammar has to be bound to some arguments before it can actually be used to parse anything:
mayor.bind( AnycharParser.new.+, 'A' )
It will now parse any Mayor from any city of class ‘A’. You can of course rebind the Grammar anytime (except during parsing): + mayor.bind( ‘Boston’, AnycharParser.new )+ Now it will parse any Mayor of any class from ‘Boston’.
The only exception to the binding rule is a dynamic Grammar that takes no arguments. Such a Grammar will be bound right at instantiation time. Rebinding will have no effect whatsoever. NOTE: Due to an existing Ruby bug, you have to define such a Grammar with an empty argument block:
chunky = Grammar.new do ||
rule :bacon => ...
end
Otherwise it will not be automatically bound. This will change, as soon as bug #574 is fixed (redmine.ruby-lang.org/issues/show/574).
You may at any time store a parser inside a rule, like this:
towns_folk = Grammar.new do ||
start_with :person % ( 'from '.to_p >> :town >> ', ' )
rule :person => :name >> blank.+ >> :name,
:town => :name
rule :name => ( ~blank ).+
end
As you can also see here, the rules are evaluated lazyly, thus enabling you to use parsers recursively and before they have actually been defined.
NOTE: As tempting as it may be, do NOT use instance variables to store Parsers, because if you use the Parser more than once, the backtrace of the first invocation of that Parser will be lost as soon as it is invoked a second time. Also the use of Closures will be broken. Storing the parsers as is described above will circumvent this problem by dupping the Parser each time it is invoked.
If you’d like to provide Grammar-like functionality in your own class(es), you can receive some from the mixins DynVarMixin, ShortcutsMixin and GrammarInspectMixin.
Instance Attribute Summary
Attributes included from Parser
Attributes inherited from Node
#actions, #backtrace, #left, #parent, #parser, #policy, #right, #symbols
Instance Method Summary collapse
-
#bind(*args) ⇒ Object
Binds a Grammar to a set of values supplied in the
args
. -
#initialize(&block) ⇒ Grammar
constructor
Defines a new Grammar.
-
#scan(iter) ⇒ Object
Parses the InputIterator
iter
with the Parsers defined in the Grammar. - #to_p ⇒ Object
Methods included from GrammarInspectMixin
Methods included from ShortcutsMixin
Methods included from DynVarMixin
Methods included from Parser
#create_match, from_POD, #inspect, #pre_skip?
Methods inherited from Node
#%, #&, #*, #**, #+, #-, #-@, #>>, #[], #^, #chain, #closure, #closure=, #closure?, #find, #initialize_copy, #inspect, #leaf?, #parse, #replace_with, #root?, #shallow_copy, #|, #~@
Constructor Details
#initialize(&block) ⇒ Grammar
Defines a new Grammar. The passed block
will be executed once the returned Grammar’s bind
method is called. All of bind
‘s parameters will be passed to the block.
If the block
takes no arguments, it will be bound at instantiation time. NOTE: Ruby bug 574 (redmine.ruby-lang.org/issues/show/574)
See Grammar for an example.
222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/spectre/base/grammar.rb', line 222 def initialize &block @dynamic = block @bound = false # nice little trick: we are node and parser in one # but for upwards compatibility, we will act as if it weren't so # from here on super(self) self.bind if block.arity == 0 end |
Instance Method Details
#bind(*args) ⇒ Object
Binds a Grammar to a set of values supplied in the args
. Executes the block given to #initialize
.
240 241 242 243 244 |
# File 'lib/spectre/base/grammar.rb', line 240 def bind *args self.instance_exec *args, &@dynamic @bound = true self end |
#scan(iter) ⇒ Object
Parses the InputIterator iter
with the Parsers defined in the Grammar. The Grammar must have been bound before doing so.
250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/spectre/base/grammar.rb', line 250 def scan iter raise "a dynamic Grammar must be bound to a value" unless @bound raise "you need to set a start rule" unless @start_rule n = @start_rule.dup # sort into tree n.parent = @node @node.left = n # start parsing create_match iter, n.parse(iter) end |
#to_p ⇒ Object
234 |
# File 'lib/spectre/base/grammar.rb', line 234 def to_p; self; end |