parslet 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+
2
+ # A tree structure that contains parse error messages. This can be used to
3
+ # give the user a detailed report on what went wrong during a parse.
4
+ #
5
+ class Parslet::ErrorTree
6
+ # The parslet that caused the error stored here.
7
+ attr_reader :parslet
8
+ # All errors that were encountered when parsing part of this +parslet+.
9
+ attr_reader :children
10
+
11
+ def initialize(parslet, *children)
12
+ @parslet = parslet
13
+ @children = children.compact
14
+ end
15
+
16
+ def nodes
17
+ 1 + children.inject(0) { |sum, node| sum + node.nodes }
18
+ end
19
+
20
+ def cause
21
+ parslet.cause || "Unknown error in #{parslet.inspect}"
22
+ end
23
+ alias :to_s :cause
24
+
25
+ # Returns an ascii tree representation of the causes of this node and its
26
+ # children.
27
+ #
28
+ def ascii_tree
29
+ StringIO.new.tap { |io|
30
+ recursive_ascii_tree(self, io, [true]) }.
31
+ string
32
+ end
33
+ private
34
+ def recursive_ascii_tree(node, stream, curved)
35
+ append_prefix(stream, curved)
36
+ stream.puts node
37
+
38
+ node.children.each do |child|
39
+ last_child = (node.children.last == child)
40
+
41
+ recursive_ascii_tree(child, stream, curved + [last_child])
42
+ end
43
+ end
44
+ def append_prefix(stream, curved)
45
+ curved[0..-2].each do |c|
46
+ stream.print c ? " " : "| "
47
+ end
48
+ stream.print curved.last ? "`- " : "|- "
49
+ end
50
+ end
@@ -0,0 +1,144 @@
1
+
2
+ # Matches trees against expressions. Trees are formed by arrays and hashes
3
+ # for expressing membership and sequence. The leafs of the tree are other
4
+ # classes.
5
+ #
6
+ # A tree issued by the parslet library might look like this:
7
+ #
8
+ # {
9
+ # :function_call => {
10
+ # :name => 'foobar',
11
+ # :args => [1, 2, 3]
12
+ # }
13
+ # }
14
+ #
15
+ # A pattern that would match against this tree would be:
16
+ #
17
+ # { :function_call => { :name => simple(:name), :args => sequence(:args) }}
18
+ #
19
+ # Note that Parslet::Pattern only matches at a given subtree; it wont try
20
+ # to match recursively. To do that, please use Parslet::Transform.
21
+ #
22
+ class Parslet::Pattern
23
+ def initialize(pattern)
24
+ @pattern = pattern
25
+ end
26
+
27
+ # Searches the given +tree+ for this pattern, yielding the subtrees that
28
+ # match to the block.
29
+ #
30
+ # Example:
31
+ #
32
+ # tree = parslet.apply(input)
33
+ # pat = Parslet::Pattern.new(:_x)
34
+ # pat.each_match(tree) do |subtree|
35
+ # # do something with the matching subtree here
36
+ # end
37
+ #
38
+ def each_match(tree, &block) # :yield: subtree
39
+ recurse_into(tree) do |subtree|
40
+ if bindings=match(subtree)
41
+ block.call(bindings) if block
42
+ end
43
+ end
44
+
45
+ return nil
46
+ end
47
+
48
+ # Decides if the given subtree matches this pattern. Returns the bindings
49
+ # made on a successful match or nil if the match fails.
50
+ #
51
+ def match(subtree)
52
+ bindings = {}
53
+ return bindings if element_match(subtree, @pattern, bindings)
54
+ end
55
+
56
+ def recurse_into(expr, &block)
57
+ # p [:attempt_match, expr]
58
+ block.call(expr)
59
+
60
+ case expr
61
+ when Array
62
+ expr.each { |y| recurse_into(y, &block) }
63
+ when Hash
64
+ expr.each { |k,v| recurse_into(v, &block) }
65
+ end
66
+ end
67
+
68
+ # Returns true if the tree element given by +tree+ matches the expression
69
+ # given by +exp+. This match must respect bindings already made in
70
+ # +bindings+.
71
+ #
72
+ def element_match(tree, exp, bindings)
73
+ # p [:elm, tree, exp]
74
+ case [tree, exp].map { |e| e.class }
75
+ when [Hash,Hash]
76
+ return element_match_hash(tree, exp, bindings)
77
+ when [Array,Array]
78
+ return element_match_ary_single(tree, exp, bindings)
79
+ else
80
+ # If elements match exactly, then that is good enough in all cases
81
+ return true if tree == exp
82
+
83
+ # If exp is a bind variable: Check if the binding matches
84
+ if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
85
+ return element_match_binding(tree, exp, bindings)
86
+ end
87
+
88
+ # Otherwise: No match (we don't know anything about the element
89
+ # combination)
90
+ return false
91
+ end
92
+ end
93
+
94
+ def element_match_binding(tree, exp, bindings)
95
+ var_name = exp.variable_name
96
+
97
+ # TODO test for the hidden :_ feature.
98
+ if var_name && bound_value = bindings[var_name]
99
+ return bound_value == tree
100
+ end
101
+
102
+ # New binding:
103
+ bindings.store var_name, tree
104
+
105
+ return true
106
+ end
107
+
108
+ def element_match_ary_single(sequence, exp, bindings)
109
+ return false if sequence.size != exp.size
110
+
111
+ return sequence.zip(exp).all? { |elt, subexp|
112
+ element_match(elt, subexp, bindings) }
113
+ end
114
+
115
+ def element_match_hash(tree, exp, bindings)
116
+ # For a hash to match, all keys must correspond and all values must
117
+ # match element wise.
118
+ tree.each do |tree_key,tree_value|
119
+ return nil unless exp.has_key?(tree_key)
120
+
121
+ # We know they both have tk as element.
122
+ exp_value = exp[tree_key]
123
+
124
+ # Recurse into the values
125
+ unless element_match(tree_value, exp_value, bindings)
126
+ # Stop matching early
127
+ return false
128
+ end
129
+ end
130
+
131
+ # Match succeeds
132
+ return true
133
+ end
134
+
135
+ # Called on a bind variable, returns the variable name without the _
136
+ #
137
+ def variable_name(bind_var)
138
+ str = bind_var.to_s
139
+
140
+ if str.size>1
141
+ str[1..-1].to_sym
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,40 @@
1
+
2
+ # Used internally for representing a bind placeholder in a Parslet::Transform
3
+ # pattern. This is the superclass for all bindings.
4
+ #
5
+ class Parslet::Pattern::Bind
6
+ attr_reader :symbol
7
+ def initialize(symbol)
8
+ @symbol = symbol
9
+ end
10
+
11
+ def variable_name
12
+ symbol
13
+ end
14
+ end
15
+
16
+ # Binds a symbol to a simple subtree, one that is not either a sequence of
17
+ # elements or a collection of attributes.
18
+ #
19
+ class Parslet::Pattern::SimpleBind < Parslet::Pattern::Bind
20
+ def inspect
21
+ "simple(#{symbol.inspect})"
22
+ end
23
+
24
+ def can_bind?(subtree)
25
+ not [Hash, Array].include?(subtree.class)
26
+ end
27
+ end
28
+
29
+ # Binds a symbol to a sequence of simple leafs ([element1, element2, ...])
30
+ #
31
+ class Parslet::Pattern::SequenceBind < Parslet::Pattern::Bind
32
+ def inspect
33
+ "sequence(#{symbol.inspect})"
34
+ end
35
+
36
+ def can_bind?(subtree)
37
+ subtree.kind_of?(Array) &&
38
+ (not subtree.any? { |el| [Hash, Array].include?(el.class) })
39
+ end
40
+ end
@@ -0,0 +1,118 @@
1
+
2
+ require 'parslet/pattern'
3
+
4
+ # Transforms an expression tree into something else. The transformation
5
+ # performs a depth-first, post-order traversal of the expression tree. During
6
+ # that traversal, each time a rule matches a node, the node is replaced by the
7
+ # result of the block associated to the rule. Otherwise the node is accepted
8
+ # as is into the result tree.
9
+ #
10
+ # This is almost what you would generally do with a tree visitor, except that
11
+ # you can match several levels of the tree at once.
12
+ #
13
+ # As a consequence of this, the resulting tree will contain pieces of the
14
+ # original tree and new pieces. Most likely, you will want to transform the
15
+ # original tree wholly, so this isn't a problem.
16
+ #
17
+ # You will not be able to create a loop, given that each node will be replaced
18
+ # only once and then left alone. This means that the results of a replacement
19
+ # will not be acted upon.
20
+ #
21
+ # Example:
22
+ #
23
+ # transform = Parslet::Transform.new
24
+ # transform.rule(
25
+ # :string => simple(:x) # (1)
26
+ # ) { |d|
27
+ # StringLiteral.new(d[:x]) # (2)
28
+ # }
29
+ #
30
+ # # Transforms the tree
31
+ # transform.apply(tree)
32
+ #
33
+ # A tree transform (Parslet::Transform) is defined by a set of rules. Each
34
+ # rule can be defined by calling #rule with the pattern as argument. The block
35
+ # given will be called every time the rule matches somewhere in the tree given
36
+ # to #apply. It is passed a Hash containing all the variable bindings of this
37
+ # pattern match.
38
+ #
39
+ # In the above example, (1) illustrates a simple matching rule. In general,
40
+ # such rules are composed of strings ("foobar"), arrays (["a", "b"]) and
41
+ # hashes like in the example above.
42
+ #
43
+ # Let's say you want to parse matching parentheses and distill a maximum
44
+ # nest depth. You would probably write a parser like the one in example/parens.rb;
45
+ # here's the relevant part:
46
+ #
47
+ # rule(:balanced) {
48
+ # str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
49
+ # }
50
+ #
51
+ # If you now apply this to a string like '(())', you get a intermediate
52
+ # parse tree that looks like this:
53
+ #
54
+ # {
55
+ # :l => "(",
56
+ # :m => {
57
+ # :l=>"(", :m=>nil, :r=>")" },
58
+ # :r => ")"
59
+ # }
60
+ #
61
+ # This parse tree is good for debugging, but what we would really like to have
62
+ # is just the nesting depth. This transformation rule will produce that:
63
+ #
64
+ # t.rule(:l => '(', :m => simple(:x), :r => ')') { |d|
65
+ # depth = d[:x]
66
+ #
67
+ # depth.nil? ? 1 : depth+1
68
+ # }
69
+ # t.apply(tree) # => 2
70
+ #
71
+ #
72
+ class Parslet::Transform
73
+ def initialize
74
+ @rules = []
75
+ end
76
+
77
+ attr_reader :rules
78
+ def rule(expression, &block)
79
+ @rules << [
80
+ Parslet::Pattern.new(expression),
81
+ block
82
+ ]
83
+ end
84
+
85
+ def apply(obj)
86
+ transform_elt(
87
+ case obj
88
+ when Hash
89
+ recurse_hash(obj)
90
+ when Array
91
+ recurse_array(obj)
92
+ else
93
+ obj
94
+ end
95
+ )
96
+ end
97
+
98
+ def transform_elt(elt)
99
+ rules.each do |pattern, block|
100
+ if bindings=pattern.match(elt)
101
+ # Produces transformed value
102
+ return block.call(bindings)
103
+ end
104
+ end
105
+
106
+ # No rule matched - element is not transformed
107
+ return elt
108
+ end
109
+ def recurse_hash(hsh)
110
+ hsh.inject({}) do |new_hsh, (k,v)|
111
+ new_hsh[k] = apply(v)
112
+ new_hsh
113
+ end
114
+ end
115
+ def recurse_array(ary)
116
+ ary.map { |elt| apply(elt) }
117
+ end
118
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parslet
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 0
9
+ version: 0.9.0
10
+ platform: ruby
11
+ authors:
12
+ - Kaspar Schiess
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-29 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: flexmock
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description:
47
+ email: kaspar.schiess@absurd.li
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - README
54
+ files:
55
+ - Gemfile
56
+ - HISTORY.txt
57
+ - LICENSE
58
+ - Rakefile
59
+ - README
60
+ - lib/parslet/atoms.rb
61
+ - lib/parslet/error_tree.rb
62
+ - lib/parslet/pattern/binding.rb
63
+ - lib/parslet/pattern.rb
64
+ - lib/parslet/transform.rb
65
+ - lib/parslet.rb
66
+ has_rdoc: true
67
+ homepage: http://kschiess.github.com/parslet
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --main
73
+ - README
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ requirements: []
93
+
94
+ rubyforge_project:
95
+ rubygems_version: 1.3.7
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Parser construction library with great error reporting in Ruby.
99
+ test_files: []
100
+