Compare commits
3 Commits
147837c011
...
8d015d47f3
Author | SHA1 | Date |
---|---|---|
Danila Fedorin | 8d015d47f3 | |
Danila Fedorin | 96059d6e04 | |
Danila Fedorin | cd0e5c2919 |
|
@ -2,9 +2,9 @@ require "./chalk/*"
|
||||||
require "option_parser"
|
require "option_parser"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
config = Config.parse!
|
config = Ui::Config.parse!
|
||||||
exit unless config.validate!
|
exit unless config.validate!
|
||||||
|
|
||||||
compiler = Compiler.new config
|
compiler = Compiler::Compiler.new config
|
||||||
compiler.run
|
compiler.run
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +1,37 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class BuiltinFunction
|
module Builtin
|
||||||
getter param_count : Int32
|
# A normal function (i.e., a "call" is generated for it)
|
||||||
|
# that is provided by chalk's standard library, and therefore
|
||||||
|
# has predefined output.
|
||||||
|
abstract class BuiltinFunction
|
||||||
|
# Gets the number of parameters this function has.
|
||||||
|
getter param_count : Int32
|
||||||
|
|
||||||
def initialize(@param_count)
|
# Creates a new function with *param_count* parameters.
|
||||||
|
def initialize(@param_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uses the given `Compiler::Emitter` to output code.
|
||||||
|
abstract def generate!(codegen)
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate!(codegen)
|
# A function to which a call is not generated. This function
|
||||||
end
|
# is copied everywhere a call to it occurs. Besides this, the
|
||||||
end
|
# function also accepts trees rather than register numbers,
|
||||||
|
# and therefore can accept and manipulate trees.
|
||||||
|
abstract class InlineFunction
|
||||||
|
# Gets the number of parameters this function has.
|
||||||
|
getter param_count : Int32
|
||||||
|
|
||||||
class InlineFunction
|
# Creates a new function with *param_count* parameters.
|
||||||
getter param_count : Int32
|
def initialize(@param_count)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@param_count)
|
# Generates code like `Compiler::CodeGenerator` would.
|
||||||
end
|
# The *codegen* parameter is used to emit instructions,
|
||||||
|
# the *params* are trees that are being passed as arguments.
|
||||||
def generate!(codegen, params, table, target, free)
|
# See `Compiler::CodeGenerator#generate!` for what the other parameters mean.
|
||||||
|
abstract def generate!(codegen, params, table, target, free)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,141 +2,163 @@ require "./ir.cr"
|
||||||
require "./emitter.cr"
|
require "./emitter.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class CodeGenerator
|
module Compiler
|
||||||
include Emitter
|
# A class that converts a tree into the corresponding
|
||||||
|
# intermediate representation, without optimizing.
|
||||||
|
class CodeGenerator
|
||||||
|
include Emitter
|
||||||
|
|
||||||
RETURN_REG = 14
|
# The register into which the return value of a function is stored.
|
||||||
STACK_REG = 13
|
RETURN_REG = 14
|
||||||
|
# The register into which the "stack pointer" is stored.
|
||||||
|
STACK_REG = 13
|
||||||
|
|
||||||
property instructions : Array(Instruction)
|
# Gets the instructions currently emitted by this code generator.
|
||||||
|
getter instructions
|
||||||
|
|
||||||
def initialize(table, @function : TreeFunction)
|
# Creates a new compiler with the given symbol *table*
|
||||||
@registers = 0
|
# and *function* for which code should be generated.
|
||||||
@instructions = [] of Instruction
|
def initialize(table, @function : Trees::TreeFunction)
|
||||||
@table = Table.new table
|
@registers = 0
|
||||||
|
@instructions = [] of Ir::Instruction
|
||||||
|
@table = Table.new table
|
||||||
|
|
||||||
@function.params.each do |param|
|
@function.params.each do |param|
|
||||||
@table[param] = VarEntry.new @registers
|
@table[param] = VarEntry.new @registers
|
||||||
@registers += 1
|
@registers += 1
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate!(tree, function : InlineFunction, table, target, free)
|
|
||||||
function.generate!(self, tree.params, table, target, free)
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate!(tree, function : TreeFunction | BuiltinFunction, table, target, free)
|
|
||||||
start_at = free
|
|
||||||
# Move I to stack
|
|
||||||
setis
|
|
||||||
# Get to correct stack position
|
|
||||||
addi STACK_REG
|
|
||||||
# Store variables
|
|
||||||
store (start_at - 1) unless start_at == 0
|
|
||||||
# Increment I and stack position
|
|
||||||
load free, start_at
|
|
||||||
opr TokenType::OpAdd, STACK_REG, free
|
|
||||||
addi free
|
|
||||||
|
|
||||||
# Calculate the parameters
|
|
||||||
tree.params.each do |param|
|
|
||||||
generate! param, table, free, free + 1
|
|
||||||
free += 1
|
|
||||||
end
|
|
||||||
# Call the function
|
|
||||||
tree.params.size.times do |time|
|
|
||||||
loadr time, time + start_at
|
|
||||||
end
|
|
||||||
call tree.name
|
|
||||||
|
|
||||||
# Reduce stack pointer
|
|
||||||
load free, start_at
|
|
||||||
opr TokenType::OpSub, STACK_REG, free
|
|
||||||
# Move I to stack
|
|
||||||
setis
|
|
||||||
# Get to correct stack position
|
|
||||||
addi STACK_REG
|
|
||||||
# Restore
|
|
||||||
restore (start_at - 1) unless start_at == 0
|
|
||||||
# Get call value into target
|
|
||||||
loadr target, RETURN_REG
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate!(tree, table, target, free)
|
|
||||||
case tree
|
|
||||||
when TreeId
|
|
||||||
entry = table[tree.id]?
|
|
||||||
raise "Unknown variable" unless entry &&
|
|
||||||
entry.is_a?(VarEntry)
|
|
||||||
loadr target, entry.register
|
|
||||||
when TreeLit
|
|
||||||
load target, tree.lit
|
|
||||||
when TreeOp
|
|
||||||
generate! tree.left, table, target, free
|
|
||||||
generate! tree.right, table, free, free + 1
|
|
||||||
opr tree.op, target, free
|
|
||||||
when TreeCall
|
|
||||||
entry = table[tree.name]?
|
|
||||||
raise "Unknown function" unless entry &&
|
|
||||||
entry.is_a?(FunctionEntry)
|
|
||||||
function = entry.function
|
|
||||||
raise "Invalid call" if tree.params.size != function.param_count
|
|
||||||
generate! tree, function, table, target, free
|
|
||||||
when TreeBlock
|
|
||||||
table = Table.new(table)
|
|
||||||
tree.children.each do |child|
|
|
||||||
free += generate! child, table, free, free + 1
|
|
||||||
end
|
end
|
||||||
when TreeVar
|
end
|
||||||
entry = table[tree.name]?
|
|
||||||
if entry == nil
|
# Generates code for an inline function, with the given *tree* being the `Trees::TreeCall`
|
||||||
entry = VarEntry.new free
|
# that caused the function call. The other parameters are as described in the more general
|
||||||
|
# `#generate!` call.
|
||||||
|
def generate!(tree, function : Builtin::InlineFunction, table, target, free)
|
||||||
|
function.generate!(self, tree.params, table, target, free)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates code for a tree or a builtin function (that is, a call is actually necessary).
|
||||||
|
# I is set to the current stack pointer, the registers are stored, and the call is made.
|
||||||
|
# The registers are then restored. The other parameters are as described in the more general
|
||||||
|
# `#generate!` call.
|
||||||
|
def generate!(tree, function : Trees::TreeFunction | Builtin::BuiltinFunction, table, target, free)
|
||||||
|
start_at = free
|
||||||
|
# Move I to stack
|
||||||
|
setis
|
||||||
|
# Get to correct stack position
|
||||||
|
addi STACK_REG
|
||||||
|
# Store variables
|
||||||
|
store (start_at - 1) unless start_at == 0
|
||||||
|
# Increment I and stack position
|
||||||
|
load free, start_at
|
||||||
|
opr TokenType::OpAdd, STACK_REG, free
|
||||||
|
addi free
|
||||||
|
|
||||||
|
# Calculate the parameters
|
||||||
|
tree.params.each do |param|
|
||||||
|
generate! param, table, free, free + 1
|
||||||
free += 1
|
free += 1
|
||||||
table[tree.name] = entry
|
|
||||||
end
|
end
|
||||||
raise "Unknown variable" unless entry.is_a?(VarEntry)
|
# Call the function
|
||||||
generate! tree.expr, table, entry.register, free
|
tree.params.size.times do |time|
|
||||||
return 1
|
loadr time, time + start_at
|
||||||
when TreeAssign
|
end
|
||||||
entry = table[tree.name]?
|
call tree.name
|
||||||
raise "Unknown variable" unless entry &&
|
|
||||||
entry.is_a?(VarEntry)
|
|
||||||
generate! tree.expr, table, entry.register, free
|
|
||||||
when TreeIf
|
|
||||||
generate! tree.condition, table, free, free + 1
|
|
||||||
sne free, 0
|
|
||||||
jump_inst = jr 0
|
|
||||||
|
|
||||||
old_size = @instructions.size
|
# Reduce stack pointer
|
||||||
generate! tree.block, table, free, free + 1
|
load free, start_at
|
||||||
jump_after = jr 0
|
opr TokenType::OpSub, STACK_REG, free
|
||||||
jump_inst.offset = @instructions.size - old_size + 1
|
# Move I to stack
|
||||||
|
setis
|
||||||
old_size = @instructions.size
|
# Get to correct stack position
|
||||||
generate! tree.otherwise, table, free, free + 1 if tree.otherwise
|
addi STACK_REG
|
||||||
jump_after.offset = @instructions.size - old_size + 1
|
# Restore
|
||||||
when TreeWhile
|
restore (start_at - 1) unless start_at == 0
|
||||||
before_cond = @instructions.size
|
# Get call value into target
|
||||||
generate! tree.condition, table, free, free + 1
|
loadr target, RETURN_REG
|
||||||
sne free, 0
|
|
||||||
cond_jump = jr 0
|
|
||||||
|
|
||||||
old_size = @instructions.size
|
|
||||||
generate! tree.block, table, free, free + 1
|
|
||||||
after_jump = jr 0
|
|
||||||
|
|
||||||
cond_jump.offset = @instructions.size - old_size + 1
|
|
||||||
after_jump.offset = before_cond - instructions.size + 1
|
|
||||||
when TreeReturn
|
|
||||||
generate! tree.rvalue, table, RETURN_REG, free
|
|
||||||
ret
|
|
||||||
end
|
end
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate!
|
# Generates code for a *tree*, using a symbol *table*
|
||||||
generate!(@function.block, @table, -1, @registers)
|
# housing all the names for identifiers in the code.
|
||||||
return @instructions
|
# The result is stored into the *target* register,
|
||||||
|
# and the *free* register is the next register into
|
||||||
|
# which a value can be stored for "scratch work".
|
||||||
|
def generate!(tree, table, target, free)
|
||||||
|
case tree
|
||||||
|
when Trees::TreeId
|
||||||
|
entry = table[tree.id]?
|
||||||
|
raise "Unknown variable" unless entry &&
|
||||||
|
entry.is_a?(VarEntry)
|
||||||
|
loadr target, entry.register
|
||||||
|
when Trees::TreeLit
|
||||||
|
load target, tree.lit
|
||||||
|
when Trees::TreeOp
|
||||||
|
generate! tree.left, table, target, free
|
||||||
|
generate! tree.right, table, free, free + 1
|
||||||
|
opr tree.op, target, free
|
||||||
|
when Trees::TreeCall
|
||||||
|
entry = table[tree.name]?
|
||||||
|
raise "Unknown function" unless entry &&
|
||||||
|
entry.is_a?(FunctionEntry)
|
||||||
|
function = entry.function
|
||||||
|
raise "Invalid call" if tree.params.size != function.param_count
|
||||||
|
generate! tree, function, table, target, free
|
||||||
|
when Trees::TreeBlock
|
||||||
|
table = Table.new(table)
|
||||||
|
tree.children.each do |child|
|
||||||
|
free += generate! child, table, free, free + 1
|
||||||
|
end
|
||||||
|
when Trees::TreeVar
|
||||||
|
entry = table[tree.name]?
|
||||||
|
if entry == nil
|
||||||
|
entry = VarEntry.new free
|
||||||
|
free += 1
|
||||||
|
table[tree.name] = entry
|
||||||
|
end
|
||||||
|
raise "Unknown variable" unless entry.is_a?(VarEntry)
|
||||||
|
generate! tree.expr, table, entry.register, free
|
||||||
|
return 1
|
||||||
|
when Trees::TreeAssign
|
||||||
|
entry = table[tree.name]?
|
||||||
|
raise "Unknown variable" unless entry &&
|
||||||
|
entry.is_a?(VarEntry)
|
||||||
|
generate! tree.expr, table, entry.register, free
|
||||||
|
when Trees::TreeIf
|
||||||
|
generate! tree.condition, table, free, free + 1
|
||||||
|
sne free, 0
|
||||||
|
jump_inst = jr 0
|
||||||
|
|
||||||
|
old_size = @instructions.size
|
||||||
|
generate! tree.block, table, free, free + 1
|
||||||
|
jump_after = jr 0
|
||||||
|
jump_inst.offset = @instructions.size - old_size + 1
|
||||||
|
|
||||||
|
old_size = @instructions.size
|
||||||
|
generate! tree.otherwise, table, free, free + 1 if tree.otherwise
|
||||||
|
jump_after.offset = @instructions.size - old_size + 1
|
||||||
|
when Trees::TreeWhile
|
||||||
|
before_cond = @instructions.size
|
||||||
|
generate! tree.condition, table, free, free + 1
|
||||||
|
sne free, 0
|
||||||
|
cond_jump = jr 0
|
||||||
|
|
||||||
|
old_size = @instructions.size
|
||||||
|
generate! tree.block, table, free, free + 1
|
||||||
|
after_jump = jr 0
|
||||||
|
|
||||||
|
cond_jump.offset = @instructions.size - old_size + 1
|
||||||
|
after_jump.offset = before_cond - instructions.size + 1
|
||||||
|
when Trees::TreeReturn
|
||||||
|
generate! tree.rvalue, table, RETURN_REG, free
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates code for the function that was given to it.
|
||||||
|
def generate!
|
||||||
|
generate!(@function.block, @table, -1, @registers)
|
||||||
|
return @instructions
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,161 +3,197 @@ require "./constant_folder.cr"
|
||||||
require "./table.cr"
|
require "./table.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class Compiler
|
module Compiler
|
||||||
def initialize(@config : Config)
|
# Top-level class to tie together the various
|
||||||
@logger = Logger.new STDOUT
|
# components, such as the `Lexer`,
|
||||||
@logger.debug("Initialized compiler")
|
# `ParserCombinators::Parser`, and `Optimizer`
|
||||||
@logger.level = Logger::DEBUG
|
class Compiler
|
||||||
end
|
# Creates a new compiler with the given *config*.
|
||||||
|
def initialize(@config : Ui::Config)
|
||||||
private def create_trees(file)
|
@logger = Logger.new STDOUT
|
||||||
string = File.read(file)
|
@logger.debug("Initialized compiler")
|
||||||
@logger.debug("Tokenizing")
|
@logger.level = Logger::DEBUG
|
||||||
lexer = Lexer.new
|
|
||||||
tokens = lexer.lex string
|
|
||||||
if tokens.size == 0 && string != ""
|
|
||||||
raise "Unable to tokenize file."
|
|
||||||
end
|
end
|
||||||
@logger.debug("Finished tokenizing")
|
|
||||||
@logger.debug("Beginning parsing")
|
# Reads a file an extracts instances of
|
||||||
parser = Parser.new
|
# `Trees:TreeFunction`.
|
||||||
if trees = parser.parse?(tokens)
|
private def create_trees(file)
|
||||||
@logger.debug("Finished parsing")
|
string = File.read(file)
|
||||||
@logger.debug("Beginning constant folding")
|
@logger.debug("Tokenizing")
|
||||||
folder = ConstantFolder.new
|
lexer = Lexer.new
|
||||||
trees.map! do |tree|
|
tokens = lexer.lex string
|
||||||
@logger.debug("Constant folding #{tree.name}")
|
if tokens.size == 0 && string != ""
|
||||||
tree.apply(folder).as(TreeFunction)
|
raise "Unable to tokenize file."
|
||||||
end
|
end
|
||||||
@logger.debug("Done constant folding")
|
@logger.debug("Finished tokenizing")
|
||||||
return trees
|
@logger.debug("Beginning parsing")
|
||||||
end
|
parser = ParserCombinators::Parser.new
|
||||||
raise "Unable to parse file."
|
if trees = parser.parse?(tokens)
|
||||||
end
|
@logger.debug("Finished parsing")
|
||||||
|
@logger.debug("Beginning constant folding")
|
||||||
private def create_table(trees)
|
folder = Trees::ConstantFolder.new
|
||||||
table = Table.new
|
trees.map! do |tree|
|
||||||
@logger.debug("Creating symbol table")
|
@logger.debug("Constant folding #{tree.name}")
|
||||||
trees.each do |tree|
|
tree.apply(folder).as(Trees::TreeFunction)
|
||||||
@logger.debug("Storing #{tree.name} in symbol table")
|
end
|
||||||
table[tree.name] = FunctionEntry.new tree
|
@logger.debug("Done constant folding")
|
||||||
end
|
return trees
|
||||||
@logger.debug("Done creating symbol table")
|
end
|
||||||
|
raise "Unable to parse file."
|
||||||
table["draw"] = FunctionEntry.new InlineDrawFunction.new
|
|
||||||
table["get_key"] = FunctionEntry.new InlineAwaitKeyFunction.new
|
|
||||||
table["get_font"] = FunctionEntry.new InlineGetFontFunction.new
|
|
||||||
table["set_delay"] = FunctionEntry.new InlineSetDelayFunction.new
|
|
||||||
table["get_delay"] = FunctionEntry.new InlineGetDelayFunction.new
|
|
||||||
return table
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_code(tree : TreeFunction, table)
|
|
||||||
generator = CodeGenerator.new table, tree
|
|
||||||
@logger.debug("Generating code for #{tree.name}")
|
|
||||||
return generator.generate!
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_code(tree : BuiltinFunction, table)
|
|
||||||
instructions = [] of Instruction
|
|
||||||
tree.generate!(instructions)
|
|
||||||
return instructions
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_code(trees : Array(TreeFunction), table)
|
|
||||||
code = {} of String => Array(Instruction)
|
|
||||||
trees.each do |tree|
|
|
||||||
code[tree.name] = create_code(tree, table)
|
|
||||||
end
|
|
||||||
return code
|
|
||||||
end
|
|
||||||
|
|
||||||
private def run_tree
|
|
||||||
trees = create_trees(@config.file)
|
|
||||||
trees.each do |it|
|
|
||||||
STDOUT << it
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def run_intermediate
|
|
||||||
trees = create_trees(@config.file)
|
|
||||||
table = create_table(trees)
|
|
||||||
code = create_code(trees, table)
|
|
||||||
code.each do |name, insts|
|
|
||||||
puts "Code for #{name}:"
|
|
||||||
insts.each { |it| puts it }
|
|
||||||
puts "-----"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def generate_binary(table, instructions, dest)
|
|
||||||
context = InstructionContext.new table, instructions.size
|
|
||||||
binary = instructions.map_with_index { |it, i| it.to_bin(context, i).to_u16 }
|
|
||||||
binary.each do |inst|
|
|
||||||
first = (inst >> 8).to_u8
|
|
||||||
dest.write_byte(first)
|
|
||||||
second = (inst & 0xff).to_u8
|
|
||||||
dest.write_byte(second)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def collect_calls(table)
|
|
||||||
open = Set(String).new
|
|
||||||
done = Set(String).new
|
|
||||||
|
|
||||||
open << "main"
|
|
||||||
while !open.empty?
|
|
||||||
first = open.first
|
|
||||||
open.delete first
|
|
||||||
|
|
||||||
entry = table[first]?
|
|
||||||
raise "Unknown function" unless entry && entry.is_a?(FunctionEntry)
|
|
||||||
function = entry.function
|
|
||||||
next if function.is_a?(InlineFunction)
|
|
||||||
done << first
|
|
||||||
next unless function.is_a?(TreeFunction)
|
|
||||||
|
|
||||||
visitor = CallVisitor.new
|
|
||||||
function.accept(visitor)
|
|
||||||
open.concat(visitor.calls - done)
|
|
||||||
end
|
|
||||||
return done
|
|
||||||
end
|
|
||||||
|
|
||||||
private def run_binary
|
|
||||||
all_instructions = [] of Instruction
|
|
||||||
trees = create_trees(@config.file)
|
|
||||||
table = create_table(trees)
|
|
||||||
names = collect_calls(table)
|
|
||||||
names.delete "main"
|
|
||||||
|
|
||||||
main_entry = table["main"]?.as(FunctionEntry)
|
|
||||||
all_instructions.concat create_code(main_entry.function.as(TreeFunction), table)
|
|
||||||
main_entry.addr = 0
|
|
||||||
all_instructions << JumpRelativeInstruction.new 0
|
|
||||||
|
|
||||||
names.each do |name|
|
|
||||||
entry = table[name]?.as(FunctionEntry)
|
|
||||||
entry.addr = all_instructions.size
|
|
||||||
function = entry.function
|
|
||||||
raise "Trying to compile inlined function" if function.is_a?(InlineFunction)
|
|
||||||
all_instructions.concat create_code(function, table)
|
|
||||||
all_instructions << ReturnInstruction.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
file = File.open("out.ch8", "w")
|
# Creates a default symbol table using the default functions,
|
||||||
generate_binary(table, all_instructions, file)
|
# as well as the functions declared by *trees*
|
||||||
file.close
|
private def create_table(trees)
|
||||||
end
|
table = Table.new
|
||||||
|
@logger.debug("Creating symbol table")
|
||||||
|
trees.each do |tree|
|
||||||
|
@logger.debug("Storing #{tree.name} in symbol table")
|
||||||
|
table[tree.name] = FunctionEntry.new tree
|
||||||
|
end
|
||||||
|
@logger.debug("Done creating symbol table")
|
||||||
|
|
||||||
def run
|
table["draw"] = FunctionEntry.new Builtin::InlineDrawFunction.new
|
||||||
case @config.mode
|
table["get_key"] = FunctionEntry.new Builtin::InlineAwaitKeyFunction.new
|
||||||
when OutputMode::Tree
|
table["get_font"] = FunctionEntry.new Builtin::InlineGetFontFunction.new
|
||||||
run_tree
|
table["set_delay"] = FunctionEntry.new Builtin::InlineSetDelayFunction.new
|
||||||
when OutputMode::Intermediate
|
table["get_delay"] = FunctionEntry.new Builtin::InlineGetDelayFunction.new
|
||||||
run_intermediate
|
return table
|
||||||
when OutputMode::Binary
|
end
|
||||||
run_binary
|
|
||||||
|
# Generates and optimizes intermediate representation for the given *tree*,
|
||||||
|
# looking up identifiers in the symbol *table*, and appending the given *instruction*
|
||||||
|
# at the end of the function to ensure correct program flow.
|
||||||
|
private def create_code(tree : Trees::TreeFunction, table, instruction = Ir::ReturnInstruction.new)
|
||||||
|
optimizer = Optimizer.new
|
||||||
|
generator = CodeGenerator.new table, tree
|
||||||
|
@logger.debug("Generating code for #{tree.name}")
|
||||||
|
code = generator.generate!
|
||||||
|
code << instruction
|
||||||
|
return optimizer.optimize(code)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate code for a builtin function. Neither the *table* nor the *instruction*
|
||||||
|
# are used, and serve to allow function overloading.
|
||||||
|
private def create_code(function : Builtin::BuiltinFunction, table, instruction = nil)
|
||||||
|
instructions = [] of Ir::Instruction
|
||||||
|
function.generate!(instructions)
|
||||||
|
return instructions
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a hash containing function names and their generated code.
|
||||||
|
# Only functions parsed from the file are compiled, and the *table*
|
||||||
|
# is used for looking up identifiers.
|
||||||
|
private def create_code(trees : Array(Trees::TreeFunction), table)
|
||||||
|
code = {} of String => Array(Ir::Instruction)
|
||||||
|
trees.each do |tree|
|
||||||
|
code[tree.name] = create_code(tree, table)
|
||||||
|
end
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
|
||||||
|
# Runs in the tree `Ui::OutputMode`. The file is
|
||||||
|
# tokenized and parsed, and the result is printed
|
||||||
|
# to the standard output.
|
||||||
|
private def run_tree
|
||||||
|
trees = create_trees(@config.file)
|
||||||
|
trees.each do |it|
|
||||||
|
STDOUT << it
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Runs in the intermediate `Ui::OutputMode`. The file
|
||||||
|
# is tokenized and parsed, and for each function,
|
||||||
|
# intermediate representation is generated. However,
|
||||||
|
# an executable is not generated, and the IR
|
||||||
|
# is printed to the screen.
|
||||||
|
private def run_intermediate
|
||||||
|
trees = create_trees(@config.file)
|
||||||
|
table = create_table(trees)
|
||||||
|
code = create_code(trees, table)
|
||||||
|
code.each do |name, insts|
|
||||||
|
puts "Code for #{name}:"
|
||||||
|
insts.each { |it| puts it }
|
||||||
|
puts "-----"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates binary from the given *instructions*,
|
||||||
|
# using the symbol *table* for lookups, and writes
|
||||||
|
# the output to *dest*
|
||||||
|
private def generate_binary(table, instructions, dest)
|
||||||
|
binary = instructions.map_with_index { |it, i| it.to_bin(table, instructions.size, i).to_u16 }
|
||||||
|
binary.each do |inst|
|
||||||
|
first = (inst >> 8).to_u8
|
||||||
|
dest.write_byte(first)
|
||||||
|
second = (inst & 0xff).to_u8
|
||||||
|
dest.write_byte(second)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Find all calls performed by the functions
|
||||||
|
# stored in the *table*, starting at the main function.
|
||||||
|
private def collect_calls(table)
|
||||||
|
open = Set(String).new
|
||||||
|
done = Set(String).new
|
||||||
|
|
||||||
|
open << "main"
|
||||||
|
while !open.empty?
|
||||||
|
first = open.first
|
||||||
|
open.delete first
|
||||||
|
|
||||||
|
entry = table[first]?
|
||||||
|
raise "Unknown function" unless entry && entry.is_a?(FunctionEntry)
|
||||||
|
function = entry.function
|
||||||
|
next if function.is_a?(Builtin::InlineFunction)
|
||||||
|
done << first
|
||||||
|
next unless function.is_a?(Trees::TreeFunction)
|
||||||
|
|
||||||
|
visitor = Trees::CallVisitor.new
|
||||||
|
function.accept(visitor)
|
||||||
|
open.concat(visitor.calls - done)
|
||||||
|
end
|
||||||
|
return done
|
||||||
|
end
|
||||||
|
|
||||||
|
# Runs in the binary `Ui::OutputMode`. The file is
|
||||||
|
# converted into an executable.
|
||||||
|
private def run_binary
|
||||||
|
all_instructions = [] of Ir::Instruction
|
||||||
|
trees = create_trees(@config.file)
|
||||||
|
table = create_table(trees)
|
||||||
|
names = collect_calls(table)
|
||||||
|
names.delete "main"
|
||||||
|
|
||||||
|
main_entry = table["main"]?.as(FunctionEntry)
|
||||||
|
all_instructions.concat create_code(main_entry.function.as(Trees::TreeFunction),
|
||||||
|
table, Ir::JumpRelativeInstruction.new 0)
|
||||||
|
main_entry.addr = 0
|
||||||
|
|
||||||
|
names.each do |name|
|
||||||
|
entry = table[name]?.as(FunctionEntry)
|
||||||
|
entry.addr = all_instructions.size
|
||||||
|
function = entry.function
|
||||||
|
raise "Trying to compile inlined function" if function.is_a?(Builtin::InlineFunction)
|
||||||
|
all_instructions.concat create_code(function, table)
|
||||||
|
all_instructions << Ir::ReturnInstruction.new
|
||||||
|
end
|
||||||
|
|
||||||
|
file = File.open("out.ch8", "w")
|
||||||
|
generate_binary(table, all_instructions, file)
|
||||||
|
file.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# Runs the compiler.
|
||||||
|
def run
|
||||||
|
case @config.mode
|
||||||
|
when Ui::OutputMode::Tree
|
||||||
|
run_tree
|
||||||
|
when Ui::OutputMode::Intermediate
|
||||||
|
run_intermediate
|
||||||
|
when Ui::OutputMode::Binary
|
||||||
|
run_binary
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,51 +1,70 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
enum OutputMode
|
module Ui
|
||||||
Tree,
|
# The mode in which the compiler operates.
|
||||||
Intermediate,
|
# Defines what actions are and aren't performed.
|
||||||
Binary
|
enum OutputMode
|
||||||
end
|
# The text is only parsed, and the result is printed to the screen.
|
||||||
|
Tree,
|
||||||
class Config
|
# The text is parsed and converted to intermediate representation.
|
||||||
property file : String
|
# The intermediate representation is then printed to the screen.
|
||||||
property mode : OutputMode
|
Intermediate,
|
||||||
|
# The text is converted into a full CHIP-8 executable.
|
||||||
def initialize(@file = "",
|
Binary
|
||||||
@mode = OutputMode::Tree)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse!
|
# A configuration class created from the command-line parameters.
|
||||||
config = self.new
|
class Config
|
||||||
OptionParser.parse! do |parser|
|
# Gets the file to be compiled.
|
||||||
parser.banner = "Usage: chalk [arguments]"
|
getter file : String
|
||||||
parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode|
|
# Sets the file to be compiled.
|
||||||
case mode.downcase
|
setter file : String
|
||||||
when "tree", "t"
|
# Gets the mode in which the compiler should operate.
|
||||||
config.mode = OutputMode::Tree
|
getter mode : OutputMode
|
||||||
when "intermediate", "i"
|
# Sets the mode in which the compiler should operate.
|
||||||
config.mode = OutputMode::Intermediate
|
setter mode : OutputMode
|
||||||
when "binary", "b"
|
|
||||||
config.mode = OutputMode::Binary
|
# Creates a new configuration.
|
||||||
else
|
def initialize(@file = "",
|
||||||
puts "Invalid mode type. Using default."
|
@mode = OutputMode::Tree)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reads a configuration from the command line options.
|
||||||
|
def self.parse!
|
||||||
|
config = self.new
|
||||||
|
OptionParser.parse! do |parser|
|
||||||
|
parser.banner = "Usage: chalk [arguments]"
|
||||||
|
parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode|
|
||||||
|
case mode.downcase
|
||||||
|
when "tree", "t"
|
||||||
|
config.mode = OutputMode::Tree
|
||||||
|
when "intermediate", "i"
|
||||||
|
config.mode = OutputMode::Intermediate
|
||||||
|
when "binary", "b"
|
||||||
|
config.mode = OutputMode::Binary
|
||||||
|
else
|
||||||
|
puts "Invalid mode type. Using default."
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
parser.on("-f", "--file=FILE", "Set the input file to compile.") do |file|
|
||||||
|
config.file = file
|
||||||
|
end
|
||||||
|
parser.on("-h", "--help", "Show this message.") { puts parser }
|
||||||
end
|
end
|
||||||
parser.on("-f", "--file=FILE", "Set the input file to compile.") do |file|
|
return config
|
||||||
config.file = file
|
|
||||||
end
|
|
||||||
parser.on("-h", "--help", "Show this message.") { puts parser }
|
|
||||||
end
|
end
|
||||||
return config
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate!
|
# Validates the options provided, returning true if
|
||||||
if file == ""
|
# they are valid and false otherwise.
|
||||||
puts "No source file specified."
|
def validate!
|
||||||
return false
|
if file == ""
|
||||||
elsif !File.exists? file
|
puts "No source file specified."
|
||||||
puts "Unable to open source file."
|
return false
|
||||||
return false
|
elsif !File.exists? file
|
||||||
|
puts "Unable to open source file."
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,33 +1,37 @@
|
||||||
require "./tree.cr"
|
require "./tree.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class ConstantFolder < Transformer
|
module Trees
|
||||||
private def perform_op(op, left, right)
|
# `Trees::Transformer` that turns operations on
|
||||||
case op
|
# Constants into constants.
|
||||||
when TokenType::OpAdd
|
class ConstantFolder < Transformer
|
||||||
left + right
|
private def perform_op(op, left, right)
|
||||||
when TokenType::OpSub
|
case op
|
||||||
left - right
|
when Compiler::TokenType::OpAdd
|
||||||
when TokenType::OpMul
|
left + right
|
||||||
left*right
|
when Compiler::TokenType::OpSub
|
||||||
when TokenType::OpDiv
|
left - right
|
||||||
left/right
|
when Compiler::TokenType::OpMul
|
||||||
when TokenType::OpAnd
|
left*right
|
||||||
left & right
|
when Compiler::TokenType::OpDiv
|
||||||
when TokenType::OpOr
|
left/right
|
||||||
left | right
|
when Compiler::TokenType::OpAnd
|
||||||
else TokenType::OpXor
|
left & right
|
||||||
left ^ right
|
when Compiler::TokenType::OpOr
|
||||||
|
left | right
|
||||||
|
else Compiler::TokenType::OpXor
|
||||||
|
left ^ right
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def transform(tree : TreeOp)
|
def transform(tree : Trees::TreeOp)
|
||||||
if tree.left.is_a?(TreeLit) && tree.right.is_a?(TreeLit)
|
if tree.left.is_a?(Trees::TreeLit) && tree.right.is_a?(Trees::TreeLit)
|
||||||
return TreeLit.new perform_op(tree.op,
|
return Trees::TreeLit.new perform_op(tree.op,
|
||||||
tree.left.as(TreeLit).lit,
|
tree.left.as(Trees::TreeLit).lit,
|
||||||
tree.right.as(TreeLit).lit)
|
tree.right.as(Trees::TreeLit).lit)
|
||||||
|
end
|
||||||
|
return tree
|
||||||
end
|
end
|
||||||
return tree
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,75 +1,111 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
module Emitter
|
module Compiler
|
||||||
def load(into, value)
|
# Module to emit instructions and store
|
||||||
inst = LoadInstruction.new into, value.to_i32
|
# them into an existing array.
|
||||||
@instructions << inst
|
module Emitter
|
||||||
return inst
|
# Emits an instruction to load a *value* into a register, *into*.
|
||||||
end
|
def load(into, value)
|
||||||
|
inst = Ir::LoadInstruction.new into, value.to_i32
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def loadr(into, from)
|
# Emits an instruction to load a register, *from*, into
|
||||||
inst = LoadRegInstruction.new into, from
|
# another register, *into*
|
||||||
@instructions << inst
|
def loadr(into, from)
|
||||||
return inst
|
inst = Ir::LoadRegInstruction.new into, from
|
||||||
end
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def op(op, into, from)
|
# Emits an instruction that's converted
|
||||||
inst = OpInstruction.new op, into, from
|
# to an operation, *op* that mutates the register, *into*,
|
||||||
@instructions << inst
|
# with the right hand operand *from*
|
||||||
return inst
|
def op(op, into, from)
|
||||||
end
|
inst = Ir::OpInstruction.new op, into, from
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def opr(op, into, from)
|
# Emits an instruction that's converted
|
||||||
inst = OpRegInstruction.new op, into, from
|
# to an operation, *op*, that mutates the register, *into*,
|
||||||
@instructions << inst
|
# with the right hand operand (a register), *from*
|
||||||
return inst
|
def opr(op, into, from)
|
||||||
end
|
inst = Ir::OpRegInstruction.new op, into, from
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def sne(l, r)
|
# Emits a "skip next instruction if not equal"
|
||||||
inst = SkipNeInstruction.new l, r
|
# instruction. The left hand side is a register,
|
||||||
@instructions << inst
|
# an the right hand side is a value.
|
||||||
return inst
|
def sne(l, r)
|
||||||
end
|
inst = Ir::SkipNeInstruction.new l, r
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def jr(o)
|
# Emits an instruction to jump relative to
|
||||||
inst = JumpRelativeInstruction.new o
|
# where the instruction is.
|
||||||
@instructions << inst
|
# ```
|
||||||
return inst
|
# jr 0 # Infinite loop
|
||||||
end
|
# jr -1 # Run previous instruction
|
||||||
|
# jr 1 # pretty much a no-op.
|
||||||
|
# ```
|
||||||
|
def jr(o)
|
||||||
|
inst = Ir::JumpRelativeInstruction.new o
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def store(up_to)
|
# Emits instruction that stores register 0 through *up_to* into
|
||||||
inst = StoreInstruction.new up_to
|
# memory at address I.
|
||||||
@instructions << inst
|
def store(up_to)
|
||||||
return inst
|
inst = Ir::StoreInstruction.new up_to
|
||||||
end
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def restore(up_to)
|
# Emits instruction that loads values from address I into
|
||||||
inst = RestoreInstruction.new up_to
|
# register 0 through *up_t*
|
||||||
@instructions << inst
|
def restore(up_to)
|
||||||
return inst
|
inst = Ir::RestoreInstruction.new up_to
|
||||||
end
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def ret
|
# Emits a return instruction.
|
||||||
inst = ReturnInstruction.new
|
def ret
|
||||||
@instructions << inst
|
inst = Ir::ReturnInstruction.new
|
||||||
return inst
|
@instructions << inst
|
||||||
end
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def call(func)
|
# Emits an instruction to call
|
||||||
inst = CallInstruction.new func
|
# the given function name.
|
||||||
@instructions << inst
|
def call(func)
|
||||||
return inst
|
inst = Ir::CallInstruction.new func
|
||||||
end
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def setis
|
# Emits instruction to set I
|
||||||
inst = SetIStackInstruction.new
|
# to the baste stack location. The stack
|
||||||
@instructions << inst
|
# pointer will need to be added to I
|
||||||
return inst
|
# to get the next available stack slot.
|
||||||
end
|
def setis
|
||||||
|
inst = Ir::SetIStackInstruction.new
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
|
|
||||||
def addi(reg)
|
# Emits instruction to add the value of a
|
||||||
inst = AddIRegInstruction.new reg
|
# register to I
|
||||||
@instructions << inst
|
def addi(reg)
|
||||||
return inst
|
inst = Ir::AddIRegInstruction.new reg
|
||||||
|
@instructions << inst
|
||||||
|
return inst
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class CallVisitor < Visitor
|
module Trees
|
||||||
property calls : Set(String)
|
# Visitor that finds all function calls in a function.
|
||||||
|
class CallVisitor < Visitor
|
||||||
|
property calls : Set(String)
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@calls = Set(String).new
|
@calls = Set(String).new
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit(t : TreeCall)
|
def visit(t : TreeCall)
|
||||||
@calls << t.name
|
@calls << t.name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,58 +1,65 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class InlineDrawFunction < InlineFunction
|
module Builtin
|
||||||
def initialize
|
# Inline function to draw sprite at address I.
|
||||||
@param_count = 3
|
class InlineDrawFunction < InlineFunction
|
||||||
end
|
def initialize
|
||||||
|
@param_count = 3
|
||||||
def generate!(emitter, params, table, target, free)
|
|
||||||
if !params[2].is_a?(TreeLit)
|
|
||||||
raise "Third parameter must be a constant."
|
|
||||||
end
|
end
|
||||||
emitter.generate! params[0], table, free, free + 1
|
|
||||||
emitter.generate! params[1], table, free + 1, free + 2
|
|
||||||
emitter.instructions << DrawInstruction.new free, free + 1, params[2].as(TreeLit).lit.to_i32
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class InlineAwaitKeyFunction < InlineFunction
|
def generate!(emitter, params, table, target, free)
|
||||||
def initialize
|
if !params[2].is_a?(Trees::TreeLit)
|
||||||
@param_count = 0
|
raise "Third parameter must be a constant."
|
||||||
|
end
|
||||||
|
emitter.generate! params[0], table, free, free + 1
|
||||||
|
emitter.generate! params[1], table, free + 1, free + 2
|
||||||
|
emitter.instructions << Ir::DrawInstruction.new free, free + 1, params[2].as(Trees::TreeLit).lit.to_i32
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate!(emitter, params, table, target, free)
|
# Inline function to await for a key and return it.
|
||||||
emitter.instructions << AwaitKeyInstruction.new target
|
class InlineAwaitKeyFunction < InlineFunction
|
||||||
end
|
def initialize
|
||||||
end
|
@param_count = 0
|
||||||
|
end
|
||||||
|
|
||||||
class InlineGetFontFunction < InlineFunction
|
def generate!(emitter, params, table, target, free)
|
||||||
def initialize
|
emitter.instructions << Ir::AwaitKeyInstruction.new target
|
||||||
@param_count = 1
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate!(emitter, params, table, target, free)
|
# Inline function to get font for a given value.
|
||||||
emitter.generate! params[0], table, free, free + 1
|
class InlineGetFontFunction < InlineFunction
|
||||||
emitter.instructions << GetFontInstruction.new free
|
def initialize
|
||||||
end
|
@param_count = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
class InlineSetDelayFunction < InlineFunction
|
def generate!(emitter, params, table, target, free)
|
||||||
def initialize
|
emitter.generate! params[0], table, free, free + 1
|
||||||
@param_count = 1
|
emitter.instructions << Ir::GetFontInstruction.new free
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate!(emitter, params, table, target, free)
|
# Inline function to set the delay timer.
|
||||||
emitter.generate! params[0], table, free, free + 1
|
class InlineSetDelayFunction < InlineFunction
|
||||||
emitter.instructions << SetDelayTimerInstruction.new free
|
def initialize
|
||||||
end
|
@param_count = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
class InlineGetDelayFunction < InlineFunction
|
def generate!(emitter, params, table, target, free)
|
||||||
def initialize
|
emitter.generate! params[0], table, free, free + 1
|
||||||
@param_count = 0
|
emitter.instructions << Ir::SetDelayTimerInstruction.new free
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate!(emitter, params, table, target, free)
|
# Inline function to get the delay timer.
|
||||||
emitter.instructions << GetDelayTimerInstruction.new target
|
class InlineGetDelayFunction < InlineFunction
|
||||||
|
def initialize
|
||||||
|
@param_count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate!(emitter, params, table, target, free)
|
||||||
|
emitter.instructions << Ir::GetDelayTimerInstruction.new target
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
608
src/chalk/ir.cr
608
src/chalk/ir.cr
|
@ -1,373 +1,371 @@
|
||||||
require "./lexer.cr"
|
require "./lexer.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class Instruction
|
module Ir
|
||||||
def to_bin(i, index)
|
# Base instruction class.
|
||||||
return 0
|
class Instruction
|
||||||
end
|
# Converts the instruction to binary, using
|
||||||
end
|
# A table for symbol lookups, the stack position,
|
||||||
|
# and the inex of the instruction.
|
||||||
class InstructionContext
|
def to_bin(table, stack, index)
|
||||||
property table : Table
|
return 0
|
||||||
property stack : Int32
|
|
||||||
|
|
||||||
def initialize(@table, @stack)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class LoadInstruction < Instruction
|
|
||||||
property register : Int32
|
|
||||||
property value : Int32
|
|
||||||
|
|
||||||
def initialize(@register, @value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "load R"
|
|
||||||
@register.to_s(16, io)
|
|
||||||
io << " " << @value
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
0x6000 | (@register << 8) | @value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class LoadRegInstruction < Instruction
|
|
||||||
property into : Int32
|
|
||||||
property from : Int32
|
|
||||||
|
|
||||||
def initialize(@into, @from)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "loadr R"
|
|
||||||
@into.to_s(16, io)
|
|
||||||
io << " R"
|
|
||||||
@from.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
0x8000 | (@into << 8) | (@from << 4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class OpInstruction < Instruction
|
|
||||||
property op : TokenType
|
|
||||||
property into : Int32
|
|
||||||
property value : Int32
|
|
||||||
|
|
||||||
def initialize(@op, @into, @value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "op " << op << " R"
|
|
||||||
@into.to_s(16, io)
|
|
||||||
io << " " << @value
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
case op
|
|
||||||
when TokenType::OpAdd
|
|
||||||
return 0x7000 | (@into << 8) | @value
|
|
||||||
else
|
|
||||||
raise "Invalid instruction"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
class OpRegInstruction < Instruction
|
# Instruction to load a value into a register.
|
||||||
property op : TokenType
|
class LoadInstruction < Instruction
|
||||||
property into : Int32
|
def initialize(@register : Int32, @value : Int32)
|
||||||
property from : Int32
|
|
||||||
|
|
||||||
def initialize(@op, @into, @from)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "opr " << op << " R"
|
|
||||||
@into.to_s(16, io)
|
|
||||||
io << " R"
|
|
||||||
@from.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
code = 0
|
|
||||||
case op
|
|
||||||
when TokenType::OpAdd
|
|
||||||
code = 4
|
|
||||||
when TokenType::OpSub
|
|
||||||
code = 5
|
|
||||||
when TokenType::OpOr
|
|
||||||
code = 1
|
|
||||||
when TokenType::OpAnd
|
|
||||||
code = 2
|
|
||||||
when TokenType::OpXor
|
|
||||||
code = 3
|
|
||||||
else
|
|
||||||
raise "Invalid instruction"
|
|
||||||
end
|
end
|
||||||
return 0x8000 | (@into << 8) | (@from << 4) | code
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class StoreInstruction < Instruction
|
def to_s(io)
|
||||||
property up_to : Int32
|
io << "load R"
|
||||||
|
@register.to_s(16, io)
|
||||||
|
io << " " << @value
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@up_to)
|
def to_bin(table, stack, index)
|
||||||
|
0x6000 | (@register << 8) | @value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to load a register into another register.
|
||||||
io << "store R"
|
class LoadRegInstruction < Instruction
|
||||||
@up_to.to_s(16, io)
|
# Gets the register being written to.
|
||||||
|
getter into
|
||||||
|
# Gets the register being used as right-hand operand.
|
||||||
|
getter from
|
||||||
|
|
||||||
|
def initialize(@into : Int32, @from : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "loadr R"
|
||||||
|
@into.to_s(16, io)
|
||||||
|
io << " R"
|
||||||
|
@from.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
0x8000 | (@into << 8) | (@from << 4)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to perform an operation on a register and a value,
|
||||||
return 0xf055 | (@up_to << 8)
|
# storing the output back into the register.
|
||||||
end
|
class OpInstruction < Instruction
|
||||||
end
|
def initialize(@op : Compiler::TokenType, @into : Int32, @value : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class RestoreInstruction < Instruction
|
def to_s(io)
|
||||||
property up_to : Int32
|
io << "op " << @op << " R"
|
||||||
|
@into.to_s(16, io)
|
||||||
|
io << " " << @value
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@up_to)
|
def to_bin(table, stack, index)
|
||||||
|
case @op
|
||||||
|
when Compiler::TokenType::OpAdd
|
||||||
|
return 0x7000 | (@into << 8) | @value
|
||||||
|
else
|
||||||
|
raise "Invalid instruction"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to perform an operation on a register and another register,
|
||||||
io << "restore R"
|
# storing the output back into left hand register.
|
||||||
@up_to.to_s(16, io)
|
class OpRegInstruction < Instruction
|
||||||
|
def initialize(@op : Compiler::TokenType, @into : Int32, @from : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "opr " << @op << " R"
|
||||||
|
@into.to_s(16, io)
|
||||||
|
io << " R"
|
||||||
|
@from.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
code = 0
|
||||||
|
case @op
|
||||||
|
when Compiler::TokenType::OpAdd
|
||||||
|
code = 4
|
||||||
|
when Compiler::TokenType::OpSub
|
||||||
|
code = 5
|
||||||
|
when Compiler::TokenType::OpOr
|
||||||
|
code = 1
|
||||||
|
when Compiler::TokenType::OpAnd
|
||||||
|
code = 2
|
||||||
|
when Compiler::TokenType::OpXor
|
||||||
|
code = 3
|
||||||
|
else
|
||||||
|
raise "Invalid instruction"
|
||||||
|
end
|
||||||
|
return 0x8000 | (@into << 8) | (@from << 4) | code
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to write registers to memory at address I.
|
||||||
return 0xf065 | (@up_to << 8)
|
# The *up_to* parameter specifies the highest register
|
||||||
end
|
# that should be stored.
|
||||||
end
|
class StoreInstruction < Instruction
|
||||||
|
def initialize(@up_to : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class ReturnInstruction < Instruction
|
def to_s(io)
|
||||||
def initialize
|
io << "store R"
|
||||||
|
@up_to.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf055 | (@up_to << 8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to read registers from memory at address I.
|
||||||
io << "return"
|
# The *up_to* parameter specifies the highest register
|
||||||
|
# that should be read into.
|
||||||
|
class RestoreInstruction < Instruction
|
||||||
|
def initialize(@up_to : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "restore R"
|
||||||
|
@up_to.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf065 | (@up_to << 8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to return from a call.
|
||||||
return 0x00ee
|
class ReturnInstruction < Instruction
|
||||||
end
|
def initialize
|
||||||
end
|
end
|
||||||
|
|
||||||
class JumpRelativeInstruction < Instruction
|
def to_s(io)
|
||||||
property offset : Int32
|
io << "return"
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@offset)
|
def to_bin(table, stack, index)
|
||||||
|
return 0x00ee
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to jump relative to its own position.
|
||||||
io << "jr " << @offset
|
class JumpRelativeInstruction < Instruction
|
||||||
|
# Gets the offset of this instruction.
|
||||||
|
getter offset
|
||||||
|
# Sets the offset of this instruction
|
||||||
|
setter offset
|
||||||
|
|
||||||
|
def initialize(@offset : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "jr " << @offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0x1000 | ((@offset + index) * 2 + 0x200)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to skip the next instruction if
|
||||||
return 0x1000 | ((@offset + index) * 2 + 0x200)
|
# the left-hand register is equal to the right-hand value.
|
||||||
end
|
class SkipEqInstruction < Instruction
|
||||||
end
|
def initialize(@left : Int32, @right : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class SkipEqInstruction < Instruction
|
def to_s(io)
|
||||||
property left : Int32
|
io << "seq R"
|
||||||
property right : Int32
|
@left.to_s(16, io)
|
||||||
|
io << " " << @right
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@left, @right)
|
def to_bin(table, stack, index)
|
||||||
|
return 0x3000 | (@left << 8) | @right
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to skip the next instruction if
|
||||||
io << "seq R"
|
# the left-hand register is not equal to the right-hand value.
|
||||||
@left.to_s(16, io)
|
class SkipNeInstruction < Instruction
|
||||||
io << " " << right
|
def initialize(@left : Int32, @right : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "sne R"
|
||||||
|
@left.to_s(16, io)
|
||||||
|
io << " " << @right
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0x4000 | (@left << 8) | @right
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to skip the next instruction if
|
||||||
return 0x3000 | (@left << 8) | @right
|
# the left-hand register is equal to the right-hand register.
|
||||||
end
|
class SkipRegEqInstruction < Instruction
|
||||||
end
|
def initialize(@left : Int32, @right : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class SkipNeInstruction < Instruction
|
def to_s(io)
|
||||||
property left : Int32
|
io << "seqr R"
|
||||||
property right : Int32
|
@left.to_s(16, io)
|
||||||
|
io << " R"
|
||||||
|
@right.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@left, @right)
|
def to_bin(table, stack, index)
|
||||||
|
return 0x5000 | (@left << 8) | (@right << 4)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to skip the next instruction if
|
||||||
io << "sne R"
|
# the left-hand register is not equal to the right-hand register.
|
||||||
@left.to_s(16, io)
|
class SkipRegNeInstruction < Instruction
|
||||||
io << " " << right
|
def initialize(@left : Int32, @right : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "sner R"
|
||||||
|
@left.to_s(16, io)
|
||||||
|
io << " R"
|
||||||
|
@right.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0x9000 | (@left << 8) | (@right << 4)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to call a function by name.
|
||||||
return 0x4000 | (@left << 8) | @right
|
class CallInstruction < Instruction
|
||||||
end
|
# Gets the name of the function being called.
|
||||||
end
|
getter name
|
||||||
|
|
||||||
class SkipRegEqInstruction < Instruction
|
def initialize(@name : String)
|
||||||
property left : Int32
|
end
|
||||||
property right : Int32
|
|
||||||
|
|
||||||
def initialize(@left, @right)
|
def to_s(io)
|
||||||
|
io << "call " << @name
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0x2000 | (table[name]?.as(Compiler::FunctionEntry).addr * 2 + 0x200)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to set I to the base position of the stack.
|
||||||
io << "seqr R"
|
class SetIStackInstruction < Instruction
|
||||||
@left.to_s(16, io)
|
def to_s(io)
|
||||||
io << " R"
|
io << "setis"
|
||||||
@right.to_s(16, io)
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xa000 | (stack * 2 + 0x200)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to add a register to I.
|
||||||
return 0x5000 | (@left << 8) | (@right << 4)
|
class AddIRegInstruction < Instruction
|
||||||
end
|
def initialize(@reg : Int32)
|
||||||
end
|
end
|
||||||
|
|
||||||
class SkipRegNeInstruction < Instruction
|
def to_s(io)
|
||||||
property left : Int32
|
io << "addi R"
|
||||||
property right : Int32
|
@reg.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@left, @right)
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf000 | (@reg << 8) | 0x1e
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to draw on screen.
|
||||||
io << "sner R"
|
# The x and y coordinates specify the position of the sprite,
|
||||||
@left.to_s(16, io)
|
# and the height gives the height of the sprite.
|
||||||
io << " R"
|
class DrawInstruction < Instruction
|
||||||
@right.to_s(16, io)
|
def initialize(@x : Int32, @y : Int32, @height : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "draw R"
|
||||||
|
@x.to_s(16, io)
|
||||||
|
io << " R"
|
||||||
|
@y.to_s(16, io)
|
||||||
|
io << " " << @height
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xd000 | (@x << 8) | (@y << 4) | @height
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to await a key press and store it into a register.
|
||||||
return 0x9000 | (@left << 8) | (@right << 4)
|
class AwaitKeyInstruction < Instruction
|
||||||
end
|
def initialize(@into : Int32)
|
||||||
end
|
end
|
||||||
|
|
||||||
class CallInstruction < Instruction
|
def to_s(io)
|
||||||
property name : String
|
io << "getk R"
|
||||||
|
@into.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@name)
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf00a | (@into << 8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# Instruction to set I to the font given by the value
|
||||||
io << "call " << @name
|
# of a register.
|
||||||
|
class GetFontInstruction < Instruction
|
||||||
|
def initialize(@from : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "font R"
|
||||||
|
@from.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf029 | (@from << 8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to set the delay timer to the value
|
||||||
return 0x2000 | (i.table[name]?.as(FunctionEntry).addr * 2 + 0x200)
|
# of the given register.
|
||||||
end
|
class SetDelayTimerInstruction < Instruction
|
||||||
end
|
def initialize(@from : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class SetIStackInstruction < Instruction
|
def to_s(io)
|
||||||
def to_s(io)
|
io << "set_delay R"
|
||||||
io << "setis"
|
@from.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_bin(table, stack, index)
|
||||||
|
return 0xf015 | (@from << 8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bin(i, index)
|
# Instruction to get the delay timer, and store
|
||||||
return 0xa000 | (i.stack * 2 + 0x200)
|
# the value into the given register.
|
||||||
end
|
class GetDelayTimerInstruction < Instruction
|
||||||
end
|
def initialize(@into : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
class AddIRegInstruction < Instruction
|
def to_s(io)
|
||||||
property reg : Int32
|
io << "get_delay R"
|
||||||
|
@into.to_s(16, io)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(@reg)
|
def to_bin(table, stack, index)
|
||||||
end
|
return 0xf007 | (@into << 8)
|
||||||
|
end
|
||||||
def to_s(io)
|
|
||||||
io << "addi R"
|
|
||||||
reg.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xf000 | (@reg << 8) | 0x1e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DrawInstruction < Instruction
|
|
||||||
property x : Int32
|
|
||||||
property y : Int32
|
|
||||||
property height : Int32
|
|
||||||
|
|
||||||
def initialize(@x, @y, @height)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "draw R"
|
|
||||||
x.to_s(16, io)
|
|
||||||
io << " R"
|
|
||||||
y.to_s(16, io)
|
|
||||||
io << " " << height
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xd000 | (@x << 8) | (@y << 4) | height
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AwaitKeyInstruction < Instruction
|
|
||||||
property into : Int32
|
|
||||||
|
|
||||||
def initialize(@into)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "getk R"
|
|
||||||
@into.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xf00a | (@into << 8)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetFontInstruction < Instruction
|
|
||||||
property from : Int32
|
|
||||||
|
|
||||||
def initialize(@from)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "font R"
|
|
||||||
@from.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xf029 | (@from << 8)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class SetDelayTimerInstruction < Instruction
|
|
||||||
property from : Int32
|
|
||||||
|
|
||||||
def initialize(@from)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "set_delay R"
|
|
||||||
@from.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xf015 | (@from << 8)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetDelayTimerInstruction < Instruction
|
|
||||||
property into : Int32
|
|
||||||
|
|
||||||
def initialize(@into)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io)
|
|
||||||
io << "get_delay R"
|
|
||||||
@into.to_s(16, io)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_bin(i, index)
|
|
||||||
return 0xf007 | (@into << 8)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,82 +1,93 @@
|
||||||
require "lex"
|
require "lex"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
enum TokenType
|
module Compiler
|
||||||
Any,
|
# The type of a token that can be lexed.
|
||||||
Str,
|
enum TokenType
|
||||||
Id,
|
Any,
|
||||||
LitDec,
|
Str,
|
||||||
LitBin,
|
Id,
|
||||||
LitHex,
|
LitDec,
|
||||||
OpAdd
|
LitBin,
|
||||||
OpSub
|
LitHex,
|
||||||
OpMul
|
OpAdd
|
||||||
OpDiv
|
OpSub
|
||||||
OpOr
|
OpMul
|
||||||
OpAnd
|
OpDiv
|
||||||
OpXor
|
OpOr
|
||||||
KwSprite
|
OpAnd
|
||||||
KwInline
|
OpXor
|
||||||
KwFun
|
KwSprite
|
||||||
KwU0
|
KwInline
|
||||||
KwU8
|
KwFun
|
||||||
KwU12
|
KwU0
|
||||||
KwVar
|
KwU8
|
||||||
KwIf
|
KwU12
|
||||||
KwElse
|
KwVar
|
||||||
KwWhile
|
KwIf
|
||||||
KwReturn
|
KwElse
|
||||||
end
|
KwWhile
|
||||||
|
KwReturn
|
||||||
class Token
|
|
||||||
def initialize(@string : String, @type : TokenType)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
getter string : String
|
# A class that stores the string it matched and its token type.
|
||||||
getter type : TokenType
|
class Token
|
||||||
end
|
def initialize(@string : String, @type : TokenType)
|
||||||
|
end
|
||||||
|
|
||||||
class Lexer
|
# Gets the string this token represents.
|
||||||
def initialize
|
getter string : String
|
||||||
@lexer = Lex::Lexer.new
|
# Gets the type of this token.
|
||||||
@lexer.add_pattern(".", TokenType::Any.value)
|
getter type : TokenType
|
||||||
@lexer.add_pattern("\"(\\\\\"|[^\"])*\"",
|
|
||||||
TokenType::Str.value)
|
|
||||||
@lexer.add_pattern("[a-zA-Z_][a-zA-Z_0-9]*",
|
|
||||||
TokenType::Id.value)
|
|
||||||
@lexer.add_pattern("[0-9]+",
|
|
||||||
TokenType::LitDec.value)
|
|
||||||
@lexer.add_pattern("0b[0-1]+",
|
|
||||||
TokenType::LitBin.value)
|
|
||||||
@lexer.add_pattern("0x[0-9a-fA-F]+",
|
|
||||||
TokenType::LitHex.value)
|
|
||||||
@lexer.add_pattern("\\+", TokenType::OpAdd.value)
|
|
||||||
@lexer.add_pattern("-", TokenType::OpSub.value)
|
|
||||||
@lexer.add_pattern("\\*", TokenType::OpMul.value)
|
|
||||||
@lexer.add_pattern("/", TokenType::OpDiv.value)
|
|
||||||
@lexer.add_pattern("&", TokenType::OpAdd.value)
|
|
||||||
@lexer.add_pattern("\\|", TokenType::OpOr.value)
|
|
||||||
@lexer.add_pattern("^", TokenType::OpXor.value)
|
|
||||||
@lexer.add_pattern("sprite", TokenType::KwSprite.value)
|
|
||||||
@lexer.add_pattern("inline", TokenType::KwInline.value)
|
|
||||||
@lexer.add_pattern("fun", TokenType::KwFun.value)
|
|
||||||
@lexer.add_pattern("u0", TokenType::KwU0.value)
|
|
||||||
@lexer.add_pattern("u8", TokenType::KwU8.value)
|
|
||||||
@lexer.add_pattern("u12", TokenType::KwU12.value)
|
|
||||||
@lexer.add_pattern("var", TokenType::KwVar.value)
|
|
||||||
@lexer.add_pattern("if", TokenType::KwIf.value)
|
|
||||||
@lexer.add_pattern("else", TokenType::KwElse.value)
|
|
||||||
@lexer.add_pattern("while", TokenType::KwWhile.value)
|
|
||||||
@lexer.add_pattern("return", TokenType::KwReturn.value)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lex(string)
|
# Creates a new Lexer with default token values.
|
||||||
return @lexer.lex(string)
|
# The lexer is backed by liblex. When a string is
|
||||||
.select { |t| !t[0][0].whitespace? }
|
# matched by several tokens, the longest match is chosen
|
||||||
.map do |tuple|
|
# first, followed by the match with the highest enum value.
|
||||||
string, id = tuple
|
class Lexer
|
||||||
Token.new(string, TokenType.new(id))
|
def initialize
|
||||||
end
|
@lexer = Lex::Lexer.new
|
||||||
|
@lexer.add_pattern(".", TokenType::Any.value)
|
||||||
|
@lexer.add_pattern("\"(\\\\\"|[^\"])*\"",
|
||||||
|
TokenType::Str.value)
|
||||||
|
@lexer.add_pattern("[a-zA-Z_][a-zA-Z_0-9]*",
|
||||||
|
TokenType::Id.value)
|
||||||
|
@lexer.add_pattern("[0-9]+",
|
||||||
|
TokenType::LitDec.value)
|
||||||
|
@lexer.add_pattern("0b[0-1]+",
|
||||||
|
TokenType::LitBin.value)
|
||||||
|
@lexer.add_pattern("0x[0-9a-fA-F]+",
|
||||||
|
TokenType::LitHex.value)
|
||||||
|
@lexer.add_pattern("\\+", TokenType::OpAdd.value)
|
||||||
|
@lexer.add_pattern("-", TokenType::OpSub.value)
|
||||||
|
@lexer.add_pattern("\\*", TokenType::OpMul.value)
|
||||||
|
@lexer.add_pattern("/", TokenType::OpDiv.value)
|
||||||
|
@lexer.add_pattern("&", TokenType::OpAdd.value)
|
||||||
|
@lexer.add_pattern("\\|", TokenType::OpOr.value)
|
||||||
|
@lexer.add_pattern("^", TokenType::OpXor.value)
|
||||||
|
@lexer.add_pattern("sprite", TokenType::KwSprite.value)
|
||||||
|
@lexer.add_pattern("inline", TokenType::KwInline.value)
|
||||||
|
@lexer.add_pattern("fun", TokenType::KwFun.value)
|
||||||
|
@lexer.add_pattern("u0", TokenType::KwU0.value)
|
||||||
|
@lexer.add_pattern("u8", TokenType::KwU8.value)
|
||||||
|
@lexer.add_pattern("u12", TokenType::KwU12.value)
|
||||||
|
@lexer.add_pattern("var", TokenType::KwVar.value)
|
||||||
|
@lexer.add_pattern("if", TokenType::KwIf.value)
|
||||||
|
@lexer.add_pattern("else", TokenType::KwElse.value)
|
||||||
|
@lexer.add_pattern("while", TokenType::KwWhile.value)
|
||||||
|
@lexer.add_pattern("return", TokenType::KwReturn.value)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Converts a string into tokens.
|
||||||
|
def lex(string)
|
||||||
|
return @lexer.lex(string)
|
||||||
|
.select { |t| !t[0][0].whitespace? }
|
||||||
|
.map do |tuple|
|
||||||
|
string, id = tuple
|
||||||
|
Token.new(string, TokenType.new(id))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,41 +1,70 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class Optimizer
|
module Compiler
|
||||||
private def check_dead(inst)
|
# Class to optimize instructions.
|
||||||
if inst.is_a?(LoadRegInstruction)
|
class Optimizer
|
||||||
return inst.from == inst.into
|
# Checks if *inst* is "dead code",
|
||||||
|
# an instruction that is completely useless.
|
||||||
|
private def check_dead(inst)
|
||||||
|
if inst.is_a?(Ir::LoadRegInstruction)
|
||||||
|
return inst.from == inst.into
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
private def optimize!(instructions, range)
|
# Optimizes *instructions* in the basic block given by the *range*,
|
||||||
offset = 0
|
# storing addresses of instructions to be deleted into *deletions*,
|
||||||
range.each do |index|
|
# and the number of deleted instructions so far into *deletions_at*
|
||||||
if check_dead(instructions[index + offset])
|
private def optimize!(instructions, range, deletions, deletions_at)
|
||||||
instructions.delete_at(index + offset)
|
range.each do |index|
|
||||||
offset -= 1
|
if check_dead(instructions[index])
|
||||||
|
deletions << index
|
||||||
|
end
|
||||||
|
deletions_at[index] = deletions.size
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return offset
|
|
||||||
end
|
|
||||||
|
|
||||||
def optimize(instructions)
|
# Optimizes the given list of instructions.
|
||||||
instructions = instructions.dup
|
# The basic blocks are inferred from the various
|
||||||
block_boundaries = [instructions.size]
|
# jumps and skips.
|
||||||
instructions.each_with_index do |inst, i|
|
def optimize(instructions)
|
||||||
if inst.is_a?(JumpRelativeInstruction)
|
instructions = instructions.dup
|
||||||
block_boundaries << (inst.offset + i)
|
block_boundaries = [instructions.size]
|
||||||
|
instructions.each_with_index do |inst, i|
|
||||||
|
if inst.is_a?(Ir::JumpRelativeInstruction)
|
||||||
|
block_boundaries << (i + 1)
|
||||||
|
block_boundaries << (inst.offset + i)
|
||||||
|
end
|
||||||
|
if inst.is_a?(Ir::SkipNeInstruction | Ir::SkipEqInstruction |
|
||||||
|
Ir::SkipRegEqInstruction | Ir::SkipRegNeInstruction)
|
||||||
|
block_boundaries << (i + 1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
block_boundaries.uniq!.sort!
|
||||||
block_boundaries.sort!
|
|
||||||
|
|
||||||
previous = 0
|
previous = 0
|
||||||
offset = 0
|
deletions = [] of Int32
|
||||||
block_boundaries.each do |boundary|
|
deletions_at = {} of Int32 => Int32
|
||||||
range = (previous + offset)...(boundary + offset)
|
block_boundaries.each do |boundary|
|
||||||
offset += optimize!(instructions, range)
|
range = previous...boundary
|
||||||
previous = boundary
|
optimize!(instructions, range, deletions, deletions_at)
|
||||||
|
previous = boundary
|
||||||
|
end
|
||||||
|
|
||||||
|
instructions.each_with_index do |inst, i|
|
||||||
|
next if !inst.is_a?(Ir::JumpRelativeInstruction)
|
||||||
|
jump_to = inst.offset + i
|
||||||
|
next unless deletions_at[jump_to]?
|
||||||
|
deletions_offset = deletions_at[i] - deletions_at[jump_to]
|
||||||
|
inst.offset += deletions_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
deletions.reverse!
|
||||||
|
deletions.each do |i|
|
||||||
|
instructions.delete_at i
|
||||||
|
end
|
||||||
|
|
||||||
|
return instructions
|
||||||
end
|
end
|
||||||
return instructions
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,173 +1,195 @@
|
||||||
require "./parser_builder.cr"
|
require "./parser_builder.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class Parser
|
module ParserCombinators
|
||||||
include ParserBuilder
|
# Parser created out of the various parser combinators.
|
||||||
|
class Parser
|
||||||
|
include ParserBuilder
|
||||||
|
|
||||||
private def create_type
|
# Creates a parser for a type.
|
||||||
either(type(TokenType::KwU0), type(TokenType::KwU8), type(TokenType::KwU12))
|
private def create_type
|
||||||
end
|
either(type(Compiler::TokenType::KwU0), type(Compiler::TokenType::KwU8), type(Compiler::TokenType::KwU12))
|
||||||
|
|
||||||
private def create_lit
|
|
||||||
dec_parser = type(TokenType::LitDec).transform &.string.to_i64
|
|
||||||
hex_parser = type(TokenType::LitHex).transform &.string.lchop("0x").to_i64(16)
|
|
||||||
bin_parser = type(TokenType::LitBin).transform &.string.lchop("0b").to_i64(2)
|
|
||||||
lit_parser = either(dec_parser, hex_parser, bin_parser).transform { |it| TreeLit.new(it).as(Tree) }
|
|
||||||
return lit_parser
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_op_expr(atom, op)
|
|
||||||
pl = PlaceholderParser(Tree).new
|
|
||||||
recurse = atom.then(op).then(pl).transform do |arr|
|
|
||||||
arr = arr.flatten
|
|
||||||
TreeOp.new(
|
|
||||||
arr[1].as(Token).type,
|
|
||||||
arr[0].as(Tree),
|
|
||||||
arr[2].as(Tree)).as(Tree)
|
|
||||||
end
|
end
|
||||||
pl.parser = either(recurse, atom)
|
|
||||||
return pl
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_op_exprs(atom, ops)
|
# Creates a parser for an integer literal.
|
||||||
ops.reduce(atom) do |previous, current|
|
private def create_lit
|
||||||
create_op_expr(previous, current)
|
dec_parser = type(Compiler::TokenType::LitDec).transform &.string.to_i64
|
||||||
|
hex_parser = type(Compiler::TokenType::LitHex).transform &.string.lchop("0x").to_i64(16)
|
||||||
|
bin_parser = type(Compiler::TokenType::LitBin).transform &.string.lchop("0b").to_i64(2)
|
||||||
|
lit_parser = either(dec_parser, hex_parser, bin_parser).transform { |it| Trees::TreeLit.new(it).as(Trees::Tree) }
|
||||||
|
return lit_parser
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private def create_call(expr)
|
# Creates a parser for an operation with a given *atom* parser
|
||||||
call = type(TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr|
|
# and *op* parser.
|
||||||
arr = arr.flatten
|
private def create_op_expr(atom, op)
|
||||||
name = arr[0].as(Token).string
|
pl = PlaceholderParser(Trees::Tree).new
|
||||||
params = arr[2..arr.size - 2].map &.as(Tree)
|
recurse = atom.then(op).then(pl).transform do |arr|
|
||||||
TreeCall.new(name, params).as(Tree)
|
|
||||||
end
|
|
||||||
return call
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_expr
|
|
||||||
expr_place = PlaceholderParser(Tree).new
|
|
||||||
literal = create_lit
|
|
||||||
id = type(TokenType::Id).transform { |it| TreeId.new(it.string).as(Tree) }
|
|
||||||
call = create_call(expr_place)
|
|
||||||
atom = either(literal, call, id)
|
|
||||||
|
|
||||||
ops = [either(type(TokenType::OpMul), type(TokenType::OpDiv)),
|
|
||||||
either(type(TokenType::OpAdd), type(TokenType::OpSub)),
|
|
||||||
type(TokenType::OpXor),
|
|
||||||
type(TokenType::OpAnd),
|
|
||||||
type(TokenType::OpOr)]
|
|
||||||
expr = create_op_exprs(atom, ops)
|
|
||||||
expr_place.parser = expr
|
|
||||||
|
|
||||||
return expr
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_var(expr)
|
|
||||||
var = type(TokenType::KwVar).then(type(TokenType::Id)).then(char '=').then(expr).then(char ';').transform do |arr|
|
|
||||||
arr = arr.flatten
|
|
||||||
name = arr[1].as(Token).string
|
|
||||||
exp = arr[arr.size - 2].as(Tree)
|
|
||||||
TreeVar.new(name, exp).as(Tree)
|
|
||||||
end
|
|
||||||
return var
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_assign(expr)
|
|
||||||
assign = type(TokenType::Id).then(char '=').then(expr).then(char ';').transform do |arr|
|
|
||||||
arr = arr.flatten
|
|
||||||
name = arr[0].as(Token).string
|
|
||||||
exp = arr[arr.size - 2].as(Tree)
|
|
||||||
TreeAssign.new(name, exp).as(Tree)
|
|
||||||
end
|
|
||||||
return assign
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_basic(expr)
|
|
||||||
basic = expr.then(char ';').transform do |arr|
|
|
||||||
arr.flatten[0].as(Tree)
|
|
||||||
end
|
|
||||||
return basic
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_if(expr, block)
|
|
||||||
iff = type(TokenType::KwIf).then(char '(').then(expr).then(char ')').then(block)
|
|
||||||
.then(optional(type(TokenType::KwElse).then(block)))
|
|
||||||
.transform do |arr|
|
|
||||||
arr = arr.flatten
|
arr = arr.flatten
|
||||||
cond = arr[2].as(Tree)
|
Trees::TreeOp.new(
|
||||||
code = arr[4].as(Tree)
|
arr[1].as(Compiler::Token).type,
|
||||||
otherwise = arr.size == 7 ? arr[6].as(Tree) : nil
|
arr[0].as(Trees::Tree),
|
||||||
TreeIf.new(cond, code, otherwise).as(Tree)
|
arr[2].as(Trees::Tree)).as(Trees::Tree)
|
||||||
end
|
end
|
||||||
return iff
|
pl.parser = either(recurse, atom)
|
||||||
end
|
return pl
|
||||||
|
|
||||||
private def create_while(expr, block)
|
|
||||||
whilee = type(TokenType::KwWhile).then(char '(').then(expr).then(char ')').then(block).transform do |arr|
|
|
||||||
arr = arr.flatten
|
|
||||||
cond = arr[2].as(Tree)
|
|
||||||
code = arr[4].as(Tree)
|
|
||||||
TreeWhile.new(cond, code).as(Tree)
|
|
||||||
end
|
end
|
||||||
return whilee
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_return(expr)
|
# Creates a parser to parse layers of *ops* with multiple
|
||||||
returnn = type(TokenType::KwReturn).then(expr).then(char ';').transform do |arr|
|
# levels of precedence, specified by their order. The *atom*
|
||||||
arr = arr.flatten
|
# is the most basic expression.
|
||||||
value = arr[1].as(Tree)
|
private def create_op_exprs(atom, ops)
|
||||||
TreeReturn.new(value).as(Tree)
|
ops.reduce(atom) do |previous, current|
|
||||||
|
create_op_expr(previous, current)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return returnn
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_block(statement)
|
# Creates a parser for a call, with the given expression parser.
|
||||||
block = char('{').then(many(statement)).then(char '}').transform do |arr|
|
private def create_call(expr)
|
||||||
arr = arr.flatten
|
call = type(Compiler::TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr|
|
||||||
params = arr[1..arr.size - 2].map &.as(Tree)
|
|
||||||
TreeBlock.new(params).as(Tree)
|
|
||||||
end
|
|
||||||
return block
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_statement_block
|
|
||||||
statement_place = PlaceholderParser(Tree).new
|
|
||||||
expr = create_expr
|
|
||||||
block = create_block(statement_place)
|
|
||||||
iff = create_if(expr, block)
|
|
||||||
whilee = create_while(expr, block)
|
|
||||||
returnn = create_return(expr)
|
|
||||||
var = create_var(expr)
|
|
||||||
assign = create_assign(expr)
|
|
||||||
basic = create_basic(expr)
|
|
||||||
statement = either(basic, var, assign, block, iff, whilee, returnn)
|
|
||||||
statement_place.parser = statement
|
|
||||||
return {statement, block}
|
|
||||||
end
|
|
||||||
|
|
||||||
private def create_func(block, type)
|
|
||||||
func = type(TokenType::KwFun).then(type(TokenType::Id))
|
|
||||||
.then(char '(').then(delimited(type(TokenType::Id), char ',')).then(char ')')
|
|
||||||
.then(char ':').then(type)
|
|
||||||
.then(block).transform do |arr|
|
|
||||||
arr = arr.flatten
|
arr = arr.flatten
|
||||||
name = arr[1].as(Token).string
|
name = arr[0].as(Compiler::Token).string
|
||||||
params = arr[3..arr.size - 5].map &.as(Token).string
|
params = arr[2..arr.size - 2].map &.as(Trees::Tree)
|
||||||
code = arr[arr.size - 1].as(Tree)
|
Trees::TreeCall.new(name, params).as(Trees::Tree)
|
||||||
type = arr[arr.size - 2].as(Token).type
|
|
||||||
TreeFunction.new(name, params, code)
|
|
||||||
end
|
end
|
||||||
return func
|
return call
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
# Creates a parser for an expression.
|
||||||
_, block = create_statement_block
|
private def create_expr
|
||||||
@parser = many(create_func(block, create_type)).as(BasicParser(Array(TreeFunction)))
|
expr_place = PlaceholderParser(Trees::Tree).new
|
||||||
end
|
literal = create_lit
|
||||||
|
id = type(Compiler::TokenType::Id).transform { |it| Trees::TreeId.new(it.string).as(Trees::Tree) }
|
||||||
|
call = create_call(expr_place)
|
||||||
|
atom = either(literal, call, id)
|
||||||
|
|
||||||
def parse?(tokens)
|
ops = [either(type(Compiler::TokenType::OpMul), type(Compiler::TokenType::OpDiv)),
|
||||||
return @parser.parse?(tokens, 0).try &.[0]
|
either(type(Compiler::TokenType::OpAdd), type(Compiler::TokenType::OpSub)),
|
||||||
|
type(Compiler::TokenType::OpXor),
|
||||||
|
type(Compiler::TokenType::OpAnd),
|
||||||
|
type(Compiler::TokenType::OpOr)]
|
||||||
|
expr = create_op_exprs(atom, ops)
|
||||||
|
expr_place.parser = expr
|
||||||
|
|
||||||
|
return expr
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a var statement.
|
||||||
|
private def create_var(expr)
|
||||||
|
var = type(Compiler::TokenType::KwVar).then(type(Compiler::TokenType::Id)).then(char '=').then(expr).then(char ';').transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
name = arr[1].as(Compiler::Token).string
|
||||||
|
exp = arr[arr.size - 2].as(Trees::Tree)
|
||||||
|
Trees::TreeVar.new(name, exp).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return var
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for an assignment statement.
|
||||||
|
private def create_assign(expr)
|
||||||
|
assign = type(Compiler::TokenType::Id).then(char '=').then(expr).then(char ';').transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
name = arr[0].as(Compiler::Token).string
|
||||||
|
exp = arr[arr.size - 2].as(Trees::Tree)
|
||||||
|
Trees::TreeAssign.new(name, exp).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return assign
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a basic statement.
|
||||||
|
private def create_basic(expr)
|
||||||
|
basic = expr.then(char ';').transform do |arr|
|
||||||
|
arr.flatten[0].as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return basic
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for an if statement.
|
||||||
|
private def create_if(expr, block)
|
||||||
|
iff = type(Compiler::TokenType::KwIf).then(char '(').then(expr).then(char ')').then(block)
|
||||||
|
.then(optional(type(Compiler::TokenType::KwElse).then(block)))
|
||||||
|
.transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
cond = arr[2].as(Trees::Tree)
|
||||||
|
code = arr[4].as(Trees::Tree)
|
||||||
|
otherwise = arr.size == 7 ? arr[6].as(Trees::Tree) : nil
|
||||||
|
Trees::TreeIf.new(cond, code, otherwise).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return iff
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a while loop.
|
||||||
|
private def create_while(expr, block)
|
||||||
|
whilee = type(Compiler::TokenType::KwWhile).then(char '(').then(expr).then(char ')').then(block).transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
cond = arr[2].as(Trees::Tree)
|
||||||
|
code = arr[4].as(Trees::Tree)
|
||||||
|
Trees::TreeWhile.new(cond, code).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return whilee
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a return.
|
||||||
|
private def create_return(expr)
|
||||||
|
returnn = type(Compiler::TokenType::KwReturn).then(expr).then(char ';').transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
value = arr[1].as(Trees::Tree)
|
||||||
|
Trees::TreeReturn.new(value).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return returnn
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a block of statements.
|
||||||
|
private def create_block(statement)
|
||||||
|
block = char('{').then(many(statement)).then(char '}').transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
params = arr[1..arr.size - 2].map &.as(Trees::Tree)
|
||||||
|
Trees::TreeBlock.new(params).as(Trees::Tree)
|
||||||
|
end
|
||||||
|
return block
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a statement and block parser, returning both.
|
||||||
|
private def create_statement_block
|
||||||
|
statement_place = PlaceholderParser(Trees::Tree).new
|
||||||
|
expr = create_expr
|
||||||
|
block = create_block(statement_place)
|
||||||
|
iff = create_if(expr, block)
|
||||||
|
whilee = create_while(expr, block)
|
||||||
|
returnn = create_return(expr)
|
||||||
|
var = create_var(expr)
|
||||||
|
assign = create_assign(expr)
|
||||||
|
basic = create_basic(expr)
|
||||||
|
statement = either(basic, var, assign, block, iff, whilee, returnn)
|
||||||
|
statement_place.parser = statement
|
||||||
|
return {statement, block}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates a parser for a function declaration.
|
||||||
|
private def create_func(block, type)
|
||||||
|
func = type(Compiler::TokenType::KwFun).then(type(Compiler::TokenType::Id))
|
||||||
|
.then(char '(').then(delimited(type(Compiler::TokenType::Id), char ',')).then(char ')')
|
||||||
|
.then(char ':').then(type)
|
||||||
|
.then(block).transform do |arr|
|
||||||
|
arr = arr.flatten
|
||||||
|
name = arr[1].as(Compiler::Token).string
|
||||||
|
params = arr[3..arr.size - 5].map &.as(Compiler::Token).string
|
||||||
|
code = arr[arr.size - 1].as(Trees::Tree)
|
||||||
|
type = arr[arr.size - 2].as(Compiler::Token).type
|
||||||
|
Trees::TreeFunction.new(name, params, code)
|
||||||
|
end
|
||||||
|
return func
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
_, block = create_statement_block
|
||||||
|
@parser = many(create_func(block, create_type)).as(BasicParser(Array(Trees::TreeFunction)))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parses the given tokens into a tree.
|
||||||
|
def parse?(tokens)
|
||||||
|
return @parser.parse?(tokens, 0).try &.[0]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,37 +2,47 @@ require "./lexer.cr"
|
||||||
require "./parsers.cr"
|
require "./parsers.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
module ParserBuilder
|
module ParserCombinators
|
||||||
def type(type) : BasicParser(Token)
|
module ParserBuilder
|
||||||
return TypeParser.new(type).as(BasicParser(Token))
|
# Creates a parser for a given token type.
|
||||||
end
|
def type(type) : BasicParser(Compiler::Token)
|
||||||
|
return TypeParser.new(type).as(BasicParser(Compiler::Token))
|
||||||
|
end
|
||||||
|
|
||||||
def char(type) : BasicParser(Token)
|
# Creates a parser for a specific character.
|
||||||
return CharParser.new(type).as(BasicParser(Token))
|
def char(type) : BasicParser(Compiler::Token)
|
||||||
end
|
return CharParser.new(type).as(BasicParser(Compiler::Token))
|
||||||
|
end
|
||||||
|
|
||||||
def transform(parser : BasicParser(T), &transform : T -> R) forall T, R
|
# Creates a parser that transforms a value according to a block.
|
||||||
return TransformParser.new(parser, &transform).as(BasicParser(R))
|
def transform(parser : BasicParser(T), &transform : T -> R) forall T, R
|
||||||
end
|
return TransformParser.new(parser, &transform).as(BasicParser(R))
|
||||||
|
end
|
||||||
|
|
||||||
def optional(parser : BasicParser(T)) : BasicParser(T?) forall T
|
# Creates a parser that allows for failure to match.
|
||||||
return OptionalParser.new(parser).as(BasicParser(T?))
|
def optional(parser : BasicParser(T)) : BasicParser(T?) forall T
|
||||||
end
|
return OptionalParser.new(parser).as(BasicParser(T?))
|
||||||
|
end
|
||||||
|
|
||||||
def either(*args : BasicParser(T)) : BasicParser(T) forall T
|
# Creates a parser that tries several parsers in sequence until one succeeds.
|
||||||
return EitherParser.new(args.to_a).as(BasicParser(T))
|
def either(*args : BasicParser(T)) : BasicParser(T) forall T
|
||||||
end
|
return EitherParser.new(args.to_a).as(BasicParser(T))
|
||||||
|
end
|
||||||
|
|
||||||
def many(parser : BasicParser(T)) : BasicParser(Array(T)) forall T
|
# Creates a parser that parses one or more of the given parsers.
|
||||||
return ManyParser.new(parser).as(BasicParser(Array(T)))
|
def many(parser : BasicParser(T)) : BasicParser(Array(T)) forall T
|
||||||
end
|
return ManyParser.new(parser).as(BasicParser(Array(T)))
|
||||||
|
end
|
||||||
|
|
||||||
def delimited(parser : BasicParser(T), delimiter : BasicParser(R)) : BasicParser(Array(T)) forall T, R
|
# Creates a parser that parses one parser delimited by another.
|
||||||
return DelimitedParser.new(parser, delimiter).as(BasicParser(Array(T)))
|
def delimited(parser : BasicParser(T), delimiter : BasicParser(R)) : BasicParser(Array(T)) forall T, R
|
||||||
end
|
return DelimitedParser.new(parser, delimiter).as(BasicParser(Array(T)))
|
||||||
|
end
|
||||||
|
|
||||||
def then(first : BasicParser(T), second : BasicParser(R)) forall T, R
|
# Creates a parser that parses one parser, then the next.
|
||||||
return NextParser.new(first, second).as(BasicParser(Array(T | R)))
|
def then(first : BasicParser(T), second : BasicParser(R)) forall T, R
|
||||||
|
return NextParser.new(first, second).as(BasicParser(Array(T | R)))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,146 +1,171 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
abstract class BasicParser(T)
|
module ParserCombinators
|
||||||
abstract def parse?(tokens : Array(Token),
|
# Abstract class for a parser function,
|
||||||
index : Int64) : Tuple(T, Int64)?
|
# as used in parser combinators. This is basically
|
||||||
|
# a building block of parsing.
|
||||||
|
abstract class BasicParser(T)
|
||||||
|
# Attempts to parse the given *tokens*, starting at the given *index*.
|
||||||
|
abstract def parse?(tokens : Array(Compiler::Token),
|
||||||
|
index : Int64) : Tuple(T, Int64)?
|
||||||
|
|
||||||
def parse(tokens, index)
|
# Attempts to parse the given tokens like `#parse?`, but throws
|
||||||
return parse?(tokens, index).not_nil!
|
# on error.
|
||||||
end
|
def parse(tokens, index)
|
||||||
|
return parse?(tokens, index).not_nil!
|
||||||
def transform(&transform : T -> R) forall R
|
|
||||||
return TransformParser.new(self, &transform).as(BasicParser(R))
|
|
||||||
end
|
|
||||||
|
|
||||||
def then(other : BasicParser(R)) : BasicParser(Array(T | R)) forall R
|
|
||||||
return NextParser.new(self, other).as(BasicParser(Array(T | R)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TypeParser < BasicParser(Token)
|
|
||||||
def initialize(@type : TokenType)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse?(tokens, index)
|
|
||||||
return nil unless index < tokens.size
|
|
||||||
return nil unless tokens[index].type == @type
|
|
||||||
return {tokens[index], index + 1}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CharParser < BasicParser(Token)
|
|
||||||
def initialize(@char : Char)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse?(tokens, index)
|
|
||||||
return nil unless index < tokens.size
|
|
||||||
return nil unless (tokens[index].type == TokenType::Any) &&
|
|
||||||
tokens[index].string[0] == @char
|
|
||||||
return {tokens[index], index + 1}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TransformParser(T, R) < BasicParser(R)
|
|
||||||
def initialize(@parser : BasicParser(T), &@block : T -> R)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse?(tokens, index)
|
|
||||||
if parsed = @parser.parse?(tokens, index)
|
|
||||||
return {@block.call(parsed[0]), parsed[1]}
|
|
||||||
end
|
end
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class OptionalParser(T) < BasicParser(T?)
|
# Applies the given transformation to this parser,
|
||||||
def initialize(@parser : BasicParser(T))
|
# creating a new parser.
|
||||||
end
|
def transform(&transform : T -> R) forall R
|
||||||
|
return TransformParser.new(self, &transform).as(BasicParser(R))
|
||||||
def parse?(tokens, index)
|
|
||||||
if parsed = @parser.parse?(tokens, index)
|
|
||||||
return {parsed[0], parsed[1]}
|
|
||||||
end
|
end
|
||||||
return {nil, index}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class EitherParser(T) < BasicParser(T)
|
# Creates a sequence with the given parser,
|
||||||
def initialize(@parsers : Array(BasicParser(T)))
|
# creating a new parser.
|
||||||
|
def then(other : BasicParser(R)) : BasicParser(Array(T | R)) forall R
|
||||||
|
return NextParser.new(self, other).as(BasicParser(Array(T | R)))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse?(tokens, index)
|
# Parser that expects a specific token type.
|
||||||
@parsers.each do |parser|
|
class TypeParser < BasicParser(Compiler::Token)
|
||||||
if parsed = parser.parse?(tokens, index)
|
def initialize(@type : Compiler::TokenType)
|
||||||
return parsed
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
return nil unless index < tokens.size
|
||||||
|
return nil unless tokens[index].type == @type
|
||||||
|
return {tokens[index], index + 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parser that expects a specific character.
|
||||||
|
class CharParser < BasicParser(Compiler::Token)
|
||||||
|
def initialize(@char : Char)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
return nil unless index < tokens.size
|
||||||
|
return nil unless (tokens[index].type == Compiler::TokenType::Any) &&
|
||||||
|
tokens[index].string[0] == @char
|
||||||
|
return {tokens[index], index + 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parser that applies a transformation to the output
|
||||||
|
# of its child parser.
|
||||||
|
class TransformParser(T, R) < BasicParser(R)
|
||||||
|
def initialize(@parser : BasicParser(T), &@block : T -> R)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
if parsed = @parser.parse?(tokens, index)
|
||||||
|
return {@block.call(parsed[0]), parsed[1]}
|
||||||
end
|
end
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ManyParser(T) < BasicParser(Array(T))
|
|
||||||
def initialize(@parser : BasicParser(T))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse?(tokens, index)
|
# Parser that attempts to use its child parser,
|
||||||
many = [] of T
|
# and successfully returns nil if the child parser fails.
|
||||||
while parsed = @parser.parse?(tokens, index)
|
class OptionalParser(T) < BasicParser(T?)
|
||||||
item, index = parsed
|
def initialize(@parser : BasicParser(T))
|
||||||
many << item
|
|
||||||
end
|
|
||||||
return {many, index}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DelimitedParser(T, R) < BasicParser(Array(T))
|
|
||||||
def initialize(@parser : BasicParser(T), @delimiter : BasicParser(R))
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse?(tokens, index)
|
|
||||||
array = [] of T
|
|
||||||
first = @parser.parse?(tokens, index)
|
|
||||||
return {array, index} unless first
|
|
||||||
first_value, index = first
|
|
||||||
array << first_value
|
|
||||||
while delimiter = @delimiter.parse?(tokens, index)
|
|
||||||
_, new_index = delimiter
|
|
||||||
new = @parser.parse?(tokens, new_index)
|
|
||||||
break unless new
|
|
||||||
new_value, index = new
|
|
||||||
array << new_value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {array, index}
|
def parse?(tokens, index)
|
||||||
end
|
if parsed = @parser.parse?(tokens, index)
|
||||||
end
|
return {parsed[0], parsed[1]}
|
||||||
|
end
|
||||||
class NextParser(T, R) < BasicParser(Array(T | R))
|
return {nil, index}
|
||||||
def initialize(@first : BasicParser(T), @second : BasicParser(R))
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse?(tokens, index)
|
# Parser that tries all of its children until one succeeds.
|
||||||
first = @first.parse?(tokens, index)
|
class EitherParser(T) < BasicParser(T)
|
||||||
return nil unless first
|
def initialize(@parsers : Array(BasicParser(T)))
|
||||||
first_value, index = first
|
end
|
||||||
|
|
||||||
second = @second.parse?(tokens, index)
|
def parse?(tokens, index)
|
||||||
return nil unless second
|
@parsers.each do |parser|
|
||||||
second_value, index = second
|
if parsed = parser.parse?(tokens, index)
|
||||||
|
return parsed
|
||||||
array = Array(T | R).new
|
end
|
||||||
array << first_value << second_value
|
end
|
||||||
return {array, index}
|
return nil
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
class PlaceholderParser(T) < BasicParser(T)
|
|
||||||
property parser : BasicParser(T)?
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@parser = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse?(tokens, index)
|
# Parser that parses at least one of a given type.
|
||||||
@parser.try &.parse?(tokens, index)
|
class ManyParser(T) < BasicParser(Array(T))
|
||||||
|
def initialize(@parser : BasicParser(T))
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
many = [] of T
|
||||||
|
while parsed = @parser.parse?(tokens, index)
|
||||||
|
item, index = parsed
|
||||||
|
many << item
|
||||||
|
end
|
||||||
|
return {many, index}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parser that parses at least 0 of its child parser,
|
||||||
|
# delimited with its other child parser.
|
||||||
|
class DelimitedParser(T, R) < BasicParser(Array(T))
|
||||||
|
def initialize(@parser : BasicParser(T), @delimiter : BasicParser(R))
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
array = [] of T
|
||||||
|
first = @parser.parse?(tokens, index)
|
||||||
|
return {array, index} unless first
|
||||||
|
first_value, index = first
|
||||||
|
array << first_value
|
||||||
|
while delimiter = @delimiter.parse?(tokens, index)
|
||||||
|
_, new_index = delimiter
|
||||||
|
new = @parser.parse?(tokens, new_index)
|
||||||
|
break unless new
|
||||||
|
new_value, index = new
|
||||||
|
array << new_value
|
||||||
|
end
|
||||||
|
|
||||||
|
return {array, index}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parser that parses using the first parser, and, if it succeeds,
|
||||||
|
# parses using the second parses.
|
||||||
|
class NextParser(T, R) < BasicParser(Array(T | R))
|
||||||
|
def initialize(@first : BasicParser(T), @second : BasicParser(R))
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
first = @first.parse?(tokens, index)
|
||||||
|
return nil unless first
|
||||||
|
first_value, index = first
|
||||||
|
|
||||||
|
second = @second.parse?(tokens, index)
|
||||||
|
return nil unless second
|
||||||
|
second_value, index = second
|
||||||
|
|
||||||
|
array = Array(T | R).new
|
||||||
|
array << first_value << second_value
|
||||||
|
return {array, index}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parser used to declare recursive grammars.
|
||||||
|
class PlaceholderParser(T) < BasicParser(T)
|
||||||
|
property parser : BasicParser(T)?
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@parser = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse?(tokens, index)
|
||||||
|
@parser.try &.parse?(tokens, index)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,53 +1,55 @@
|
||||||
require "./tree.cr"
|
require "./tree.cr"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
class PrintVisitor < Visitor
|
module Trees
|
||||||
def initialize(@stream : IO)
|
# Visitor that prints a `Tree`.
|
||||||
@indent = 0
|
class PrintVisitor < Visitor
|
||||||
end
|
def initialize(@stream : IO)
|
||||||
|
@indent = 0
|
||||||
def print_indent
|
|
||||||
@indent.times do
|
|
||||||
@stream << " "
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def visit(id : TreeId)
|
private def print_indent
|
||||||
print_indent
|
@indent.times do
|
||||||
@stream << id.id << "\n"
|
@stream << " "
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit(lit : TreeLit)
|
|
||||||
print_indent
|
|
||||||
@stream << lit.lit << "\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def visit(op : TreeOp)
|
|
||||||
print_indent
|
|
||||||
@stream << "[op] "
|
|
||||||
@stream << op.op << "\n"
|
|
||||||
@indent += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def finish(op : TreeOp)
|
|
||||||
@indent -= 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def visit(function : TreeFunction)
|
|
||||||
print_indent
|
|
||||||
@stream << "[function] " << function.name << "( "
|
|
||||||
function.params.each do |param|
|
|
||||||
@stream << param << " "
|
|
||||||
end
|
end
|
||||||
@stream << ")" << "\n"
|
|
||||||
@indent += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def finish(function : TreeFunction)
|
def visit(id : TreeId)
|
||||||
@indent -= 1
|
print_indent
|
||||||
end
|
@stream << id.id << "\n"
|
||||||
|
end
|
||||||
|
|
||||||
macro forward(text, type)
|
def visit(lit : TreeLit)
|
||||||
|
print_indent
|
||||||
|
@stream << lit.lit << "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def visit(op : TreeOp)
|
||||||
|
print_indent
|
||||||
|
@stream << "[op] "
|
||||||
|
@stream << op.op << "\n"
|
||||||
|
@indent += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish(op : TreeOp)
|
||||||
|
@indent -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def visit(function : TreeFunction)
|
||||||
|
print_indent
|
||||||
|
@stream << "[function] " << function.name << "( "
|
||||||
|
function.params.each do |param|
|
||||||
|
@stream << param << " "
|
||||||
|
end
|
||||||
|
@stream << ")" << "\n"
|
||||||
|
@indent += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish(function : TreeFunction)
|
||||||
|
@indent -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
macro forward(text, type)
|
||||||
def visit(tree : {{type}})
|
def visit(tree : {{type}})
|
||||||
print_indent
|
print_indent
|
||||||
@stream << {{text}} << "\n"
|
@stream << {{text}} << "\n"
|
||||||
|
@ -59,18 +61,19 @@ module Chalk
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
forward("[call]", TreeCall)
|
forward("[call]", TreeCall)
|
||||||
forward("[block]", TreeBlock)
|
forward("[block]", TreeBlock)
|
||||||
forward("[var]", TreeVar)
|
forward("[var]", TreeVar)
|
||||||
forward("[assign]", TreeAssign)
|
forward("[assign]", TreeAssign)
|
||||||
forward("[if]", TreeIf)
|
forward("[if]", TreeIf)
|
||||||
forward("[while]", TreeWhile)
|
forward("[while]", TreeWhile)
|
||||||
forward("[return]", TreeReturn)
|
forward("[return]", TreeReturn)
|
||||||
end
|
end
|
||||||
|
|
||||||
class Tree
|
class Tree
|
||||||
def to_s(io)
|
def to_s(io)
|
||||||
accept(PrintVisitor.new io)
|
accept(PrintVisitor.new io)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,51 +1,68 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class Entry
|
module Compiler
|
||||||
end
|
# An entry in the symbol table.
|
||||||
|
class Entry
|
||||||
class FunctionEntry < Entry
|
|
||||||
property function : TreeFunction | BuiltinFunction | InlineFunction
|
|
||||||
property addr : Int32
|
|
||||||
|
|
||||||
def initialize(@function, @addr = -1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# An entry that represents a function in the symbol table.
|
||||||
io << "[function]"
|
class FunctionEntry < Entry
|
||||||
end
|
# Gets the function stored in this entry.
|
||||||
end
|
getter function
|
||||||
|
# Gets the address in code of this function.
|
||||||
|
getter addr
|
||||||
|
# Sets the address in code of this function.
|
||||||
|
setter addr
|
||||||
|
|
||||||
class VarEntry < Entry
|
def initialize(@function : Trees::TreeFunction | Builtin::BuiltinFunction | Builtin::InlineFunction,
|
||||||
property register : Int32
|
@addr = -1)
|
||||||
|
end
|
||||||
def initialize(@register)
|
|
||||||
end
|
def to_s(io)
|
||||||
|
io << "[function]"
|
||||||
def to_s(io)
|
|
||||||
io << "[variable] " << "(R" << @register.to_s(16) << ")"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Table
|
|
||||||
property parent : Table?
|
|
||||||
|
|
||||||
def initialize(@parent = nil)
|
|
||||||
@data = {} of String => Entry
|
|
||||||
end
|
|
||||||
|
|
||||||
def []?(key)
|
|
||||||
if entry = @data[key]?
|
|
||||||
return entry
|
|
||||||
end
|
end
|
||||||
return @parent.try &.[key]?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def []=(key, entry)
|
# An entry that represents a variable in the symbol table.
|
||||||
@data[key] = entry
|
class VarEntry < Entry
|
||||||
|
# Gets the register occupied by the variable
|
||||||
|
# in this entry.
|
||||||
|
getter register
|
||||||
|
|
||||||
|
def initialize(@register : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "[variable] " << "(R" << @register.to_s(16) << ")"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s(io)
|
# A symbol table.
|
||||||
@parent.try &.to_s(io)
|
class Table
|
||||||
io << @data.map { |k, v| k + ": " + v.to_s }.join("\n")
|
# Gets the parent of this table.
|
||||||
|
getter parent
|
||||||
|
|
||||||
|
def initialize(@parent : Table? = nil)
|
||||||
|
@data = {} of String => Entry
|
||||||
|
end
|
||||||
|
|
||||||
|
# Looks up the given *key* first in this table,
|
||||||
|
# then in its parent, continuing recursively.
|
||||||
|
def []?(key)
|
||||||
|
if entry = @data[key]?
|
||||||
|
return entry
|
||||||
|
end
|
||||||
|
return @parent.try &.[key]?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stores an *entry* under the given *key* into this table.
|
||||||
|
def []=(key, entry)
|
||||||
|
@data[key] = entry
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
@parent.try &.to_s(io)
|
||||||
|
io << @data.map { |k, v| k + ": " + v.to_s }.join("\n")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,228 +1,248 @@
|
||||||
module Chalk
|
module Chalk
|
||||||
class Visitor
|
module Trees
|
||||||
def visit(tree)
|
# A class used to visit nodes of a tree.
|
||||||
end
|
class Visitor
|
||||||
|
def visit(tree)
|
||||||
def finish(tree)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Transformer
|
|
||||||
def transform(tree)
|
|
||||||
return tree
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Tree
|
|
||||||
def accept(v)
|
|
||||||
v.visit(self)
|
|
||||||
v.finish(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply(t)
|
|
||||||
return t.transform(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeId < Tree
|
|
||||||
property id : String
|
|
||||||
|
|
||||||
def initialize(@id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeLit < Tree
|
|
||||||
property lit : Int64
|
|
||||||
|
|
||||||
def initialize(@lit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeCall < Tree
|
|
||||||
property name : String
|
|
||||||
property params : Array(Tree)
|
|
||||||
|
|
||||||
def initialize(@name, @params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def accept(v)
|
|
||||||
v.visit(self)
|
|
||||||
@params.each &.accept(v)
|
|
||||||
v.finish(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply(t)
|
|
||||||
@params.map! do |param|
|
|
||||||
param.apply(t)
|
|
||||||
end
|
end
|
||||||
return t.transform(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeOp < Tree
|
def finish(tree)
|
||||||
property op : TokenType
|
|
||||||
property left : Tree
|
|
||||||
property right : Tree
|
|
||||||
|
|
||||||
def initialize(@op, @left, @right)
|
|
||||||
end
|
|
||||||
|
|
||||||
def accept(v)
|
|
||||||
v.visit(self)
|
|
||||||
@left.accept(v)
|
|
||||||
@right.accept(v)
|
|
||||||
v.finish(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply(t)
|
|
||||||
@left = @left.apply(t)
|
|
||||||
@right = @right.apply(t)
|
|
||||||
return t.transform(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeBlock < Tree
|
|
||||||
property children : Array(Tree)
|
|
||||||
|
|
||||||
def initialize(@children)
|
|
||||||
end
|
|
||||||
|
|
||||||
def accept(v)
|
|
||||||
v.visit(self)
|
|
||||||
@children.each &.accept(v)
|
|
||||||
v.finish(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply(t)
|
|
||||||
@children.map! do |child|
|
|
||||||
child.apply(t)
|
|
||||||
end
|
end
|
||||||
return t.transform(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeFunction < Tree
|
|
||||||
property name : String
|
|
||||||
property params : Array(String)
|
|
||||||
property block : Tree
|
|
||||||
|
|
||||||
def initialize(@name, @params, @block)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def param_count
|
# A class used to transform a tree, bottom up.
|
||||||
return @params.size
|
# "Modern Compiler Design" refers to this technique
|
||||||
|
# as BURS.
|
||||||
|
class Transformer
|
||||||
|
def transform(tree)
|
||||||
|
return tree
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# The base class of a tree.
|
||||||
v.visit(self)
|
class Tree
|
||||||
@block.accept(v)
|
def accept(v)
|
||||||
v.finish(self)
|
v.visit(self)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents an ID.
|
||||||
@block = @block.apply(t)
|
class TreeId < Tree
|
||||||
return t.transform(self)
|
property id : String
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeVar < Tree
|
def initialize(@id)
|
||||||
property name : String
|
end
|
||||||
property expr : Tree
|
|
||||||
|
|
||||||
def initialize(@name, @expr)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# A tree that represents an integer literal.
|
||||||
v.visit(self)
|
class TreeLit < Tree
|
||||||
@expr.accept(v)
|
property lit : Int64
|
||||||
v.finish(self)
|
|
||||||
|
def initialize(@lit)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents a function call.
|
||||||
@expr = @expr.apply(t)
|
class TreeCall < Tree
|
||||||
return t.transform(self)
|
property name : String
|
||||||
end
|
property params : Array(Tree)
|
||||||
end
|
|
||||||
|
|
||||||
class TreeAssign < Tree
|
def initialize(@name, @params)
|
||||||
property name : String
|
end
|
||||||
property expr : Tree
|
|
||||||
|
|
||||||
def initialize(@name, @expr)
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@params.each &.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@params.map! do |param|
|
||||||
|
param.apply(t)
|
||||||
|
end
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# A tree that represents an operation on two values.
|
||||||
v.visit(self)
|
class TreeOp < Tree
|
||||||
@expr.accept(v)
|
property op : Compiler::TokenType
|
||||||
v.finish(self)
|
property left : Tree
|
||||||
|
property right : Tree
|
||||||
|
|
||||||
|
def initialize(@op, @left, @right)
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@left.accept(v)
|
||||||
|
@right.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@left = @left.apply(t)
|
||||||
|
@right = @right.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents a block of statements.
|
||||||
@expr = @expr.apply(t)
|
class TreeBlock < Tree
|
||||||
return t.transform(self)
|
property children : Array(Tree)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeIf < Tree
|
def initialize(@children)
|
||||||
property condition : Tree
|
end
|
||||||
property block : Tree
|
|
||||||
property otherwise : Tree?
|
|
||||||
|
|
||||||
def initialize(@condition, @block, @otherwise = nil)
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@children.each &.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@children.map! do |child|
|
||||||
|
child.apply(t)
|
||||||
|
end
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# A tree that represents a function declaration.
|
||||||
v.visit(self)
|
class TreeFunction < Tree
|
||||||
@condition.accept(v)
|
property name : String
|
||||||
@block.accept(v)
|
property params : Array(String)
|
||||||
@otherwise.try &.accept(v)
|
property block : Tree
|
||||||
v.finish(self)
|
|
||||||
|
def initialize(@name, @params, @block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def param_count
|
||||||
|
return @params.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@block.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@block = @block.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents the declaration of
|
||||||
@condition = @condition.apply(t)
|
# a new variable.
|
||||||
@block = @block.apply(t)
|
class TreeVar < Tree
|
||||||
@otherwise = @otherwise.try &.apply(t)
|
property name : String
|
||||||
return t.transform(self)
|
property expr : Tree
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TreeWhile < Tree
|
def initialize(@name, @expr)
|
||||||
property condition : Tree
|
end
|
||||||
property block : Tree
|
|
||||||
|
|
||||||
def initialize(@condition, @block)
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@expr.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@expr = @expr.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# A tree that represents the assignment
|
||||||
v.visit(self)
|
# to an existing variable.
|
||||||
@condition.accept(v)
|
class TreeAssign < Tree
|
||||||
@block.accept(v)
|
property name : String
|
||||||
v.finish(self)
|
property expr : Tree
|
||||||
|
|
||||||
|
def initialize(@name, @expr)
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@expr.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@expr = @expr.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents an if statement.
|
||||||
@condition = @condition.apply(t)
|
class TreeIf < Tree
|
||||||
@block = @block.apply(t)
|
property condition : Tree
|
||||||
return t.transform(self)
|
property block : Tree
|
||||||
end
|
property otherwise : Tree?
|
||||||
end
|
|
||||||
|
|
||||||
class TreeReturn < Tree
|
def initialize(@condition, @block, @otherwise = nil)
|
||||||
property rvalue : Tree
|
end
|
||||||
|
|
||||||
def initialize(@rvalue)
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@condition.accept(v)
|
||||||
|
@block.accept(v)
|
||||||
|
@otherwise.try &.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@condition = @condition.apply(t)
|
||||||
|
@block = @block.apply(t)
|
||||||
|
@otherwise = @otherwise.try &.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(v)
|
# A tree that represents a while loop.
|
||||||
v.visit(self)
|
class TreeWhile < Tree
|
||||||
@rvalue.accept(v)
|
property condition : Tree
|
||||||
v.finish(self)
|
property block : Tree
|
||||||
|
|
||||||
|
def initialize(@condition, @block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@condition.accept(v)
|
||||||
|
@block.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@condition = @condition.apply(t)
|
||||||
|
@block = @block.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply(t)
|
# A tree that represents a return statement.
|
||||||
@rvalue = @rvalue.apply(t)
|
class TreeReturn < Tree
|
||||||
return t.transform(self)
|
property rvalue : Tree
|
||||||
|
|
||||||
|
def initialize(@rvalue)
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(v)
|
||||||
|
v.visit(self)
|
||||||
|
@rvalue.accept(v)
|
||||||
|
v.finish(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply(t)
|
||||||
|
@rvalue = @rvalue.apply(t)
|
||||||
|
return t.transform(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue