code:
-- This file contains LuaJIT definitions of stuff that's common to the game and
-- editor.  The 'decl' function is expected to be defined in the global
-- environment.
-- See the file "BUILDLIC.TXT" distributed with EDuke32 for license info.

-- Will be 'true' if running from engine Lua state:
local _LUNATIC_AUX = _LUNATIC_AUX

local ffi = require("ffi")
local ffiC = ffi.C
local bit = require("bit")

local bor = bit.bor
local pcall = pcall

ffi.cdef "const char **g_argv;"

-- Lunatic debugging options (-Lopts=<opt1>,<opt2>,... from the command line):
--     diag: print diagnostic information
--    nojit: disable JIT compilation
--   traces: load LuaJIT's 'v' module, printing trace info
--           (env var: LUAJIT_VERBOSEFILE)
--     dump: load LuaJIT's 'dump' module, printing generated IR/machine code
--           (env var: LUAJIT_DUMPFILE)
--  profile: load LuaJIT's 'jit.p' module for profiling (LuaJIT 2.1 only)
--           (env var: LUAJIT_PROFILEFILE)
--   strict: catch various conditions that may indicate a logical error
-- TODO for strict: actor[], spriteext[], per-actor gamevars
local debug_flags = {}
local IS_DEBUG_FLAG = {
    diag=true, nojit=true, traces=true, dump=true,
    strict=true, profile=true,
}

-- Handle command-line argument. (Look for -Lopts=...)
local function handle_cmdline_arg(str)
    local opts = str:match("^-Lopts=(.*)")

    if (opts ~= nil) then
        for opt in opts:gmatch("[^,]+") do
            if (IS_DEBUG_FLAG[opt]) then
                debug_flags[opt] = true
            end
        end
    end
end

local i=0
while (ffiC.g_argv[i] ~= nil) do
    handle_cmdline_arg(ffi.string(ffiC.g_argv[i]))
    i = i+1
end

-- Print diagnostic information?
ffi.cdef("enum { _DEBUG_LUNATIC="..(debug_flags.diag and 1 or 0).." }")
-- Be strict?
ffi.cdef("enum { _LUNATIC_STRICT="..(debug_flags.strict and 1 or 0).." }")

if (debug_flags.nojit) then
    require("jit").off()
end

if (not _LUNATIC_AUX) then
    if (debug_flags.dump) then
        require("dump").on("+rs")
    elseif (debug_flags.traces) then
        require("v").on()
    end

    if (debug_flags.profile) then
        if (pcall(function() require("jit.p").start() end) == false) then
            print("Warning: failed enabing profiler. Running LuaJIT 2.0 build?")
        end
    end
end

local math = require("math")
local string = require("string")
local table = require("table")

local assert = assert
local error = error
local pairs = pairs
local require = require
local setmetatable = setmetatable
local tostring = tostring
local type = type

local decl = assert(decl)
local getfenv = getfenv

decl "void OSD_Printf(const char *fmt, ...);"
print = function(str)
    -- our "print" doesn't use the global "tostring", but the initial one
    str = tostring(str)
    if (type(str) ~= "string") then
        error("invalid argument to print: must be convertible to a string")
    end
    ffiC.OSD_Printf("%s\n", str)
end

local print=print

module(...)

local band = bit.band
local bor = bit.bor
local bnot = bit.bnot
local lshift = bit.lshift
local rshift = bit.rshift
local xor = bit.bxor

--== bitint type factory ==--

-- Metatable for an integer type that is treated as bitfield. The integer
-- itself must be named '_v'.
local bitint_mt = {
    __index = {
        set = function(self, bits)
            self._v = bor(self._v, bits)
        end,

        clear = function(self, bits)
            self._v = band(self._v, bnot(bits))
        end,

        flip = function(self, bits)
            self._v = xor(self._v, bits)
        end,

        test = function(self, bits)
            return (band(self._v, bits) ~= 0)
        end,

        mask = function(self, bits)
            return band(self._v, bits)
        end,
    },

    __metatable = true,
}

local bitint_to_base_type = {}

function bitint_new_struct_type(basetypename, newtypename)
    assert(bitint_to_base_type[newtypename] == nil)
    assert(type(basetypename)=="string")
    assert(type(newtypename)=="string")

    local bitint_struct_t = ffi.typeof("struct { $ _v; }", ffi.typeof(basetypename))
    ffi.metatype(bitint_struct_t, bitint_mt)
    ffi.cdef("typedef $ $;", bitint_struct_t, newtypename)

    bitint_to_base_type[newtypename] = basetypename
end

function bitint_member(bitint_struct_typename, membname)
    return string.format("union { %s %s; %s %sbits; };",
                         bitint_to_base_type[bitint_struct_typename], membname,
                         bitint_struct_typename, membname)
end

bitint_new_struct_type("uint8_t", "UBit8")
bitint_new_struct_type("uint16_t", "UBit16")

-- Converts a template struct definition to an internal, unrestricted one.
-- NOTE: "[^ ]*" for const decorations in _defs_game.lua.
function strip_const(structstr)
    return (string.gsub(structstr, "const[^ ]* ", ""));
end

local function maybe_strip_const(str)
    return _LUNATIC_AUX and strip_const(str) or str
end

--== Core engine structs ==--

local CF_MEMBERS = [[
    const int16_t ~picnum;
    int16_t ~heinum;
    const int16_t ~bunch;
]]..bitint_member("UBit16", "~stat")..[[
    int32_t ~z;
    int8_t ~shade;
    uint8_t ~pal, ~xpanning, ~ypanning;
]]

ffi.cdef("typedef struct { "..CF_MEMBERS:gsub("~","").." } ceiling_or_floor_t;")

local hplane_ptr_ct = ffi.typeof("struct { "..strip_const(CF_MEMBERS:gsub("~","")).." } *")

local SECTOR_STRUCT = [[
struct {
    const int16_t wallptr, wallnum;
]]..
string.format([[
    union {
      struct { ceiling_or_floor_t ceiling, floor; };
      struct { %s %s };
    };
]], CF_MEMBERS:gsub("~","ceiling"), CF_MEMBERS:gsub("~","floor"))
..[[
    uint8_t visibility, fogpal;
    int16_t lotag, hitag;  // NOTE: signed for Lunatic
    int16_t extra;
}]]

local SPRITE_STRUCT = [[
struct {
    // TODO: transparent union with vec3_t pos?
    int32_t x, y, z;
]]..bitint_member("UBit16", "cstat")..[[
    const int16_t picnum;
    int8_t shade;
    uint8_t pal, clipdist, blend;
    uint8_t xrepeat, yrepeat;
    int8_t xoffset, yoffset;
    const int16_t sectnum, statnum;
    int16_t ang;

    const int16_t owner;
    int16_t xvel;
    // NOTE: yvel is often used as player index in game code.
    const int16_t yvel;
    int16_t zvel;

    int16_t lotag, hitag, extra;
}]]

local WALL_STRUCT = [[
struct {
    int32_t x, y;
    const int16_t point2, nextwall, nextsector;
    const int16_t upwall, dnwall;
]]..bitint_member("UBit16", "cstat")..[[
    const int16_t picnum, overpicnum;
    int8_t shade;
    uint8_t pal, xrepeat, yrepeat, xpanning, ypanning;
    int16_t lotag, hitag, extra;
    uint8_t blend, _filler;
}]]

-- NOTE for FFI definitions: we're compiling EDuke32 with -funsigned-char, so
-- we need to take care to declare chars as unsigned whenever it matters, for
-- example if it represents a palette index.  (I think it's harmless for stuff
-- like passing a function argument, but it should be done there for clarity.)

-- TODO: provide getters for unsigned {hi,lo}tag?
ffi.cdef([[
typedef $ sectortype;
typedef $ walltype;
// NOTE: spritetype and uspritetype are different types with the same data members.
typedef $ spritetype;
typedef struct { spritetype; } uspritetype;

typedef struct {
    int32_t x, y;
} vec2_t;

typedef struct {
    int32_t x, y, z;
} vec3_t;

typedef struct {
    const uint32_t mdanimtims;
    const int16_t mdanimcur;
    int16_t angoff, pitch, roll;
    vec3_t mdoff;
]]..bitint_member("UBit8", "flags")..[[
    uint8_t xpanning, ypanning;
    const uint8_t filler;
    float alpha;
    // NOTE: const aggregate fixed with LuaJIT git fe9934feea0a8d580de1
    // ("FFI: Fix handling of qualified transparent structs/unions.")
    const union {
        intptr_t _tspr;
        struct { int32_t _dummy0, _dummy1; };
    };
} spriteext_t;

typedef struct {
    vec3_t pos;
    int16_t sprite, wall, sector;
} hitdata_t;
]],
ffi.typeof(maybe_strip_const(SECTOR_STRUCT)),
ffi.typeof(maybe_strip_const(WALL_STRUCT)),
ffi.typeof(maybe_strip_const(SPRITE_STRUCT)))

if (not _LUNATIC_AUX) then
    -- Define the "palette_t" type, which for us has .{r,g,b} fields and a
    -- bound-checking array of length 3 overlaid.
    require("bcarray").new("uint8_t", 3, "RGB array", "palette_t",
                           { "r", "g", "b", "f" })
    assert(ffi.alignof("palette_t")==1)
end

local vec3_ct = ffi.typeof("vec3_t")  -- will be metatype'd in xmath.lua:

if (not _LUNATIC_AUX) then
    require("xmath")
end

-- TODO: 'isceiling' and 'isfloor' methods or similar?
local hitdata_ct = ffi.typeof("hitdata_t")

decl[[
const int32_t engine_main_arrays_are_static;
const int32_t engine_v8;
]]

--== Engine data and functions ==--

-- NOTE TO SELF: This is not C, never EVER write
--    if (x)
-- when checking a C variable x for 'thuthiness'
if (ffiC.engine_main_arrays_are_static ~= 0) then
    decl[[
    sectortype sector[];
    walltype wall[];
    spritetype sprite[];
    uspritetype tsprite[];
    spriteext_t spriteext[];
    ]]
else
    decl[[
    sectortype *sector;
    walltype *wall;
    spritetype *sprite;
    uspritetype *tsprite;
    spriteext_t *spriteext;
    ]]
end

if (ffiC.engine_v8 == 0) then
    -- V7
    ffi.cdef[[
enum
{
    MAXSECTORS = 1024,
    MAXWALLS = 8192,
    MAXSPRITES = 4096,
}
]]
else
    -- V8
    ffi.cdef[[
enum
{
    MAXSECTORS = 4096,
    MAXWALLS = 16384,
    MAXSPRITES = 16384,
}
]]
end

ffi.cdef[[
enum {
    MAXSTATUS = 1024,
    MAXTILES = 30720,
    MAXSPRITESONSCREEN = 4096,

    MAXBUNCHES = 512,
    CEILING = 0,  // must be 0
    FLOOR = 1,  // must be 1
    BOTH_CF = 2,

    CLIPMASK0 = (1<<16)+1,  // blocking
    CLIPMASK1 = (256<<16)+64,  // hittable
};
]]

ffi.cdef(maybe_strip_const("const int16_t numsectors, numwalls;"))

ffi.cdef[[
const int32_t Numsprites;
const int32_t numyaxbunches;  // XXX
const int32_t totalclock;
int32_t randomseed;  // DEPRECATED
const int32_t xdim, ydim;
const vec2_t windowxy1, windowxy2;
]]

decl[[
int32_t kopen4load(const char *filename, char searchfirst);
int32_t kfilelength(int32_t handle);
void kclose(int32_t handle);
int32_t kread(int32_t handle, void *buffer, int32_t leng);
int32_t klseek(int32_t handle, int32_t offset, int32_t whence);

int32_t sectorofwall_noquick(int16_t theline);
]]

-- Reads the whole file given by the k* file descriptor into a Lua string.
-- Does not close the file descriptor.
function readintostr(fd, kopen4load_func)
    -- XXX: this is pretty much the same as the code in L_RunOnce()

    local sz = ffiC.kfilelength(fd)
    if (sz == 0) then
        return ""
    end

    if (sz < 0) then
        error("INTERNAL ERROR: kfilelength() returned negative length")
    end

    local str = ffi.new("char [?]", sz)
    local readlen = ffiC.kread(fd, str, sz)

    if (readlen ~= sz) then
        error("INTERNAL ERROR: couldn't read file wholly")
    end

    return ffi.string(str, sz)
end

if (_LUNATIC_AUX) then
    -- XXX: The global doesn't show up in 'engine_maptext'.
    -- I guess I still haven't fully grokked globals in Lua.
    string.readintostr = readintostr
    require "engine_maptext"
    return
end

ffi.cdef "const int32_t rendmode;"

decl[[
int32_t yxaspect;
int32_t viewingrange;
int32_t spritesortcnt;
int32_t guniqhudid;
const int16_t headspritesect[MAXSECTORS+1], headspritestat[MAXSTATUS+1];
const int16_t prevspritesect[MAXSPRITES], prevspritestat[MAXSPRITES];
const int16_t nextspritesect[MAXSPRITES], nextspritestat[MAXSPRITES];
const vec2_t tilesiz[MAXTILES];
typedef struct {
    uint8_t num;  // animate number
    int8_t xofs, yofs;
    uint8_t sf;  // anim. speed and flags
} picanm_t;
const picanm_t picanm[MAXTILES];

uint8_t show2dsector[(MAXSECTORS+7)>>3];

const int16_t headsectbunch[2][MAXBUNCHES], nextsectbunch[2][MAXSECTORS];

int16_t yax_getbunch(int16_t i, int16_t cf);

int32_t   getceilzofslopeptr(const sectortype *sec, int32_t dax, int32_t day);
int32_t   getflorzofslopeptr(const sectortype *sec, int32_t dax, int32_t day);
void   getzsofslopeptr(const sectortype *sec, int32_t dax, int32_t day,
                       int32_t *ceilz, int32_t *florz);
int32_t spriteheightofsptr(const spritetype *spr, int32_t *height, int32_t alsotileyofs);

int32_t changespritesect(int16_t spritenum, int16_t newsectnum);
int32_t changespritestat(int16_t spritenum, int16_t newstatnum);

int32_t hitscan(const vec3_t *sv, int16_t sectnum, int32_t vx, int32_t vy, int32_t vz,
                hitdata_t *hitinfo, uint32_t cliptype);
int32_t cansee(int32_t x1, int32_t y1, int32_t z1, int16_t sect1,
               int32_t x2, int32_t y2, int32_t z2, int16_t sect2);
void neartag(int32_t xs, int32_t ys, int32_t zs, int16_t sectnum, int16_t ange, int16_t *neartagsector, int16_t *neartagwall,
             int16_t *neartagsprite, int32_t *neartaghitdist, int32_t neartagrange, uint8_t tagsearch,
             int32_t (*blacklist_sprite_func)(int32_t));
void dragpoint(int16_t pointhighlight, int32_t dax, int32_t day, uint8_t flags);
void getzrange(const vec3_t *pos, int16_t sectnum,
               int32_t *ceilz, int32_t *ceilhit, int32_t *florz, int32_t *florhit,
               int32_t walldist, uint32_t cliptype);
int32_t clipmovex(vec3_t *pos, int16_t *sectnum, int32_t xvect, int32_t yvect,
                  int32_t walldist, int32_t ceildist, int32_t flordist, uint32_t cliptype,
                  uint8_t noslidep);

int32_t ldist(const spritetype *s1, const spritetype *s2);
int32_t dist(const spritetype *s1, const spritetype *s2);

int32_t inside(int32_t x, int32_t y, int16_t sectnum);
void updatesector(int32_t x, int32_t y, int16_t *sectnum);
void updatesectorbreadth(int32_t x, int32_t y, int16_t *sectnum);
void updatesectorz(int32_t x, int32_t y, int32_t z, int16_t *sectnum);

void rotatesprite_(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum,
                   int8_t dashade, unsigned char dapalnum, int32_t dastat, uint8_t alpha, uint8_t dablend,
                   int32_t cx1, int32_t cy1, int32_t cx2, int32_t cy2);

void setaspect(int32_t daxrange, int32_t daaspect);
]]

-- misc. functions
ffi.cdef[[
uint32_t getticks(void);
double gethiticks(void);

int32_t krand(void);
int32_t ksqrt(uint32_t num);
int32_t getangle(int32_t xvect, int32_t yvect);
int32_t Mulscale(int32_t a, int32_t b, int32_t sh);
]]

local bcheck = require("bcheck")
local check_sector_idx = bcheck.sector_idx
local check_wall_idx = bcheck.wall_idx
local check_sprite_idx = bcheck.sprite_idx
local check_tile_idx = bcheck.tile_idx

local wallsofsec  -- fwd-decl

local sectortype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(SECTOR_STRUCT)))

local function get_sector_idx(sec)
    local i = ffi.cast(sectortype_ptr_ct, sec)-ffi.cast(sectortype_ptr_ct, ffiC.sector)
--    assert(i >= 0 and i < ffiC.numsectors)
    return i
end

local zret = ffi.new("int32_t [4]")
local zret_t = ffi.typeof[[const struct {
    struct {
        bool spritep;
        int32_t num;  // number of sector or sprite
        int32_t z;
    } c, f;
}]]

local sectortype_mt = {
    __index = {
        --- Setters
        set_ceilingpicnum = function(s, picnum)
            check_tile_idx(picnum)
            ffi.cast(sectortype_ptr_ct, s).ceilingpicnum = picnum
        end,

        set_floorpicnum = function(s, picnum)
            check_tile_idx(picnum)
            ffi.cast(sectortype_ptr_ct, s).floorpicnum = picnum
        end,

        --- Other methods
        ceilingzat = function(s, pos)
            return ffiC.getceilzofslopeptr(s, pos.x, pos.y)
        end,

        floorzat = function(s, pos)
            return ffiC.getflorzofslopeptr(s, pos.x, pos.y)
        end,

        -- getzrange() interface
        zrangeat = function(s, pos, walldist, cliptype)
            local sectnum = get_sector_idx(s)
            local ipos = vec3_ct(pos.x, pos.y, pos.z)
            walldist = walldist or 128
            cliptype = cliptype or ffiC.CLIPMASK0

            ffiC.getzrange(ipos, sectnum, zret+0, zret+1, zret+2, zret+3,
                           walldist, cliptype)
            local ceilz, ceilhit, florz, florhit = zret[0], zret[1], zret[2], zret[3]

            return zret_t({ ceilhit>=49152, bit.band(ceilhit,16383), ceilz },
                          { florhit>=49152, bit.band(florhit,16383), florz })
        end,

        -- inside() port, OUTOFSYNC with engine.c
        contains = function(s, pos)
            local x, y = pos.x, pos.y
            local cnt = 0

            for w in wallsofsec(s) do
                local wal2 = ffiC.wall[ffiC.wall[w].point2]
                local y1, y2 = ffiC.wall[w].y-y, wal2.y-y

                if (xor(y1, y2) < 0) then
                    local x1, x2 = ffiC.wall[w].x-x, wal2.x-x

                    if (xor(x1, x2)>=0) then
                        cnt = xor(cnt, x1)
                    else
                        cnt = xor(cnt, xor(x1*y2-x2*y1, y2))
                    end
                end
            end

            return (cnt < 0)
        end,
    }
}
ffi.metatype("sectortype", sectortype_mt)

local hplane_mt = {
    __index = {
        set_picnum = function(hp, picnum)
            check_tile_idx(picnum)
            ffi.cast(hplane_ptr_ct, hp).picnum = picnum
        end
    },
}
ffi.metatype("ceiling_or_floor_t", hplane_mt)

local walltype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(WALL_STRUCT)))

local walltype_mt = {
    __index = {
        --- Setters
        set_picnum = function(w, picnum)
            check_tile_idx(picnum)
            ffi.cast(walltype_ptr_ct, w).picnum = picnum
        end,

        set_overpicnum = function(w, picnum)
            check_tile_idx(picnum)
            ffi.cast(walltype_ptr_ct, w).overpicnum = picnum
        end,

        _set_nextwall = function(w, nextwall)
            -- NOTE: Allow setting a wall to white too, but no checking of the
            -- consistency invariant ".nextwall>=0 iff .nextsector>=0".
            if (not (nextwall < 0)) then
                check_wall_idx(nextwall)
            end
            ffi.cast(walltype_ptr_ct, w).nextwall = nextwall
        end,

        _set_nextsector = function(w, nextsector)
            if (not (nextsector < 0)) then
                check_sector_idx(nextsector)
            end
            ffi.cast(walltype_ptr_ct, w).nextsector = nextsector
        end,

        --- Predicates
        isblocking = function(w)
            return (band(w.cstat, 1)~=0)
        end,

        ismasked = function(w)
            return (band(w.cstat, 16)~=0)
        end,

        isoneway = function(w)
            return (band(w.cstat, 32)~=0)
        end,

        ishittable = function(w)
            return (band(w.cstat, 64)~=0)
        end,

        -- Indexing a wall with 'z' gets 0, so that you can e.g. use a wall as
        -- RHS to vec3_t addition.
        z = 0,
    }
}
ffi.metatype("walltype", walltype_mt)

local spriteext_mt = {
    __index = {
        -- Enable EVENT_ANIMATESPRITES for this sprite.
        -- XXX: unused?
        make_animated = function(sx)
            sx.flags = bor(sx.flags, 16)
        end,
    },
}
ffi.metatype("spriteext_t", spriteext_mt)

local spritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(SPRITE_STRUCT)))
-- NOTE: this is the *protected* uspritetype pointer.
local tspritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof("uspritetype"))

local intarg = ffi.new("int32_t[1]")

-- XXX: We ought to be using the dynamic value, but users remapping that tile
-- are likely somewhat confused anyway. Also, it would be more proper if this
-- lived on game-side Lunatic.
local APLAYER = 1405

local spritetype_mt = {
    __pow = function(s, zofs)
        return vec3_ct(s.x, s.y, s.z-zofs)
    end,

    __index = {
        --- Setters
        set_picnum = function(s, tilenum)
            if (s.picnum == APLAYER or tilenum == APLAYER) then
                error("setting picnum to or on an APLAYER sprite forbidden")
            end
            check_tile_idx(tilenum)
            ffi.cast(spritetype_ptr_ct, s).picnum = tilenum
        end,

        set_yvel = function(s, yvel)
            -- XXX: A malicious user might still find some backdoor way to
            --  inject a bad yvel value, but this together with the above
            --  set_picnum() check should at least prevent naive attempts.
            if (s.picnum == APLAYER) then
                error("setting yvel on an APLAYER sprite forbidden", 2)
            end
            ffi.cast(spritetype_ptr_ct, s).yvel = yvel
        end,

        _set_owner = function(s, owner)
            -- XXX: AMC TC sets owner to -1 in the cutscene.
            check_sprite_idx(owner)
            ffi.cast(spritetype_ptr_ct, s).owner = owner
        end,

        --- Custom methods ---

        getheightofs = function(s)
            -- XXX: better reimplement in Lua?
            local zofs = ffiC.spriteheightofsptr(s, intarg, 0)
            return intarg[0], zofs
        end,
    },
}

local function deep_copy(tab)
    local ntab = {}
    for key, val in pairs(tab) do
        if (type(val)=="table") then
            ntab[key] = deep_copy(val)
        else
            assert(type(val)=="function")
            ntab[key] = val
        end
    end
    return ntab
end

local tspritetype_mt = deep_copy(spritetype_mt)

-- Get the sprite index of a sprite reference.
-- This is relatively slow if the code doesn't get compiled, see related
-- discussion here:
-- http://www.freelists.org/post/luajit/FFI-versus-Lua-C-API-in-purely-interpreted-mode
local function get_sprite_idx(spr)
    local i = ffi.cast(spritetype_ptr_ct, spr)-ffi.cast(spritetype_ptr_ct, ffiC.sprite)
--    assert(i >= 0 and i < ffiC.MAXSPRITES)
    return i
end

---=== Methods that are specific to sprites ===---

local l_updatesector  -- fwd-decl
local l_changesect  -- fwd-decl

-- The 'setpos' method is available for sprite and tsprite objects.
-- spr:setpos(pos [, newsect]),
-- where <newsect> is taken to mean "set the sprite's sector number to <newsect>";
-- don't run update/search routines or anything like this.
function spritetype_mt.__index.setpos(spr, pos, newsect)
    spr.x, spr.y, spr.z = pos.x, pos.y, pos.z
    if (newsect ~= nil) then
        spr:changesect(newsect)
    end
    return spr
end

-- spr:changesect(newsect)
-- changes the sector number of <spr> in an 'unforgiving' fashion (<newsect> of
-- -1 is error)
function spritetype_mt.__index.changesect(spr, newsect)
    if (newsect ~= spr.sectnum) then
        l_changesect(get_sprite_idx(spr), newsect)
    end
end

-- spr:updatesect(flags)
-- updates <spr>'s sectnum; if no matching sector is found, no-op
-- returns the updated sector number
function spritetype_mt.__index.updatesect(spr, flags)
    local newsect = l_updatesector(spr, spr.sectnum, flags)
    if (newsect ~= -1) then
        spr:changesect(newsect)
    end
    return newsect
end

---=== Methods that are specific to tsprites ===---

-- This ought to be called 'set_sectnum', but 'changesect' is for consistency
-- with the sprite method.
function tspritetype_mt.__index.changesect(tspr, sectnum)
    if (tspr.sectnum ~= sectnum) then
        check_sector_idx(sectnum)
        ffi.cast(spritetype_ptr_ct, tspr).sectnum = sectnum
    end
end

function tspritetype_mt.__index.setpos(tspr, pos, newsect)
    tspr.x, tspr.y, tspr.z = pos.x, pos.y, pos.z
    if (newsect ~= nil) then
        tspr:changesect(newsect)
    end
    return tspr
end

function tspritetype_mt.__index.updatesect(tspr, flags)
    local newsect = l_updatesector(tspr, tspr.sectnum, flags)
    if (newsect ~= -1 and newsect ~= tspr.sectnum) then
        tspr:changesect(newsect)
    end
    return newsect
end

function tspritetype_mt.__index.dup(tspr)
    if (ffiC.spritesortcnt >= ffiC.MAXSPRITESONSCREEN+0ULL) then
        return nil
    end

    local newtspr = ffiC.tsprite[ffiC.spritesortcnt]
    ffi.copy(newtspr, tspr, ffi.sizeof(tspr))
    ffiC.spritesortcnt = ffiC.spritesortcnt+1

    return newtspr
end

function tspritetype_mt.__index.getspr(tspr)
    check_sprite_idx(tspr.owner)
    return ffiC.sprite[tspr.owner]
end

---======---

-- The user of this module can insert additional "spritetype" index
-- methods and register them with "ffi.metatype".
function finish_spritetype(mt_index)
    for name, func in pairs(mt_index) do
        spritetype_mt.__index[name] = func
        tspritetype_mt.__index[name] = func
    end
    ffi.metatype("spritetype", spritetype_mt)
    ffi.metatype("uspritetype", tspritetype_mt)
end

---=== Restricted access to C variables from Lunatic ===---

-- set metatable and forbid setting it further
function setmtonce(tab, mt)
    mt.__metatable = true
    return setmetatable(tab, mt)
end

---- indirect C array access ----

-- Create a safe indirection for an ffi.C array.
function creategtab(ctab, maxidx, name)
    local tab = {}
    local tmpmt = {
        __index = function(tab, key)
            if (key>=0 and key < maxidx) then
                return ctab[key]
            end
            error('out-of-bounds '..name..' read access', 2)
        end,
        __newindex = function()
            error('cannot write directly to '..name, 2)
        end,
    }

    return setmtonce(tab, tmpmt)
end

-- Create a a safe indirection for an ffi.C struct array, accessing a given
-- member.
function creategtab_membidx(ctab, membname, maxidx, name)
    local tab = {}
    local tmpmt = {
        __index = function(tab, key)
            if (key>=0 and key < maxidx) then
                return ctab[key][membname]
            end
            error('out-of-bounds '..name..' read access', 2)
        end,
        __newindex = function()
            error('cannot write directly to '..name, 2)
        end,
    }

    return setmtonce(tab, tmpmt)
end

-- Create a a safe indirection for an ffi.C struct array, accessing a given
-- pointer member, which either points to one element, or is NULL.
function creategtab_membidx_ptr(ctab, membname, maxidx, name)
    local tab = {}
    local tmpmt = {
        __index = function(tab, key)
            if (key>=0 and key < maxidx) then
                local ptr = ctab[key][membname]
                if (ptr ~= nil) then
                    return ctab[key][membname][0]
                end
                return nil
--                error(name .. '[' .. key .. '] is null', 2)
            end
            error('out-of-bounds '..name..'[] read access', 2)
        end,
        __newindex = function()
            error('cannot write directly to '..name..'[]', 2)
        end,
    }

    return setmtonce(tab, tmpmt)
end

-- Construct const struct from table
function conststruct(tab)
    local strtab = { "struct {" }

    if (tab[1] ~= nil) then
        -- e.g. { "KNEE", "PISTOL", ... } provided
        for i=1,#tab do
            strtab[#strtab+1] = "static const int "..tab[i].."="..(i-1)..";"
        end
    else
        -- e.g. { KNEE=0, PISTOL=1, ... } provided
        for member, val in pairs(tab) do
            strtab[#strtab+1] = "static const int "..member.."="..val..";"
        end
    end
    strtab[#strtab+1] = "}"

    return ffi.new(table.concat(strtab))
end

do
    local smt_mt = {
        __index = function()
            error("invalid access to static data", 2)
        end
    }

    function static_members_tab()
        return setmtonce({}, smt_mt)
    end
end

-- Static, non-instance members. Used to hold constants, for example
-- sprite.CSTAT.TRANS1
local static_members = {
    sector = static_members_tab(),
    wall = static_members_tab(),
    sprite = static_members_tab(),
}

static_members.sector.STAT = conststruct
{
    PARALLAX = 1,
    SLOPE = 2,
    SWAPXY = 4,
    SMOOSH = 8,
    FLIPX = 16,
    FLIPY = 32,
    RELATIVE = 64,
    MASK = 128,
    -- NOTE the reversed order
    TRANS2 = 128,
    TRANS1 = 256,
    BLOCK = 512,
    HITSCAN = 2048,

    FLIP_BITMASK = 16+32,
    ORIENT_BITMASK = 4+16+32,
    TRANS_BITMASK = 128+256,
}

static_members.sector.NEARTAG_FLAGS = conststruct
{
    LOTAG = 1,
    HITAG = 2,
    NOSPRITES = 4,
}

static_members.sector.UPDATE_FLAGS = conststruct
{
    BREADTH = 1,
    Z = 2,
}

static_members.wall.CSTAT = conststruct
{
    BLOCK = 1,
    BOTTOMSWAP = 2,
    ALIGNBOTTOM = 4,
    FLIPX = 8,
    MASK = 16,
    ONEWAY = 32,
    HITSCAN = 64,
    TRANS1 = 128,
    FLIPY = 256,
    TRANS2 = 512,

    FLIP_BITMASK = 8+256,
    TRANS_BITMASK = 128+512,
}

static_members.sprite.CSTAT = conststruct
{
    BLOCK = 1,
    TRANS1 = 2,
    XFLIP = 4,
    YFLIP = 8,
    ALIGNWALL = 16,
    ALIGNFLOOR = 32,
    ONESIDE = 64,
    CENTER = 128,
    HITSCAN = 256,
    TRANS2 = 512,

    ALIGN_BITMASK = 16+32,
    TRANS_BITMASK = 2+512,

    INVISIBLE = 32768,
}

local bitar = require("bitar")
-- XXX: bitar uses int32_t arrays, while show2dsector[] is a uint8_t
-- array. Potential unaligned access. Also, only works on little-endian
-- machines. This sucks.
static_members.sector.showbitmap = bitar.new(ffiC.MAXSECTORS-1, ffi.cast("int32_t *", ffiC.show2dsector))

static_members.sector.DAMAGEHPLANE = conststruct
{
    SUPPRESS = -1,
    DEFAULT = 0,
    GLASSBREAK = 2^20,
}

function static_members.sector.damagehplane_whatsect(RETURN)
    local what = (band(RETURN, 65536)~=0) and "ceiling" or "floor"
    local sectnum = band(RETURN, ffiC.MAXSECTORS-1)
    return what, sectnum
end

local function iter_allsprites(_, curi)
    for i=curi+1,ffiC.MAXSPRITES-1 do
        if (ffiC.sprite[i].statnum ~= ffiC.MAXSTATUS) then
            return i
        end
    end
end

function static_members.sprite.all()
    return iter_allsprites, nil, -1
end

local sms = static_members.sprite
sms._headspritesect = creategtab(ffiC.headspritesect, ffiC.MAXSECTORS, 'headspritesect[]')
-- NOTE: don't allow freelist access
sms._headspritestat = creategtab(ffiC.headspritestat, ffiC.MAXSTATUS, 'headspritestat[]')
sms._nextspritesect = creategtab(ffiC.nextspritesect, ffiC.MAXSPRITES, 'nextspritesect[]')
sms._nextspritestat = creategtab(ffiC.nextspritestat, ffiC.MAXSPRITES, 'nextspritestat[]')
sms._prevspritesect = creategtab(ffiC.prevspritesect, ffiC.MAXSPRITES, 'prevspritesect[]')
sms._prevspritestat = creategtab(ffiC.prevspritestat, ffiC.MAXSPRITES, 'prevspritestat[]')

function static_members.wall.dragto(wallnum, pos)
    check_wall_idx(wallnum)

    -- TODO: some kind of validation of the position?
    ffiC.dragpoint(wallnum, pos.x, pos.y, 0)
end

-- Functions changing the sector/status number of a sprite, without asking.

-- Changes sector number of sprite with index <spritenum> to <sectnum>,
-- unconditionally and "unforgiving" (oob <sectnum> gives error).
-- <noerr> is for CON compatibility and prevents error on *sprite not in the
-- game world* if true.
function static_members.sprite.changesect(spritenum, sectnum, noerr)
    check_sprite_idx(spritenum)
    check_sector_idx(sectnum)
    if (ffiC.changespritesect(spritenum, sectnum)==-1 and not noerr) then
        error("cannot change sector number of sprite not in the game world", 2)
    end
end

l_changesect = static_members.sprite.changesect

function static_members.sprite.changestat(spritenum, statnum, noerr)
    -- TODO: see gameexec.c's CON_CHANGESPRITESTAT.
    check_sprite_idx(spritenum)
    if (not (statnum >= 0 and statnum < ffiC.MAXSTATUS)) then
        error("invalid status number "..statnum, 2)
    end
    if (ffiC.changespritestat(spritenum, statnum)==-1 and not noerr) then
        error("cannot change status number of sprite not in the game world", 2)
    end
end

-- Update a sprite's sector number from its current position and sector number.
function static_members.sprite.updatesect(spritenum, flags)
    check_sprite_idx(spritenum)
    local spr = ffiC.sprite[spritenum]

    local newsect = l_updatesector(spr, spr.sectnum, flags)
    if (newsect ~= -1 and newsect ~= spr.sectnum) then
        l_changesect(spritenum, newsect)
    end
    return newsect
end

local strictp = debug_flags.strict

function GenStructMetatable(Structname, Boundname, StaticMembersTab)
    StaticMembersTab = StaticMembersTab or static_members[Structname]

    -- If we're running with the 'strict' option, disallow accesses to void
    -- sprites.
    local index_func = (strictp and Structname=="sprite") and

        -- Mostly CODEDUP of lower function, ...
        function(tab, key)
            if (type(key)=="number") then
                if (key >= 0 and key < ffiC[Boundname]) then
                    -- ... except this.
                    -- (Inlining into the other function did slow things down.)
                    if (ffiC.sprite[key].statnum == ffiC.MAXSTATUS) then
                        error("attempt to access void sprite with index "..key, 2)
                    end
                    return ffiC[Structname][key]
                end
                error("out-of-bounds "..Structname.."[] read access with index "..key, 2)
            elseif (type(key)=="string") then
                return StaticMembersTab[key]
            end
        end

        or

        function(tab, key)
            if (type(key)=="number") then
                if (key >= 0 and key < ffiC[Boundname]) then
                    return ffiC[Structname][key]
                end
                error("out-of-bounds "..Structname.."[] read access with index "..key, 2)
            elseif (type(key)=="string") then
                return StaticMembersTab[key]
            end
        end

    return {
        __index = index_func,
        __newindex = function() error("cannot write directly to "..Structname.."[]", 2) end,
    }
end

local sector_mt = GenStructMetatable("sector", "numsectors")
local wall_mt = GenStructMetatable("wall", "numwalls")
local sprite_mt = GenStructMetatable("sprite", "MAXSPRITES")

local atsprite_mt = {
    __index = function(tab, idx)
        check_sprite_idx(idx)

        local tspr = ffi.cast(tspritetype_ptr_ct, ffiC.spriteext[idx]._tspr)
        if (tspr == nil) then
            error("tsprite of actor "..idx.." unavailable", 2)
        end

        -- Return a reference to a tsprite[] element.
        return tspr[0]
    end,

    __newindex = function() error('cannot write directly to atsprite[]', 2) end,
}

local vars_to_ignore = {}
for varname,_ in pairs(getfenv(1)) do
    if (ffiC._DEBUG_LUNATIC ~= 0) then
        print("IGNORE "..varname)
    end
    vars_to_ignore[varname] = true
end

--== ALL GLOBALS FROM HERE ON ARE EXPORTED UPWARDS (see create_globals() below) ==--

sector = setmtonce({}, sector_mt)
local sector = sector
wall = setmtonce({}, wall_mt)
sprite = setmtonce({}, sprite_mt)
spriteext = creategtab(ffiC.spriteext, ffiC.MAXSPRITES, 'spriteext[]')
_atsprite = setmtonce({}, atsprite_mt)

local function iter_wallsofsec(endwall, w)
    w = w+1
    if (w < endwall) then
        return w
    end
end

wallsofsec = function(sec)  -- local
    return iter_wallsofsec, sec.wallptr+sec.wallnum, sec.wallptr-1
end

function wallsofsect(sect)
    check_sector_idx(sect)
    return iter_wallsofsec, sector[sect].wallptr+sector[sect].wallnum, sector[sect].wallptr-1
end

--== Per-sector/per-statnum sprite iterators ==--
local function iter_spritesofsect(sect, i)
    if (i < 0) then
        i = ffiC.headspritesect[sect]
    else
        i = ffiC.nextspritesect[i]
    end

    if (i >= 0) then return i end
end

-- sprites of sectnum iterator that allows deleting the iterated sprite
local function iter_spritesofsect_safe(tab, i)
    if (i < 0) then
        i = ffiC.headspritesect[-i]
    else
        i = tab[1]
    end

    if (i >= 0) then
        tab[1] = ffiC.nextspritesect[i]
        return i
    end
end

function spritesofsect(sect, maydelete)
    check_sector_idx(sect)

    if (maydelete) then
        return iter_spritesofsect_safe, { -1 }, -sect
    else
        return iter_spritesofsect, sect, -1
    end
end

local function iter_spritesofstat(stat, i)
    if (i < 0) then
        i = ffiC.headspritestat[stat]
    else
        i = ffiC.nextspritestat[i]
    end

    if (i >= 0) then return i end
end

-- sprites of statnum iterator that allows deleting the iterated sprite
local function iter_spritesofstat_safe(tab, i)
    if (i < 0) then
        i = ffiC.headspritestat[-i]
    else
        i = tab[1]
    end

    if (i >= 0) then
        tab[1] = ffiC.nextspritestat[i]
        return i
    end
end

function spritesofstat(stat, maydelete)
    if (not (stat >= 0 and stat < ffiC.MAXSTATUS)) then
        error("passed invalid statnum to spritesofstat iterator", 2)
    end

    if (maydelete) then
        return iter_spritesofstat_safe, { -1 }, -stat
    else
        return iter_spritesofstat, stat, -1
    end
end

--== TROR iterators ==--
local function iter_sectorsofbunch(cf, i)
    if (i < 0) then
        i = ffiC.headsectbunch[cf][-i-1];
    else
        i = ffiC.nextsectbunch[cf][i];
    end

    if (i >= 0) then return i end
end

local function iter_sectorsofbunch_both(cftab, i)
    local cf = cftab[1]

    if (i < 0) then
        i = ffiC.headsectbunch[cf][-i-1];
    else
        i = ffiC.nextsectbunch[cf][i];
    end

    if (i < 0 and cf==0) then
        cftab[1] = 1
        i = ffiC.headsectbunch[1][cftab[2]]
        assert(i >= 0, "TROR bunch lists corrupt")
    end

    if (i >= 0) then
        return i, cftab[1]==0 and "ceiling" or "floor"
    end
end

function sectorsofbunch(bunchnum, cf)
    if (not (bunchnum >= 0 and bunchnum < ffiC.numyaxbunches)) then
        error("passed invalid bunchnum to sectorsofbunch iterator", 2)
    end

    if (cf == ffiC.BOTH_CF) then
        return iter_sectorsofbunch_both, { 0, bunchnum }, -bunchnum-1
    else
        if (not (cf == 0 or cf == 1)) then
            error("passed invalid 'cf' to sectorsofbunch iterator, must be 0 or 1", 2)
        end

        return iter_sectorsofbunch, cf, -bunchnum-1
    end
end

---=== Engine functions, wrapped for Lua convenience ===---

-- returns a hitdata_ct
-- TODO: make cliptype optional? What should be the default?
function hitscan(pos, sectnum, ray, cliptype)
    check_sector_idx(sectnum)
    local vec = vec3_ct(pos.x, pos.y, pos.z)
    local hitdata = hitdata_ct()

    ffiC.hitscan(vec, sectnum, ray.x, ray.y, ray.z, hitdata, cliptype)
    return hitdata
end

function cansee(pos1,sect1, pos2,sect2)
    check_sector_idx(sect1)
    check_sector_idx(sect2)

    local ret = ffiC.cansee(pos1.x,pos1.y,pos1.z, sect1,
                            pos2.x,pos2.y,pos2.z, sect2)
    return (ret~=0)
end

local neartag_ret_ct = ffi.typeof[[
const struct {
    int32_t sector, wall, sprite;
    int32_t dist;
}
]]

local function newar() return ffi.new("int16_t [1]") end
-- NOTE: <tagsearch> flags are in sector.NEARTAG_FLAGS
function neartag(pos, sectnum, ang, range, tagsearch)
    check_sector_idx(sectnum)
    local a, b, c, d = newar(), newar(), newar(), ffi.new("int32_t [1]")
    ffiC.neartag(pos.x, pos.y, pos.z, sectnum, ang, a, b, c, d, range, tagsearch, nil)
    return neartag_ret_ct(a[0], b[0], c[0], d[0])
end

function inside(pos, sectnum)
    check_sector_idx(sectnum)
    return (ffiC.inside(pos.x, pos.y, sectnum)==1)
end

local us_retsect = ffi.new("int16_t [1]")
local USF = sector.UPDATE_FLAGS

function updatesector(pos, sectnum, flags)
    if (sectnum ~= -1) then
        check_sector_idx(sectnum)
    end

    us_retsect[0] = sectnum

    if (flags==nil or flags==0) then
        ffiC.updatesector(pos.x, pos.y, us_retsect)
    elseif (flags==USF.BREADTH) then
        ffiC.updatesectorbreadth(pos.x, pos.y, us_retsect)
    elseif (flags==USF.Z) then
        -- Same as updatesectorz, but useful if we are called from
        -- e.g. sprite.updatesect().
        ffiC.updatesectorz(pos.x, pos.y, pos.z, us_retsect)
    else
        error("invalid argument #3 (flags)", 2)
    end

    return us_retsect[0]
end

l_updatesector = updatesector

function updatesectorz(pos, sectnum, flags)
    if (sectnum ~= -1) then
        check_sector_idx(sectnum)
    end
    if (flags ~= nil) then
        error("invalid argument #3 (flags)", 2)
    end

    us_retsect[0] = sectnum
    ffiC.updatesectorz(pos.x, pos.y, pos.z, us_retsect)
    return us_retsect[0]
end

function printf(fmt, ...)
    print(string.format(fmt, ...))
end

-- This is supposed to be run from the file that 'require's this module to take
-- over the non-local variables from here into its global environment.
function create_globals(_G_their)
    local _G_our = getfenv(1)
    vars_to_ignore["create_globals"] = true

    for varname,obj in pairs(_G_our) do
        if (not vars_to_ignore[varname]) then
            if (ffiC._DEBUG_LUNATIC ~= 0) then
                print("EXPORT "..varname)
            end
            _G_their[varname] = obj
        end
    end
end

repo_name: orochi/nblood

license: mit


code:
----------------------------------------------------------------------------
-- LuaJIT x64 disassembler wrapper module.
--
-- Copyright (C) 2005-2015 Mike Pall. All rights reserved.
-- Released under the MIT license. See Copyright Notice in luajit.h
----------------------------------------------------------------------------
-- This module just exports the 64 bit functions from the combined
-- x86/x64 disassembler module. All the interesting stuff is there.
------------------------------------------------------------------------------

local require = require

module(...)

local dis_x86 = require(_PACKAGE.."dis_x86")

create = dis_x86.create64
disass = dis_x86.disass64
regname = dis_x86.regname64


code:
----------------------------------------------------------------------------
-- LuaJIT x86/x64 disassembler module.
--
-- Copyright (C) 2005-2015 Mike Pall. All rights reserved.
-- Released under the MIT license. See Copyright Notice in luajit.h
----------------------------------------------------------------------------
-- This is a helper module used by the LuaJIT machine code dumper module.
--
-- Sending small code snippets to an external disassembler and mixing the
-- output with our own stuff was too fragile. So I had to bite the bullet
-- and write yet another x86 disassembler. Oh well ...
--
-- The output format is very similar to what ndisasm generates. But it has
-- been developed independently by looking at the opcode tables from the
-- Intel and AMD manuals. The supported instruction set is quite extensive
-- and reflects what a current generation Intel or AMD CPU implements in
-- 32 bit and 64 bit mode. Yes, this includes MMX, SSE, SSE2, SSE3, SSSE3,
-- SSE4.1, SSE4.2, SSE4a and even privileged and hypervisor (VMX/SVM)
-- instructions.
--
-- Notes:
-- * The (useless) a16 prefix, 3DNow and pre-586 opcodes are unsupported.
-- * No attempt at optimization has been made -- it's fast enough for my needs.
-- * The public API may change when more architectures are added.
------------------------------------------------------------------------------

local type = type
local sub, byte, format = string.sub, string.byte, string.format
local match, gmatch, gsub = string.match, string.gmatch, string.gsub
local lower, rep = string.lower, string.rep

-- Map for 1st opcode byte in 32 bit mode. Ugly? Well ... read on.
local map_opc1_32 = {
--0x
[0]="addBmr","addVmr","addBrm","addVrm","addBai","addVai","push es","pop es",
"orBmr","orVmr","orBrm","orVrm","orBai","orVai","push cs","opc2*",
--1x
"adcBmr","adcVmr","adcBrm","adcVrm","adcBai","adcVai","push ss","pop ss",
"sbbBmr","sbbVmr","sbbBrm","sbbVrm","sbbBai","sbbVai","push ds","pop ds",
--2x
"andBmr","andVmr","andBrm","andVrm","andBai","andVai","es:seg","daa",
"subBmr","subVmr","subBrm","subVrm","subBai","subVai","cs:seg","das",
--3x
"xorBmr","xorVmr","xorBrm","xorVrm","xorBai","xorVai","ss:seg","aaa",
"cmpBmr","cmpVmr","cmpBrm","cmpVrm","cmpBai","cmpVai","ds:seg","aas",
--4x
"incVR","incVR","incVR","incVR","incVR","incVR","incVR","incVR",
"decVR","decVR","decVR","decVR","decVR","decVR","decVR","decVR",
--5x
"pushUR","pushUR","pushUR","pushUR","pushUR","pushUR","pushUR","pushUR",
"popUR","popUR","popUR","popUR","popUR","popUR","popUR","popUR",
--6x
"sz*pushaw,pusha","sz*popaw,popa","boundVrm","arplWmr",
"fs:seg","gs:seg","o16:","a16",
"pushUi","imulVrmi","pushBs","imulVrms",
"insb","insVS","outsb","outsVS",
--7x
"joBj","jnoBj","jbBj","jnbBj","jzBj","jnzBj","jbeBj","jaBj",
"jsBj","jnsBj","jpeBj","jpoBj","jlBj","jgeBj","jleBj","jgBj",
--8x
"arith!Bmi","arith!Vmi","arith!Bmi","arith!Vms",
"testBmr","testVmr","xchgBrm","xchgVrm",
"movBmr","movVmr","movBrm","movVrm",
"movVmg","leaVrm","movWgm","popUm",
--9x
"nop*xchgVaR|pause|xchgWaR|repne nop","xchgVaR","xchgVaR","xchgVaR",
"xchgVaR","xchgVaR","xchgVaR","xchgVaR",
"sz*cbw,cwde,cdqe","sz*cwd,cdq,cqo","call farViw","wait",
"sz*pushfw,pushf","sz*popfw,popf","sahf","lahf",
--Ax
"movBao","movVao","movBoa","movVoa",
"movsb","movsVS","cmpsb","cmpsVS",
"testBai","testVai","stosb","stosVS",
"lodsb","lodsVS","scasb","scasVS",
--Bx
"movBRi","movBRi","movBRi","movBRi","movBRi","movBRi","movBRi","movBRi",
"movVRI","movVRI","movVRI","movVRI","movVRI","movVRI","movVRI","movVRI",
--Cx
"shift!Bmu","shift!Vmu","retBw","ret","$lesVrm","$ldsVrm","movBmi","movVmi",
"enterBwu","leave","retfBw","retf","int3","intBu","into","iretVS",
--Dx
"shift!Bm1","shift!Vm1","shift!Bmc","shift!Vmc","aamBu","aadBu","salc","xlatb",
"fp*0","fp*1","fp*2","fp*3","fp*4","fp*5","fp*6","fp*7",
--Ex
"loopneBj","loopeBj","loopBj","sz*jcxzBj,jecxzBj,jrcxzBj",
"inBau","inVau","outBua","outVua",
"callVj","jmpVj","jmp farViw","jmpBj","inBad","inVad","outBda","outVda",
--Fx
"lock:","int1","repne:rep","rep:","hlt","cmc","testb!Bm","testv!Vm",
"clc","stc","cli","sti","cld","std","incb!Bm","incd!Vm",
}
assert(#map_opc1_32 == 255)

-- Map for 1st opcode byte in 64 bit mode (overrides only).
local map_opc1_64 = setmetatable({
  [0x06]=false, [0x07]=false, [0x0e]=false,
  [0x16]=false, [0x17]=false, [0x1e]=false, [0x1f]=false,
  [0x27]=false, [0x2f]=false, [0x37]=false, [0x3f]=false,
  [0x60]=false, [0x61]=false, [0x62]=false, [0x63]="movsxdVrDmt", [0x67]="a32:",
  [0x40]="rex*",   [0x41]="rex*b",   [0x42]="rex*x",   [0x43]="rex*xb",
  [0x44]="rex*r",  [0x45]="rex*rb",  [0x46]="rex*rx",  [0x47]="rex*rxb",
  [0x48]="rex*w",  [0x49]="rex*wb",  [0x4a]="rex*wx",  [0x4b]="rex*wxb",
  [0x4c]="rex*wr", [0x4d]="rex*wrb", [0x4e]="rex*wrx", [0x4f]="rex*wrxb",
  [0x82]=false, [0x9a]=false, [0xc4]=false, [0xc5]=false, [0xce]=false,
  [0xd4]=false, [0xd5]=false, [0xd6]=false, [0xea]=false,
}, { __index = map_opc1_32 })

-- Map for 2nd opcode byte (0F xx). True CISC hell. Hey, I told you.
-- Prefix dependent MMX/SSE opcodes: (none)|rep|o16|repne, -|F3|66|F2
local map_opc2 = {
--0x
[0]="sldt!Dmp","sgdt!Ump","larVrm","lslVrm",nil,"syscall","clts","sysret",
"invd","wbinvd",nil,"ud1",nil,"$prefetch!Bm","femms","3dnowMrmu",
--1x
"movupsXrm|movssXrm|movupdXrm|movsdXrm",
"movupsXmr|movssXmr|movupdXmr|movsdXmr",
"movhlpsXrm$movlpsXrm|movsldupXrm|movlpdXrm|movddupXrm",
"movlpsXmr||movlpdXmr",
"unpcklpsXrm||unpcklpdXrm",
"unpckhpsXrm||unpckhpdXrm",
"movlhpsXrm$movhpsXrm|movshdupXrm|movhpdXrm",
"movhpsXmr||movhpdXmr",
"$prefetcht!Bm","hintnopVm","hintnopVm","hintnopVm",
"hintnopVm","hintnopVm","hintnopVm","hintnopVm",
--2x
"movUmx$","movUmy$","movUxm$","movUym$","movUmz$",nil,"movUzm$",nil,
"movapsXrm||movapdXrm",
"movapsXmr||movapdXmr",
"cvtpi2psXrMm|cvtsi2ssXrVmt|cvtpi2pdXrMm|cvtsi2sdXrVmt",
"movntpsXmr|movntssXmr|movntpdXmr|movntsdXmr",
"cvttps2piMrXm|cvttss2siVrXm|cvttpd2piMrXm|cvttsd2siVrXm",
"cvtps2piMrXm|cvtss2siVrXm|cvtpd2piMrXm|cvtsd2siVrXm",
"ucomissXrm||ucomisdXrm",
"comissXrm||comisdXrm",
--3x
"wrmsr","rdtsc","rdmsr","rdpmc","sysenter","sysexit",nil,"getsec",
"opc3*38",nil,"opc3*3a",nil,nil,nil,nil,nil,
--4x
"cmovoVrm","cmovnoVrm","cmovbVrm","cmovnbVrm",
"cmovzVrm","cmovnzVrm","cmovbeVrm","cmovaVrm",
"cmovsVrm","cmovnsVrm","cmovpeVrm","cmovpoVrm",
"cmovlVrm","cmovgeVrm","cmovleVrm","cmovgVrm",
--5x
"movmskpsVrXm$||movmskpdVrXm$","sqrtpsXrm|sqrtssXrm|sqrtpdXrm|sqrtsdXrm",
"rsqrtpsXrm|rsqrtssXrm","rcppsXrm|rcpssXrm",
"andpsXrm||andpdXrm","andnpsXrm||andnpdXrm",
"orpsXrm||orpdXrm","xorpsXrm||xorpdXrm",
"addpsXrm|addssXrm|addpdXrm|addsdXrm","mulpsXrm|mulssXrm|mulpdXrm|mulsdXrm",
"cvtps2pdXrm|cvtss2sdXrm|cvtpd2psXrm|cvtsd2ssXrm",
"cvtdq2psXrm|cvttps2dqXrm|cvtps2dqXrm",
"subpsXrm|subssXrm|subpdXrm|subsdXrm","minpsXrm|minssXrm|minpdXrm|minsdXrm",
"divpsXrm|divssXrm|divpdXrm|divsdXrm","maxpsXrm|maxssXrm|maxpdXrm|maxsdXrm",
--6x
"punpcklbwPrm","punpcklwdPrm","punpckldqPrm","packsswbPrm",
"pcmpgtbPrm","pcmpgtwPrm","pcmpgtdPrm","packuswbPrm",
"punpckhbwPrm","punpckhwdPrm","punpckhdqPrm","packssdwPrm",
"||punpcklqdqXrm","||punpckhqdqXrm",
"movPrVSm","movqMrm|movdquXrm|movdqaXrm",
--7x
"pshufwMrmu|pshufhwXrmu|pshufdXrmu|pshuflwXrmu","pshiftw!Pmu",
"pshiftd!Pmu","pshiftq!Mmu||pshiftdq!Xmu",
"pcmpeqbPrm","pcmpeqwPrm","pcmpeqdPrm","emms|",
"vmreadUmr||extrqXmuu$|insertqXrmuu$","vmwriteUrm||extrqXrm$|insertqXrm$",
nil,nil,
"||haddpdXrm|haddpsXrm","||hsubpdXrm|hsubpsXrm",
"movVSmMr|movqXrm|movVSmXr","movqMmr|movdquXmr|movdqaXmr",
--8x
"joVj","jnoVj","jbVj","jnbVj","jzVj","jnzVj","jbeVj","jaVj",
"jsVj","jnsVj","jpeVj","jpoVj","jlVj","jgeVj","jleVj","jgVj",
--9x
"setoBm","setnoBm","setbBm","setnbBm","setzBm","setnzBm","setbeBm","setaBm",
"setsBm","setnsBm","setpeBm","setpoBm","setlBm","setgeBm","setleBm","setgBm",
--Ax
"push fs","pop fs","cpuid","btVmr","shldVmru","shldVmrc",nil,nil,
"push gs","pop gs","rsm","btsVmr","shrdVmru","shrdVmrc","fxsave!Dmp","imulVrm",
--Bx
"cmpxchgBmr","cmpxchgVmr","$lssVrm","btrVmr",
"$lfsVrm","$lgsVrm","movzxVrBmt","movzxVrWmt",
"|popcntVrm","ud2Dp","bt!Vmu","btcVmr",
"bsfVrm","bsrVrm|lzcntVrm|bsrWrm","movsxVrBmt","movsxVrWmt",
--Cx
"xaddBmr","xaddVmr",
"cmppsXrmu|cmpssXrmu|cmppdXrmu|cmpsdXrmu","$movntiVmr|",
"pinsrwPrWmu","pextrwDrPmu",
"shufpsXrmu||shufpdXrmu","$cmpxchg!Qmp",
"bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR",
--Dx
"||addsubpdXrm|addsubpsXrm","psrlwPrm","psrldPrm","psrlqPrm",
"paddqPrm","pmullwPrm",
"|movq2dqXrMm|movqXmr|movdq2qMrXm$","pmovmskbVrMm||pmovmskbVrXm",
"psubusbPrm","psubuswPrm","pminubPrm","pandPrm",
"paddusbPrm","padduswPrm","pmaxubPrm","pandnPrm",
--Ex
"pavgbPrm","psrawPrm","psradPrm","pavgwPrm",
"pmulhuwPrm","pmulhwPrm",
"|cvtdq2pdXrm|cvttpd2dqXrm|cvtpd2dqXrm","$movntqMmr||$movntdqXmr",
"psubsbPrm","psubswPrm","pminswPrm","porPrm",
"paddsbPrm","paddswPrm","pmaxswPrm","pxorPrm",
--Fx
"|||lddquXrm","psllwPrm","pslldPrm","psllqPrm",
"pmuludqPrm","pmaddwdPrm","psadbwPrm","maskmovqMrm||maskmovdquXrm$",
"psubbPrm","psubwPrm","psubdPrm","psubqPrm",
"paddbPrm","paddwPrm","padddPrm","ud",
}
assert(map_opc2[255] == "ud")

-- Map for three-byte opcodes. Can't wait for their next invention.
local map_opc3 = {
["38"] = { -- [66] 0f 38 xx
--0x
[0]="pshufbPrm","phaddwPrm","phadddPrm","phaddswPrm",
"pmaddubswPrm","phsubwPrm","phsubdPrm","phsubswPrm",
"psignbPrm","psignwPrm","psigndPrm","pmulhrswPrm",
nil,nil,nil,nil,
--1x
"||pblendvbXrma",nil,nil,nil,
"||blendvpsXrma","||blendvpdXrma",nil,"||ptestXrm",
nil,nil,nil,nil,
"pabsbPrm","pabswPrm","pabsdPrm",nil,
--2x
"||pmovsxbwXrm","||pmovsxbdXrm","||pmovsxbqXrm","||pmovsxwdXrm",
"||pmovsxwqXrm","||pmovsxdqXrm",nil,nil,
"||pmuldqXrm","||pcmpeqqXrm","||$movntdqaXrm","||packusdwXrm",
nil,nil,nil,nil,
--3x
"||pmovzxbwXrm","||pmovzxbdXrm","||pmovzxbqXrm","||pmovzxwdXrm",
"||pmovzxwqXrm","||pmovzxdqXrm",nil,"||pcmpgtqXrm",
"||pminsbXrm","||pminsdXrm","||pminuwXrm","||pminudXrm",
"||pmaxsbXrm","||pmaxsdXrm","||pmaxuwXrm","||pmaxudXrm",
--4x
"||pmulddXrm","||phminposuwXrm",
--Fx
[0xf0] = "|||crc32TrBmt",[0xf1] = "|||crc32TrVmt",
},

["3a"] = { -- [66] 0f 3a xx
--0x
[0x00]=nil,nil,nil,nil,nil,nil,nil,nil,
"||roundpsXrmu","||roundpdXrmu","||roundssXrmu","||roundsdXrmu",
"||blendpsXrmu","||blendpdXrmu","||pblendwXrmu","palignrPrmu",
--1x
nil,nil,nil,nil,
"||pextrbVmXru","||pextrwVmXru","||pextrVmSXru","||extractpsVmXru",
nil,nil,nil,nil,nil,nil,nil,nil,
--2x
"||pinsrbXrVmu","||insertpsXrmu","||pinsrXrVmuS",nil,
--4x
[0x40] = "||dppsXrmu",
[0x41] = "||dppdXrmu",
[0x42] = "||mpsadbwXrmu",
--6x
[0x60] = "||pcmpestrmXrmu",[0x61] = "||pcmpestriXrmu",
[0x62] = "||pcmpistrmXrmu",[0x63] = "||pcmpistriXrmu",
},
}

-- Map for VMX/SVM opcodes 0F 01 C0-FF (sgdt group with register operands).
local map_opcvm = {
[0xc1]="vmcall",[0xc2]="vmlaunch",[0xc3]="vmresume",[0xc4]="vmxoff",
[0xc8]="monitor",[0xc9]="mwait",
[0xd8]="vmrun",[0xd9]="vmmcall",[0xda]="vmload",[0xdb]="vmsave",
[0xdc]="stgi",[0xdd]="clgi",[0xde]="skinit",[0xdf]="invlpga",
[0xf8]="swapgs",[0xf9]="rdtscp",
}

-- Map for FP opcodes. And you thought stack machines are simple?
local map_opcfp = {
-- D8-DF 00-BF: opcodes with a memory operand.
-- D8
[0]="faddFm","fmulFm","fcomFm","fcompFm","fsubFm","fsubrFm","fdivFm","fdivrFm",
"fldFm",nil,"fstFm","fstpFm","fldenvVm","fldcwWm","fnstenvVm","fnstcwWm",
-- DA
"fiaddDm","fimulDm","ficomDm","ficompDm",
"fisubDm","fisubrDm","fidivDm","fidivrDm",
-- DB
"fildDm","fisttpDm","fistDm","fistpDm",nil,"fld twordFmp",nil,"fstp twordFmp",
-- DC
"faddGm","fmulGm","fcomGm","fcompGm","fsubGm","fsubrGm","fdivGm","fdivrGm",
-- DD
"fldGm","fisttpQm","fstGm","fstpGm","frstorDmp",nil,"fnsaveDmp","fnstswWm",
-- DE
"fiaddWm","fimulWm","ficomWm","ficompWm",
"fisubWm","fisubrWm","fidivWm","fidivrWm",
-- DF
"fildWm","fisttpWm","fistWm","fistpWm",
"fbld twordFmp","fildQm","fbstp twordFmp","fistpQm",
-- xx C0-FF: opcodes with a pseudo-register operand.
-- D8
"faddFf","fmulFf","fcomFf","fcompFf","fsubFf","fsubrFf","fdivFf","fdivrFf",
-- D9
"fldFf","fxchFf",{"fnop"},nil,
{"fchs","fabs",nil,nil,"ftst","fxam"},
{"fld1","fldl2t","fldl2e","fldpi","fldlg2","fldln2","fldz"},
{"f2xm1","fyl2x","fptan","fpatan","fxtract","fprem1","fdecstp","fincstp"},
{"fprem","fyl2xp1","fsqrt","fsincos","frndint","fscale","fsin","fcos"},
-- DA
"fcmovbFf","fcmoveFf","fcmovbeFf","fcmovuFf",nil,{nil,"fucompp"},nil,nil,
-- DB
"fcmovnbFf","fcmovneFf","fcmovnbeFf","fcmovnuFf",
{nil,nil,"fnclex","fninit"},"fucomiFf","fcomiFf",nil,
-- DC
"fadd toFf","fmul toFf",nil,nil,
"fsub toFf","fsubr toFf","fdivr toFf","fdiv toFf",
-- DD
"ffreeFf",nil,"fstFf","fstpFf","fucomFf","fucompFf",nil,nil,
-- DE
"faddpFf","fmulpFf",nil,{nil,"fcompp"},
"fsubrpFf","fsubpFf","fdivrpFf","fdivpFf",
-- DF
nil,nil,nil,nil,{"fnstsw ax"},"fucomipFf","fcomipFf",nil,
}
assert(map_opcfp[126] == "fcomipFf")

-- Map for opcode groups. The subkey is sp from the ModRM byte.
local map_opcgroup = {
  arith = { "add", "or", "adc", "sbb", "and", "sub", "xor", "cmp" },
  shift = { "rol", "ror", "rcl", "rcr", "shl", "shr", "sal", "sar" },
  testb = { "testBmi", "testBmi", "not", "neg", "mul", "imul", "div", "idiv" },
  testv = { "testVmi", "testVmi", "not", "neg", "mul", "imul", "div", "idiv" },
  incb = { "inc", "dec" },
  incd = { "inc", "dec", "callUmp", "$call farDmp",
	   "jmpUmp", "$jmp farDmp", "pushUm" },
  sldt = { "sldt", "str", "lldt", "ltr", "verr", "verw" },
  sgdt = { "vm*$sgdt", "vm*$sidt", "$lgdt", "vm*$lidt",
	   "smsw", nil, "lmsw", "vm*$invlpg" },
  bt = { nil, nil, nil, nil, "bt", "bts", "btr", "btc" },
  cmpxchg = { nil, "sz*,cmpxchg8bQmp,cmpxchg16bXmp", nil, nil,
	      nil, nil, "vmptrld|vmxon|vmclear", "vmptrst" },
  pshiftw = { nil, nil, "psrlw", nil, "psraw", nil, "psllw" },
  pshiftd = { nil, nil, "psrld", nil, "psrad", nil, "pslld" },
  pshiftq = { nil, nil, "psrlq", nil, nil, nil, "psllq" },
  pshiftdq = { nil, nil, "psrlq", "psrldq", nil, nil, "psllq", "pslldq" },
  fxsave = { "$fxsave", "$fxrstor", "$ldmxcsr", "$stmxcsr",
	     nil, "lfenceDp$", "mfenceDp$", "sfenceDp$clflush" },
  prefetch = { "prefetch", "prefetchw" },
  prefetcht = { "prefetchnta", "prefetcht0", "prefetcht1", "prefetcht2" },
}

------------------------------------------------------------------------------

-- Maps for register names.
local map_regs = {
  B = { "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh",
	"r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" },
  B64 = { "al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil",
	  "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" },
  W = { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di",
	"r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" },
  D = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
	"r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" },
  Q = { "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
	"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" },
  M = { "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
	"mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" }, -- No x64 ext!
  X = { "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
	"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15" },
}
local map_segregs = { "es", "cs", "ss", "ds", "fs", "gs", "segr6", "segr7" }

-- Maps for size names.
local map_sz2n = {
  B = 1, W = 2, D = 4, Q = 8, M = 8, X = 16,
}
local map_sz2prefix = {
  B = "byte", W = "word", D = "dword",
  Q = "qword",
  M = "qword", X = "xword",
  F = "dword", G = "qword", -- No need for sizes/register names for these two.
}

------------------------------------------------------------------------------

-- Output a nicely formatted line with an opcode and operands.
local function putop(ctx, text, operands)
  local code, pos, hex = ctx.code, ctx.pos, ""
  local hmax = ctx.hexdump
  if hmax > 0 then
    for i=ctx.start,pos-1 do
      hex = hex..format("%02X", byte(code, i, i))
    end
    if #hex > hmax then hex = sub(hex, 1, hmax)..". "
    else hex = hex..rep(" ", hmax-#hex+2) end
  end
  if operands then text = text.." "..operands end
  if ctx.o16 then text = "o16 "..text; ctx.o16 = false end
  if ctx.a32 then text = "a32 "..text; ctx.a32 = false end
  if ctx.rep then text = ctx.rep.." "..text; ctx.rep = false end
  if ctx.rex then
    local t = (ctx.rexw and "w" or "")..(ctx.rexr and "r" or "")..
	      (ctx.rexx and "x" or "")..(ctx.rexb and "b" or "")
    if t ~= "" then text = "rex."..t.." "..text end
    ctx.rexw = false; ctx.rexr = false; ctx.rexx = false; ctx.rexb = false
    ctx.rex = false
  end
  if ctx.seg then
    local text2, n = gsub(text, "%[", "["..ctx.seg..":")
    if n == 0 then text = ctx.seg.." "..text else text = text2 end
    ctx.seg = false
  end
  if ctx.lock then text = "lock "..text; ctx.lock = false end
  local imm = ctx.imm
  if imm then
    local sym = ctx.symtab[imm]
    if sym then text = text.."\t->"..sym end
  end
  ctx.out(format("%08x  %s%s\n", ctx.addr+ctx.start, hex, text))
  ctx.mrm = false
  ctx.start = pos
  ctx.imm = nil
end

-- Clear all prefix flags.
local function clearprefixes(ctx)
  ctx.o16 = false; ctx.seg = false; ctx.lock = false; ctx.rep = false
  ctx.rexw = false; ctx.rexr = false; ctx.rexx = false; ctx.rexb = false
  ctx.rex = false; ctx.a32 = false
end

-- Fallback for incomplete opcodes at the end.
local function incomplete(ctx)
  ctx.pos = ctx.stop+1
  clearprefixes(ctx)
  return putop(ctx, "(incomplete)")
end

-- Fallback for unknown opcodes.
local function unknown(ctx)
  clearprefixes(ctx)
  return putop(ctx, "(unknown)")
end

-- Return an immediate of the specified size.
local function getimm(ctx, pos, n)
  if pos+n-1 > ctx.stop then return incomplete(ctx) end
  local code = ctx.code
  if n == 1 then
    local b1 = byte(code, pos, pos)
    return b1
  elseif n == 2 then
    local b1, b2 = byte(code, pos, pos+1)
    return b1+b2*256
  else
    local b1, b2, b3, b4 = byte(code, pos, pos+3)
    local imm = b1+b2*256+b3*65536+b4*16777216
    ctx.imm = imm
    return imm
  end
end

-- Process pattern string and generate the operands.
local function putpat(ctx, name, pat)
  local operands, regs, sz, mode, sp, rm, sc, rx, sdisp
  local code, pos, stop = ctx.code, ctx.pos, ctx.stop

  -- Chars used: 1DFGIMPQRSTUVWXacdfgijmoprstuwxyz
  for p in gmatch(pat, ".") do
    local x = nil
    if p == "V" or p == "U" then
      if ctx.rexw then sz = "Q"; ctx.rexw = false
      elseif ctx.o16 then sz = "W"; ctx.o16 = false
      elseif p == "U" and ctx.x64 then sz = "Q"
      else sz = "D" end
      regs = map_regs[sz]
    elseif p == "T" then
      if ctx.rexw then sz = "Q"; ctx.rexw = false else sz = "D" end
      regs = map_regs[sz]
    elseif p == "B" then
      sz = "B"
      regs = ctx.rex and map_regs.B64 or map_regs.B
    elseif match(p, "[WDQMXFG]") then
      sz = p
      regs = map_regs[sz]
    elseif p == "P" then
      sz = ctx.o16 and "X" or "M"; ctx.o16 = false
      regs = map_regs[sz]
    elseif p == "S" then
      name = name..lower(sz)
    elseif p == "s" then
      local imm = getimm(ctx, pos, 1); if not imm then return end
      x = imm <= 127 and format("+0x%02x", imm)
		     or format("-0x%02x", 256-imm)
      pos = pos+1
    elseif p == "u" then
      local imm = getimm(ctx, pos, 1); if not imm then return end
      x = format("0x%02x", imm)
      pos = pos+1
    elseif p == "w" then
      local imm = getimm(ctx, pos, 2); if not imm then return end
      x = format("0x%x", imm)
      pos = pos+2
    elseif p == "o" then -- [offset]
      if ctx.x64 then
	local imm1 = getimm(ctx, pos, 4); if not imm1 then return end
	local imm2 = getimm(ctx, pos+4, 4); if not imm2 then return end
	x = format("[0x%08x%08x]", imm2, imm1)
	pos = pos+8
      else
	local imm = getimm(ctx, pos, 4); if not imm then return end
	x = format("[0x%08x]", imm)
	pos = pos+4
      end
    elseif p == "i" or p == "I" then
      local n = map_sz2n[sz]
      if n == 8 and ctx.x64 and p == "I" then
	local imm1 = getimm(ctx, pos, 4); if not imm1 then return end
	local imm2 = getimm(ctx, pos+4, 4); if not imm2 then return end
	x = format("0x%08x%08x", imm2, imm1)
      else
	if n == 8 then n = 4 end
	local imm = getimm(ctx, pos, n); if not imm then return end
	if sz == "Q" and (imm < 0 or imm > 0x7fffffff) then
	  imm = (0xffffffff+1)-imm
	  x = format(imm > 65535 and "-0x%08x" or "-0x%x", imm)
	else
	  x = format(imm > 65535 and "0x%08x" or "0x%x", imm)
	end
      end
      pos = pos+n
    elseif p == "j" then
      local n = map_sz2n[sz]
      if n == 8 then n = 4 end
      local imm = getimm(ctx, pos, n); if not imm then return end
      if sz == "B" and imm > 127 then imm = imm-256
      elseif imm > 2147483647 then imm = imm-4294967296 end
      pos = pos+n
      imm = imm + pos + ctx.addr
      if imm > 4294967295 and not ctx.x64 then imm = imm-4294967296 end
      ctx.imm = imm
      if sz == "W" then
	x = format("word 0x%04x", imm%65536)
      elseif ctx.x64 then
	local lo = imm % 0x1000000
	x = format("0x%02x%06x", (imm-lo) / 0x1000000, lo)
      else
	x = format("0x%08x", imm)
      end
    elseif p == "R" then
      local r = byte(code, pos-1, pos-1)%8
      if ctx.rexb then r = r + 8; ctx.rexb = false end
      x = regs[r+1]
    elseif p == "a" then x = regs[1]
    elseif p == "c" then x = "cl"
    elseif p == "d" then x = "dx"
    elseif p == "1" then x = "1"
    else
      if not mode then
	mode = ctx.mrm
	if not mode then
	  if pos > stop then return incomplete(ctx) end
	  mode = byte(code, pos, pos)
	  pos = pos+1
	end
	rm = mode%8; mode = (mode-rm)/8
	sp = mode%8; mode = (mode-sp)/8
	sdisp = ""
	if mode < 3 then
	  if rm == 4 then
	    if pos > stop then return incomplete(ctx) end
	    sc = byte(code, pos, pos)
	    pos = pos+1
	    rm = sc%8; sc = (sc-rm)/8
	    rx = sc%8; sc = (sc-rx)/8
	    if ctx.rexx then rx = rx + 8; ctx.rexx = false end
	    if rx == 4 then rx = nil end
	  end
	  if mode > 0 or rm == 5 then
	    local dsz = mode
	    if dsz ~= 1 then dsz = 4 end
	    local disp = getimm(ctx, pos, dsz); if not disp then return end
	    if mode == 0 then rm = nil end
	    if rm or rx or (not sc and ctx.x64 and not ctx.a32) then
	      if dsz == 1 and disp > 127 then
		sdisp = format("-0x%x", 256-disp)
	      elseif disp >= 0 and disp <= 0x7fffffff then
		sdisp = format("+0x%x", disp)
	      else
		sdisp = format("-0x%x", (0xffffffff+1)-disp)
	      end
	    else
	      sdisp = format(ctx.x64 and not ctx.a32 and
		not (disp >= 0 and disp <= 0x7fffffff)
		and "0xffffffff%08x" or "0x%08x", disp)
	    end
	    pos = pos+dsz
	  end
	end
	if rm and ctx.rexb then rm = rm + 8; ctx.rexb = false end
	if ctx.rexr then sp = sp + 8; ctx.rexr = false end
      end
      if p == "m" then
	if mode == 3 then x = regs[rm+1]
	else
	  local aregs = ctx.a32 and map_regs.D or ctx.aregs
	  local srm, srx = "", ""
	  if rm then srm = aregs[rm+1]
	  elseif not sc and ctx.x64 and not ctx.a32 then srm = "rip" end
	  ctx.a32 = false
	  if rx then
	    if rm then srm = srm.."+" end
	    srx = aregs[rx+1]
	    if sc > 0 then srx = srx.."*"..(2^sc) end
	  end
	  x = format("[%s%s%s]", srm, srx, sdisp)
	end
	if mode < 3 and
	   (not match(pat, "[aRrgp]") or match(pat, "t")) then -- Yuck.
	  x = map_sz2prefix[sz].." "..x
	end
      elseif p == "r" then x = regs[sp+1]
      elseif p == "g" then x = map_segregs[sp+1]
      elseif p == "p" then -- Suppress prefix.
      elseif p == "f" then x = "st"..rm
      elseif p == "x" then
	if sp == 0 and ctx.lock and not ctx.x64 then
	  x = "CR8"; ctx.lock = false
	else
	  x = "CR"..sp
	end
      elseif p == "y" then x = "DR"..sp
      elseif p == "z" then x = "TR"..sp
      elseif p == "t" then
      else
	error("bad pattern `"..pat.."'")
      end
    end
    if x then operands = operands and operands..", "..x or x end
  end
  ctx.pos = pos
  return putop(ctx, name, operands)
end

-- Forward declaration.
local map_act

-- Fetch and cache MRM byte.
local function getmrm(ctx)
  local mrm = ctx.mrm
  if not mrm then
    local pos = ctx.pos
    if pos > ctx.stop then return nil end
    mrm = byte(ctx.code, pos, pos)
    ctx.pos = pos+1
    ctx.mrm = mrm
  end
  return mrm
end

-- Dispatch to handler depending on pattern.
local function dispatch(ctx, opat, patgrp)
  if not opat then return unknown(ctx) end
  if match(opat, "%|") then -- MMX/SSE variants depending on prefix.
    local p
    if ctx.rep then
      p = ctx.rep=="rep" and "%|([^%|]*)" or "%|[^%|]*%|[^%|]*%|([^%|]*)"
      ctx.rep = false
    elseif ctx.o16 then p = "%|[^%|]*%|([^%|]*)"; ctx.o16 = false
    else p = "^[^%|]*" end
    opat = match(opat, p)
    if not opat then return unknown(ctx) end
--    ctx.rep = false; ctx.o16 = false
    --XXX fails for 66 f2 0f 38 f1 06  crc32 eax,WORD PTR [esi]
    --XXX remove in branches?
  end
  if match(opat, "%$") then -- reg$mem variants.
    local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end
    opat = match(opat, mrm >= 192 and "^[^%$]*" or "%$(.*)")
    if opat == "" then return unknown(ctx) end
  end
  if opat == "" then return unknown(ctx) end
  local name, pat = match(opat, "^([a-z0-9 ]*)(.*)")
  if pat == "" and patgrp then pat = patgrp end
  return map_act[sub(pat, 1, 1)](ctx, name, pat)
end

-- Get a pattern from an opcode map and dispatch to handler.
local function dispatchmap(ctx, opcmap)
  local pos = ctx.pos
  local opat = opcmap[byte(ctx.code, pos, pos)]
  pos = pos + 1
  ctx.pos = pos
  return dispatch(ctx, opat)
end

-- Map for action codes. The key is the first char after the name.
map_act = {
  -- Simple opcodes without operands.
  [""] = function(ctx, name, pat)
    return putop(ctx, name)
  end,

  -- Operand size chars fall right through.
  B = putpat, W = putpat, D = putpat, Q = putpat,
  V = putpat, U = putpat, T = putpat,
  M = putpat, X = putpat, P = putpat,
  F = putpat, G = putpat,

  -- Collect prefixes.
  [":"] = function(ctx, name, pat)
    ctx[pat == ":" and name or sub(pat, 2)] = name
    if ctx.pos - ctx.start > 5 then return unknown(ctx) end -- Limit #prefixes.
  end,

  -- Chain to special handler specified by name.
  ["*"] = function(ctx, name, pat)
    return map_act[name](ctx, name, sub(pat, 2))
  end,

  -- Use named subtable for opcode group.
  ["!"] = function(ctx, name, pat)
    local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end
    return dispatch(ctx, map_opcgroup[name][((mrm-(mrm%8))/8)%8+1], sub(pat, 2))
  end,

  -- o16,o32[,o64] variants.
  sz = function(ctx, name, pat)
    if ctx.o16 then ctx.o16 = false
    else
      pat = match(pat, ",(.*)")
      if ctx.rexw then
	local p = match(pat, ",(.*)")
	if p then pat = p; ctx.rexw = false end
      end
    end
    pat = match(pat, "^[^,]*")
    return dispatch(ctx, pat)
  end,

  -- Two-byte opcode dispatch.
  opc2 = function(ctx, name, pat)
    return dispatchmap(ctx, map_opc2)
  end,

  -- Three-byte opcode dispatch.
  opc3 = function(ctx, name, pat)
    return dispatchmap(ctx, map_opc3[pat])
  end,

  -- VMX/SVM dispatch.
  vm = function(ctx, name, pat)
    return dispatch(ctx, map_opcvm[ctx.mrm])
  end,

  -- Floating point opcode dispatch.
  fp = function(ctx, name, pat)
    local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end
    local rm = mrm%8
    local idx = pat*8 + ((mrm-rm)/8)%8
    if mrm >= 192 then idx = idx + 64 end
    local opat = map_opcfp[idx]
    if type(opat) == "table" then opat = opat[rm+1] end
    return dispatch(ctx, opat)
  end,

  -- REX prefix.
  rex = function(ctx, name, pat)
    if ctx.rex then return unknown(ctx) end -- Only 1 REX prefix allowed.
    for p in gmatch(pat, ".") do ctx["rex"..p] = true end
    ctx.rex = true
  end,

  -- Special case for nop with REX prefix.
  nop = function(ctx, name, pat)
    return dispatch(ctx, ctx.rex and pat or "nop")
  end,
}

------------------------------------------------------------------------------

-- Disassemble a block of code.
local function disass_block(ctx, ofs, len)
  if not ofs then ofs = 0 end
  local stop = len and ofs+len or #ctx.code
  ofs = ofs + 1
  ctx.start = ofs
  ctx.pos = ofs
  ctx.stop = stop
  ctx.imm = nil
  ctx.mrm = false
  clearprefixes(ctx)
  while ctx.pos <= stop do dispatchmap(ctx, ctx.map1) end
  if ctx.pos ~= ctx.start then incomplete(ctx) end
end

-- Extended API: create a disassembler context. Then call ctx:disass(ofs, len).
local function create_(code, addr, out)
  local ctx = {}
  ctx.code = code
  ctx.addr = (addr or 0) - 1
  ctx.out = out or io.write
  ctx.symtab = {}
  ctx.disass = disass_block
  ctx.hexdump = 16
  ctx.x64 = false
  ctx.map1 = map_opc1_32
  ctx.aregs = map_regs.D
  return ctx
end

local function create64_(code, addr, out)
  local ctx = create_(code, addr, out)
  ctx.x64 = true
  ctx.map1 = map_opc1_64
  ctx.aregs = map_regs.Q
  return ctx
end

-- Simple API: disassemble code (a string) at address and output via out.
local function disass_(code, addr, out)
  create_(code, addr, out):disass()
end

local function disass64_(code, addr, out)
  create64_(code, addr, out):disass()
end

-- Return register name for RID.
local function regname_(r)
  if r < 8 then return map_regs.D[r+1] end
  return map_regs.X[r-7]
end

local function regname64_(r)
  if r < 16 then return map_regs.Q[r+1] end
  return map_regs.X[r-15]
end

-- Public module functions.
module(...)

create = create_
create64 = create64_
disass = disass_
disass64 = disass64_
regname = regname_
regname64 = regname64_


code:
FILES=lunatic.html lunacon.html

# NOTE: the 'source-highlight-args' attribute is not present in the default
# AsciiDoc distribution. It has to be hacked into its
# filters/source/source-highlight-filter.conf like this:
#
# filter="source-highlight -f xhtml -s {language} (...)"
#  -->
# filter="source-highlight {source-highlight-args} -f xhtml -s {language} (...)"
%.html: %.txt Makefile lunatic_sh.style
	asciidoc -v -a latexmath -a source-highlight-args="--style-file=$(shell pwd)/lunatic_sh.style" $<

all: $(FILES)

clean:
	rm -f $(FILES)

path: source/rr/src/lunatic/doc/Makefile


code:
Lunatic depends on the following:

* LuaJIT 2.0.3 or later [http://luajit.org]. It is recommended to get the
  latest git HEAD of the 2.0 branch. As of time of writing, LuaJIT 2.1 is
  in alpha, and has not been successfully tested with Lunatic.

* LPeg 0.12 [http://www.inf.puc-rio.br/~roberto/lpeg].

For Windows, static libraries and headers have been provided.

See platform/Windows/lib/instructions.txt for information on building.

Build EDuke32 with the GNU Make invocation "make LUNATIC=1".


code:
diff --git a/lpcap.h b/lpcap.h
index c0a0e38..1bfc109 100644
--- a/lpcap.h
+++ b/lpcap.h
@@ -18,7 +18,7 @@ typedef enum CapKind {

 typedef struct Capture {
   const char *s;  /* subject position */
-  short idx;  /* extra info about capture (group name, arg index, etc.) */
+  int idx;  /* extra info about capture (group name, arg index, etc.) */
   byte kind;  /* kind of capture */
   byte siz;  /* size of full capture + 1 (0 = not a full capture) */
 } Capture;
diff --git a/makefile b/makefile
index 57a18fb..88e8a60 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,8 @@
 LIBNAME = lpeg
-LUADIR = ../lua/
+# Linux example:
+LUADIR = /usr/local/include/luajit-2.0
+# Windows example (from MSYS bash):
+#LUADIR = /f/g/mod/luajit-2.0/src

 COPT = -O2
 # COPT = -DLPEG_DEBUG -g
@@ -19,10 +22,21 @@ CWARNS = -Wall -Wextra -pedantic \
 	-Wmissing-prototypes \
 	-Wnested-externs \
 	-Wstrict-prototypes \
+	-Werror-implicit-function-declaration \
 # -Wunreachable-code \

+# Example:
+# --------
+# 1. Build shared lib (for LunaCON standalone)
+#  $ make linux
+# 2. Remove shared-lib *.o
+#  $ rm *.o
+# 3. Build static lib (for EDuke32/Lunatic)
+#  $ PIC='' make liblpeg.a

-CFLAGS = $(CWARNS) $(COPT) -std=c99 -I$(LUADIR) -fPIC
+PIC ?= -fPIC
+
+CFLAGS = $(CWARNS) $(COPT) -std=c99 -I$(LUADIR) $(PIC)
 CC = gcc

 FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o
@@ -38,13 +52,16 @@ macosx:
 lpeg.so: $(FILES)
 	env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so

+liblpeg.a: $(FILES)
+	$(AR) rc $@ $^
+
 $(FILES): makefile

 test: test.lua re.lua lpeg.so
 	./test.lua

 clean:
-	rm -f $(FILES) lpeg.so
+	rm -f $(FILES) lpeg.so liblpeg.a

 lpcap.o: lpcap.c lpcap.h lptypes.h
@@ -52,4 +69,3 @@ lpcode.o: lpcode.c lptypes.h lpcode.h lptree.h lpvm.h lpcap.h
 lpprint.o: lpprint.c lptypes.h lpprint.h lptree.h lpvm.h lpcap.h
 lptree.o: lptree.c lptypes.h lpcap.h lpcode.h lptree.h lpvm.h lpprint.h
 lpvm.o: lpvm.c lpcap.h lptypes.h lpvm.h lpprint.h lptree.h
-


code:
LunaCON User Manual -- The ``Lunatic Translator''
=================================================
Helixhorned <contact: Duke4.net forums>
:max-width: 56em
:numbered:
:icons:
:toc:
:conf-files: lunatic.conf

Introduction
------------

The Lunatic build of EDuke32 completely reimplements the CON scripting language
using the framework provided by its Lua interface. It does so by generating at
start-up time one Lunatic module from all specified CON files and
mutators.footnote:[In CON, the *`include`* directive amounts to merely textual
inclusion, as is mutator appending with the `-mx` switch. All CON code is
translated in one global context sharing the same namespace.]

LunaCON implements nearly all of modern EDuke32 CON. One of its main aims is
correctness in a broad sense, which encompasses multiple issues.

* LunaCON more strictly validates the CON source at translation time. For
  example, using labels of mismatched type, such as a ++define++d number where
  a `move` is expected, issues an translation error.

* Lexically and syntactically, LunaCON accepts only a subset of what the C-CON
  translator would pass through. This includes rejecting clearly erroneous code
  such as an unfinished directive at the end of a translation unit, but also
  code that implies ambiguities in the language.

* On the run-time side, most checking is done by Lunatic. For example, indexing
  actor gamevars with out-of-bounds values produces an error that gets
  permanently displayed on the screen. Like with Lua code, the
  link:lunatic.html#error[error] entry in the log then contains a traceback
  annotated with line numbers, making it possible for the CON coder to pinpoint
  its location and context.

For these reasons, many existing CON mods and TCs are expected to need
amendments in order to translate and/or run properly. This is generally a
desired thing, since otherwise (most of the time, unintentional) misuse of the
CON system may produce behavior that is either erratic, or appears to work on
the surface but is hiding potential issues.

.A warning
The code generated by LunaCON is unsuitable for human consumption --
specifically, it should not be used as a base for new Lunatic code. Even though
that code runs in Lunatic's protected user environment, it uses private
interfaces that are subject to change in addition to the officially exposed and
documented ones.

Nevertheless, LunaCON is also available as a stand-alone Lua script. Primarily,
this allows it to be used as a checking tool during CON development without the
need to start EDuke32. Also, multiple CON codebases can be checked in a batch
fashion for non-runtime problems.

Usage
-----
:LuaJIT: http://luajit.org
:LPeg: http://www.inf.puc-rio.br/~roberto/lpeg/

The stand-alone LunaCON script, `lunacon.lua`, needs {LuaJIT}[LuaJIT] for
execution and {LPeg}[LPeg] as additional dependency.footnote:[In order to
translate some very large CON files, minor modifications have to be made to
LuaJIT and LPeg. Refer to `lunatic/doc/how_to_build_lunatic.txt` in the EDuke32
source distribution for details.] The script also requires `con_lang.lua` to be
present. It expects one or more names of root CON files together with any
number of options, in any order (arguments starting with a dash are always
interpreted as the latter).

.Example usage
----------
luajit ./lunacon.lua -Wall mymod.con
----------

All arguments after a single `@` argument are taken to name _file lists_.
These are files containing lines either

* a file name of a root CON file which gets processed, or
* a completely blank line or a line starting with `#`, both of which are ignored.

Options
-------

Most options documented in the following can also be passed to the Lunatic
build of EDuke32. Options on the command line are processed in their given
order, from left to right. Many of them can be negated by appeding `no-` after
the ``option category letter'', for example `-Wno-not-redefined`. This index
only lists the positive forms of each non-compound option and labels whether it
is enabled or disabled by default.

General options
~~~~~~~~~~~~~~~

++-I__directory__++ (stand-alone only)::
Specifies a _default directory_ to search for CON files as a last resort. This
can be useful if mods expect part of their included files to reside inside GRP
or ZIP containers, which the stand-alone translator cannot examine. This option
can only be passed once.

Warning options
~~~~~~~~~~~~~~~

These options affect on which occasions warnings, or in some cases, errors
instead of warnings, are produced at translation time.

`-Wall`::
Enables all warning and errors described in this subsection.

`-Wbad-identifier` (default: off)::
Warns whenever an identifier does not match `[A-Za-z_][A-Za-z0-9_]*`. In words,
a ``good'' identifier is expected to start with a letter or an underscore,
followed by zero or more letters, underscores, or digits.

`-Wchained-loadactor` (default: on)::
Warns whenever an `eventloadactor` block appears multiple times for the same
tile number. In LunaCON, these are translated to an `EVENT_LOADACTOR` block
that checks the tile number of its current actor. This event gets chained to
the end of all preceding `EVENT_LOADACTOR` definitions, whereas with C-CON, a
new `eventloadactor` block for the same tile number would override the existing
one.

`-Werror-bad-getactorvar` (default: off)::
When enabled, produces an error whenever a global or per-player gamevar is
attempted to be read using ++getactorvar++. Otherwise, a warning is emitted. In
this case, the generated code is either a read of the (global) gamevar, or an
access of the per-player gamevar with the given index or the current player
index (depending on `-fbad-getactorvar-use-pli`), which is probably not what
the coder intended.

`-Wnot-redefined` (default: on)::
Warns whenever a `define` directive was ignored because it attempted to
redefine an already existing label to a different number. The label can exist
either due to a previous `define`, or because it is a predefined label such as
`NO`.

`-Wnumber-conversion` (default: on)::
Warns whenever a literal decimal number is encountered that is out of the range
for a 32-bit integer, but inside that of an unsigned 32-bit integer. In this
case, 2^32^ is subtracted from the number, producing a negative value without
changing the bit representation.

`-Wnever-used-gamevar` (default: off)::
After translation, issues a warning for every CON-side user gamevar definition
that was never referenced, that is, neither read nor written in the CON code.

`-Wnever-read-gamevar` (default: off)::
After translation, issues a warning for every CON-side user gamevar definition
that was assigned to but never read in the CON code.

`-Wsystem-gamevar` (default: on)::
Warns whenever the initial value of a system gamevar was overridden (by issuing
`gamevar` at file scope), but the provided gamevar flags did not match those of
the kept predefined ones.

Code generation options
~~~~~~~~~~~~~~~~~~~~~~~

These options change the way certain CON code constructs are translated to Lua,
set the output behavior of the stand-alone translator, or toggle various error
conditions.

`-fno` (stand-alone only)::
Disable printing out the generated code and validating its syntax.

`-fno=onlycheck` (stand-alone only)::
Disable printing out the generated code, but validate whether it is
syntactically legal Lua code. A failure of this check represents a bug with
LunaCON itself.

`-fnames` (stand-alone only)::
Instead of generating Lua code, output all ++define++d names that were ever
passed as tile number to `actor`, `useractor` or `eventloadactor` in a form
suitable for the `names.h` file read by Mapster32.

`-ferror-nostate` (default: on)::
If enabled, an attempt to call a `state` that was not previously defined
results in an error. Otherwise, a warning is issued and no code is generated
for the `state` invocation.

`-ferror-nonlocal-userdef` (default: on)::
If enabled, an attept to issue `getuserdef` or `setuserdef` when the current
player doesn't equal the local player generates an error. Otherwise, the
userdef structure can be accessed irrespective of the current player.

`-ferror-negative-tag-write` (default: off)::
If enabled, an attempt to assign a negative value to the `lotag` or `hitag`
member of the sector, wall or (t)sprite structures produces an error.
Normally, it is legal to assign negative values to these members, but since
they are unsigned 16-bit integers on the C side, such values will be converted
to positive ones and may entail undesired behavior.
+
NOTE: From CON as well as Lunatic, `hitag` and `lotag` are seen as *signed*
16-bit integers.

`-fbad-getactorvar-use-pli` (default: on)::
If enabled and `-Werror-bad-getactorvar` is off, a `getactorvar` of a
per-player variable will result the gamevar being indexed with the current
player instead of the provided index. This is the (probably unintended)
behavior of C-CON.

`-fplayervar` (default: on)::
If enabled, per-player `gamevar` definitions really generate `con.playervar`
initializations in the translated Lua code. Otherwise, per-player gamevars are
treated as global gamevars, which can be useful for code that attempts to
access them in contexts with no current player, yielding errors in Lunatic.

`-ftrapv` (default: off)::
Enable _trapping_ behavior for arithmetic operations whose result overflows the
range of a signed 32-bit integer, i.e. generate an error on overflow. By
default, overflow results in undefined behavior. Currently, only multiplication
is handled.

`-fwrapv` (default: off)::
Enable _wrapping_ behavior for arithmetic operations whose result overflows the
range of a signed 32-bit integer, i.e. after each operation, only the 32 lower
bits of the result are kept. Currently, only multiplication is handled. Only
one of `-ftrapv` or `-fwrapv` may be enabled at the same time.

Differences from C-CON
----------------------

Despite the aim to provide as much compatibility to CON as possible where
reasonable, a couple of its ``features'' -- some of which are coincidental --
cannot be implemented without unnaturally bending the implementation into
shape. On the other hand, LunaCON sports some features that C-CON lacks, and
does not exhibit some of its strange quirks or outright bugs.

Syntactic and lexical changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The two command classes
^^^^^^^^^^^^^^^^^^^^^^^

LunaCON makes a clear distinction between ``outer'' commands that have an
effect when translating a CON file (_directives_ such as `gamevar`,
`definesound` or `spritenvg`) and ``inner'' commands meant to be effective at
execution time of `actor`/`useractor`, `state`, `event` and `eventloadactor`
blocks. Thus, issuing directives inside of these will make LunaCON reject the
input file due to a syntax error, as will an attempt to use run-time commands
such as `sizeat` at file scope.

This strict behavior is one hand a consequence of how the LunaCON parser is
structured, but on the other hand it may expose code for which the author
misunderstood its meaning. An example for the first case would be a `gamevar`
inside a block, which one mistakenly could take to have local scope. Gamevars
in CON always have both global scope and lifetime though, so such a mental
model on the coder's part may lead to unexpected bugs. In the second case,
run-time commands at file scope are really translated to bytecode by C-CON, but
as they reside outside of any block, they are never reached -- in other words,
they are dead code.

Currently, the only exception to this rule is that a `definequote` is allowed
inside delimited blocks, which however does not change its semantics in any
way: it still only defines the initial contents of a quote, and does not
magically act like `redefinequote`.

Ambiguous lexical elements
^^^^^^^^^^^^^^^^^^^^^^^^^^

LunaCON is fairly relaxed as to which character sequences constitute valid
identifier names used for e.g. ++define++d labels or variables. It does so out
of the necessity of supporting CON code found ``in the wild''. An identifier

* must not be a token denoting a number,
* must start with an _allowed first character_,
* may contain any number of _allowed following characters_,

where

* allowed first characters are: letters, digits, and those in ```_*?`''
* allowed following characters are the same as allowed first characters, plus
  ``+++++'', ``++-++'' and ``++.++''.

// ^ `+` and `-`

Numbers can be written in either decimal or hexadecimal form, optionally
prepended by a ``++-++'' sign. In the decimal case, the modulus of the number
can be (lexically) any sequence of decimal digits, though there are
restrictions on the permitted values.footnote:[Specifically, it is forbidden to
write a number whose value falls outside the range [--2^31^-1 .. 2^32^--1\].]
Hexadecimal number literals must be prefixed with `0x` or `0X`, and may
optionally be suffixed with an `h`.

The following constructions are not allowed, as they would create ambiguities
with the definitions above:

* A sequence of digits followed by letters to mean the the digits interpreted
  as a number, ignoring the trailing letters, e.g. `1267AT`.
* A hexadecimal constant denoted using only a trailing `h`, for example
  `00000000h`.

Miscellaneous
^^^^^^^^^^^^^

* Read array expressions as well as `switch` statements can be arbitrarily
  nested.

// XXX: 'Read' is confusing. Need better wording.

Run-time changes
~~~~~~~~~~~~~~~~

Behavior on error
^^^^^^^^^^^^^^^^^

As LunaCON is implemented by translating all given CON code to a Lunatic
module, it is the Lunatic runtime that checks for proper use of its services at
execution time and takes care of error handling and reporting. In Lua, an error
link:lunatic.html#nlcf[transfers control] to the end of the innermost
``protected'' call of a Lua chunk.

This is in contrast to C-CON, which for some errors would print a message to
the log, but otherwise would continue execution as if nothing had happened. In
LunaCON, the code following an error is *not* executed. This way, the author of
the CON code is notified of the presence of the bug, and by fixing it
eventually obtains cleaner code.

Quote behavior
^^^^^^^^^^^^^^

* The `redefinequote` command can be issued even if the given quote number has
  not been previously allocated using the `definequote` directive. This makes
  it possible to use a range of quote numbers on the fly without the need to
  declare their future use.

Miscellaneous
^^^^^^^^^^^^^

:r5097: http://svn.eduke32.com/listing.php?repname=eduke32&rev=5097

* Issuing `break` inside a part of an event chain (defined using multiple
  `onevent` blocks for one event kind) does not abort the whole chain. (Since
  EDuke32 {r5097}[r5097], this is the behavior of C-CON as well.)

Unavailable functionality
~~~~~~~~~~~~~~~~~~~~~~~~~

C-CON's `jump` and `getcurraddress` are not available and will not be
implemented.

The following commands are not yet implemented. Those highlighted in bold give
errors, while the others merely warn either at translation or execution
time.

Directives
^^^^^^^^^^
*`includedefault`*, `definecheat`, `setcfgname`, `setgamename`

Run-time commands
^^^^^^^^^^^^^^^^^

*`activatecheat`*, *`clearmapstate`*, *`startcutscene`*, *`ifcutscene`*, `save`,
`savenn`, *`lineintersect`*, *`rayintersect`*, *`sectorofwall`*.

Additionally, various multiplayer-related commands either unconditionally
return results as if no multiplayer game is in progress, or are non-functional
for more than the first player. Also, some MP-related variables are merely
predefined constants.


code:
[replacements]
# Make -- an en dash for good looking minus signs.
--=–