parslet 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/HISTORY.txt +21 -0
- data/LICENSE +23 -0
- data/README +101 -0
- data/Rakefile +73 -0
- data/lib/parslet.rb +301 -0
- data/lib/parslet/atoms.rb +492 -0
- data/lib/parslet/error_tree.rb +50 -0
- data/lib/parslet/pattern.rb +144 -0
- data/lib/parslet/pattern/binding.rb +40 -0
- data/lib/parslet/transform.rb +118 -0
- metadata +100 -0
@@ -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
|
+
|