Add a basic CLI, and begin work on codegen.

This commit is contained in:
Danila Fedorin 2018-07-26 19:47:56 -07:00
parent fbb0da9e44
commit 3853d212b9
9 changed files with 478 additions and 20 deletions

View File

@ -1,12 +1,11 @@
require "./chalk/*"
require "option_parser"
module Chalk
lexer = Lexer.new
parser = Parser.new
config = Config.parse!
exit unless config.validate!
tokens = lexer.lex(File.read("test.txt"))
trees = parser.parse?(tokens)
trees.try do |trees|
trees.each { |tree| puts tree }
end
generator = CodeGenerator.new (Table.new)
compiler = Compiler.new config
compiler.run
end

79
src/chalk/codegen.cr Normal file
View File

@ -0,0 +1,79 @@
require "./ir.cr"
module Chalk
class CodeGenerator
def initialize(@table : Table)
@register = 0
@instructions = [] of Instruction
@block_edges = [] of Int32
end
private def load(into, value)
@instructions << LoadInstruction.new into, value.to_i32
end
private def loadr(into, from)
@instructions << LoadRegInstruction.new into, from
end
private def op(op, into, from)
@instructions << OpRegInstruction.new op, into, from
end
private def store(up_to)
@instructions << StoreInstruction.new up_to
end
private def restore(up_to)
@instructions << RestoreInstruction.new up_to
end
private def ret(reg)
@instructions << ReturnInstruction.new reg
end
def generate(tree : Tree, target : Int32)
case tree
when TreeId
entry = @table[tree.id]?
raise "Unknown variable" unless entry &&
entry.is_a?(VarEntry)
loadr target, entry.as(VarEntry).register
when TreeLit
load target, tree.lit
when TreeOp
generate tree.left, target
generate tree.right, @register
op tree.op, target, @register
when TreeBlock
tree.children.each do |child|
generate child, @register
end
when TreeVar
entry = @table[tree.name]?
if entry == nil
entry = VarEntry.new @register
@register += 1
@table[tree.name] = entry
end
raise "Unknown variable" unless entry.is_a?(VarEntry)
generate tree.expr, entry.register
when TreeAssign
entry = @table[tree.name]?
raise "Unknown variable" unless entry &&
entry.is_a?(VarEntry)
generate tree.expr, entry.as(VarEntry).register
when TreeReturn
generate tree.rvalue, target
ret target
end
end
def generate(tree : Tree)
generate(tree, 0)
@block_edges << @instructions.size
@block_edges.sort!
return @instructions
end
end
end

77
src/chalk/compiler.cr Normal file
View File

@ -0,0 +1,77 @@
require "logger"
require "./constant_folder.cr"
require "./table.cr"
module Chalk
class Compiler
def initialize(@config : Config)
@logger = Logger.new STDOUT
@lexer = Lexer.new
@parser = Parser.new
@logger.debug("Initialized compiler")
@logger.level = Logger::DEBUG
end
private def create_trees(file)
string = File.read(file)
tokens = @lexer.lex string
if tokens.size == 0 && string != ""
raise "Unable to tokenize file."
end
@logger.debug("Finished tokenizing")
if trees = @parser.parse?(tokens)
@logger.debug("Finished parsing")
return trees
end
raise "Unable to parse file."
end
private def process_initial(trees)
table = Table.new
folder = ConstantFolder.new
trees.each do |tree|
tree = tree.as(TreeFunction)
@logger.debug("Constant folding #{tree.name}")
tree = tree.apply(folder).as(TreeFunction)
@logger.debug("Storing #{tree.name} in symbol table")
table[tree.name] = FunctionEntry.new tree
end
return table
end
private def generate_code(trees, table)
trees.each do |tree|
generator = CodeGenerator.new table
instructions = generator.generate tree.as(TreeFunction).block
instructions.each { |it| puts it }
end
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 = process_initial(trees)
generate_code(trees, table)
end
private def run_binary
end
def run
case @config.mode
when OutputMode::Tree
run_tree
when OutputMode::Intermediate
run_intermediate
when OutputMode::Binary
run_binary
end
end
end
end

View File

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

103
src/chalk/ir.cr Normal file
View File

@ -0,0 +1,103 @@
require "./lexer.cr"
module Chalk
class Instruction
end
class LoadInstruction < Instruction
property register : Int32
property value : Int32
def initialize(@register : Int32, @value : Int32)
end
def to_s(io)
io << "load R"
@register.to_s(16, io)
io << " " << @value
end
end
class LoadRegInstruction < Instruction
property into : Int32
property from : Int32
def initialize(@into : Int32, @from : Int32)
end
def to_s(io)
io << "loadr R"
@into.to_s(16, io)
io << " R"
@from.to_s(16, io)
end
end
class OpInstruction < Instruction
property op : TokenType
property into : Int32
property value : Int32
def initialize(@op : TokenType, @into : Int32,
@value : Int32)
end
def to_s(io)
io << "op " << op << " R"
@into.to_s(16, io)
io << " " << @value
end
end
class OpRegInstruction < Instruction
property op : TokenType
property into : Int32
property from : Int32
def initialize(@op : TokenType, @into : Int32, @from : Int32)
end
def to_s(io)
io << "opr " << op << " R"
@into.to_s(16, io)
io << " R"
@from.to_s(16, io)
end
end
class StoreInstruction < Instruction
property up_to : Int32
def initialize(@up_to : Int32)
end
def to_s(io)
io << "store R"
@up_to.to_s(16, io)
end
end
class RestoreInstruction < Instruction
property up_to : Int32
def initialize(@up_to : Int32)
end
def to_s(io)
io << "restore R"
@up_to.to_s(16, io)
end
end
class ReturnInstruction < Instruction
property to_return : Int32
def initialize(@to_return : Int32)
end
def to_s(io)
io << "return R"
@to_return.to_s(16, io)
end
end
end

View File

@ -48,16 +48,16 @@ module Chalk
end
macro forward(text, type)
def visit(tree : {{type}})
print_indent
@stream << {{text}} << "\n"
@indent += 1
end
def visit(tree : {{type}})
print_indent
@stream << {{text}} << "\n"
@indent += 1
end
def finish(tree : {{type}})
@indent -= 1
end
end
def finish(tree : {{type}})
@indent -= 1
end
end
forward("[call]", TreeCall)
forward("[block]", TreeBlock)
@ -67,4 +67,10 @@ module Chalk
forward("[while]", TreeWhile)
forward("[return]", TreeReturn)
end
class Tree
def to_s(io)
accept(PrintVisitor.new io)
end
end
end

51
src/chalk/table.cr Normal file
View File

@ -0,0 +1,51 @@
module Chalk
class Entry
end
class FunctionEntry < Entry
property function : TreeFunction
property addr : Int32
def initialize(@function : TreeFunction, @addr : Int32 = -1)
end
def to_s(io)
io << "[function]"
end
end
class VarEntry < Entry
property register : Int32
def initialize(@register : Int32)
end
def to_s(io)
io << "[variable] " << "(R" << @register.to_s(16) << ")"
end
end
class Table
property parent : Table?
def initialize(@parent : Table? = nil)
@data = {} of String => Entry
end
def []?(key) : Entry?
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

View File

@ -1,5 +1,3 @@
require "./print_visitor.cr"
module Chalk
class Visitor
def visit(tree : Tree)
@ -9,14 +7,20 @@ module Chalk
end
end
class Transformer
def transform(tree : Tree) : Tree
return tree
end
end
class Tree
def accept(v : Visitor)
v.visit(self)
v.finish(self)
end
def to_s(io)
accept(PrintVisitor.new io)
def apply(t : Transformer)
return t.transform(self)
end
end
@ -46,6 +50,13 @@ module Chalk
@params.each &.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@params.map! do |param|
param.apply(t)
end
return t.transform(self)
end
end
class TreeOp < Tree
@ -62,9 +73,17 @@ module Chalk
@right.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@left = @left.apply(t)
@right = @right.apply(t)
return t.transform(self)
end
end
class TreeBlock < Tree
property children : Array(Tree)
def initialize(@children : Array(Tree))
end
@ -73,6 +92,13 @@ module Chalk
@children.each &.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@children.map! do |child|
child.apply(t)
end
return t.transform(self)
end
end
class TreeFunction < Tree
@ -88,6 +114,11 @@ module Chalk
@block.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@block = @block.apply(t)
return t.transform(self)
end
end
class TreeVar < Tree
@ -102,6 +133,11 @@ module Chalk
@expr.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@expr = @expr.apply(t)
return t.transform(self)
end
end
class TreeAssign < Tree
@ -116,6 +152,11 @@ module Chalk
@expr.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@expr = @expr.apply(t)
return t.transform(self)
end
end
class TreeIf < Tree
@ -133,6 +174,13 @@ module Chalk
@otherwise.try &.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@condition = @condition.apply(t)
@block = @block.apply(t)
@otherwise = @otherwise.try &.apply(t)
return t.transform(self)
end
end
class TreeWhile < Tree
@ -148,6 +196,12 @@ module Chalk
@block.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@condition = @condition.apply(t)
@block = @block.apply(t)
return t.transform(self)
end
end
class TreeReturn < Tree
@ -161,5 +215,10 @@ module Chalk
@rvalue.accept(v)
v.finish(self)
end
def apply(t : Transformer)
@rvalue = @rvalue.apply(t)
return t.transform(self)
end
end
end

51
src/chalk/ui.cr Normal file
View File

@ -0,0 +1,51 @@
module Chalk
enum OutputMode
Tree,
Intermediate,
Binary
end
class Config
property file : String
property mode : OutputMode
def initialize(@file : String = "",
@mode = OutputMode::Tree)
end
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
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
return config
end
def validate!
if file == ""
puts "No source file specified."
return false
elsif !File.exists? file
puts "Unable to open source file."
return false
end
return true
end
end
end