Lunatic translator: fix codegen for "break" inside while loops.

Note: usually, languages call this "continue". Jeez, CON...

git-svn-id: https://svn.eduke32.com/eduke32@3529 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-02-28 17:29:58 +00:00
parent d747e26fd0
commit 677c585b72
4 changed files with 167 additions and 18 deletions

View file

@ -26,6 +26,8 @@ local tostring = tostring
local type = type
local unpack = unpack
local format = require("string").format
local actor, player = assert(actor), assert(player)
local dc = require("defs_common")
local cansee, hitscan, neartag = dc.cansee, dc.hitscan, dc.neartag

View file

@ -654,6 +654,7 @@ player_static_members.INPUT_EXT_BITS = defs_c.conststruct
INPUT_TURN_RIGHT = 32,
}
-- XXX: error message will say "g_player_ps"
player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "playerswhenstarted", player_static_members))
-- needed by "control"

View file

@ -100,6 +100,11 @@ local g_cgopt = { ["no"]=false, }
-- needed to cope with CONs dangling-else resolution
local g_iflevel = 0
local g_ifelselevel = 0
-- Stack with *true* on top if the innermost block is a "whilevar*n".
local g_isWhile = {}
-- Sequence number of 'while' statements, used to implement CON "break" inside
-- whilevar*n, which really behaves like what sane languages call "continue"...
local g_whilenum = 0
---=== Code generation ===---
local GVFLAG = {
@ -1611,7 +1616,11 @@ local userdef_common_pat = (arraypat + sp1)/{} * lpeg.Cc(0) * lpeg.Ct(singlememb
local Cinner = {
-- these can appear anywhere in the script
["break"] = cmd()
/ "do return end",
/ function()
return g_isWhile[#g_isWhile]
and format("goto l%d", g_whilenum)
or "do return end"
end,
["return"] = cmd() -- NLCF
/ "_con.longjmp()",
@ -1678,7 +1687,7 @@ local Cinner = {
xorvar = varopf "_bit.bxor",
randvar = varop / "%1=_con._rand(%2)",
shiftvarl = varopf "_bit.lshift",
shiftvarr = varopf "_bit.rshift",
shiftvarr = varopf "_bit.arshift",
--- 2. Math operations
sqrt = cmd(R,W)
@ -2378,7 +2387,7 @@ end
-- if desired.
local function Keyw(kwname) return TraceFunc(kwname, "kw", false) end
local function NotKeyw(text) return TraceFunc(text, "!kw", false) end
local function Ident(idname) return TraceFunc(idname, "id", false) end
--local function Ident(idname) return TraceFunc(idname, "id", false) end
local function Stmt(cmdpat) return TraceFunc(cmdpat, "st", false) end
--local function Temp(kwname) return TraceFunc(kwname, "temp", true) end
@ -2433,7 +2442,14 @@ local function attachnames(kwtab, matchtimefunc)
-- acts as a barrier to captures to delay (but not fully prevent) stack
-- overflow (and to make lpeg.match return a subject position at the
-- end)
kwtab[cmdname] = lpeg.Cmt(Keyw(cmdname) * cmdpat, matchtimefunc)
local newpat = Keyw(cmdname) * cmdpat
if (cmdname~="break") then
kwtab[cmdname] = lpeg.Cmt(newpat, matchtimefunc)
else
-- Must not attack a Cmt to "break" because it would break the
-- while/switch sequencing.
kwtab[cmdname] = newpat
end
end
end
@ -2574,7 +2590,7 @@ local function get_deferred_code(tab, lev, code)
return code
end
local function begin_if_fn(condstr, endifstr, endifelsestr)
function on.if_begin(condstr, endifstr, endifelsestr)
assert(type(condstr)=="string")
add_deferred_code(g_endIfCode, g_iflevel, endifstr)
@ -2586,7 +2602,7 @@ local function begin_if_fn(condstr, endifstr, endifelsestr)
return format("if (%s) then", condstr)
end
local function end_if_fn()
function on.if_end()
g_iflevel = g_iflevel-1
local code = get_deferred_code(g_endIfCode, g_iflevel, "")
if (code ~= "") then
@ -2594,17 +2610,35 @@ local function end_if_fn()
end
end
local function end_if_else_fn()
function on.if_else_end()
g_ifelselevel = g_ifelselevel-1
return get_deferred_code(g_endIfElseCode, g_ifelselevel, "end ")
end
function on.while_begin(v1, v2)
table.insert(g_isWhile, true)
return format("while (%s~=%s) do", v1, v2)
end
function on.while_end()
table.remove(g_isWhile)
local code=format("::l%d:: end", g_whilenum)
g_whilenum = g_whilenum+1
return code
end
function on.switch_begin()
table.insert(g_isWhile, false)
end
function on.switch_end(testvar, blocks)
local SW = format("_SW[%d]", g_switchCount)
local swcode = { format("%s={", SW) }
local have = {}
local havedefault = false
table.remove(g_isWhile)
for i=1,#blocks do
local block = blocks[i]
assert(#block >= 1)
@ -2712,7 +2746,7 @@ local Grammar = Pat{
t_singlearrayexp = tok.identifier * arraypat * singlememberpat^-1,
-- SWITCH
switch_stmt = Keyw("switch") * sp1 * tok.rvar *
switch_stmt = Keyw("switch") * sp1 * tok.rvar * (lpeg.Cc(nil)/on.switch_begin) *
lpeg.Ct((Var("case_block") + Var("default_block"))^0) * sp1 * "endswitch"
/ on.switch_end,
@ -2725,21 +2759,21 @@ local Grammar = Pat{
optional_else = (sp1 * lpeg.C("else") * sp1 * Var("single_stmt"))^-1,
if_else_bodies = Var("single_stmt2") * (Pat("")/end_if_fn) * Var("optional_else"),
if_else_bodies = Var("single_stmt2") * (Pat("")/on.if_end) * Var("optional_else"),
if_stmt = con_if_begs/begin_if_fn * sp1
if_stmt = con_if_begs/on.if_begin * sp1
* Var("if_else_bodies")
* (Pat("")/end_if_else_fn),
* (Pat("")/on.if_else_end),
if_stmt2 = con_if_begs/begin_if_fn * sp1
* (-con_if_begs * Var("single_stmt") * (Pat("")/end_if_fn)
if_stmt2 = con_if_begs/on.if_begin * sp1
* (-con_if_begs * Var("single_stmt") * (Pat("")/on.if_end)
+ Var("if_else_bodies"))
* (Pat("")/end_if_else_fn),
* (Pat("")/on.if_else_end),
while_stmt = Keyw("whilevarvarn") * sp1 * tok.rvar * sp1 * tok.rvar / "while (%1~=%2) do"
* sp1 * Var("single_stmt") * lpeg.Cc("end")
+ Keyw("whilevarn") * sp1 * tok.rvar * sp1 * tok.define / "while (%1~=%2) do"
* sp1 * Var("single_stmt") * lpeg.Cc("end"),
while_stmt = Keyw("whilevarvarn") * sp1 * tok.rvar * sp1 * tok.rvar / on.while_begin
* sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end)
+ Keyw("whilevarn") * sp1 * tok.rvar * sp1 * tok.define / on.while_begin
* sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end),
stmt_common = Keyw("{") * sp1 * "}" -- space separation of commands in CON is for a reason!
+ Keyw("{") * sp1 * stmt_list * sp1 * "}"
@ -2803,6 +2837,8 @@ end
function on.parse_begin()
g_iflevel = 0
g_ifelselevel = 0
g_isWhile = {}
g_whilenum = 0
g_have_file[g_filename] = true
-- set up new state

View file

@ -0,0 +1,110 @@
gamevar j 0 0
gamevar k 0 0
definequote 400 QWE
definequote 500 ===
onevent EVENT_ENTERLEVEL
switch j
case 0
case -1
userquote 29
break
case 1
userquote 30
break
default
userquote 31
break
case 0
redefinequote 400 ASD
userquote 400
break
endswitch
addvar j 1
switch j
case 0
case -2
userquote 29
break
case 3
userquote 30
break
default
userquote 31
break
endswitch
// test "-" codegen (must not generate comment!).
subvar j -3
// result:
// ASD (from second case 0)
// BRIGHTNESS LEVEL: THREE (from default)
////////// test nested switch/while //////////
ifvare 0 1
break // outer level: "do return end"
setvar j 0
whilevarn j 3
{
addvar j 1
userquote 500
break // while is inner: "continue"
} // 3x "==="
setvar j 0
whilevarn j 1
{
switch j
case 0
addvar j 1
// switch is inner: "do return end" (likewise with following
// "break" commands):
break
addvar j 1
break
case 1
nullop
break
default
nullop
break
endswitch
}
// NOTE: this is syntactically invalid, though may be useful:
// case X:
// default:
// <code...>
setvar j 0
switch j
case 0,
whilevarn j 1
{
addvar j 1
break // while is inner: "continue"
}
break
endswitch
/*
// LunaCON only: nested switches.
// Untested, but the generated code looks sane.
switch j
case 0,
switch k
default
userquote 29 // BRIGHTNESS LEVEL: ONE
endswitch
case 1,
userquote 30
endswitch
*/
endevent