ghazel-parslet 1.4.0.1
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.
- 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)
|