Class: Spectre::Grammar

Inherits:
Node
  • Object
show all
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

#node

Attributes inherited from Node

#actions, #backtrace, #left, #parent, #parser, #policy, #right, #symbols

Instance Method Summary collapse

Methods included from GrammarInspectMixin

#inspect

Methods included from ShortcutsMixin

register_shortcut

Methods included from DynVarMixin

#close, #rule, #start_with

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_pObject



234
# File 'lib/spectre/base/grammar.rb', line 234

def to_p; self; end