Organize code into modules.

This commit is contained in:
Danila Fedorin 2018-08-01 22:40:41 -07:00
parent cd0e5c2919
commit 96059d6e04
18 changed files with 1493 additions and 1459 deletions

View File

@ -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

View File

@ -1,21 +1,23 @@
module Chalk module Chalk
class BuiltinFunction module Builtin
getter param_count : Int32 class BuiltinFunction
getter param_count : Int32
def initialize(@param_count) def initialize(@param_count)
end
def generate!(codegen)
end
end end
def generate!(codegen) class InlineFunction
end getter param_count : Int32
end
class InlineFunction def initialize(@param_count)
getter param_count : Int32 end
def initialize(@param_count) def generate!(codegen, params, table, target, free)
end end
def generate!(codegen, params, table, target, free)
end end
end end
end end

View File

@ -2,141 +2,143 @@ require "./ir.cr"
require "./emitter.cr" require "./emitter.cr"
module Chalk module Chalk
class CodeGenerator module Compiler
include Emitter class CodeGenerator
include Emitter
RETURN_REG = 14 RETURN_REG = 14
STACK_REG = 13 STACK_REG = 13
property instructions : Array(Instruction) property instructions : Array(Ir::Instruction)
def initialize(table, @function : TreeFunction) def initialize(table, @function : Trees::TreeFunction)
@registers = 0 @registers = 0
@instructions = [] of Instruction @instructions = [] of Ir::Instruction
@table = Table.new table @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 def generate!(tree, function : Builtin::InlineFunction, table, target, free)
entry = VarEntry.new free function.generate!(self, tree.params, table, target, free)
end
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! def generate!(tree, table, target, free)
generate!(@function.block, @table, -1, @registers) case tree
return @instructions 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
def generate!
generate!(@function.block, @table, -1, @registers)
return @instructions
end
end end
end end
end end

View File

@ -3,164 +3,166 @@ require "./constant_folder.cr"
require "./table.cr" require "./table.cr"
module Chalk module Chalk
class Compiler module Compiler
def initialize(@config : Config) class Compiler
@logger = Logger.new STDOUT def initialize(@config : Ui::Config)
@logger.debug("Initialized compiler") @logger = Logger.new STDOUT
@logger.level = Logger::DEBUG @logger.debug("Initialized compiler")
end @logger.level = Logger::DEBUG
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 end
@logger.debug("Finished tokenizing")
@logger.debug("Beginning parsing") private def create_trees(file)
parser = Parser.new string = File.read(file)
if trees = parser.parse?(tokens) @logger.debug("Tokenizing")
@logger.debug("Finished parsing") lexer = Lexer.new
@logger.debug("Beginning constant folding") tokens = lexer.lex string
folder = ConstantFolder.new if tokens.size == 0 && string != ""
trees.map! do |tree| raise "Unable to tokenize file."
@logger.debug("Constant folding #{tree.name}")
tree.apply(folder).as(TreeFunction)
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, instruction = 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
private def create_code(tree : BuiltinFunction, table, instruction = nil)
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, 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?(InlineFunction)
all_instructions.concat create_code(function, table)
all_instructions << ReturnInstruction.new
end end
file = File.open("out.ch8", "w") private def create_table(trees)
generate_binary(table, all_instructions, file) table = Table.new
file.close @logger.debug("Creating symbol table")
end 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
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
private def create_code(tree : Builtin::BuiltinFunction, table, instruction = nil)
instructions = [] of Ir::Instruction
tree.generate!(instructions)
return instructions
end
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
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 = Ir::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?(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
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
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,51 +1,53 @@
module Chalk module Chalk
enum OutputMode module Ui
Tree, enum OutputMode
Intermediate, Tree,
Binary Intermediate,
end Binary
class Config
property file : String
property mode : OutputMode
def initialize(@file = "",
@mode = OutputMode::Tree)
end end
def self.parse! class Config
config = self.new property file : String
OptionParser.parse! do |parser| property mode : OutputMode
parser.banner = "Usage: chalk [arguments]"
parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode| def initialize(@file = "",
case mode.downcase @mode = OutputMode::Tree)
when "tree", "t" end
config.mode = OutputMode::Tree
when "intermediate", "i" def self.parse!
config.mode = OutputMode::Intermediate config = self.new
when "binary", "b" OptionParser.parse! do |parser|
config.mode = OutputMode::Binary parser.banner = "Usage: chalk [arguments]"
else parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode|
puts "Invalid mode type. Using default." 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! def validate!
if file == "" if file == ""
puts "No source file specified." puts "No source file specified."
return false return false
elsif !File.exists? file elsif !File.exists? file
puts "Unable to open source file." puts "Unable to open source file."
return false return false
end
return true
end end
return true
end end
end end
end end

View File

@ -1,33 +1,35 @@
require "./tree.cr" require "./tree.cr"
module Chalk module Chalk
class ConstantFolder < Transformer module Trees
private def perform_op(op, left, right) class ConstantFolder < Transformer
case op private def perform_op(op, left, right)
when TokenType::OpAdd case op
left + right when Compiler::TokenType::OpAdd
when TokenType::OpSub left + right
left - right when Compiler::TokenType::OpSub
when TokenType::OpMul left - right
left*right when Compiler::TokenType::OpMul
when TokenType::OpDiv left*right
left/right when Compiler::TokenType::OpDiv
when TokenType::OpAnd left/right
left & right when Compiler::TokenType::OpAnd
when TokenType::OpOr left & right
left | right when Compiler::TokenType::OpOr
else TokenType::OpXor left | right
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

View File

@ -1,75 +1,77 @@
module Chalk module Chalk
module Emitter module Compiler
def load(into, value) module Emitter
inst = LoadInstruction.new into, value.to_i32 def load(into, value)
@instructions << inst inst = Ir::LoadInstruction.new into, value.to_i32
return inst @instructions << inst
end return inst
end
def loadr(into, from) def loadr(into, from)
inst = LoadRegInstruction.new into, from inst = Ir::LoadRegInstruction.new into, from
@instructions << inst @instructions << inst
return inst return inst
end end
def op(op, into, from) def op(op, into, from)
inst = OpInstruction.new op, into, from inst = Ir::OpInstruction.new op, into, from
@instructions << inst @instructions << inst
return inst return inst
end end
def opr(op, into, from) def opr(op, into, from)
inst = OpRegInstruction.new op, into, from inst = Ir::OpRegInstruction.new op, into, from
@instructions << inst @instructions << inst
return inst return inst
end end
def sne(l, r) def sne(l, r)
inst = SkipNeInstruction.new l, r inst = Ir::SkipNeInstruction.new l, r
@instructions << inst @instructions << inst
return inst return inst
end end
def jr(o) def jr(o)
inst = JumpRelativeInstruction.new o inst = Ir::JumpRelativeInstruction.new o
@instructions << inst @instructions << inst
return inst return inst
end end
def store(up_to) def store(up_to)
inst = StoreInstruction.new up_to inst = Ir::StoreInstruction.new up_to
@instructions << inst @instructions << inst
return inst return inst
end end
def restore(up_to) def restore(up_to)
inst = RestoreInstruction.new up_to inst = Ir::RestoreInstruction.new up_to
@instructions << inst @instructions << inst
return inst return inst
end end
def ret def ret
inst = ReturnInstruction.new inst = Ir::ReturnInstruction.new
@instructions << inst @instructions << inst
return inst return inst
end end
def call(func) def call(func)
inst = CallInstruction.new func inst = Ir::CallInstruction.new func
@instructions << inst @instructions << inst
return inst return inst
end end
def setis def setis
inst = SetIStackInstruction.new inst = Ir::SetIStackInstruction.new
@instructions << inst @instructions << inst
return inst return inst
end end
def addi(reg) def addi(reg)
inst = AddIRegInstruction.new reg inst = Ir::AddIRegInstruction.new reg
@instructions << inst @instructions << inst
return inst return inst
end
end end
end end
end end

View File

@ -1,13 +1,15 @@
module Chalk module Chalk
class CallVisitor < Visitor module Trees
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,58 +1,60 @@
module Chalk module Chalk
class InlineDrawFunction < InlineFunction module Builtin
def initialize class InlineDrawFunction < InlineFunction
@param_count = 3 def initialize
end @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) class InlineAwaitKeyFunction < InlineFunction
emitter.instructions << AwaitKeyInstruction.new target def initialize
end @param_count = 0
end 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) class InlineGetFontFunction < InlineFunction
emitter.generate! params[0], table, free, free + 1 def initialize
emitter.instructions << GetFontInstruction.new free @param_count = 1
end 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) class InlineSetDelayFunction < InlineFunction
emitter.generate! params[0], table, free, free + 1 def initialize
emitter.instructions << SetDelayTimerInstruction.new free @param_count = 1
end 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) class InlineGetDelayFunction < InlineFunction
emitter.instructions << GetDelayTimerInstruction.new target 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

View File

@ -1,373 +1,375 @@
require "./lexer.cr" require "./lexer.cr"
module Chalk module Chalk
class Instruction module Ir
def to_bin(i, index) class Instruction
return 0 def to_bin(i, index)
end return 0
end
class InstructionContext
property table : Table
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 class InstructionContext
property op : TokenType property table : Compiler::Table
property into : Int32 property stack : Int32
property from : Int32
def initialize(@op, @into, @from) def initialize(@table, @stack)
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
property up_to : Int32
def initialize(@up_to)
end end
def to_s(io) class LoadInstruction < Instruction
io << "store R" property register : Int32
@up_to.to_s(16, io) 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 end
def to_bin(i, index) class LoadRegInstruction < Instruction
return 0xf055 | (@up_to << 8) property into : Int32
end property from : Int32
end
class RestoreInstruction < Instruction def initialize(@into, @from)
property up_to : Int32 end
def initialize(@up_to) 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 end
def to_s(io) class OpInstruction < Instruction
io << "restore R" property op : Compiler::TokenType
@up_to.to_s(16, io) 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 Compiler::TokenType::OpAdd
return 0x7000 | (@into << 8) | @value
else
raise "Invalid instruction"
end
end
end end
def to_bin(i, index) class OpRegInstruction < Instruction
return 0xf065 | (@up_to << 8) property op : Compiler::TokenType
end property into : Int32
end property from : Int32
class ReturnInstruction < Instruction def initialize(@op, @into, @from)
def initialize 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 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_s(io) class StoreInstruction < Instruction
io << "return" 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 end
def to_bin(i, index) class RestoreInstruction < Instruction
return 0x00ee property up_to : Int32
end
end
class JumpRelativeInstruction < Instruction def initialize(@up_to)
property offset : Int32 end
def initialize(@offset) 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 end
def to_s(io) class ReturnInstruction < Instruction
io << "jr " << @offset def initialize
end
def to_s(io)
io << "return"
end
def to_bin(i, index)
return 0x00ee
end
end end
def to_bin(i, index) class JumpRelativeInstruction < Instruction
return 0x1000 | ((@offset + index) * 2 + 0x200) property offset : Int32
end
end
class SkipEqInstruction < Instruction def initialize(@offset)
property left : Int32 end
property right : Int32
def initialize(@left, @right) def to_s(io)
io << "jr " << @offset
end
def to_bin(i, index)
return 0x1000 | ((@offset + index) * 2 + 0x200)
end
end end
def to_s(io) class SkipEqInstruction < Instruction
io << "seq R" property left : Int32
@left.to_s(16, io) property right : Int32
io << " " << right
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 end
def to_bin(i, index) class SkipNeInstruction < Instruction
return 0x3000 | (@left << 8) | @right property left : Int32
end property right : Int32
end
class SkipNeInstruction < Instruction def initialize(@left, @right)
property left : Int32 end
property right : Int32
def initialize(@left, @right) 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 end
def to_s(io) class SkipRegEqInstruction < Instruction
io << "sne R" property left : Int32
@left.to_s(16, io) property right : Int32
io << " " << right
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 end
def to_bin(i, index) class SkipRegNeInstruction < Instruction
return 0x4000 | (@left << 8) | @right property left : Int32
end property right : Int32
end
class SkipRegEqInstruction < Instruction def initialize(@left, @right)
property left : Int32 end
property right : Int32
def initialize(@left, @right) 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 end
def to_s(io) class CallInstruction < Instruction
io << "seqr R" property name : String
@left.to_s(16, io)
io << " R" def initialize(@name)
@right.to_s(16, io) end
def to_s(io)
io << "call " << @name
end
def to_bin(i, index)
return 0x2000 | (i.table[name]?.as(Compiler::FunctionEntry).addr * 2 + 0x200)
end
end end
def to_bin(i, index) class SetIStackInstruction < Instruction
return 0x5000 | (@left << 8) | (@right << 4) def to_s(io)
end io << "setis"
end end
class SkipRegNeInstruction < Instruction def to_bin(i, index)
property left : Int32 return 0xa000 | (i.stack * 2 + 0x200)
property right : Int32 end
def initialize(@left, @right)
end end
def to_s(io) class AddIRegInstruction < Instruction
io << "sner R" property reg : Int32
@left.to_s(16, io)
io << " R" def initialize(@reg)
@right.to_s(16, io) 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 end
def to_bin(i, index) class DrawInstruction < Instruction
return 0x9000 | (@left << 8) | (@right << 4) property x : Int32
end property y : Int32
end property height : Int32
class CallInstruction < Instruction def initialize(@x, @y, @height)
property name : String end
def initialize(@name) 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 end
def to_s(io) class AwaitKeyInstruction < Instruction
io << "call " << @name 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 end
def to_bin(i, index) class GetFontInstruction < Instruction
return 0x2000 | (i.table[name]?.as(FunctionEntry).addr * 2 + 0x200) property from : Int32
end
end
class SetIStackInstruction < Instruction def initialize(@from)
def to_s(io) end
io << "setis"
def to_s(io)
io << "font R"
@from.to_s(16, io)
end
def to_bin(i, index)
return 0xf029 | (@from << 8)
end
end end
def to_bin(i, index) class SetDelayTimerInstruction < Instruction
return 0xa000 | (i.stack * 2 + 0x200) property from : Int32
end
end
class AddIRegInstruction < Instruction def initialize(@from)
property reg : Int32 end
def initialize(@reg) 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 end
def to_s(io) class GetDelayTimerInstruction < Instruction
io << "addi R" property into : Int32
reg.to_s(16, io)
end
def to_bin(i, index) def initialize(@into)
return 0xf000 | (@reg << 8) | 0x1e end
end
end
class DrawInstruction < Instruction def to_s(io)
property x : Int32 io << "get_delay R"
property y : Int32 @into.to_s(16, io)
property height : Int32 end
def initialize(@x, @y, @height) def to_bin(i, index)
end return 0xf007 | (@into << 8)
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

View File

@ -1,82 +1,84 @@
require "lex" require "lex"
module Chalk module Chalk
enum TokenType module Compiler
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
getter string : String class Token
getter type : TokenType def initialize(@string : String, @type : TokenType)
end end
class Lexer getter string : String
def initialize getter type : TokenType
@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 end
def lex(string) class Lexer
return @lexer.lex(string) def initialize
.select { |t| !t[0][0].whitespace? } @lexer = Lex::Lexer.new
.map do |tuple| @lexer.add_pattern(".", TokenType::Any.value)
string, id = tuple @lexer.add_pattern("\"(\\\\\"|[^\"])*\"",
Token.new(string, TokenType.new(id)) TokenType::Str.value)
end @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
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,55 +1,57 @@
module Chalk module Chalk
class Optimizer module Compiler
private def check_dead(inst) class Optimizer
if inst.is_a?(LoadRegInstruction) private def check_dead(inst)
return inst.from == inst.into if inst.is_a?(Ir::LoadRegInstruction)
end return inst.from == inst.into
return false
end
private def optimize!(instructions, range, deletions, deletions_at)
range.each do |index|
if check_dead(instructions[index])
deletions << index
end end
deletions_at[index] = deletions.size return false
end end
end
def optimize(instructions) private def optimize!(instructions, range, deletions, deletions_at)
instructions = instructions.dup range.each do |index|
block_boundaries = [instructions.size] if check_dead(instructions[index])
instructions.each_with_index do |inst, i| deletions << index
if inst.is_a?(JumpRelativeInstruction) end
block_boundaries << i deletions_at[index] = deletions.size
block_boundaries << (inst.offset + i)
end end
end end
block_boundaries.uniq!.sort!
previous = 0 def optimize(instructions)
deletions = [] of Int32 instructions = instructions.dup
deletions_at = {} of Int32 => Int32 block_boundaries = [instructions.size]
block_boundaries.each do |boundary| instructions.each_with_index do |inst, i|
range = previous...boundary if inst.is_a?(Ir::JumpRelativeInstruction)
optimize!(instructions, range, deletions, deletions_at) block_boundaries << i
previous = boundary block_boundaries << (inst.offset + i)
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
instructions.each_with_index do |inst, i|
next if !inst.is_a?(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
end end
end end

View File

@ -1,173 +1,175 @@
require "./parser_builder.cr" require "./parser_builder.cr"
module Chalk module Chalk
class Parser module ParserCombinators
include ParserBuilder class Parser
include ParserBuilder
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
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
private def create_call(expr) private def create_op_expr(atom, op)
call = type(TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr| pl = PlaceholderParser(Trees::Tree).new
arr = arr.flatten recurse = atom.then(op).then(pl).transform do |arr|
name = arr[0].as(Token).string
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
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) private def create_op_exprs(atom, ops)
returnn = type(TokenType::KwReturn).then(expr).then(char ';').transform do |arr| ops.reduce(atom) do |previous, current|
arr = arr.flatten create_op_expr(previous, current)
value = arr[1].as(Tree) end
TreeReturn.new(value).as(Tree)
end end
return returnn
end
private def create_block(statement) private def create_call(expr)
block = char('{').then(many(statement)).then(char '}').transform do |arr| call = type(Compiler::TokenType::Id).then(char '(').then(delimited(expr, char ',')).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[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 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)
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
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
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
private def create_basic(expr)
basic = expr.then(char ';').transform do |arr|
arr.flatten[0].as(Trees::Tree)
end
return basic
end
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
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
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
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
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
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
def parse?(tokens)
return @parser.parse?(tokens, 0).try &.[0]
end
end end
end end
end end

View File

@ -2,37 +2,39 @@ 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)) def type(type) : BasicParser(Compiler::Token)
end return TypeParser.new(type).as(BasicParser(Compiler::Token))
end
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
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
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
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
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
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
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,146 +1,148 @@
module Chalk module Chalk
abstract class BasicParser(T) module ParserCombinators
abstract def parse?(tokens : Array(Token), abstract class BasicParser(T)
index : Int64) : Tuple(T, Int64)? abstract def parse?(tokens : Array(Compiler::Token),
index : Int64) : Tuple(T, Int64)?
def parse(tokens, index) def parse(tokens, index)
return parse?(tokens, index).not_nil! return parse?(tokens, index).not_nil!
end
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?) def transform(&transform : T -> R) forall R
def initialize(@parser : BasicParser(T)) return TransformParser.new(self, &transform).as(BasicParser(R))
end
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) def then(other : BasicParser(R)) : BasicParser(Array(T | R)) forall R
def initialize(@parsers : Array(BasicParser(T))) return NextParser.new(self, other).as(BasicParser(Array(T | R)))
end
end end
def parse?(tokens, index) class TypeParser < BasicParser(Compiler::Token)
@parsers.each do |parser| def initialize(@type : Compiler::TokenType)
if parsed = parser.parse?(tokens, index) end
return parsed
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(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
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) class OptionalParser(T) < BasicParser(T?)
many = [] of T def initialize(@parser : BasicParser(T))
while parsed = @parser.parse?(tokens, index)
item, index = parsed
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) class EitherParser(T) < BasicParser(T)
first = @first.parse?(tokens, index) def initialize(@parsers : Array(BasicParser(T)))
return nil unless first end
first_value, index = first
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) class ManyParser(T) < BasicParser(Array(T))
@parser.try &.parse?(tokens, index) 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
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
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
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,53 +1,54 @@
require "./tree.cr" require "./tree.cr"
module Chalk module Chalk
class PrintVisitor < Visitor module Trees
def initialize(@stream : IO) class PrintVisitor < Visitor
@indent = 0 def initialize(@stream : IO)
end @indent = 0
def print_indent
@indent.times do
@stream << " "
end end
end
def visit(id : TreeId) 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 +60,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

View File

@ -1,51 +1,53 @@
module Chalk module Chalk
class Entry module Compiler
end class Entry
class FunctionEntry < Entry
property function : TreeFunction | BuiltinFunction | InlineFunction
property addr : Int32
def initialize(@function, @addr = -1)
end end
def to_s(io) class FunctionEntry < Entry
io << "[function]" property function : Trees::TreeFunction | Builtin::BuiltinFunction | Builtin::InlineFunction
end property addr : Int32
end
class VarEntry < Entry def initialize(@function, @addr = -1)
property register : Int32 end
def initialize(@register) def to_s(io)
end 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) class VarEntry < Entry
@data[key] = entry property register : Int32
def initialize(@register)
end
def to_s(io)
io << "[variable] " << "(R" << @register.to_s(16) << ")"
end
end end
def to_s(io) class Table
@parent.try &.to_s(io) property parent : Table?
io << @data.map { |k, v| k + ": " + v.to_s }.join("\n")
def initialize(@parent = nil)
@data = {} of String => Entry
end
def []?(key)
if entry = @data[key]?
return entry
end
return @parent.try &.[key]?
end
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

View File

@ -1,228 +1,230 @@
module Chalk module Chalk
class Visitor module Trees
def visit(tree) class Visitor
end 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 class Transformer
return @params.size def transform(tree)
return tree
end
end end
def accept(v) class Tree
v.visit(self) def accept(v)
@block.accept(v) v.visit(self)
v.finish(self) v.finish(self)
end
def apply(t)
return t.transform(self)
end
end end
def apply(t) class TreeId < Tree
@block = @block.apply(t) property id : String
return t.transform(self)
end
end
class TreeVar < Tree def initialize(@id)
property name : String end
property expr : Tree
def initialize(@name, @expr)
end end
def accept(v) class TreeLit < Tree
v.visit(self) property lit : Int64
@expr.accept(v)
v.finish(self) def initialize(@lit)
end
end end
def apply(t) class TreeCall < Tree
@expr = @expr.apply(t) property name : String
return t.transform(self) property params : Array(Tree)
end
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) class TreeOp < Tree
v.visit(self) property op : Compiler::TokenType
@expr.accept(v) property left : Tree
v.finish(self) 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) class TreeBlock < Tree
@expr = @expr.apply(t) property children : Array(Tree)
return t.transform(self)
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) class TreeFunction < Tree
v.visit(self) property name : String
@condition.accept(v) property params : Array(String)
@block.accept(v) property block : Tree
@otherwise.try &.accept(v)
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) class TreeVar < Tree
@condition = @condition.apply(t) property name : String
@block = @block.apply(t) property expr : Tree
@otherwise = @otherwise.try &.apply(t)
return t.transform(self)
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) class TreeAssign < Tree
v.visit(self) property name : String
@condition.accept(v) property expr : Tree
@block.accept(v)
v.finish(self) 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) class TreeIf < Tree
@condition = @condition.apply(t) property condition : Tree
@block = @block.apply(t) property block : Tree
return t.transform(self) property otherwise : Tree?
end
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) class TreeWhile < Tree
v.visit(self) property condition : Tree
@rvalue.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
def apply(t) class TreeReturn < Tree
@rvalue = @rvalue.apply(t) property rvalue : Tree
return t.transform(self)
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