Add basic type checking.

This commit is contained in:
Danila Fedorin 2018-08-03 01:13:23 -07:00
parent 8d015d47f3
commit f8320bcb82
8 changed files with 182 additions and 3 deletions

View File

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

View File

@ -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}")

View File

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

View File

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

View File

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

View File

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