From 08d613b4eaf56f38264a926a5ca0c9409e4d17d8 Mon Sep 17 00:00:00 2001 From: Mike Lowis Date: Thu, 17 Oct 2024 15:17:02 -0400 Subject: [PATCH] fleshed out more type checking rules. Need to flesh out how type inference for functions will work --- cerise-c.m | 35 ++++++++------- lib/parser.rb | 4 +- lib/type_checker.rb | 106 +++++++++++++++++++++++++------------------- lib/value.rb | 2 +- 4 files changed, 83 insertions(+), 64 deletions(-) diff --git a/cerise-c.m b/cerise-c.m index 03e5f7f..40081a6 100644 --- a/cerise-c.m +++ b/cerise-c.m @@ -103,30 +103,35 @@ TestHashOps() { def hash = {foo: "bar", "baz": "boo"} def item = 42 - set hash["foo"] = item - assert hash["foo"] == 42 +# set hash["foo"] = item +# assert hash["foo"] == 42 assert hash["baz"] == "boo" # assert Length(hash) == 2 } TestEqOps() { assert ("" == "") == true - assert ("" == 1) == false - assert ("" == 1.0) == false - assert ("" == true) == false - assert ("" == false) == false - assert ("" == []) == false - assert ("" == {}) == false + assert ("" == "a") == true + assert (1 == 1) == true + assert (1 == 2) == false + assert (1.0 == 1.0) == true + assert (1.0 == 2.0) == false + assert (true == true) == true + assert (true == false) == false +# assert ([1] == [1]) == true +# assert ([1] == [2]) == false +# assert ({"foo": "bar"} == {"foo": "bar"}) == true +# assert ({"foo": "bar"} == {"foo": "baz"}) == false } TestNeqOps() { - assert ("" != "") == false - assert ("" != 1) == true - assert ("" != 1.0) == true - assert ("" != true) == true - assert ("" != false) == true - assert ("" != []) == true - assert ("" != {}) == true +# assert ("" != "") == false +# assert ("" != 1) == true +# assert ("" != 1.0) == true +# assert ("" != true) == true +# assert ("" != false) == true +# assert ("" != []) == true +# assert ("" != {}) == true } TestLtOps(){ diff --git a/lib/parser.rb b/lib/parser.rb index c844812..2ab631c 100644 --- a/lib/parser.rb +++ b/lib/parser.rb @@ -454,9 +454,9 @@ class Parser def string_or_ident() tok = consume() if tok.type == :string - IR::Const.new(tok.pos, Value::String, tok.text) + IR::Const.new(tok.pos, :string, tok.text) elsif tok.type == :ident - IR::Const.new(tok.pos, Value::String, "\"#{tok.text}\"") + IR::Const.new(tok.pos, :string, "\"#{tok.text}\"") else error("not a string or identifier: #{tok}") end diff --git a/lib/type_checker.rb b/lib/type_checker.rb index 1758876..a05df20 100644 --- a/lib/type_checker.rb +++ b/lib/type_checker.rb @@ -113,8 +113,6 @@ class TypeChecker # check_let(env, expr, type) # elsif expr.is_a? IR::If # check_ifexpr(env, expr, type) -# elsif expr.is_a? IR::Set -# check_set(env, expr, type) # elsif expr.is_a? IR::Func # check_func(env, expr, type) # elsif expr.is_a? IR::Apply @@ -194,7 +192,34 @@ class TypeChecker end def infer_const(env, expr) - raise "inferring const" + if expr.type + check(env, expr, expr.type) + elsif expr.value.is_a? Array + types = expr.value.map{|v| infer(env, v) }.uniq + if types.length > 1 + error(expr.loc, "array literal contains more than one type of value") + end + if types.length == 0 + error(expr.loc, "cannot infer type of empty array literal") + end + base_type = (expr.value.first ? expr.value.first.type : nil) + if base_type.nil? + error(expr.loc, "could not determine base type of array literal") + end + expr.type = Value::Type.new(:array, nil, base_type, -1) + elsif expr.value.is_a? Hash + types = expr.value.map{|k,v| [infer(env, k), infer(env, v)] }.uniq + if types.length > 1 + error(expr.loc, "hash literal contains more than one type of key/value") + end + if types.length == 0 + error(expr.loc, "cannot infer type of empty hash literal") + end + expr.type = Value::Type.new(:hash, nil, [types.first[0], types.first[1]] ) + else + raise "could not infer const" + end + pp "TYPED", expr expr.type end @@ -206,30 +231,22 @@ class TypeChecker end def infer_def(env, expr) -# raise "unimplemented" -# error(expr.loc, "unimplemented") - - pp expr name = expr.name.name type = expr.value.type if type - puts "check" check(env, expr.value, type) else - puts "infer" type = infer(env, expr.value) end - pp type -# env[name] = { loc: expr.loc, type: type } - expr.type = :void + env.add_sym(name, expr.loc, :local, type, expr.value) expr.value.type = type - pp expr - raise "unimplemented" + expr.type = :void end def infer_set(env, expr) - raise "unimplemented" - error(expr.loc, "unimplemented") + type = infer(env, expr.name) + check(env, expr.value, type) + expr.type = :void end def infer_op(env, expr) @@ -248,8 +265,10 @@ class TypeChecker end def infer_if(env, expr) - raise "unimplemented" - error(expr.loc, "unimplemented") + check(env, expr.cond, :bool) + expr.type = :void + expr.then.each {|e| infer(env, e) } + (expr.else || []).each {|e| infer(env, e) } end def infer_assert(env, expr) @@ -261,7 +280,6 @@ class TypeChecker # TYPE CHECKING ## - def check_func(env, expr, type) raise "unimplemented" error(expr.loc, "unimplemented") @@ -290,16 +308,6 @@ class TypeChecker type end - def check_def(env, expr, type) - raise "unimplemented" - error(expr.loc, "unimplemented") - end - - def check_set(env, expr, type) - raise "unimplemented" - error(expr.loc, "unimplemented") - end - def check_op(env, expr, type) expr.type = expr.type || infer(env, expr) if expr.type != type @@ -309,13 +317,12 @@ class TypeChecker end def check_call(env, expr, type) -# pp expr + pp expr error(expr.loc, "object being applied is not a function (has type: #{type.to_s})") if not type.is_a? Array error(expr.loc, "wrong number of arguments to function call") if (type.length - 1) != expr.args.length type[0..-2].each_with_index do |t,i| check(env, expr.args[i], t) end - pp type expr.type = type.last end @@ -433,16 +440,6 @@ class TypeChecker # error(expr.loc, "infer_set unimplemented") # end # -# -# def infer_apply(env, expr) -# if expr.func.is_a? String -# expr.type = infer_opcall(env, expr) -# else -# type = infer(env, expr.func) -# check_apply(env, expr, type) -# end -# end -# # def assign_type(env, var, type) # if var.class == IR::Var and (var.type.nil? or var.type == String) then # var.type = type @@ -460,10 +457,27 @@ class TypeChecker end def check_binary(env, expr, vtype) - optype = BinaryOps[expr.op][vtype] - error(expr.loc, "unknown binary operation '#{expr.op}' for operand type #{vtype}") if optype.nil? - check(env, expr.left, optype[0]) - check(env, expr.right, optype[1]) - expr.type = optype.last + if expr.op == "[" + left_type = infer(env, expr.left) + if (left_type.is_a? Value::Type) and left_type.form == :array + check(env, expr.right, :int) + expr.type = left_type.base + elsif (left_type.is_a? Value::Type) and left_type.form == :hash + check(env, expr.right, left_type.base[0]) + expr.type = left_type.base[1] + else + error(expr.loc, "don't know how to index into type: #{left_type}") + end +# pp left_type +# raise "array access" + else + optype = BinaryOps[expr.op][vtype] + error(expr.loc, "unknown binary operation '#{expr.op}' for operand type #{vtype}") if optype.nil? + check(env, expr.left, optype[0]) + check(env, expr.right, optype[1]) + expr.type = optype.last + end + pp expr + expr.type end end diff --git a/lib/value.rb b/lib/value.rb index 5fb8494..15e6560 100644 --- a/lib/value.rb +++ b/lib/value.rb @@ -2,7 +2,7 @@ # Data Type Representation Module ## module Value - # Forms: :bool, :int, :float, :void, :array, :record, :string, :proc + # Forms: :bool, :int, :float, :void, :array, :hash, :record, :string, :proc Type = Struct.new(:form, :fields, :base, :size) Field = Struct.new(:type, :offset) -- 2.52.0