diff --git a/src/chalk.cr b/src/chalk.cr index b42f081..0443cb5 100644 --- a/src/chalk.cr +++ b/src/chalk.cr @@ -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 diff --git a/src/chalk/codegen.cr b/src/chalk/codegen.cr new file mode 100644 index 0000000..402d21f --- /dev/null +++ b/src/chalk/codegen.cr @@ -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 diff --git a/src/chalk/compiler.cr b/src/chalk/compiler.cr new file mode 100644 index 0000000..840b1c0 --- /dev/null +++ b/src/chalk/compiler.cr @@ -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 diff --git a/src/chalk/constant_folder.cr b/src/chalk/constant_folder.cr new file mode 100644 index 0000000..f5dc229 --- /dev/null +++ b/src/chalk/constant_folder.cr @@ -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 diff --git a/src/chalk/ir.cr b/src/chalk/ir.cr new file mode 100644 index 0000000..a1959b5 --- /dev/null +++ b/src/chalk/ir.cr @@ -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 diff --git a/src/chalk/print_visitor.cr b/src/chalk/print_visitor.cr index 6c10b8b..d21c13f 100644 --- a/src/chalk/print_visitor.cr +++ b/src/chalk/print_visitor.cr @@ -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 diff --git a/src/chalk/table.cr b/src/chalk/table.cr new file mode 100644 index 0000000..387b80a --- /dev/null +++ b/src/chalk/table.cr @@ -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 diff --git a/src/chalk/tree.cr b/src/chalk/tree.cr index d41f3aa..624c48e 100644 --- a/src/chalk/tree.cr +++ b/src/chalk/tree.cr @@ -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 diff --git a/src/chalk/ui.cr b/src/chalk/ui.cr new file mode 100644 index 0000000..2bce834 --- /dev/null +++ b/src/chalk/ui.cr @@ -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