mirror of
https://github.com/samsonjs/compiler.git
synced 2026-03-25 08:45:52 +00:00
[NEW] Rudimentary if statement. No else yet.
This commit is contained in:
parent
cc83fdfabc
commit
f5678a312e
4 changed files with 204 additions and 125 deletions
5
Makefile
5
Makefile
|
|
@ -5,3 +5,8 @@ build:
|
|||
# $? indicates success as per unix convention
|
||||
./test
|
||||
|
||||
clean:
|
||||
@rm -f test.o
|
||||
@rm -f test
|
||||
@rm -f test.asm
|
||||
|
||||
|
|
|
|||
306
compiler.rb
306
compiler.rb
|
|
@ -6,7 +6,12 @@
|
|||
# sjs
|
||||
# may 2009
|
||||
|
||||
class ParseError < StandardError; end
|
||||
class ParseError < StandardError
|
||||
attr_reader :caller
|
||||
def initialize(caller)
|
||||
@caller = caller
|
||||
end
|
||||
end
|
||||
|
||||
class Compiler
|
||||
attr_reader :data, :bss, :code
|
||||
|
|
@ -17,121 +22,20 @@ class Compiler
|
|||
@data = '' # data section
|
||||
@bss = '' # bss section
|
||||
@code = '' # code section
|
||||
@vars = {}
|
||||
@vars = {} # symbol table
|
||||
@num_labels = 0 # used to generate unique labels
|
||||
@keywords = %w[i e] # reserved words (... constant?)
|
||||
|
||||
# seed the lexer
|
||||
get_char
|
||||
skip_whitespace
|
||||
end
|
||||
|
||||
def parse
|
||||
statement until eof?
|
||||
block
|
||||
expected('end of file'.to_sym) unless eof?
|
||||
[@data, @bss, @code]
|
||||
end
|
||||
|
||||
# Read the next character from the input stream.
|
||||
def get_char
|
||||
@look = if @input.eof?
|
||||
nil
|
||||
else
|
||||
@input.readbyte.chr
|
||||
end
|
||||
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} (##{@look[0]})."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Recognize an alphabetical character.
|
||||
def alpha?(char)
|
||||
('A'..'Z') === char.upcase
|
||||
end
|
||||
|
||||
# Recognize a decimal digit.
|
||||
def digit?(char)
|
||||
('0'..'9') === char
|
||||
end
|
||||
|
||||
# Recognize an alphanumeric character.
|
||||
def alnum?(char)
|
||||
alpha?(char) || digit?(char)
|
||||
end
|
||||
|
||||
def whitespace?(char)
|
||||
char == ' ' || char == '\t'
|
||||
end
|
||||
|
||||
|
||||
# Match a specific input character.
|
||||
def match(char)
|
||||
expected("'#{char}'") unless @look == char
|
||||
get_char
|
||||
skip_whitespace
|
||||
end
|
||||
|
||||
# Parse zero or more consecutive characters for which the test is
|
||||
# true.
|
||||
def many(test)
|
||||
token = ''
|
||||
while test.call(@look)
|
||||
token << @look
|
||||
get_char
|
||||
end
|
||||
skip_whitespace
|
||||
token
|
||||
end
|
||||
|
||||
# Get an identifier.
|
||||
def get_name
|
||||
expected('identifier') unless alpha?(@look)
|
||||
many(method(:alnum?))
|
||||
end
|
||||
|
||||
# Get a number.
|
||||
def get_num
|
||||
expected('integer') unless digit?(@look)
|
||||
many(method(:digit?))
|
||||
end
|
||||
|
||||
# Skip all leading whitespace.
|
||||
def skip_whitespace
|
||||
get_char while whitespace?(@look)
|
||||
end
|
||||
|
||||
|
||||
# Define a constant in the .data section.
|
||||
def equ(name, value)
|
||||
@data << "#{name}\tequ #{value}"
|
||||
end
|
||||
|
||||
# Define a variable with the given name and size (in dwords).
|
||||
def var(name, dwords=1)
|
||||
unless @vars[name]
|
||||
@bss << "#{name}: resd #{dwords}\n"
|
||||
@vars[name] = name
|
||||
# else
|
||||
# raise ParseError, "identifier #{name} redefined"
|
||||
end
|
||||
end
|
||||
|
||||
# Emit a line of code wrapped between a tab and a newline.
|
||||
def emit(s)
|
||||
@code << "\t#{s}\n"
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Parse and translate an identifier or function call.
|
||||
def identifier
|
||||
|
|
@ -159,7 +63,7 @@ class Compiler
|
|||
elsif digit?(@look)
|
||||
x86_mov(:eax, get_num)
|
||||
else
|
||||
expected("a number, identifier, or an expression wrapped in parens")
|
||||
expected('integer, identifier, or parenthesized expression'.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -221,21 +125,40 @@ class Compiler
|
|||
x86_mov("dword [#{name}]", :eax)
|
||||
end
|
||||
|
||||
# Parse one or more newlines.
|
||||
def newline
|
||||
if @look == "\n" || @look == "\r"
|
||||
get_char while @look == "\n" || @look == "\r"
|
||||
# Parse an assignment expression followed by a newline.
|
||||
def statement
|
||||
case @look
|
||||
when 'i'
|
||||
if_stmt
|
||||
else
|
||||
expected('newline')
|
||||
assignment
|
||||
newline
|
||||
end
|
||||
end
|
||||
|
||||
# Parse an assignment expression followed by a newline.
|
||||
def statement
|
||||
assignment
|
||||
newline
|
||||
# Parse a code block.
|
||||
def block
|
||||
until @look == 'e' || eof?
|
||||
statement
|
||||
skip_any_whitespace
|
||||
end
|
||||
end
|
||||
|
||||
# Parse an if statement.
|
||||
def if_stmt
|
||||
match('i')
|
||||
label = unique_label
|
||||
condition
|
||||
x86_jz(label)
|
||||
block
|
||||
match('e')
|
||||
emit_label(label)
|
||||
end
|
||||
|
||||
# Dummy condition function. Will handle boolean expressions later.
|
||||
def condition
|
||||
emit('<condition>')
|
||||
end
|
||||
|
||||
# Parse an addition operator and the 2nd term (b). The result is
|
||||
# left in eax. The 1st term (a) is expected on the stack.
|
||||
|
|
@ -279,9 +202,10 @@ class Compiler
|
|||
|
||||
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
############
|
||||
# internal #
|
||||
############
|
||||
|
||||
|
||||
def eof?
|
||||
@input.eof? && @look.nil?
|
||||
|
|
@ -296,6 +220,144 @@ private
|
|||
end
|
||||
|
||||
|
||||
# Read the next character from the input stream.
|
||||
def get_char
|
||||
@look = if @input.eof?
|
||||
nil
|
||||
else
|
||||
@input.readbyte.chr
|
||||
end
|
||||
end
|
||||
|
||||
# Report error and halt
|
||||
def abort(msg)
|
||||
raise ParseError, msg
|
||||
end
|
||||
|
||||
# Report what was expected
|
||||
def expected(what, options={})
|
||||
got = options.has_key?(:got) ? options[:got] : @look
|
||||
got, what = *[got, what].map {|x| x.is_a?(Symbol) ? x : "'#{x}'" }
|
||||
if eof?
|
||||
raise ParseError.new(caller), "Premature end of file, expected: #{what}."
|
||||
else
|
||||
raise ParseError.new(caller), "Expected #{what} but got #{got}. (\"...#{@input.readline rescue '(EOF)'}...\")"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Recognize an alphabetical character.
|
||||
def alpha?(char)
|
||||
('A'..'Z') === char.upcase
|
||||
end
|
||||
|
||||
# Recognize a decimal digit.
|
||||
def digit?(char)
|
||||
('0'..'9') === char
|
||||
end
|
||||
|
||||
# Recognize an alphanumeric character.
|
||||
def alnum?(char)
|
||||
alpha?(char) || digit?(char)
|
||||
end
|
||||
|
||||
def whitespace?(char)
|
||||
char == ' ' || char == "\t"
|
||||
end
|
||||
|
||||
def any_whitespace?(char)
|
||||
char == ' ' || char == "\t" || char == "\n" || char == "\r"
|
||||
end
|
||||
|
||||
# Parse one or more newlines.
|
||||
def newline
|
||||
if @look == "\n" || @look == "\r"
|
||||
get_char while @look == "\n" || @look == "\r"
|
||||
else
|
||||
expected(:newline)
|
||||
end
|
||||
end
|
||||
|
||||
# Match a specific input character.
|
||||
def match(char)
|
||||
expected(char) unless @look == char
|
||||
get_char
|
||||
skip_whitespace
|
||||
end
|
||||
|
||||
# Parse zero or more consecutive characters for which the test is
|
||||
# true.
|
||||
def many(test)
|
||||
token = ''
|
||||
while test.call(@look)
|
||||
token << @look
|
||||
get_char
|
||||
end
|
||||
skip_whitespace
|
||||
token
|
||||
end
|
||||
|
||||
|
||||
# Get an identifier.
|
||||
def get_name
|
||||
expected(:identifier) unless alpha?(@look)
|
||||
name = many(method(:alnum?))
|
||||
if @keywords.include?(name)
|
||||
expected(:identifier, :got => :keyword)
|
||||
end
|
||||
name
|
||||
end
|
||||
|
||||
# Get a number.
|
||||
def get_num
|
||||
expected(:integer) unless digit?(@look)
|
||||
many(method(:digit?))
|
||||
end
|
||||
|
||||
# Skip leading whitespace.
|
||||
def skip_whitespace
|
||||
get_char while whitespace?(@look)
|
||||
end
|
||||
|
||||
# Skip leading whitespace including newlines.
|
||||
def skip_any_whitespace
|
||||
get_char while any_whitespace?(@look)
|
||||
end
|
||||
|
||||
|
||||
# Define a constant in the .data section.
|
||||
def equ(name, value)
|
||||
@data << "#{name}\tequ #{value}"
|
||||
end
|
||||
|
||||
# Define a variable with the given name and size (in dwords).
|
||||
def var(name, dwords=1)
|
||||
unless @vars[name]
|
||||
@bss << "#{name}: resd #{dwords}\n"
|
||||
@vars[name] = name
|
||||
# else
|
||||
# raise ParseError, "identifier #{name} redefined"
|
||||
end
|
||||
end
|
||||
|
||||
# Emit a line of code wrapped between a tab and a newline.
|
||||
def emit(code, options={})
|
||||
tab = options.has_key?(:tab) ? options[:tab] : "\t"
|
||||
@code << "#{tab}#{code}\n"
|
||||
end
|
||||
|
||||
def emit_label(name=unique_label)
|
||||
emit("#{name}:", :tab => nil)
|
||||
end
|
||||
|
||||
# Generate a unique label.
|
||||
def unique_label
|
||||
@num_labels += 1
|
||||
"L#{sprintf "%06d", @num_labels}"
|
||||
end
|
||||
|
||||
|
||||
# Some asm methods for convenience and arity checks.
|
||||
|
||||
def x86_mov(dest, src)
|
||||
|
|
@ -337,4 +399,8 @@ private
|
|||
def x86_xor(op1, op2)
|
||||
emit("xor #{op1}, #{op2}")
|
||||
end
|
||||
|
||||
def x86_jz(label)
|
||||
emit("jz #{label}")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
17
test.code
17
test.code
|
|
@ -4,8 +4,15 @@ somethinglong=65536
|
|||
b=5*(3-5)
|
||||
c=a-1
|
||||
d=-1
|
||||
e=b*2+2
|
||||
f=e-27/9
|
||||
g=f-8/2
|
||||
h=g-4*(5+5+5)
|
||||
i=h+85
|
||||
f=b*2+2
|
||||
g=f-27/9
|
||||
h=g-8/2
|
||||
j=h-4*(5+5+5)
|
||||
k=h+85
|
||||
i b=3
|
||||
i c=4
|
||||
e
|
||||
e
|
||||
ib=3
|
||||
ic=4
|
||||
ee
|
||||
1
test.rb
1
test.rb
|
|
@ -10,6 +10,7 @@ def parse(input)
|
|||
rescue ParseError => e
|
||||
error("[error] #{e.message}")
|
||||
error("Aborting!")
|
||||
error(e.caller)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue