]> git.mdlowis.com Git - proto/cerise-c.git/commitdiff
reworked codegen module and now generate the main function instead of hardcoding it
authorMike Lowis <mike.lowis@gentex.com>
Thu, 20 Jun 2024 19:08:54 +0000 (15:08 -0400)
committerMike Lowis <mike.lowis@gentex.com>
Thu, 20 Jun 2024 19:08:54 +0000 (15:08 -0400)
.gitignore
build.sh
cerise-c.m
cerise-c.rb
runtime.h
runtime/Dict.c
runtime/Error.c
runtime/Length.c [new file with mode: 0644]

index 6107d0b7246a0eea60a3b4db82971229aa53ce4a..a1e3826ffc3b4d7e4f9507cd35e648ac51231f34 100644 (file)
@@ -3,3 +3,4 @@ cerise-c.c
 *.o
 *.a
 tags
+main.c
index 06606ef1831b0f1573ed16d238e83934385e7f5e..2877b46b3f492c265d33de8832d03b01851347e3 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -2,19 +2,28 @@
 
 OPTLEVEL=1
 
+# cleanup old files
 rm -f cerise-c libruntime.a runtime/*.o
 
+# compile all runtime sources
 for f in runtime/*.c; do
     printf "CC %s\n" "$f"
     gcc -c -I. -O$OPTLEVEL -o "${f%.c}.o" "$f" &
 done
 wait
+
+# compile the static lib
 printf "LIB %s\n" libruntime.a
 ar rcs libruntime.a runtime/*.o
 
+# generate and compile the main function
+printf "#include \"runtime.h\"\nENTRYPOINT(%s)\n" TestSuite_Main > main.c
+gcc -c -I. -O$OPTLEVEL -o main.o main.c
+
+# now compile the cerise file and link
 printf "\n"
 ./cerise-c.rb > cerise-c.c \
-    && gcc -I. -O2 -o cerise-c cerise-c.c libruntime.a \
+    && gcc -I. -O$optlevel -o cerise-c main.o cerise-c.o libruntime.a \
     && size cerise-c \
     && printf "\n" \
     && ./cerise-c
index 4f17ff8a60d938efc362139882037ae582f15bbf..cbca0d6b9cace854cffb5cdbe7cc692a798ddba5 100644 (file)
@@ -1,3 +1,6 @@
+module TestSuite
+
+
 TestLiterals()
 {
     assert 42
@@ -83,7 +86,7 @@ TestStringOps()
     assert ("foo" + 123.0) == "foo123.000000"
     assert ("foo" + true) == "footrue"
     assert ("foo" + false) == "foofalse"
-#    assert length("foo") == 3
+    assert Length("foo") == 3
 }
 
 TestArrayOps()
@@ -94,6 +97,7 @@ TestArrayOps()
     assert array[0] == 42
     assert array[1] == 2
     assert array[2] == 3
+    assert Length(array) == 3
 }
 
 TestHashOps()
@@ -103,6 +107,7 @@ TestHashOps()
     set hash["foo"] = item
     assert hash["foo"] == 42
     assert hash["baz"] == "boo"
+    assert Length(hash) == 2
 }
 
 TestEqOps() {
@@ -137,6 +142,17 @@ TestGtOps(){
 TestGtEqOps(){
 }
 
+TestIfBlocks(){
+    if (1 < 2) {
+        assert true
+    }
+    if (2 < 2) {
+        assert false
+    } else {
+        assert true
+    }
+}
+
 Main()
 {
     TestLiterals()
@@ -148,4 +164,5 @@ Main()
     TestHashOps()
     TestEqOps()
     TestNeqOps()
+    TestIfBlocks()
 }
index 6408f0a0c90749d659884c75e9bc29ac933a26e0..42a5df81c370a94544a1d367741f6b183e3e4795 100755 (executable)
@@ -94,6 +94,7 @@ class Lexer
     "set"    => :set,
     "return" => :return,
     "assert" => :assert,
+    "module" => :module
   }
 
   def next
@@ -131,15 +132,22 @@ end
 class Parser
   attr_accessor :syms
   attr_accessor :checker
+  attr_reader   :path
+  attr_reader   :module
 
   def initialize(path = nil)
+    @path = path
     @syms = SymTable.new
     @checker = TypeChecker.new(self)
-    syms.add_builtin(:void,   0, :type, :void)
-    syms.add_builtin(:bool,   0, :type, :bool)
-    syms.add_builtin(:int,    0, :type, :int)
-    syms.add_builtin(:string, 0, :type, :string)
-    syms.add_builtin(:float,  0, :type, :float)
+#    syms.add_builtin(:void,   0, :type, :void)
+#    syms.add_builtin(:bool,   0, :type, :bool)
+#    syms.add_builtin(:int,    0, :type, :int)
+#    syms.add_builtin(:string, 0, :type, :string)
+#    syms.add_builtin(:float,  0, :type, :float)
+
+    syms.add_builtin(:Length, :func, [:any, :int], nil)
+    syms.add_builtin(:Error,  :func, [:any, :void], nil)
+
     parse_file(path)
   end
 
@@ -228,6 +236,8 @@ class Parser
   end
 
   def toplevel
+    expect :module
+    @module = expect(:ident).text.to_sym
     imports
     exports
     while !matches(:eof)
@@ -598,13 +608,14 @@ class SymTable
   end
 
   def local?(name)
-    find_sym(@scopes[3..-1], name)
+    find_sym((@scopes[3..-1] || []).flatten, name)
   end
 
   def find_sym(scopes, name)
     scopes.reverse_each.detect do |scope|
-      return scope[name]
+      return scope[name] if scope[name]
     end
+    nil
   end
 
   def open_scope()
@@ -1073,83 +1084,101 @@ end
 # Code Generation Module
 ##
 
-module Codegen
+class Codegen
   State = Struct.new(:parser, :label, :locals, :syms, :temp, :indent)
 
-  def self.init(parser)
-    State.new(parser, 0, [], parser.syms, 0, 1)
-  end
-
-  def self.putln(state, msg)
-    puts ("    " * state.indent) + msg
-  end
+  def initialize(parser)
+    @parser = parser
+    @label = 0
+    @locals = []
+    @syms = @parser.syms
+    @temp = 0
+    @indent = 1
+    @output = $stdout
+#    @state = State.new(parser, 0, [], parser.syms, 0, 1)
+  end
+
+  def output(outpath)
+    @output = File.open(outpath, "wb")
+
+    puts "#include <runtime.h>\n\n"
+    @syms.each do |name, val|
+      args = val.value.args.map{|e| "Value #{e.name}" }.join(", ")
+      puts "Value #{symname(val)}(#{args});"
+    end
+    puts ""
+
+    @syms.each do |name, val|
+      args = val.value.args.map{|e| "Value #{e.name}" }.join(", ")
+      puts "Value #{symname(val)}(#{args}) {"
+      @syms.open_scope
+      @locals = val.value.locals
+      val.value.args.each_with_index do |name, idx|
+        @syms.add_sym(name.name, name.loc, :param, name.type, idx)
+      end
+      val.value.body.each do |stmnt|
+        emit(stmnt)
+      end
+      puts "}\n\n"
+      @syms.close_scope
+    end
 
-  def self.mktemp(state)
-    name = "_t#{state.temp}"
-    state.temp += 1
-    name
+    @output.close
   end
 
-  def self.genlabel(state)
-   label = state.label
-   state.label = state.label + 1
-  end
+  private
 
-  def self.emit(state, v)
+  def emit(v)
     if v.is_a? IR::Return then
-      emit_return(state, v)
+      emit_return(v)
     elsif v.is_a? IR::Const then
-      emit_const(state, v)
+      emit_const(v)
     elsif v.is_a? IR::Op then
-      emit_binop(state, v)
+      emit_binop(v)
     elsif v.is_a? IR::Var then
-      emit_var(state, v)
+      emit_var(v)
     elsif v.is_a? IR::Call then
-      emit_call(state, v)
+      emit_call(v)
     elsif v.is_a? IR::Def then
-      emit_def(state, v)
+      emit_def(v)
     elsif v.is_a? IR::Set then
-      emit_set(state, v)
+      emit_set(v)
     elsif v.is_a? IR::Assert then
-      emit_assert(state, v)
+      emit_assert(v)
     elsif v.is_a? IR::If then
-      emit_if(state, v)
+      emit_if(v)
     else
       raise "code emitting of #{v.class} not implemented"
     end
   end
 
-  def self.emit_return(state, v)
-    if v.value
-      temp = emit(state, v.value)
-      putln state, "return #{temp};"
-    else
-      putln state, "return MakeNil();"
-    end
+  def emit_assert(v)
+    temp = emit(v.value)
+    linenum = @parser.linenum(v.loc[1])
+    putln "Assert(#{v.loc[0].inspect}, #{linenum}, #{temp});"
   end
 
-  def self.emit_const(state, v)
-    var = mktemp(state)
+  def emit_const(v)
+    var = mktemp()
     if v.value.is_a? Integer
-      putln state, "Value #{var} = MakeInt(#{v.value});"
+      putln "Value #{var} = MakeInt(#{v.value});"
     elsif v.value.is_a? Float
-      putln state, "Value #{var} = MakeReal(#{v.value});"
+      putln "Value #{var} = MakeReal(#{v.value});"
     elsif v.value == true || v.value == false
-      putln state, "Value #{var} = MakeBool(#{v.value});"
+      putln "Value #{var} = MakeBool(#{v.value});"
     elsif v.value.is_a? String
-      putln state, "Value #{var} = MakeString(#{v.value});"
+      putln "Value #{var} = MakeString(#{v.value});"
     elsif v.value.is_a? Array
-      putln state, "Value #{var} = MakeArray(#{v.value.length});"
+      putln "Value #{var} = MakeArray(#{v.value.length});"
       v.value.each_with_index do |e, i|
-        val = emit(state, e)
-        putln state, "Array_Set(#{var}, #{i}, #{val});"
-
+        val = emit(e)
+        putln "Array_Set(#{var}, #{i}, #{val});"
       end
     elsif v.value.is_a? Hash
-      putln state, "Value #{var} = MakeDict(#{v.value.length});"
+      putln "Value #{var} = MakeDict(#{v.value.length});"
       v.value.each do |k, v|
-        val = emit(state, v)
-        putln state, "Dict_Set(#{var}, MakeString(#{k.value}), #{val});"
+        val = emit(v)
+        putln "Dict_Set(#{var}, MakeString(#{k.value}), #{val});"
       end
     else
       raise "code emitting constants of this type not implemented"
@@ -1157,61 +1186,70 @@ module Codegen
     var
   end
 
-  def self.emit_binop(state, v)
+  def emit_return(v)
+    if v.value
+      temp = emit(v.value)
+      putln "return #{temp};"
+    else
+      putln "return MakeNil();"
+    end
+  end
+
+  def emit_binop(v)
     if v.op == "&&"
-      lvar = emit(state, v.left)
-      result = mktemp(state);
-      putln state, "Value #{result} = #{lvar};"
-      putln state, "if (IsTrue(#{result})) {"
-      state.indent += 1
-      rvar = emit(state, v.right)
-      putln state, "#{result} = #{rvar};"
-      state.indent -= 1
-      putln state, "} else {"
-      putln state, "    #{result} = MakeBool(false);"
-      putln state, "}"
+      lvar = emit(v.left)
+      result = mktemp();
+      putln "Value #{result} = #{lvar};"
+      putln "if (IsTrue(#{result})) {"
+      @indent += 1
+      rvar = emit(v.right)
+      putln "#{result} = #{rvar};"
+      @indent -= 1
+      putln "} else {"
+      putln "    #{result} = MakeBool(false);"
+      putln "}"
     elsif v.op == "||"
-      lvar = emit(state, v.left)
-      result = mktemp(state);
-      putln state, "Value #{result} = #{lvar};"
-      putln state, "if (IsFalse(#{result})) {"
-      state.indent += 1
-      rvar = emit(state, v.right)
-      putln state, "#{result} = #{rvar};"
-      state.indent -= 1
-      putln state, "}"
+      lvar = emit(v.left)
+      result = mktemp();
+      putln "Value #{result} = #{lvar};"
+      putln "if (IsFalse(#{result})) {"
+      @indent += 1
+      rvar = emit(v.right)
+      putln "#{result} = #{rvar};"
+      @indent -= 1
+      putln "}"
     elsif v.op == "["
-      lvar = emit(state, v.left)
-      rvar = emit(state, v.right)
-      result = mktemp(state);
-      putln state, "Value #{result} = Object_Get(#{lvar}, #{rvar});"
+      lvar = emit(v.left)
+      rvar = emit(v.right)
+      result = mktemp();
+      putln "Value #{result} = Object_Get(#{lvar}, #{rvar});"
     else
-      lvar = emit(state, v.left)
-      rvar = emit(state, v.right)
-      result = mktemp(state);
+      lvar = emit(v.left)
+      rvar = emit(v.right)
+      result = mktemp();
       case v.op
       when "+"
-        putln state,  "Value #{result} = OpAdd(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpAdd(#{lvar}, #{rvar});"
       when "-"
-        putln state,  "Value #{result} = OpSub(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpSub(#{lvar}, #{rvar});"
       when "*"
-        putln state,  "Value #{result} = OpMul(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpMul(#{lvar}, #{rvar});"
       when "/"
-        putln state,  "Value #{result} = OpDiv(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpDiv(#{lvar}, #{rvar});"
       when "%"
-        putln state,  "Value #{result} = OpMod(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpMod(#{lvar}, #{rvar});"
       when "<"
-        putln state,  "Value #{result} = OpLt(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpLt(#{lvar}, #{rvar});"
       when "<="
-        putln state,  "Value #{result} = OpLtEq(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpLtEq(#{lvar}, #{rvar});"
       when ">"
-        putln state,  "Value #{result} = OpGt(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpGt(#{lvar}, #{rvar});"
       when ">="
-        putln state,  "Value #{result} = OpGtEq(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpGtEq(#{lvar}, #{rvar});"
       when "=="
-        putln state,  "Value #{result} = OpEq(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpEq(#{lvar}, #{rvar});"
       when "!="
-        putln state,  "Value #{result} = OpNeq(#{lvar}, #{rvar});"
+        putln "Value #{result} = OpNeq(#{lvar}, #{rvar});"
       else
         raise "not implemented"
       end
@@ -1219,107 +1257,120 @@ module Codegen
     result
   end
 
-  def self.emit_var(state, v)
-    var = state.syms[v.name]
-    if var.nil?
-      raise "no such variable: #{v.name}"
-    end
-    var.name
-  end
-
-  def self.emit_call(state, v)
-    result = mktemp(state)
+  def emit_call(v)
+    result = mktemp()
     vars = v.args.reverse.map do |arg|
-      emit(state, arg)
+      emit(arg)
     end.join(", ")
-    putln state, "Value #{result} = #{v.func.name}(#{vars});"
+    func = lookup_func(v.func)
+    if (func.is_a? SymTable::Symbol) then
+      error v, "symbol '#{func.name}' is not a function" if (func.kind != :func)
+      putln "Value #{result} = #{symname(func)}(#{vars});"
+    else
+      error v, "expression result is not a function"
+    end
+
     result
   end
 
-  def self.emit_def(state, v)
-    state.syms.add_sym(
-        v.name.name, v.loc, :local, v.type, v.value)
-    temp = emit(state, v.value)
-    raise "invalid local definition: #{v.name.name}" if not state.locals.index(v.name.name)
-    putln state, "Value #{v.name.name} = #{temp};"
+  def emit_def(v)
+    @syms.add_sym(v.name.name, v.loc, :local, v.type, v.value)
+    temp = emit(v.value)
+    error v, "invalid local definition: #{v.name.name}" if not @locals.index(v.name.name)
+    putln "Value #{v.name.name} = #{temp};"
   end
 
-  def self.emit_set(state, v)
+  def emit_set(v)
     if v.name.is_a? IR::Op
-      raise "not a valid array or hash access expression" if v.name.op != "["
-      object = emit(state, v.name.left)
-      key = emit(state, v.name.right)
-      value = emit(state, v.value)
-      putln state, "Object_Set(#{object}, #{key}, #{value});"
+      error v, "not a valid array or hash access expression" if v.name.op != "["
+      object = emit(v.name.left)
+      key = emit(v.name.right)
+      value = emit(v.value)
+      putln "Object_Set(#{object}, #{key}, #{value});"
     else
-      temp = emit(state, v.value)
-      raise "invalid local definition: #{v.name.name}" if not state.locals.index(v.name.name)
-      putln state, "#{v.name.name} = #{temp};"
+      temp = emit(v.value)
+      error v,  "invalid local definition: #{v.name.name}" if not @locals.index(v.name.name)
+      putln "#{v.name.name} = #{temp};"
     end
   end
 
-  def self.emit_assert(state, v)
-    temp = emit(state, v.value)
-    linenum = state.parser.linenum(v.loc[1])
-    putln state, "Assert(#{v.loc[0].inspect}, #{linenum}, #{temp});"
+  def emit_var(v)
+    var = @syms[v.name]
+    if var.nil?
+      error v, "no such variable: #{v.name}"
+    end
+    var.name
   end
 
-  def self.emit_if(state, v)
-    cond = emit(state, v.cond)
-    putln state,  "if (#{cond}) {"
-    emit_block(state, v.then)
-    putln state,  "} else {"
-    emit_block(state, v.else)
-    putln state,  "}"
+  def emit_if(v)
+    cond = emit(v.cond)
+    putln "if (ValueAsBool(#{cond})) {"
+    emit_block(v.then)
+    putln "} else {"
+    emit_block(v.else)
+    putln "}"
   end
 
-  def self.emit_block(state, v)
+  def emit_block(v)
     return if v.nil?
-    state.indent += 1
+    @indent += 1
     v.each do |v|
-      emit(state, v);
+      emit(v);
     end
-    state.indent -= 1
+    @indent -= 1
   end
-end
 
-##
-# Add Builtin Functions
-##
+  def lookup_func(func)
+    if func.is_a? IR::Var then
+      value = @syms[func.name]
+      error func, "no such function: #{func.name}" if value.nil?
+    else
+      value = func
+    end
+    value
+  end
 
+  def error(expr, msg)
+    file = @parser.path
+    line = @parser.linenum(expr.loc)
+    $stderr.puts "#{file}:#{line}:error: #{msg}"
+    exit 1
+  end
 
+  def puts(msg)
+    @output.puts msg
+  end
 
-##
-# Parse and Analyze
-##
+  def putln(msg)
+    @output.puts ("    " * @indent) + msg
+  end
 
-$parser  = Parser.new("cerise-c.m")
-$checker = TypeChecker.new($parser)
-$state = Codegen.init($parser)
+  def mktemp()
+    name = "_t#{@temp}"
+    @temp += 1
+    name
+  end
+
+  def genlabel()
+   state.label = state.label + 1
+  end
+
+  def symname(sym)
+    if @syms.builtin? sym.name
+      "#{sym.name}"
+    else
+      "#{@parser.module}_#{sym.name}"
+    end
+  end
+end
 
 
 ##
-# Emit the Code
+# Parse and Analyze
 ##
 
-puts "#include <runtime.h>\n\n"
-$parser.syms.each do |name, val|
-  args = val.value.args.map{|e| "Value #{e.name}" }.join(", ")
-  puts "Value #{name}(#{args});"
-end
-puts ""
-$parser.syms.each do |name, val|
-  args = val.value.args.map{|e| "Value #{e.name}" }.join(", ")
-  puts "Value #{name}(#{args}) {"
-  $parser.syms.open_scope
-  $state.locals = val.value.locals
-  val.value.args.each_with_index do |name, idx|
-    $parser.syms.add_sym(
-        name.name, name.loc, :param, name.type, idx)
-  end
-  val.value.body.each do |stmnt|
-    Codegen.emit($state, stmnt)
-  end
-  puts "}\n\n"
-  $parser.syms.close_scope
-end
+$parser  = Parser.new("cerise-c.m")
+$checker = TypeChecker.new($parser)
+$codegen = Codegen.new($parser)
+$codegen.output("cerise-c.c")
+`gcc -c -I. -O1 -o cerise-c.o #{"cerise-c.c"}`
index 609f0db4dc0c84a120eaf43908da66b4197bf939..72404d7860ccde7942e4804e1d99bd54deafd795 100644 (file)
--- a/runtime.h
+++ b/runtime.h
@@ -212,9 +212,10 @@ Value ToString(Value val);
 /* Builtin Functions
  *************************************************/
 
-void Error(Value val);
+Value Error(Value val);
 void RuntimeError(char* s);
 void Assert(char* file, int lineno, Value val);
+Value Length(Value val);
 
 
 /* Binary Operators
@@ -251,3 +252,11 @@ void Object_Set(Value object, Value key, Value value);
  *************************************************/
 
 void* GC_Allocate(size_t nbytes);
+
+
+#define ENTRYPOINT(func) \
+    int main(int argc, char** argv) { \
+        extern Value func(); \
+        (void)func(); \
+        return 0; \
+    }
index 0c895f82a12bec55106e587ec52a5f8217dc81a3..08dcb79c6ffef7c805d9c3b271e51e2e3519bf9c 100644 (file)
@@ -56,6 +56,7 @@ void Dict_Set(Value dictionary, Value key, Value value)
         DictNode* new_node = GC_Allocate(sizeof(DictNode));
         *new_node = node;
         *curr = new_node;
+        dict->length++;
     } else {
         (*curr)->value = value;
     }
index 9bf98a38377f58113531c8afe480c7c4d4440d88..c74cba72fb174deb00e28098efa123843ab020e1 100644 (file)
@@ -1,7 +1,8 @@
 #include "runtime.h"
 
-void Error(Value val) {
+Value Error(Value val) {
     RuntimeError(ValueAsString(val)->bytes);
+    return MakeNil();
 }
 
 void RuntimeError(char* s) {
diff --git a/runtime/Length.c b/runtime/Length.c
new file mode 100644 (file)
index 0000000..4ac494f
--- /dev/null
@@ -0,0 +1,15 @@
+#include "runtime.h"
+
+Value Length(Value val) {
+    Value result = MakeInt(0);
+    if (IsDict(val)) {
+        result = MakeInt( ValueAsDict(val)->length );
+    } else if (IsArray(val)) {
+        result = MakeInt( ValueAsArray(val)->length );
+    } else if (IsString(val)) {
+        result = MakeInt( ValueAsString(val)->length );
+    } else {
+        RuntimeError("value is not an aggregate type");
+    }
+    return result;
+}