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.
|
# Uses the given `Compiler::Emitter` to output code.
|
||||||
abstract def generate!(codegen)
|
abstract def generate!(codegen)
|
||||||
|
# Gets the `Compiler::FunctionType` of this function.
|
||||||
|
abstract def type
|
||||||
end
|
end
|
||||||
|
|
||||||
# A function to which a call is not generated. This function
|
# 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.
|
# the *params* are trees that are being passed as arguments.
|
||||||
# See `Compiler::CodeGenerator#generate!` for what the other parameters mean.
|
# See `Compiler::CodeGenerator#generate!` for what the other parameters mean.
|
||||||
abstract def generate!(codegen, params, table, target, free)
|
abstract def generate!(codegen, params, table, target, free)
|
||||||
|
# Gets the `Compiler::FunctionType` of this function.
|
||||||
|
abstract def type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -65,6 +65,7 @@ module Chalk
|
|||||||
# looking up identifiers in the symbol *table*, and appending the given *instruction*
|
# looking up identifiers in the symbol *table*, and appending the given *instruction*
|
||||||
# at the end of the function to ensure correct program flow.
|
# at the end of the function to ensure correct program flow.
|
||||||
private def create_code(tree : Trees::TreeFunction, table, instruction = Ir::ReturnInstruction.new)
|
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
|
optimizer = Optimizer.new
|
||||||
generator = CodeGenerator.new table, tree
|
generator = CodeGenerator.new table, tree
|
||||||
@logger.debug("Generating code for #{tree.name}")
|
@logger.debug("Generating code for #{tree.name}")
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
require "./builtin"
|
||||||
|
require "./type"
|
||||||
|
|
||||||
module Chalk
|
module Chalk
|
||||||
module Builtin
|
module Builtin
|
||||||
# Inline function to draw sprite at address I.
|
# Inline function to draw sprite at address I.
|
||||||
@ -14,6 +17,9 @@ module Chalk
|
|||||||
emitter.generate! params[1], table, free + 1, free + 2
|
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
|
emitter.instructions << Ir::DrawInstruction.new free, free + 1, params[2].as(Trees::TreeLit).lit.to_i32
|
||||||
end
|
end
|
||||||
|
def type
|
||||||
|
return Compiler::FunctionType.new([Compiler::Type::U8] * 3, Compiler::Type::U0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inline function to await for a key and return it.
|
# Inline function to await for a key and return it.
|
||||||
@ -25,6 +31,9 @@ module Chalk
|
|||||||
def generate!(emitter, params, table, target, free)
|
def generate!(emitter, params, table, target, free)
|
||||||
emitter.instructions << Ir::AwaitKeyInstruction.new target
|
emitter.instructions << Ir::AwaitKeyInstruction.new target
|
||||||
end
|
end
|
||||||
|
def type
|
||||||
|
return Compiler::FunctionType.new(([] of Compiler::Type), Compiler::Type::U8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inline function to get font for a given value.
|
# Inline function to get font for a given value.
|
||||||
@ -37,6 +46,9 @@ module Chalk
|
|||||||
emitter.generate! params[0], table, free, free + 1
|
emitter.generate! params[0], table, free, free + 1
|
||||||
emitter.instructions << Ir::GetFontInstruction.new free
|
emitter.instructions << Ir::GetFontInstruction.new free
|
||||||
end
|
end
|
||||||
|
def type
|
||||||
|
return Compiler::FunctionType.new([Compiler::Type::U8], Compiler::Type::U0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inline function to set the delay timer.
|
# Inline function to set the delay timer.
|
||||||
@ -49,6 +61,9 @@ module Chalk
|
|||||||
emitter.generate! params[0], table, free, free + 1
|
emitter.generate! params[0], table, free, free + 1
|
||||||
emitter.instructions << Ir::SetDelayTimerInstruction.new free
|
emitter.instructions << Ir::SetDelayTimerInstruction.new free
|
||||||
end
|
end
|
||||||
|
def type
|
||||||
|
return Compiler::FunctionType.new([Compiler::Type::U8], Compiler::Type::U0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inline function to get the delay timer.
|
# Inline function to get the delay timer.
|
||||||
@ -60,6 +75,9 @@ module Chalk
|
|||||||
def generate!(emitter, params, table, target, free)
|
def generate!(emitter, params, table, target, free)
|
||||||
emitter.instructions << Ir::GetDelayTimerInstruction.new target
|
emitter.instructions << Ir::GetDelayTimerInstruction.new target
|
||||||
end
|
end
|
||||||
|
def type
|
||||||
|
return Compiler::FunctionType.new(([] of Compiler::Type), Compiler::Type::U8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,6 +21,7 @@ module Chalk
|
|||||||
KwInline
|
KwInline
|
||||||
KwFun
|
KwFun
|
||||||
KwU0
|
KwU0
|
||||||
|
KwU4
|
||||||
KwU8
|
KwU8
|
||||||
KwU12
|
KwU12
|
||||||
KwVar
|
KwVar
|
||||||
@ -70,6 +71,7 @@ module Chalk
|
|||||||
@lexer.add_pattern("inline", TokenType::KwInline.value)
|
@lexer.add_pattern("inline", TokenType::KwInline.value)
|
||||||
@lexer.add_pattern("fun", TokenType::KwFun.value)
|
@lexer.add_pattern("fun", TokenType::KwFun.value)
|
||||||
@lexer.add_pattern("u0", TokenType::KwU0.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("u8", TokenType::KwU8.value)
|
||||||
@lexer.add_pattern("u12", TokenType::KwU12.value)
|
@lexer.add_pattern("u12", TokenType::KwU12.value)
|
||||||
@lexer.add_pattern("var", TokenType::KwVar.value)
|
@lexer.add_pattern("var", TokenType::KwVar.value)
|
||||||
|
@ -8,7 +8,10 @@ module Chalk
|
|||||||
|
|
||||||
# Creates a parser for a type.
|
# Creates a parser for a type.
|
||||||
private def create_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
|
end
|
||||||
|
|
||||||
# Creates a parser for an integer literal.
|
# Creates a parser for an integer literal.
|
||||||
@ -176,7 +179,13 @@ module Chalk
|
|||||||
params = arr[3..arr.size - 5].map &.as(Compiler::Token).string
|
params = arr[3..arr.size - 5].map &.as(Compiler::Token).string
|
||||||
code = arr[arr.size - 1].as(Trees::Tree)
|
code = arr[arr.size - 1].as(Trees::Tree)
|
||||||
type = arr[arr.size - 2].as(Compiler::Token).type
|
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
|
end
|
||||||
return func
|
return func
|
||||||
end
|
end
|
||||||
|
@ -18,6 +18,11 @@ module Chalk
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Reducer(T)
|
||||||
|
def reduce(tree, children)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# The base class of a tree.
|
# The base class of a tree.
|
||||||
class Tree
|
class Tree
|
||||||
def accept(v)
|
def accept(v)
|
||||||
@ -28,6 +33,10 @@ module Chalk
|
|||||||
def apply(t)
|
def apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
return r.reduce(self, [] of T)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents an ID.
|
# A tree that represents an ID.
|
||||||
@ -66,6 +75,10 @@ module Chalk
|
|||||||
end
|
end
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
return r.reduce(self, @params.map &.reduce(r))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents an operation on two values.
|
# A tree that represents an operation on two values.
|
||||||
@ -89,6 +102,10 @@ module Chalk
|
|||||||
@right = @right.apply(t)
|
@right = @right.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
return r.reduce(self, [@left.reduce(r), @right.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents a block of statements.
|
# A tree that represents a block of statements.
|
||||||
@ -110,6 +127,9 @@ module Chalk
|
|||||||
end
|
end
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
return r.reduce(self, @children.map &.reduce(r))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents a function declaration.
|
# A tree that represents a function declaration.
|
||||||
@ -117,8 +137,10 @@ module Chalk
|
|||||||
property name : String
|
property name : String
|
||||||
property params : Array(String)
|
property params : Array(String)
|
||||||
property block : Tree
|
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
|
end
|
||||||
|
|
||||||
def param_count
|
def param_count
|
||||||
@ -135,6 +157,10 @@ module Chalk
|
|||||||
@block = @block.apply(t)
|
@block = @block.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
return r.reduce(self, [@block.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents the declaration of
|
# A tree that represents the declaration of
|
||||||
@ -156,6 +182,10 @@ module Chalk
|
|||||||
@expr = @expr.apply(t)
|
@expr = @expr.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
r.reduce(self, [@expr.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents the assignment
|
# A tree that represents the assignment
|
||||||
@ -177,6 +207,10 @@ module Chalk
|
|||||||
@expr = @expr.apply(t)
|
@expr = @expr.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
r.reduce(self, [@expr.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents an if statement.
|
# A tree that represents an if statement.
|
||||||
@ -202,6 +236,16 @@ module Chalk
|
|||||||
@otherwise = @otherwise.try &.apply(t)
|
@otherwise = @otherwise.try &.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
# A tree that represents a while loop.
|
# A tree that represents a while loop.
|
||||||
@ -224,6 +268,10 @@ module Chalk
|
|||||||
@block = @block.apply(t)
|
@block = @block.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
r.reduce(self, [@condition.reduce(r), @block.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A tree that represents a return statement.
|
# A tree that represents a return statement.
|
||||||
@ -243,6 +291,10 @@ module Chalk
|
|||||||
@rvalue = @rvalue.apply(t)
|
@rvalue = @rvalue.apply(t)
|
||||||
return t.transform(self)
|
return t.transform(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reduce(r : Reducer(T)) forall T
|
||||||
|
r.reduce(self, [@rvalue.reduce(r)])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
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