Skip to content

Commit cef8b63

Browse files
committed
WIP Compilation Engine
1 parent 17a3cfa commit cef8b63

File tree

2 files changed

+438
-0
lines changed

2 files changed

+438
-0
lines changed

lib/compilation_engine.rb

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
require_relative 'symbol_table'
2+
require_relative 'vm_writer'
3+
4+
class CompilationEngine
5+
attr_reader :input, :vm_writer
6+
7+
def initialize(input, output)
8+
@input = input
9+
@vm_writer = VMWriter.new(output)
10+
end
11+
12+
def compile_class
13+
@symbols = SymbolTable.new
14+
15+
# b.tag!(:class) do
16+
# Get the ball moving!
17+
input.advance
18+
19+
consume(Tokenizer::KEYWORD, 'class')
20+
21+
@class_name = current_token
22+
consume(Tokenizer::IDENTIFIER)
23+
24+
consume_wrapped('{') do
25+
while %w[field static].include? current_token
26+
compile_class_var_dec
27+
end
28+
29+
while %w[constructor function method].include? current_token
30+
compile_subroutine
31+
end
32+
end
33+
# end
34+
end
35+
36+
def compile_class_var_dec
37+
# b.classVarDec do
38+
kind = current_token # field, static, etc.
39+
consume(Tokenizer::KEYWORD)
40+
41+
type = current_token # int, char, etc.
42+
consume_type
43+
44+
consume_separated(',') do
45+
name = current_token
46+
@symbols.define(name, type, kind)
47+
consume_identifier(name)
48+
end
49+
50+
consume(Tokenizer::SYMBOL, ';')
51+
# end
52+
end
53+
54+
def compile_subroutine
55+
@symbols.start_subroutine
56+
57+
# b.subroutineDec do
58+
consume(Tokenizer::KEYWORD)
59+
60+
try_consume(Tokenizer::KEYWORD, 'void') || consume_type
61+
62+
method_name = full_method_name
63+
consume(Tokenizer::IDENTIFIER)
64+
65+
n = 0
66+
consume_wrapped('(') do
67+
n = compile_parameter_list
68+
end
69+
70+
vm_writer.write_function(method_name, n)
71+
72+
compile_subroutine_body
73+
# end
74+
end
75+
76+
def compile_parameter_list
77+
# b.parameterList do
78+
return 0 if current_token == ')'
79+
80+
n = 0
81+
consume_separated(',') do
82+
kind = :arg
83+
84+
type = current_token # int, char, etc.
85+
consume_type
86+
87+
name = current_token
88+
@symbols.define(name, type, kind)
89+
consume_identifier(name)
90+
91+
n += 1
92+
end
93+
# end
94+
95+
n
96+
end
97+
98+
def compile_subroutine_body
99+
# b.subroutineBody do
100+
consume_wrapped('{') do
101+
while current_token == "var"
102+
compile_var_dec
103+
end
104+
105+
compile_statements
106+
end
107+
# end
108+
end
109+
110+
def compile_statements
111+
# b.statements do
112+
loop do
113+
case current_token
114+
when 'let'
115+
compile_let
116+
when 'do'
117+
compile_do
118+
when 'return'
119+
compile_return
120+
when 'if'
121+
compile_if
122+
when 'while'
123+
compile_while
124+
else
125+
break
126+
end
127+
end
128+
# end
129+
end
130+
131+
def compile_while
132+
# b.whileStatement do
133+
consume(Tokenizer::KEYWORD, 'while')
134+
135+
consume_wrapped('(') do
136+
compile_expression
137+
end
138+
139+
consume_wrapped('{') do
140+
compile_statements
141+
end
142+
# end
143+
end
144+
145+
def compile_var_dec
146+
# b.varDec do
147+
consume(Tokenizer::KEYWORD, 'var')
148+
149+
kind = :var
150+
151+
type = current_token
152+
consume_type
153+
154+
consume_separated(',') do
155+
name = current_token
156+
@symbols.define(name, type, kind)
157+
consume_identifier(name)
158+
end
159+
160+
consume(Tokenizer::SYMBOL, ';')
161+
# end
162+
end
163+
164+
def compile_let
165+
# b.letStatement do
166+
consume(Tokenizer::KEYWORD, 'let')
167+
168+
consume_identifier
169+
170+
try_consume_wrapped('[') do
171+
compile_expression
172+
end
173+
174+
consume(Tokenizer::SYMBOL, '=')
175+
176+
compile_expression
177+
178+
consume(Tokenizer::SYMBOL, ';')
179+
# end
180+
end
181+
182+
def compile_do
183+
# b.doStatement do
184+
consume(Tokenizer::KEYWORD, 'do')
185+
186+
consume_subroutine_call
187+
188+
consume(Tokenizer::SYMBOL, ';')
189+
# end
190+
end
191+
192+
def compile_return
193+
# b.returnStatement do
194+
consume(Tokenizer::KEYWORD, 'return')
195+
196+
compile_expression unless current_token == ';'
197+
198+
consume(Tokenizer::SYMBOL, ';')
199+
# end
200+
end
201+
202+
def compile_if
203+
# b.ifStatement do
204+
consume(Tokenizer::KEYWORD, 'if')
205+
206+
consume_wrapped('(') do
207+
compile_expression
208+
end
209+
210+
consume_wrapped('{') do
211+
compile_statements
212+
end
213+
214+
if try_consume(Tokenizer::KEYWORD, 'else')
215+
consume_wrapped('{') do
216+
compile_statements
217+
end
218+
end
219+
# end
220+
end
221+
222+
def compile_expression_list
223+
# b.expressionList do
224+
return if current_token == ')'
225+
226+
consume_separated(',') do
227+
compile_expression
228+
end
229+
# end
230+
end
231+
232+
def compile_expression
233+
# b.expression do
234+
compile_term
235+
236+
while %w[+ - * / & | < > =].include? current_token
237+
consume(Tokenizer::SYMBOL)
238+
compile_term
239+
end
240+
# end
241+
end
242+
243+
def compile_term
244+
# b.term do
245+
return if try_consume(Tokenizer::INT_CONST) ||
246+
try_consume(Tokenizer::STRING_CONST) ||
247+
try_consume_wrapped('(') { compile_expression }
248+
249+
case current_token
250+
when 'true', 'false', 'null', 'this' #keywordConst
251+
consume(Tokenizer::KEYWORD)
252+
when '-', '~' # unary op
253+
consume(Tokenizer::SYMBOL)
254+
compile_term
255+
else
256+
name = current_token
257+
input.advance
258+
259+
case current_token
260+
when '['
261+
# b.identifier(
262+
# name,
263+
# type: @symbols.type_of(name),
264+
# kind: @symbols.kind_of(name),
265+
# index: @symbols.index_of(name)
266+
# )
267+
268+
consume_wrapped('[') do
269+
compile_expression
270+
end
271+
when '.', '('
272+
# b.identifier(name)
273+
consume_subroutine_call(skip_identifier: true)
274+
else
275+
# b.identifier(
276+
# name,
277+
# type: @symbols.type_of(name),
278+
# kind: @symbols.kind_of(name),
279+
# index: @symbols.index_of(name)
280+
# )
281+
end
282+
end
283+
# end
284+
end
285+
286+
private
287+
288+
def consume(expected_type, expected_token = nil)
289+
unless try_consume(expected_type, expected_token)
290+
if expected_token
291+
raise "expected a #{expected_type} `#{expected_token}`, got #{current_type} `#{current_token}`"
292+
else
293+
raise "expected any #{expected_type}, got #{current_type} `#{current_token}`"
294+
end
295+
end
296+
end
297+
298+
def try_consume(expected_type, expected_token = nil)
299+
return false unless current_type == expected_type &&
300+
current_token == (expected_token || current_token)
301+
302+
# b.tag!(current_type, current_token)
303+
304+
input.advance if input.has_more_tokens?
305+
true
306+
end
307+
308+
def consume_identifier(name = current_token)
309+
# b.identifier(
310+
# name,
311+
# type: @symbols.type_of(name),
312+
# kind: @symbols.kind_of(name),
313+
# index: @symbols.index_of(name)
314+
# )
315+
316+
input.advance if input.has_more_tokens?
317+
end
318+
319+
def consume_wrapped(opening, &block)
320+
unless try_consume_wrapped(opening) { block.call }
321+
raise "expected wrapping `#{opening}`, got #{current_type}, `#{current_token}`"
322+
end
323+
end
324+
325+
def try_consume_wrapped(opening)
326+
closing = case opening
327+
when '(' then ')'
328+
when '[' then ']'
329+
when '{' then '}'
330+
else raise "Unknown opening brace: `#{opening}`"
331+
end
332+
333+
if try_consume(Tokenizer::SYMBOL, opening)
334+
yield
335+
consume(Tokenizer::SYMBOL, closing)
336+
true
337+
end
338+
end
339+
340+
def b
341+
@builder
342+
end
343+
344+
def current_type
345+
input.token_type
346+
end
347+
348+
def current_token
349+
case current_type
350+
when Tokenizer::KEYWORD
351+
input.keyword
352+
when Tokenizer::SYMBOL
353+
input.symbol
354+
when Tokenizer::IDENTIFIER
355+
input.identifier
356+
when Tokenizer::INT_CONST
357+
input.int_val
358+
when Tokenizer::STRING_CONST
359+
input.string_val
360+
end
361+
end
362+
363+
def consume_separated(sep)
364+
begin
365+
yield
366+
end while try_consume(Tokenizer::SYMBOL, sep)
367+
end
368+
369+
def consume_type
370+
case current_type
371+
when Tokenizer::KEYWORD
372+
if %w[int char boolean].include? current_token
373+
consume(Tokenizer::KEYWORD)
374+
end
375+
else
376+
consume(Tokenizer::IDENTIFIER)
377+
end
378+
end
379+
380+
def consume_subroutine_call(skip_identifier: false)
381+
consume(Tokenizer::IDENTIFIER) unless skip_identifier
382+
383+
if try_consume(Tokenizer::SYMBOL, '.')
384+
consume(Tokenizer::IDENTIFIER)
385+
end
386+
387+
consume_wrapped('(') do
388+
compile_expression_list
389+
end
390+
end
391+
392+
def full_method_name
393+
"#{@class_name}.#{current_token}"
394+
end
395+
end

0 commit comments

Comments
 (0)