diff options
Diffstat (limited to 'script/src/parser/ast.lua')
-rw-r--r-- | script/src/parser/ast.lua | 1912 |
1 files changed, 1912 insertions, 0 deletions
diff --git a/script/src/parser/ast.lua b/script/src/parser/ast.lua new file mode 100644 index 00000000..38cd8576 --- /dev/null +++ b/script/src/parser/ast.lua @@ -0,0 +1,1912 @@ +local tonumber = tonumber +local string_char = string.char +local utf8_char = utf8.char +local type = type +local table = table + +local Errs +local State +local function pushError(err) + if err.finish < err.start then + err.finish = err.start + end + local last = Errs[#Errs] + if last then + if last.start <= err.start and last.finish >= err.finish then + return + end + end + err.level = err.level or 'error' + Errs[#Errs+1] = err + return err +end + +-- goto 单独处理 +local RESERVED = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local VersionOp = { + ['&'] = {'Lua 5.3', 'Lua 5.4'}, + ['~'] = {'Lua 5.3', 'Lua 5.4'}, + ['|'] = {'Lua 5.3', 'Lua 5.4'}, + ['<<'] = {'Lua 5.3', 'Lua 5.4'}, + ['>>'] = {'Lua 5.3', 'Lua 5.4'}, + ['//'] = {'Lua 5.3', 'Lua 5.4'}, +} + +local function checkOpVersion(op, start) + local versions = VersionOp[op] + if not versions then + return + end + for i = 1, #versions do + if versions[i] == State.Version then + return + end + end + pushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = start + #op - 1, + version = versions, + info = { + version = State.Version, + } + } +end + +local Exp + +local function expSplit(list, start, finish, level) + if start == finish then + return list[start] + end + local info = Exp[level] + if not info then + return + end + local func = info[1] + return func(list, start, finish, level) +end + +local function binaryForward(list, start, finish, level) + local info = Exp[level] + for i = finish-1, start+2, -1 do + local op = list[i] + if info[op] then + local e1 = expSplit(list, start, i-2, level) + if not e1 then + goto CONTINUE + end + local e2 = expSplit(list, i+1, finish, level+1) + if not e2 then + goto CONTINUE + end + checkOpVersion(op, list[i-1]) + return { + type = 'binary', + op = op, + start = e1.start, + finish = e2.finish, + [1] = e1, + [2] = e2, + } + end + ::CONTINUE:: + end + return expSplit(list, start, finish, level+1) +end + +local function binaryBackward(list, start, finish, level) + local info = Exp[level] + for i = start+2, finish-1 do + local op = list[i] + if info[op] then + local e1 = expSplit(list, start, i-2, level+1) + if not e1 then + goto CONTINUE + end + local e2 = expSplit(list, i+1, finish, level) + if not e2 then + goto CONTINUE + end + checkOpVersion(op, list[i-1]) + return { + type = 'binary', + op = op, + start = e1.start, + finish = e2.finish, + [1] = e1, + [2] = e2, + } + end + ::CONTINUE:: + end + return expSplit(list, start, finish, level+1) +end + +local function unary(list, start, finish, level) + local info = Exp[level] + local op = list[start+1] + if info[op] then + local e1 = expSplit(list, start+2, finish, level) + if e1 then + checkOpVersion(op, list[start]) + return { + type = 'unary', + op = op, + start = list[start], + finish = e1.finish, + [1] = e1, + } + end + end + return expSplit(list, start, finish, level+1) +end + +local function checkMissEnd(start) + if not State.MissEndErr then + return + end + local err = State.MissEndErr + State.MissEndErr = nil + local _, finish = State.Lua:find('[%w_]+', start) + if not finish then + return + end + err.info.related = { start, finish } + pushError { + type = 'MISS_END', + start = start, + finish = finish, + } +end + +Exp = { + { + ['or'] = true, + binaryForward, + }, + { + ['and'] = true, + binaryForward, + }, + { + ['<='] = true, + ['>='] = true, + ['<'] = true, + ['>'] = true, + ['~='] = true, + ['=='] = true, + binaryForward, + }, + { + ['|'] = true, + binaryForward, + }, + { + ['~'] = true, + binaryForward, + }, + { + ['&'] = true, + binaryForward, + }, + { + ['<<'] = true, + ['>>'] = true, + binaryForward, + }, + { + ['..'] = true, + binaryBackward, + }, + { + ['+'] = true, + ['-'] = true, + binaryForward, + }, + { + ['*'] = true, + ['//'] = true, + ['/'] = true, + ['%'] = true, + binaryForward, + }, + { + ['^'] = true, + binaryBackward, + }, + { + ['not'] = true, + ['#'] = true, + ['~'] = true, + ['-'] = true, + unary, + }, +} + +local Defs = { + Nil = function (pos) + return { + type = 'nil', + start = pos, + finish = pos + 2, + } + end, + True = function (pos) + return { + type = 'boolean', + start = pos, + finish = pos + 3, + [1] = true, + } + end, + False = function (pos) + return { + type = 'boolean', + start = pos, + finish = pos + 4, + [1] = false, + } + end, + LongComment = function (beforeEq, afterEq, str, finish, missPos) + State.Comments[#State.Comments+1] = { + start = beforeEq, + finish = finish, + } + if missPos then + local endSymbol = ']' .. ('='):rep(afterEq-beforeEq) .. ']' + local s, _, w = str:find('(%][%=]*%])[%c%s]*$') + if s then + pushError { + type = 'ERR_LCOMMENT_END', + start = missPos - #str + s - 1, + finish = missPos - #str + s + #w - 2, + info = { + symbol = endSymbol, + }, + fix = { + title = 'FIX_LCOMMENT_END', + { + start = missPos - #str + s - 1, + finish = missPos - #str + s + #w - 2, + text = endSymbol, + } + }, + } + end + pushError { + type = 'MISS_SYMBOL', + start = missPos, + finish = missPos, + info = { + symbol = endSymbol, + }, + fix = { + title = 'ADD_LCOMMENT_END', + { + start = missPos, + finish = missPos, + text = endSymbol, + } + }, + } + end + end, + CLongComment = function (start1, finish1, start2, finish2) + pushError { + type = 'ERR_C_LONG_COMMENT', + start = start1, + finish = finish2 - 1, + fix = { + title = 'FIX_C_LONG_COMMENT', + { + start = start1, + finish = finish1 - 1, + text = '--[[', + }, + { + start = start2, + finish = finish2 - 1, + text = '--]]' + }, + } + } + end, + CCommentPrefix = function (start, finish) + pushError { + type = 'ERR_COMMENT_PREFIX', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_COMMENT_PREFIX', + { + start = start, + finish = finish - 1, + text = '--', + }, + } + } + return false + end, + String = function (start, quote, str, finish) + return { + type = 'string', + start = start, + finish = finish - 1, + [1] = str, + [2] = quote, + } + end, + LongString = function (beforeEq, afterEq, str, missPos) + if missPos then + local endSymbol = ']' .. ('='):rep(afterEq-beforeEq) .. ']' + local s, _, w = str:find('(%][%=]*%])[%c%s]*$') + if s then + pushError { + type = 'ERR_LSTRING_END', + start = missPos - #str + s - 1, + finish = missPos - #str + s + #w - 2, + info = { + symbol = endSymbol, + }, + fix = { + title = 'FIX_LSTRING_END', + { + start = missPos - #str + s - 1, + finish = missPos - #str + s + #w - 2, + text = endSymbol, + } + }, + } + end + pushError { + type = 'MISS_SYMBOL', + start = missPos, + finish = missPos, + info = { + symbol = endSymbol, + }, + fix = { + title = 'ADD_LSTRING_END', + { + start = missPos, + finish = missPos, + text = endSymbol, + } + }, + } + end + return '[' .. ('='):rep(afterEq-beforeEq) .. '[', str + end, + Char10 = function (char) + char = tonumber(char) + if not char or char < 0 or char > 255 then + return '' + end + return string_char(char) + end, + Char16 = function (pos, char) + if State.Version == 'Lua 5.1' then + pushError { + type = 'ERR_ESC', + start = pos-1, + finish = pos, + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.Version, + } + } + return char + end + return string_char(tonumber(char, 16)) + end, + CharUtf8 = function (pos, char) + if State.Version ~= 'Lua 5.3' + and State.Version ~= 'Lua 5.4' + and State.Version ~= 'LuaJIT' + then + pushError { + type = 'ERR_ESC', + start = pos-3, + finish = pos-2, + version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.Version, + } + } + return char + end + if #char == 0 then + pushError { + type = 'UTF8_SMALL', + start = pos-3, + finish = pos, + } + return '' + end + local v = tonumber(char, 16) + if not v then + for i = 1, #char do + if not tonumber(char:sub(i, i), 16) then + pushError { + type = 'MUST_X16', + start = pos + i - 1, + finish = pos + i - 1, + } + end + end + return '' + end + if State.Version == 'Lua 5.4' then + if v < 0 or v > 0x7FFFFFFF then + pushError { + type = 'UTF8_MAX', + start = pos-3, + finish = pos+#char, + info = { + min = '00000000', + max = '7FFFFFFF', + } + } + end + else + if v < 0 or v > 0x10FFFF then + pushError { + type = 'UTF8_MAX', + start = pos-3, + finish = pos+#char, + version = v <= 0x7FFFFFFF and 'Lua 5.4' or nil, + info = { + min = '000000', + max = '10FFFF', + } + } + end + end + if v >= 0 and v <= 0x10FFFF then + return utf8_char(v) + end + return '' + end, + Number = function (start, number, finish) + local n = tonumber(number) + if n then + State.LastNumber = { + type = 'number', + start = start, + finish = finish - 1, + [1] = n, + } + return State.LastNumber + else + pushError { + type = 'MALFORMED_NUMBER', + start = start, + finish = finish - 1, + } + State.LastNumber = { + type = 'number', + start = start, + finish = finish - 1, + [1] = 0, + } + return State.LastNumber + end + end, + FFINumber = function (start, symbol) + if math.type(State.LastNumber[1]) == 'float' then + pushError { + type = 'UNKNOWN_SYMBOL', + start = start, + finish = start + #symbol - 1, + info = { + symbol = symbol, + } + } + State.LastNumber[1] = 0 + return + end + if State.Version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = start + #symbol - 1, + version = 'LuaJIT', + info = { + version = State.Version, + } + } + State.LastNumber[1] = 0 + end + end, + ImaginaryNumber = function (start, symbol) + if State.Version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = start + #symbol - 1, + version = 'LuaJIT', + info = { + version = State.Version, + } + } + end + State.LastNumber[1] = 0 + end, + Name = function (start, str, finish) + local isKeyWord + if RESERVED[str] then + isKeyWord = true + elseif str == 'goto' then + if State.Version ~= 'Lua 5.1' and State.Version ~= 'LuaJIT' then + isKeyWord = true + end + end + if isKeyWord then + pushError { + type = 'KEYWORD', + start = start, + finish = finish - 1, + } + end + return { + type = 'name', + start = start, + finish = finish - 1, + [1] = str, + } + end, + Simple = function (first, ...) + if ... then + local obj = { + type = 'simple', + start = first.start, + first, ..., + } + local last = obj[#obj] + obj.finish = last.finish + return obj + elseif first == '' then + return nil + else + return first + end + end, + SimpleCall = function (simple) + if not simple then + return nil + end + if simple.type ~= 'simple' then + pushError { + type = 'EXP_IN_ACTION', + start = simple.start, + finish = simple.finish, + } + return simple + end + local last = simple[#simple] + if last.type == 'call' then + return simple + end + local colon = simple[#simple-1] + if colon and colon.type == ':' then + -- 型如 `obj:method`,将错误让给MISS_SYMBOL + return simple + end + pushError { + type = 'EXP_IN_ACTION', + start = simple[1].start, + finish = last.finish, + } + return simple + end, + Exp = function (first, ...) + if not ... then + return first + end + local list = {first, ...} + return expSplit(list, 1, #list, 1) + end, + Prefix = function (start, exp, finish) + exp.brackets = true + return exp + end, + Index = function (start, exp, finish) + return { + type = 'index', + start = start, + finish = finish - 1, + [1] = exp, + } + end, + Call = function (start, arg, finish) + if arg == nil then + return { + type = 'call', + start = start, + finish = finish - 1, + } + end + if arg.type == 'list' then + arg.type = 'call' + arg.start = start + arg.finish = finish - 1 + return arg + end + local obj = { + type = 'call', + start = start, + finish = finish - 1, + [1] = arg, + } + return obj + end, + DOTS = function (start) + return { + type = '...', + start = start, + finish = start + 2, + } + end, + DotsAsArg = function (obj) + State.Dots[#State.Dots] = true + return obj + end, + DotsAsExp = function (obj) + if not State.Dots[#State.Dots] then + pushError { + type = 'UNEXPECT_DOTS', + start = obj.start, + finish = obj.finish, + } + end + return obj + end, + COLON = function (start) + return { + type = ':', + start = start, + finish = start, + } + end, + DOT = function (start) + return { + type = '.', + start = start, + finish = start, + } + end, + Function = function (start, argStart, arg, argFinish, ...) + local obj = { + type = 'function', + start = start, + arg = arg, + argStart = argStart - 1, + argFinish = argFinish, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + if obj.argFinish > obj.finish then + obj.argFinish = obj.finish + end + checkMissEnd(start) + return obj + end, + NamedFunction = function (start, name, argStart, arg, argFinish, ...) + local obj = { + type = 'function', + start = start, + name = name, + arg = arg, + argStart = argStart - 1, + argFinish = argFinish, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + if obj.argFinish > obj.finish then + obj.argFinish = obj.finish + end + checkMissEnd(start) + return obj + end, + LocalFunction = function (start, name, argStart, arg, argFinish, ...) + local obj = { + type = 'localfunction', + start = start, + name = name, + arg = arg, + argStart = argStart - 1, + argFinish = argFinish, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + if obj.argFinish > obj.finish then + obj.argFinish = obj.finish + end + + if name.type ~= 'name' then + pushError { + type = 'UNEXPECT_LFUNC_NAME', + start = name.start, + finish = name.finish, + } + end + + checkMissEnd(start) + return obj + end, + Table = function (start, ...) + local args = {...} + local max = #args + local finish = args[max] - 1 + local table = { + type = 'table', + start = start, + finish = finish + } + start = start + 1 + local wantField = true + for i = 1, max-1 do + local arg = args[i] + local isField = type(arg) == 'table' + local isEmmy = isField and arg.type:sub(1, 4) == 'emmy' + if wantField and not isField then + pushError { + type = 'MISS_EXP', + start = start, + finish = arg - 1, + } + elseif not wantField and isField and not isEmmy then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = start, + finish = arg.start-1, + } + end + if isField then + table[#table+1] = arg + if not isEmmy then + wantField = false + start = arg.finish + 1 + end + else + wantField = true + start = arg + end + end + return table + end, + NewField = function (key, value) + return { + type = 'pair', + start = key.start, + finish = value.finish, + key, value, + } + end, + NewIndex = function (key, value) + return { + type = 'pair', + start = key.start, + finish = value.finish, + key, value, + } + end, + List = function (first, second, ...) + if second then + local list = { + type = 'list', + start = first.start, + first, second, ... + } + local last = list[#list] + list.finish = last.finish + return list + elseif type(first) == 'table' then + return first + else + return nil + end + end, + ArgList = function (...) + if ... == '' then + return nil + end + local args = table.pack(...) + local list = {} + local max = args.n + args.n = nil + local wantName = true + for i = 1, max do + local obj = args[i] + if type(obj) == 'number' then + if wantName then + pushError { + type = 'MISS_NAME', + start = obj, + finish = obj, + } + end + wantName = true + else + if not wantName then + pushError { + type = 'MISS_SYMBOL', + start = obj.start-1, + finish = obj.start-1, + info = { + symbol = ',', + } + } + end + wantName = false + list[#list+1] = obj + if obj.type == '...' then + if i < max then + local a = args[i+1] + local b = args[max] + pushError { + type = 'ARGS_AFTER_DOTS', + start = type(a) == 'number' and a or a.start, + finish = type(b) == 'number' and b or b.finish, + } + end + break + end + end + end + if wantName then + local last = args[max] + pushError { + type = 'MISS_NAME', + start = last+1, + finish = last+1, + } + end + if #list == 0 then + return nil + elseif #list == 1 then + return list[1] + else + list.type = 'list' + list.start = list[1].start + list.finish = list[#list].finish + return list + end + end, + CallArgList = function (start, ...) + local args = {...} + local max = #args + local finish = args[max] - 1 + local exps = { + type = 'list', + start = start, + finish = finish, + } + local wantExp = true + for i = 1, max-1 do + local arg = args[i] + local isExp = type(arg) == 'table' + if wantExp and not isExp then + pushError { + type = 'MISS_EXP', + start = start, + finish = arg - 1, + } + elseif not wantExp and isExp then + pushError { + type = 'MISS_SYMBOL', + start = start, + finish = arg.start-1, + info = { + symbol = ',', + } + } + end + if isExp then + exps[#exps+1] = arg + wantExp = false + start = arg.finish + 1 + else + wantExp = true + start = arg + end + end + if wantExp then + pushError { + type = 'MISS_EXP', + start = start, + finish = finish, + } + end + if #exps == 0 then + return nil + elseif #exps == 1 then + return exps[1] + else + return exps + end + end, + Nothing = function () + return nil + end, + None = function() + return + end, + Skip = function () + return false + end, + Set = function (keys, values) + return { + type = 'set', + keys, values, + } + end, + LocalTag = function (...) + if not ... or ... == '' then + return nil + end + local tags = {...} + for i, tag in ipairs(tags) do + if State.Version ~= 'Lua 5.4' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = tag.start, + finish = tag.finish, + version = 'Lua 5.4', + info = { + version = State.Version, + } + } + elseif tag[1] ~= 'const' and tag[1] ~= 'close' then + pushError { + type = 'UNKNOWN_TAG', + start = tag.start, + finish = tag.finish, + info = { + tag = tag[1], + } + } + elseif i > 1 then + pushError { + type = 'MULTI_TAG', + start = tag.start, + finish = tag.finish, + info = { + tag = tag[1], + } + } + end + end + return tags + end, + LocalName = function (name, tags) + name.tags = tags + return name + end, + Local = function (keys, values) + return { + type = 'local', + keys, values, + } + end, + DoBody = function (...) + if ... == '' then + return { + type = 'do', + } + else + return { + type = 'do', + ... + } + end + end, + Do = function (start, action, finish) + action.start = start + action.finish = finish - 1 + checkMissEnd(start) + return action + end, + Break = function (finish, ...) + if State.Break > 0 then + local breakChunk = { + type = 'break', + } + if not ... then + return breakChunk + end + local action = select(-1, ...) + if not action then + return breakChunk + end + if State.Version == 'Lua 5.1' or State.Version == 'LuaJIT' then + pushError { + type = 'ACTION_AFTER_BREAK', + start = finish - #'break', + finish = finish - 1, + } + end + return breakChunk, action + else + pushError { + type = 'BREAK_OUTSIDE', + start = finish - #'break', + finish = finish - 1, + } + if not ... then + return false + end + local action = select(-1, ...) + if not action then + return false + end + return action + end + end, + BreakStart = function () + State.Break = State.Break + 1 + end, + BreakEnd = function () + State.Break = State.Break - 1 + end, + Return = function (start, exp, finish) + if not finish then + finish = exp + exp = { + type = 'return', + start = start, + finish = finish - 1, + } + else + if exp.type == 'list' then + exp.type = 'return' + exp.start = start + exp.finish = finish - 1 + else + exp = { + type = 'return', + start = start, + finish = finish - 1, + [1] = exp, + } + end + end + return exp + end, + Label = function (start, name, finish) + if State.Version == 'Lua 5.1' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = finish - 1, + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.Version, + } + } + return false + end + name.type = 'label' + local labels = State.Label[#State.Label] + local str = name[1] + if labels[str] then + --pushError { + -- type = 'REDEFINE_LABEL', + -- start = name.start, + -- finish = name.finish, + -- info = { + -- label = str, + -- related = {labels[str].start, labels[str].finish}, + -- } + --} + else + labels[str] = name + end + return name + end, + GoTo = function (start, name, finish) + if State.Version == 'Lua 5.1' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = finish - 1, + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.Version, + } + } + return false + end + name.type = 'goto' + local labels = State.Label[#State.Label] + labels[#labels+1] = name + return name + end, + -- TODO 这里的检查不完整,但是完整的检查比较复杂,开销比较高 + -- 不能jump到另一个局部变量的作用域 + -- 函数会切断goto与label + -- 不能从block外jump到block内,但是可以从block内jump到block外 + BlockStart = function () + State.Label[#State.Label+1] = {} + State.Dots[#State.Dots+1] = false + end, + BlockEnd = function () + local labels = State.Label[#State.Label] + State.Label[#State.Label] = nil + State.Dots[#State.Dots] = nil + for i = 1, #labels do + local name = labels[i] + local str = name[1] + if not labels[str] then + pushError { + type = 'NO_VISIBLE_LABEL', + start = name.start, + finish = name.finish, + info = { + label = str, + } + } + end + end + end, + IfBlock = function (exp, start, ...) + local obj = { + filter = exp, + start = start, + ... + } + local max = #obj + obj.finish = obj[max] + obj[max] = nil + return obj + end, + ElseIfBlock = function (exp, start, ...) + local obj = { + filter = exp, + start = start, + ... + } + local max = #obj + obj.finish = obj[max] + obj[max] = nil + return obj + end, + ElseBlock = function (start, ...) + local obj = { + start = start, + ... + } + local max = #obj + obj.finish = obj[max] + obj[max] = nil + return obj + end, + If = function (start, ...) + local obj = { + type = 'if', + start = start, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + checkMissEnd(start) + return obj + end, + Loop = function (start, arg, min, max, step, ...) + local obj = { + type = 'loop', + start = start, + arg = arg, + min = min, + max = max, + step = step, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + checkMissEnd(start) + return obj + end, + In = function (start, arg, exp, ...) + local obj = { + type = 'in', + start = start, + arg = arg, + exp = exp, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + checkMissEnd(start) + return obj + end, + While = function (start, filter, ...) + local obj = { + type = 'while', + start = start, + filter = filter, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj[max] = nil + checkMissEnd(start) + return obj + end, + Repeat = function (start, ...) + local obj = { + type = 'repeat', + start = start, + ... + } + local max = #obj + obj.finish = obj[max] - 1 + obj.filter = obj[max-1] + obj[max] = nil + obj[max-1] = nil + return obj + end, + Lua = function (...) + if ... == '' then + return {} + end + return {...} + end, + + -- EmmyLua 支持 + EmmyName = function (start, str) + return { + type = 'emmyName', + start = start, + finish = start + #str - 1, + [1] = str, + } + end, + DirtyEmmyName = function (pos) + pushError { + type = 'MISS_NAME', + level = 'warning', + start = pos, + finish = pos, + } + return { + type = 'emmyName', + start = pos-1, + finish = pos-1, + [1] = '' + } + end, + EmmyClass = function (class, startPos, extends) + if extends and extends[1] == '' then + extends.start = startPos + end + return { + type = 'emmyClass', + start = class.start, + finish = (extends or class).finish, + [1] = class, + [2] = extends, + } + end, + EmmyType = function (typeDef) + return typeDef + end, + EmmyCommonType = function (...) + local result = { + type = 'emmyType', + ... + } + for i = 1, #result // 2 do + local startPos = result[i * 2] + local emmyName = result[i * 2 + 1] + if emmyName[1] == '' then + emmyName.start = startPos + end + result[i + 1] = emmyName + end + for i = #result // 2 + 2, #result do + result[i] = nil + end + result.start = result[1].start + result.finish = result[#result].finish + return result + end, + EmmyArrayType = function (start, emmy, _, finish) + emmy.type = 'emmyArrayType' + emmy.start = start + emmy.finish = finish - 1 + return emmy + end, + EmmyTableType = function (start, keyType, valueType, finish) + return { + type = 'emmyTableType', + start = start, + finish = finish - 1, + [1] = keyType, + [2] = valueType, + } + end, + EmmyFunctionType = function (start, args, returns, finish) + local result = { + start = start, + finish = finish - 1, + type = 'emmyFunctionType', + args = args, + returns = returns, + } + return result + end, + EmmyFunctionRtns = function (...) + return {...} + end, + EmmyFunctionArgs = function (...) + local args = {...} + args[#args] = nil + return args + end, + EmmyAlias = function (name, emmyName, ...) + return { + type = 'emmyAlias', + start = name.start, + finish = emmyName.finish, + name, + emmyName, + ... + } + end, + EmmyParam = function (argName, emmyName, option, ...) + local emmy = { + type = 'emmyParam', + option = option, + argName, + emmyName, + ... + } + emmy.start = emmy[1].start + emmy.finish = emmy[#emmy].finish + return emmy + end, + EmmyReturn = function (start, type, name, finish, option) + local emmy = { + type = 'emmyReturn', + option = option, + start = start, + finish = finish - 1, + [1] = type, + [2] = name, + } + return emmy + end, + EmmyField = function (access, fieldName, ...) + local obj = { + type = 'emmyField', + access, fieldName, + ... + } + obj.start = obj[2].start + obj.finish = obj[3].finish + return obj + end, + EmmyGenericBlock = function (genericName, parentName) + return { + start = genericName.start, + finish = parentName and parentName.finish or genericName.finish, + genericName, + parentName, + } + end, + EmmyGeneric = function (...) + local emmy = { + type = 'emmyGeneric', + ... + } + emmy.start = emmy[1].start + emmy.finish = emmy[#emmy].finish + return emmy + end, + EmmyVararg = function (typeName) + return { + type = 'emmyVararg', + start = typeName.start, + finish = typeName.finish, + typeName, + } + end, + EmmyLanguage = function (language) + return { + type = 'emmyLanguage', + start = language.start, + finish = language.finish, + language, + } + end, + EmmySee = function (start, className, methodName, finish) + return { + type = 'emmySee', + start = start, + finish = finish - 1, + className, methodName + } + end, + EmmyOverLoad = function (EmmyFunctionType) + EmmyFunctionType.type = 'emmyOverLoad' + return EmmyFunctionType + end, + EmmyIncomplete = function (emmyName) + emmyName.type = 'emmyIncomplete' + return emmyName + end, + EmmyComment = function (...) + local lines = {...} + for i = 2, #lines do + local line = lines[i] + if line:sub(1, 1) == '|' then + lines[i] = '\n' .. line:sub(2) + end + end + return { + type = 'emmyComment', + [1] = table.concat(lines, '\n'), + } + end, + EmmyOption = function (options) + if not options or options == '' then + return nil + end + local option = {} + for _, pair in ipairs(options) do + if pair.type == 'pair' then + local key = pair[1] + local value = pair[2] + if key.type == 'name' then + option[key[1]] = value[1] + end + end + end + return option + end, + EmmyTypeEnum = function (default, enum, comment) + enum.type = 'emmyEnum' + if default ~= '' then + enum.default = true + end + enum.comment = comment + return enum + end, + + -- 捕获错误 + UnknownSymbol = function (start, symbol) + pushError { + type = 'UNKNOWN_SYMBOL', + start = start, + finish = start + #symbol - 1, + info = { + symbol = symbol, + } + } + return + end, + UnknownAction = function (start, symbol) + pushError { + type = 'UNKNOWN_SYMBOL', + start = start, + finish = start + #symbol - 1, + info = { + symbol = symbol, + } + } + return false + end, + DirtyName = function (pos) + pushError { + type = 'MISS_NAME', + start = pos, + finish = pos, + } + return { + type = 'name', + start = pos-1, + finish = pos-1, + [1] = '' + } + end, + DirtyExp = function (pos) + pushError { + type = 'MISS_EXP', + start = pos, + finish = pos, + } + return { + type = 'name', + start = pos, + finish = pos, + [1] = '' + } + end, + MissExp = function (pos) + pushError { + type = 'MISS_EXP', + start = pos, + finish = pos, + } + end, + MissExponent = function (start, finish) + pushError { + type = 'MISS_EXPONENT', + start = start, + finish = finish - 1, + } + end, + MissQuote1 = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '"' + } + } + end, + MissQuote2 = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = "'" + } + } + end, + MissEscX = function (pos) + pushError { + type = 'MISS_ESC_X', + start = pos-2, + finish = pos+1, + } + end, + MissTL = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '{', + } + } + end, + MissTR = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '}', + } + } + return pos + 1 + end, + MissBR = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ']', + } + } + return pos + 1 + end, + MissPL = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '(', + } + } + end, + DirtyPR = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ')', + } + } + return pos + 1 + end, + MissPR = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ')', + } + } + end, + ErrEsc = function (pos) + pushError { + type = 'ERR_ESC', + start = pos-1, + finish = pos, + } + end, + MustX16 = function (pos, str) + pushError { + type = 'MUST_X16', + start = pos, + finish = pos + #str - 1, + } + end, + MissAssign = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '=', + } + } + end, + MissTableSep = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ',' + } + } + end, + MissField = function (pos) + pushError { + type = 'MISS_FIELD', + start = pos, + finish = pos, + } + end, + MissMethod = function (pos) + pushError { + type = 'MISS_METHOD', + start = pos, + finish = pos, + } + end, + MissLabel = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '::', + } + } + end, + MissEnd = function (pos) + State.MissEndErr = pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = 'end', + } + } + end, + MissDo = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = 'do', + } + } + end, + MissComma = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ',', + } + } + end, + MissIn = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = 'in', + } + } + end, + MissUntil = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = 'until', + } + } + end, + MissThen = function (pos) + pushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = 'then', + } + } + end, + ExpInAction = function (start, exp, finish) + pushError { + type = 'EXP_IN_ACTION', + start = start, + finish = finish - 1, + } + return exp + end, + AfterReturn = function (rtn, ...) + if not ... then + return rtn + end + local action = select(-1, ...) + if not action then + return rtn + end + pushError { + type = 'ACTION_AFTER_RETURN', + start = rtn.start, + finish = rtn.finish, + } + return rtn, action + end, + MissIf = function (start, block) + pushError { + type = 'MISS_SYMBOL', + start = start, + finish = start, + info = { + symbol = 'if', + } + } + return block + end, + MissGT = function (start) + pushError { + type = 'MISS_SYMBOL', + start = start, + finish = start, + info = { + symbol = '>' + } + } + end, + ErrAssign = function (start, finish) + pushError { + type = 'ERR_ASSIGN_AS_EQ', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_ASSIGN_AS_EQ', + { + start = start, + finish = finish - 1, + text = '=', + } + } + } + end, + ErrEQ = function (start, finish) + pushError { + type = 'ERR_EQ_AS_ASSIGN', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_EQ_AS_ASSIGN', + { + start = start, + finish = finish - 1, + text = '==', + } + } + } + return '==' + end, + ErrUEQ = function (start, finish) + pushError { + type = 'ERR_UEQ', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_UEQ', + { + start = start, + finish = finish - 1, + text = '~=', + } + } + } + return '==' + end, + ErrThen = function (start, finish) + pushError { + type = 'ERR_THEN_AS_DO', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_THEN_AS_DO', + { + start = start, + finish = finish - 1, + text = 'then', + } + } + } + end, + ErrDo = function (start, finish) + pushError { + type = 'ERR_DO_AS_THEN', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_DO_AS_THEN', + { + start = start, + finish = finish - 1, + text = 'do', + } + } + } + end, +} + +local function init(state, errs) + State = state + Errs = errs +end + +return { + defs = Defs, + init = init, +} |