rltk 1.1.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.
- data/AUTHORS +1 -0
- data/LICENSE +27 -0
- data/README +386 -0
- data/Rakefile +67 -0
- data/lib/rltk/ast.rb +264 -0
- data/lib/rltk/cfg.rb +491 -0
- data/lib/rltk/lexer.rb +298 -0
- data/lib/rltk/lexers/calculator.rb +41 -0
- data/lib/rltk/lexers/ebnf.rb +40 -0
- data/lib/rltk/parser.rb +1354 -0
- data/lib/rltk/parsers/infix_calc.rb +43 -0
- data/lib/rltk/parsers/postfix_calc.rb +34 -0
- data/lib/rltk/parsers/prefix_calc.rb +34 -0
- data/lib/rltk/token.rb +66 -0
- data/test/tc_ast.rb +85 -0
- data/test/tc_cfg.rb +149 -0
- data/test/tc_lexer.rb +217 -0
- data/test/tc_parser.rb +275 -0
- data/test/tc_token.rb +34 -0
- metadata +87 -0
data/test/tc_parser.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Ruby Language Toolkit
|
3
|
+
# Date: 2011/04/06
|
4
|
+
# Description: This file contains unit tests for the RLTK::Parser class.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
require 'pp'
|
12
|
+
require 'test/unit'
|
13
|
+
|
14
|
+
# Ruby Language Toolkit
|
15
|
+
require 'rltk/lexer'
|
16
|
+
require 'rltk/parser'
|
17
|
+
require 'rltk/lexers/calculator'
|
18
|
+
require 'rltk/parsers/prefix_calc'
|
19
|
+
require 'rltk/parsers/infix_calc'
|
20
|
+
require 'rltk/parsers/postfix_calc'
|
21
|
+
|
22
|
+
#######################
|
23
|
+
# Classes and Modules #
|
24
|
+
#######################
|
25
|
+
|
26
|
+
class ABLexer < RLTK::Lexer
|
27
|
+
rule(/a/) { [:A, 1] }
|
28
|
+
rule(/b/) { [:B, 2] }
|
29
|
+
|
30
|
+
rule(/\s/)
|
31
|
+
end
|
32
|
+
|
33
|
+
class APlusBParser < RLTK::Parser
|
34
|
+
production(:a, 'A+ B') { |a, _| a.length }
|
35
|
+
|
36
|
+
finalize
|
37
|
+
end
|
38
|
+
|
39
|
+
class AQuestionBParser < RLTK::Parser
|
40
|
+
production(:a, 'A? B') { |a, _| a }
|
41
|
+
|
42
|
+
finalize
|
43
|
+
end
|
44
|
+
|
45
|
+
class AStarBParser < RLTK::Parser
|
46
|
+
production(:a, 'A* B') { |a, _| a.length }
|
47
|
+
|
48
|
+
finalize
|
49
|
+
end
|
50
|
+
|
51
|
+
class AmbiguousParser < RLTK::Parser
|
52
|
+
production(:e) do
|
53
|
+
clause('NUM') {|n| n}
|
54
|
+
|
55
|
+
clause('e PLS e') { |e0, _, e1| e0 + e1 }
|
56
|
+
clause('e SUB e') { |e0, _, e1| e0 - e1 }
|
57
|
+
clause('e MUL e') { |e0, _, e1| e0 * e1 }
|
58
|
+
clause('e DIV e') { |e0, _, e1| e0 / e1 }
|
59
|
+
end
|
60
|
+
|
61
|
+
finalize
|
62
|
+
end
|
63
|
+
|
64
|
+
class ArrayCalc < RLTK::Parser
|
65
|
+
array_args
|
66
|
+
|
67
|
+
production(:e) do
|
68
|
+
clause('NUM') { |v| v[0] }
|
69
|
+
|
70
|
+
clause('PLS e e') { |v| v[1] + v[2] }
|
71
|
+
clause('SUB e e') { |v| v[1] - v[2] }
|
72
|
+
clause('MUL e e') { |v| v[1] * v[2] }
|
73
|
+
clause('DIV e e') { |v| v[1] / v[2] }
|
74
|
+
end
|
75
|
+
|
76
|
+
finalize
|
77
|
+
end
|
78
|
+
|
79
|
+
class DummyError1 < Exception; end
|
80
|
+
class DummyError2 < Exception; end
|
81
|
+
|
82
|
+
class ErrorCalc < RLTK::Parser
|
83
|
+
production(:e) do
|
84
|
+
clause('NUM') {|n| n}
|
85
|
+
|
86
|
+
clause('e PLS e') { |e0, _, e1| e0 + e1 }
|
87
|
+
clause('e SUB e') { |e0, _, e1| e0 - e1 }
|
88
|
+
clause('e MUL e') { |e0, _, e1| e0 * e1 }
|
89
|
+
clause('e DIV e') { |e0, _, e1| e0 / e1 }
|
90
|
+
|
91
|
+
clause('e PLS ERROR') { |_, _, _| raise DummyError1 }
|
92
|
+
clause('e SUB ERROR') { |_, _, _| raise DummyError2 }
|
93
|
+
end
|
94
|
+
|
95
|
+
finalize
|
96
|
+
end
|
97
|
+
|
98
|
+
class ELLexer < RLTK::Lexer
|
99
|
+
rule(/\n/) { :NEWLINE }
|
100
|
+
rule(/;/) { :SEMI }
|
101
|
+
|
102
|
+
rule(/\s/)
|
103
|
+
|
104
|
+
rule(/[A-Za-z]+/) { |t| [:WORD, t] }
|
105
|
+
end
|
106
|
+
|
107
|
+
class ErrorLine < RLTK::Parser
|
108
|
+
|
109
|
+
production(:s, 'line*') { |l| l }
|
110
|
+
|
111
|
+
production(:line) do
|
112
|
+
clause('NEWLINE') { |_| nil }
|
113
|
+
|
114
|
+
clause('WORD+ SEMI NEWLINE') { |w, _, _| w }
|
115
|
+
clause('WORD+ ERROR NEWLINE') { |w, e, _| error(pos(1).line_number); w }
|
116
|
+
end
|
117
|
+
|
118
|
+
finalize
|
119
|
+
end
|
120
|
+
|
121
|
+
class RotatingCalc < RLTK::Parser
|
122
|
+
production(:e) do
|
123
|
+
clause('NUM') {|n| n}
|
124
|
+
|
125
|
+
clause('PLS e e') { |_, e0, e1| e0.send(get_op(:+), e1) }
|
126
|
+
clause('SUB e e') { |_, e0, e1| e0.send(get_op(:-), e1) }
|
127
|
+
clause('MUL e e') { |_, e0, e1| e0.send(get_op(:*), e1) }
|
128
|
+
clause('DIV e e') { |_, e0, e1| e0.send(get_op(:/), e1) }
|
129
|
+
end
|
130
|
+
|
131
|
+
class Environment < Environment
|
132
|
+
def initialize
|
133
|
+
@map = { :+ => 0, :- => 1, :* => 2, :/ => 3 }
|
134
|
+
@ops = [ :+, :-, :*, :/ ]
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_op(orig_op)
|
138
|
+
new_op = @ops[@map[orig_op]]
|
139
|
+
|
140
|
+
@ops = @ops[1..-1] << @ops[0]
|
141
|
+
|
142
|
+
new_op
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
finalize
|
147
|
+
end
|
148
|
+
|
149
|
+
class ParserTester < Test::Unit::TestCase
|
150
|
+
def test_ambiguous_grammar
|
151
|
+
actual = AmbiguousParser.parse(RLTK::Lexers::Calculator.lex('1 + 2 * 3'), {:accept => :all})
|
152
|
+
assert_equal([7, 9], actual.sort)
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_array_args
|
156
|
+
actual = ArrayCalc.parse(RLTK::Lexers::Calculator.lex('+ 1 2'))
|
157
|
+
assert_equal(3, actual)
|
158
|
+
|
159
|
+
actual = ArrayCalc.parse(RLTK::Lexers::Calculator.lex('+ 1 * 2 3'))
|
160
|
+
assert_equal(7, actual)
|
161
|
+
|
162
|
+
actual = ArrayCalc.parse(RLTK::Lexers::Calculator.lex('* + 1 2 3'))
|
163
|
+
assert_equal(9, actual)
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_ebnf_parsing
|
167
|
+
################
|
168
|
+
# APlusBParser #
|
169
|
+
################
|
170
|
+
|
171
|
+
assert_raise(RLTK::NotInLanguage) { APlusBParser.parse(ABLexer.lex('b')) }
|
172
|
+
assert_equal(1, APlusBParser.parse(ABLexer.lex('ab')))
|
173
|
+
assert_equal(2, APlusBParser.parse(ABLexer.lex('aab')))
|
174
|
+
assert_equal(3, APlusBParser.parse(ABLexer.lex('aaab')))
|
175
|
+
assert_equal(4, APlusBParser.parse(ABLexer.lex('aaaab')))
|
176
|
+
|
177
|
+
####################
|
178
|
+
# AQuestionBParser #
|
179
|
+
####################
|
180
|
+
|
181
|
+
assert_raise(RLTK::NotInLanguage) { AQuestionBParser.parse(ABLexer.lex('aab')) }
|
182
|
+
assert_nil(AQuestionBParser.parse(ABLexer.lex('b')))
|
183
|
+
assert_not_nil(AQuestionBParser.parse(ABLexer.lex('ab')))
|
184
|
+
|
185
|
+
################
|
186
|
+
# AStarBParser #
|
187
|
+
################
|
188
|
+
|
189
|
+
assert_equal(0, AStarBParser.parse(ABLexer.lex('b')))
|
190
|
+
assert_equal(1, AStarBParser.parse(ABLexer.lex('ab')))
|
191
|
+
assert_equal(2, AStarBParser.parse(ABLexer.lex('aab')))
|
192
|
+
assert_equal(3, AStarBParser.parse(ABLexer.lex('aaab')))
|
193
|
+
assert_equal(4, AStarBParser.parse(ABLexer.lex('aaaab')))
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_environment
|
197
|
+
actual = RotatingCalc.parse(RLTK::Lexers::Calculator.lex('+ 1 2'))
|
198
|
+
assert_equal(3, actual)
|
199
|
+
|
200
|
+
actual = RotatingCalc.parse(RLTK::Lexers::Calculator.lex('/ 1 * 2 3'))
|
201
|
+
assert_equal(7, actual)
|
202
|
+
|
203
|
+
actual = RotatingCalc.parse(RLTK::Lexers::Calculator.lex('- + 1 2 3'))
|
204
|
+
assert_equal(9, actual)
|
205
|
+
|
206
|
+
parser = RotatingCalc.new
|
207
|
+
|
208
|
+
actual = parser.parse(RLTK::Lexers::Calculator.lex('+ 1 2'))
|
209
|
+
assert_equal(3, actual)
|
210
|
+
|
211
|
+
actual = parser.parse(RLTK::Lexers::Calculator.lex('/ 1 2'))
|
212
|
+
assert_equal(3, actual)
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_error_productions
|
216
|
+
assert_raise(DummyError1) { ErrorCalc.parse(RLTK::Lexers::Calculator.lex('1 + +')) }
|
217
|
+
assert_raise(DummyError2) { ErrorCalc.parse(RLTK::Lexers::Calculator.lex('1 - +')) }
|
218
|
+
|
219
|
+
test_string = "first line;\n"
|
220
|
+
test_string += "second line\n"
|
221
|
+
test_string += "third line;\n"
|
222
|
+
test_string += "fourth line\n"
|
223
|
+
|
224
|
+
assert_raise(RLTK::HandledError) { ErrorLine.parse(ELLexer.lex(test_string)) }
|
225
|
+
|
226
|
+
begin
|
227
|
+
ErrorLine.parse(ELLexer.lex(test_string))
|
228
|
+
rescue RLTK::HandledError => e
|
229
|
+
assert_equal(e.errors, [2,4])
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_infix_calc
|
234
|
+
actual = RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::Calculator.lex('1 + 2'))
|
235
|
+
assert_equal(3, actual)
|
236
|
+
|
237
|
+
actual = RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::Calculator.lex('1 + 2 * 3'))
|
238
|
+
assert_equal(7, actual)
|
239
|
+
|
240
|
+
actual = RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::Calculator.lex('(1 + 2) * 3'))
|
241
|
+
assert_equal(9, actual)
|
242
|
+
|
243
|
+
assert_raise(RLTK::NotInLanguage) { RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::Calculator.lex('1 2 + 3 *')) }
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_input
|
247
|
+
assert_raise(RLTK::BadToken) { RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::EBNF.lex('A B C')) }
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_postfix_calc
|
251
|
+
actual = RLTK::Parsers::PostfixCalc.parse(RLTK::Lexers::Calculator.lex('1 2 +'))
|
252
|
+
assert_equal(3, actual)
|
253
|
+
|
254
|
+
actual = RLTK::Parsers::PostfixCalc.parse(RLTK::Lexers::Calculator.lex('1 2 3 * +'))
|
255
|
+
assert_equal(7, actual)
|
256
|
+
|
257
|
+
actual = RLTK::Parsers::PostfixCalc.parse(RLTK::Lexers::Calculator.lex('1 2 + 3 *'))
|
258
|
+
assert_equal(9, actual)
|
259
|
+
|
260
|
+
assert_raise(RLTK::NotInLanguage) { RLTK::Parsers::InfixCalc.parse(RLTK::Lexers::Calculator.lex('* + 1 2 3')) }
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_prefix_calc
|
264
|
+
actual = RLTK::Parsers::PrefixCalc.parse(RLTK::Lexers::Calculator.lex('+ 1 2'))
|
265
|
+
assert_equal(3, actual)
|
266
|
+
|
267
|
+
actual = RLTK::Parsers::PrefixCalc.parse(RLTK::Lexers::Calculator.lex('+ 1 * 2 3'))
|
268
|
+
assert_equal(7, actual)
|
269
|
+
|
270
|
+
actual = RLTK::Parsers::PrefixCalc.parse(RLTK::Lexers::Calculator.lex('* + 1 2 3'))
|
271
|
+
assert_equal(9, actual)
|
272
|
+
|
273
|
+
assert_raise(RLTK::NotInLanguage) { RLTK::Parsers::PrefixCalc.parse(RLTK::Lexers::Calculator.lex('1 + 2 * 3')) }
|
274
|
+
end
|
275
|
+
end
|
data/test/tc_token.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Ruby Language Toolkit
|
3
|
+
# Date: 2011/04/06
|
4
|
+
# Description: This file contains unit tests for the RLTK::Token class.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
require 'test/unit'
|
12
|
+
|
13
|
+
# Ruby Language Toolkit
|
14
|
+
require 'rltk/token'
|
15
|
+
|
16
|
+
#######################
|
17
|
+
# Classes and Modules #
|
18
|
+
#######################
|
19
|
+
|
20
|
+
class TokenTester < Test::Unit::TestCase
|
21
|
+
def test_equal
|
22
|
+
t0 = RLTK::Token.new(:FOO, 0)
|
23
|
+
t1 = RLTK::Token.new(:FOO, 0)
|
24
|
+
t2 = RLTK::Token.new(:FOO, 1)
|
25
|
+
t3 = RLTK::Token.new(:BAR, 0)
|
26
|
+
t4 = RLTK::Token.new(:BAR, 1)
|
27
|
+
|
28
|
+
assert_equal(t0, t1)
|
29
|
+
|
30
|
+
assert_not_equal(t0, t2)
|
31
|
+
assert_not_equal(t0, t3)
|
32
|
+
assert_not_equal(t0, t4)
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rltk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 1.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chris Wailes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-27 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: The Ruby Language Toolkit provides classes for creatingcontext-free grammars, lexers, parsers, and abstract syntax trees.
|
22
|
+
email: chris.wailes@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- LICENSE
|
31
|
+
- AUTHORS
|
32
|
+
- README
|
33
|
+
- Rakefile
|
34
|
+
- lib/rltk/cfg.rb
|
35
|
+
- lib/rltk/lexer.rb
|
36
|
+
- lib/rltk/lexers/ebnf.rb
|
37
|
+
- lib/rltk/lexers/calculator.rb
|
38
|
+
- lib/rltk/ast.rb
|
39
|
+
- lib/rltk/parser.rb
|
40
|
+
- lib/rltk/token.rb
|
41
|
+
- lib/rltk/parsers/postfix_calc.rb
|
42
|
+
- lib/rltk/parsers/infix_calc.rb
|
43
|
+
- lib/rltk/parsers/prefix_calc.rb
|
44
|
+
- test/tc_ast.rb
|
45
|
+
- test/tc_token.rb
|
46
|
+
- test/tc_cfg.rb
|
47
|
+
- test/tc_parser.rb
|
48
|
+
- test/tc_lexer.rb
|
49
|
+
homepage: http://github.com/chriswailes/RLTK
|
50
|
+
licenses:
|
51
|
+
- University of Illinois/NCSA Open Source License
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.10
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: The Ruby Language Toolkit
|
82
|
+
test_files:
|
83
|
+
- test/tc_ast.rb
|
84
|
+
- test/tc_token.rb
|
85
|
+
- test/tc_cfg.rb
|
86
|
+
- test/tc_parser.rb
|
87
|
+
- test/tc_lexer.rb
|