2018-07-26 19:47:56 -07:00
|
|
|
require "logger"
|
|
|
|
require "./constant_folder.cr"
|
|
|
|
require "./table.cr"
|
|
|
|
|
|
|
|
module Chalk
|
2018-08-01 22:40:41 -07:00
|
|
|
module Compiler
|
2018-08-02 01:09:48 -07:00
|
|
|
# Top-level class to tie together the various
|
|
|
|
# components, such as the `Lexer`,
|
|
|
|
# `ParserCombinators::Parser`, and `Optimizer`
|
2018-08-01 22:40:41 -07:00
|
|
|
class Compiler
|
2018-08-02 01:09:48 -07:00
|
|
|
# Creates a new compiler with the given *config*.
|
2018-08-01 22:40:41 -07:00
|
|
|
def initialize(@config : Ui::Config)
|
|
|
|
@logger = Logger.new STDOUT
|
|
|
|
@logger.debug("Initialized compiler")
|
2018-08-03 16:36:27 -07:00
|
|
|
@logger.level = @config.loglevel
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
2018-08-01 22:40:41 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Reads a file an extracts instances of
|
|
|
|
# `Trees:TreeFunction`.
|
2018-08-01 22:40:41 -07:00
|
|
|
private def create_trees(file)
|
|
|
|
string = File.read(file)
|
|
|
|
@logger.debug("Tokenizing")
|
|
|
|
lexer = Lexer.new
|
|
|
|
tokens = lexer.lex string
|
|
|
|
if tokens.size == 0 && string != ""
|
|
|
|
raise "Unable to tokenize file."
|
|
|
|
end
|
|
|
|
@logger.debug("Finished tokenizing")
|
|
|
|
@logger.debug("Beginning parsing")
|
|
|
|
parser = ParserCombinators::Parser.new
|
|
|
|
if trees = parser.parse?(tokens)
|
|
|
|
@logger.debug("Finished parsing")
|
|
|
|
@logger.debug("Beginning constant folding")
|
|
|
|
folder = Trees::ConstantFolder.new
|
|
|
|
trees.map! do |tree|
|
2018-08-06 21:51:53 -07:00
|
|
|
next tree unless tree.is_a?(Trees::TreeFunction)
|
2018-08-01 22:40:41 -07:00
|
|
|
@logger.debug("Constant folding #{tree.name}")
|
2018-08-06 21:51:53 -07:00
|
|
|
tree.apply(folder)
|
2018-08-01 22:40:41 -07:00
|
|
|
end
|
|
|
|
@logger.debug("Done constant folding")
|
|
|
|
return trees
|
2018-07-27 22:50:31 -07:00
|
|
|
end
|
2018-08-01 22:40:41 -07:00
|
|
|
raise "Unable to parse file."
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Creates a default symbol table using the default functions,
|
|
|
|
# as well as the functions declared by *trees*
|
2018-08-01 22:40:41 -07:00
|
|
|
private def create_table(trees)
|
|
|
|
table = Table.new
|
|
|
|
@logger.debug("Creating symbol table")
|
|
|
|
trees.each do |tree|
|
2018-08-06 21:51:53 -07:00
|
|
|
case tree
|
|
|
|
when Trees::TreeSprite
|
|
|
|
@logger.debug("Storing sprite #{tree.name} in symbol table")
|
|
|
|
table.set_sprite tree.name, SpriteEntry.new tree.sprite
|
|
|
|
when Trees::TreeFunction
|
|
|
|
@logger.debug("Storing function #{tree.name} in symbol table")
|
|
|
|
table.set_function tree.name, FunctionEntry.new tree
|
|
|
|
else
|
|
|
|
@logger.debug("Unexpected tree type in input.")
|
|
|
|
end
|
2018-08-01 22:40:41 -07:00
|
|
|
end
|
|
|
|
@logger.debug("Done creating symbol table")
|
|
|
|
|
2018-08-05 20:32:10 -07:00
|
|
|
table.set_function "get_key", FunctionEntry.new Builtin::InlineAwaitKeyFunction.new
|
|
|
|
table.set_function "set_delay", FunctionEntry.new Builtin::InlineSetDelayFunction.new
|
|
|
|
table.set_function "get_delay", FunctionEntry.new Builtin::InlineGetDelayFunction.new
|
|
|
|
table.set_function "set_sound", FunctionEntry.new Builtin::InlineSetSoundFunction.new
|
|
|
|
table.set_function "draw_number", FunctionEntry.new Builtin::InlineDrawNumberFunction.new
|
2018-08-08 21:47:47 -07:00
|
|
|
table.set_function "draw_sprite", FunctionEntry.new Builtin::InlineDrawSpriteFunction.new
|
2018-08-01 22:40:41 -07:00
|
|
|
return table
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# 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.
|
2018-08-01 22:40:41 -07:00
|
|
|
private def create_code(tree : Trees::TreeFunction, table, instruction = Ir::ReturnInstruction.new)
|
2018-08-03 01:13:23 -07:00
|
|
|
tree.reduce(Trees::TypeChecker.new table, tree.type.return_type)
|
2018-08-01 22:40:41 -07:00
|
|
|
optimizer = Optimizer.new
|
|
|
|
generator = CodeGenerator.new table, tree
|
|
|
|
@logger.debug("Generating code for #{tree.name}")
|
|
|
|
code = generator.generate!
|
|
|
|
code << instruction
|
2018-08-08 22:21:50 -07:00
|
|
|
return code # optimizer.optimize(code)
|
2018-08-01 22:40:41 -07:00
|
|
|
end
|
2018-07-28 16:17:06 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# 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)
|
2018-08-01 22:40:41 -07:00
|
|
|
instructions = [] of Ir::Instruction
|
2018-08-02 01:09:48 -07:00
|
|
|
function.generate!(instructions)
|
2018-08-01 22:40:41 -07:00
|
|
|
return instructions
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# 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.
|
2018-08-01 22:40:41 -07:00
|
|
|
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
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
|
|
|
|
2018-08-06 21:51:53 -07:00
|
|
|
private def create_code(trees : Array(Trees::Tree), table)
|
|
|
|
functions = trees.select &.is_a?(Trees::TreeFunction)
|
|
|
|
return create_code(functions.map &.as(Trees::TreeFunction), table)
|
|
|
|
end
|
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Runs in the tree `Ui::OutputMode`. The file is
|
|
|
|
# tokenized and parsed, and the result is printed
|
|
|
|
# to the standard output.
|
2018-08-01 22:40:41 -07:00
|
|
|
private def run_tree
|
|
|
|
trees = create_trees(@config.file)
|
|
|
|
trees.each do |it|
|
|
|
|
STDOUT << it
|
|
|
|
end
|
2018-07-27 00:06:33 -07:00
|
|
|
end
|
2018-07-26 19:47:56 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# 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.
|
2018-08-01 22:40:41 -07:00
|
|
|
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
|
2018-07-27 22:50:31 -07:00
|
|
|
end
|
2018-07-27 19:56:26 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Creates binary from the given *instructions*,
|
|
|
|
# using the symbol *table* for lookups, and writes
|
|
|
|
# the output to *dest*
|
2018-08-01 22:40:41 -07:00
|
|
|
private def generate_binary(table, instructions, dest)
|
2018-08-02 01:09:48 -07:00
|
|
|
binary = instructions.map_with_index { |it, i| it.to_bin(table, instructions.size, i).to_u16 }
|
2018-08-01 22:40:41 -07:00
|
|
|
binary.each do |inst|
|
|
|
|
first = (inst >> 8).to_u8
|
2018-08-06 23:43:46 -07:00
|
|
|
dest << first
|
2018-08-01 22:40:41 -07:00
|
|
|
second = (inst & 0xff).to_u8
|
2018-08-06 23:43:46 -07:00
|
|
|
dest << second
|
2018-08-01 22:40:41 -07:00
|
|
|
end
|
2018-07-27 23:27:13 -07:00
|
|
|
end
|
2018-07-27 23:24:37 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Find all calls performed by the functions
|
|
|
|
# stored in the *table*, starting at the main function.
|
2018-08-01 22:40:41 -07:00
|
|
|
private def collect_calls(table)
|
|
|
|
open = Set(String).new
|
|
|
|
done = Set(String).new
|
|
|
|
|
|
|
|
open << "main"
|
|
|
|
while !open.empty?
|
|
|
|
first = open.first
|
|
|
|
open.delete first
|
|
|
|
|
2018-08-05 20:32:10 -07:00
|
|
|
entry = table.get_function? first
|
|
|
|
raise "Unknown function" unless entry
|
2018-08-01 22:40:41 -07:00
|
|
|
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
|
2018-07-27 19:56:26 -07:00
|
|
|
end
|
2018-07-27 23:24:37 -07:00
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Runs in the binary `Ui::OutputMode`. The file is
|
|
|
|
# converted into an executable.
|
2018-08-01 22:40:41 -07:00
|
|
|
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"
|
|
|
|
|
2018-08-05 20:32:10 -07:00
|
|
|
main_entry = table.get_function?("main").not_nil!
|
2018-08-01 22:40:41 -07:00
|
|
|
all_instructions.concat create_code(main_entry.function.as(Trees::TreeFunction),
|
|
|
|
table, Ir::JumpRelativeInstruction.new 0)
|
|
|
|
main_entry.addr = 0
|
|
|
|
|
|
|
|
names.each do |name|
|
2018-08-05 20:32:10 -07:00
|
|
|
entry = table.get_function?(name).not_nil!
|
2018-08-01 22:40:41 -07:00
|
|
|
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
|
2018-07-26 19:47:56 -07:00
|
|
|
|
2018-08-08 21:47:47 -07:00
|
|
|
sprite_bytes = [] of UInt8
|
|
|
|
offset = 0
|
|
|
|
table.sprites.each do |k, v|
|
|
|
|
data = v.sprite.encode
|
|
|
|
v.addr = offset + all_instructions.size * 2
|
|
|
|
offset += data.size
|
|
|
|
sprite_bytes.concat data
|
|
|
|
end
|
|
|
|
|
2018-08-06 23:43:46 -07:00
|
|
|
binary = [] of UInt8
|
2018-08-04 13:49:29 -07:00
|
|
|
file = File.open(@config.output, "w")
|
2018-08-06 23:43:46 -07:00
|
|
|
generate_binary(table, all_instructions, binary)
|
2018-08-08 21:47:47 -07:00
|
|
|
binary.concat sprite_bytes
|
2018-08-06 23:43:46 -07:00
|
|
|
binary.each do |byte|
|
|
|
|
file.write_byte byte
|
|
|
|
end
|
2018-08-01 22:40:41 -07:00
|
|
|
file.close
|
|
|
|
end
|
|
|
|
|
2018-08-02 01:09:48 -07:00
|
|
|
# Runs the compiler.
|
2018-08-01 22:40:41 -07:00
|
|
|
def run
|
|
|
|
case @config.mode
|
|
|
|
when Ui::OutputMode::Tree
|
|
|
|
run_tree
|
|
|
|
when Ui::OutputMode::Intermediate
|
|
|
|
run_intermediate
|
|
|
|
when Ui::OutputMode::Binary
|
|
|
|
run_binary
|
|
|
|
end
|
2018-07-26 19:47:56 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|