From: Mike Lowis Date: Thu, 20 Jun 2024 19:08:54 +0000 (-0400) Subject: reworked codegen module and now generate the main function instead of hardcoding it X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=d758b49d245e8771aa8da3ae203d4b926f402e17;p=proto%2Fcerise-c.git reworked codegen module and now generate the main function instead of hardcoding it --- diff --git a/.gitignore b/.gitignore index 6107d0b..a1e3826 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ cerise-c.c *.o *.a tags +main.c diff --git a/build.sh b/build.sh index 06606ef..2877b46 100755 --- 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 diff --git a/cerise-c.m b/cerise-c.m index 4f17ff8..cbca0d6 100644 --- a/cerise-c.m +++ b/cerise-c.m @@ -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() } diff --git a/cerise-c.rb b/cerise-c.rb index 6408f0a..42a5df8 100755 --- a/cerise-c.rb +++ b/cerise-c.rb @@ -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 \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 \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"}` diff --git a/runtime.h b/runtime.h index 609f0db..72404d7 100644 --- 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; \ + } diff --git a/runtime/Dict.c b/runtime/Dict.c index 0c895f8..08dcb79 100644 --- a/runtime/Dict.c +++ b/runtime/Dict.c @@ -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; } diff --git a/runtime/Error.c b/runtime/Error.c index 9bf98a3..c74cba7 100644 --- a/runtime/Error.c +++ b/runtime/Error.c @@ -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 index 0000000..4ac494f --- /dev/null +++ b/runtime/Length.c @@ -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; +}