summaryrefslogtreecommitdiff
path: root/script-beta/parser/compile.lua
diff options
context:
space:
mode:
Diffstat (limited to 'script-beta/parser/compile.lua')
-rw-r--r--script-beta/parser/compile.lua549
1 files changed, 549 insertions, 0 deletions
diff --git a/script-beta/parser/compile.lua b/script-beta/parser/compile.lua
new file mode 100644
index 00000000..bcd9ecc8
--- /dev/null
+++ b/script-beta/parser/compile.lua
@@ -0,0 +1,549 @@
+local guide = require 'parser.guide'
+local type = type
+
+local specials = {
+ ['_G'] = true,
+ ['rawset'] = true,
+ ['rawget'] = true,
+ ['setmetatable'] = true,
+ ['require'] = true,
+ ['dofile'] = true,
+ ['loadfile'] = true,
+ ['pcall'] = true,
+ ['xpcall'] = true,
+}
+
+_ENV = nil
+
+local LocalLimit = 200
+local pushError, Compile, CompileBlock, Block, GoToTag, ENVMode, Compiled, LocalCount, Version, Root
+
+local function addRef(node, obj)
+ if not node.ref then
+ node.ref = {}
+ end
+ node.ref[#node.ref+1] = obj
+ obj.node = node
+end
+
+local function addSpecial(name, obj)
+ if not Root.specials then
+ Root.specials = {}
+ end
+ if not Root.specials[name] then
+ Root.specials[name] = {}
+ end
+ Root.specials[name][#Root.specials[name]+1] = obj
+ obj.special = name
+end
+
+local vmMap = {
+ ['getname'] = function (obj)
+ local loc = guide.getLocal(obj, obj[1], obj.start)
+ if loc then
+ obj.type = 'getlocal'
+ obj.loc = loc
+ addRef(loc, obj)
+ if loc.special then
+ addSpecial(loc.special, obj)
+ end
+ else
+ obj.type = 'getglobal'
+ if ENVMode == '_ENV' then
+ local node = guide.getLocal(obj, '_ENV', obj.start)
+ if node then
+ addRef(node, obj)
+ end
+ end
+ local name = obj[1]
+ if specials[name] then
+ addSpecial(name, obj)
+ end
+ end
+ return obj
+ end,
+ ['getfield'] = function (obj)
+ Compile(obj.node, obj)
+ end,
+ ['call'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.args, obj)
+ end,
+ ['callargs'] = function (obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ end,
+ ['binary'] = function (obj)
+ Compile(obj[1], obj)
+ Compile(obj[2], obj)
+ end,
+ ['unary'] = function (obj)
+ Compile(obj[1], obj)
+ end,
+ ['varargs'] = function (obj)
+ local func = guide.getParentFunction(obj)
+ if func then
+ local index, vararg = guide.getFunctionVarArgs(func)
+ if not index then
+ pushError {
+ type = 'UNEXPECT_DOTS',
+ start = obj.start,
+ finish = obj.finish,
+ }
+ end
+ if vararg then
+ if not vararg.ref then
+ vararg.ref = {}
+ end
+ vararg.ref[#vararg.ref+1] = obj
+ end
+ end
+ end,
+ ['paren'] = function (obj)
+ Compile(obj.exp, obj)
+ end,
+ ['getindex'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.index, obj)
+ end,
+ ['setindex'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.index, obj)
+ Compile(obj.value, obj)
+ end,
+ ['getmethod'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.method, obj)
+ end,
+ ['setmethod'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.method, obj)
+ local value = obj.value
+ value.localself = {
+ type = 'local',
+ start = 0,
+ finish = 0,
+ method = obj,
+ effect = obj.finish,
+ tag = 'self',
+ [1] = 'self',
+ }
+ Compile(value, obj)
+ end,
+ ['function'] = function (obj)
+ local lastBlock = Block
+ local LastLocalCount = LocalCount
+ Block = obj
+ LocalCount = 0
+ if obj.localself then
+ Compile(obj.localself, obj)
+ obj.localself = nil
+ end
+ Compile(obj.args, obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ Block = lastBlock
+ LocalCount = LastLocalCount
+ end,
+ ['funcargs'] = function (obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ end,
+ ['table'] = function (obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ end,
+ ['tablefield'] = function (obj)
+ Compile(obj.value, obj)
+ end,
+ ['tableindex'] = function (obj)
+ Compile(obj.index, obj)
+ Compile(obj.value, obj)
+ end,
+ ['index'] = function (obj)
+ Compile(obj.index, obj)
+ end,
+ ['select'] = function (obj)
+ local vararg = obj.vararg
+ if vararg.parent then
+ if not vararg.extParent then
+ vararg.extParent = {}
+ end
+ vararg.extParent[#vararg.extParent+1] = obj
+ else
+ Compile(vararg, obj)
+ end
+ end,
+ ['setname'] = function (obj)
+ Compile(obj.value, obj)
+ local loc = guide.getLocal(obj, obj[1], obj.start)
+ if loc then
+ obj.type = 'setlocal'
+ obj.loc = loc
+ addRef(loc, obj)
+ if loc.attrs then
+ local const
+ for i = 1, #loc.attrs do
+ local attr = loc.attrs[i][1]
+ if attr == 'const'
+ or attr == 'close' then
+ const = true
+ break
+ end
+ end
+ if const then
+ pushError {
+ type = 'SET_CONST',
+ start = obj.start,
+ finish = obj.finish,
+ }
+ end
+ end
+ else
+ obj.type = 'setglobal'
+ if ENVMode == '_ENV' then
+ local node = guide.getLocal(obj, '_ENV', obj.start)
+ if node then
+ addRef(node, obj)
+ end
+ end
+ end
+ end,
+ ['local'] = function (obj)
+ local attrs = obj.attrs
+ if attrs then
+ for i = 1, #attrs do
+ Compile(attrs[i], obj)
+ end
+ end
+ if Block then
+ if not Block.locals then
+ Block.locals = {}
+ end
+ Block.locals[#Block.locals+1] = obj
+ LocalCount = LocalCount + 1
+ if LocalCount > LocalLimit then
+ pushError {
+ type = 'LOCAL_LIMIT',
+ start = obj.start,
+ finish = obj.finish,
+ }
+ end
+ end
+ if obj.localfunction then
+ obj.localfunction = nil
+ end
+ Compile(obj.value, obj)
+ if obj.value and obj.value.special then
+ addSpecial(obj.value.special, obj)
+ end
+ end,
+ ['setfield'] = function (obj)
+ Compile(obj.node, obj)
+ Compile(obj.value, obj)
+ end,
+ ['do'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['return'] = function (obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ if Block and Block[#Block] ~= obj then
+ pushError {
+ type = 'ACTION_AFTER_RETURN',
+ start = obj.start,
+ finish = obj.finish,
+ }
+ end
+ local func = guide.getParentFunction(obj)
+ if func then
+ if not func.returns then
+ func.returns = {}
+ end
+ func.returns[#func.returns+1] = obj
+ end
+ end,
+ ['label'] = function (obj)
+ local block = guide.getBlock(obj)
+ if block then
+ if not block.labels then
+ block.labels = {}
+ end
+ local name = obj[1]
+ local label = guide.getLabel(block, name)
+ if label then
+ if Version == 'Lua 5.4'
+ or block == guide.getBlock(label) then
+ pushError {
+ type = 'REDEFINED_LABEL',
+ start = obj.start,
+ finish = obj.finish,
+ relative = {
+ {
+ label.start,
+ label.finish,
+ }
+ }
+ }
+ end
+ end
+ block.labels[name] = obj
+ end
+ end,
+ ['goto'] = function (obj)
+ GoToTag[#GoToTag+1] = obj
+ end,
+ ['if'] = function (obj)
+ for i = 1, #obj do
+ Compile(obj[i], obj)
+ end
+ end,
+ ['ifblock'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ Compile(obj.filter, obj)
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['elseifblock'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ Compile(obj.filter, obj)
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['elseblock'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['loop'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ Compile(obj.loc, obj)
+ Compile(obj.max, obj)
+ Compile(obj.step, obj)
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['in'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ local keys = obj.keys
+ for i = 1, #keys do
+ Compile(keys[i], obj)
+ end
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['while'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ Compile(obj.filter, obj)
+ CompileBlock(obj, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['repeat'] = function (obj)
+ local lastBlock = Block
+ Block = obj
+ CompileBlock(obj, obj)
+ Compile(obj.filter, obj)
+ if Block.locals then
+ LocalCount = LocalCount - #Block.locals
+ end
+ Block = lastBlock
+ end,
+ ['break'] = function (obj)
+ local block = guide.getBreakBlock(obj)
+ if block then
+ if not block.breaks then
+ block.breaks = {}
+ end
+ block.breaks[#block.breaks+1] = obj
+ else
+ pushError {
+ type = 'BREAK_OUTSIDE',
+ start = obj.start,
+ finish = obj.finish,
+ }
+ end
+ end,
+ ['main'] = function (obj)
+ Block = obj
+ if ENVMode == '_ENV' then
+ Compile({
+ type = 'local',
+ start = 0,
+ finish = 0,
+ effect = 0,
+ tag = '_ENV',
+ special= '_G',
+ [1] = '_ENV',
+ }, obj)
+ end
+ --- _ENV 是上值,不计入局部变量计数
+ LocalCount = 0
+ CompileBlock(obj, obj)
+ Block = nil
+ end,
+}
+
+function CompileBlock(obj, parent)
+ for i = 1, #obj do
+ local act = obj[i]
+ local f = vmMap[act.type]
+ if f then
+ act.parent = parent
+ f(act)
+ end
+ end
+end
+
+function Compile(obj, parent)
+ if not obj then
+ return nil
+ end
+ if Compiled[obj] then
+ return
+ end
+ Compiled[obj] = true
+ obj.parent = parent
+ local f = vmMap[obj.type]
+ if not f then
+ return
+ end
+ f(obj)
+end
+
+local function compileGoTo(obj)
+ local name = obj[1]
+ local label = guide.getLabel(obj, name)
+ if not label then
+ pushError {
+ type = 'NO_VISIBLE_LABEL',
+ start = obj.start,
+ finish = obj.finish,
+ info = {
+ label = name,
+ }
+ }
+ return
+ end
+ if not label.ref then
+ label.ref = {}
+ end
+ label.ref[#label.ref+1] = obj
+
+ -- 如果有局部变量在 goto 与 label 之间声明,
+ -- 并在 label 之后使用,则算作语法错误
+
+ -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量
+ if obj.start > label.start then
+ return
+ end
+
+ local block = guide.getBlock(obj)
+ local locals = block and block.locals
+ if not locals then
+ return
+ end
+
+ for i = 1, #locals do
+ local loc = locals[i]
+ -- 检查局部变量声明位置为 goto 与 label 之间
+ if loc.start < obj.start or loc.finish > label.finish then
+ goto CONTINUE
+ end
+ -- 检查局部变量的使用位置在 label 之后
+ local refs = loc.ref
+ if not refs then
+ goto CONTINUE
+ end
+ for j = 1, #refs do
+ local ref = refs[j]
+ if ref.finish > label.finish then
+ pushError {
+ type = 'JUMP_LOCAL_SCOPE',
+ start = obj.start,
+ finish = obj.finish,
+ info = {
+ loc = loc[1],
+ },
+ relative = {
+ {
+ start = label.start,
+ finish = label.finish,
+ },
+ {
+ start = loc.start,
+ finish = loc.finish,
+ }
+ },
+ }
+ return
+ end
+ end
+ ::CONTINUE::
+ end
+end
+
+local function PostCompile()
+ for i = 1, #GoToTag do
+ compileGoTo(GoToTag[i])
+ end
+end
+
+return function (self, lua, mode, version)
+ local state, err = self:parse(lua, mode, version)
+ if not state then
+ return nil, err
+ end
+ pushError = state.pushError
+ if version == 'Lua 5.1' or version == 'LuaJIT' then
+ ENVMode = 'fenv'
+ else
+ ENVMode = '_ENV'
+ end
+ Compiled = {}
+ GoToTag = {}
+ LocalCount = 0
+ Version = version
+ Root = state.ast
+ if type(state.ast) == 'table' then
+ Compile(state.ast)
+ end
+ PostCompile()
+ Compiled = nil
+ GoToTag = nil
+ return state
+end