Lunatic translator: dangling else for the 1000th time: not dangling any more.

Also update test/dangling_else.con. See there for how if* cascades are handled
in CON. It's actually kind of pretty. Also, take care to handle code deferred
to after the if/if-else properly (ifpdistl, ifpdistg, ifcanseetartet).

git-svn-id: https://svn.eduke32.com/eduke32@3534 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-03-03 16:06:12 +00:00
parent 8955316bba
commit ba0970cede
2 changed files with 117 additions and 77 deletions

View file

@ -54,7 +54,7 @@ lpeg.setmaxstack(1024);
local Pat, Set, Range, Var = lpeg.P, lpeg.S, lpeg.R, lpeg.V local Pat, Set, Range, Var = lpeg.P, lpeg.S, lpeg.R, lpeg.V
local POS = lpeg.Cp local POS, Cc, Ctab = lpeg.Cp, lpeg.Cc, lpeg.Ct
-- CON language definitions (among other things, all keywords pattern). -- CON language definitions (among other things, all keywords pattern).
local conl = require("con_lang") local conl = require("con_lang")
@ -1597,9 +1597,9 @@ local handle =
-- readgamevar or savegamevar -- readgamevar or savegamevar
RSgamevar = function(identifier, dosave) RSgamevar = function(identifier, dosave)
-- check identifier for sanity -- check identifier for sanity
if (not identifier:match("^[A-Za-z][A-Za-z0-9_]*$")) then if (not identifier:match("^[A-Za-z][A-Za-z0-9_%-]*$")) then
errprintf("%s: invalid identifier name `%s' for config file persistence", errprintf("%s: bad identifier `%s' for config file persistence",
g_lastkw) g_lastkw, identifier)
return "_BADRSGV()" return "_BADRSGV()"
end end
@ -2207,10 +2207,10 @@ local Cif = {
ifrnd = cmd(D) ifrnd = cmd(D)
/ "_con.rnd(%1)", / "_con.rnd(%1)",
ifpdistl = cmd(D) ifpdistl = cmd(D) -- DEFER
/ function(val) return "_dist<"..val, "", "_con._sleepcheck(_aci,_dist)" end, / function(val) return { "_dist<"..val, nil, "_con._sleepcheck(_aci,_dist)" } end,
ifpdistg = cmd(D) ifpdistg = cmd(D) -- DEFER
/ function(val) return "_dist>"..val, "", "_con._sleepcheck(_aci,_dist)" end, / function(val) return { "_dist>"..val, nil, "_con._sleepcheck(_aci,_dist)" } end,
ifactioncount = cmd(D) ifactioncount = cmd(D)
/ ACS":get_acount()>=%1", / ACS":get_acount()>=%1",
ifcount = cmd(D) ifcount = cmd(D)
@ -2315,8 +2315,10 @@ local Cif = {
/ "false", -- TODO_MP / "false", -- TODO_MP
ifcanshoottarget = cmd() ifcanshoottarget = cmd()
/ "_con._canshoottarget(_dist,_aci)", / "_con._canshoottarget(_dist,_aci)",
ifcanseetarget = cmd() -- XXX: 1536 is SLEEPTIME ifcanseetarget = cmd() -- DEFER -- XXX: 1536 is SLEEPTIME
/ function() return format("_con._canseetarget(%s,%s)", SPS"", PLS""), ACS".timetosleep=1536" end, / function()
return { format("_con._canseetarget(%s,%s)", SPS"", PLS""), ACS".timetosleep=1536" }
end,
ifcansee = cmd() * #sp1 ifcansee = cmd() * #sp1
/ format("_con._cansee(_aci,%s)", PLS""), / format("_con._cansee(_aci,%s)", PLS""),
ifbulletnear = cmd() ifbulletnear = cmd()
@ -2467,7 +2469,7 @@ local function after_cmd_Cmt(subj, pos, ...)
return true -- don't return any captures return true -- don't return any captures
end end
-- attach the command names at the front! -- Attach the command names at the front!
local function attachnames(kwtab, matchtimefunc) local function attachnames(kwtab, matchtimefunc)
for cmdname,cmdpat in pairs(kwtab) do for cmdname,cmdpat in pairs(kwtab) do
-- The match-time function capture at the end is so that every command -- The match-time function capture at the end is so that every command
@ -2598,55 +2600,56 @@ local t_good_identifier = Range("AZ", "az", "__") * Range("AZ", "az", "__", "09"
local t_broken_identifier = BadIdent(-((tok.number + t_good_identifier) * (sp1 + Set("[]:"))) * local t_broken_identifier = BadIdent(-((tok.number + t_good_identifier) * (sp1 + Set("[]:"))) *
(alphanum + Set(BAD_ID_CHARS0)) * (alphanum + Set(BAD_ID_CHARS1))^0) (alphanum + Set(BAD_ID_CHARS0)) * (alphanum + Set(BAD_ID_CHARS1))^0)
-- These two tables hold code to be inserted at a later point: either at function on.if_else_end(ifconds, ifstmt, elsestmt, ...)
-- the end of the "if" body, or the end of the whole "if [else]" block. assert(#{...}==0)
-- For CON interpreter patterns like these: assert(type(ifconds)=="table" and #ifconds>=1)
-- VM_CONDITIONAL(<condition>);
-- <do_something_afterwards>
-- (Still not the same if the body returns or jumps out)
local g_endIfCode = {}
local g_endIfElseCode = {}
local function add_deferred_code(tab, lev, str) -- A condition may be a table carrying "deferred" code to add either
if (str ~= nil) then -- [1] after the 'if' or
assert(type(str)=="string") -- [2] after the whole if/else block.
tab[lev] = str -- In CON, it's always the same code for the same kind of "deferedness",
end -- and it's always idempotent (executing it multiple times has the same
end -- effect as executing it once), so generate code for it only once, too.
local deferred = { nil, nil }
local function get_deferred_code(tab, lev, code) local ifcondstr = {}
if (tab[lev]) then for i=1,#ifconds do
code = code..tab[lev] local cond = ifconds[i]
tab[lev] = nil local hasmore = type(cond=="table")
ifcondstr[i] = hasmore and cond[1] or cond
assert(type(ifcondstr[i])=="string")
if (hasmore) then
for i=1,2 do
if (deferred[i]==nil) then
deferred[i] = cond[i+1]
end
end
end
end end
-- Construct a string of ANDed conditions
local conds = "(" .. table.concat(ifcondstr, ")and(") .. ")"
local code = {
format("if %s then", conds),
ifstmt,
}
code[#code+1] = deferred[1]
if (elsestmt~=nil) then
code[#code+1] = "else"
code[#code+1] = elsestmt
end
code[#code+1] = "end"
code[#code+1] = deferred[2]
return code return code
end end
function on.if_begin(condstr, endifstr, endifelsestr)
assert(type(condstr)=="string")
add_deferred_code(g_endIfCode, g_iflevel, endifstr)
add_deferred_code(g_endIfElseCode, g_ifelselevel, endifelsestr)
g_iflevel = g_iflevel+1
g_ifelselevel = g_ifelselevel+1
return format("if (%s) then", condstr)
end
function on.if_end()
g_iflevel = g_iflevel-1
local code = get_deferred_code(g_endIfCode, g_iflevel, "")
if (code ~= "") then
return code
end
end
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) function on.while_begin(v1, v2)
table.insert(g_isWhile, true) table.insert(g_isWhile, true)
return format("while (%s~=%s) do", v1, v2) return format("while (%s~=%s) do", v1, v2)
@ -2788,18 +2791,8 @@ local Grammar = Pat{
default_block = lpeg.Ct(sp1 * Keyw("default") * (sp0*":"*sp0 + sp1) * default_block = lpeg.Ct(sp1 * Keyw("default") * (sp0*":"*sp0 + sp1) *
stmt_list_nosp_or_eps), -- * "break", stmt_list_nosp_or_eps), -- * "break",
optional_else = (sp1 * lpeg.C("else") * sp1 * Var("single_stmt"))^-1, if_stmt = lpeg.Ct((con_if_begs * sp1)^1) * Var("single_stmt")
* (sp1 * Keyw("else") * sp1 * Var("single_stmt"))^-1 / on.if_else_end,
if_else_bodies = Var("single_stmt2") * (Pat("")/on.if_end) * Var("optional_else"),
if_stmt = con_if_begs/on.if_begin * sp1
* Var("if_else_bodies")
* (Pat("")/on.if_else_end),
if_stmt2 = con_if_begs/on.if_begin * sp1
* (-con_if_begs * Var("single_stmt") * (Pat("")/on.if_end)
+ Var("if_else_bodies"))
* (Pat("")/on.if_else_end),
while_stmt = Keyw("whilevarvarn") * sp1 * tok.rvar * sp1 * tok.rvar / on.while_begin while_stmt = Keyw("whilevarvarn") * sp1 * tok.rvar * sp1 * tok.rvar / on.while_begin
* sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end) * sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end)
@ -2807,11 +2800,10 @@ local Grammar = Pat{
* sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end), * sp1 * Var("single_stmt") * (lpeg.Cc(nil) / on.while_end),
stmt_common = Keyw("{") * sp1 * "}" -- space separation of commands in CON is for a reason! stmt_common = Keyw("{") * sp1 * "}" -- space separation of commands in CON is for a reason!
+ Keyw("{") * sp1 * stmt_list * sp1 * "}" + Keyw("{") * sp1 * lpeg.Ct(stmt_list) * sp1 * "}"
+ con_inner_command + Var("switch_stmt") + Var("while_stmt"), + con_inner_command + Var("switch_stmt") + lpeg.Ct(Var("while_stmt")),
single_stmt = Stmt( lone_else^-1 * (Var("stmt_common") + Var("if_stmt")) ), single_stmt = Stmt( lone_else^-1 * (Var("stmt_common") + Var("if_stmt")) ),
single_stmt2 = Stmt( lone_else^-1 * (Var("stmt_common") + Var("if_stmt2")) ),
-- a non-empty statement/command list -- a non-empty statement/command list
stmt_list = Var("single_stmt") * (sp1 * Var("single_stmt"))^0, stmt_list = Var("single_stmt") * (sp1 * Var("single_stmt"))^0,

View file

@ -1,10 +1,58 @@
// a dangling 'else' in CON attaches to the outermost matching 'if' // In CON, the dangling else ambiguity is resolved in a very peculiar way:
onevent EVENT_INIT // A cascade "ifP1 ifP2 ifP3 Stmt1 ... else Stmt2" is considered as if the
redefinequote 114 DANGLING ELSE CHECK FAILED 1 // predicates P1, P2, P3, ... are combined with a logical AND.
ifvare 0 1 // So effectively, there is no ambiguity at all, and one of Stmt1 and Stmt2
ifvare 0 0 redefinequote 114 DANGLING ELSE CHECK FAILED 2 // is guaranteed to be taken.
else
redefinequote 114 DANGLING ELSE CHECK PASSED
echo 114 define Q 114
define GLOBAL 0
gamevar x 0 GLOBAL
// divisible by 2, 3, 5?
gamevar xm2 0 GLOBAL
gamevar xm3 0 GLOBAL
gamevar xm5 0 GLOBAL
state testelse
setvarvar xm2 x, modvar xm2 2
setvarvar xm3 x, modvar xm3 3
setvarvar xm5 x, modvar xm5 5
redefinequote Q ERROR: one path of if/else must be taken
ifvarn xm2 0
ifvarn xm3 0
ifvarn xm5 0
redefinequote Q x=%d is not divisible by either 2, 3 or 5
else
redefinequote Q x=%d is divisible by either 2, 3 or 5
qsprintf Q Q x
userquote Q
ends
onevent EVENT_ENTERLEVEL
setvar x 0
whilevarn x 31
{
state testelse
addvar x 1
}
endevent
// Test deferred code. This CON code doesn't make much sense, but check out
// the generated Lua code...
onevent EVENT_JUMP
ifpdistl 100 ifpdistg 0 ifcanseetarget
{
palfrom 32 32 32
ifcanseetarget
quote 29
ifcanseetarget
quote 30
setvar x 0
state testelse
}
else ifcanseetarget
quote 31
endevent endevent