aniero-tire_swing 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 1.0.4 / 2008-11-17
2
+
3
+ * 4 minor enhancements
4
+ * Updated array traversal code to be recursive by default
5
+ * Updated visitor code to use class instances instead of class-level methods for greater flexibility
6
+ * Updated error handling to include parsing context in error message
7
+ * Added clone method for deep copy of AST
8
+ * Updated node building to set node parents for easier traversal of the AST
9
+
1
10
  == 1.0.0 / 2008-06-29
2
11
 
3
12
  * 1 major enhancement
@@ -31,6 +31,8 @@ module TireSwing
31
31
  @attribute_mapping ||= {}
32
32
  end
33
33
 
34
+ attr_accessor :parent
35
+
34
36
  # Instantiate a node.
35
37
  #
36
38
  # Values can either be a hash of values to set the values of this node's attributes, or a Treetop syntax node
@@ -46,21 +48,18 @@ module TireSwing
46
48
  end
47
49
  end
48
50
 
51
+ # Deep-copy of this node and any children. Use this if you need to manipulate an AST without modifying the original
52
+ def clone
53
+ Marshal.load(Marshal.dump(self))
54
+ end
55
+
49
56
  protected
50
57
 
51
58
  # Auto-builds this node using the provided parsed node and the defined attributes and mapped attributes.
52
59
  def build_from_parsed_node(parsed_node)
53
60
  attributes.each do |attrib|
54
61
  if handler = mapping(attrib)
55
- if handler.kind_of?(Proc)
56
- value = handler.call(parsed_node)
57
- else
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
63
- end
62
+ value = apply_mapping(handler, parsed_node)
64
63
  else
65
64
  value = parsed_node.send(attrib)
66
65
  end
@@ -75,9 +74,25 @@ module TireSwing
75
74
  end
76
75
  end
77
76
 
77
+ def apply_mapping(handler, parsed_node)
78
+ # TODO add in handler for arrays of methods to call in order
79
+ if handler.kind_of?(Proc)
80
+ value = handler.call(parsed_node)
81
+ else
82
+ # TODO need to add more error checking
83
+ if parsed_node.respond_to?(handler)
84
+ value = parsed_node.send(handler)
85
+ else
86
+ value = parsed_node.text_value.send(handler)
87
+ end
88
+ end
89
+ end
90
+
78
91
  def extract_value(value)
79
92
  if value.respond_to?(:build)
80
- value.build
93
+ node = value.build
94
+ node.parent = self if node.kind_of?(Node)
95
+ node
81
96
  elsif value.kind_of?(Treetop::Runtime::SyntaxNode)
82
97
  value.text_value
83
98
  else
@@ -2,15 +2,16 @@ module TireSwing::NodeDefinition
2
2
 
3
3
  module ModuleMethods
4
4
 
5
- # Returns a NodeCreator to act as a stand-in for a normal node class definition.
5
+ # Returns a NodeCreator to act as a stand-in for a node class in the treetop parser.
6
6
  # According to the treetop metagrammar, node class definitions can have more than just a class in them.
7
- # For example:
7
+ #
8
+ # Given the initial grammar,
8
9
  #
9
10
  # rule variable
10
11
  # [a-z]+ <Variable>
11
12
  # end
12
13
  #
13
- # Instead, use this method to use AST node auto-build functionality. Given
14
+ # you might instead use use the AST node auto-build functionality. Given
14
15
  #
15
16
  # node :variable, :value => :text_value
16
17
  #
@@ -20,6 +21,8 @@ module TireSwing::NodeDefinition
20
21
  # [a-z]+ <node(:variable)>
21
22
  # end
22
23
  #
24
+ # and voila, you'll magically have a Variable node with a #value attribute containing the text value.
25
+ #
23
26
  # Also note that you can specify alternate namespaces:
24
27
  #
25
28
  # module AST
@@ -28,15 +31,15 @@ module TireSwing::NodeDefinition
28
31
  #
29
32
  # <AST.create_node(:variable)>
30
33
  #
31
- # When you're using the parser extension:
34
+ # and when you're using the parser extension, tell the parser about the namespace
32
35
  #
33
36
  # TireSwing.parses_grammar(Grammar, AST)
34
37
  #
35
- # you can use
38
+ # and then you can use the shortest possible syntax,
36
39
  #
37
40
  # <node(...)>
38
41
  #
39
- # Which is an instance method wrapper for the create_node class method.
42
+ # which is an instance method wrapper for the create_node class method.
40
43
  #
41
44
  def create_node(name)
42
45
  TireSwing::NodeCreator.new(name, const_get(name.to_s.camelize))
@@ -133,7 +136,7 @@ module TireSwing::NodeDefinition
133
136
  #
134
137
  # If you provide a block, the filtered result will be yielded to the block and returned as the final result.
135
138
  #
136
- def array_of(kind, recursive = false, &blk)
139
+ def array_of(kind, recursive = true, &blk)
137
140
  lambda do |node|
138
141
  result = NodeFilters.filter(node, kind, recursive)
139
142
  blk ? result.map(&blk) : result
@@ -8,7 +8,14 @@ module TireSwing
8
8
  if result
9
9
  result.build
10
10
  else
11
- raise TireSwing::ParseError.new(parser.failure_reason, parser)
11
+ raise ParseError.new(
12
+ [
13
+ parser.failure_reason,
14
+ parser.input.split("\n")[parser.failure_line-1],
15
+ " " * parser.failure_index + "^"
16
+ ].join("\n"),
17
+ parser
18
+ )
12
19
  end
13
20
  end
14
21
 
@@ -16,18 +16,15 @@ module TireSwing
16
16
  end
17
17
  end
18
18
 
19
- # Visit the given node using the visitor mapping defined with .visits.
20
- # This finds the block to call for the given node and calls it with the node and additional arguments, if any.
21
- #
22
- # Raises an exception if no visitor is found for the given node.
23
- #
19
+ # delegates to a new instance of the visitor class
24
20
  def visit(node, *args)
25
- block = visitor_for(node)
26
- if args.empty?
27
- block.call(node)
28
- else
29
- block.call(node, *args)
30
- end
21
+ visitor = new
22
+ visitor.visit(node, *args)
23
+ end
24
+
25
+ # Look up a visitor block for this node
26
+ def visitor_for(node)
27
+ nodes[node.class] or raise "could not find visitor definition for #{node.class}: #{node.inspect}"
31
28
  end
32
29
 
33
30
  protected
@@ -37,11 +34,20 @@ module TireSwing
37
34
  @nodes ||= {}
38
35
  end
39
36
 
40
- # Look up a visitor block for this node
41
- def visitor_for(node)
42
- nodes[node.class] or raise "could not find visitor definition for #{node.class}: #{node.inspect}"
43
- end
37
+ end
44
38
 
39
+ # Visit the given node using the visitor mapping defined with .visits.
40
+ # This finds the block to call for the given node and calls it with the node and additional arguments, if any.
41
+ #
42
+ # Raises an exception if no visitor is found for the given node.
43
+ #
44
+ def visit(node, *args)
45
+ block = self.class.visitor_for(node)
46
+ if args.empty?
47
+ block.call(node)
48
+ else
49
+ block.call(node, *args)
50
+ end
45
51
  end
46
52
 
47
53
  end
@@ -35,6 +35,14 @@ module TireSwing::VisitorDefinition
35
35
  #
36
36
  # HashVisitor.visit( assignment_node )
37
37
  #
38
+ # Note that this is using class_eval, so you can define methods inside, including overrides to #visit.
39
+ #
40
+ # Note also that this is just a wrapper for:
41
+ #
42
+ # class MyVisitor < TireSwing::Visitor
43
+ # visits ...
44
+ # end
45
+ #
38
46
  def visitor(name, &blk)
39
47
  klass = Class.new(TireSwing::Visitor)
40
48
  const_set name.to_s.camelize, klass
data/lib/tire_swing.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module TireSwing
2
2
 
3
3
  # :stopdoc:
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
6
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
7
  # :startdoc:
@@ -76,19 +76,19 @@ describe TireSwing::NodeDefinition do
76
76
  TestNodes.array_of(:foo).call(node).should == [node]
77
77
  end
78
78
 
79
- it "does not filter recursively by default" do
79
+ it "filters recursively by default" do
80
80
  b = mock_syntax_node("b", :elements => ["stuff", "whatever"], :node_to_build => :foo)
81
81
  a = mock_syntax_node("a", :elements => ["jkl", b], :node_to_build => :foo)
82
82
  top = mock_syntax_node("top", :elements => ["asdf", a], :node_to_build => :foo)
83
- TestNodes.array_of(:foo).call(top).should == [top, a]
83
+ TestNodes.array_of(:foo, true).call(top).should == [top, a, b]
84
84
  end
85
85
 
86
- describe "with the recursive flag" do
87
- it "returns a lambda that filters recursively for the right kind of child" do
86
+ describe "with the recursive flag set to false" do
87
+ it "returns a lambda that does not filter recursively" do
88
88
  b = mock_syntax_node("b", :elements => ["stuff", "whatever"], :node_to_build => :foo)
89
89
  a = mock_syntax_node("a", :elements => ["jkl", b], :node_to_build => :foo)
90
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]
91
+ TestNodes.array_of(:foo, false).call(top).should == [top, a]
92
92
  end
93
93
  end
94
94
 
data/spec/node_spec.rb CHANGED
@@ -2,6 +2,13 @@ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
2
 
3
3
  describe TireSwing::Node do
4
4
 
5
+ it "has a parent accessor" do
6
+ node = TireSwing::Node.new
7
+ node.parent.should be_nil
8
+ node.parent = :parent
9
+ node.parent.should == :parent
10
+ end
11
+
5
12
  describe ".create" do
6
13
  it "returns a class that inherits from TireSwing::Node" do
7
14
  klass = TireSwing::Node.create
@@ -120,6 +127,16 @@ describe TireSwing::Node do
120
127
  TireSwing::Node.create(:foo => :children).new(@top).foo.should == ["foo", 1, "child"]
121
128
  end
122
129
 
130
+ it "assigns the parent node if the child node is also a TireSwing node" do
131
+ @top.should_receive(:child).and_return(@child)
132
+ node = TireSwing::Node.create(:foo => :child)
133
+ child_node = TireSwing::Node.new
134
+ @child.should_receive(:build).and_return(child_node)
135
+ new_top = node.new(@top)
136
+ new_top.foo.should == child_node
137
+ child_node.parent.should == new_top
138
+ end
139
+
123
140
  end
124
141
 
125
142
  it "yields the syntax node instance if a named attribute is a lambda" do
@@ -139,4 +156,13 @@ describe TireSwing::Node do
139
156
  end
140
157
  end
141
158
 
159
+ describe "#clone" do
160
+ it "does a deep copy of a node" do
161
+ # can't leave this as an anonymous class, marshal dump/load doesn't like it
162
+ MyNode = TireSwing::Node.create
163
+ node = MyNode.new
164
+ node.clone.should_not eql node
165
+ end
166
+ end
167
+
142
168
  end
@@ -70,8 +70,8 @@ end
70
70
 
71
71
  describe "with invalid input" do
72
72
 
73
- it "raises an exception" do
74
- lambda { parse }.should raise_error(TireSwing::ParseError)
73
+ it "raises an exception with" do
74
+ lambda { parse }.should raise_error(TireSwing::ParseError, /as3f.*\^/m)
75
75
  end
76
76
 
77
77
  def parse
@@ -32,11 +32,11 @@ describe TireSwing::VisitorDefinition, "when included" do
32
32
  @nodes.const_get("Printer").methods.should include("visits")
33
33
  end
34
34
 
35
- it "takes a block with which to define visitors and evaluates it in the context of the new visitor class" do
35
+ it "evaluates at a class level to allow for attribute definitions and more" do
36
36
  @nodes.class_eval do
37
- def what; end
37
+ visitor(:printer) { attr_accessor :thingy }
38
38
  end
39
- @nodes.instance_methods.should include("what")
39
+ @nodes.const_get("Printer").new.should respond_to(:thingy=)
40
40
  end
41
41
 
42
42
  end
data/spec/visitor_spec.rb CHANGED
@@ -25,24 +25,38 @@ describe TireSwing::Visitor do
25
25
 
26
26
  end
27
27
 
28
- describe "#visit" do
28
+ describe "visitors", :shared => true do
29
29
  before(:each) do
30
30
  @visitor.visits(Foo) { |node| "#{node.class}-foo!" }
31
31
  end
32
32
 
33
33
  it "takes a node and calls the appropriate block, passing in the node" do
34
- @visitor.visit(Foo.new).should == "#{Foo}-foo!"
34
+ @visitor_instance.visit(Foo.new).should == "#{Foo}-foo!"
35
35
  end
36
36
 
37
37
  it "raises an exception if it doesn't know how to handle a node" do
38
- lambda { @visitor.visit(Bar.new) }.should raise_error(Exception, /Bar/)
38
+ lambda { @visitor_instance.visit(Bar.new) }.should raise_error(Exception, /Bar/)
39
39
  end
40
40
 
41
41
  it "calls the visit block with arguments, if arguments are given" do
42
42
  @visitor.visits(Bar) { |node, a, b| "#{a}-#{b}" }
43
- @visitor.visit(Bar.new, "foo", "bar").should == "foo-bar"
43
+ @visitor_instance.visit(Bar.new, "foo", "bar").should == "foo-bar"
44
+ end
45
+
46
+ end
47
+
48
+ describe ".visit" do
49
+ before(:each) do
50
+ @visitor_instance = @visitor
44
51
  end
52
+ it_should_behave_like "visitors"
53
+ end
45
54
 
55
+ describe "#visit" do
56
+ before(:each) do
57
+ @visitor_instance = @visitor.new
58
+ end
59
+ it_should_behave_like "visitors"
46
60
  end
47
61
 
48
62
  end
data/tire_swing.gemspec CHANGED
@@ -1,10 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = %q{tire_swing}
3
- s.version = "0.0.3"
5
+ s.version = "0.0.4"
4
6
 
5
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
8
  s.authors = ["Nathan Witmer"]
7
- s.date = %q{2008-08-11}
9
+ s.date = %q{2008-11-17}
8
10
  s.description = %q{Simple node and visitor definitions for Treetop grammars.}
9
11
  s.email = %q{nwitmer at gmail dot com}
10
12
  s.extra_rdoc_files = ["History.txt", "README.txt", "spec/fixtures/assignments.txt"]
@@ -14,14 +16,14 @@ Gem::Specification.new do |s|
14
16
  s.rdoc_options = ["--main", "README.txt"]
15
17
  s.require_paths = ["lib"]
16
18
  s.rubyforge_project = %q{}
17
- s.rubygems_version = %q{1.2.0}
19
+ s.rubygems_version = %q{1.3.1}
18
20
  s.summary = %q{Simple node and visitor definitions for Treetop grammars}
19
21
 
20
22
  if s.respond_to? :specification_version then
21
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
24
  s.specification_version = 2
23
25
 
24
- if current_version >= 3 then
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
25
27
  s.add_runtime_dependency(%q<treetop>, [">= 1.2.4"])
26
28
  s.add_runtime_dependency(%q<attributes>, [">= 5.0.1"])
27
29
  s.add_runtime_dependency(%q<activesupport>, [">= 2.0.2"])
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.3
4
+ version: 0.0.4
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-08-11 00:00:00 -07:00
12
+ date: 2008-11-17 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency