mirror of
https://github.com/samsonjs/compiler.git
synced 2026-03-25 08:45:52 +00:00
119 lines
2.8 KiB
Ruby
119 lines
2.8 KiB
Ruby
# A compiler skeleton, or cradle, as described by Jack Crenshaw in his
|
|
# famous book "Let's Build a Compiler". At least in the beginning,
|
|
# this code will closely reflect the Pascal code written by Jack.
|
|
# Over time it may become more idiomatic, however this is an academic
|
|
# exercise.
|
|
|
|
class ParseError < StandardError; end
|
|
|
|
class Compiler
|
|
def initialize(input=STDIN)
|
|
@look = '' # next lookahead char
|
|
@input = input # stream to read from
|
|
|
|
# seed the lexer
|
|
get_char
|
|
end
|
|
|
|
# Read the next character from the input stream
|
|
def get_char
|
|
@look = @input.getc
|
|
@look = @look.chr if @look
|
|
end
|
|
|
|
# Report error and halt
|
|
def abort(msg)
|
|
raise ParseError, msg
|
|
end
|
|
|
|
# Report what was expected
|
|
def expected(what)
|
|
if eof?
|
|
raise ParseError, "Premature end of file, expected: #{what}."
|
|
else
|
|
raise ParseError, "Expected: #{what}, got: #{@look}."
|
|
end
|
|
end
|
|
|
|
# Match a specific input character
|
|
def match(char)
|
|
if @look == char
|
|
get_char
|
|
else
|
|
expected("'#{char}'")
|
|
end
|
|
end
|
|
|
|
# Recognize an alphabetical character
|
|
def is_alpha(char)
|
|
('A'..'Z') === char.upcase
|
|
end
|
|
|
|
# Recognize a decimal digit
|
|
def is_digit(char)
|
|
('0'..'9') === char
|
|
end
|
|
|
|
# Get an identifier
|
|
def get_name
|
|
expected('identifier') unless is_alpha(@look)
|
|
c = @look
|
|
get_char
|
|
return c
|
|
end
|
|
|
|
# Get a number
|
|
def get_num
|
|
expected('integer') unless is_digit(@look)
|
|
c = @look
|
|
get_char
|
|
return c
|
|
end
|
|
|
|
# Print a tab followed by a string and a newline
|
|
def emit(s)
|
|
puts "\t#{s}"
|
|
end
|
|
|
|
# Parse and translate a single mathematical term. Result is in eax.
|
|
def term
|
|
emit("mov eax, #{get_num}")
|
|
end
|
|
|
|
# Parse an addition operator and the 2nd term. The 1st term is
|
|
# expected in ebx, and is added to the 2nd term leaving the result
|
|
# in eax.
|
|
def add
|
|
match('+')
|
|
term # result in eax
|
|
emit("add eax, ebx")
|
|
end
|
|
|
|
# Parse a subtraction operator and the 2nd term (b). The 1st term
|
|
# (a) is expected in ebx, and the b is subtracted from a
|
|
# leaving the result in eax.
|
|
def subtract
|
|
match('-')
|
|
term # result in eax (b)
|
|
emit("sub eax, ebx") # subtract a from b (this is backwards)
|
|
emit("neg eax") # fix things up. -(b-a) == a-b
|
|
end
|
|
|
|
# Parse and translate a mathematical expression of terms. Result is
|
|
# in eax.
|
|
def expression
|
|
term # result is in eax
|
|
emit("mov ebx, eax") # move 1st term to ebx (expected by
|
|
# add & subtract)
|
|
case @look
|
|
when '+': add
|
|
when '-': subtract
|
|
else
|
|
expected('Addition or subtraction operator (+ or -)')
|
|
end
|
|
end
|
|
|
|
def eof?
|
|
@input.eof? && @look.nil?
|
|
end
|
|
end
|