treetop 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -2
- data/examples/TALK +33 -0
- data/examples/lambda_calculus/arithmetic.treetop +99 -0
- data/examples/lambda_calculus/arithmetic_node_classes.rb +7 -0
- data/examples/lambda_calculus/arithmetic_test.rb +54 -0
- data/examples/lambda_calculus/lambda_calculus.treetop +139 -0
- data/examples/lambda_calculus/lambda_calculus_node_classes.rb +5 -0
- data/examples/lambda_calculus/lambda_calculus_test.rb +89 -0
- data/examples/lambda_calculus/test_helper.rb +18 -0
- data/lib/treetop/compiler/metagrammar.rb +17 -12
- data/lib/treetop/compiler/metagrammar.treetop +11 -11
- data/lib/treetop/compiler/node_classes/anything_symbol.rb +1 -1
- data/lib/treetop/compiler/node_classes/character_class.rb +1 -1
- data/lib/treetop/compiler/node_classes/choice.rb +2 -0
- data/lib/treetop/compiler/node_classes/nonterminal.rb +2 -0
- data/lib/treetop/compiler/node_classes/parenthesized_expression.rb +1 -1
- data/lib/treetop/compiler/node_classes/parsing_expression.rb +19 -4
- data/lib/treetop/compiler/node_classes/repetition.rb +2 -2
- data/lib/treetop/compiler/node_classes/sequence.rb +7 -3
- data/lib/treetop/compiler/node_classes/terminal.rb +1 -1
- data/lib/treetop/compiler/ruby_builder.rb +4 -0
- data/test/compiler/choice_test.rb +26 -1
- data/test/compiler/nonterminal_symbol_test.rb +28 -5
- metadata +11 -2
data/Rakefile
CHANGED
@@ -15,13 +15,13 @@ end
|
|
15
15
|
|
16
16
|
gemspec = Gem::Specification.new do |s|
|
17
17
|
s.name = "treetop"
|
18
|
-
s.version = "1.0
|
18
|
+
s.version = "1.1.0"
|
19
19
|
s.author = "Nathan Sobo"
|
20
20
|
s.email = "nathansobo@gmail.com"
|
21
21
|
s.homepage = "http://functionalform.blogspot.com"
|
22
22
|
s.platform = Gem::Platform::RUBY
|
23
23
|
s.summary = "A Ruby-based text parsing and interpretation DSL"
|
24
|
-
s.files = FileList["README", "Rakefile", "{test,lib,bin}/**/*"].to_a
|
24
|
+
s.files = FileList["README", "Rakefile", "{test,lib,bin,examples}/**/*"].to_a
|
25
25
|
s.bindir = "bin"
|
26
26
|
s.executables = ["tt"]
|
27
27
|
s.require_path = "lib"
|
data/examples/TALK
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Before talk?
|
2
|
+
------------
|
3
|
+
- non node-instantiating expressions extend their results with inline modules
|
4
|
+
- allow load_grammar and require statements at the top of treetop files
|
5
|
+
- deal with facets versioning issue
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
Function application... the left-recursion problem
|
10
|
+
--------------------------------------------------
|
11
|
+
f g x
|
12
|
+
|
13
|
+
rule expression
|
14
|
+
application / function / variable
|
15
|
+
end
|
16
|
+
|
17
|
+
rule application
|
18
|
+
expression space expression
|
19
|
+
end
|
20
|
+
|
21
|
+
Function application... how can we avoid left recursion?
|
22
|
+
--------------------------------------------------------
|
23
|
+
rule expression
|
24
|
+
application / function / variable
|
25
|
+
end
|
26
|
+
|
27
|
+
rule application
|
28
|
+
operator space expression
|
29
|
+
end
|
30
|
+
|
31
|
+
rule operator
|
32
|
+
function / variable
|
33
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
grammar Arithmetic
|
2
|
+
rule expression
|
3
|
+
comparative / additive
|
4
|
+
end
|
5
|
+
|
6
|
+
rule comparative
|
7
|
+
exp_1:additive space '==' space exp_2:additive {
|
8
|
+
def eval(env={})
|
9
|
+
exp_1.eval(env) == exp_2.eval(env)
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
rule additive
|
15
|
+
operand_1:multitive
|
16
|
+
space operator:additive_op space
|
17
|
+
operand_2:additive <BinaryOperation>
|
18
|
+
/
|
19
|
+
multitive
|
20
|
+
end
|
21
|
+
|
22
|
+
rule additive_op
|
23
|
+
'+' {
|
24
|
+
def apply(a, b)
|
25
|
+
a + b
|
26
|
+
end
|
27
|
+
}
|
28
|
+
/
|
29
|
+
'-' {
|
30
|
+
def apply(a, b)
|
31
|
+
a - b
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
rule multitive
|
37
|
+
operand_1:primary
|
38
|
+
space operator:multitive_op space
|
39
|
+
operand_2:multitive <BinaryOperation>
|
40
|
+
/
|
41
|
+
primary
|
42
|
+
end
|
43
|
+
|
44
|
+
rule multitive_op
|
45
|
+
'*' {
|
46
|
+
def apply(a, b)
|
47
|
+
a * b
|
48
|
+
end
|
49
|
+
}
|
50
|
+
/
|
51
|
+
'/' {
|
52
|
+
def apply(a, b)
|
53
|
+
a / b
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
rule primary
|
59
|
+
variable
|
60
|
+
/
|
61
|
+
number
|
62
|
+
/
|
63
|
+
'(' space expression space ')' {
|
64
|
+
def eval(env={})
|
65
|
+
expression.eval(env)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule variable
|
71
|
+
[a-z] {
|
72
|
+
def eval(env={})
|
73
|
+
env[name]
|
74
|
+
end
|
75
|
+
|
76
|
+
def name
|
77
|
+
text_value
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
rule number
|
83
|
+
[1-9] [0-9]* {
|
84
|
+
def eval(env={})
|
85
|
+
text_value.to_i
|
86
|
+
end
|
87
|
+
}
|
88
|
+
/
|
89
|
+
'0' {
|
90
|
+
def eval(env={})
|
91
|
+
text_value.to_i
|
92
|
+
end
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
rule space
|
97
|
+
' '*
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require File.expand_path("#{dir}/test_helper")
|
3
|
+
|
4
|
+
require File.expand_path("#{dir}/arithmetic_node_classes")
|
5
|
+
load_grammar File.expand_path("#{dir}/arithmetic")
|
6
|
+
|
7
|
+
class ArithmeticParserTest < Test::Unit::TestCase
|
8
|
+
include ParserTestHelper
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@parser = ArithmeticParser.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_number
|
15
|
+
assert_equal 0, parse('0').eval
|
16
|
+
assert_equal 1, parse('1').eval
|
17
|
+
assert_equal 123, parse('123').eval
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_variable
|
21
|
+
assert_equal 0, parse('x').eval('x' => 0)
|
22
|
+
assert_equal 3, parse('x').eval('x' => 3)
|
23
|
+
assert_equal 10, parse('y').eval('y' => 10)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_addition
|
27
|
+
assert_equal 10, parse('x + 5').eval('x' => 5)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_subtraction
|
31
|
+
assert_equal 0, parse('x - 5').eval('x' => 5)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_multiplication
|
35
|
+
assert_equal 6, parse('x * 2').eval('x' => 3)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_division
|
39
|
+
assert_equal 3, parse('x / 2').eval('x' => 6)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_order_of_operations
|
43
|
+
assert_equal 11, parse('1 + 2 * 3 + 4').eval
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parentheses
|
47
|
+
assert_equal 25, parse('(5 + x) * (10 - y)').eval('x' => 0, 'y' => 5)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_equality
|
51
|
+
assert parse('4 == 4').eval
|
52
|
+
assert !parse('4 == 3').eval
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
grammar LambdaCalculus
|
2
|
+
include Arithmetic
|
3
|
+
|
4
|
+
rule program
|
5
|
+
expression more_expressions:(';' space expression)* {
|
6
|
+
def eval(env={})
|
7
|
+
env = env.clone
|
8
|
+
last_eval = nil
|
9
|
+
expressions.each do |exp|
|
10
|
+
last_eval = exp.eval(env)
|
11
|
+
end
|
12
|
+
last_eval
|
13
|
+
end
|
14
|
+
|
15
|
+
def expressions
|
16
|
+
[expression] + more_expressions.elements.map {|elt| elt.expression}
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
rule expression
|
22
|
+
definition / conditional / application / function / super
|
23
|
+
end
|
24
|
+
|
25
|
+
rule definition
|
26
|
+
'def' space variable space expression {
|
27
|
+
def eval(env)
|
28
|
+
env[variable.name] = expression.eval(env)
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
rule conditional
|
34
|
+
'if' space '(' space condition:expression space ')' space
|
35
|
+
true_case:expression space 'else' space false_case:expression {
|
36
|
+
def eval(env)
|
37
|
+
if condition.eval(env)
|
38
|
+
true_case.eval(env)
|
39
|
+
else
|
40
|
+
false_case.eval(env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
rule primary
|
47
|
+
application / super
|
48
|
+
end
|
49
|
+
|
50
|
+
rule application
|
51
|
+
operator space expression <Application> {
|
52
|
+
def eval(env={})
|
53
|
+
left_associative_apply(operator.eval(env), env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def left_associative_apply(operator, env)
|
57
|
+
if expression.instance_of?(Application)
|
58
|
+
expression.left_associative_apply(operator.apply(expression.operator.eval(env)), env)
|
59
|
+
else
|
60
|
+
operator.apply(expression.eval(env))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s(env={})
|
65
|
+
operator.to_s(env) + ' ' + expression.to_s(env)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule operator
|
71
|
+
function / variable
|
72
|
+
end
|
73
|
+
|
74
|
+
rule non_application
|
75
|
+
function / variable
|
76
|
+
end
|
77
|
+
|
78
|
+
rule function
|
79
|
+
'\\' param:variable '(' body:expression ')' {
|
80
|
+
class Closure
|
81
|
+
attr_reader :env, :function
|
82
|
+
|
83
|
+
def initialize(function, env)
|
84
|
+
@function = function
|
85
|
+
@env = env
|
86
|
+
end
|
87
|
+
|
88
|
+
def apply(arg)
|
89
|
+
function.body.eval(function.param.bind(arg, env))
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s(other_env={})
|
93
|
+
"\\#{function.param.to_s}(#{function.body.to_s(other_env.merge(env))})"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def eval(env={})
|
98
|
+
Closure.new(self, env)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_s(env={})
|
102
|
+
eval(env).to_s
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
rule variable
|
108
|
+
!keyword [a-z]+ {
|
109
|
+
def eval(env={})
|
110
|
+
env.has_key?(name) ? env[name] : self
|
111
|
+
end
|
112
|
+
|
113
|
+
def bind(value, env)
|
114
|
+
env.merge(name => value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def name
|
118
|
+
text_value
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_s(env={})
|
122
|
+
env.has_key?(name) ? env[name].to_s : name
|
123
|
+
end
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
rule keyword
|
128
|
+
('if' / 'else') !non_space_char
|
129
|
+
end
|
130
|
+
|
131
|
+
rule non_space_char
|
132
|
+
![ \n] .
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
rule space
|
137
|
+
[ \n]*
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require File.expand_path("#{dir}/test_helper")
|
3
|
+
require File.expand_path("#{dir}/arithmetic_node_classes")
|
4
|
+
require File.expand_path("#{dir}/lambda_calculus_node_classes")
|
5
|
+
load_grammar File.expand_path("#{dir}/arithmetic")
|
6
|
+
load_grammar File.expand_path("#{dir}/lambda_calculus")
|
7
|
+
|
8
|
+
class Treetop::Runtime::SyntaxNode
|
9
|
+
def method_missing(method, *args)
|
10
|
+
raise "Node representing #{text_value} does not respond to #{method}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class LambdaCalculusParserTest < Test::Unit::TestCase
|
15
|
+
include ParserTestHelper
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@parser = LambdaCalculusParser.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_free_variable
|
22
|
+
assert_equal 'x', parse('x').eval.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_variable_binding
|
26
|
+
variable = parse('x').eval
|
27
|
+
env = variable.bind(1, {})
|
28
|
+
assert_equal 1, env['x']
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_bound_variable_evaluation
|
32
|
+
assert_equal 1, parse('x').eval({'x' => 1})
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_identity_function
|
36
|
+
assert_equal '\x(x)', parse('\x(x)').eval.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_function_returning_constant_function
|
40
|
+
assert_equal '\x(\y(x))', parse('\x(\y(x))').eval.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_identity_function_application
|
44
|
+
assert_equal 1, parse('\x(x) 1').eval
|
45
|
+
assert_equal '\y(y)', parse('\x(x) \y(y)').eval.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_constant_function_construction
|
49
|
+
assert_equal '\y(1)', parse('\x(\y(x)) 1').eval.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_multiple_argument_application_is_left_associative
|
53
|
+
assert_equal '\b(b)', parse('\x(\y(x y)) \a(a) \b(b)').eval.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_parentheses_override_application_order
|
57
|
+
assert_equal '\y(\b(b) y)', parse('\x(\y(x y)) (\a(a) \b(b))').eval.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_arithmetic_in_function_body
|
61
|
+
assert_equal 10, parse('\x(x + 5) 5').eval
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_addition_of_function_results
|
65
|
+
assert_equal 20, parse('\x(x + 5) 5 + \x(15 - x) 5').eval
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_conditional
|
69
|
+
result = parse('if (x) 1 else 2')
|
70
|
+
assert_equal 1, result.eval({'x' => true})
|
71
|
+
assert_equal 2, result.eval({'x' => false})
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_keyword
|
75
|
+
assert @parser.parse('if').failure?
|
76
|
+
assert @parser.parse('else').failure?
|
77
|
+
assert parse('elsee').success?
|
78
|
+
assert parse('iff').success?
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_program
|
82
|
+
result = parse('def fact \x(if (x == 0)
|
83
|
+
1
|
84
|
+
else
|
85
|
+
x * fact (x - 1));
|
86
|
+
fact(5)').eval
|
87
|
+
assert_equal 5 * 4 * 3 * 2, result
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'treetop'
|
4
|
+
|
5
|
+
module ParserTestHelper
|
6
|
+
def assert_evals_to_self(input)
|
7
|
+
assert_evals_to(input, input)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(input)
|
11
|
+
result = @parser.parse(input)
|
12
|
+
if result.failure?
|
13
|
+
puts result.nested_failures.join("\n")
|
14
|
+
end
|
15
|
+
assert result.success?
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
@@ -4,7 +4,12 @@ module Treetop
|
|
4
4
|
include ::Treetop::Runtime
|
5
5
|
|
6
6
|
def root
|
7
|
-
_nt_treetop_file
|
7
|
+
result = _nt_treetop_file
|
8
|
+
if index == input.size
|
9
|
+
return result
|
10
|
+
else
|
11
|
+
return ParseFailure.new(input, index, result.nested_failures)
|
12
|
+
end
|
8
13
|
end
|
9
14
|
|
10
15
|
module TreetopFile0
|
@@ -928,8 +933,8 @@ module Treetop
|
|
928
933
|
atomic
|
929
934
|
end
|
930
935
|
|
931
|
-
def
|
932
|
-
node_class_declarations.
|
936
|
+
def node_class_name
|
937
|
+
node_class_declarations.node_class_name
|
933
938
|
end
|
934
939
|
|
935
940
|
def inline_modules
|
@@ -956,8 +961,8 @@ module Treetop
|
|
956
961
|
atomic.compile(address, builder, self)
|
957
962
|
end
|
958
963
|
|
959
|
-
def
|
960
|
-
node_class_declarations.
|
964
|
+
def node_class_name
|
965
|
+
node_class_declarations.node_class_name
|
961
966
|
end
|
962
967
|
|
963
968
|
def inline_modules
|
@@ -1245,8 +1250,8 @@ module Treetop
|
|
1245
1250
|
suffix.compile(lexical_address, builder, self)
|
1246
1251
|
end
|
1247
1252
|
|
1248
|
-
def
|
1249
|
-
|
1253
|
+
def node_class_name
|
1254
|
+
nil
|
1250
1255
|
end
|
1251
1256
|
|
1252
1257
|
def inline_modules
|
@@ -1381,8 +1386,8 @@ module Treetop
|
|
1381
1386
|
end
|
1382
1387
|
|
1383
1388
|
module NodeClassDeclarations1
|
1384
|
-
def
|
1385
|
-
node_class_expression.
|
1389
|
+
def node_class_name
|
1390
|
+
node_class_expression.node_class_name
|
1386
1391
|
end
|
1387
1392
|
|
1388
1393
|
def inline_modules
|
@@ -2001,14 +2006,14 @@ module Treetop
|
|
2001
2006
|
end
|
2002
2007
|
|
2003
2008
|
module NodeClassExpression2
|
2004
|
-
def
|
2009
|
+
def node_class_name
|
2005
2010
|
elements[2].text_value
|
2006
2011
|
end
|
2007
2012
|
end
|
2008
2013
|
|
2009
2014
|
module NodeClassExpression3
|
2010
|
-
def
|
2011
|
-
|
2015
|
+
def node_class_name
|
2016
|
+
nil
|
2012
2017
|
end
|
2013
2018
|
end
|
2014
2019
|
|
@@ -132,8 +132,8 @@ module Treetop
|
|
132
132
|
atomic
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
136
|
-
node_class_declarations.
|
135
|
+
def node_class_name
|
136
|
+
node_class_declarations.node_class_name
|
137
137
|
end
|
138
138
|
|
139
139
|
def inline_modules
|
@@ -150,8 +150,8 @@ module Treetop
|
|
150
150
|
atomic.compile(address, builder, self)
|
151
151
|
end
|
152
152
|
|
153
|
-
def
|
154
|
-
node_class_declarations.
|
153
|
+
def node_class_name
|
154
|
+
node_class_declarations.node_class_name
|
155
155
|
end
|
156
156
|
|
157
157
|
def inline_modules
|
@@ -224,8 +224,8 @@ module Treetop
|
|
224
224
|
suffix.compile(lexical_address, builder, self)
|
225
225
|
end
|
226
226
|
|
227
|
-
def
|
228
|
-
|
227
|
+
def node_class_name
|
228
|
+
nil
|
229
229
|
end
|
230
230
|
|
231
231
|
def inline_modules
|
@@ -250,8 +250,8 @@ module Treetop
|
|
250
250
|
|
251
251
|
rule node_class_declarations
|
252
252
|
node_class_expression trailing_inline_module {
|
253
|
-
def
|
254
|
-
node_class_expression.
|
253
|
+
def node_class_name
|
254
|
+
node_class_expression.node_class_name
|
255
255
|
end
|
256
256
|
|
257
257
|
def inline_modules
|
@@ -318,14 +318,14 @@ module Treetop
|
|
318
318
|
|
319
319
|
rule node_class_expression
|
320
320
|
space '<' (!'>' .)+ '>' {
|
321
|
-
def
|
321
|
+
def node_class_name
|
322
322
|
elements[2].text_value
|
323
323
|
end
|
324
324
|
}
|
325
325
|
/
|
326
326
|
'' {
|
327
|
-
def
|
328
|
-
|
327
|
+
def node_class_name
|
328
|
+
nil
|
329
329
|
end
|
330
330
|
}
|
331
331
|
end
|
@@ -3,7 +3,7 @@ module Treetop
|
|
3
3
|
class AnythingSymbol < AtomicExpression
|
4
4
|
def compile(address, builder, parent_expression = nil)
|
5
5
|
super
|
6
|
-
assign_result "parse_anything(#{
|
6
|
+
assign_result "parse_anything(#{node_class_name}#{optional_arg(inline_module_name)})"
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -3,7 +3,7 @@ module Treetop
|
|
3
3
|
class CharacterClass < AtomicExpression
|
4
4
|
def compile(address, builder, parent_expression = nil)
|
5
5
|
super
|
6
|
-
assign_result "parse_char_class(/#{text_value}/, '#{elements[1].text_value.gsub(/'$/, "\\\\'")}', #{
|
6
|
+
assign_result "parse_char_class(/#{text_value}/, '#{elements[1].text_value.gsub(/'$/, "\\\\'")}', #{node_class_name}#{optional_arg(inline_module_name)})"
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -15,6 +15,8 @@ module Treetop
|
|
15
15
|
accumulate_nested_result
|
16
16
|
builder.if__ subexpression_success? do
|
17
17
|
assign_result subexpression_result_var
|
18
|
+
extend_result_with_declared_module
|
19
|
+
extend_result_with_inline_module
|
18
20
|
builder << "#{subexpression_result_var}.update_nested_results(#{nested_results_var})"
|
19
21
|
end
|
20
22
|
builder.else_ do
|
@@ -9,13 +9,16 @@ module Treetop
|
|
9
9
|
@parent_expression = parent_expression
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
parent_expression && parent_expression.
|
12
|
+
def node_class_name
|
13
|
+
parent_expression && parent_expression.node_class_name || 'SyntaxNode'
|
14
|
+
end
|
15
|
+
|
16
|
+
def declared_module_name
|
17
|
+
parent_expression && parent_expression.node_class_name
|
14
18
|
end
|
15
19
|
|
16
20
|
def inline_module_name
|
17
|
-
|
18
|
-
parent_expression.inline_module_name
|
21
|
+
parent_expression && parent_expression.inline_module_name
|
19
22
|
end
|
20
23
|
|
21
24
|
def optional_arg(arg)
|
@@ -70,6 +73,18 @@ module Treetop
|
|
70
73
|
def assign_result(value_ruby)
|
71
74
|
builder.assign result_var, value_ruby
|
72
75
|
end
|
76
|
+
|
77
|
+
def extend_result(module_name)
|
78
|
+
builder.extend result_var, module_name
|
79
|
+
end
|
80
|
+
|
81
|
+
def extend_result_with_declared_module
|
82
|
+
extend_result declared_module_name if declared_module_name
|
83
|
+
end
|
84
|
+
|
85
|
+
def extend_result_with_inline_module
|
86
|
+
extend_result inline_module_name if inline_module_name
|
87
|
+
end
|
73
88
|
|
74
89
|
def reset_index
|
75
90
|
builder.assign 'self.index', start_index_var
|
@@ -25,8 +25,8 @@ module Treetop
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def assign_and_extend_result
|
28
|
-
assign_result "#{
|
29
|
-
|
28
|
+
assign_result "#{node_class_name}.new(input, #{start_index_var}...index, #{accumulator_var}, #{nested_results_var})"
|
29
|
+
extend_result_with_inline_module
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -7,9 +7,9 @@ module Treetop
|
|
7
7
|
use_vars :result, :start_index, :accumulator, :nested_results
|
8
8
|
compile_sequence_elements(sequence_elements)
|
9
9
|
builder.if__ "#{accumulator_var}.last.success?" do
|
10
|
-
assign_result "(#{
|
11
|
-
|
12
|
-
|
10
|
+
assign_result "(#{node_class_name}).new(input, #{start_index_var}...index, #{accumulator_var})"
|
11
|
+
extend_result sequence_element_accessor_module_name if sequence_element_accessor_module_name
|
12
|
+
extend_result_with_inline_module
|
13
13
|
end
|
14
14
|
builder.else_ do
|
15
15
|
reset_index
|
@@ -18,6 +18,10 @@ module Treetop
|
|
18
18
|
end_comment(self)
|
19
19
|
end
|
20
20
|
|
21
|
+
def node_class_name
|
22
|
+
node_class_declarations.node_class_name || 'SyntaxNode'
|
23
|
+
end
|
24
|
+
|
21
25
|
def compile_sequence_elements(elements)
|
22
26
|
obtain_new_subexpression_address
|
23
27
|
elements.first.compile(subexpression_address, builder)
|
@@ -3,7 +3,7 @@ module Treetop
|
|
3
3
|
class Terminal < AtomicExpression
|
4
4
|
def compile(address, builder, parent_expression = nil)
|
5
5
|
super
|
6
|
-
assign_result "parse_terminal(#{text_value}, #{
|
6
|
+
assign_result "parse_terminal(#{text_value}, #{node_class_name}#{optional_arg(inline_module_name)})"
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -46,4 +46,29 @@ describe "A choice between sequences", :extend => CompilerTestCase do
|
|
46
46
|
parse('foobarbaz').should be_success
|
47
47
|
parse('bingbangboom').should be_success
|
48
48
|
end
|
49
|
-
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "A choice between terminals followed by a block", :extend => CompilerTestCase do
|
52
|
+
testing_expression "('a'/ 'b' / 'c') { def a_method; end }"
|
53
|
+
|
54
|
+
it "extends a match of any of its subexpressions with a module created from the block" do
|
55
|
+
['a', 'b', 'c'].each do |letter|
|
56
|
+
parse(letter).should respond_to(:a_method)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class ChoiceWithDeclaredModuleTest < CompilerTestCase
|
62
|
+
module TestModule
|
63
|
+
def a_method
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
testing_expression "('a'/ 'b' / 'c') <TestModule>"
|
68
|
+
|
69
|
+
it "extends a match of any of its subexpressions with a module created from the block" do
|
70
|
+
['a', 'b', 'c'].each do |letter|
|
71
|
+
parse(letter).should respond_to(:a_method)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
2
2
|
|
3
|
-
describe "A nonterminal symbol", :extend => CompilerTestCase do
|
4
|
-
testing_expression 'foo'
|
3
|
+
describe "A nonterminal symbol followed by a block", :extend => CompilerTestCase do
|
4
|
+
testing_expression 'foo { def a_method; end }'
|
5
5
|
|
6
6
|
parser_class_under_test.class_eval do
|
7
7
|
def _nt_foo
|
@@ -9,7 +9,30 @@ describe "A nonterminal symbol", :extend => CompilerTestCase do
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
it "compiles to a
|
13
|
-
parse('')
|
12
|
+
it "compiles to a method call, extending its results with the anonymous module for the block" do
|
13
|
+
result = parse('')
|
14
|
+
result.should == '_nt_foo called'
|
15
|
+
result.should respond_to(:a_method)
|
14
16
|
end
|
15
|
-
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NonterminalWithModuleDeclationTest < CompilerTestCase
|
20
|
+
module TestModule
|
21
|
+
def a_method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
testing_expression 'foo <TestModule>'
|
26
|
+
|
27
|
+
parser_class_under_test.class_eval do
|
28
|
+
def _nt_foo
|
29
|
+
'_nt_foo called'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "compiles to a method call, extending its results with the anonymous module for the block" do
|
34
|
+
result = parse('')
|
35
|
+
result.should == '_nt_foo called'
|
36
|
+
result.should respond_to(:a_method)
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: treetop
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.0
|
7
|
-
date: 2007-10-
|
6
|
+
version: 1.1.0
|
7
|
+
date: 2007-10-25 00:00:00 -07:00
|
8
8
|
summary: A Ruby-based text parsing and interpretation DSL
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -144,6 +144,15 @@ files:
|
|
144
144
|
- lib/treetop/runtime.rb
|
145
145
|
- lib/treetop.rb
|
146
146
|
- bin/tt
|
147
|
+
- examples/lambda_calculus
|
148
|
+
- examples/lambda_calculus/arithmetic.treetop
|
149
|
+
- examples/lambda_calculus/arithmetic_node_classes.rb
|
150
|
+
- examples/lambda_calculus/arithmetic_test.rb
|
151
|
+
- examples/lambda_calculus/lambda_calculus.treetop
|
152
|
+
- examples/lambda_calculus/lambda_calculus_node_classes.rb
|
153
|
+
- examples/lambda_calculus/lambda_calculus_test.rb
|
154
|
+
- examples/lambda_calculus/test_helper.rb
|
155
|
+
- examples/TALK
|
147
156
|
test_files: []
|
148
157
|
|
149
158
|
rdoc_options: []
|