]> git.mdlowis.com Git - proto/sclpl-rb.git/commitdiff
fleshed out an ANF based compiler based on ml
authorMichael D. Lowis <mike@mdlowis.com>
Tue, 3 Jan 2023 05:00:15 +0000 (00:00 -0500)
committerMichael D. Lowis <mike@mdlowis.com>
Tue, 3 Jan 2023 05:00:15 +0000 (00:00 -0500)
inputs/cerise.m
lib/cerise.rb
lib/utils/anf.rb
lib/utils/sym_table.rb

index f7f412a26619b783c2e4b4baa73df7be6412c6c1..1cbe4d458e1e81a5ddfd948d535e419eee396319 100644 (file)
 #if true then 2 else 3.0 # error
 #if true then 2.0 else 3 # error
 
-
 # type variables and unused args
-
 # TODO: implement operator precedence
 # TODO: desugar to ANF *after* typing
 
-foo = func(a)
-  let b = bar(a) in
-  let c = 123.0 + b in c
-
-bar = func(a) # TODO func type is wrong, for a
-  let b = 1.0 + a in b
-
-
-
-#bar : int -> int
-#bar = func(a)
+#foo : float -> float
+#foo = func(a)
+#  let b = bar(a) in
+#  let c = 123.0 + b in c
+#
+#bar = func(a) # TODO func type is wrong, for a
+#  let b = 1.0 + a in b
+#
+#boo : int -> int
+#boo = func(a)
 #  123
 
+# TODO: Constructs we should handle someday
+#baz1 = func(a) : float
+#  let b = -a in b
+#baz2 = func(a)
+#  let b : float = -a in b
 
-
-#foo = func(a, b)
-#  let bar = foo(a, b) in
-#  bar
-
-
-#let bar : int -> int -> int = func(a,b)
-#  if 1 then 2 else 3
-#
-#let setter : int -> void = func(a)
-#  set! value = 4
+makeAdder = func(a : int)
+  let d = 5 in
+  func(b) : int
+    let c = b + a in c
index 3bebdc22152af1b6c5f056207547b06a29d28f6f..439aa4c30bcf3e45aebc42bb85ce8c4f7f867522 100755 (executable)
@@ -132,12 +132,14 @@ class TypeChecker
   def check(env, expr, type)
     if (expr.is_a? ANF::If)
       check_ifexpr(env, expr, type)
-#    elsif (expr.is_a? ANF::Func)
-#      check_func(env, expr, type)
+    elsif (expr.is_a? ANF::Func)
+      check_func(env, expr, type)
     elsif (expr.is_a? ANF::Var)
       check_var(env, expr, type)
     elsif (expr.is_a? ANF::Let)
       check_let(env, expr, type)
+#    elsif (expr.is_a? ANF::Apply)
+#      check_apply(env, expr, type)
     else
       etype = infer(env, expr)
       if type != etype
@@ -189,15 +191,12 @@ class TypeChecker
 
   def check_apply(env, expr, type)
     # Handle global functions that haven't been typed yet but are
-    # being called. We pause to infer them. This probably causes
-    # a loop on recursive functions. Break that later.
+    # being called. We pause to infer their type.
     if untyped_global_func?(env, expr.func, type)
       value = env[expr.func.name][:value]
       env[expr.func.name][:value] = nil
       infer(@parser.syms, value)
       type = infer(env, expr.func)
-
-      pp "CHECK_APPLY", expr.func.name, type, expr.func, value
     end
 
     error(expr.loc, "object being applied is not a function (has type: #{type.to_s})") if not type.is_a? Array
@@ -225,11 +224,25 @@ class TypeChecker
     type
   end
 
-#  def check_func(env, expr, type)
-#  end
+  def verify(cond, loc, msg)
+    error(loc, msg) if not cond
+  end
 
-#  def check_let(env, expr, type)
-#  end
+  def check_func(env, expr, type)
+    env = env.clone
+    verify((type.length-1) == expr.args.length, expr.loc,
+      "incorrect number of arguments (#{expr.args.length}, expected #{(type.length-1)}")
+    expr.args.each_with_index do |a,i|
+      env.add_sym(a.name, a.loc, :arg, type[i])
+    end
+    check(env, expr.expr, type.last)
+  end
+
+  def check_let(env, let, type)
+    env = env.clone
+    env.add_sym(let.var.name, let.var.loc, :var, let.var.type)
+    check(env, let.expr, type)
+  end
 
   def infer_const(env, expr)
     expr.type
@@ -252,14 +265,12 @@ class TypeChecker
 
   def infer_decl(env, let)
     env = env.clone
-
     # handle the binding
     if let.var.type
       check(env, let.expr, let.var.type)
     else
       let.var.type = infer(env, let.expr)
     end
-
     env.set_type(let.var.name, let.var.type)
     env[let.var.name][:value] = nil
     let.type = :void
@@ -331,20 +342,22 @@ class TypeChecker
 
     # use the operand type to pick op type and check it
     if expr.args.length == 1
-      optype = UnaryOps[expr.func][vtype]
-      error(expr.loc, "unknown unary operation '#{expr.func}' for operand type #{vtype}") if not optype
-      check_apply(env, expr, optype)
+      check_unary(env, expr, vtype)
+
     elsif expr.args.length == 2
-      infer_binary(env, expr, vtype)
+      check_binary(env, expr, vtype)
     else
       error(expr.loc, "too many operands for operator '#{expr.func}'")
     end
   end
 
-  def infer_unary(env, expr, vtype)
+  def check_unary(env, expr, vtype)
+    optype = UnaryOps[expr.func][vtype]
+    error(expr.loc, "unknown unary operation '#{expr.func}' for operand type #{vtype}") if not optype
+    check_apply(env, expr, optype)
   end
 
-  def infer_binary(env, expr, vtype)
+  def check_binary(env, expr, vtype)
     optype = BinaryOps[expr.func][vtype]
     error(expr.loc, "unknown binary operation '#{expr.func}' for operand type #{vtype}") if optype.nil?
 
@@ -355,10 +368,31 @@ class TypeChecker
   end
 end
 
-
 class Parser < BaseParser
   attr_accessor :exprs
 
+  def initialize(path = nil)
+    super(nil)
+    syms.add_sym(:void, 0, :type, :void)
+    syms.add_sym(:bool, 0, :type, :bool)
+    syms.add_sym(:int, 0, :type, :int)
+    syms.add_sym(:string, 0, :type, :string)
+    syms.add_sym(:float, 0, :type, :float)
+    parse_file(path)
+  end
+
+  OPERATORS = {
+    "+" => true,
+    "-" => true,
+    "*" => true,
+    "/" => true,
+    "%" => true,
+  }
+
+  def operator?
+    OPERATORS[peek().type]
+  end
+
   def toplevel
     @type_checker = TypeChecker.new(self)
     @exprs = []
@@ -373,7 +407,7 @@ class Parser < BaseParser
     @exprs.each do |e|
       @type_checker.infer(syms, e)
     end
-    pp syms
+#    pp syms
   end
 
   def declaration()
@@ -410,12 +444,14 @@ class Parser < BaseParser
       expr = if_expr()
     elsif matches(:set)
       expr = var_set()
+    elsif matches_any(["+","-", "!"])
+      expr = unop_call()
     else
       expr = atomic_expr()
       if matches("(")
         expr = func_call(expr)
       elsif operator?
-        expr = operator_call(expr)
+        expr = binop_call(expr)
       end
     end
     expr
@@ -455,27 +491,18 @@ class Parser < BaseParser
 
 
 
-
-  OPERATORS = {
-    "+" => true,
-    "-" => true,
-    "*" => true,
-    "/" => true,
-    "%" => true,
-  }
-
-  def operator?
-    OPERATORS[peek().type]
+  def unop_call()
+    op = consume()
+    rhs = atomic_expr()
+    ANF::Apply.new(op.pos, nil, op.type, [rhs])
   end
 
-  def operator_call(expr)
+  def binop_call(expr)
     op = consume()
     rhs = atomic_expr()
     ANF::Apply.new(expr.loc, nil, op.type, [expr, rhs])
   end
 
-
-
   def if_expr()
     loc = expect(:if).pos
     cond = atomic_expr()
@@ -511,11 +538,13 @@ class Parser < BaseParser
     expect("(")
     while !matches(")")
       args << ident()
+      args.last.type = type_spec() if accept(":")
       expect(",") if not matches(")")
     end
     expect(")")
+    type = type_spec() if accept(":")
     body = expression()
-    ANF::Func.new(loc, nil, args, body)
+    ANF::Func.new(loc, type, args, body)
   end
 
   def ident()
@@ -523,6 +552,15 @@ class Parser < BaseParser
     ANF::Var.new(name.pos, nil, name.text.to_sym)
   end
 
+  def typename()
+    name = expect(:ident)
+    sym = syms[name.text]
+    if sym && sym[:kind] != :type
+      error(name.pos, "#{name.text} is not a valid type")
+    end
+    ANF::Var.new(name.pos, nil, name.text.to_sym)
+  end
+
   def constant()
     tok = consume()
     if tok.type == :bool
@@ -543,7 +581,7 @@ class Parser < BaseParser
   def let_expr()
     expect(:let)
     name = ident()
-    type = type_spec() if accept(":")
+    name.type = type_spec() if accept(":")
     expect("=")
     expr = complex_expr()
     expect(:in)
@@ -552,13 +590,77 @@ class Parser < BaseParser
   end
 
   def type_spec()
-    type = [ident().name]
+    type = [typename().name]
     while accept("->")
-      type << ident().name
+      type << typename().name
     end
     (type.length == 1 ? type[0] : type)
   end
 end
 
-parser = Parser.new("inputs/cerise.m")
-pp parser.syms
+
+$parser = Parser.new("inputs/cerise.m")
+
+def replace_var(env, expr)
+  if not env.defined? expr.name
+    env[:$free][expr.name] ||= env[:$free].length
+    expr = ANF::EnvRef.new(expr.loc, expr.type, env[:$free][expr.name])
+  end
+  expr
+end
+
+def patch_vars(env, expr, close_funcs = true)
+  if expr.is_a? ANF::Var
+    expr = replace_var(env, expr)
+  elsif expr.is_a? ANF::Let
+    expr.expr = patch_vars(env, expr.expr, close_funcs)
+    env.add_sym(expr.var.name, 0, :var, expr.var.type)
+    expr.body = patch_vars(env, expr.body, close_funcs)
+  elsif expr.is_a? ANF::If
+    expr.cond = patch_vars(env, expr.cond, close_funcs)
+    expr.then = patch_vars(env, expr.then, close_funcs)
+    expr.else = patch_vars(env, expr.else, close_funcs)
+  elsif expr.is_a? ANF::Set
+    expr.else = patch_vars(env, expr.else, close_funcs)
+  elsif expr.is_a? ANF::Func
+    expr = close_func(env, expr, close_funcs)
+  elsif expr.is_a? ANF::Apply
+    expr.func = patch_vars(env, expr.func, close_funcs)
+    expr.args = expr.args.map{|a| patch_vars(env, a, close_funcs) }
+  end
+  expr
+end
+
+$lifted = {}
+
+def close_func(env, func, close_funcs = true)
+  if close_funcs
+    func.type.unshift(:$ENV)
+    func.args.unshift(ANF::Var.new(0, :$ENV, :_env))
+    env = $parser.syms.clone
+  end
+  env[:$free] = {}
+  func.args.each do |a|
+    env.add_sym(a.name, 0, :var, a.type)
+  end
+  func.expr = patch_vars(env, func.expr, true)
+  if close_funcs
+    name = "__func#{$lifted.length}".to_sym
+    $lifted[name] = func
+    func = ANF::Var.new(0, func.type, name)
+  end
+  func
+end
+
+$parser.exprs.each do |e|
+    if e.is_a? ANF::Let and e.expr.is_a? ANF::Func
+      patch_vars($parser.syms.clone, e, false)
+    else
+      patch_vars($parser.syms.clone, e, true)
+    end
+    pp e
+end
+pp $lifted
+#pp parser.exprs[0]
+#pp parser.syms
+#pp parser.exprs
index 471351aafca4c1f8b9ab88b3745e9b8ea173f170..aa5cede879fa6475f59fd6d9e66d4a37a2988010 100644 (file)
 #       | <cexp>
 
 module ANF
-  Const = Struct.new(:loc, :type, :value)
-  Var   = Struct.new(:loc, :type, :name)
-  Let   = Struct.new(:loc, :type, :var,  :expr, :body)
-  If    = Struct.new(:loc, :type, :cond, :then, :else)
-  Set   = Struct.new(:loc, :type, :var,  :expr)
-  Func  = Struct.new(:loc, :type, :args, :expr)
-  Apply = Struct.new(:loc, :type, :func, :args)
+  Const  = Struct.new(:loc, :type, :value)
+  Var    = Struct.new(:loc, :type, :name)
+  EnvRef = Struct.new(:loc, :type, :index)
+  Let    = Struct.new(:loc, :type, :var,  :expr, :body)
+  If     = Struct.new(:loc, :type, :cond, :then, :else)
+  Set    = Struct.new(:loc, :type, :var,  :expr)
+  Func   = Struct.new(:loc, :type, :args, :expr)
+  Apply  = Struct.new(:loc, :type, :func, :args)
 
   def self.check_atomic(expr)
     case expr.class
index 711ef273e7278cbb257ec46b31e0c52b0d69bd11..2e6f300a9dcf1cb09414007fc8f44876b8c25a92 100644 (file)
@@ -1,4 +1,6 @@
 class SymTable < Hash
+  attr_accessor :parent
+
   def initialize(parent = nil)
     @global_scope = true if not parent.nil?
     @parent = (parent || {})
@@ -42,6 +44,16 @@ class SymTable < Hash
     end
   end
 
+  def depth
+    count = 0
+    env = self
+    while env.class != Hash
+      count += 1
+      env = env.parent
+    end
+    count
+  end
+
 #  def local(key)
 #    method(:[]).super_method.call(key)
 #  end