Compare commits

..

No commits in common. "8d015d47f31893917c838657414e451fb72a07b0" and "147837c011b4b1e3ac4e6686ee9214695c56554a" have entirely different histories.

18 changed files with 1479 additions and 1757 deletions

View File

@ -2,9 +2,9 @@ require "./chalk/*"
require "option_parser" require "option_parser"
module Chalk module Chalk
config = Ui::Config.parse! config = Config.parse!
exit unless config.validate! exit unless config.validate!
compiler = Compiler::Compiler.new config compiler = Compiler.new config
compiler.run compiler.run
end end

View File

@ -1,37 +1,21 @@
module Chalk module Chalk
module Builtin class BuiltinFunction
# A normal function (i.e., a "call" is generated for it) getter param_count : Int32
# 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
# Creates a new function with *param_count* parameters. def initialize(@param_count)
def initialize(@param_count)
end
# Uses the given `Compiler::Emitter` to output code.
abstract def generate!(codegen)
end end
# A function to which a call is not generated. This function def generate!(codegen)
# is copied everywhere a call to it occurs. Besides this, the end
# function also accepts trees rather than register numbers, end
# and therefore can accept and manipulate trees.
abstract class InlineFunction
# Gets the number of parameters this function has.
getter param_count : Int32
# Creates a new function with *param_count* parameters. class InlineFunction
def initialize(@param_count) getter param_count : Int32
end
# Generates code like `Compiler::CodeGenerator` would. def initialize(@param_count)
# The *codegen* parameter is used to emit instructions, end
# the *params* are trees that are being passed as arguments.
# See `Compiler::CodeGenerator#generate!` for what the other parameters mean. def generate!(codegen, params, table, target, free)
abstract def generate!(codegen, params, table, target, free)
end end
end end
end end

View File

@ -2,163 +2,141 @@ require "./ir.cr"
require "./emitter.cr" require "./emitter.cr"
module Chalk module Chalk
module Compiler class CodeGenerator
# A class that converts a tree into the corresponding include Emitter
# intermediate representation, without optimizing.
class CodeGenerator
include Emitter
# The register into which the return value of a function is stored. RETURN_REG = 14
RETURN_REG = 14 STACK_REG = 13
# The register into which the "stack pointer" is stored.
STACK_REG = 13
# Gets the instructions currently emitted by this code generator. property instructions : Array(Instruction)
getter instructions
# Creates a new compiler with the given symbol *table* def initialize(table, @function : TreeFunction)
# and *function* for which code should be generated. @registers = 0
def initialize(table, @function : Trees::TreeFunction) @instructions = [] of Instruction
@registers = 0 @table = Table.new table
@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
end when TreeVar
entry = table[tree.name]?
# Generates code for an inline function, with the given *tree* being the `Trees::TreeCall` if entry == nil
# that caused the function call. The other parameters are as described in the more general entry = VarEntry.new free
# `#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
# Call the function raise "Unknown variable" unless entry.is_a?(VarEntry)
tree.params.size.times do |time| generate! tree.expr, table, entry.register, free
loadr time, time + start_at return 1
end when TreeAssign
call tree.name entry = table[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
# Reduce stack pointer old_size = @instructions.size
load free, start_at generate! tree.block, table, free, free + 1
opr TokenType::OpSub, STACK_REG, free jump_after = jr 0
# Move I to stack jump_inst.offset = @instructions.size - old_size + 1
setis
# Get to correct stack position old_size = @instructions.size
addi STACK_REG generate! tree.otherwise, table, free, free + 1 if tree.otherwise
# Restore jump_after.offset = @instructions.size - old_size + 1
restore (start_at - 1) unless start_at == 0 when TreeWhile
# Get call value into target before_cond = @instructions.size
loadr target, RETURN_REG 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 TreeReturn
generate! tree.rvalue, table, RETURN_REG, free
ret
end end
return 0
end
# Generates code for a *tree*, using a symbol *table* def generate!
# housing all the names for identifiers in the code. generate!(@function.block, @table, -1, @registers)
# The result is stored into the *target* register, return @instructions
# 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

View File

@ -3,197 +3,161 @@ require "./constant_folder.cr"
require "./table.cr" require "./table.cr"
module Chalk module Chalk
module Compiler class Compiler
# Top-level class to tie together the various def initialize(@config : Config)
# components, such as the `Lexer`, @logger = Logger.new STDOUT
# `ParserCombinators::Parser`, and `Optimizer` @logger.debug("Initialized compiler")
class Compiler @logger.level = Logger::DEBUG
# Creates a new compiler with the given *config*. end
def initialize(@config : Ui::Config)
@logger = Logger.new STDOUT
@logger.debug("Initialized compiler")
@logger.level = Logger::DEBUG
end
# Reads a file an extracts instances of private def create_trees(file)
# `Trees:TreeFunction`. string = File.read(file)
private def create_trees(file) @logger.debug("Tokenizing")
string = File.read(file) lexer = Lexer.new
@logger.debug("Tokenizing") tokens = lexer.lex string
lexer = Lexer.new if tokens.size == 0 && string != ""
tokens = lexer.lex string raise "Unable to tokenize file."
if tokens.size == 0 && string != "" end
raise "Unable to tokenize file." @logger.debug("Finished tokenizing")
@logger.debug("Beginning parsing")
parser = Parser.new
if trees = parser.parse?(tokens)
@logger.debug("Finished parsing")
@logger.debug("Beginning constant folding")
folder = ConstantFolder.new
trees.map! do |tree|
@logger.debug("Constant folding #{tree.name}")
tree.apply(folder).as(TreeFunction)
end end
@logger.debug("Finished tokenizing") @logger.debug("Done constant folding")
@logger.debug("Beginning parsing") return trees
parser = ParserCombinators::Parser.new end
if trees = parser.parse?(tokens) raise "Unable to parse file."
@logger.debug("Finished parsing") end
@logger.debug("Beginning constant folding")
folder = Trees::ConstantFolder.new private def create_table(trees)
trees.map! do |tree| table = Table.new
@logger.debug("Constant folding #{tree.name}") @logger.debug("Creating symbol table")
tree.apply(folder).as(Trees::TreeFunction) trees.each do |tree|
end @logger.debug("Storing #{tree.name} in symbol table")
@logger.debug("Done constant folding") table[tree.name] = FunctionEntry.new tree
return trees end
end @logger.debug("Done creating symbol table")
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
# Creates a default symbol table using the default functions, file = File.open("out.ch8", "w")
# as well as the functions declared by *trees* generate_binary(table, all_instructions, file)
private def create_table(trees) file.close
table = Table.new end
@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")
table["draw"] = FunctionEntry.new Builtin::InlineDrawFunction.new def run
table["get_key"] = FunctionEntry.new Builtin::InlineAwaitKeyFunction.new case @config.mode
table["get_font"] = FunctionEntry.new Builtin::InlineGetFontFunction.new when OutputMode::Tree
table["set_delay"] = FunctionEntry.new Builtin::InlineSetDelayFunction.new run_tree
table["get_delay"] = FunctionEntry.new Builtin::InlineGetDelayFunction.new when OutputMode::Intermediate
return table run_intermediate
end when OutputMode::Binary
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

View File

@ -1,70 +1,51 @@
module Chalk module Chalk
module Ui enum OutputMode
# The mode in which the compiler operates. Tree,
# Defines what actions are and aren't performed. Intermediate,
enum OutputMode Binary
# The text is only parsed, and the result is printed to the screen. end
Tree,
# The text is parsed and converted to intermediate representation. class Config
# The intermediate representation is then printed to the screen. property file : String
Intermediate, property mode : OutputMode
# The text is converted into a full CHIP-8 executable.
Binary def initialize(@file = "",
@mode = OutputMode::Tree)
end end
# A configuration class created from the command-line parameters. def self.parse!
class Config config = self.new
# Gets the file to be compiled. OptionParser.parse! do |parser|
getter file : String parser.banner = "Usage: chalk [arguments]"
# Sets the file to be compiled. parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode|
setter file : String case mode.downcase
# Gets the mode in which the compiler should operate. when "tree", "t"
getter mode : OutputMode config.mode = OutputMode::Tree
# Sets the mode in which the compiler should operate. when "intermediate", "i"
setter mode : OutputMode config.mode = OutputMode::Intermediate
when "binary", "b"
# Creates a new configuration. config.mode = OutputMode::Binary
def initialize(@file = "", else
@mode = OutputMode::Tree) puts "Invalid mode type. Using default."
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
return config 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
return config
end
# Validates the options provided, returning true if def validate!
# they are valid and false otherwise. if file == ""
def validate! puts "No source file specified."
if file == "" return false
puts "No source file specified." elsif !File.exists? file
return false puts "Unable to open source file."
elsif !File.exists? file return false
puts "Unable to open source file."
return false
end
return true
end end
return true
end end
end end
end end

View File

@ -1,37 +1,33 @@
require "./tree.cr" require "./tree.cr"
module Chalk module Chalk
module Trees class ConstantFolder < Transformer
# `Trees::Transformer` that turns operations on private def perform_op(op, left, right)
# Constants into constants. case op
class ConstantFolder < Transformer when TokenType::OpAdd
private def perform_op(op, left, right) left + right
case op when TokenType::OpSub
when Compiler::TokenType::OpAdd left - right
left + right when TokenType::OpMul
when Compiler::TokenType::OpSub left*right
left - right when TokenType::OpDiv
when Compiler::TokenType::OpMul left/right
left*right when TokenType::OpAnd
when Compiler::TokenType::OpDiv left & right
left/right when TokenType::OpOr
when Compiler::TokenType::OpAnd left | right
left & right else TokenType::OpXor
when Compiler::TokenType::OpOr left ^ right
left | right
else Compiler::TokenType::OpXor
left ^ right
end
end end
end
def transform(tree : Trees::TreeOp) def transform(tree : TreeOp)
if tree.left.is_a?(Trees::TreeLit) && tree.right.is_a?(Trees::TreeLit) if tree.left.is_a?(TreeLit) && tree.right.is_a?(TreeLit)
return Trees::TreeLit.new perform_op(tree.op, return TreeLit.new perform_op(tree.op,
tree.left.as(Trees::TreeLit).lit, tree.left.as(TreeLit).lit,
tree.right.as(Trees::TreeLit).lit) tree.right.as(TreeLit).lit)
end
return tree
end end
return tree
end end
end end
end end

View File

@ -1,111 +1,75 @@
module Chalk module Chalk
module Compiler module Emitter
# Module to emit instructions and store def load(into, value)
# them into an existing array. inst = LoadInstruction.new into, value.to_i32
module Emitter @instructions << inst
# Emits an instruction to load a *value* into a register, *into*. return inst
def load(into, value) end
inst = Ir::LoadInstruction.new into, value.to_i32
@instructions << inst
return inst
end
# Emits an instruction to load a register, *from*, into def loadr(into, from)
# another register, *into* inst = LoadRegInstruction.new into, from
def loadr(into, from) @instructions << inst
inst = Ir::LoadRegInstruction.new into, from return inst
@instructions << inst end
return inst
end
# Emits an instruction that's converted def op(op, into, from)
# to an operation, *op* that mutates the register, *into*, inst = OpInstruction.new op, into, from
# with the right hand operand *from* @instructions << inst
def op(op, into, from) return inst
inst = Ir::OpInstruction.new op, into, from end
@instructions << inst
return inst
end
# Emits an instruction that's converted def opr(op, into, from)
# to an operation, *op*, that mutates the register, *into*, inst = OpRegInstruction.new op, into, from
# with the right hand operand (a register), *from* @instructions << inst
def opr(op, into, from) return inst
inst = Ir::OpRegInstruction.new op, into, from end
@instructions << inst
return inst
end
# Emits a "skip next instruction if not equal" def sne(l, r)
# instruction. The left hand side is a register, inst = SkipNeInstruction.new l, r
# an the right hand side is a value. @instructions << inst
def sne(l, r) return inst
inst = Ir::SkipNeInstruction.new l, r end
@instructions << inst
return inst
end
# Emits an instruction to jump relative to def jr(o)
# where the instruction is. inst = JumpRelativeInstruction.new o
# ``` @instructions << inst
# jr 0 # Infinite loop return inst
# jr -1 # Run previous instruction end
# jr 1 # pretty much a no-op.
# ```
def jr(o)
inst = Ir::JumpRelativeInstruction.new o
@instructions << inst
return inst
end
# Emits instruction that stores register 0 through *up_to* into def store(up_to)
# memory at address I. inst = StoreInstruction.new up_to
def store(up_to) @instructions << inst
inst = Ir::StoreInstruction.new up_to return inst
@instructions << inst end
return inst
end
# Emits instruction that loads values from address I into def restore(up_to)
# register 0 through *up_t* inst = RestoreInstruction.new up_to
def restore(up_to) @instructions << inst
inst = Ir::RestoreInstruction.new up_to return inst
@instructions << inst end
return inst
end
# Emits a return instruction. def ret
def ret inst = ReturnInstruction.new
inst = Ir::ReturnInstruction.new @instructions << inst
@instructions << inst return inst
return inst end
end
# Emits an instruction to call def call(func)
# the given function name. inst = CallInstruction.new func
def call(func) @instructions << inst
inst = Ir::CallInstruction.new func return inst
@instructions << inst end
return inst
end
# Emits instruction to set I def setis
# to the baste stack location. The stack inst = SetIStackInstruction.new
# pointer will need to be added to I @instructions << inst
# to get the next available stack slot. return inst
def setis end
inst = Ir::SetIStackInstruction.new
@instructions << inst
return inst
end
# Emits instruction to add the value of a def addi(reg)
# register to I inst = AddIRegInstruction.new reg
def addi(reg) @instructions << inst
inst = Ir::AddIRegInstruction.new reg return inst
@instructions << inst
return inst
end
end end
end end
end end

View File

@ -1,16 +1,13 @@
module Chalk module Chalk
module Trees class CallVisitor < Visitor
# Visitor that finds all function calls in a function. property calls : Set(String)
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

View File

@ -1,65 +1,58 @@
module Chalk module Chalk
module Builtin class InlineDrawFunction < InlineFunction
# Inline function to draw sprite at address I. def initialize
class InlineDrawFunction < InlineFunction @param_count = 3
def initialize
@param_count = 3
end
def generate!(emitter, params, table, target, free)
if !params[2].is_a?(Trees::TreeLit)
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
# Inline function to await for a key and return it. def generate!(emitter, params, table, target, free)
class InlineAwaitKeyFunction < InlineFunction if !params[2].is_a?(TreeLit)
def initialize raise "Third parameter must be a constant."
@param_count = 0
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
def generate!(emitter, params, table, target, free) class InlineAwaitKeyFunction < InlineFunction
emitter.instructions << Ir::AwaitKeyInstruction.new target def initialize
end @param_count = 0
end end
# Inline function to get font for a given value. def generate!(emitter, params, table, target, free)
class InlineGetFontFunction < InlineFunction emitter.instructions << AwaitKeyInstruction.new target
def initialize end
@param_count = 1 end
end
def generate!(emitter, params, table, target, free) class InlineGetFontFunction < InlineFunction
emitter.generate! params[0], table, free, free + 1 def initialize
emitter.instructions << Ir::GetFontInstruction.new free @param_count = 1
end
end end
# Inline function to set the delay timer. def generate!(emitter, params, table, target, free)
class InlineSetDelayFunction < InlineFunction emitter.generate! params[0], table, free, free + 1
def initialize emitter.instructions << GetFontInstruction.new free
@param_count = 1 end
end end
def generate!(emitter, params, table, target, free) class InlineSetDelayFunction < InlineFunction
emitter.generate! params[0], table, free, free + 1 def initialize
emitter.instructions << Ir::SetDelayTimerInstruction.new free @param_count = 1
end
end end
# Inline function to get the delay timer. def generate!(emitter, params, table, target, free)
class InlineGetDelayFunction < InlineFunction emitter.generate! params[0], table, free, free + 1
def initialize emitter.instructions << SetDelayTimerInstruction.new free
@param_count = 0 end
end end
def generate!(emitter, params, table, target, free) class InlineGetDelayFunction < InlineFunction
emitter.instructions << Ir::GetDelayTimerInstruction.new target def initialize
end @param_count = 0
end
def generate!(emitter, params, table, target, free)
emitter.instructions << GetDelayTimerInstruction.new target
end end
end end
end end

View File

@ -1,371 +1,373 @@
require "./lexer.cr" require "./lexer.cr"
module Chalk module Chalk
module Ir class Instruction
# Base instruction class. def to_bin(i, index)
class Instruction return 0
# Converts the instruction to binary, using end
# A table for symbol lookups, the stack position, end
# and the inex of the instruction.
def to_bin(table, stack, index) class InstructionContext
return 0 property table : Table
end property stack : Int32
def initialize(@table, @stack)
end
end
class LoadInstruction < Instruction
property register : Int32
property value : Int32
def initialize(@register, @value)
end end
# Instruction to load a value into a register. def to_s(io)
class LoadInstruction < Instruction io << "load R"
def initialize(@register : Int32, @value : Int32) @register.to_s(16, io)
end io << " " << @value
def to_s(io)
io << "load R"
@register.to_s(16, io)
io << " " << @value
end
def to_bin(table, stack, index)
0x6000 | (@register << 8) | @value
end
end end
# Instruction to load a register into another register. def to_bin(i, index)
class LoadRegInstruction < Instruction 0x6000 | (@register << 8) | @value
# Gets the register being written to. end
getter into end
# Gets the register being used as right-hand operand.
getter from
def initialize(@into : Int32, @from : Int32) class LoadRegInstruction < Instruction
end property into : Int32
property from : Int32
def to_s(io) def initialize(@into, @from)
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
# Instruction to perform an operation on a register and a value, def to_s(io)
# storing the output back into the register. io << "loadr R"
class OpInstruction < Instruction @into.to_s(16, io)
def initialize(@op : Compiler::TokenType, @into : Int32, @value : Int32) io << " R"
end @from.to_s(16, io)
def to_s(io)
io << "op " << @op << " R"
@into.to_s(16, io)
io << " " << @value
end
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
# Instruction to perform an operation on a register and another register, def to_bin(i, index)
# storing the output back into left hand register. 0x8000 | (@into << 8) | (@from << 4)
class OpRegInstruction < Instruction end
def initialize(@op : Compiler::TokenType, @into : Int32, @from : Int32) end
end
def to_s(io) class OpInstruction < Instruction
io << "opr " << @op << " R" property op : TokenType
@into.to_s(16, io) property into : Int32
io << " R" property value : Int32
@from.to_s(16, io)
end
def to_bin(table, stack, index) def initialize(@op, @into, @value)
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
# Instruction to write registers to memory at address I. def to_s(io)
# The *up_to* parameter specifies the highest register io << "op " << op << " R"
# that should be stored. @into.to_s(16, io)
class StoreInstruction < Instruction io << " " << @value
def initialize(@up_to : Int32)
end
def to_s(io)
io << "store R"
@up_to.to_s(16, io)
end
def to_bin(table, stack, index)
return 0xf055 | (@up_to << 8)
end
end end
# Instruction to read registers from memory at address I. def to_bin(i, index)
# The *up_to* parameter specifies the highest register case op
# that should be read into. when TokenType::OpAdd
class RestoreInstruction < Instruction return 0x7000 | (@into << 8) | @value
def initialize(@up_to : Int32) else
end raise "Invalid instruction"
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
# Instruction to return from a call.
class ReturnInstruction < Instruction
def initialize
end
def to_s(io)
io << "return"
end
def to_bin(table, stack, index)
return 0x00ee
end
end
# Instruction to jump relative to its own position.
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
# Instruction to skip the next instruction if
# the left-hand register is equal to the right-hand value.
class SkipEqInstruction < Instruction
def initialize(@left : Int32, @right : Int32)
end
def to_s(io)
io << "seq R"
@left.to_s(16, io)
io << " " << @right
end
def to_bin(table, stack, index)
return 0x3000 | (@left << 8) | @right
end
end
# Instruction to skip the next instruction if
# the left-hand register is not equal to the right-hand value.
class SkipNeInstruction < Instruction
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
# Instruction to skip the next instruction if
# the left-hand register is equal to the right-hand register.
class SkipRegEqInstruction < Instruction
def initialize(@left : Int32, @right : Int32)
end
def to_s(io)
io << "seqr R"
@left.to_s(16, io)
io << " R"
@right.to_s(16, io)
end
def to_bin(table, stack, index)
return 0x5000 | (@left << 8) | (@right << 4)
end
end
# Instruction to skip the next instruction if
# the left-hand register is not equal to the right-hand register.
class SkipRegNeInstruction < Instruction
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
# Instruction to call a function by name.
class CallInstruction < Instruction
# Gets the name of the function being called.
getter name
def initialize(@name : String)
end
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
# Instruction to set I to the base position of the stack.
class SetIStackInstruction < Instruction
def to_s(io)
io << "setis"
end
def to_bin(table, stack, index)
return 0xa000 | (stack * 2 + 0x200)
end
end
# Instruction to add a register to I.
class AddIRegInstruction < Instruction
def initialize(@reg : Int32)
end
def to_s(io)
io << "addi R"
@reg.to_s(16, io)
end
def to_bin(table, stack, index)
return 0xf000 | (@reg << 8) | 0x1e
end
end
# Instruction to draw on screen.
# The x and y coordinates specify the position of the sprite,
# and the height gives the height of the sprite.
class DrawInstruction < Instruction
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
# Instruction to await a key press and store it into a register.
class AwaitKeyInstruction < Instruction
def initialize(@into : Int32)
end
def to_s(io)
io << "getk R"
@into.to_s(16, io)
end
def to_bin(table, stack, index)
return 0xf00a | (@into << 8)
end
end
# Instruction to set I to the font given by the value
# 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
# Instruction to set the delay timer to the value
# of the given register.
class SetDelayTimerInstruction < Instruction
def initialize(@from : Int32)
end
def to_s(io)
io << "set_delay R"
@from.to_s(16, io)
end
def to_bin(table, stack, index)
return 0xf015 | (@from << 8)
end
end
# Instruction to get the delay timer, and store
# the value into the given register.
class GetDelayTimerInstruction < Instruction
def initialize(@into : Int32)
end
def to_s(io)
io << "get_delay R"
@into.to_s(16, io)
end
def to_bin(table, stack, index)
return 0xf007 | (@into << 8)
end end
end end
end end
class OpRegInstruction < Instruction
property op : TokenType
property into : 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
return 0x8000 | (@into << 8) | (@from << 4) | code
end
end
class StoreInstruction < Instruction
property up_to : Int32
def initialize(@up_to)
end
def to_s(io)
io << "store R"
@up_to.to_s(16, io)
end
def to_bin(i, index)
return 0xf055 | (@up_to << 8)
end
end
class RestoreInstruction < Instruction
property up_to : Int32
def initialize(@up_to)
end
def to_s(io)
io << "restore R"
@up_to.to_s(16, io)
end
def to_bin(i, index)
return 0xf065 | (@up_to << 8)
end
end
class ReturnInstruction < Instruction
def initialize
end
def to_s(io)
io << "return"
end
def to_bin(i, index)
return 0x00ee
end
end
class JumpRelativeInstruction < Instruction
property offset : Int32
def initialize(@offset)
end
def to_s(io)
io << "jr " << @offset
end
def to_bin(i, index)
return 0x1000 | ((@offset + index) * 2 + 0x200)
end
end
class SkipEqInstruction < Instruction
property left : Int32
property right : Int32
def initialize(@left, @right)
end
def to_s(io)
io << "seq R"
@left.to_s(16, io)
io << " " << right
end
def to_bin(i, index)
return 0x3000 | (@left << 8) | @right
end
end
class SkipNeInstruction < Instruction
property left : Int32
property right : Int32
def initialize(@left, @right)
end
def to_s(io)
io << "sne R"
@left.to_s(16, io)
io << " " << right
end
def to_bin(i, index)
return 0x4000 | (@left << 8) | @right
end
end
class SkipRegEqInstruction < Instruction
property left : Int32
property right : Int32
def initialize(@left, @right)
end
def to_s(io)
io << "seqr R"
@left.to_s(16, io)
io << " R"
@right.to_s(16, io)
end
def to_bin(i, index)
return 0x5000 | (@left << 8) | (@right << 4)
end
end
class SkipRegNeInstruction < Instruction
property left : Int32
property right : Int32
def initialize(@left, @right)
end
def to_s(io)
io << "sner R"
@left.to_s(16, io)
io << " R"
@right.to_s(16, io)
end
def to_bin(i, index)
return 0x9000 | (@left << 8) | (@right << 4)
end
end
class CallInstruction < Instruction
property name : String
def initialize(@name)
end
def to_s(io)
io << "call " << @name
end
def to_bin(i, index)
return 0x2000 | (i.table[name]?.as(FunctionEntry).addr * 2 + 0x200)
end
end
class SetIStackInstruction < Instruction
def to_s(io)
io << "setis"
end
def to_bin(i, index)
return 0xa000 | (i.stack * 2 + 0x200)
end
end
class AddIRegInstruction < Instruction
property reg : Int32
def initialize(@reg)
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

View File

@ -1,93 +1,82 @@
require "lex" require "lex"
module Chalk module Chalk
module Compiler enum TokenType
# The type of a token that can be lexed. Any,
enum TokenType Str,
Any, Id,
Str, LitDec,
Id, LitBin,
LitDec, LitHex,
LitBin, OpAdd
LitHex, OpSub
OpAdd OpMul
OpSub OpDiv
OpMul OpOr
OpDiv OpAnd
OpOr OpXor
OpAnd KwSprite
OpXor KwInline
KwSprite KwFun
KwInline KwU0
KwFun KwU8
KwU0 KwU12
KwU8 KwVar
KwU12 KwIf
KwVar KwElse
KwIf KwWhile
KwElse KwReturn
KwWhile end
KwReturn
class Token
def initialize(@string : String, @type : TokenType)
end end
# A class that stores the string it matched and its token type. getter string : String
class Token getter type : TokenType
def initialize(@string : String, @type : TokenType) end
end
# Gets the string this token represents. class Lexer
getter string : String def initialize
# Gets the type of this token. @lexer = Lex::Lexer.new
getter type : TokenType @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 end
# Creates a new Lexer with default token values. def lex(string)
# The lexer is backed by liblex. When a string is return @lexer.lex(string)
# matched by several tokens, the longest match is chosen .select { |t| !t[0][0].whitespace? }
# first, followed by the match with the highest enum value. .map do |tuple|
class Lexer string, id = tuple
def initialize Token.new(string, TokenType.new(id))
@lexer = Lex::Lexer.new end
@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

View File

@ -1,70 +1,41 @@
module Chalk module Chalk
module Compiler class Optimizer
# Class to optimize instructions. private def check_dead(inst)
class Optimizer if inst.is_a?(LoadRegInstruction)
# Checks if *inst* is "dead code", return inst.from == inst.into
# 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
# Optimizes *instructions* in the basic block given by the *range*, private def optimize!(instructions, range)
# storing addresses of instructions to be deleted into *deletions*, offset = 0
# and the number of deleted instructions so far into *deletions_at* range.each do |index|
private def optimize!(instructions, range, deletions, deletions_at) if check_dead(instructions[index + offset])
range.each do |index| instructions.delete_at(index + offset)
if check_dead(instructions[index]) offset -= 1
deletions << index
end
deletions_at[index] = deletions.size
end end
end end
return offset
end
# Optimizes the given list of instructions. def optimize(instructions)
# The basic blocks are inferred from the various instructions = instructions.dup
# jumps and skips. block_boundaries = [instructions.size]
def optimize(instructions) instructions.each_with_index do |inst, i|
instructions = instructions.dup if inst.is_a?(JumpRelativeInstruction)
block_boundaries = [instructions.size] block_boundaries << (inst.offset + i)
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
block_boundaries.uniq!.sort!
previous = 0
deletions = [] of Int32
deletions_at = {} of Int32 => Int32
block_boundaries.each do |boundary|
range = 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
block_boundaries.sort!
previous = 0
offset = 0
block_boundaries.each do |boundary|
range = (previous + offset)...(boundary + offset)
offset += optimize!(instructions, range)
previous = boundary
end
return instructions
end end
end end
end end

View File

@ -1,195 +1,173 @@
require "./parser_builder.cr" require "./parser_builder.cr"
module Chalk module Chalk
module ParserCombinators class Parser
# Parser created out of the various parser combinators. include ParserBuilder
class Parser
include ParserBuilder
# Creates a parser for a type. private def create_type
private def create_type either(type(TokenType::KwU0), type(TokenType::KwU8), type(TokenType::KwU12))
either(type(Compiler::TokenType::KwU0), type(Compiler::TokenType::KwU8), type(Compiler::TokenType::KwU12)) end
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
# Creates a parser for an integer literal. private def create_op_exprs(atom, ops)
private def create_lit ops.reduce(atom) do |previous, current|
dec_parser = type(Compiler::TokenType::LitDec).transform &.string.to_i64 create_op_expr(previous, current)
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
# Creates a parser for an operation with a given *atom* parser private def create_call(expr)
# and *op* parser. call = type(TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr|
private def create_op_expr(atom, op) arr = arr.flatten
pl = PlaceholderParser(Trees::Tree).new name = arr[0].as(Token).string
recurse = atom.then(op).then(pl).transform do |arr| params = arr[2..arr.size - 2].map &.as(Tree)
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
Trees::TreeOp.new( cond = arr[2].as(Tree)
arr[1].as(Compiler::Token).type, code = arr[4].as(Tree)
arr[0].as(Trees::Tree), otherwise = arr.size == 7 ? arr[6].as(Tree) : nil
arr[2].as(Trees::Tree)).as(Trees::Tree) TreeIf.new(cond, code, otherwise).as(Tree)
end end
pl.parser = either(recurse, atom) return iff
return pl end
end
# Creates a parser to parse layers of *ops* with multiple private def create_while(expr, block)
# levels of precedence, specified by their order. The *atom* whilee = type(TokenType::KwWhile).then(char '(').then(expr).then(char ')').then(block).transform do |arr|
# is the most basic expression. arr = arr.flatten
private def create_op_exprs(atom, ops) cond = arr[2].as(Tree)
ops.reduce(atom) do |previous, current| code = arr[4].as(Tree)
create_op_expr(previous, current) TreeWhile.new(cond, code).as(Tree)
end
end end
return whilee
end
# Creates a parser for a call, with the given expression parser. private def create_return(expr)
private def create_call(expr) returnn = type(TokenType::KwReturn).then(expr).then(char ';').transform do |arr|
call = type(Compiler::TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr| arr = arr.flatten
value = arr[1].as(Tree)
TreeReturn.new(value).as(Tree)
end
return returnn
end
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(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[0].as(Compiler::Token).string name = arr[1].as(Token).string
params = arr[2..arr.size - 2].map &.as(Trees::Tree) params = arr[3..arr.size - 5].map &.as(Token).string
Trees::TreeCall.new(name, params).as(Trees::Tree) code = arr[arr.size - 1].as(Tree)
type = arr[arr.size - 2].as(Token).type
TreeFunction.new(name, params, code)
end end
return call return func
end end
# Creates a parser for an expression. def initialize
private def create_expr _, block = create_statement_block
expr_place = PlaceholderParser(Trees::Tree).new @parser = many(create_func(block, create_type)).as(BasicParser(Array(TreeFunction)))
literal = create_lit end
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)
ops = [either(type(Compiler::TokenType::OpMul), type(Compiler::TokenType::OpDiv)), def parse?(tokens)
either(type(Compiler::TokenType::OpAdd), type(Compiler::TokenType::OpSub)), return @parser.parse?(tokens, 0).try &.[0]
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

View File

@ -2,47 +2,37 @@ require "./lexer.cr"
require "./parsers.cr" require "./parsers.cr"
module Chalk module Chalk
module ParserCombinators module ParserBuilder
module ParserBuilder def type(type) : BasicParser(Token)
# Creates a parser for a given token type. return TypeParser.new(type).as(BasicParser(Token))
def type(type) : BasicParser(Compiler::Token) end
return TypeParser.new(type).as(BasicParser(Compiler::Token))
end
# Creates a parser for a specific character. def char(type) : BasicParser(Token)
def char(type) : BasicParser(Compiler::Token) return CharParser.new(type).as(BasicParser(Token))
return CharParser.new(type).as(BasicParser(Compiler::Token)) end
end
# Creates a parser that transforms a value according to a block. def transform(parser : BasicParser(T), &transform : T -> R) forall T, R
def transform(parser : BasicParser(T), &transform : T -> R) forall T, R return TransformParser.new(parser, &transform).as(BasicParser(R))
return TransformParser.new(parser, &transform).as(BasicParser(R)) end
end
# Creates a parser that allows for failure to match. def optional(parser : BasicParser(T)) : BasicParser(T?) forall T
def optional(parser : BasicParser(T)) : BasicParser(T?) forall T return OptionalParser.new(parser).as(BasicParser(T?))
return OptionalParser.new(parser).as(BasicParser(T?)) end
end
# Creates a parser that tries several parsers in sequence until one succeeds. def either(*args : BasicParser(T)) : BasicParser(T) forall T
def either(*args : BasicParser(T)) : BasicParser(T) forall T return EitherParser.new(args.to_a).as(BasicParser(T))
return EitherParser.new(args.to_a).as(BasicParser(T)) end
end
# Creates a parser that parses one or more of the given parsers. def many(parser : BasicParser(T)) : BasicParser(Array(T)) forall T
def many(parser : BasicParser(T)) : BasicParser(Array(T)) forall T return ManyParser.new(parser).as(BasicParser(Array(T)))
return ManyParser.new(parser).as(BasicParser(Array(T))) end
end
# Creates a parser that parses one parser delimited by another. def delimited(parser : BasicParser(T), delimiter : BasicParser(R)) : BasicParser(Array(T)) forall T, R
def delimited(parser : BasicParser(T), delimiter : BasicParser(R)) : BasicParser(Array(T)) forall T, R return DelimitedParser.new(parser, delimiter).as(BasicParser(Array(T)))
return DelimitedParser.new(parser, delimiter).as(BasicParser(Array(T))) end
end
# Creates a parser that parses one parser, then the next. def then(first : BasicParser(T), second : BasicParser(R)) forall T, R
def then(first : BasicParser(T), second : BasicParser(R)) forall T, R return NextParser.new(first, second).as(BasicParser(Array(T | R)))
return NextParser.new(first, second).as(BasicParser(Array(T | R)))
end
end end
end end
end end

View File

@ -1,171 +1,146 @@
module Chalk module Chalk
module ParserCombinators abstract class BasicParser(T)
# Abstract class for a parser function, abstract def parse?(tokens : Array(Token),
# as used in parser combinators. This is basically index : Int64) : Tuple(T, Int64)?
# 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)?
# Attempts to parse the given tokens like `#parse?`, but throws def parse(tokens, index)
# on error. return parse?(tokens, index).not_nil!
def parse(tokens, index)
return parse?(tokens, index).not_nil!
end
# Applies the given transformation to this parser,
# creating a new parser.
def transform(&transform : T -> R) forall R
return TransformParser.new(self, &transform).as(BasicParser(R))
end
# Creates a sequence with the given parser,
# 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
# Parser that expects a specific token type. def transform(&transform : T -> R) forall R
class TypeParser < BasicParser(Compiler::Token) return TransformParser.new(self, &transform).as(BasicParser(R))
def initialize(@type : Compiler::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 end
# Parser that expects a specific character. def then(other : BasicParser(R)) : BasicParser(Array(T | R)) forall R
class CharParser < BasicParser(Compiler::Token) return NextParser.new(self, other).as(BasicParser(Array(T | R)))
def initialize(@char : Char) end
end end
def parse?(tokens, index) class TypeParser < BasicParser(Token)
return nil unless index < tokens.size def initialize(@type : TokenType)
return nil unless (tokens[index].type == Compiler::TokenType::Any) &&
tokens[index].string[0] == @char
return {tokens[index], index + 1}
end
end end
# Parser that applies a transformation to the output def parse?(tokens, index)
# of its child parser. return nil unless index < tokens.size
class TransformParser(T, R) < BasicParser(R) return nil unless tokens[index].type == @type
def initialize(@parser : BasicParser(T), &@block : T -> R) return {tokens[index], index + 1}
end end
end
def parse?(tokens, index) class CharParser < BasicParser(Token)
if parsed = @parser.parse?(tokens, index) def initialize(@char : Char)
return {@block.call(parsed[0]), parsed[1]} 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
return nil
end
end
class OptionalParser(T) < BasicParser(T?)
def initialize(@parser : BasicParser(T))
end
def parse?(tokens, index)
if parsed = @parser.parse?(tokens, index)
return {parsed[0], parsed[1]}
end
return {nil, index}
end
end
class EitherParser(T) < BasicParser(T)
def initialize(@parsers : Array(BasicParser(T)))
end
def parse?(tokens, index)
@parsers.each do |parser|
if parsed = parser.parse?(tokens, index)
return parsed
end end
return nil
end end
return nil
end
end
class ManyParser(T) < BasicParser(Array(T))
def initialize(@parser : BasicParser(T))
end end
# Parser that attempts to use its child parser, def parse?(tokens, index)
# and successfully returns nil if the child parser fails. many = [] of T
class OptionalParser(T) < BasicParser(T?) while parsed = @parser.parse?(tokens, index)
def initialize(@parser : BasicParser(T)) item, index = parsed
many << item
end end
return {many, index}
end
end
def parse?(tokens, index) class DelimitedParser(T, R) < BasicParser(Array(T))
if parsed = @parser.parse?(tokens, index) def initialize(@parser : BasicParser(T), @delimiter : BasicParser(R))
return {parsed[0], parsed[1]}
end
return {nil, index}
end
end end
# Parser that tries all of its children until one succeeds. def parse?(tokens, index)
class EitherParser(T) < BasicParser(T) array = [] of T
def initialize(@parsers : Array(BasicParser(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
def parse?(tokens, index) return {array, index}
@parsers.each do |parser| end
if parsed = parser.parse?(tokens, index) end
return parsed
end class NextParser(T, R) < BasicParser(Array(T | R))
end def initialize(@first : BasicParser(T), @second : BasicParser(R))
return nil
end
end end
# Parser that parses at least one of a given type. def parse?(tokens, index)
class ManyParser(T) < BasicParser(Array(T)) first = @first.parse?(tokens, index)
def initialize(@parser : BasicParser(T)) return nil unless first
end first_value, index = first
def parse?(tokens, index) second = @second.parse?(tokens, index)
many = [] of T return nil unless second
while parsed = @parser.parse?(tokens, index) second_value, index = second
item, index = parsed
many << item array = Array(T | R).new
end array << first_value << second_value
return {many, index} return {array, index}
end end
end
class PlaceholderParser(T) < BasicParser(T)
property parser : BasicParser(T)?
def initialize
@parser = nil
end end
# Parser that parses at least 0 of its child parser, def parse?(tokens, index)
# delimited with its other child parser. @parser.try &.parse?(tokens, index)
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

View File

@ -1,55 +1,53 @@
require "./tree.cr" require "./tree.cr"
module Chalk module Chalk
module Trees class PrintVisitor < Visitor
# Visitor that prints a `Tree`. def initialize(@stream : IO)
class PrintVisitor < Visitor @indent = 0
def initialize(@stream : IO) end
@indent = 0
end
private def print_indent def print_indent
@indent.times do @indent.times do
@stream << " " @stream << " "
end
end end
end
def visit(id : TreeId) def visit(id : TreeId)
print_indent print_indent
@stream << id.id << "\n" @stream << id.id << "\n"
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 visit(lit : TreeLit) def finish(function : TreeFunction)
print_indent @indent -= 1
@stream << lit.lit << "\n" end
end
def visit(op : TreeOp) macro forward(text, type)
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"
@ -61,19 +59,18 @@ 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

View File

@ -1,68 +1,51 @@
module Chalk module Chalk
module Compiler class Entry
# An entry in the symbol table. end
class Entry
class FunctionEntry < Entry
property function : TreeFunction | BuiltinFunction | InlineFunction
property addr : Int32
def initialize(@function, @addr = -1)
end end
# An entry that represents a function in the symbol table. def to_s(io)
class FunctionEntry < Entry io << "[function]"
# Gets the function stored in this entry. end
getter function end
# Gets the address in code of this function.
getter addr
# Sets the address in code of this function.
setter addr
def initialize(@function : Trees::TreeFunction | Builtin::BuiltinFunction | Builtin::InlineFunction, class VarEntry < Entry
@addr = -1) property register : Int32
end
def to_s(io) def initialize(@register)
io << "[function]"
end
end end
# An entry that represents a variable in the symbol table. def to_s(io)
class VarEntry < Entry io << "[variable] " << "(R" << @register.to_s(16) << ")"
# Gets the register occupied by the variable end
# in this entry. end
getter register
def initialize(@register : Int32) class Table
end property parent : Table?
def to_s(io) def initialize(@parent = nil)
io << "[variable] " << "(R" << @register.to_s(16) << ")" @data = {} of String => Entry
end
end end
# A symbol table. def []?(key)
class Table if entry = @data[key]?
# Gets the parent of this table. return entry
getter parent
def initialize(@parent : Table? = nil)
@data = {} of String => Entry
end end
return @parent.try &.[key]?
end
# Looks up the given *key* first in this table, def []=(key, entry)
# then in its parent, continuing recursively. @data[key] = entry
def []?(key) end
if entry = @data[key]?
return entry
end
return @parent.try &.[key]?
end
# Stores an *entry* under the given *key* into this table. def to_s(io)
def []=(key, entry) @parent.try &.to_s(io)
@data[key] = entry io << @data.map { |k, v| k + ": " + v.to_s }.join("\n")
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

View File

@ -1,248 +1,228 @@
module Chalk module Chalk
module Trees class Visitor
# A class used to visit nodes of a tree. def visit(tree)
class Visitor
def visit(tree)
end
def finish(tree)
end
end end
# A class used to transform a tree, bottom up. def finish(tree)
# "Modern Compiler Design" refers to this technique end
# as BURS. end
class Transformer
def transform(tree) class Transformer
return tree def transform(tree)
end return tree
end
end
class Tree
def accept(v)
v.visit(self)
v.finish(self)
end end
# The base class of a tree. def apply(t)
class Tree return t.transform(self)
def accept(v) end
v.visit(self) end
v.finish(self)
end
def apply(t) class TreeId < Tree
return t.transform(self) property id : String
end
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 end
# A tree that represents an ID. def accept(v)
class TreeId < Tree v.visit(self)
property id : String @params.each &.accept(v)
v.finish(self)
def initialize(@id)
end
end end
# A tree that represents an integer literal. def apply(t)
class TreeLit < Tree @params.map! do |param|
property lit : Int64 param.apply(t)
def initialize(@lit)
end end
return t.transform(self)
end
end
class TreeOp < Tree
property op : TokenType
property left : Tree
property right : Tree
def initialize(@op, @left, @right)
end end
# A tree that represents a function call. def accept(v)
class TreeCall < Tree v.visit(self)
property name : String @left.accept(v)
property params : Array(Tree) @right.accept(v)
v.finish(self)
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
return t.transform(self)
end
end end
# A tree that represents an operation on two values. def apply(t)
class TreeOp < Tree @left = @left.apply(t)
property op : Compiler::TokenType @right = @right.apply(t)
property left : Tree return t.transform(self)
property right : Tree end
end
def initialize(@op, @left, @right) class TreeBlock < Tree
end property children : Array(Tree)
def accept(v) def initialize(@children)
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
# A tree that represents a block of statements. def accept(v)
class TreeBlock < Tree v.visit(self)
property children : Array(Tree) @children.each &.accept(v)
v.finish(self)
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
return t.transform(self)
end
end end
# A tree that represents a function declaration. def apply(t)
class TreeFunction < Tree @children.map! do |child|
property name : String child.apply(t)
property params : Array(String)
property block : Tree
def initialize(@name, @params, @block)
end end
return t.transform(self)
end
end
def param_count class TreeFunction < Tree
return @params.size property name : String
end property params : Array(String)
property block : Tree
def accept(v) def initialize(@name, @params, @block)
v.visit(self)
@block.accept(v)
v.finish(self)
end
def apply(t)
@block = @block.apply(t)
return t.transform(self)
end
end end
# A tree that represents the declaration of def param_count
# a new variable. return @params.size
class TreeVar < Tree
property name : String
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
# A tree that represents the assignment def accept(v)
# to an existing variable. v.visit(self)
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
# A tree that represents an if statement. def apply(t)
class TreeIf < Tree @block = @block.apply(t)
property condition : Tree return t.transform(self)
property block : Tree end
property otherwise : Tree? end
def initialize(@condition, @block, @otherwise = nil) class TreeVar < Tree
end property name : String
property expr : Tree
def accept(v) def initialize(@name, @expr)
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
# A tree that represents a while loop. def accept(v)
class TreeWhile < Tree v.visit(self)
property condition : Tree @expr.accept(v)
property block : Tree v.finish(self)
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
# A tree that represents a return statement. def apply(t)
class TreeReturn < Tree @expr = @expr.apply(t)
property rvalue : Tree return t.transform(self)
end
end
def initialize(@rvalue) class TreeAssign < Tree
end property name : String
property expr : Tree
def accept(v) def initialize(@name, @expr)
v.visit(self) end
@rvalue.accept(v)
v.finish(self)
end
def apply(t) def accept(v)
@rvalue = @rvalue.apply(t) v.visit(self)
return t.transform(self) @expr.accept(v)
end v.finish(self)
end
def apply(t)
@expr = @expr.apply(t)
return t.transform(self)
end
end
class TreeIf < Tree
property condition : Tree
property block : Tree
property otherwise : Tree?
def initialize(@condition, @block, @otherwise = nil)
end
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
class TreeWhile < Tree
property condition : Tree
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
class TreeReturn < Tree
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