Add basic type checking.
This commit is contained in:
parent
8d015d47f3
commit
f8320bcb82
@ -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
|
||||
|
@ -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}")
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
35
src/chalk/type.cr
Normal file
35
src/chalk/type.cr
Normal file
@ -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
|
58
src/chalk/typecheck.cr
Normal file
58
src/chalk/typecheck.cr
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user