parslet 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+