mirror of
https://github.com/samsonjs/compiler.git
synced 2026-04-27 14:57:45 +00:00
[NEW] Parse assignment statements. Added template and test code.
This commit is contained in:
parent
ff2b68a8f2
commit
268c6f6c29
5 changed files with 191 additions and 66 deletions
4
Makefile
4
Makefile
|
|
@ -1,7 +1,5 @@
|
||||||
build:
|
build:
|
||||||
# ruby test.rb '5-5+3-2-1'
|
ruby test.rb test.code
|
||||||
# ruby test.rb '5*3-5*2+3-9/3-1*1-8/2'
|
|
||||||
ruby test.rb '5*(3-5)*2+2-9/3-8/2-4*(5+5+5)'
|
|
||||||
nasm -f elf -g -o test.o test.asm
|
nasm -f elf -g -o test.o test.asm
|
||||||
ld -o test test.o
|
ld -o test test.o
|
||||||
# $? indicates success as per unix convention
|
# $? indicates success as per unix convention
|
||||||
|
|
|
||||||
181
compiler.rb
181
compiler.rb
|
|
@ -9,19 +9,31 @@
|
||||||
class ParseError < StandardError; end
|
class ParseError < StandardError; end
|
||||||
|
|
||||||
class Compiler
|
class Compiler
|
||||||
def initialize(input=STDIN, output=STDOUT)
|
attr_reader :data, :bss, :code
|
||||||
|
|
||||||
|
def initialize(input=STDIN)
|
||||||
@look = '' # next lookahead char
|
@look = '' # next lookahead char
|
||||||
@input = input # stream to read from
|
@input = input # stream to read from
|
||||||
@output = output # stream to write to
|
@data = '' # data section
|
||||||
|
@bss = '' # bss section
|
||||||
|
@code = '' # code section
|
||||||
|
|
||||||
# seed the lexer
|
# seed the lexer
|
||||||
get_char
|
get_char
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse
|
||||||
|
statement until eof?
|
||||||
|
[@data, @bss, @code]
|
||||||
|
end
|
||||||
|
|
||||||
# Read the next character from the input stream
|
# Read the next character from the input stream
|
||||||
def get_char
|
def get_char
|
||||||
@look = @input.getc
|
@look = if @input.eof?
|
||||||
@look = @look.chr if @look
|
nil
|
||||||
|
else
|
||||||
|
@input.readbyte.chr
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Report error and halt
|
# Report error and halt
|
||||||
|
|
@ -34,7 +46,7 @@ class Compiler
|
||||||
if eof?
|
if eof?
|
||||||
raise ParseError, "Premature end of file, expected: #{what}."
|
raise ParseError, "Premature end of file, expected: #{what}."
|
||||||
else
|
else
|
||||||
raise ParseError, "Expected: #{what}, got: #{@look}."
|
raise ParseError, "Expected: #{what}, got: #{@look} (##{@look[0]})."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -48,18 +60,18 @@ class Compiler
|
||||||
end
|
end
|
||||||
|
|
||||||
# Recognize an alphabetical character
|
# Recognize an alphabetical character
|
||||||
def is_alpha(char)
|
def alpha?(char)
|
||||||
('A'..'Z') === char.upcase
|
('A'..'Z') === char.upcase
|
||||||
end
|
end
|
||||||
|
|
||||||
# Recognize a decimal digit
|
# Recognize a decimal digit
|
||||||
def is_digit(char)
|
def digit?(char)
|
||||||
('0'..'9') === char
|
('0'..'9') === char
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get an identifier
|
# Get an identifier
|
||||||
def get_name
|
def get_name
|
||||||
expected('identifier') unless is_alpha(@look)
|
expected('identifier') unless alpha?(@look)
|
||||||
c = @look
|
c = @look
|
||||||
get_char
|
get_char
|
||||||
return c
|
return c
|
||||||
|
|
@ -67,29 +79,60 @@ class Compiler
|
||||||
|
|
||||||
# Get a number
|
# Get a number
|
||||||
def get_num
|
def get_num
|
||||||
expected('integer') unless is_digit(@look)
|
expected('integer') unless digit?(@look)
|
||||||
c = @look
|
c = @look
|
||||||
get_char
|
get_char
|
||||||
return c
|
return c
|
||||||
end
|
end
|
||||||
|
|
||||||
# Print a tab followed by a string and a newline
|
# 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)
|
||||||
|
@bss << "#{name}: resd #{dwords}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Emit a line of code wrapped between a tab and a newline.
|
||||||
def emit(s)
|
def emit(s)
|
||||||
@output.puts("\t#{s}")
|
@code << "\t#{s}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parse and translate an identifier or function call.
|
||||||
|
def identifier
|
||||||
|
name = get_name
|
||||||
|
|
||||||
|
if @look == '('
|
||||||
|
# function call
|
||||||
|
match('(')
|
||||||
|
match(')')
|
||||||
|
call(name)
|
||||||
|
else
|
||||||
|
# variable access
|
||||||
|
mov("eax", "dword [#{name}]")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse and translate a single factor. Result is in eax.
|
# Parse and translate a single factor. Result is in eax.
|
||||||
def factor
|
def factor
|
||||||
if @look == '('
|
case
|
||||||
|
when @look == '('
|
||||||
match('(')
|
match('(')
|
||||||
expression
|
expression
|
||||||
match(')')
|
match(')')
|
||||||
|
when alpha?(@look)
|
||||||
|
identifier
|
||||||
|
when digit?(@look)
|
||||||
|
mov("eax", get_num)
|
||||||
else
|
else
|
||||||
emit("mov eax, #{get_num}")
|
expected("a number, identifier, or an expression wrapped in parens")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse and translate a single term. Result is in eax.
|
# Parse and translate a single term (factor or mulop). Result is in
|
||||||
|
# eax.
|
||||||
def term
|
def term
|
||||||
factor # Result in eax.
|
factor # Result in eax.
|
||||||
while mulop?
|
while mulop?
|
||||||
|
|
@ -97,25 +140,25 @@ class Compiler
|
||||||
# multiply & divide. Because they leave their results in eax
|
# multiply & divide. Because they leave their results in eax
|
||||||
# associativity works. Each interim result is pushed on the
|
# associativity works. Each interim result is pushed on the
|
||||||
# stack here.
|
# stack here.
|
||||||
emit("push eax")
|
push("eax")
|
||||||
|
|
||||||
case @look
|
if @look == '*'
|
||||||
when '*': multiply
|
multiply
|
||||||
when '/': divide
|
|
||||||
else
|
else
|
||||||
expected('Multiplication or division operator (* or /)')
|
divide
|
||||||
end
|
end
|
||||||
emit("add esp, 4") # Remove the 1st factor from the stack.
|
|
||||||
|
add("esp", 4) # Remove the 1st factor from the stack.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse and translate a mathematical expression of terms. Result is
|
# Parse and translate a general expression of terms. Result is
|
||||||
# in eax.
|
# in eax.
|
||||||
def expression
|
def expression
|
||||||
if addop?
|
if addop?
|
||||||
# Clear eax simulating a zero before unary plus and minus
|
# Clear eax simulating a zero before unary plus and minus
|
||||||
# operations.
|
# operations.
|
||||||
emit("xor eax, eax")
|
xor("eax", "eax")
|
||||||
else
|
else
|
||||||
term # Result is in eax.
|
term # Result is in eax.
|
||||||
end
|
end
|
||||||
|
|
@ -125,24 +168,49 @@ class Compiler
|
||||||
# subtract. Because they leave their results in eax
|
# subtract. Because they leave their results in eax
|
||||||
# associativity works. Each interim result is pushed on the
|
# associativity works. Each interim result is pushed on the
|
||||||
# stack here.
|
# stack here.
|
||||||
emit("push eax")
|
push("eax")
|
||||||
|
|
||||||
case @look
|
if @look == '+'
|
||||||
when '+': add
|
add
|
||||||
when '-': subtract
|
|
||||||
else
|
else
|
||||||
expected('Addition or subtraction operator (+ or -)')
|
subtract
|
||||||
end
|
end
|
||||||
emit("add esp, 4") # Remove 1st term (a) from the stack.
|
|
||||||
|
add("esp", 4) # Remove 1st term (a) from the stack.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse an assignment statement. Value is in eax.
|
||||||
|
def assignment
|
||||||
|
name = get_name
|
||||||
|
match('=')
|
||||||
|
expression
|
||||||
|
var(name)
|
||||||
|
mov("dword [#{name}]", "eax")
|
||||||
|
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
|
||||||
|
|
||||||
|
# Parse an assignment expression followed by a newline.
|
||||||
|
def statement
|
||||||
|
assignment
|
||||||
|
newline
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Parse an addition operator and the 2nd term (b). The result is
|
# Parse an addition operator and the 2nd term (b). The result is
|
||||||
# left in eax. The 1st term (a) is expected on the stack.
|
# left in eax. The 1st term (a) is expected on the stack.
|
||||||
def add
|
def add
|
||||||
match('+')
|
match('+')
|
||||||
term # Result is in eax.
|
term # Result is in eax.
|
||||||
emit("add eax, [esp]") # Add a to b.
|
add('eax', '[esp]') # Add a to b.
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse a subtraction operator and the 2nd term (b). The result is
|
# Parse a subtraction operator and the 2nd term (b). The result is
|
||||||
|
|
@ -150,8 +218,8 @@ class Compiler
|
||||||
def subtract
|
def subtract
|
||||||
match('-')
|
match('-')
|
||||||
term # Result is in eax.
|
term # Result is in eax.
|
||||||
emit("sub eax, [esp]") # Subtract a from b (this is backwards).
|
sub('eax', '[esp]') # Subtract a from b (this is backwards).
|
||||||
emit("neg eax") # Fix things up. -(b-a) == a-b
|
neg('eax') # Fix things up. -(b-a) == a-b
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse an addition operator and the 2nd term (b). The result is
|
# Parse an addition operator and the 2nd term (b). The result is
|
||||||
|
|
@ -159,7 +227,7 @@ class Compiler
|
||||||
def multiply
|
def multiply
|
||||||
match('*')
|
match('*')
|
||||||
factor # Result is in eax.
|
factor # Result is in eax.
|
||||||
emit("imul dword [esp]") # Multiply a by b.
|
imul('dword [esp]') # Multiply a by b.
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse a division operator and the divisor (b). The result is
|
# Parse a division operator and the divisor (b). The result is
|
||||||
|
|
@ -167,11 +235,17 @@ class Compiler
|
||||||
def divide
|
def divide
|
||||||
match('/')
|
match('/')
|
||||||
factor # Result is in eax.
|
factor # Result is in eax.
|
||||||
emit("xchg eax, [esp]") # Swap the divisor and dividend into
|
xchg('eax', '[esp]') # Swap the divisor and dividend into
|
||||||
# the correct places.
|
# the correct places.
|
||||||
emit("idiv dword [esp]") # Divide a (eax) by b ([esp]).
|
idiv('dword [esp]') # Divide a (eax) by b ([esp]).
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#######
|
||||||
|
private
|
||||||
|
#######
|
||||||
|
|
||||||
def eof?
|
def eof?
|
||||||
@input.eof? && @look.nil?
|
@input.eof? && @look.nil?
|
||||||
end
|
end
|
||||||
|
|
@ -183,4 +257,43 @@ class Compiler
|
||||||
def mulop?
|
def mulop?
|
||||||
@look == '*' || @look == '/'
|
@look == '*' || @look == '/'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Some asm methods for convenience and arity checks.
|
||||||
|
|
||||||
|
def mov(dest, src)
|
||||||
|
emit("mov #{dest}, #{src}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(dest, src)
|
||||||
|
emit("add #{dest}, #{src}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def sub(dest, src)
|
||||||
|
emit("sub #{dest}, #{src}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def imul(op)
|
||||||
|
emit("imul #{op}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def idiv(op)
|
||||||
|
emit("idiv #{op}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def push(reg)
|
||||||
|
emit("push #{reg}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(label)
|
||||||
|
emit("call #{label}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def neg(reg)
|
||||||
|
emit("neg #{reg}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def xchg(op1, op2)
|
||||||
|
emit("xchg #{op1}, #{op2}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
12
template.asm
Normal file
12
template.asm
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
GLOBAL _start
|
||||||
|
SECTION .data
|
||||||
|
{data}
|
||||||
|
SECTION .bss
|
||||||
|
{bss}
|
||||||
|
SECTION .text
|
||||||
|
_start:
|
||||||
|
{code}
|
||||||
|
;; The result in eax is the exit code, move it to ebx.
|
||||||
|
mov ebx, eax
|
||||||
|
mov eax, 1 ; _exit syscall
|
||||||
|
int 0x80 ; call Linux
|
||||||
5
test.code
Normal file
5
test.code
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
a=9
|
||||||
|
5*(3-5)*2+2-9/3-8/2-4*(5+5+5)
|
||||||
|
a-1
|
||||||
|
x()+1
|
||||||
|
-1
|
||||||
53
test.rb
53
test.rb
|
|
@ -1,39 +1,36 @@
|
||||||
require 'compiler'
|
require 'compiler'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
|
|
||||||
MaxRetries = 1
|
|
||||||
|
|
||||||
def error(msg) STDERR.puts(msg) end
|
def error(msg) STDERR.puts(msg) end
|
||||||
|
|
||||||
# Main program
|
def parse(input)
|
||||||
def main
|
compiler = Compiler.new(input)
|
||||||
retries = 0
|
compiler.parse # tuple of [data, bss, code]
|
||||||
input = StringIO.new(ARGV[0] || '5-5')
|
|
||||||
output = StringIO.new
|
rescue ParseError => e
|
||||||
parse = Compiler.new(input, output)
|
|
||||||
until parse.eof?
|
|
||||||
begin
|
|
||||||
parse.expression
|
|
||||||
rescue ParseError => e
|
|
||||||
error("[error] #{e.message}")
|
error("[error] #{e.message}")
|
||||||
if retries < MaxRetries
|
|
||||||
retries += 1
|
|
||||||
error("Skipping token...")
|
|
||||||
parse.get_char
|
|
||||||
retry
|
|
||||||
else
|
|
||||||
error("Aborting!")
|
error("Aborting!")
|
||||||
break
|
exit(1)
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
output.string
|
|
||||||
end
|
end
|
||||||
|
|
||||||
code = main
|
def interpolate(template, data)
|
||||||
File.open("test.asm", "w") do |f|
|
data.inject(template) do |template, mapping|
|
||||||
f.puts(File.readlines("prologue.asm"))
|
token, replacement = *mapping
|
||||||
f.puts(code)
|
template.sub("{#{token}}", replacement)
|
||||||
f.puts(File.readlines("epilogue.asm"))
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def main(arg)
|
||||||
|
input = if File.readable?(arg)
|
||||||
|
File.open(arg)
|
||||||
|
else
|
||||||
|
# StringIO.new("5*(3-5)*2+2-9/3-8/2-4*(5+5+5)\n")
|
||||||
|
StringIO.new("a=9\n")
|
||||||
|
end
|
||||||
|
data, bss, code = *parse(input)
|
||||||
|
template = File.read("template.asm")
|
||||||
|
asm = interpolate(template, :data => data, :bss => bss, :code => code)
|
||||||
|
File.open("test.asm", "w") { |f| f.puts(asm) }
|
||||||
|
end
|
||||||
|
|
||||||
|
main(ARGV[0].to_s)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue