-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompiler.rb
More file actions
executable file
·183 lines (157 loc) · 4.42 KB
/
compiler.rb
File metadata and controls
executable file
·183 lines (157 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin ruby
class Tokenizer
TOKEN_TYPES = [
[:def, /\bdef\b/],
[:end, /\bend\b/],
[:identifier, /\b[a-zA-Z]+\b/],
[:integer, /\b[0-9]+\b/],
[:var_name, /\b[a-zA-Z]+\b/],
[:open_paren, /\(/],
[:close_paren, /\)/],
[:assignment_operator, /\=/],
[:separator, /\,/]
]
def initialize(code)
@code = code
end
def tokenize
tokens = []
until @code.empty?
tokens << tokenize_single_token
end
tokens
end
def tokenize_single_token
TOKEN_TYPES.each do |type, regex|
regex = /\A(#{regex})/
if @code =~ regex
token_value = $1
@code = @code[token_value.length..-1].strip
return Token.new(type, token_value)
end
end
raise RuntimeError.new("Couldn't match token on #{@code.inspect}")
end
end
Token = Struct.new(:type, :value)
class Parser
def initialize(tokens)
@tokens = tokens
end
def parse
nodes = []
nodes << parse_expression if check_next_token_type(:identifier)
return nodes if @tokens.empty?
nodes << parse_def if check_next_token_type(:def)
nodes
end
def parse_variable_assignment
var_name = VarRefNode.new(consume(:identifier).value)
consume(:assignment_operator)
var_value = IntegerNode.new(consume(:integer).value)
VarAssignmentNode.new(var_name, var_value)
end
def parse_def
consume(:def)
identifier = consume(:identifier)
arg_names = parse_arg_names
body = parse_expression
consume(:end)
DefNode.new(identifier.value, arg_names, body)
end
def parse_expression
if check_next_token_type(:integer)
IntegerNode.new(consume(:integer).value)
elsif check_next_token_type(:identifier) && @tokens[1].type == :open_paren
parse_function_call
elsif check_next_token_type(:identifier) && @tokens[1].type == :assignment_operator
parse_variable_assignment
elsif check_next_token_type(:identifier)
VarRefNode.new(consume(:identifier).value)
else
raise RuntimeError.new("expected integer or identifier but got #{@tokens[0].type}")
end
end
def parse_arg_names
consume(:open_paren)
args = []
args = addAnyArgs(args)
consume(:close_paren)
args
end
def addAnyArgs(args)
args << consume(:identifier).value if check_next_token_type(:identifier)
if check_next_token_type(:separator)
consume(:separator)
addAnyArgs(args)
end
args
end
def parse_arg_expressions
consume(:open_paren)
args = []
args = addAnyArgExpressions(args)
consume(:close_paren)
args
end
def addAnyArgExpressions(args)
return args if check_next_token_type(:close_paren)
args << consume(:identifier).value
if check_next_token_type(:separator)
consume(:separator)
addAnyArgExpressions(args)
end
args
end
def check_next_token_type(expected_type)
@tokens[0].type == expected_type
end
def parse_function_call
function_name = VarRefNode.new(consume(:identifier).value)
arg_expressions = parse_arg_expressions
FunctionCallNode.new(function_name, arg_expressions)
end
def consume(expected_type)
if @tokens[0].type == expected_type
@tokens.shift
else
raise RuntimeError.new("expected #{expected_type} but got #{@tokens[0].type}")
end
end
end
DefNode = Struct.new(:name, :arg_names, :body)
VarAssignmentNode = Struct.new(:var_name, :var_value)
FunctionCallNode = Struct.new(:name, :arg_expressions)
IntegerNode = Struct.new(:value)
VarRefNode = Struct.new(:value)
class Generator
def generate(nodes)
str = ''
nodes.each do |node|
str += generate_single_node(node)
end
str
end
def generate_single_node(node)
case node
when VarAssignmentNode
"var #{generate_single_node(node.var_name)} = #{generate_single_node(node.var_value)};"
when DefNode
"function #{node.name} (#{node.arg_names.join(', ')}) { return #{generate_single_node(node.body)} }"
when FunctionCallNode
"#{generate_single_node(node.name)}(#{node.arg_expressions.join(', ')})"
when IntegerNode
node.value
when VarRefNode
node.value
end
end
end
# tokens = Tokenizer.new(File.read("test.src")).tokenize
# #puts tokens.map(&:inspect).join("\n")
# tree = Parser.new(tokens).parse
# #puts tree
# generated = Generator.new.generate(tree)
# RUNTIME = "function add(x, y) { return x + y };"
# TEST = "console.log(newFunction(17))"
# puts [RUNTIME, generated, TEST].join("\n")