Class: SexpBuilder::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/sexp_builder/context.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(builder, parent = nil) ⇒ Context

Returns a new instance of Context.



5
6
7
8
9
10
11
12
13
14
15
# File 'lib/sexp_builder/context.rb', line 5

def initialize(builder, parent = nil)
  @builder = builder
  @parent = parent
  
  @contexts  = {}
  @rewriters = []
  
  @query_scope = Module.new do
    include parent.query_scope if parent
  end
end

Instance Attribute Details

#builderObject (readonly)

Returns the value of attribute builder.



3
4
5
# File 'lib/sexp_builder/context.rb', line 3

def builder
  @builder
end

#contextsObject (readonly)

Returns the value of attribute contexts.



3
4
5
# File 'lib/sexp_builder/context.rb', line 3

def contexts
  @contexts
end

#parentObject (readonly)

Returns the value of attribute parent.



3
4
5
# File 'lib/sexp_builder/context.rb', line 3

def parent
  @parent
end

#query_scopeObject (readonly)

Returns the value of attribute query_scope.



3
4
5
# File 'lib/sexp_builder/context.rb', line 3

def query_scope
  @query_scope
end

#rewritersObject (readonly)

Returns the value of attribute rewriters.



3
4
5
# File 'lib/sexp_builder/context.rb', line 3

def rewriters
  @rewriters
end

Instance Method Details

#context(name, &blk) ⇒ Object

Defines or finds a sub-context, and evalutes the block under it. If you want to process something under this context, you would have to call process_{name}.



29
30
31
32
33
# File 'lib/sexp_builder/context.rb', line 29

def context(name, &blk)
  context = define_context(name.to_sym)
  context.instance_eval(&blk) if blk
  context
end

#main_contextObject

Returns the top-most context.



18
19
20
21
22
23
24
# File 'lib/sexp_builder/context.rb', line 18

def main_context
  if @parent
    @parent.main_context
  else
    self
  end
end

#matcher(name, &blk) ⇒ Object

Defines a matcher. A matcher is a bit of Ruby code which can be used in your rules. The expression it should match is passed in, and it should return a true-ish value if it matches. The matcher will be evaluated under the instatiated processor, so you can use other instance methods and instance variables too.

matcher :five_arguments do |exp|
  self             # => the instance of Example
  exp.length == 6  # the first will always be :arglist
end

Under the hood, this will define:

  • The block as matcher_five_arguments on the builder.

  • five_arguments, which will invoke the matcher, on the query scope.



50
51
52
53
54
55
56
57
58
59
# File 'lib/sexp_builder/context.rb', line 50

def matcher(name, &blk)
  method_name = "matcher_#{name}"
  define(method_name, &blk)
  
  define_query_scope(name) do
    block do |exp|
      instance.send(method_name, exp)
    end
  end
end

#rewrite(*rules, &blk) ⇒ Object

Defines a rewriter. Rewriters take one or more rules and defines replacements when they match. The data-object from SexpPath is given as an argument. If you want some of the sub-expressions matched too, you’ll have to call process() yourself.

rewrite :plus_sequence do |data|
  # sum the numbers
  sum = data[:number].inject { |all, one| all + one }
  # return a new number
  s(:lit, sum)
end

Under the hood, this will define:

  • The block as rewrite_plus_sequence.

  • And @rewriters will now include :plus_sequence.

You can also give this several rules, or none if you want it to match every single Sexp.

Context shortcut

rewrite :foo, :in => :bar do
  ...
end

Is the same as:

context :bar do
  rewrite :foo do
    ...
  end
end


129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sexp_builder/context.rb', line 129

def rewrite(*rules, &blk)
  options = rules.last.is_a?(Hash) ? rules.pop : {}
  rules << :wild if rules.empty?
  
  return context(options[:in]).rewrite(*rules, &blk) if options[:in]
  
  rules.each do |rule|
    @rewriters << rule
    define("rewrite_#{rule}", &blk)
  end
end

#rule(name, &blk) ⇒ Object

Defines a rule. Rules are simply snippets of SexpPath which can refer to each other and itself. They can also take arguments too.

# Matches any number.
rule :number do |capture_as|
  # Doesn't make very much sense to take an argument here,
  # it's just an example
  s(:lit, _ % capture_as)
end

# Matches a sequence of plusses: 1 + 2 + 3
rule :plus_sequence do
  s(:call,               # a method call
     number(:number) |   # the receiver can be a number
     plus_sequence,      # or a sequence   
    :+,
    s(:arglist,
     number(:number) |   # the argument can be a number
     plus_sequence       # or a sequence
end

Under the hood, this will define:

  • The blocks as real_number and real_plus_sequence on the query_scope.

  • number and plus_sequence which wraps the methods above as QueryBuilder::Deferred.



88
89
90
91
92
93
94
# File 'lib/sexp_builder/context.rb', line 88

def rule(name, &blk)
  real_name = "real_#{name}"
  define_query_scope(real_name, &blk)
  define_query_scope(name) do |*args|
    QueryBuilder::Deferred.new(self, real_name, args, name)
  end
end