diff --git a/src/chalk/builtin.cr b/src/chalk/builtin.cr index f9dea66..987cd94 100644 --- a/src/chalk/builtin.cr +++ b/src/chalk/builtin.cr @@ -13,6 +13,8 @@ module Chalk # Uses the given `Compiler::Emitter` to output code. abstract def generate!(codegen) + # Gets the `Compiler::FunctionType` of this function. + abstract def type end # A function to which a call is not generated. This function @@ -32,6 +34,8 @@ module Chalk # the *params* are trees that are being passed as arguments. # See `Compiler::CodeGenerator#generate!` for what the other parameters mean. abstract def generate!(codegen, params, table, target, free) + # Gets the `Compiler::FunctionType` of this function. + abstract def type end end end diff --git a/src/chalk/compiler.cr b/src/chalk/compiler.cr index 60cad79..602cd8b 100644 --- a/src/chalk/compiler.cr +++ b/src/chalk/compiler.cr @@ -65,6 +65,7 @@ module Chalk # 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) + tree.reduce(Trees::TypeChecker.new table, tree.type.return_type) optimizer = Optimizer.new generator = CodeGenerator.new table, tree @logger.debug("Generating code for #{tree.name}") diff --git a/src/chalk/inline.cr b/src/chalk/inline.cr index 154a6a6..ac3a36d 100644 --- a/src/chalk/inline.cr +++ b/src/chalk/inline.cr @@ -1,3 +1,6 @@ +require "./builtin" +require "./type" + module Chalk module Builtin # Inline function to draw sprite at address I. @@ -14,6 +17,9 @@ module Chalk 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 + def type + return Compiler::FunctionType.new([Compiler::Type::U8] * 3, Compiler::Type::U0) + end end # Inline function to await for a key and return it. @@ -25,6 +31,9 @@ module Chalk def generate!(emitter, params, table, target, free) emitter.instructions << Ir::AwaitKeyInstruction.new target end + def type + return Compiler::FunctionType.new(([] of Compiler::Type), Compiler::Type::U8) + end end # Inline function to get font for a given value. @@ -37,6 +46,9 @@ module Chalk emitter.generate! params[0], table, free, free + 1 emitter.instructions << Ir::GetFontInstruction.new free end + def type + return Compiler::FunctionType.new([Compiler::Type::U8], Compiler::Type::U0) + end end # Inline function to set the delay timer. @@ -49,6 +61,9 @@ module Chalk emitter.generate! params[0], table, free, free + 1 emitter.instructions << Ir::SetDelayTimerInstruction.new free end + def type + return Compiler::FunctionType.new([Compiler::Type::U8], Compiler::Type::U0) + end end # Inline function to get the delay timer. @@ -60,6 +75,9 @@ module Chalk def generate!(emitter, params, table, target, free) emitter.instructions << Ir::GetDelayTimerInstruction.new target end + def type + return Compiler::FunctionType.new(([] of Compiler::Type), Compiler::Type::U8) + end end end end diff --git a/src/chalk/lexer.cr b/src/chalk/lexer.cr index 0507da3..426b6c3 100644 --- a/src/chalk/lexer.cr +++ b/src/chalk/lexer.cr @@ -21,6 +21,7 @@ module Chalk KwInline KwFun KwU0 + KwU4 KwU8 KwU12 KwVar @@ -70,6 +71,7 @@ module Chalk @lexer.add_pattern("inline", TokenType::KwInline.value) @lexer.add_pattern("fun", TokenType::KwFun.value) @lexer.add_pattern("u0", TokenType::KwU0.value) + @lexer.add_pattern("u4", TokenType::KwU4.value) @lexer.add_pattern("u8", TokenType::KwU8.value) @lexer.add_pattern("u12", TokenType::KwU12.value) @lexer.add_pattern("var", TokenType::KwVar.value) diff --git a/src/chalk/parser.cr b/src/chalk/parser.cr index 066829a..3544f08 100644 --- a/src/chalk/parser.cr +++ b/src/chalk/parser.cr @@ -8,7 +8,10 @@ module Chalk # Creates a parser for a type. private def create_type - either(type(Compiler::TokenType::KwU0), type(Compiler::TokenType::KwU8), type(Compiler::TokenType::KwU12)) + either(type(Compiler::TokenType::KwU0), + type(Compiler::TokenType::KwU4), + type(Compiler::TokenType::KwU8), + type(Compiler::TokenType::KwU12)) end # Creates a parser for an integer literal. @@ -176,7 +179,13 @@ module Chalk 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) + table = { + Compiler::TokenType::KwU0 => Compiler::Type::U0, + Compiler::TokenType::KwU4 => Compiler::Type::U4, + Compiler::TokenType::KwU8 => Compiler::Type::U8, + Compiler::TokenType::KwU12 => Compiler::Type::U12 + } + Trees::TreeFunction.new(name, params, table[type], code) end return func end diff --git a/src/chalk/tree.cr b/src/chalk/tree.cr index a14f95c..db1b34f 100644 --- a/src/chalk/tree.cr +++ b/src/chalk/tree.cr @@ -18,6 +18,11 @@ module Chalk end end + class Reducer(T) + def reduce(tree, children) + end + end + # The base class of a tree. class Tree def accept(v) @@ -28,6 +33,10 @@ module Chalk def apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + return r.reduce(self, [] of T) + end end # A tree that represents an ID. @@ -66,6 +75,10 @@ module Chalk end return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + return r.reduce(self, @params.map &.reduce(r)) + end end # A tree that represents an operation on two values. @@ -89,6 +102,10 @@ module Chalk @right = @right.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + return r.reduce(self, [@left.reduce(r), @right.reduce(r)]) + end end # A tree that represents a block of statements. @@ -110,6 +127,9 @@ module Chalk end return t.transform(self) end + def reduce(r : Reducer(T)) forall T + return r.reduce(self, @children.map &.reduce(r)) + end end # A tree that represents a function declaration. @@ -117,8 +137,10 @@ module Chalk property name : String property params : Array(String) property block : Tree + getter type - def initialize(@name, @params, @block) + def initialize(@name, @params, return_type : Compiler::Type, @block) + @type = Compiler::FunctionType.new([Compiler::Type::U8] * @params.size, return_type) end def param_count @@ -135,6 +157,10 @@ module Chalk @block = @block.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + return r.reduce(self, [@block.reduce(r)]) + end end # A tree that represents the declaration of @@ -156,6 +182,10 @@ module Chalk @expr = @expr.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + r.reduce(self, [@expr.reduce(r)]) + end end # A tree that represents the assignment @@ -177,6 +207,10 @@ module Chalk @expr = @expr.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + r.reduce(self, [@expr.reduce(r)]) + end end # A tree that represents an if statement. @@ -202,6 +236,16 @@ module Chalk @otherwise = @otherwise.try &.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + cond = @condition.reduce(r) + blk = @block.reduce(r) + if other = @otherwise + r.reduce(self, [cond, blk,other.reduce(r)]) + else + r.reduce(self, [cond, blk]) + end + end end # A tree that represents a while loop. @@ -224,6 +268,10 @@ module Chalk @block = @block.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + r.reduce(self, [@condition.reduce(r), @block.reduce(r)]) + end end # A tree that represents a return statement. @@ -243,6 +291,10 @@ module Chalk @rvalue = @rvalue.apply(t) return t.transform(self) end + + def reduce(r : Reducer(T)) forall T + r.reduce(self, [@rvalue.reduce(r)]) + end end end end diff --git a/src/chalk/type.cr b/src/chalk/type.cr new file mode 100644 index 0000000..0f8b44c --- /dev/null +++ b/src/chalk/type.cr @@ -0,0 +1,35 @@ +module Chalk + module Compiler + # Possible types of values in chalk + enum Type + U0 + U4 + U8 + U12 + + # Checks if one value can be cast to another. + def casts_to?(other) + return false if other == Type::U0 || self == Type::U0 + return other.value >= self.value + end + end + + # A type of a function. + class FunctionType + # Gets the types of the function's parameters. + getter param_types + # Gets the return type of the function. + getter return_type + + def initialize(@param_types : Array(Type), @return_type : Type) + end + + def to_s(io) + io << "(" + io << param_types.map(&.to_s).join(", ") + io << ") -> " + return_type.to_s(io) + end + end + end +end diff --git a/src/chalk/typecheck.cr b/src/chalk/typecheck.cr new file mode 100644 index 0000000..eb384ac --- /dev/null +++ b/src/chalk/typecheck.cr @@ -0,0 +1,58 @@ +require "./type" + +module Chalk + module Trees + # Reducer to check types. + class TypeChecker < Reducer(Compiler::Type) + def initialize(@table : Compiler::Table, @return_type : Compiler::Type) + end + + def reduce(t, children) + return Compiler::Type::U0 + end + + def reduce(t : TreeCall, children) + entry = @table[t.name]? + raise "Unknwon function" unless entry && entry.is_a?(Compiler::FunctionEntry) + type = entry.function.type + raise "Invalid parameters" if type.param_types.size != children.size + children.each_with_index do |child, i| + raise "Incompatible parameter" if !child.casts_to?(type.param_types[i]) + end + return entry.function.type.return_type + end + + def reduce(t : TreeId, children) + return Compiler::Type::U8 + end + + def reduce(t : TreeLit, children) + max_12 = (2 ** 12) - 1 + max_8 = (2 ** 8) - 1 + max_4 = (2 ** 4) - 1 + raise "Number too big" if t.lit > max_12 + return Compiler::Type::U12 if t.lit > max_8 + return Compiler::Type::U8 if t.lit > max_4 + return Compiler::Type::U4 + end + + def reduce(t : TreeOp, children) + left = children[0] + right = children[1] + return left if right.casts_to?(left) + return right if left.casts_to?(right) + raise "Invalid operation" + end + + def reduce(t : TreeAssign | TreeVar, children) + raise "Invalid assignment" if !children[0].casts_to?(Compiler::Type::U8) + return Compiler::Type::U0 + end + + def reduce(t : TreeReturn, children) + raise "Incompatible return type" if !children[0].casts_to?(@return_type) + return Compiler::Type::U0 + end + end + end +end