From 660bf61643e22f52e706a597393c27a6248910d7 Mon Sep 17 00:00:00 2001 From: Fabio Mascarenhas Date: Wed, 18 Oct 2017 12:02:35 +0000 Subject: [PATCH 1/4] laying out the infrastructure for generating code for global variables --- titan-compiler/coder.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/titan-compiler/coder.lua b/titan-compiler/coder.lua index c6d9f3f..3d87f2c 100644 --- a/titan-compiler/coder.lua +++ b/titan-compiler/coder.lua @@ -790,19 +790,34 @@ local preamble = [[ local postamble = [[ int luaopen_%s(lua_State *L) { + %s_init(L); lua_newtable(L); %s return 1; } ]] +local init = [[ +void %s_init(lua_State *L) { + if(!_initialized) { + _initialized = 1; + %s + } +} +]] + function coder.generate(modname, ast) local tlcontext = { module = modname, } local code = { preamble } + + -- has this module already been initialized? + table.insert(code, "static int _initialized = 0;") + local funcs = {} + local initvars = {} for _, node in pairs(ast) do if not node._ignore then @@ -819,13 +834,17 @@ function coder.generate(modname, ast) end elseif tag == "TopLevel_Var" then codevardec(tlcontext, node) + table.insert(code, node._cdecl) + table.insert(initvars, node._init) else error("code generation not implemented for node " .. tag) end end end - table.insert(code, string.format(postamble, modname, table.concat(funcs, "\n"))) + table.insert(code, string.format(init, modname, table.concat(initvars, "\n"))) + + table.insert(code, string.format(postamble, modname, modname, table.concat(funcs, "\n"))) return table.concat(code, "\n\n") end From e2b28833f47dce18f68047f78072e90e6ce11af6 Mon Sep 17 00:00:00 2001 From: Fabio Mascarenhas Date: Thu, 19 Oct 2017 18:32:25 +0000 Subject: [PATCH 2/4] code gereration for global variables, both exported and not exported, also fixed issue #34 --- examples/artisanal.titan | 38 ++++++ spec/coder_spec.lua | 153 ++++++++++++++++++++++- spec/parser_spec.lua | 4 +- testfiles/toplevel_var.titan | 4 +- titan-compiler/checker.lua | 1 + titan-compiler/coder.lua | 228 ++++++++++++++++++++++++++++++----- titan-compiler/parser.lua | 4 +- 7 files changed, 397 insertions(+), 35 deletions(-) diff --git a/examples/artisanal.titan b/examples/artisanal.titan index 52ffcca..e7b096c 100644 --- a/examples/artisanal.titan +++ b/examples/artisanal.titan @@ -4,6 +4,44 @@ function foo(x: integer, y: integer): integer return x + y end +function doublea(): integer + a = a * 2 + return a +end + +function doubleb(): integer + b = b * 2 + return b +end + +function setc(t: { integer }): nil + c = t +end + +function sumc(): integer + local s = 0 + for i = 1, #c do + s = s + c[i] + end + return s +end + +function sumd(): integer + local s = 0 + for i = 1, #d do + s = s + d[i] + end + return s +end + +local a: integer = foo(2,3) + +b: integer = foo(10,5) + +local c: {integer} = {1,2,3} + +d: {integer} = {3,4,5} + function filltable(N: integer): { integer } local xs: { integer } = {} for i=1,N do diff --git a/spec/coder_spec.lua b/spec/coder_spec.lua index 6dcf878..cf642d4 100644 --- a/spec/coder_spec.lua +++ b/spec/coder_spec.lua @@ -327,12 +327,92 @@ describe("Titan code generator ", function() assert.truthy(ok, err) end) - pending("handles coercion to integer", function() + it("generates code for integer module-local variables", function() + local code = [[ + local a: integer = 1 + function geta(): integer + return a + end + function seta(x: integer): nil + a = x + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.geta() == 1);titan_test.seta(2);assert(titan_test.geta() == 2)") + assert.truthy(ok, err) + end) + + it("generates code for float module-local variables", function() + local code = [[ + local a: float = 1 + function geta(): float + return a + end + function seta(x: float): nil + a = x + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.geta() == 1);titan_test.seta(2);assert(titan_test.geta() == 2)") + assert.truthy(ok, err) + end) + + it("generates code for boolean module-local variables", function() + local code = [[ + local a: boolean = true + function geta(): boolean + return a + end + function seta(x: boolean): nil + a = x + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.geta() == true);titan_test.seta(false);assert(titan_test.geta() == false)") + assert.truthy(ok, err) + end) + + it("generates code for array module-local variables", function() + local code = [[ + local a: {integer} = {} + function len(): integer + return #a + end + function seta(x: {integer}): nil + a = x + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.len() == 0);titan_test.seta({1});assert(titan_test.len() == 1)") + assert.truthy(ok, err) + end) + + it("handles coercion to integer", function() local code = [[ function fn(): integer local f: float = 1.0 local i: integer = f - return 1 + return i end ]] local ast, err = parser.parse(code) @@ -340,12 +420,79 @@ describe("Titan code generator ", function() local ok, err = checker.check(ast, code, "test.titan") assert.truthy(ok, err) assert.same("Exp_ToInt", ast[1].block.stats[2].exp._tag) + local ok, err = generate(ast, "titan_test1") + assert.truthy(ok, err) + local ok, err = call("titan_test1", "local x = titan_test1.fn(); assert(math.type(x) == 'integer')") + assert.truthy(ok, err) + end) + + it("generates code for integer exported variables", function() + local code = [[ + a: integer = 1 + function geta(): integer + return a + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) local ok, err = generate(ast, "titan_test") assert.truthy(ok, err) - local ok, err = call("titan_test", "local x = titan_test.fn(); assert(math.type(x) == 'integer')") + local ok, err = call("titan_test", "assert(titan_test.geta() == 1);titan_test.a = 2;assert(titan_test.geta() == 2)") assert.truthy(ok, err) end) + it("generates code for exported float variables", function() + local code = [[ + a: float = 1 + function geta(): float + return a + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.geta() == 1);titan_test.a = 2;assert(titan_test.geta() == 2)") + assert.truthy(ok, err) + end) + + it("generates code for exported boolean variables", function() + local code = [[ + a: boolean = true + function geta(): boolean + return a + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.geta() == true);titan_test.a = false;assert(titan_test.geta() == false)") + assert.truthy(ok, err) + end) + + it("generates code for exported array variables", function() + local code = [[ + a: {integer} = {} + function len(): integer + return #a + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "assert(titan_test.len() == 0);titan_test.a={1};assert(titan_test.len() == 1)") + assert.truthy(ok, err) + end) end) diff --git a/spec/parser_spec.lua b/spec/parser_spec.lua index bb535e5..6405761 100644 --- a/spec/parser_spec.lua +++ b/spec/parser_spec.lua @@ -44,10 +44,10 @@ describe("Titan parser", function() assert_ast(program, { { _tag = "TopLevel_Var", islocal = true, - decl = { name = "x", type = false } }, + decl = { name = "x", type = { _tag = "Type_Basic", name = "integer" } } }, { _tag = "TopLevel_Var", islocal = false, - decl = { name = "y", type = false } }, + decl = { name = "y", type = { _tag = "Type_Basic", name = "integer" } } }, }) end) diff --git a/testfiles/toplevel_var.titan b/testfiles/toplevel_var.titan index 109b9ec..108da7d 100644 --- a/testfiles/toplevel_var.titan +++ b/testfiles/toplevel_var.titan @@ -1,3 +1,3 @@ -local x=17 +local x:integer=17 -y=18 +y:integer=18 diff --git a/titan-compiler/checker.lua b/titan-compiler/checker.lua index 6631de0..05808d1 100644 --- a/titan-compiler/checker.lua +++ b/titan-compiler/checker.lua @@ -125,6 +125,7 @@ local function firstpass(ast, st, errors) elseif tag == "TopLevel_Var" then name = tlnode.decl.name tlnode._type = typefromnode(tlnode.decl.type, errors) + tlnode._lin = util.get_line_number(errors.subject, tlnode._pos) else error("impossible") end diff --git a/titan-compiler/coder.lua b/titan-compiler/coder.lua index 3d87f2c..02287b2 100644 --- a/titan-compiler/coder.lua +++ b/titan-compiler/coder.lua @@ -17,13 +17,14 @@ local function node2literal(node) end local function getslot(t, c, s) + c = c and c .. " =" or "" local tmpl - if types.equals(t, types.Integer) then tmpl = "%s = ivalue(%s);" - elseif types.equals(t, types.Float) then tmpl = "%s = fltvalue(%s);" - elseif types.equals(t, types.Boolean) then tmpl = "%s = bvalue(%s);" - elseif types.equals(t, types.Nil) then tmpl = "%s = 0;" - elseif types.equals(t, types.String) then tmpl = "%s = svalue(%s);" - elseif types.has_tag(t, "Array") then tmpl = "%s = hvalue(%s);" + if types.equals(t, types.Integer) then tmpl = "%s ivalue(%s)" + elseif types.equals(t, types.Float) then tmpl = "%s fltvalue(%s)" + elseif types.equals(t, types.Boolean) then tmpl = "%s bvalue(%s)" + elseif types.equals(t, types.Nil) then tmpl = "%s 0" + elseif types.equals(t, types.String) then tmpl = "%s svalue(%s)" + elseif types.has_tag(t, "Array") then tmpl = "%s hvalue(%s)" else error("invalid type " .. types.tostring(t)) end @@ -52,6 +53,28 @@ local function checkandget(t, c, s, lin) ]], tag, s, getslot(t, c, s), lin, tag, s) end +local function checkandset(t, st, sl, lin) + local tag + if types.equals(t, types.Integer) then tag = "integer" + elseif types.equals(t, types.Float) then + return string.format([[ + if(ttisinteger(%s)) { setfltvalue(%s, ((lua_Number)ivalue(%s))); } + else if(ttisfloat(%s)) { setobj2t(L, %s, %s); } + else luaL_error(L, "type error at line %d, expected float but found %%s", lua_typename(L, ttnov(%s))); + ]], sl, st, sl, sl, st, sl, lin, sl) + elseif types.equals(t, types.Boolean) then tag = "boolean" + elseif types.equals(t, types.Nil) then tag = "nil" + elseif types.equals(t, types.String) then tag = "string" + elseif types.has_tag(t, "Array") then tag = "table" + else + error("invalid type " .. types.tostring(t)) + end + return string.format([[ + if(ttis%s(%s)) { setobj2t(L, %s, %s); } + else luaL_error(L, "type error at line %d, expected %s but found %%s", lua_typename(L, ttnov(%s))); + ]], tag, sl, st, sl, lin, tag, sl) +end + local function setslot(t, s, c) local tmpl if types.equals(t, types.Integer) then tmpl = "setivalue(%s, %s);" @@ -278,20 +301,27 @@ local function codeassignment(ctx, node) local vtag = node.var._tag if vtag == "Var_Name" then local cstats, cexp = codeexp(ctx, node.exp) - local cset = "" - if types.is_gc(node.var._type) then - cset = string.format([[ - /* update slot */ + if node.var._decl._tag == "TopLevel_Var" and not node.var._decl.islocal then + return string.format([[ %s - ]], setslot(node.var._type, node.var._decl._slot, node.var._decl._cvar)) + %s; + ]], cstats, setslot(node.var._type, node.var._decl._slot, cexp)) + else + local cset = "" + if types.is_gc(node.var._type) then + cset = string.format([[ + /* update slot */ + %s + ]], setslot(node.var._type, node.var._decl._slot, node.var._decl._cvar)) + end + return string.format([[ + { + %s + %s = %s; + %s + } + ]], cstats, node.var._decl._cvar, cexp, cset) end - return string.format([[ - { - %s - %s = %s; - %s - } - ]], cstats, node.var._decl._cvar, cexp, cset) elseif vtag == "Var_Index" then local arr = node.var.exp1 local idx = node.var.exp2 @@ -455,7 +485,11 @@ end -- the preliminary code is always the empty string local function codevar(ctx, node) - return "", node._decl._cvar + if node._decl._tag == "TopLevel_Var" and not node._decl.islocal then + return "", getslot(node._type, nil, node._decl._slot) + else + return "", node._decl._cvar + end end local function codevalue(ctx, node) @@ -672,7 +706,7 @@ function codeexp(ctx, node, iscondition) %s %s %s - %s = fltvalue(%s); + %s = %s; %s = l_floor(%s); if (%s != %s) %s = 0; else lua_numbertointeger(%s, &%s); ]], cstat, ctmp1, ctmp2, ctmp3, tmpname1, cexp, tmpname2, tmpname1, tmpname1, @@ -768,12 +802,37 @@ local function codefuncdec(tlcontext, node) }]], node.name, #node.params, node.name, #node.params, table.concat(stats, "\n")) end -local function codevardec(node) - -- TODO: generate code for global variables - error("code generation for global variables is not implemented") +local function codevardec(tlctx, ctx, node) + local cstats, cexp = codeexp(ctx, node.value) + if node.islocal then + node._cvar = "_global_" .. node.decl.name + node._cdecl = "static " .. ctype(node._type) .. " " .. node._cvar .. ";" + node._init = string.format([[ + %s + %s = %s; + ]], cstats, node._cvar, cexp) + if types.is_gc(node._type) then + node._slot = "_globalslot_" .. node.decl.name + node._cdecl = "static TValue *" .. node._slot .. ";\n" .. + node._cdecl + node._init = string.format([[ + %s + %s; + ]], node._init, setslot(node._type, node._slot, node._cvar)) + end + else + node._slot = node.decl.name .. "_titanvar" + node._cdecl = "TValue *" .. node._slot .. ";" + node._init = string.format([[ + %s + %s; + ]], cstats, setslot(node._type, node._slot, cexp)) + end end local preamble = [[ +#include + #include "lauxlib.h" #include "lualib.h" @@ -793,6 +852,7 @@ int luaopen_%s(lua_State *L) { %s_init(L); lua_newtable(L); %s + luaL_setmetatable(L, "titan module %s"); return 1; } ]] @@ -818,6 +878,29 @@ function coder.generate(modname, ast) local funcs = {} local initvars = {} + local varslots = {} + local gvars = {} + + local initctx = newcontext() + + for _, node in pairs(ast) do + if not node._ignore then + local tag = node._tag + if tag == "TopLevel_Func" then + -- ignore functions in the first pass + elseif tag == "TopLevel_Var" then + codevardec(tlcontext, initctx, node) + table.insert(code, node._cdecl) + table.insert(initvars, node._init) + table.insert(varslots, node._slot) + if not node.islocal then + table.insert(gvars, node) + end + else + error("code generation not implemented for node " .. tag) + end + end + end for _, node in pairs(ast) do if not node._ignore then @@ -833,18 +916,109 @@ function coder.generate(modname, ast) ]], node.name, node.name)) end elseif tag == "TopLevel_Var" then - codevardec(tlcontext, node) - table.insert(code, node._cdecl) - table.insert(initvars, node._init) + -- ignore vars in second pass else error("code generation not implemented for node " .. tag) end end end + if initctx.nslots + #varslots > 0 then + local switch_get, switch_set = {}, {} + + for i, var in ipairs(gvars) do + table.insert(switch_get, string.format([[ + case %d: setobj2t(L, L->top-1, %s); break; + ]], i, var._slot)) + table.insert(switch_set, string.format([[ + case %d: { + lua_pushvalue(L, 3); + %s; + break; + } + ]], i, checkandset(var._type, var._slot, "L->top-1", var._lin))) + end + + table.insert(code, string.format([[ + static int __index(lua_State *L) { + lua_pushvalue(L, 2); + lua_rawget(L, lua_upvalueindex(1)); + if(lua_isnil(L, -1)) { + return luaL_error(L, + "global variable '%%s' does not exist in Titan module '%%s'", + lua_tostring(L, 2), "%s"); + } + switch(lua_tointeger(L, -1)) { + %s + } + return 1; + } + + static int __newindex(lua_State *L) { + lua_pushvalue(L, 2); + lua_rawget(L, lua_upvalueindex(1)); + if(lua_isnil(L, -1)) { + return luaL_error(L, + "global variable '%%s' does not exist in Titan module '%%s'", + lua_tostring(L, 2), "%s"); + } + switch(lua_tointeger(L, -1)) { + %s + } + return 1; + } + ]], modname, table.concat(switch_get, "\n"), modname, table.concat(switch_set, "\n"))) + + local nslots = initctx.nslots + #varslots + 1 + + table.insert(initvars, 1, string.format([[ + luaL_newmetatable(L, "titan module %s"); /* push metatable */ + int _meta = lua_gettop(L); + TValue *_base = L->top; + /* protect it */ + lua_pushliteral(L, "titan module %s"); + lua_setfield(L, -2, "__metatable"); + /* reserve needed stack space */ + if (L->stack_last - L->top > %d) { + if (L->ci->top < L->top + %d) L->ci->top = L->top + %d; + } else { + lua_checkstack(L, %d); + } + L->top += %d; + for(TValue *_s = L->top - 1; _base <= _s; _s--) + setnilvalue(_s); + Table *_map = luaH_new(L); + sethvalue(L, L->top-%d, _map); + lua_pushcclosure(L, __index, %d); + TValue *_upvals = clCvalue(L->top-1)->upvalue; + lua_setfield(L, _meta, "__index"); + sethvalue(L, L->top, _map); + L->top++; + lua_pushcclosure(L, __newindex, 1); + lua_setfield(L, _meta, "__newindex"); + L->top++; + sethvalue(L, L->top-1, _map); + ]], modname, modname, nslots, nslots, nslots, nslots, nslots, #varslots+1, + #varslots+1)) + for i, slot in ipairs(varslots) do + table.insert(initvars, i+1, string.format([[ + %s = &_upvals[%d]; + ]], slot, i)) + end + for i, var in ipairs(gvars) do + table.insert(initvars, 2, string.format([[ + lua_pushinteger(L, %d); + lua_setfield(L, -2, "%s"); + ]], i, var.decl.name)) + end + table.insert(initvars, string.format([[ + L->top = _base-1; + ]], modname)) + end + table.insert(code, string.format(init, modname, table.concat(initvars, "\n"))) - table.insert(code, string.format(postamble, modname, modname, table.concat(funcs, "\n"))) + table.insert(code, string.format(postamble, modname, modname, table.concat(funcs, "\n"), modname)) return table.concat(code, "\n\n") end diff --git a/titan-compiler/parser.lua b/titan-compiler/parser.lua index 60f7893..4a3ccc3 100644 --- a/titan-compiler/parser.lua +++ b/titan-compiler/parser.lua @@ -158,7 +158,9 @@ local grammar = re.compile([[ COLON type block END) -> TopLevel_Func - toplevelvar <- ({} localopt decl ASSIGN exp) -> TopLevel_Var + toplevelvar <- ({} localopt + ({} NAME COLON type) -> Decl_Decl + ASSIGN exp) -> TopLevel_Var localopt <- (LOCAL)? -> boolopt From 347edf80eb57f2974d79d3f45c64533e9008e7fd Mon Sep 17 00:00:00 2001 From: Fabio Mascarenhas Date: Thu, 19 Oct 2017 19:16:11 +0000 Subject: [PATCH 3/4] check module variables before module functions so they will not be able to call the module functions --- spec/parser_spec.lua | 4 +-- testfiles/toplevel_var.titan | 4 +-- titan-compiler/checker.lua | 63 ++++++++++++++++++++++++------------ titan-compiler/parser.lua | 4 +-- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/spec/parser_spec.lua b/spec/parser_spec.lua index 6405761..bb535e5 100644 --- a/spec/parser_spec.lua +++ b/spec/parser_spec.lua @@ -44,10 +44,10 @@ describe("Titan parser", function() assert_ast(program, { { _tag = "TopLevel_Var", islocal = true, - decl = { name = "x", type = { _tag = "Type_Basic", name = "integer" } } }, + decl = { name = "x", type = false } }, { _tag = "TopLevel_Var", islocal = false, - decl = { name = "y", type = { _tag = "Type_Basic", name = "integer" } } }, + decl = { name = "y", type = false } }, }) end) diff --git a/testfiles/toplevel_var.titan b/testfiles/toplevel_var.titan index 108da7d..109b9ec 100644 --- a/testfiles/toplevel_var.titan +++ b/testfiles/toplevel_var.titan @@ -1,3 +1,3 @@ -local x:integer=17 +local x=17 -y:integer=18 +y=18 diff --git a/titan-compiler/checker.lua b/titan-compiler/checker.lua index 05808d1..b2761af 100644 --- a/titan-compiler/checker.lua +++ b/titan-compiler/checker.lua @@ -104,8 +104,7 @@ local function trycoerce(node, target) end end --- First typecheck pass over the module, collects type information --- for top-level functions and variables and checks for duplicate definitions +-- First typecheck pass over the module, typecheks module variables -- ast: AST for the whole module -- st: symbol table -- errors: list of compile-time errors @@ -113,27 +112,51 @@ end -- annotates whether a top-level declaration is duplicated with a "_ignore" field local function firstpass(ast, st, errors) for _, tlnode in ipairs(ast) do - local tag = tlnode._tag local name - if tag == "TopLevel_Func" then + if tlnode._tag == "TopLevel_Var" then + name = tlnode.decl.name + if tlnode.decl.type then + tlnode._type = typefromnode(tlnode.decl.type, errors) + checkexp(tlnode.value, st, errors, tlnode._type) + else + checkexp(tlnode.value, st, errors) + tlnode._type = tlnode.value._type + end + tlnode._lin = util.get_line_number(errors.subject, tlnode._pos) + if st:find_dup(name) then + typeerror(errors, "duplicate variable declaration for " .. name, tlnode._pos) + tlnode._ignore = true + else + st:add_symbol(name, tlnode) + end + end + end +end + + +-- Second typecheck pass over the module, collects type information +-- for top-level functions +-- ast: AST for the whole module +-- st: symbol table +-- errors: list of compile-time errors +-- annotates the top-level nodes with their types in a "_type" field +-- annotates whether a top-level declaration is duplicated with a "_ignore" field +local function secondpass(ast, st, errors) + for _, tlnode in ipairs(ast) do + local name + if tlnode._tag == "TopLevel_Func" then name = tlnode.name local ptypes = {} for _, pdecl in ipairs(tlnode.params) do table.insert(ptypes, typefromnode(pdecl.type, errors)) end tlnode._type = types.Function(ptypes, typefromnode(tlnode.rettype, errors)) - elseif tag == "TopLevel_Var" then - name = tlnode.decl.name - tlnode._type = typefromnode(tlnode.decl.type, errors) - tlnode._lin = util.get_line_number(errors.subject, tlnode._pos) - else - error("impossible") - end - if st:find_dup(name) then - typeerror(errors, "duplicate function or variable declaration for " .. name, tlnode._pos) - tlnode._ignore = true - else - st:add_symbol(name, tlnode) + if st:find_dup(name) then + typeerror(errors, "duplicate function or variable declaration for " .. name, tlnode._pos) + tlnode._ignore = true + else + st:add_symbol(name, tlnode) + end end end end @@ -552,19 +575,16 @@ local function checkfunc(node, st, errors) end end --- Second typechecking pass over the module, checks function bodies --- and rhs of top-level variable declarations +-- Third typechecking pass over the module, checks function bodies -- ast: AST for the whole module -- st: symbol table -- errors: list of compile-time errors -local function secondpass(ast, st, errors) +local function thirdpass(ast, st, errors) for _, tlnode in ipairs(ast) do if not tlnode._ignore then local tag = tlnode._tag if tag == "TopLevel_Func" then st:with_block(checkfunc, tlnode, st, errors) - else - checkexp(tlnode.value, st, errors, tlnode._type) end end end @@ -582,6 +602,7 @@ function checker.check(ast, subject, filename) local errors = {subject = subject, filename = filename} firstpass(ast, st, errors) secondpass(ast, st, errors) + thirdpass(ast, st, errors) if #errors > 0 then return false, table.concat(errors, "\n") end diff --git a/titan-compiler/parser.lua b/titan-compiler/parser.lua index 4a3ccc3..60f7893 100644 --- a/titan-compiler/parser.lua +++ b/titan-compiler/parser.lua @@ -158,9 +158,7 @@ local grammar = re.compile([[ COLON type block END) -> TopLevel_Func - toplevelvar <- ({} localopt - ({} NAME COLON type) -> Decl_Decl - ASSIGN exp) -> TopLevel_Var + toplevelvar <- ({} localopt decl ASSIGN exp) -> TopLevel_Var localopt <- (LOCAL)? -> boolopt From eedd59ab6a62b379cdf3dca3e504fddf2cac9046 Mon Sep 17 00:00:00 2001 From: Fabio Mascarenhas Date: Fri, 20 Oct 2017 16:51:54 +0000 Subject: [PATCH 4/4] avoid warnings by the C compiler on unused local variables --- spec/coder_spec.lua | 20 +++++++++++++++-- titan-compiler/checker.lua | 2 +- titan-compiler/coder.lua | 45 ++++++++++++++++++++++---------------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/spec/coder_spec.lua b/spec/coder_spec.lua index cf642d4..1abc1c0 100644 --- a/spec/coder_spec.lua +++ b/spec/coder_spec.lua @@ -420,9 +420,25 @@ describe("Titan code generator ", function() local ok, err = checker.check(ast, code, "test.titan") assert.truthy(ok, err) assert.same("Exp_ToInt", ast[1].block.stats[2].exp._tag) - local ok, err = generate(ast, "titan_test1") + local ok, err = generate(ast, "titan_test") + assert.truthy(ok, err) + local ok, err = call("titan_test", "local x = titan_test.fn(); assert(math.type(x) == 'integer')") assert.truthy(ok, err) - local ok, err = call("titan_test1", "local x = titan_test1.fn(); assert(math.type(x) == 'integer')") + end) + + it("handles unused locals", function() + local code = [[ + function fn(): nil + local f: float = 1.0 + local i: integer = f + end + ]] + local ast, err = parser.parse(code) + assert.truthy(ast, err) + local ok, err = checker.check(ast, code, "test.titan") + assert.truthy(ok, err) + assert.same("Exp_ToInt", ast[1].block.stats[2].exp._tag) + local ok, err = generate(ast, "titan_test") assert.truthy(ok, err) end) diff --git a/titan-compiler/checker.lua b/titan-compiler/checker.lua index b2761af..f255ea1 100644 --- a/titan-compiler/checker.lua +++ b/titan-compiler/checker.lua @@ -324,7 +324,6 @@ function checkexp(node, st, errors, context) if tag == "Var_Name" then local decl = st:find_symbol(node.name) if not decl then - -- TODO generate better error messages when we have the line num local msg = "variable '" .. node.name .. "' not declared" typeerror(errors, msg, node._pos) node._type = types.Integer @@ -332,6 +331,7 @@ function checkexp(node, st, errors, context) typeerror(errors, "reference to function " .. node.name .. " outside of function call", decl._pos) node._type = types.Integer else + decl._used = true node._decl = decl node._type = decl._type end diff --git a/titan-compiler/coder.lua b/titan-compiler/coder.lua index 02287b2..9b58e9f 100644 --- a/titan-compiler/coder.lua +++ b/titan-compiler/coder.lua @@ -434,28 +434,35 @@ function codestat(ctx, node) local tag = node._tag if tag == "Stat_Decl" then local cstats, cexp = codeexp(ctx, node.exp) - local typ = node.decl._type - node.decl._cvar = "_local_" .. node.decl.name - local cdecl = ctype(typ) .. " " .. node.decl._cvar .. ";" - local cslot = "" - local cset = "" - if types.is_gc(typ) then - node.decl._slot = "_localslot_" .. node.decl.name - cslot = newslot(ctx, node.decl._slot); - cset = string.format([[ - /* update slot */ + if node.decl._used then + local typ = node.decl._type + node.decl._cvar = "_local_" .. node.decl.name + local cdecl = ctype(typ) .. " " .. node.decl._cvar .. ";" + local cslot = "" + local cset = "" + if types.is_gc(typ) then + node.decl._slot = "_localslot_" .. node.decl.name + cslot = newslot(ctx, node.decl._slot); + cset = string.format([[ + /* update slot */ + %s + ]], setslot(typ, node.decl._slot, node.decl._cvar)) + end + return string.format([[ %s - ]], setslot(typ, node.decl._slot, node.decl._cvar)) - end - return string.format([[ - %s - %s - { %s - %s = %s; + { + %s + %s = %s; + %s + } + ]], cdecl, cslot, cstats, node.decl._cvar, cexp, cset) + else + return string.format([[ %s - } - ]], cdecl, cslot, cstats, node.decl._cvar, cexp, cset) + ((void)%s); + ]], cstats, cexp) + end elseif tag == "Stat_Block" then return codeblock(ctx, node) elseif tag == "Stat_While" then