ghazel-parslet 1.4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +195 -0
- data/LICENSE +23 -0
- data/README +70 -0
- data/Rakefile +49 -0
- data/example/boolean_algebra.rb +70 -0
- data/example/calc.rb +153 -0
- data/example/comments.rb +35 -0
- data/example/deepest_errors.rb +131 -0
- data/example/documentation.rb +18 -0
- data/example/email_parser.rb +52 -0
- data/example/empty.rb +13 -0
- data/example/erb.rb +47 -0
- data/example/ignore.rb +33 -0
- data/example/ip_address.rb +125 -0
- data/example/json.rb +128 -0
- data/example/local.rb +34 -0
- data/example/mathn.rb +44 -0
- data/example/minilisp.rb +94 -0
- data/example/modularity.rb +47 -0
- data/example/nested_errors.rb +132 -0
- data/example/output/boolean_algebra.out +4 -0
- data/example/output/calc.out +1 -0
- data/example/output/comments.out +8 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/documentation.err +4 -0
- data/example/output/documentation.out +1 -0
- data/example/output/email_parser.out +2 -0
- data/example/output/empty.err +1 -0
- data/example/output/erb.out +7 -0
- data/example/output/ignore.out +1 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +9 -0
- data/example/output/json.out +5 -0
- data/example/output/local.out +3 -0
- data/example/output/mathn.out +4 -0
- data/example/output/minilisp.out +5 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/example/output/parens.out +8 -0
- data/example/output/readme.out +1 -0
- data/example/output/seasons.out +28 -0
- data/example/output/sentence.out +1 -0
- data/example/output/simple_xml.out +2 -0
- data/example/output/string_parser.out +3 -0
- data/example/parens.rb +42 -0
- data/example/readme.rb +30 -0
- data/example/seasons.rb +46 -0
- data/example/sentence.rb +36 -0
- data/example/simple.lit +3 -0
- data/example/simple_xml.rb +54 -0
- data/example/string_parser.rb +77 -0
- data/example/test.lit +4 -0
- data/lib/parslet.rb +254 -0
- data/lib/parslet/atoms.rb +32 -0
- data/lib/parslet/atoms/alternative.rb +50 -0
- data/lib/parslet/atoms/base.rb +124 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/context.rb +94 -0
- data/lib/parslet/atoms/dsl.rb +98 -0
- data/lib/parslet/atoms/entity.rb +41 -0
- data/lib/parslet/atoms/lookahead.rb +49 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +38 -0
- data/lib/parslet/atoms/repetition.rb +63 -0
- data/lib/parslet/atoms/rule.rb +12 -0
- data/lib/parslet/atoms/rule/position.rb +143 -0
- data/lib/parslet/atoms/sequence.rb +38 -0
- data/lib/parslet/atoms/str.rb +37 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/cause.rb +94 -0
- data/lib/parslet/convenience.rb +35 -0
- data/lib/parslet/error_reporter.rb +7 -0
- data/lib/parslet/error_reporter/deepest.rb +95 -0
- data/lib/parslet/error_reporter/tree.rb +57 -0
- data/lib/parslet/export.rb +162 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/parser.rb +67 -0
- data/lib/parslet/pattern.rb +114 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/rig/rspec.rb +51 -0
- data/lib/parslet/slice.rb +101 -0
- data/lib/parslet/source.rb +62 -0
- data/lib/parslet/source/line_cache.rb +95 -0
- data/lib/parslet/transform.rb +236 -0
- data/lib/parslet/transform/context.rb +32 -0
- metadata +264 -0
data/HISTORY.txt
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
= 2.0 / ?? (future release changes, like a reminder to self)
|
2
|
+
|
3
|
+
- prsnt? and absnt? are now finally banned into oblivion. Wasting vocals for
|
4
|
+
the win.
|
5
|
+
|
6
|
+
= 1.4.0 / 25May2012
|
7
|
+
|
8
|
+
+ Revised documentation. A few new API features have finally made it into
|
9
|
+
the documentation. Examples in the documentation are now curated and
|
10
|
+
run against the current code so that they really really work.
|
11
|
+
Also, the website generation tools have been replaced with 2012-style
|
12
|
+
tools. Much less pain to update now.
|
13
|
+
|
14
|
+
+ Parslet::Source now doesn't hold a StringIO, it directly holds the
|
15
|
+
buffer to be parsed. The api of Source has changed a tiny bit. This change
|
16
|
+
has been made for speed optimisation reasons.
|
17
|
+
|
18
|
+
+ :reporter argument to parse, allowing to customize error reporting within
|
19
|
+
wide boundaries. See issue #64 for a discussion.
|
20
|
+
Included are two error reporters, one (default) with the existing error
|
21
|
+
tree functionality, one reporting deepest errors as defined by the above
|
22
|
+
ticket.
|
23
|
+
|
24
|
+
+ Optimistic parse: Parsing is two phase, with the first phase assuming
|
25
|
+
there will be no errors. This yields ~ 20% speed improvement in the
|
26
|
+
case where the parse succeeds.
|
27
|
+
Also, internal error handling is now using tuples. This and other
|
28
|
+
optimizations have yielded ~ 30% overall improvement.
|
29
|
+
|
30
|
+
! #error_tree and #cause removed from all of parslet. The
|
31
|
+
Parslet::ParseFailed exception now contains a #cause field that can
|
32
|
+
be asked for an #ascii_tree as before.
|
33
|
+
Cleaner internal error handling, not stateful in atoms anymore. Some
|
34
|
+
parsers will see correct error reporting for the first time. (issue #65)
|
35
|
+
|
36
|
+
+ Made it possible to pass a custom Parslet::Source implementor to #parse.
|
37
|
+
(see #63)
|
38
|
+
|
39
|
+
+ #parse has now a second argument that is an options hash. See
|
40
|
+
Parslet::Atoms::Base#parse for documentation.
|
41
|
+
|
42
|
+
- VM engine on the way out. No benefit except for the intellectual
|
43
|
+
challenge.
|
44
|
+
|
45
|
+
= 1.3.0 / 5Mar2012
|
46
|
+
|
47
|
+
! Parslet::Transform::Context is now much more well-behaved. It has
|
48
|
+
#respond_to? and #method_missing; it now looks like a plain old Ruby
|
49
|
+
object with instance variables and attribute readers.
|
50
|
+
|
51
|
+
- Grammar transforms turned out to be a dead end and have been removed.
|
52
|
+
|
53
|
+
! A few problems in error message generation have been fixed. This will
|
54
|
+
improve diagnostics further.
|
55
|
+
|
56
|
+
+ A VM driven parser engine: Removes the limitation that parsing needs a
|
57
|
+
lot of stack space, something dearly missing from Ruby 1.9.3 fibers.
|
58
|
+
This engine is experimental and might be removed in the future.
|
59
|
+
|
60
|
+
! Interaction with mathn fixed - Line number generation will terminate.
|
61
|
+
|
62
|
+
. Internal reorganisation, removing cruft and bit rot.
|
63
|
+
|
64
|
+
= 1.2.3 / 22Sep2011
|
65
|
+
|
66
|
+
+ Transform#apply can now be called with a hash as second argument. This
|
67
|
+
provides bindings and a way to inject context.
|
68
|
+
|
69
|
+
! Fixes a bug thar modified parslet atoms in place, defeating oop chaining.
|
70
|
+
(#50)
|
71
|
+
|
72
|
+
= 1.2.1 / 6Jun2011
|
73
|
+
|
74
|
+
! FIX: Input at the end of a parse raises Parslet::UnconsumedInput. (see
|
75
|
+
issue 18)
|
76
|
+
|
77
|
+
! FIX: Unicode parsing should now work as expected. (see issue 38)
|
78
|
+
|
79
|
+
! FIX: Slice#slice returned wrong bits at times (see issue 36).
|
80
|
+
|
81
|
+
= 1.2.0 / 4Feb2011
|
82
|
+
|
83
|
+
+ Parslet::Parser is now also a grammar atom, it can be composed freely with
|
84
|
+
other atoms. (str('f') >> MiniLispParser.new >> str('b'))
|
85
|
+
|
86
|
+
+ No strings, only slices are returned as part of the parser result.
|
87
|
+
Parslet::Slice is almost a string class, but one that remembers the
|
88
|
+
source offset. This has also bought us a slight speedup.
|
89
|
+
|
90
|
+
+ require 'parslet/convenience' now brings #parse_with_debug to all parslets.
|
91
|
+
This is a consequence of the above change.
|
92
|
+
|
93
|
+
+ Deprecates prsnt? and absnt? in favor of the more readable absent? and
|
94
|
+
prsnt?. Uses 3 bytes more RAM. The old variants will exist until we release
|
95
|
+
2.0.
|
96
|
+
|
97
|
+
INTERNALLY
|
98
|
+
|
99
|
+
+ Visitors now should have methods that all begin with 'visit_*'. #str
|
100
|
+
becomes #visit_str.
|
101
|
+
|
102
|
+
+ Parslet::Atoms::Entity now takes only a block argument instead of context
|
103
|
+
and block.
|
104
|
+
|
105
|
+
= 1.1.1 / 4Feb2011
|
106
|
+
|
107
|
+
! FIX: Line counting was broken by performance optimisations.
|
108
|
+
|
109
|
+
+ Squeezed out another few drops of performance.
|
110
|
+
|
111
|
+
= 1.1.0 / 2Feb2011
|
112
|
+
|
113
|
+
+ Uses return (fail/success), cached line counts, memoizing of parse results
|
114
|
+
and other tricks internally for at least an order of magnitude increase
|
115
|
+
in execution speed.
|
116
|
+
|
117
|
+
+ str('foo').maybe will now return an empty string again. Use .as(...) to
|
118
|
+
name things and get back [] from #repeat and nil from #maybe.
|
119
|
+
|
120
|
+
+ If you require 'parslet/atoms/visitor', you'll get an accept method on
|
121
|
+
all known Parslet::Atoms.
|
122
|
+
|
123
|
+
+ If you require 'parslet/export', you can call #to_citrus and #to_treetop
|
124
|
+
to produce string versions of your grammar in those dialects.
|
125
|
+
|
126
|
+
+ Requiring 'parslet/convenience' will given you a parse_with_debug on
|
127
|
+
your Parslet::Parser class. This prints some diagnostics on parse failure.
|
128
|
+
(Thanks to Florian Hanke)
|
129
|
+
|
130
|
+
= 1.0.1 / 17Jan2011
|
131
|
+
|
132
|
+
A happy new year!
|
133
|
+
|
134
|
+
! FIX: Parslet::Transform was wrongly fixed earlier - it now wont mangle
|
135
|
+
hashes anymore. (Blake Sweeney)
|
136
|
+
|
137
|
+
+ parslet/rig/rspec.rb contains useful rspec matchers. (R. Konstantin Haase)
|
138
|
+
|
139
|
+
= 1.0.0 / 29Dez2010
|
140
|
+
|
141
|
+
- #each_match was removed. There was some duplication of code that even
|
142
|
+
confused me - and we should not have 2 methods of achieving the same
|
143
|
+
goal.
|
144
|
+
|
145
|
+
+ Full documentation. Fixed sdoc.
|
146
|
+
|
147
|
+
= 0.11.0 / 25Nov2010
|
148
|
+
|
149
|
+
! Bugfixes to tree handling. Let's hope that was the last such significant
|
150
|
+
change to the core.
|
151
|
+
|
152
|
+
= 0.10.1 / 22Nov2010
|
153
|
+
|
154
|
+
+ Allow match['a-z'], shortcut for match('[a-z]')
|
155
|
+
|
156
|
+
! Fixed output inconsistencies (behaviour in connection to 'maybe')
|
157
|
+
|
158
|
+
= 0.10.0 / 22Nov2010
|
159
|
+
|
160
|
+
+ Parslet::Transform now takes a block on initialisation, wherein you can
|
161
|
+
define all the rules directly.
|
162
|
+
|
163
|
+
+ Parslet::Transform now only passes a hash to the block during transform
|
164
|
+
when its arity is 1. Otherwise all hash contents as bound as local
|
165
|
+
variables.
|
166
|
+
|
167
|
+
+ Both inline and other documentation have been improved.
|
168
|
+
|
169
|
+
+ You can now use 'subtree(:x)' to bind any subtree to x during tree pattern
|
170
|
+
matching.
|
171
|
+
|
172
|
+
+ Transform classes can now include rules into class definition. This makes
|
173
|
+
Parser and Transformer behave the same.
|
174
|
+
|
175
|
+
= 0.9.0 / 28Oct2010
|
176
|
+
* More of everything: Examples, documentation, etc...
|
177
|
+
|
178
|
+
* Breaking change: Ruby's binary or ('|') is now used for alternatives,
|
179
|
+
instead of the division sign ('/') - this reduces the amount of
|
180
|
+
parenthesis needed for a grammar overall.
|
181
|
+
|
182
|
+
* parslet.maybe now yields the result or nil in case of parse failure. This
|
183
|
+
is probably better than the array it did before; the jury is still out on
|
184
|
+
that.
|
185
|
+
|
186
|
+
* parslet.repeat(min, max) is now valid syntax
|
187
|
+
|
188
|
+
= 0.1.0 / not released.
|
189
|
+
|
190
|
+
* Initial version. Classes for parsing, matching in the resulting trees
|
191
|
+
and transforming the trees into something more useful.
|
192
|
+
|
193
|
+
* Parses and outputs intermediary trees
|
194
|
+
|
195
|
+
* Matching of single elements and sequences
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2010 Kaspar Schiess
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person
|
5
|
+
obtaining a copy of this software and associated documentation
|
6
|
+
files (the "Software"), to deal in the Software without
|
7
|
+
restriction, including without limitation the rights to use,
|
8
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following
|
11
|
+
conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
INTRODUCTION
|
2
|
+
|
3
|
+
Parslet makes developing complex parsers easy. It does so by
|
4
|
+
|
5
|
+
* providing the best error reporting possible
|
6
|
+
* not generating reams of code for you to debug
|
7
|
+
|
8
|
+
Parslet takes the long way around to make your job easier. It allows for
|
9
|
+
incremental language construction. Often, you start out small, implementing
|
10
|
+
the atoms of your language first; _parslet_ takes pride in making this
|
11
|
+
possible.
|
12
|
+
|
13
|
+
Eager to try this out? Please see the associated web site:
|
14
|
+
http://kschiess.github.com/parslet
|
15
|
+
|
16
|
+
SYNOPSIS
|
17
|
+
|
18
|
+
require 'parslet'
|
19
|
+
include Parslet
|
20
|
+
|
21
|
+
# parslet parses strings
|
22
|
+
str('foo').
|
23
|
+
parse('foo') # => "foo"@0
|
24
|
+
|
25
|
+
# it matches character sets
|
26
|
+
match['abc'].parse('a') # => "a"@0
|
27
|
+
match['abc'].parse('b') # => "b"@0
|
28
|
+
match['abc'].parse('c') # => "c"@0
|
29
|
+
|
30
|
+
# and it annotates its output
|
31
|
+
str('foo').as(:important_bit).
|
32
|
+
parse('foo') # => {:important_bit=>"foo"@0}
|
33
|
+
|
34
|
+
# you can construct parsers with just a few lines
|
35
|
+
quote = str('"')
|
36
|
+
simple_string = quote >> (quote.absent? >> any).repeat >> quote
|
37
|
+
|
38
|
+
simple_string.
|
39
|
+
parse('"Simple Simple Simple"') # => "\"Simple Simple Simple\""@0
|
40
|
+
|
41
|
+
# or by making a fuss about it
|
42
|
+
class Smalltalk < Parslet::Parser
|
43
|
+
root :smalltalk
|
44
|
+
|
45
|
+
rule(:smalltalk) { statements }
|
46
|
+
rule(:statements) {
|
47
|
+
# insert smalltalk parser here (outside of the scope of this readme)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# and then
|
52
|
+
Smalltalk.new.parse('smalltalk')
|
53
|
+
|
54
|
+
COMPATIBILITY
|
55
|
+
|
56
|
+
This library should work with most rubies. I've tested it with MRI 1.8
|
57
|
+
(except 1.8.6), 1.9, rbx-head, jruby. Please report as a bug if you encounter
|
58
|
+
issues.
|
59
|
+
|
60
|
+
Note that due to Ruby 1.8 internals, Unicode parsing is not supported on that
|
61
|
+
version.
|
62
|
+
|
63
|
+
On Mac OS X Lion, ruby-1.8.7-p352 has been known to segfault. Use
|
64
|
+
ruby-1.8.7-p334 for better results.
|
65
|
+
|
66
|
+
STATUS
|
67
|
+
|
68
|
+
At version 1.4.0 - See HISTORY.txt for changes.
|
69
|
+
|
70
|
+
(c) 2010, 2011, 2012 Kaspar Schiess
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rdoc/task'
|
2
|
+
require 'sdoc'
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require "rubygems/package_task"
|
6
|
+
|
7
|
+
desc "Run all tests: Exhaustive."
|
8
|
+
RSpec::Core::RakeTask.new
|
9
|
+
|
10
|
+
namespace :spec do
|
11
|
+
desc "Only run unit tests: Fast. "
|
12
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
13
|
+
task.pattern = "spec/parslet/**/*_spec.rb"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => :spec
|
18
|
+
|
19
|
+
# Generate documentation
|
20
|
+
RDoc::Task.new do |rdoc|
|
21
|
+
rdoc.title = "parslet - construction of parsers made easy"
|
22
|
+
rdoc.options << '--line-numbers'
|
23
|
+
rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
|
24
|
+
rdoc.template = 'direct' # lighter template used on railsapi.com
|
25
|
+
rdoc.main = "README"
|
26
|
+
rdoc.rdoc_files.include("README", "lib/**/*.rb")
|
27
|
+
rdoc.rdoc_dir = "rdoc"
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Clear out RDoc'
|
31
|
+
task :clean => [:clobber_rdoc, :clobber_package]
|
32
|
+
|
33
|
+
# This task actually builds the gem.
|
34
|
+
task :gem => :spec
|
35
|
+
spec = eval(File.read('parslet.gemspec'))
|
36
|
+
|
37
|
+
desc "Generate the gem package."
|
38
|
+
Gem::PackageTask.new(spec) do |pkg|
|
39
|
+
pkg.gem_spec = spec
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Prints LOC stats"
|
43
|
+
task :stat do
|
44
|
+
%w(lib spec example).each do |dir|
|
45
|
+
loc = %x(find #{dir} -name "*.rb" | xargs wc -l | grep 'total').split.first.to_i
|
46
|
+
printf("%20s %d\n", dir, loc)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
2
|
+
|
3
|
+
require "parslet"
|
4
|
+
require "pp"
|
5
|
+
|
6
|
+
# Parses strings like "var1 and (var2 or var3)" respecting operator precedence
|
7
|
+
# and parentheses. After that transforms the parse tree into an array of
|
8
|
+
# arrays like this:
|
9
|
+
#
|
10
|
+
# [["1", "2"], ["1", "3"]]
|
11
|
+
#
|
12
|
+
# The array represents a DNF (disjunctive normal form). Elements of outer
|
13
|
+
# array are connected with "or" operator, while elements of inner arrays are
|
14
|
+
# joined with "and".
|
15
|
+
#
|
16
|
+
class Parser < Parslet::Parser
|
17
|
+
rule(:space) { match[" "].repeat(1) }
|
18
|
+
rule(:space?) { space.maybe }
|
19
|
+
|
20
|
+
rule(:lparen) { str("(") >> space? }
|
21
|
+
rule(:rparen) { str(")") >> space? }
|
22
|
+
|
23
|
+
rule(:and_operator) { str("and") >> space? }
|
24
|
+
rule(:or_operator) { str("or") >> space? }
|
25
|
+
|
26
|
+
rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? }
|
27
|
+
|
28
|
+
# The primary rule deals with parentheses.
|
29
|
+
rule(:primary) { lparen >> or_operation >> rparen | var }
|
30
|
+
|
31
|
+
# Note that following rules are both right-recursive.
|
32
|
+
rule(:and_operation) {
|
33
|
+
(primary.as(:left) >> and_operator >>
|
34
|
+
and_operation.as(:right)).as(:and) |
|
35
|
+
primary }
|
36
|
+
|
37
|
+
rule(:or_operation) {
|
38
|
+
(and_operation.as(:left) >> or_operator >>
|
39
|
+
or_operation.as(:right)).as(:or) |
|
40
|
+
and_operation }
|
41
|
+
|
42
|
+
# We start at the lowest precedence rule.
|
43
|
+
root(:or_operation)
|
44
|
+
end
|
45
|
+
|
46
|
+
class Transformer < Parslet::Transform
|
47
|
+
rule(:var => simple(:var)) { [[String(var)]] }
|
48
|
+
|
49
|
+
rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do
|
50
|
+
(left + right)
|
51
|
+
end
|
52
|
+
|
53
|
+
rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do
|
54
|
+
res = []
|
55
|
+
left.each do |l|
|
56
|
+
right.each do |r|
|
57
|
+
res << (l + r)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
res
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
pp tree = Parser.new.parse("var1 and (var2 or var3)")
|
65
|
+
# {:and=>
|
66
|
+
# {:left=>{:var=>"1"@3},
|
67
|
+
# :right=>{:or=>{:left=>{:var=>"2"@13}, :right=>{:var=>"3"@21}}}}}
|
68
|
+
pp Transformer.new.apply(tree)
|
69
|
+
# [["1", "2"], ["1", "3"]]
|
70
|
+
|
data/example/calc.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# A simple integer calculator to answer the question about how to do
|
2
|
+
# left and right associativity in parslet (PEG) once and for all.
|
3
|
+
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'parslet'
|
8
|
+
require 'parslet/rig/rspec'
|
9
|
+
|
10
|
+
# This is the parsing stage. It expresses left associativity by compiling
|
11
|
+
# list of things that have the same associativity.
|
12
|
+
class CalcParser < Parslet::Parser
|
13
|
+
root :addition
|
14
|
+
|
15
|
+
rule(:addition) {
|
16
|
+
multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) |
|
17
|
+
multiplication
|
18
|
+
}
|
19
|
+
|
20
|
+
rule(:multiplication) {
|
21
|
+
integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) |
|
22
|
+
integer }
|
23
|
+
|
24
|
+
rule(:integer) { digit.repeat(1).as(:i) >> space? }
|
25
|
+
|
26
|
+
rule(:mult_op) { match['*/'].as(:o) >> space? }
|
27
|
+
rule(:add_op) { match['+-'].as(:o) >> space? }
|
28
|
+
|
29
|
+
rule(:digit) { match['0-9'] }
|
30
|
+
rule(:space?) { match['\s'].repeat }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Classes for the abstract syntax tree.
|
34
|
+
Int = Struct.new(:int) {
|
35
|
+
def eval; self end
|
36
|
+
def op(operation, other)
|
37
|
+
left = int
|
38
|
+
right = other.int
|
39
|
+
|
40
|
+
Int.new(
|
41
|
+
case operation
|
42
|
+
when '+'
|
43
|
+
left + right
|
44
|
+
when '-'
|
45
|
+
left - right
|
46
|
+
when '*'
|
47
|
+
left * right
|
48
|
+
when '/'
|
49
|
+
left / right
|
50
|
+
end)
|
51
|
+
end
|
52
|
+
def to_i
|
53
|
+
int
|
54
|
+
end
|
55
|
+
}
|
56
|
+
Seq = Struct.new(:sequence) {
|
57
|
+
def eval
|
58
|
+
sequence.reduce { |accum, operation|
|
59
|
+
operation.call(accum) }
|
60
|
+
end
|
61
|
+
}
|
62
|
+
LeftOp = Struct.new(:operation, :right) {
|
63
|
+
def call(left)
|
64
|
+
left = left.eval
|
65
|
+
right = self.right.eval
|
66
|
+
|
67
|
+
left.op(operation, right)
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
# Transforming intermediary syntax tree into a real AST.
|
72
|
+
class CalcTransform < Parslet::Transform
|
73
|
+
rule(i: simple(:i)) { Int.new(Integer(i)) }
|
74
|
+
rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) }
|
75
|
+
rule(l: simple(:i)) { i }
|
76
|
+
rule(sequence(:seq)) { Seq.new(seq) }
|
77
|
+
end
|
78
|
+
|
79
|
+
# And this calls everything in the right order.
|
80
|
+
def calculate(str)
|
81
|
+
intermediary_tree = CalcParser.new.parse(str)
|
82
|
+
abstract_tree = CalcTransform.new.apply(intermediary_tree)
|
83
|
+
result = abstract_tree.eval
|
84
|
+
|
85
|
+
result.to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
# A test suite for the above parser
|
89
|
+
describe CalcParser do
|
90
|
+
let(:p) { described_class.new }
|
91
|
+
describe '#integer' do
|
92
|
+
let(:i) { p.integer }
|
93
|
+
it "parses integers" do
|
94
|
+
i.should parse('1')
|
95
|
+
i.should parse('123')
|
96
|
+
end
|
97
|
+
it "consumes trailing white space" do
|
98
|
+
i.should parse('123 ')
|
99
|
+
end
|
100
|
+
it "doesn't parse floats" do
|
101
|
+
i.should_not parse('1.3')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
describe '#multiplication' do
|
105
|
+
let(:m) { p.multiplication }
|
106
|
+
it "parses simple multiplication" do
|
107
|
+
m.should parse('1*2')
|
108
|
+
end
|
109
|
+
it "parses division" do
|
110
|
+
m.should parse('1/2')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
describe '#addition' do
|
114
|
+
let(:a) { p.addition }
|
115
|
+
|
116
|
+
it "parses simple addition" do
|
117
|
+
a.should parse('1+2')
|
118
|
+
a.should parse('1+2+3-4')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
describe CalcTransform do
|
123
|
+
def t(obj)
|
124
|
+
described_class.new.apply(obj)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "transforms integers" do
|
128
|
+
t(i: '1').should == Int.new(1)
|
129
|
+
end
|
130
|
+
it "unwraps left operand" do
|
131
|
+
t(l: :obj).should == :obj
|
132
|
+
end
|
133
|
+
end
|
134
|
+
describe 'whole computation specs' do
|
135
|
+
def self.result_of(str, int)
|
136
|
+
it(str) { calculate(str).should == int }
|
137
|
+
end
|
138
|
+
|
139
|
+
result_of '1+1', 2
|
140
|
+
result_of '1-1-1', -1
|
141
|
+
result_of '1+1+3*5/2', 9
|
142
|
+
result_of '123*2', 246
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Enable these if you want to change the code.
|
147
|
+
# RSpec::Core::Runner.run([], $stderr, $stdout)
|
148
|
+
|
149
|
+
str = ARGV.join
|
150
|
+
str = '123*2' if str.match(/^\s*$/)
|
151
|
+
|
152
|
+
print "#{str} (command line): -> "
|
153
|
+
puts calculate(str)
|