babel_bridge 0.3.1 → 0.4.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/babel_bridge.gemspec +18 -15
- data/examples/turing/turing++.rb +154 -0
- data/examples/turing/turing_demo.rb +19 -19
- data/lib/nodes.rb +2 -1
- data/lib/nodes/empty_node.rb +11 -1
- data/lib/nodes/many_node.rb +37 -48
- data/lib/nodes/node.rb +26 -32
- data/lib/nodes/non_terminal_node.rb +23 -90
- data/lib/nodes/rule_node.rb +102 -0
- data/lib/nodes/terminal_node.rb +6 -18
- data/lib/parser.rb +45 -31
- data/lib/pattern_element.rb +34 -47
- data/lib/rule.rb +3 -3
- data/lib/shell.rb +35 -16
- data/lib/version.rb +2 -2
- data/spec/bb_spec.rb +164 -0
- data/test/test_bb.rb +12 -17
- metadata +44 -5
data/babel_bridge.gemspec
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__),"lib/babel_bridge.rb")
|
2
2
|
|
3
|
-
$gemspec = Gem::Specification.new do |
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Babel Bridge is an object oriented parser generator for parsing expression grammars (PEG).
|
3
|
+
$gemspec = Gem::Specification.new do |gem|
|
4
|
+
gem.name = "babel_bridge"
|
5
|
+
gem.version = BabelBridge::VERSION
|
6
|
+
gem.author = "Shane Brinkman-Davis"
|
7
|
+
gem.date = "2010-11-28"
|
8
|
+
gem.email = "shanebdavis@gmail.com"
|
9
|
+
gem.homepage = "http://babel-bridge.rubyforge.org"
|
10
|
+
gem.platform = Gem::Platform::RUBY
|
11
|
+
gem.rubyforge_project = "babel-bridge"
|
12
|
+
gem.summary = "A Ruby-based parser-generator based on Parsing Expression Grammars."
|
13
|
+
gem.description = <<DESCRIPTION
|
14
|
+
Babel Bridge is an object oriented parser generator for parsing expression grammars (PEG).
|
15
15
|
Generate memoizing packrat parsers 100% in Ruby code with a simple embedded DSL.
|
16
16
|
DESCRIPTION
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
|
18
|
+
gem.files = ["LICENSE", "README", "Rakefile", "babel_bridge.gemspec", "{test,spec,lib,doc,examples}/**/*"].map{|p| Dir[p]}.flatten
|
19
|
+
gem.has_rdoc = false
|
20
|
+
|
21
|
+
gem.add_development_dependency 'rake'
|
22
|
+
gem.add_development_dependency 'rspec'
|
20
23
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),"..","..","lib","babel_bridge")
|
2
|
+
# A turing complete programming language
|
3
|
+
# Example program that computes the power of two of the value stored in the [0] register:
|
4
|
+
# => [0]=32;[1]=1;while [0]>0 do [1] = [1] * 2; [0] = [0]-1; end;[1]
|
5
|
+
|
6
|
+
# DONE: turing.rb PLUS
|
7
|
+
# => functions
|
8
|
+
# => local variables
|
9
|
+
# => stack
|
10
|
+
# TODO: add variables and functions
|
11
|
+
# TODO: add closures
|
12
|
+
# TODO: add classes
|
13
|
+
|
14
|
+
class TuringParser < BabelBridge::Parser
|
15
|
+
ignore_whitespace
|
16
|
+
# TODO: add "whole_words" option to convert all literal matching patterns that are words into /word\b/
|
17
|
+
|
18
|
+
def store
|
19
|
+
@store||=[]
|
20
|
+
end
|
21
|
+
|
22
|
+
# the stack consists of an array of hashs
|
23
|
+
# Each entry in the stack is a call-frame with a hash of local variable names
|
24
|
+
def stack; @stack ||= [{}]; end
|
25
|
+
|
26
|
+
def current_stack_frame; stack[-1]; end
|
27
|
+
def globals; stack[0]; end
|
28
|
+
|
29
|
+
rule :statements, many(:statement,";"), match?(";") do
|
30
|
+
def evaluate
|
31
|
+
ret = nil
|
32
|
+
statement.each do |s|
|
33
|
+
ret = s.evaluate
|
34
|
+
end
|
35
|
+
ret
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
rule :statement, :function_definition
|
40
|
+
|
41
|
+
rule :function_definition, "def", :identifier, :parameter_list?, "do", :statements, "end" do
|
42
|
+
def evaluate
|
43
|
+
stack_frame = parser.current_stack_frame
|
44
|
+
stack_frame[identifier.to_sym] = self
|
45
|
+
1 # return true
|
46
|
+
end
|
47
|
+
|
48
|
+
def parameter_names; @parameter_names||=parameter_list ? parameter_list.parameter_names : []; end
|
49
|
+
|
50
|
+
def evaluate_function(params)
|
51
|
+
params ||= []
|
52
|
+
locals = {}
|
53
|
+
raise "wrong number of parameters. #{identifier} expects #{parameter_names.length} but got #{params.length}" unless params.length == parameter_names.length
|
54
|
+
parameter_names.each_with_index do |name,index|
|
55
|
+
locals[name] = params[index]
|
56
|
+
end
|
57
|
+
parser.stack << locals
|
58
|
+
statements.evaluate.tap {parser.stack.pop}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
rule :parameter_list, "(", many(:identifier, ","), ")" do
|
63
|
+
def parameter_names
|
64
|
+
@parameter_names ||= identifier.collect{|a|a.to_sym}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
rule :statement, "if", :statement, "then", :statements, :else_clause?, "end" do
|
69
|
+
def evaluate
|
70
|
+
if statement.evaluate
|
71
|
+
statements.evaluate
|
72
|
+
elsif else_clause
|
73
|
+
else_clause.evaluate
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
rule :else_clause, "else", :statements
|
78
|
+
|
79
|
+
rule :statement, "while", :statement, "do", :statements, "end" do
|
80
|
+
def evaluate
|
81
|
+
ret = nil
|
82
|
+
while statement.evaluate
|
83
|
+
ret = statements.evaluate
|
84
|
+
end
|
85
|
+
ret
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
binary_operators_rule :statement, :operand, [[:/, :*], [:+, :-], [:<, :<=, :>, :>=, :==]] do
|
90
|
+
def evaluate
|
91
|
+
ret = left.evaluate.send operator, right.evaluate
|
92
|
+
case operator
|
93
|
+
when :<, :<=, :>, :>=, :== then ret ? 1 : nil
|
94
|
+
else ret
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
rule :operand, "(", :statement, ")"
|
100
|
+
|
101
|
+
rule :operand, "[", :statement, "]", "=", :statement do
|
102
|
+
def evaluate
|
103
|
+
parser.store[statement[0].evaluate] = statement[1].evaluate
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
rule :operand, "[", :statement, "]" do
|
108
|
+
def evaluate
|
109
|
+
parser.store[statement.evaluate]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
rule :operand, "nil" do
|
114
|
+
def evaluate
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
rule :operand, :identifier, "=", :statement do
|
120
|
+
def evaluate
|
121
|
+
stack_frame = parser.current_stack_frame
|
122
|
+
stack_frame[identifier.to_sym] = statement.evaluate
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
rule :operand, :identifier, :parameters? do
|
127
|
+
def evaluate
|
128
|
+
globals = parser.globals
|
129
|
+
stack_frame = parser.current_stack_frame
|
130
|
+
name = identifier.to_sym
|
131
|
+
raise "undefined variable: #{name.inspect}" unless globals.has_key?(name) || stack_frame.has_key?(name)
|
132
|
+
case value = stack_frame.has_key?(name) ? stack_frame[name] : globals[name]
|
133
|
+
when BabelBridge::Node then value.evaluate_function(parameters.evaluate)
|
134
|
+
else value
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
rule :parameters, "(", many(:statement,","), ")" do
|
140
|
+
def evaluate
|
141
|
+
statement.collect {|s|s.evaluate}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
rule :identifier, /[_a-zA-Z][_a-zA-Z0-9]*/
|
146
|
+
|
147
|
+
rule :operand, /[-]?[0-9]+/ do
|
148
|
+
def evaluate
|
149
|
+
to_s.to_i
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
BabelBridge::Shell.new(TuringParser.new).start
|
@@ -1,44 +1,32 @@
|
|
1
|
-
require
|
1
|
+
require "babel_bridge"
|
2
2
|
|
3
3
|
class TuringParser < BabelBridge::Parser
|
4
4
|
ignore_whitespace
|
5
5
|
|
6
6
|
def store
|
7
7
|
@store||=[]
|
8
|
-
end
|
8
|
+
end
|
9
9
|
|
10
10
|
rule :statements, many(:statement,";"), match?(";") do
|
11
11
|
def evaluate
|
12
12
|
ret = nil
|
13
13
|
statement.each do |s|
|
14
|
-
puts "class is: "+s.class
|
15
14
|
ret = s.evaluate
|
16
15
|
end
|
17
16
|
ret
|
18
17
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
binary_operators_rule :statement, :operand, [[:/, :*], [:+, :-], [:<, :<=, :>, :>=, :==]] do
|
22
|
-
def evaluate
|
23
|
-
case operator
|
24
|
-
when :<, :<=, :>, :>=, :==
|
25
|
-
(left.evaluate.send operator, right.evaluate) ? 1 : nil
|
26
|
-
else
|
27
|
-
left.evaluate.send operator, right.evaluate
|
28
|
-
end
|
29
|
-
end
|
30
18
|
end
|
31
19
|
|
32
20
|
rule :statement, "if", :statement, "then", :statements, :else_clause?, "end" do
|
33
21
|
def evaluate
|
34
|
-
if
|
35
|
-
|
22
|
+
if matches[1].evaluate
|
23
|
+
matches[3].evaluate
|
36
24
|
else
|
37
25
|
else_clause.evaluate if else_clause
|
38
26
|
end
|
39
27
|
end
|
40
28
|
end
|
41
|
-
rule :else_clause, "else", :
|
29
|
+
rule :else_clause, "else", :statements
|
42
30
|
|
43
31
|
rule :statement, "while", :statement, "do", :statements, "end" do
|
44
32
|
def evaluate
|
@@ -46,7 +34,19 @@ class TuringParser < BabelBridge::Parser
|
|
46
34
|
statements.evaluate
|
47
35
|
end
|
48
36
|
end
|
49
|
-
end
|
37
|
+
end
|
38
|
+
|
39
|
+
binary_operators_rule :statement, :operand, [[:/, :*], [:+, :-], [:<, :<=, :>, :>=, :==]] do
|
40
|
+
def evaluate
|
41
|
+
res = left.evaluate.send operator, right.evaluate
|
42
|
+
case operator
|
43
|
+
when :<, :<=, :>, :>=, :==
|
44
|
+
res ? 1 : nil
|
45
|
+
else
|
46
|
+
res
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
50
|
|
51
51
|
rule :operand, "[", :statement, "]", "=", :statement do
|
52
52
|
def evaluate
|
@@ -61,7 +61,7 @@ class TuringParser < BabelBridge::Parser
|
|
61
61
|
end
|
62
62
|
|
63
63
|
rule :operand, "(", :statement, ")"
|
64
|
-
rule :operand,
|
64
|
+
rule :operand, /[-]?[0-9]+/ do
|
65
65
|
def evaluate
|
66
66
|
to_s.to_i
|
67
67
|
end
|
data/lib/nodes.rb
CHANGED
data/lib/nodes/empty_node.rb
CHANGED
@@ -13,5 +13,15 @@ class EmptyNode < Node
|
|
13
13
|
def inspect(options={})
|
14
14
|
"EmptyNode" unless options[:simple]
|
15
15
|
end
|
16
|
+
|
17
|
+
# EmptyNodes should always match at the beginning of the whitespace range
|
18
|
+
def node_init(parent_or_parser)
|
19
|
+
super
|
20
|
+
self.offset = preceding_whitespace_range.first
|
21
|
+
self.preceding_whitespace_range = match_range
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches; [self]; end
|
25
|
+
|
26
|
+
end
|
16
27
|
end
|
17
|
-
end
|
data/lib/nodes/many_node.rb
CHANGED
@@ -5,60 +5,49 @@ http://babel-bridge.rubyforge.org/
|
|
5
5
|
=end
|
6
6
|
|
7
7
|
module BabelBridge
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
attr_accessor :matches,:delimiter_matches
|
12
|
-
def initialize(parent)
|
13
|
-
node_init(parent)
|
14
|
-
self.matches=[]
|
15
|
-
self.delimiter_matches=[]
|
16
|
-
end
|
17
|
-
|
18
|
-
def match_length; self.next-offset end
|
8
|
+
# generated by a :poly PatternElement
|
9
|
+
# Not subclassed
|
10
|
+
class ManyNode < NonTerminalNode
|
19
11
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
if d=delimiter_matches[-1]
|
24
|
-
d_next=d.next
|
25
|
-
m_next > d_next ? m_next : d_next
|
26
|
-
else
|
27
|
-
m_next
|
28
|
-
end
|
29
|
-
else
|
30
|
-
parent.next
|
31
|
-
end
|
32
|
-
end
|
12
|
+
def delimiter_matches
|
13
|
+
@delimiter_matches||=[]
|
14
|
+
end
|
33
15
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
ret = ret.collect {|a| " "+a.gsub("\n","\n ")}
|
41
|
-
(simple ? ret : ["[",ret,"]"]).flatten.join("\n") #.gsub("\n","\n ")
|
42
|
-
end
|
43
|
-
ret
|
44
|
-
end
|
16
|
+
def separate_delimiter_matches
|
17
|
+
count = 0
|
18
|
+
@matches, @delimiter_matches = @matches.partition {|el| count+=1;(count%2==1)}
|
19
|
+
@delimiter_matches = @delimiter_matches[0..@matches.length-2] #delimiter_matches should be exactly 1 shorter
|
20
|
+
update_match_length
|
21
|
+
end
|
45
22
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
ret+=" delimiters="+inspect_helper(delimiter_matches,options) if delimiter_matches.length>0
|
55
|
-
ret
|
56
|
-
end
|
23
|
+
def inspect_helper(list,options)
|
24
|
+
simple=options[:simple]
|
25
|
+
ret=list.collect {|a|a.inspect(options)}.compact
|
26
|
+
ret= if ret.length==0 then simple ? nil : "[]"
|
27
|
+
elsif ret.length==1 && !ret[0]["\n"] then (simple ? ret[0] : "[#{ret[0]}]")
|
28
|
+
else
|
29
|
+
ret = ret.collect {|a| " "+a.gsub("\n","\n ")}
|
30
|
+
(simple ? ret : ["[",ret,"]"]).flatten.join("\n") #.gsub("\n","\n ")
|
57
31
|
end
|
32
|
+
ret
|
33
|
+
end
|
58
34
|
|
59
|
-
|
60
|
-
|
35
|
+
def inspect(options={})
|
36
|
+
if options[:simple]
|
37
|
+
c=[]
|
38
|
+
matches.each_with_index {|n,i| c<<n;c<<delimiter_matches[i]}
|
39
|
+
c=c.compact
|
40
|
+
inspect_helper(c,options)
|
41
|
+
else
|
42
|
+
ret=inspect_helper(matches,options)
|
43
|
+
ret+=" delimiters="+inspect_helper(delimiter_matches,options) if delimiter_matches.length>0
|
44
|
+
ret
|
61
45
|
end
|
46
|
+
end
|
62
47
|
|
48
|
+
def method_missing(method_name, *args) #method_name is a symbol
|
49
|
+
self.map {|match| match.send(method_name,*args)}
|
63
50
|
end
|
51
|
+
|
52
|
+
end
|
64
53
|
end
|
data/lib/nodes/node.rb
CHANGED
@@ -7,12 +7,33 @@ end
|
|
7
7
|
|
8
8
|
# base class for all parse-tree nodes
|
9
9
|
class Node
|
10
|
-
attr_accessor :src,:offset,:match_length,:parent,:parser
|
10
|
+
attr_accessor :src,:offset,:match_length,:parent,:parser,:preceding_whitespace_range
|
11
|
+
|
12
|
+
def whitespace_regexp
|
13
|
+
parser.whitespace_regexp
|
14
|
+
end
|
15
|
+
|
16
|
+
# the index of the first character after the match
|
17
|
+
def offset_after_match
|
18
|
+
offset + match_length
|
19
|
+
end
|
20
|
+
|
21
|
+
def match_range
|
22
|
+
offset..(offset+match_length-1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def trailing_whitespace_range
|
26
|
+
parser.white_space_range offset_after_match
|
27
|
+
end
|
11
28
|
|
12
29
|
def to_s
|
13
30
|
text
|
14
31
|
end
|
15
32
|
|
33
|
+
def to_sym
|
34
|
+
to_s.to_sym
|
35
|
+
end
|
36
|
+
|
16
37
|
def node_init(parent_or_parser)
|
17
38
|
self.match_length=0
|
18
39
|
case parent_or_parser
|
@@ -24,6 +45,7 @@ class Node
|
|
24
45
|
self.parent=parent_or_parser
|
25
46
|
self.parser=parent.parser
|
26
47
|
self.offset=parent.next
|
48
|
+
self.preceding_whitespace_range=parent.trailing_whitespace_range
|
27
49
|
self.src=parent.src
|
28
50
|
raise "parent node does not have parser set" unless parser
|
29
51
|
else
|
@@ -51,8 +73,8 @@ class Node
|
|
51
73
|
#********************
|
52
74
|
# info methods
|
53
75
|
#********************
|
54
|
-
def next;
|
55
|
-
def text; src[
|
76
|
+
def next; trailing_whitespace_range.last+1 end # index of first character after match and any trailing whitespace
|
77
|
+
def text; src[match_range] end # the substring in src matched
|
56
78
|
|
57
79
|
# length returns the number of sub-nodes
|
58
80
|
def length
|
@@ -66,36 +88,8 @@ class Node
|
|
66
88
|
def node_path
|
67
89
|
"#{parent && (parent.node_path+' > ')}#{self.class}(#{offset})"
|
68
90
|
end
|
69
|
-
|
70
|
-
#*****************************
|
71
|
-
# Array interface implementation
|
72
|
-
#*****************************
|
73
|
-
def matches # override this with function that returns array of matches to be used for Array indexing and iteration
|
74
|
-
[]
|
75
|
-
end
|
76
|
-
|
77
|
-
include Enumerable
|
78
|
-
def length
|
79
|
-
matches.length
|
80
|
-
end
|
81
|
-
|
82
|
-
def <<(node)
|
83
|
-
matches<<node
|
84
|
-
end
|
85
|
-
|
86
|
-
def add_delimiter(node)
|
87
|
-
delimiter_matches<<node
|
88
|
-
end
|
89
|
-
|
90
|
-
def [](i)
|
91
|
-
matches[i]
|
92
|
-
end
|
93
|
-
|
94
|
-
def each(&block)
|
95
|
-
matches.each(&block)
|
96
|
-
end
|
97
91
|
end
|
98
92
|
|
99
93
|
class RootNode < Node
|
100
94
|
end
|
101
|
-
end
|
95
|
+
end
|