aniero-tire_swing 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -4,25 +4,31 @@ README.txt
4
4
  Rakefile
5
5
  examples/simple_assignment.rb
6
6
  lib/tire_swing.rb
7
+ lib/tire_swing/error.rb
7
8
  lib/tire_swing/metaid.rb
8
9
  lib/tire_swing/node.rb
9
10
  lib/tire_swing/node_creator.rb
10
11
  lib/tire_swing/node_definition.rb
12
+ lib/tire_swing/parser_extension.rb
11
13
  lib/tire_swing/visitor.rb
12
14
  lib/tire_swing/visitor_definition.rb
15
+ spec/error_spec.rb
13
16
  spec/fixtures/assignments.txt
14
17
  spec/fixtures/ey00-s00348.xen
15
18
  spec/grammars/assignments.rb
16
19
  spec/grammars/assignments.treetop
17
20
  spec/grammars/dot_xen.rb
18
21
  spec/grammars/dot_xen.treetop
22
+ spec/grammars/lists.rb
19
23
  spec/grammars/magic.rb
20
24
  spec/integration/assignments_spec.rb
21
25
  spec/integration/dot_xen_spec.rb
26
+ spec/integration/lists_spec.rb
22
27
  spec/integration/magic_spec.rb
23
28
  spec/node_creator_spec.rb
24
29
  spec/node_definition_spec.rb
25
30
  spec/node_spec.rb
31
+ spec/parser_extension_spec.rb
26
32
  spec/spec_helper.rb
27
33
  spec/tire_swing_spec.rb
28
34
  spec/visitor_definition_spec.rb
data/README.txt CHANGED
@@ -17,10 +17,10 @@ Given a treetop grammar:
17
17
 
18
18
  grammar SimpleAssignment
19
19
  rule assignment
20
- lhs:variable space* "=" space* rhs:variable <create_node(:assignment)>
20
+ lhs:variable space* "=" space* rhs:variable <node(:assignment)>
21
21
  end
22
22
  rule variable
23
- [a-z]+ <create_node(:variable)>
23
+ [a-z]+ <node(:variable)>
24
24
  end
25
25
  rule space
26
26
  [ ]+
@@ -36,10 +36,14 @@ You can use TireSwing to define nodes for the grammar:
36
36
  node :variable, :value => :text_value
37
37
  end
38
38
 
39
- When you parse the grammar and call .build, it will return an AST using the nodes you defined, auto-building everything
39
+ And use TireSwing to extend the Treetop-provided parser with a helper method or two:
40
+
41
+ TireSwing.parses_grammar(SimpleAssignment)
42
+
43
+ When you parse the grammar using the helper, it will return an AST using the nodes you defined, auto-building everything
40
44
  for you:
41
45
 
42
- ast = Parser.parse("foo = bar").build
46
+ ast = SimpleAssignment.ast("foo = bar")
43
47
 
44
48
  ast.class #=> SimpleAssignment::Assignment
45
49
  ast.lhs.class #=> SimpleAssignment::Variable
@@ -4,10 +4,10 @@ Treetop.load_from_string <<-GRAMMAR
4
4
  module SimpleAssignment
5
5
  grammar Grammar
6
6
  rule assignment
7
- lhs:variable space* "=" space* rhs:variable <AST.create_node(:assignment)>
7
+ lhs:variable space* "=" space* rhs:variable <node(:assignment)>
8
8
  end
9
9
  rule variable
10
- [a-z]+ <AST.create_node(:variable)>
10
+ [a-z]+ <node(:variable)>
11
11
  end
12
12
  rule space
13
13
  [ ]+
@@ -37,17 +37,14 @@ module SimpleAssignment
37
37
  end
38
38
  end
39
39
 
40
- class Parser < ::Treetop::Runtime::CompiledParser
41
- include Grammar
42
- def self.parse(io)
43
- new.parse(io).build
44
- end
45
- end
40
+ # Set up the parser helper, pointing to the grammar and the AST
41
+ # This defines GrammarParser.ast on the Treetop-provided GrammarParser.
42
+ TireSwing.parses_grammar(Grammar, AST)
46
43
 
47
44
  end
48
45
 
49
46
  require "pp"
50
- ast = SimpleAssignment::Parser.parse("foo=bar")
47
+ ast = SimpleAssignment::GrammarParser.ast("foo=bar")
51
48
  puts "----- AST -----"
52
49
  pp ast
53
50
  puts "\n----- visitor output -----"
data/lib/tire_swing.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module TireSwing
2
2
 
3
3
  # :stopdoc:
4
- VERSION = '0.0.2'
4
+ VERSION = "0.0.3"
5
5
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
6
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
7
  # :startdoc:
@@ -0,0 +1,10 @@
1
+ module TireSwing
2
+ class ParseError < StandardError
3
+ attr_reader :parser
4
+ def initialize(message, parser)
5
+ @parser = parser
6
+ super(message)
7
+ end
8
+ # TODO add in pretty error formatting, given the parser and the message
9
+ end
10
+ end
@@ -51,21 +51,40 @@ module TireSwing
51
51
  # Auto-builds this node using the provided parsed node and the defined attributes and mapped attributes.
52
52
  def build_from_parsed_node(parsed_node)
53
53
  attributes.each do |attrib|
54
- if value = mapping(attrib)
55
- if value.kind_of?(Proc)
56
- value = value.call(parsed_node)
54
+ if handler = mapping(attrib)
55
+ if handler.kind_of?(Proc)
56
+ value = handler.call(parsed_node)
57
57
  else
58
- value = parsed_node.send(mapping(attrib))
58
+ if parsed_node.respond_to?(handler)
59
+ value = parsed_node.send(handler)
60
+ else
61
+ value = parsed_node.text_value.send(handler)
62
+ end
59
63
  end
60
64
  else
61
65
  value = parsed_node.send(attrib)
62
66
  end
63
- value = value.map { |val| val.respond_to?(:build) ? val.build : val } if value.kind_of?(Array)
64
- value = value.build if value.respond_to?(:build)
67
+
68
+ value = if value.kind_of?(Array)
69
+ value.map { |val| extract_value(val) }
70
+ else
71
+ extract_value(value)
72
+ end
73
+
65
74
  send("#{attrib}=", value)
66
75
  end
67
76
  end
68
77
 
78
+ def extract_value(value)
79
+ if value.respond_to?(:build)
80
+ value.build
81
+ elsif value.kind_of?(Treetop::Runtime::SyntaxNode)
82
+ value.text_value
83
+ else
84
+ value
85
+ end
86
+ end
87
+
69
88
  def attributes
70
89
  self.class.attributes
71
90
  end
@@ -1,11 +1,12 @@
1
1
  module TireSwing
2
2
  class NodeCreator
3
3
 
4
- # Creates a node creator for the given node class. This is meant to act as a stand-in for the treetop
5
- # syntax node class.
4
+ # Creates a node creator for the given node type and class. This is meant to act as a stand-in for the treetop
5
+ # syntax node class. The node_name, although somewhat redundant, is used for filtering later on
6
+ # (see: array_of)
6
7
  #
7
- def initialize(node_class)
8
- @node_class = node_class
8
+ def initialize(node_name, node_class)
9
+ @node_name, @node_class = node_name, node_class
9
10
  end
10
11
 
11
12
  # Returns a new treetop syntax node to act as a standin for a normal syntax node. Before returning the node,
@@ -16,11 +17,18 @@ module TireSwing
16
17
  def new(*args)
17
18
  parsed_node = Treetop::Runtime::SyntaxNode.new(*args)
18
19
 
19
- node_class = @node_class # local scope for the block below
20
+ node_name, node_class = @node_name, @node_class # local scope for the block below
21
+
22
+ # so the node knows how to build itself:
20
23
  parsed_node.meta_def :build do
21
24
  node_class.new(self)
22
25
  end
23
26
 
27
+ # so the node can be filtered based on what kind of AST node it will build
28
+ parsed_node.meta_def :node_to_build do
29
+ node_name
30
+ end
31
+
24
32
  parsed_node
25
33
  end
26
34
 
@@ -17,11 +17,29 @@ module TireSwing::NodeDefinition
17
17
  # You can define the grammar as
18
18
  #
19
19
  # rule variable
20
- # [a-z]+ <create_node(:variable)>
20
+ # [a-z]+ <node(:variable)>
21
21
  # end
22
22
  #
23
+ # Also note that you can specify alternate namespaces:
24
+ #
25
+ # module AST
26
+ # node :variable
27
+ # end
28
+ #
29
+ # <AST.create_node(:variable)>
30
+ #
31
+ # When you're using the parser extension:
32
+ #
33
+ # TireSwing.parses_grammar(Grammar, AST)
34
+ #
35
+ # you can use
36
+ #
37
+ # <node(...)>
38
+ #
39
+ # Which is an instance method wrapper for the create_node class method.
40
+ #
23
41
  def create_node(name)
24
- TireSwing::NodeCreator.new(const_get(name.to_s.camelize))
42
+ TireSwing::NodeCreator.new(name, const_get(name.to_s.camelize))
25
43
  end
26
44
 
27
45
  # Define a node.
@@ -53,18 +71,31 @@ module TireSwing::NodeDefinition
53
71
  # The second method takes a treetop syntax node and auto-builds the node using the syntax node as a basis.
54
72
  # The auto-build functionality uses the attribute names and mapped attributes in the following way:
55
73
  #
56
- # * Simple attributes: calls the method by that name on the syntax node
57
- # * Mapped attributes: if the attribute value is a symbol or a string, it calls that method on the syntax node.
58
- # If the attribute value is a lambda, it yields the syntax node to the lambda.
74
+ # * Simple attributes: calls the method by that name on the syntax node, e.g.
75
+ #
76
+ # node :assignment, :lhs, :rhs
77
+ #
78
+ # * Mapped attributes:
79
+ # * symbol/string - call that method on *either* the node, if it responds, or on its text value (e.g. #to_i)
59
80
  #
60
- # If the value (or array of values) returned by one of these methods is another syntax node, the auto-build code
61
- # will call the build method on each syntax node or array item (assuming each syntax node has a build method,
62
- # usually auto-defined using create_node). Any value not responding to the build method is left alone.
81
+ # node :number, :value => :to_i # calls to_i on the number node's text value
82
+ #
83
+ # * lambda - yields the parsed node to the lambda
84
+ #
85
+ # Whatever the value (or array of values) is returned by the mapped call will then be built. Any item returned
86
+ # that responds to the build method will have it called. If there's any bare syntax nodes left over, they are
87
+ # converted into their text value, and anything else will be returned as-is (numbers, strings, etc.)
88
+ #
89
+ # Note that you can use the array_of and extract helpers defined below to define lambdas for doing more advanced
90
+ # filtering on the node and it's children.
63
91
  #
64
92
  # Simple example:
65
93
  #
66
94
  # rule assignment
67
- # variable:lhs "=" variable:rhs <create_node(:assignment)>
95
+ # variable:lhs "=" variable:rhs <node(:assignment)>
96
+ # end
97
+ # rule variable
98
+ # [a-z]+
68
99
  # end
69
100
  #
70
101
  # node :assignment, :lhs, :rhs
@@ -84,22 +115,85 @@ module TireSwing::NodeDefinition
84
115
  klass.class_eval &blk if block_given?
85
116
  end
86
117
 
118
+ # Returns a lambda to select only child nodes of the given kind. This is best used for rules that can return
119
+ # arrays of different kind of nodes, some of which you want to ignore, e.g.:
120
+ #
121
+ # rule assignments
122
+ # assignment* <node(:assignments)>
123
+ # end
124
+ #
125
+ # node :assignments, :assignments => array_of(:assignment)
126
+ #
127
+ # When parsed, this will give you a recursive set of nodes:
128
+ #
129
+ # [assignment, [assignment, [assignment]]]
130
+ #
131
+ # If you specify that the array is recursive, it will retrieve all nested child nodes, no matter how deep, which
132
+ # provide the kind of node you want.
133
+ #
134
+ # If you provide a block, the filtered result will be yielded to the block and returned as the final result.
135
+ #
136
+ def array_of(kind, recursive = false, &blk)
137
+ lambda do |node|
138
+ result = NodeFilters.filter(node, kind, recursive)
139
+ blk ? result.map(&blk) : result
140
+ end
141
+ end
142
+
143
+ # Returns a lambda which takes a node and calls node_name on each in turn if possible, returning the result.
144
+ # This is useful for nested rules, such as:
145
+ #
146
+ # rule assignments
147
+ # (assignment [\n])+
148
+ # end
149
+ #
150
+ # This is a subtle difference from array_of. The rule given here provides a syntax node with multiple elements,
151
+ # each one corresponding to the grouping, not to an individual child node -- that is, it's a list of
152
+ # [[assignment node, newline node], [assignment, newline], ...] instead of being a flattened list of
153
+ # [assignment, newline, assignment, newline, ...].
154
+ #
155
+ # To extract these:
156
+ #
157
+ # node :assignments, :assignments => extract(:assignment)
158
+ #
159
+ # Which will extract just the assignments out of those "nested children", and then do what you expect from there.
160
+ #
161
+ def extract(node_name)
162
+ lambda do |node|
163
+ results = []
164
+ results << node.send(node_name) if node.respond_to?(node_name)
165
+
166
+ results.push *node.elements.select { |elem| elem.respond_to?(node_name) }.map { |elem| elem.send(node_name) }
167
+
168
+ results
169
+ end
170
+ end
171
+
87
172
  end
88
173
 
89
- # Include this module to get access to the node and create_node methods.
90
- # A good place to include this is in a separate AST module, e.g.
174
+ module NodeFilters
175
+
176
+ def self.filter(node, kind, recursive)
177
+ nodes = []
178
+ children = node.respond_to?(:elements) ? (node.elements || []) : []
179
+ if recursive
180
+ nodes << node if node.respond_to?(:node_to_build) && node.node_to_build == kind
181
+ children.each { |child| nodes.push *filter(child, kind, true) }
182
+ else
183
+ nodes = ([node] + children).select { |n| n.respond_to?(:node_to_build) && n.node_to_build == kind }
184
+ end
185
+ nodes
186
+ end
187
+
188
+ end
189
+
190
+ # Include this module to get access to the node and create_node class methods.
91
191
  #
92
192
  # module AST
93
193
  # include NodeDefinition
94
194
  # node :foo
95
195
  # end
96
196
  #
97
- # And then in your grammar:
98
- #
99
- # rule foo
100
- # "foo" <AST.create_node(:foo)>
101
- # end
102
- #
103
197
  def self.included(base)
104
198
  base.extend ModuleMethods
105
199
  end
@@ -0,0 +1,33 @@
1
+ module TireSwing
2
+
3
+ module ParserExtension
4
+
5
+ def ast(io)
6
+ parser = new
7
+ result = parser.parse(io)
8
+ if result
9
+ result.build
10
+ else
11
+ raise TireSwing::ParseError.new(parser.failure_reason, parser)
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ # Extends the treetop-provided grammar parser with a .ast class method for simple parsing and building of
18
+ # an AST defined by TireSwing. Takes the grammar module as an argument.
19
+ #
20
+ # Additionally, this defines a #node method on the grammar to delegate to the class or the AST to create
21
+ # new nodes, e.g. <node(:variable)> instead of <AST.create_node(:variable)>
22
+ #
23
+ # You can specify an alternate module which contains the AST if desired.
24
+ def self.parses_grammar(grammar, ast=nil)
25
+ parser = (grammar.to_s + "Parser").constantize
26
+ ast ||= grammar
27
+ parser.module_eval do
28
+ extend ParserExtension
29
+ define_method(:node) { |*args| ast.create_node(*args) }
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe TireSwing::ParseError do
4
+
5
+ describe ".new" do
6
+ it "takes a message and a parser instance" do
7
+ TireSwing::ParseError.new("message", "parser").should be_an_instance_of(TireSwing::ParseError)
8
+ end
9
+ end
10
+
11
+ it "has a message" do
12
+ e = TireSwing::ParseError.new("message", "parser")
13
+ e.message.should == "message"
14
+ end
15
+
16
+ it "has a parser" do
17
+ e = TireSwing::ParseError.new("message", "parser")
18
+ e.parser.should == "parser"
19
+ end
20
+
21
+ end
@@ -28,6 +28,9 @@ module AssignmentsLanguage
28
28
  end
29
29
  end
30
30
 
31
+ # This uses an external eval method to build the TireSwing AST manually. Don't do this, but it's here for
32
+ # an example.
33
+
31
34
  class Parser < ::Treetop::Runtime::CompiledParser
32
35
  include Grammar
33
36
  def self.parse(io)
@@ -1,4 +1,5 @@
1
1
  # This demonstrates a grammar using inline evals to use Treetop to build an external TireSwing-based AST.
2
+ # Don't do this, this takes away a good bit of the magic.
2
3
  module AssignmentsLanguage
3
4
  grammar Grammar
4
5
 
@@ -0,0 +1,36 @@
1
+ # More magic: handle recursive rules
2
+
3
+ Treetop.load_from_string <<-GRAMMAR
4
+
5
+ module Lists
6
+ grammar Grammar
7
+ rule lists
8
+ (list [\n])+ <node(:lists)>
9
+ end
10
+
11
+ rule list
12
+ "[" whitespace* number ("," whitespace* number)* "]" <node(:list)>
13
+ end
14
+
15
+ rule number
16
+ [1-9] [0-9]* <node(:number)>
17
+ end
18
+
19
+ rule whitespace
20
+ [ ]
21
+ end
22
+ end
23
+ end
24
+
25
+ GRAMMAR
26
+
27
+ module Lists
28
+ module AST
29
+ include TireSwing::NodeDefinition
30
+ node :lists, :elements, :lists => extract(:list)
31
+ node :list, :elements, :numbers => array_of(:number, true) { |num| num.text_value.to_i }
32
+ node :number # placeholder
33
+ end
34
+
35
+ TireSwing.parses_grammar(Grammar, AST)
36
+ end
@@ -5,19 +5,19 @@ Treetop.load_from_string <<-GRAMMAR
5
5
  module MagicAssignments
6
6
  grammar Grammar
7
7
  rule assignments
8
- ( blank_line / assignment )* <AST.create_node(:assignments)>
8
+ ( blank_line / assignment )* <node(:assignments)>
9
9
  end
10
10
  rule assignment
11
- lhs:variable whitespace* "=" whitespace* rhs:variable [\\n] <AST.create_node(:assignment)>
11
+ lhs:variable whitespace* "=" whitespace* rhs:variable [\\n] <node(:assignment)>
12
12
  end
13
13
  rule variable
14
- [a-z]+ <AST.create_node(:variable)>
14
+ [a-z]+
15
15
  end
16
16
  rule whitespace
17
17
  [ ]
18
18
  end
19
19
  rule blank_line
20
- whitespace* [\\n] <AST.create_node(:blank_line)>
20
+ whitespace* [\\n]
21
21
  end
22
22
  end
23
23
  end
@@ -28,11 +28,8 @@ module MagicAssignments
28
28
  module AST
29
29
  include TireSwing::NodeDefinition
30
30
 
31
- node :assignments, :assignments => :elements
32
31
  node :assignment, :lhs, :rhs
33
- node :blank_line
34
- node :variable, :value => :text_value
35
-
32
+ node :assignments, :assignments => array_of(:assignment)
36
33
  end
37
34
 
38
35
  include TireSwing::VisitorDefinition
@@ -44,20 +41,11 @@ module MagicAssignments
44
41
  hash
45
42
  end
46
43
  visits AST::Assignment do |assignment, hash|
47
- hash[visit(assignment.lhs)] = visit(assignment.rhs)
48
- end
49
- visits AST::BlankLine
50
- visits AST::Variable do |variable|
51
- variable.value
44
+ hash[assignment.lhs] = assignment.rhs
52
45
  end
53
46
  end
54
47
 
55
- class Parser < ::Treetop::Runtime::CompiledParser
56
- include Grammar
57
- def self.parse(io)
58
- new.parse(io).build
59
- end
60
- end
48
+ TireSwing.parses_grammar(Grammar, AST)
61
49
 
62
50
  end
63
51
 
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
+
3
+ require TireSwing.path(%w(spec grammars lists))
4
+
5
+ describe Lists::GrammarParser do
6
+ describe ".ast" do
7
+ before(:each) do
8
+ @input = <<-EOD
9
+ [1, 2, 3]
10
+ [4, 5]
11
+ [6, 7,8,9,10]
12
+ EOD
13
+ @result = Lists::GrammarParser.ast(@input)
14
+ end
15
+
16
+ it "returns an AST" do
17
+ @result.should be_an_instance_of(Lists::AST::Lists)
18
+ end
19
+
20
+ it "has an array of lists" do
21
+ @result.should have(3).lists
22
+ end
23
+
24
+ it "has lists with numbers" do
25
+ @result.lists.first.should have(3).numbers
26
+ @result.lists.first.numbers.should == [1, 2, 3]
27
+ end
28
+
29
+ end
30
+ end
@@ -2,25 +2,25 @@ require File.join(File.dirname(__FILE__), %w[.. spec_helper])
2
2
 
3
3
  require TireSwing.path(%w(spec grammars magic))
4
4
 
5
- describe MagicAssignments::Parser do
6
- describe ".parse" do
5
+ describe MagicAssignments::GrammarParser do
6
+ describe ".ast" do
7
7
  before(:each) do
8
8
  @input = File.read(TireSwing.path(%w(spec fixtures assignments.txt)))
9
- @result = MagicAssignments::Parser.parse(@input)
9
+ @result = MagicAssignments::GrammarParser.ast(@input)
10
10
  end
11
11
 
12
12
  it "returns an AST" do
13
13
  @result.should be_an_instance_of(MagicAssignments::AST::Assignments)
14
14
  end
15
15
 
16
- it "has the right number of nodes" do
17
- @result.should have(7).assignments
16
+ it "has the right number of assignments" do
17
+ @result.should have(3).assignments
18
18
  end
19
19
 
20
20
  it "has an assignment with correct values" do
21
21
  @result.assignments.first.should be_an_instance_of(MagicAssignments::AST::Assignment)
22
- @result.assignments.first.lhs.value.should == "foo"
23
- @result.assignments.first.rhs.value.should == "bar"
22
+ @result.assignments.first.lhs.should == "foo"
23
+ @result.assignments.first.rhs.should == "bar"
24
24
  end
25
25
 
26
26
  end
@@ -29,7 +29,7 @@ end
29
29
  describe MagicAssignments::HashVisitor do
30
30
  describe ".visit" do
31
31
  it "returns a hash representation of the assignments" do
32
- ast = MagicAssignments::Parser.parse(File.read(TireSwing.path(%w(spec fixtures assignments.txt))))
32
+ ast = MagicAssignments::GrammarParser.ast(File.read(TireSwing.path(%w(spec fixtures assignments.txt))))
33
33
  MagicAssignments::HashVisitor.visit(ast).should == {
34
34
  "foo" => "bar",
35
35
  "baz" => "blech",
@@ -37,4 +37,4 @@ describe MagicAssignments::HashVisitor do
37
37
  }
38
38
  end
39
39
  end
40
- end
40
+ end
@@ -11,14 +11,14 @@ describe TireSwing::NodeCreator do
11
11
 
12
12
  describe "#initialize" do
13
13
 
14
- it "takes a class as an argument" do
15
- nc = TireSwing::NodeCreator.new(SomeNode)
14
+ it "takes a node name and a node class as arguments" do
15
+ nc = TireSwing::NodeCreator.new(:some_node, SomeNode)
16
16
  end
17
17
  end
18
18
 
19
19
  describe "#new" do
20
20
  before(:each) do
21
- @nc = TireSwing::NodeCreator.new(SomeNode)
21
+ @nc = TireSwing::NodeCreator.new(:some_node, SomeNode)
22
22
  end
23
23
 
24
24
  it "takes parameters as if it were a treetop syntax node" do
@@ -35,6 +35,11 @@ describe TireSwing::NodeCreator do
35
35
  node.methods.should include("build")
36
36
  end
37
37
 
38
+ it "defines a node_to_build method on the syntax node that returns the kind of ast node it's going to build" do
39
+ node = @nc.new("what", 0..3)
40
+ node.node_to_build.should == :some_node
41
+ end
42
+
38
43
  it "creates an instance of the defined class when build is called on the resulting syntax node" do
39
44
  node = @nc.new("what", 0..3)
40
45
  mock_node = mock("node")
@@ -5,6 +5,14 @@ describe TireSwing::NodeDefinition do
5
5
  include TireSwing::NodeDefinition
6
6
  end
7
7
 
8
+ before(:all) do
9
+ TestNodes.class_eval { node :foo_node }
10
+ end
11
+
12
+ after(:all) do
13
+ TestNodes.send(:remove_const, "FooNode")
14
+ end
15
+
8
16
  it "adds a node method when included" do
9
17
  TestNodes.methods.should include("node")
10
18
  end
@@ -34,12 +42,6 @@ describe TireSwing::NodeDefinition do
34
42
  end
35
43
 
36
44
  describe ".create_node" do
37
- before(:all) do
38
- TestNodes.class_eval { node :foo_node }
39
- end
40
- after(:all) do
41
- TestNodes.send(:remove_const, "FooNode")
42
- end
43
45
 
44
46
  it "takes a node name as an argument" do
45
47
  TestNodes.create_node(:foo_node)
@@ -49,12 +51,84 @@ describe TireSwing::NodeDefinition do
49
51
  TestNodes.create_node(:foo_node).should be_an_instance_of(TireSwing::NodeCreator)
50
52
  end
51
53
 
52
- it "instantiates the node creator with the class corresponding to the node name" do
54
+ it "instantiates the node creator for the given node name" do
53
55
  obj = Object.new
54
- TireSwing::NodeCreator.should_receive(:new).with(TestNodes::FooNode).and_return(obj)
56
+ TireSwing::NodeCreator.should_receive(:new).with(:foo_node, TestNodes::FooNode).and_return(obj)
55
57
  TestNodes.create_node(:foo_node).should == obj
56
58
  end
57
59
 
58
60
  end
59
61
 
62
+ describe ".array_of" do
63
+
64
+ it "returns a lambda" do
65
+ TestNodes.array_of(:foo_node).should be_an_instance_of(Proc)
66
+ end
67
+
68
+ it "returns a lambda that filters a node's elements for nodes that will build the given node type" do
69
+ a, b = mock_syntax_node("a", :node_to_build => :foo_node), mock_syntax_node("b", :node_to_build => :foo_node)
70
+ node = mock_syntax_node("node", :elements => [1, 2, a, 3, b])
71
+ TestNodes.array_of(:foo_node).call(node).should == [a, b]
72
+ end
73
+
74
+ it "will return the node itself if it provides the given node type" do
75
+ node = mock_syntax_node("node", :node_to_build => :foo)
76
+ TestNodes.array_of(:foo).call(node).should == [node]
77
+ end
78
+
79
+ it "does not filter recursively by default" do
80
+ b = mock_syntax_node("b", :elements => ["stuff", "whatever"], :node_to_build => :foo)
81
+ a = mock_syntax_node("a", :elements => ["jkl", b], :node_to_build => :foo)
82
+ top = mock_syntax_node("top", :elements => ["asdf", a], :node_to_build => :foo)
83
+ TestNodes.array_of(:foo).call(top).should == [top, a]
84
+ end
85
+
86
+ describe "with the recursive flag" do
87
+ it "returns a lambda that filters recursively for the right kind of child" do
88
+ b = mock_syntax_node("b", :elements => ["stuff", "whatever"], :node_to_build => :foo)
89
+ a = mock_syntax_node("a", :elements => ["jkl", b], :node_to_build => :foo)
90
+ top = mock_syntax_node("top", :elements => ["asdf", a], :node_to_build => :foo)
91
+ TestNodes.array_of(:foo, true).call(top).should == [top, a, b]
92
+ end
93
+ end
94
+
95
+ describe "with a block provided" do
96
+ it "yields the filtered results to the block" do
97
+ a = mock_syntax_node("a", :node_to_build => :foo_node, :x => 1)
98
+ b = mock_syntax_node("b", :node_to_build => :foo_node, :x => 2)
99
+ node = mock_syntax_node("node", :elements => [1, 2, a, 3, b])
100
+ yielded = []
101
+ array_of = TestNodes.array_of(:foo_node) { |node| yielded << node; node.x }
102
+ array_of.call(node).should == [1, 2]
103
+ yielded.should == [a, b]
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ describe ".extract" do
110
+
111
+ it "returns a lambda" do
112
+ TestNodes.extract(:thing).should be_an_instance_of(Proc)
113
+ end
114
+
115
+ it "extracts all nodes by calling the given node name wherever it applies" do
116
+ q = mock_syntax_node("q")
117
+ a = mock_syntax_node("a", :value => q)
118
+ b = mock_syntax_node("b")
119
+ c = mock_syntax_node("c", :value => "c")
120
+ node = mock_syntax_node("node", :elements => [a, b, c])
121
+ TestNodes.extract(:value).call(node).should == [q, "c"]
122
+ end
123
+
124
+ it "will extract the given node as well as the nested children " do
125
+ a = mock_syntax_node("a", :value => "a")
126
+ b = mock_syntax_node("b")
127
+ c = mock_syntax_node("c", :value => "c")
128
+ node = mock_syntax_node("node", :value => "foo", :elements => [a, b, c])
129
+ TestNodes.extract(:value).call(node).should == ["foo", "a", "c"]
130
+ end
131
+
132
+ end
133
+
60
134
  end
data/spec/node_spec.rb CHANGED
@@ -87,44 +87,55 @@ describe TireSwing::Node do
87
87
 
88
88
  describe "with an instance of Treetop::Runtime::SyntaxNode" do
89
89
  before(:each) do
90
- @child = mock_syntax_node :build => "child value"
91
- @top = mock_syntax_node :child => @child, :data => "data"
90
+ @child = mock_syntax_node("child")
91
+ @top = mock_syntax_node("top")
92
92
  end
93
93
 
94
- it "sets the named values by calling build on the named child node" do
95
- @top.should_receive(:child).and_return(@child)
96
- @child.should_receive(:build).and_return("child value")
97
- n = @node.new(@top)
98
- n.child.should == "child value"
94
+ describe "for a node with a single attribute as a named method" do
95
+ it "retrieves the value for the attribute by calling the named method" do
96
+ @node = TireSwing::Node.create(:foo => :a_method)
97
+ @top.should_receive(:a_method).and_return("asdf")
98
+ @node.new(@top).foo.should == "asdf"
99
+ end
99
100
  end
100
101
 
101
- it "calls the specified method directly for hash values" do
102
- @top.should_receive(:data).and_return do
103
- puts "called from #{caller.detect {|c| c !~ /rspec/}}"
104
- "foo"
102
+ describe "for a node with an attribute naming a child syntax node" do
103
+ it "retrieves the value by calling build on the child" do
104
+ @top.should_receive(:child).and_return(@child)
105
+ @node = TireSwing::Node.create(:foo => :child)
106
+ @child.should_receive(:build).and_return("child value")
107
+ @node.new(@top).foo.should == "child value"
105
108
  end
106
- n = @node.new(@top)
107
- n.value.should == "foo"
108
- end
109
109
 
110
- it "calls build on each element of a referenced node value if that value is an array" do
111
- @top.should_receive(:child).and_return([@child, @child])
112
- @child.should_receive(:build).exactly(2).times.and_return("one", "two")
113
- n = @node.new(@top)
114
- n.child.should == %w(one two)
115
- end
110
+ it "calls text_value on the child if the child doesn't have a build method" do
111
+ @top.should_receive(:child).and_return(@child)
112
+ @node = TireSwing::Node.create(:foo => :child)
113
+ @child.should_receive(:text_value).and_return("child value")
114
+ @node.new(@top).foo.should == "child value"
115
+ end
116
+
117
+ it "retrieves the value of each item in an array if the named attribute returns one" do
118
+ @child.should_receive(:build).and_return("child")
119
+ @top.should_receive(:children).and_return(["foo", 1, @child])
120
+ TireSwing::Node.create(:foo => :children).new(@top).foo.should == ["foo", 1, "child"]
121
+ end
116
122
 
117
- it "only calls build on elements of an array if they respond to that method" do
118
- @top.should_receive(:child).and_return([@child, "blah"])
119
- @node.new(@top).child.should == ["child value", "blah"]
120
123
  end
121
124
 
122
- it "yields the syntax node instance if a mapped attribute is a lambda" do
123
- @node = TireSwing::Node.create(:value => lambda { |x| @x = x })
124
- @node.new(@top)
125
+ it "yields the syntax node instance if a named attribute is a lambda" do
126
+ @node = TireSwing::Node.create(:value => lambda { |x| @x = x; "asdf" })
127
+ @node.new(@top).value.should == "asdf"
125
128
  @x.should == @top
126
129
  end
127
130
 
131
+ describe "for a node with an attribute as a method to call on the text value" do
132
+ it "calls that method on the text value, not the parsed node" do
133
+ node = TireSwing::Node.create(:value => :to_i)
134
+ @top.should_receive(:text_value).and_return("1234")
135
+ node.new(@top).value.should == 1234
136
+ end
137
+ end
138
+
128
139
  end
129
140
  end
130
141
 
@@ -0,0 +1,85 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe TireSwing::ParserExtension do
4
+
5
+ it "defines a parses_grammar method on TireSwing" do
6
+ TireSwing.should respond_to(:parses_grammar)
7
+ end
8
+
9
+ describe "when given a grammar" do
10
+ module Foo; end
11
+ class FooParser; end
12
+ module AST; end
13
+
14
+ it "extends the parser for that grammar with the ParserExtension module, defining an ast method" do
15
+ TireSwing.parses_grammar(Foo)
16
+ FooParser.should respond_to(:ast)
17
+ end
18
+
19
+ it "defines a node method on the parser that delegates to the grammar" do
20
+ TireSwing.parses_grammar(Foo)
21
+ Foo.should_receive(:create_node).with("a", "b", "c")
22
+ FooParser.new.node("a", "b", "c")
23
+ end
24
+
25
+ it "defines a create_node method on the parser that delegates to the given AST, if provided" do
26
+ TireSwing.parses_grammar(Foo, AST)
27
+ AST.should_receive(:create_node).with("a", "b", "c")
28
+ FooParser.new.node("a", "b", "c")
29
+ end
30
+
31
+ end
32
+
33
+ describe "#ast" do
34
+ before(:all) do
35
+
36
+ Treetop.load_from_string <<-GRAMMAR
37
+ module TestGramma
38
+ grammar Grammar
39
+ rule letters
40
+ letter* <node(:letters)>
41
+ end
42
+ rule letter
43
+ value:[a-z] [\n]? <node(:letter)>
44
+ end
45
+ end
46
+ end
47
+ GRAMMAR
48
+
49
+ module ::TestGramma
50
+ module AST
51
+ include TireSwing::NodeDefinition
52
+ node :letters, :letters => array_of(:letter)
53
+ node :letter, :value
54
+ end
55
+ TireSwing.parses_grammar(Grammar, AST)
56
+ end
57
+
58
+ end
59
+
60
+ after(:all) do
61
+ Object.send(:remove_const, "TestGramma")
62
+ end
63
+
64
+ it "takes an AST and returns the built AST" do
65
+ asdf = TestGramma::GrammarParser.ast("asdf")
66
+ asdf.should be_an_instance_of ::TestGramma::AST::Letters
67
+ asdf.should have(4).letters
68
+ asdf.letters.map { |l| l.value }.should == %w(a s d f)
69
+ end
70
+
71
+ describe "with invalid input" do
72
+
73
+ it "raises an exception" do
74
+ lambda { parse }.should raise_error(TireSwing::ParseError)
75
+ end
76
+
77
+ def parse
78
+ TestGramma::GrammarParser.ast("as3f")
79
+ end
80
+ end
81
+
82
+
83
+ end
84
+
85
+ end
data/spec/spec_helper.rb CHANGED
@@ -11,8 +11,8 @@ Spec::Runner.configure do |config|
11
11
  # config.mock_with :rr
12
12
 
13
13
  # borrowed from rspec_on_rails' mock_model
14
- def mock_syntax_node(stubs={})
15
- m = mock "mock syntax node", stubs
14
+ def mock_syntax_node(name, stubs={})
15
+ m = mock "mock syntax node (#{name})", stubs
16
16
  m.send(:__mock_proxy).instance_eval do
17
17
  def @target.is_a?(other)
18
18
  Treetop::Runtime::SyntaxNode.ancestors.include?(other)
data/tire_swing.gemspec CHANGED
@@ -1,14 +1,14 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{tire_swing}
3
- s.version = "0.0.2"
3
+ s.version = "0.0.3"
4
4
 
5
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
6
  s.authors = ["Nathan Witmer"]
7
- s.date = %q{2008-07-17}
7
+ s.date = %q{2008-08-11}
8
8
  s.description = %q{Simple node and visitor definitions for Treetop grammars.}
9
9
  s.email = %q{nwitmer at gmail dot com}
10
10
  s.extra_rdoc_files = ["History.txt", "README.txt", "spec/fixtures/assignments.txt"]
11
- s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "examples/simple_assignment.rb", "lib/tire_swing.rb", "lib/tire_swing/metaid.rb", "lib/tire_swing/node.rb", "lib/tire_swing/node_creator.rb", "lib/tire_swing/node_definition.rb", "lib/tire_swing/visitor.rb", "lib/tire_swing/visitor_definition.rb", "spec/fixtures/assignments.txt", "spec/fixtures/ey00-s00348.xen", "spec/grammars/assignments.rb", "spec/grammars/assignments.treetop", "spec/grammars/dot_xen.rb", "spec/grammars/dot_xen.treetop", "spec/grammars/magic.rb", "spec/integration/assignments_spec.rb", "spec/integration/dot_xen_spec.rb", "spec/integration/magic_spec.rb", "spec/node_creator_spec.rb", "spec/node_definition_spec.rb", "spec/node_spec.rb", "spec/spec_helper.rb", "spec/tire_swing_spec.rb", "spec/visitor_definition_spec.rb", "spec/visitor_spec.rb", "tasks/ann.rake", "tasks/bones.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/manifest.rake", "tasks/notes.rake", "tasks/post_load.rake", "tasks/rdoc.rake", "tasks/rubyforge.rake", "tasks/setup.rb", "tasks/spec.rake", "tasks/svn.rake", "tasks/test.rake", "tire_swing.gemspec"]
11
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "examples/simple_assignment.rb", "lib/tire_swing.rb", "lib/tire_swing/error.rb", "lib/tire_swing/metaid.rb", "lib/tire_swing/node.rb", "lib/tire_swing/node_creator.rb", "lib/tire_swing/node_definition.rb", "lib/tire_swing/parser_extension.rb", "lib/tire_swing/visitor.rb", "lib/tire_swing/visitor_definition.rb", "spec/error_spec.rb", "spec/fixtures/assignments.txt", "spec/fixtures/ey00-s00348.xen", "spec/grammars/assignments.rb", "spec/grammars/assignments.treetop", "spec/grammars/dot_xen.rb", "spec/grammars/dot_xen.treetop", "spec/grammars/lists.rb", "spec/grammars/magic.rb", "spec/integration/assignments_spec.rb", "spec/integration/dot_xen_spec.rb", "spec/integration/lists_spec.rb", "spec/integration/magic_spec.rb", "spec/node_creator_spec.rb", "spec/node_definition_spec.rb", "spec/node_spec.rb", "spec/parser_extension_spec.rb", "spec/spec_helper.rb", "spec/tire_swing_spec.rb", "spec/visitor_definition_spec.rb", "spec/visitor_spec.rb", "tasks/ann.rake", "tasks/bones.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/manifest.rake", "tasks/notes.rake", "tasks/post_load.rake", "tasks/rdoc.rake", "tasks/rubyforge.rake", "tasks/setup.rb", "tasks/spec.rake", "tasks/svn.rake", "tasks/test.rake", "tire_swing.gemspec"]
12
12
  s.has_rdoc = true
13
13
  s.homepage = %q{http://github.com/aniero/tire_swing}
14
14
  s.rdoc_options = ["--main", "README.txt"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aniero-tire_swing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Witmer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-07-17 00:00:00 -07:00
12
+ date: 2008-08-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -56,25 +56,31 @@ files:
56
56
  - Rakefile
57
57
  - examples/simple_assignment.rb
58
58
  - lib/tire_swing.rb
59
+ - lib/tire_swing/error.rb
59
60
  - lib/tire_swing/metaid.rb
60
61
  - lib/tire_swing/node.rb
61
62
  - lib/tire_swing/node_creator.rb
62
63
  - lib/tire_swing/node_definition.rb
64
+ - lib/tire_swing/parser_extension.rb
63
65
  - lib/tire_swing/visitor.rb
64
66
  - lib/tire_swing/visitor_definition.rb
67
+ - spec/error_spec.rb
65
68
  - spec/fixtures/assignments.txt
66
69
  - spec/fixtures/ey00-s00348.xen
67
70
  - spec/grammars/assignments.rb
68
71
  - spec/grammars/assignments.treetop
69
72
  - spec/grammars/dot_xen.rb
70
73
  - spec/grammars/dot_xen.treetop
74
+ - spec/grammars/lists.rb
71
75
  - spec/grammars/magic.rb
72
76
  - spec/integration/assignments_spec.rb
73
77
  - spec/integration/dot_xen_spec.rb
78
+ - spec/integration/lists_spec.rb
74
79
  - spec/integration/magic_spec.rb
75
80
  - spec/node_creator_spec.rb
76
81
  - spec/node_definition_spec.rb
77
82
  - spec/node_spec.rb
83
+ - spec/parser_extension_spec.rb
78
84
  - spec/spec_helper.rb
79
85
  - spec/tire_swing_spec.rb
80
86
  - spec/visitor_definition_spec.rb