diff options
Diffstat (limited to 'server-beta')
-rw-r--r-- | server-beta/src/parser/ast.lua | 1644 | ||||
-rw-r--r-- | server-beta/src/parser/calcline.lua | 93 | ||||
-rw-r--r-- | server-beta/src/parser/compile.lua | 574 | ||||
-rw-r--r-- | server-beta/src/parser/emmy.lua | 321 | ||||
-rw-r--r-- | server-beta/src/parser/grammar.lua | 534 | ||||
-rw-r--r-- | server-beta/src/parser/guide.lua | 175 | ||||
-rw-r--r-- | server-beta/src/parser/init.lua | 11 | ||||
-rw-r--r-- | server-beta/src/parser/lines.lua | 187 | ||||
-rw-r--r-- | server-beta/src/parser/parse.lua | 37 | ||||
-rw-r--r-- | server-beta/src/parser/relabel.lua | 361 | ||||
-rw-r--r-- | server-beta/src/parser/split.lua | 9 |
11 files changed, 3946 insertions, 0 deletions
diff --git a/server-beta/src/parser/ast.lua b/server-beta/src/parser/ast.lua new file mode 100644 index 00000000..a7cb1d08 --- /dev/null +++ b/server-beta/src/parser/ast.lua @@ -0,0 +1,1644 @@ +local emmy = require 'parser.emmy' + +local tonumber = tonumber +local stringChar = string.char +local utf8Char = utf8.char +local tableUnpack = table.unpack +local mathType = math.type +local tableRemove = table.remove + +_ENV = nil + +local State +local PushError + +-- 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) + local versions = VersionOp[op.type] + 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 = op.start, + finish = op.finish, + 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+1, -1 do + local op = list[i] + local opType = op.type + if info[opType] then + local e1 = expSplit(list, start, i-1, 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) + 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+1, finish-1 do + local op = list[i] + local opType = op.type + if info[opType] then + local e1 = expSplit(list, start, i-1, 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) + 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] + local opType = op.type + if info[opType] then + local e1 = expSplit(list, start+1, finish, level) + if e1 then + checkOpVersion(op) + return { + type = 'unary', + op = op, + start = op.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 + +local function getSelect(vararg, index) + return { + type = 'select', + vararg = vararg, + index = index, + } +end + +local function getValue(values, i) + if not values then + return nil, nil + end + local value = values[i] + if not value then + local last = values[#values] + if not last then + return nil, nil + end + if last.type == 'call' or last.type == 'varargs' then + return getSelect(last, i - #values + 1) + end + return nil, nil + end + if value.type == 'call' or value.type == 'varargs' then + value = getSelect(value, 1) + end + return value +end + +local function createLocal(key, effect, value, attrs) + if not key then + return nil + end + key.type = 'local' + key.effect = effect + key.value = value + key.attrs = attrs + return key +end + +local function createCall(args, start, finish) + if args then + args.type = 'callargs' + args.start = start + args.finish = finish + end + return { + type = 'call', + start = start, + finish = finish, + args = args, + } +end + +local function packList(start, list, finish) + local lastFinish = start + local wantName = true + local count = 0 + for i = 1, #list do + local ast = list[i] + if ast.type == ',' then + if wantName or i == #list then + PushError { + type = 'UNEXPECT_SYMBOL', + start = ast.start, + finish = ast.finish, + info = { + symbol = ',', + } + } + end + wantName = true + else + if not wantName then + PushError { + type = 'MISS_SYMBOL', + start = lastFinish, + finish = ast.start - 1, + info = { + symbol = ',', + } + } + end + wantName = false + count = count + 1 + list[count] = list[i] + end + lastFinish = ast.finish + 1 + end + for i = count + 1, #list do + list[i] = nil + end + list.start = start + list.finish = finish - 1 + return list +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, missPos) + 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 = '--', + }, + } + } + 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 stringChar(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 stringChar(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 utf8Char(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) + local lastNumber = State.LastNumber + if mathType(lastNumber[1]) == 'float' then + PushError { + type = 'UNKNOWN_SYMBOL', + start = start, + finish = start + #symbol - 1, + info = { + symbol = symbol, + } + } + 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, + } + } + lastNumber[1] = 0 + end + end, + ImaginaryNumber = function (start, symbol) + local lastNumber = State.LastNumber + if State.version ~= 'LuaJIT' then + PushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = start + #symbol - 1, + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + 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, + GetField = function (dot, field) + if field then + field.type = 'field' + end + return { + type = 'getfield', + field = field, + dot = dot, + start = dot.start, + finish = (field or dot).finish, + } + end, + GetIndex = function (start, index, finish) + return { + type = 'getindex', + start = start, + finish = finish - 1, + index = index, + } + end, + GetMethod = function (colon, method) + if method then + method.type = 'method' + end + return { + type = 'getmethod', + method = method, + colon = colon, + start = colon.start, + finish = (method or colon).finish, + } + end, + Single = function (unit) + unit.type = 'getname' + return unit + end, + Simple = function (units) + local last = units[1] + for i = 2, #units do + local current = units[i] + current.node = last + current.start = last.start + last = units[i] + end + return last + end, + SimpleCall = function (call) + if call.type ~= 'call' and call.type ~= 'getmethod' then + PushError { + type = 'EXP_IN_ACTION', + start = call.start, + finish = call.finish, + } + end + return call + end, + BinaryOp = function (start, op) + return { + type = op, + start = start, + finish = start + #op - 1, + } + end, + UnaryOp = function (start, op) + return { + type = op, + start = start, + finish = start + #op - 1, + } + end, + Exp = function (first, ...) + if not ... then + return first + end + local list = {first, ...} + return expSplit(list, 1, #list, 1) + end, + Paren = function (start, exp, finish) + if exp and exp.type == 'paren' then + exp.start = start + exp.finish = finish - 1 + return exp + end + return { + type = 'paren', + start = start, + finish = finish - 1, + exp = exp + } + end, + VarArgs = function (dots) + dots.type = 'varargs' + return dots + end, + PackLoopArgs = function (start, list, finish) + local list = packList(start, list, finish) + if #list == 0 then + PushError { + type = 'MISS_LOOP_MIN', + start = finish, + finish = finish, + } + elseif #list == 1 then + PushError { + type = 'MISS_LOOP_MAX', + start = finish, + finish = finish, + } + end + return list + end, + PackInNameList = function (start, list, finish) + local list = packList(start, list, finish) + if #list == 0 then + PushError { + type = 'MISS_NAME', + start = start, + finish = finish, + } + end + return list + end, + PackInExpList = function (start, list, finish) + local list = packList(start, list, finish) + if #list == 0 then + PushError { + type = 'MISS_EXP', + start = start, + finish = finish, + } + end + return list + end, + PackExpList = function (start, list, finish) + local list = packList(start, list, finish) + return list + end, + PackNameList = function (start, list, finish) + local list = packList(start, list, finish) + return list + end, + Call = function (start, args, finish) + return createCall(args, start, finish-1) + end, + COMMA = function (start) + return { + type = ',', + start = start, + finish = start, + } + end, + SEMICOLON = function (start) + return { + type = ';', + start = start, + finish = start, + } + end, + DOTS = function (start) + return { + type = '...', + start = start, + finish = start + 2, + } + end, + COLON = function (start) + return { + type = ':', + start = start, + finish = start, + } + end, + DOT = function (start) + return { + type = '.', + start = start, + finish = start, + } + end, + Function = function (start, args, actions, finish) + actions.type = 'function' + actions.start = start + actions.finish = finish - 1 + actions.args = args + checkMissEnd(start) + return actions + end, + NamedFunction = function (start, name, args, actions, finish) + actions.type = 'function' + actions.start = start + actions.finish = finish - 1 + actions.args = args + checkMissEnd(start) + if not name then + return + end + if name.type == 'getname' then + name.type = 'setname' + name.value = actions + return name + elseif name.type == 'getfield' then + name.type = 'setfield' + name.value = actions + return name + elseif name.type == 'getmethod' then + name.type = 'setmethod' + name.value = actions + return name + end + end, + LocalFunction = function (start, name, args, actions, finish) + actions.type = 'function' + actions.start = start + actions.finish = finish - 1 + actions.args = args + checkMissEnd(start) + + if not name then + return + end + + if name.type ~= 'getname' then + PushError { + type = 'UNEXPECT_LFUNC_NAME', + start = name.start, + finish = name.finish, + } + return + end + + local loc = createLocal(name, start, actions) + + return loc + end, + Table = function (start, tbl, finish) + tbl.type = 'table' + tbl.start = start + tbl.finish = finish - 1 + local wantField = true + local lastStart = start + 1 + local fieldCount = 0 + for i = 1, #tbl do + local field = tbl[i] + if field.type == ',' or field.type == ';' then + if wantField then + PushError { + type = 'MISS_EXP', + start = lastStart, + finish = field.start - 1, + } + end + wantField = true + lastStart = field.finish + 1 + else + if not wantField then + PushError { + type = 'MISS_SEP_IN_TABLE', + start = lastStart, + finish = field.start - 1, + } + end + wantField = false + lastStart = field.finish + 1 + fieldCount = fieldCount + 1 + tbl[fieldCount] = field + end + end + for i = fieldCount + 1, #tbl do + tbl[i] = nil + end + return tbl + end, + NewField = function (start, field, value, finish) + field.type = 'field' + return { + type = 'tablefield', + start = start, + finish = finish-1, + field = field, + value = value, + } + end, + Index = function (start, index, finish) + return { + type = 'index', + start = start, + finish = finish - 1, + index = index, + } + end, + NewIndex = function (start, index, value, finish) + return { + type = 'tableindex', + start = start, + finish = finish-1, + index = index, + value = value, + } + end, + FuncArgs = function (start, args, finish) + args.type = 'funcargs' + args.start = start + args.finish = finish - 1 + local lastStart = start + 1 + local wantName = true + local argCount = 0 + for i = 1, #args do + local arg = args[i] + local argAst = arg + if argAst.type == ',' then + if wantName then + PushError { + type = 'MISS_NAME', + start = lastStart, + finish = argAst.start-1, + } + end + wantName = true + else + if not wantName then + PushError { + type = 'MISS_SYMBOL', + start = lastStart-1, + finish = argAst.start-1, + info = { + symbol = ',', + } + } + end + wantName = false + argCount = argCount + 1 + + if argAst.type == '...' then + args[argCount] = arg + if i < #args then + local a = args[i+1] + local b = args[#args] + PushError { + type = 'ARGS_AFTER_DOTS', + start = a.start, + finish = b.finish, + } + end + break + else + args[argCount] = createLocal(arg, arg.start) + end + end + lastStart = argAst.finish + 1 + end + for i = argCount + 1, #args do + args[i] = nil + end + if wantName and argCount > 0 then + PushError { + type = 'MISS_NAME', + start = lastStart, + finish = finish - 1, + } + end + return args + end, + Set = function (start, keys, values, finish) + for i = 1, #keys do + local key = keys[i] + if key.type == 'getname' then + key.type = 'setname' + key.value = getValue(values, i) + elseif key.type == 'getfield' then + key.type = 'setfield' + key.value = getValue(values, i) + end + end + return tableUnpack(keys) + end, + LocalAttr = function (attrs) + for i = 1, #attrs do + local attr = attrs[i] + local attrAst = attr + attrAst.type = 'localattr' + if State.version ~= 'Lua 5.4' then + PushError { + type = 'UNSUPPORT_SYMBOL', + start = attrAst.start, + finish = attrAst.finish, + version = 'Lua 5.4', + info = { + version = State.version, + } + } + elseif attrAst[1] ~= 'const' and attrAst[1] ~= 'close' then + PushError { + type = 'UNKNOWN_TAG', + start = attrAst.start, + finish = attrAst.finish, + info = { + tag = attrAst[1], + } + } + elseif i > 1 then + PushError { + type = 'MULTI_TAG', + start = attrAst.start, + finish = attrAst.finish, + info = { + tag = attrAst[1], + } + } + end + end + return attrs + end, + LocalName = function (name, attrs) + if not name then + return name + end + name.attrs = attrs + return name + end, + Local = function (start, keys, values, finish) + for i = 1, #keys do + local key = keys[i] + local attrs = key.attrs + key.attrs = nil + local value = getValue(values, i) + createLocal(key, finish, value, attrs) + end + return tableUnpack(keys) + end, + Do = function (start, actions, finish) + actions.type = 'do' + actions.start = start + actions.finish = finish - 1 + checkMissEnd(start) + return actions + end, + Break = function (start, finish) + return { + type = 'break', + start = start, + finish = finish - 1, + } + end, + Return = function (start, exps, finish) + exps.type = 'return' + exps.start = start + exps.finish = finish - 1 + return exps + 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 + end + if not name then + return nil + end + name.type = 'label' + 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 + end + if not name then + return nil + end + name.type = 'goto' + return name + end, + IfBlock = function (start, exp, actions, finish) + actions.type = 'ifblock' + actions.start = start + actions.finish = finish - 1 + actions.filter = exp + return actions + end, + ElseIfBlock = function (start, exp, actions, finish) + actions.type = 'elseifblock' + actions.start = start + actions.finish = finish - 1 + actions.filter = exp + return actions + end, + ElseBlock = function (start, actions, finish) + actions.type = 'elseblock' + actions.start = start + actions.finish = finish - 1 + return actions + end, + If = function (start, blocks, finish) + blocks.type = 'if' + blocks.start = start + blocks.finish = finish - 1 + local hasElse + for i = 1, #blocks do + local block = blocks[i] + if i == 1 and block.type ~= 'ifblock' then + PushError { + type = 'MISS_SYMBOL', + start = block.start, + finish = block.start, + info = { + symbol = 'if', + } + } + end + if hasElse then + PushError { + type = 'BLOCK_AFTER_ELSE', + start = block.start, + finish = block.finish, + } + end + if block.type == 'elseblock' then + hasElse = true + end + end + checkMissEnd(start) + return blocks + end, + Loop = function (start, arg, steps, blockStart, block, finish) + local loc = createLocal(arg, blockStart, steps[1]) + block.type = 'loop' + block.start = start + block.finish = finish - 1 + block.loc = loc + block.max = steps[2] + block.step = steps[3] + checkMissEnd(start) + return block + end, + In = function (start, keys, exp, blockStart, block, finish) + local func = tableRemove(exp, 1) + block.type = 'in' + block.start = start + block.finish = finish - 1 + block.keys = {} + + local values + if func then + local call = createCall(exp, func.finish + 1, exp.finish) + call.node = func + values = { call } + end + for i = 1, #keys do + local loc = keys[i] + if values then + block.keys[i] = createLocal(loc, blockStart, getValue(values, i)) + else + block.keys[i] = createLocal(loc, blockStart) + end + end + checkMissEnd(start) + return block + end, + While = function (start, filter, block, finish) + block.type = 'while' + block.start = start + block.finish = finish - 1 + block.filter = filter + checkMissEnd(start) + return block + end, + Repeat = function (start, block, filter, finish) + block.type = 'repeat' + block.start = start + block.finish = finish + block.filter = filter + return block + end, + Lua = function (actions) + actions.type = 'main' + return actions + 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, + } + } + end, + DirtyName = function (pos) + PushError { + type = 'MISS_NAME', + start = pos, + finish = pos, + } + return nil + end, + DirtyExp = function (pos) + PushError { + type = 'MISS_EXP', + start = pos, + finish = pos, + } + return nil + 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 = '}', + } + } + end, + MissBR = function (pos) + PushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = ']', + } + } + end, + MissPL = function (pos) + PushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = '(', + } + } + 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, + MissName = function (pos) + PushError { + type = 'MISS_NAME', + start = pos, + finish = pos, + } + end, + ExpInAction = function (start, exp, finish) + PushError { + type = 'EXP_IN_ACTION', + start = start, + finish = finish - 1, + } + return exp + 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, +} + +--for k, v in pairs(emmy.ast) do +-- Defs[k] = v +--end + +local function init(state) + State = state + PushError = state.pushError + emmy.init(State) +end + +local function close() + State = nil + PushError = nil +end + +return { + defs = Defs, + init = init, + close = close, +} diff --git a/server-beta/src/parser/calcline.lua b/server-beta/src/parser/calcline.lua new file mode 100644 index 00000000..26f475d9 --- /dev/null +++ b/server-beta/src/parser/calcline.lua @@ -0,0 +1,93 @@ +local m = require 'lpeglabel' + +local row +local fl +local NL = (m.P'\r\n' + m.S'\r\n') * m.Cp() / function (pos) + row = row + 1 + fl = pos +end +local ROWCOL = (NL + m.P(1))^0 +local function rowcol(str, n) + row = 1 + fl = 1 + ROWCOL:match(str:sub(1, n)) + local col = n - fl + 1 + return row, col +end + +local function rowcol_utf8(str, n) + row = 1 + fl = 1 + ROWCOL:match(str:sub(1, n)) + return row, utf8.len(str, fl, n) +end + +local function position(str, _row, _col) + local cur = 1 + local row = 1 + while true do + if row == _row then + return cur + _col - 1 + elseif row > _row then + return cur - 1 + end + local pos = str:find('[\r\n]', cur) + if not pos then + return #str + end + row = row + 1 + if str:sub(pos, pos+1) == '\r\n' then + cur = pos + 2 + else + cur = pos + 1 + end + end +end + +local function position_utf8(str, _row, _col) + local cur = 1 + local row = 1 + while true do + if row == _row then + return utf8.offset(str, _col, cur) + elseif row > _row then + return cur - 1 + end + local pos = str:find('[\r\n]', cur) + if not pos then + return #str + end + row = row + 1 + if str:sub(pos, pos+1) == '\r\n' then + cur = pos + 2 + else + cur = pos + 1 + end + end +end + +local NL = m.P'\r\n' + m.S'\r\n' + +local function line(str, row) + local count = 0 + local res + local LINE = m.Cmt((1 - NL)^0, function (_, _, c) + count = count + 1 + if count == row then + res = c + return false + end + return true + end) + local MATCH = (LINE * NL)^0 * LINE + MATCH:match(str) + return res +end + +return { + rowcol = rowcol, + rowcol_utf8 = rowcol_utf8, + position = position, + position_utf8 = position_utf8, + line = line, +} diff --git a/server-beta/src/parser/compile.lua b/server-beta/src/parser/compile.lua new file mode 100644 index 00000000..f6efb6e1 --- /dev/null +++ b/server-beta/src/parser/compile.lua @@ -0,0 +1,574 @@ +local guide = require 'parser.guide' +local type = type + +_ENV = nil + +local pushError, Root, Compile, CompileBlock, Cache, Block, GoToTag + +local vmMap = { + ['nil'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['boolean'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['string'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['number'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['...'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['getname'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local loc = guide.getLocal(Root, obj, obj[1], obj.start) + if loc then + obj.type = 'getlocal' + obj.loc = Cache[loc] + if not loc.ref then + loc.ref = {} + end + loc.ref[#loc.ref+1] = id + else + obj.type = 'getglobal' + end + return id + end, + ['getfield'] = function (obj) + local node = obj.node + Root[#Root+1] = obj + local id = #Root + obj.node = Compile(node, id) + return id + end, + ['call'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local node = obj.node + local args = obj.args + if node then + obj.node = Compile(node, id) + end + if args then + obj.args = Compile(args, id) + end + return id + end, + ['callargs'] = function (obj) + Root[#Root+1] = obj + local id = #Root + for i = 1, #obj do + local arg = obj[i] + obj[i] = Compile(arg, id) + end + return id + end, + ['binary'] = function (obj) + local e1 = obj[1] + local e2 = obj[2] + Root[#Root+1] = obj + local id = #Root + obj[1] = Compile(e1, id) + obj[2] = Compile(e2, id) + return id + end, + ['unary'] = function (obj) + local e = obj[1] + Root[#Root+1] = obj + local id = #Root + obj[1] = Compile(e, id) + return id + end, + ['varargs'] = function (obj) + local func = guide.getParentFunction(Root, obj) + if func then + local index = guide.getFunctionVarArgs(Root, func) + if not index then + pushError { + type = 'UNEXPECT_DOTS', + start = obj.start, + finish = obj.finish, + } + end + end + Root[#Root+1] = obj + return #Root + end, + ['paren'] = function (obj) + local exp = obj.exp + Root[#Root+1] = obj + local id = #Root + obj.exp = Compile(exp, id) + return id + end, + ['getindex'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local node = obj.node + obj.node = Compile(node, id) + local index = obj.index + if index then + obj.index = Compile(index, id) + end + return id + end, + ['getmethod'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local node = obj.node + local method = obj.method + obj.node = Compile(node, id) + if method then + obj.method = Compile(method, id) + end + return id + end, + ['setmethod'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local node = obj.node + local method = obj.method + local value = obj.value + obj.node = Compile(node, id) + if method then + obj.method = Compile(method, id) + end + obj.value = Compile(value, id) + return id + end, + ['method'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['function'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local args = obj.args + if args then + obj.args = Compile(args, id) + end + for i = 1, #obj do + local act = obj[i] + obj[i] = Compile(act, id) + end + Block = lastBlock + return id + end, + ['funcargs'] = function (obj) + Root[#Root+1] = obj + local id = #Root + for i = 1, #obj do + local arg = obj[i] + obj[i] = Compile(arg, id) + end + return id + end, + ['table'] = function (obj) + Root[#Root+1] = obj + local id = #Root + for i = 1, #obj do + local v = obj[i] + obj[i] = Compile(v, id) + end + return id + end, + ['tablefield'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local value = obj.value + if value then + obj.value = Compile(value, id) + end + return id + end, + ['tableindex'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local index = obj.index + local value = obj.value + obj.index = Compile(index, id) + obj.value = Compile(value, id) + return id + end, + ['index'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local index = obj.index + obj.index = Compile(index, id) + return id + end, + ['select'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local vararg = obj.vararg + if not Cache[vararg] then + obj.vararg = Compile(vararg, id) + Cache[vararg] = obj.vararg + else + obj.vararg = Cache[vararg] + if not vararg.extParent then + vararg.extParent = {} + end + vararg.extParent[#vararg.extParent+1] = id + end + return id + end, + ['setname'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local value = obj.value + if value then + obj.value = Compile(value, id) + end + local loc = guide.getLocal(Root, obj, obj[1], obj.start) + if loc then + obj.type = 'setlocal' + obj.loc = Cache[loc] + if not loc.ref then + loc.ref = {} + end + loc.ref[#loc.ref+1] = id + else + obj.type = 'setglobal' + end + return id + end, + ['local'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local attrs = obj.attrs + if attrs then + for i = 1, #attrs do + local attr = attrs[i] + attrs[i] = Compile(attr, id) + end + end + if Block then + if not Block.locals then + Block.locals = {} + end + Block.locals[#Block.locals+1] = id + end + local value = obj.value + if value then + obj.value = Compile(value, id) + end + Cache[obj] = id + return id + end, + ['localattr'] = function (obj) + Root[#Root+1] = obj + return #Root + end, + ['setfield'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local node = obj.node + local field = obj.field + local value = obj.value + obj.node = Compile(node, id) + obj.field = Compile(field, id) + obj.value = Compile(value, id) + return id + end, + ['do'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['return'] = function (obj) + Root[#Root+1] = obj + local id = #Root + for i = 1, #obj do + local act = obj[i] + obj[i] = Compile(act, id) + end + if Block and Block[#Block] ~= obj then + pushError { + type = 'ACTION_AFTER_RETURN', + start = obj.start, + finish = obj.finish, + } + end + return id + end, + ['label'] = function (obj) + Root[#Root+1] = obj + local id = #Root + local block = guide.getBlock(Root, obj) + if block then + if not block.labels then + block.labels = {} + end + local name = obj[1] + local label = guide.getLabel(Root, block, name) + if label then + pushError { + type = 'REDEFINED_LABEL', + start = obj.start, + finish = obj.finish, + relative = { + { + label.start, + label.finish, + } + } + } + end + block.labels[name] = id + end + return id + end, + ['goto'] = function (obj) + Root[#Root+1] = obj + local id = #Root + GoToTag[#GoToTag+1] = obj + return id + end, + ['if'] = function (obj) + Root[#Root+1] = obj + local id = #Root + for i = 1, #obj do + local block = obj[i] + obj[i] = Compile(block, id) + end + return id + end, + ['ifblock'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local filter = obj.filter + obj.filter = Compile(filter, id) + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['elseifblock'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local filter = obj.filter + if filter then + obj.filter = Compile(filter, id) + end + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['elseblock'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['loop'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local loc = obj.loc + local max = obj.max + local step = obj.step + if loc then + obj.loc = Compile(loc, id) + end + if max then + obj.max = Compile(max, id) + end + if step then + obj.step = Compile(step, id) + end + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['in'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local keys = obj.keys + for i = 1, #keys do + local loc = keys[i] + keys[i] = Compile(loc, id) + end + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['while'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + local filter = obj.filter + obj.filter = Compile(filter, id) + CompileBlock(obj, id) + Block = lastBlock + return id + end, + ['repeat'] = function (obj) + local lastBlock = Block + Block = obj + Root[#Root+1] = obj + local id = #Root + CompileBlock(obj, id) + local filter = obj.filter + obj.filter = Compile(filter, id) + Block = lastBlock + return id + end, + ['break'] = function (obj) + if not guide.getBreakBlock(Root, obj) then + pushError { + type = 'BREAK_OUTSIDE', + start = obj.start, + finish = obj.finish, + } + end + Root[#Root+1] = obj + return #Root + end, + ['main'] = function (obj) + Block = obj + Root[#Root+1] = obj + local id = #Root + CompileBlock(obj, id) + Block = nil + return id + 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 + obj[i] = f(act) + end + end +end + +function Compile(obj, parent) + if not obj then + return nil + end + local f = vmMap[obj.type] + if not f then + return nil + end + obj.parent = parent + return f(obj) +end + +local function compileGoTo(obj) + local name = obj[1] + local label = guide.getLabel(Root, obj, name) + if not label then + pushError { + type = 'NO_VISIBLE_LABEL', + start = obj.start, + finish = obj.finish, + info = { + label = name, + } + } + return + end + -- 如果有局部变量在 goto 与 label 之间声明, + -- 并在 label 之后使用,则算作语法错误 + + -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量 + if obj.start > label.start then + return + end + + local block = guide.getBlock(Root, obj) + local locals = block and block.locals + if not locals then + return + end + + for i = 1, #locals do + local loc = Root[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 = Root[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, errs = self:parse(lua, mode, version) + if not state then + return errs + end + pushError = state.pushError + Root = state.root + Cache = {} + GoToTag = {} + if type(state.ast) == 'table' then + Compile(state.ast) + end + PostCompile() + state.ast = nil + Cache = nil + return state, errs +end diff --git a/server-beta/src/parser/emmy.lua b/server-beta/src/parser/emmy.lua new file mode 100644 index 00000000..4c1e087a --- /dev/null +++ b/server-beta/src/parser/emmy.lua @@ -0,0 +1,321 @@ +local State +local pushError + +local grammar = [[ +EmmyLua <- ({} '---' EmmyBody {} ShortComment) + -> EmmyLua +EmmySp <- (!'---@' !'---' Comment / %s / %nl)* +EmmyComments <- (EmmyComment (%nl EmmyComMulti / %nl EmmyComSingle)*) +EmmyComment <- EmmySp %s* {(!%nl .)*} +EmmyComMulti <- EmmySp '---|' {} -> en {(!%nl .)*} +EmmyComSingle <- EmmySp '---' !'@' %s* {} -> ' ' {(!%nl .)*} +EmmyBody <- '@class' %s+ EmmyClass -> EmmyClass + / '@type' %s+ EmmyType -> EmmyType + / '@alias' %s+ EmmyAlias -> EmmyAlias + / '@param' %s+ EmmyParam -> EmmyParam + / '@return' %s+ EmmyReturn -> EmmyReturn + / '@field' %s+ EmmyField -> EmmyField + / '@generic' %s+ EmmyGeneric -> EmmyGeneric + / '@vararg' %s+ EmmyVararg -> EmmyVararg + / '@language' %s+ EmmyLanguage -> EmmyLanguage + / '@see' %s+ EmmySee -> EmmySee + / '@overload' %s+ EmmyOverLoad -> EmmyOverLoad + / %s* EmmyComments -> EmmyComment + / EmmyIncomplete + +EmmyName <- ({} {[a-zA-Z_] [a-zA-Z0-9_]*}) + -> EmmyName +MustEmmyName <- EmmyName / DirtyEmmyName +DirtyEmmyName <- {} -> DirtyEmmyName +EmmyLongName <- ({} {(!%nl .)+}) + -> EmmyName +EmmyIncomplete <- MustEmmyName + -> EmmyIncomplete + +EmmyClass <- (MustEmmyName EmmyParentClass?) +EmmyParentClass <- %s* {} ':' %s* MustEmmyName + +EmmyType <- EmmyTypeUnits EmmyTypeEnums +EmmyTypeUnits <- {| + EmmyTypeUnit? + (%s* '|' %s* !String EmmyTypeUnit)* + |} +EmmyTypeEnums <- {| EmmyTypeEnum* |} +EmmyTypeUnit <- EmmyFunctionType + / EmmyTableType + / EmmyArrayType + / EmmyCommonType +EmmyCommonType <- EmmyName + -> EmmyCommonType +EmmyTypeEnum <- %s* (%nl %s* '---')? '|'? EmmyEnum + -> EmmyTypeEnum +EmmyEnum <- %s* {'>'?} %s* String (EmmyEnumComment / (!%nl !'|' .)*) +EmmyEnumComment <- %s* '#' %s* {(!%nl .)*} + +EmmyAlias <- MustEmmyName %s* EmmyType EmmyTypeEnum* + +EmmyParam <- MustEmmyName %s* EmmyType %s* EmmyOption %s* EmmyTypeEnum* +EmmyOption <- Table? + -> EmmyOption + +EmmyReturn <- {} %nil {} Table -> EmmyOption + / {} EmmyType {} EmmyOption + +EmmyField <- (EmmyFieldAccess MustEmmyName %s* EmmyType) +EmmyFieldAccess <- ({'public'} Cut %s*) + / ({'protected'} Cut %s*) + / ({'private'} Cut %s*) + / {} -> 'public' + +EmmyGeneric <- EmmyGenericBlock + (%s* ',' %s* EmmyGenericBlock)* +EmmyGenericBlock<- (MustEmmyName %s* (':' %s* EmmyType)?) + -> EmmyGenericBlock + +EmmyVararg <- EmmyType + +EmmyLanguage <- MustEmmyName + +EmmyArrayType <- ({} MustEmmyName -> EmmyCommonType {} '[' DirtyBR) + -> EmmyArrayType + / ({} PL EmmyCommonType DirtyPR '[' DirtyBR) + -> EmmyArrayType + +EmmyTableType <- ({} 'table' Cut '<' %s* EmmyType %s* ',' %s* EmmyType %s* '>' {}) + -> EmmyTableType + +EmmyFunctionType<- ({} 'fun' Cut %s* EmmyFunctionArgs %s* EmmyFunctionRtns {}) + -> EmmyFunctionType +EmmyFunctionArgs<- ('(' %s* EmmyFunctionArg %s* (',' %s* EmmyFunctionArg %s*)* DirtyPR) + -> EmmyFunctionArgs + / '(' %nil DirtyPR -> None + / %nil +EmmyFunctionRtns<- (':' %s* EmmyType (%s* ',' %s* EmmyType)*) + -> EmmyFunctionRtns + / %nil +EmmyFunctionArg <- MustEmmyName %s* ':' %s* EmmyType + +EmmySee <- {} MustEmmyName %s* '#' %s* MustEmmyName {} +EmmyOverLoad <- EmmyFunctionType +]] + +local ast = { + EmmyLua = function (start, emmy, finish) + emmy.start = start + emmy.finish = finish - 1 + State.emmy[#State.emmy+1] = emmy + end, + EmmyName = function (start, str) + return { + type = 'name', + 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 = 'class', + class = class, + extends = extends, + } + end, + EmmyType = function (types, enums) + local result = { + type = 'type', + types = types, + enums = enums, + } + return result + end, + EmmyCommonType = function (name) + return { + type = 'common', + start = name.start, + finish = name.finish, + name = name, + } + 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, finish, option) + local emmy = { + type = 'emmyReturn', + option = option, + start = start, + finish = finish - 1, + [1] = type, + } + 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 (...) + return { + type = 'emmyComment', + [1] = table.concat({...}), + } + 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 = 'enum' + if default ~= '' then + enum.default = true + end + enum.comment = comment + return enum + end, +} + +local function init(state) + State = state + pushError = state.pushError +end + +return { + grammar = grammar, + ast = ast, + init = init, +} diff --git a/server-beta/src/parser/grammar.lua b/server-beta/src/parser/grammar.lua new file mode 100644 index 00000000..dfee8a1b --- /dev/null +++ b/server-beta/src/parser/grammar.lua @@ -0,0 +1,534 @@ +local re = require 'parser.relabel' +local m = require 'lpeglabel' +local emmy = require 'parser.emmy' +local ast = require 'parser.ast' + +local scriptBuf = '' +local compiled = {} +local defs = ast.defs + +-- 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, +} + +defs.nl = (m.P'\r\n' + m.S'\r\n') +defs.s = m.S' \t' +defs.S = - defs.s +defs.ea = '\a' +defs.eb = '\b' +defs.ef = '\f' +defs.en = '\n' +defs.er = '\r' +defs.et = '\t' +defs.ev = '\v' +defs['nil'] = m.Cp() / function () return nil end +defs['false'] = m.Cp() / function () return false end +defs.NotReserved = function (_, _, str) + if RESERVED[str] then + return false + end + return true +end +defs.Reserved = function (_, _, str) + if RESERVED[str] then + return true + end + return false +end +defs.None = function () end +defs.np = m.Cp() / function (n) return n+1 end + +m.setmaxstack(1000) + +local eof = re.compile '!. / %{SYNTAX_ERROR}' + +local function grammar(tag) + return function (script) + scriptBuf = script .. '\r\n' .. scriptBuf + compiled[tag] = re.compile(scriptBuf, defs) * eof + end +end + +local function errorpos(pos, err) + return { + type = 'UNKNOWN', + start = pos or 0, + finish = pos or 0, + err = err, + } +end + +grammar 'Comment' [[ +Comment <- LongComment + / '--' ShortComment +LongComment <- ('--[' {} {:eq: '='* :} {} '[' + {(!CommentClose .)*} + (CommentClose / {})) + -> LongComment + / ( + {} '/*' {} + (!'*/' .)* + {} '*/' {} + ) + -> CLongComment +CommentClose <- ']' =eq ']' +ShortComment <- (!%nl .)* +]] + +grammar 'Sp' [[ +Sp <- (EmmyLua / Comment / %nl / %s)* +Sps <- (EmmyLua / Comment / %nl / %s)+ + +-- 占位 +EmmyLua <- !. . +]] + +grammar 'Common' [[ +Word <- [a-zA-Z0-9_] +Cut <- !Word +X16 <- [a-fA-F0-9] +Rest <- (!%nl .)* + +AND <- Sp {'and'} Cut +BREAK <- Sp 'break' Cut +DO <- Sp 'do' Cut + / Sp ({} 'then' Cut {}) -> ErrDo +ELSE <- Sp 'else' Cut +ELSEIF <- Sp 'elseif' Cut +END <- Sp 'end' Cut +FALSE <- Sp 'false' Cut +FOR <- Sp 'for' Cut +FUNCTION <- Sp 'function' Cut +GOTO <- Sp 'goto' Cut +IF <- Sp 'if' Cut +IN <- Sp 'in' Cut +LOCAL <- Sp 'local' Cut +NIL <- Sp 'nil' Cut +NOT <- Sp 'not' Cut +OR <- Sp {'or'} Cut +REPEAT <- Sp 'repeat' Cut +RETURN <- Sp 'return' Cut +THEN <- Sp 'then' Cut + / Sp ({} 'do' Cut {}) -> ErrThen +TRUE <- Sp 'true' Cut +UNTIL <- Sp 'until' Cut +WHILE <- Sp 'while' Cut + +Esc <- '\' -> '' + EChar +EChar <- 'a' -> ea + / 'b' -> eb + / 'f' -> ef + / 'n' -> en + / 'r' -> er + / 't' -> et + / 'v' -> ev + / '\' + / '"' + / "'" + / %nl + / ('z' (%nl / %s)*) -> '' + / ({} 'x' {X16 X16}) -> Char16 + / ([0-9] [0-9]? [0-9]?) -> Char10 + / ('u{' {} {Word*} '}') -> CharUtf8 + -- 错误处理 + / 'x' {} -> MissEscX + / 'u' !'{' {} -> MissTL + / 'u{' Word* !'}' {} -> MissTR + / {} -> ErrEsc + +BOR <- Sp {'|'} +BXOR <- Sp {'~'} !'=' +BAND <- Sp {'&'} +Bshift <- Sp {BshiftList} +BshiftList <- '<<' + / '>>' +Concat <- Sp {'..'} +Adds <- Sp {AddsList} +AddsList <- '+' + / '-' +Muls <- Sp {MulsList} +MulsList <- '*' + / '//' + / '/' + / '%' +Unary <- Sp {} {UnaryList} +UnaryList <- NOT + / '#' + / '-' + / '~' !'=' +POWER <- Sp {'^'} + +BinaryOp <-( Sp {} {'or'} Cut + / Sp {} {'and'} Cut + / Sp {} {'<=' / '>=' / '<'!'<' / '>'!'>' / '~=' / '=='} + / Sp {} ({} '=' {}) -> ErrEQ + / Sp {} ({} '!=' {}) -> ErrUEQ + / Sp {} {'|'} + / Sp {} {'~'} + / Sp {} {'&'} + / Sp {} {'<<' / '>>'} + / Sp {} {'..'} !'.' + / Sp {} {'+' / '-'} + / Sp {} {'*' / '//' / '/' / '%'} + / Sp {} {'^'} + )-> BinaryOp +UnaryOp <-( Sp {} {'not' Cut / '#' / '~' !'=' / '-' !'-'} + )-> UnaryOp + +PL <- Sp '(' +PR <- Sp ')' +BL <- Sp '[' !'[' !'=' +BR <- Sp ']' +TL <- Sp '{' +TR <- Sp '}' +COMMA <- Sp ({} ',') + -> COMMA +SEMICOLON <- Sp ({} ';') + -> SEMICOLON +DOTS <- Sp ({} '...') + -> DOTS +DOT <- Sp ({} '.' !'.') + -> DOT +COLON <- Sp ({} ':' !':') + -> COLON +LABEL <- Sp '::' +ASSIGN <- Sp '=' !'=' +AssignOrEQ <- Sp ({} '==' {}) + -> ErrAssign + / Sp '=' + +DirtyBR <- BR / {} -> MissBR +DirtyTR <- TR / {} -> MissTR +DirtyPR <- PR / {} -> MissPR +DirtyLabel <- LABEL / {} -> MissLabel +NeedEnd <- END / {} -> MissEnd +NeedDo <- DO / {} -> MissDo +NeedAssign <- ASSIGN / {} -> MissAssign +NeedComma <- COMMA / {} -> MissComma +NeedIn <- IN / {} -> MissIn +NeedUntil <- UNTIL / {} -> MissUntil +NeedThen <- THEN / {} -> MissThen +]] + +grammar 'Nil' [[ +Nil <- Sp ({} -> Nil) NIL +]] + +grammar 'Boolean' [[ +Boolean <- Sp ({} -> True) TRUE + / Sp ({} -> False) FALSE +]] + +grammar 'String' [[ +String <- Sp ({} StringDef {}) + -> String +StringDef <- {'"'} + {~(Esc / !%nl !'"' .)*~} -> 1 + ('"' / {} -> MissQuote1) + / {"'"} + {~(Esc / !%nl !"'" .)*~} -> 1 + ("'" / {} -> MissQuote2) + / ('[' {} {:eq: '='* :} {} '[' %nl? + {(!StringClose .)*} -> 1 + (StringClose / {})) + -> LongString +StringClose <- ']' =eq ']' +]] + +grammar 'Number' [[ +Number <- Sp ({} {NumberDef} {}) -> Number + NumberSuffix? + ErrNumber? +NumberDef <- Number16 / Number10 +NumberSuffix<- ({} {[uU]? [lL] [lL]}) -> FFINumber + / ({} {[iI]}) -> ImaginaryNumber +ErrNumber <- ({} {([0-9a-zA-Z] / '.')+}) -> UnknownSymbol + +Number10 <- Float10 Float10Exp? + / Integer10 Float10? Float10Exp? +Integer10 <- [0-9]+ ('.' [0-9]*)? +Float10 <- '.' [0-9]+ +Float10Exp <- [eE] [+-]? [0-9]+ + / ({} [eE] [+-]? {}) -> MissExponent + +Number16 <- '0' [xX] Float16 Float16Exp? + / '0' [xX] Integer16 Float16? Float16Exp? +Integer16 <- X16+ ('.' X16*)? + / ({} {Word*}) -> MustX16 +Float16 <- '.' X16+ + / '.' ({} {Word*}) -> MustX16 +Float16Exp <- [pP] [+-]? [0-9]+ + / ({} [pP] [+-]? {}) -> MissExponent +]] + +grammar 'Name' [[ +Name <- Sp ({} NameBody {}) + -> Name +NameBody <- {[a-zA-Z_] [a-zA-Z0-9_]*} +FreeName <- Sp ({} {NameBody=>NotReserved} {}) + -> Name +KeyWord <- Sp NameBody=>Reserved +MustName <- Name / DirtyName +DirtyName <- {} -> DirtyName +]] + +grammar 'Exp' [[ +Exp <- (UnUnit (BinaryOp (UnUnit / {} -> MissExp))*) + -> Exp +UnUnit <- ExpUnit + / UnaryOp+ (ExpUnit / {} -> DirtyExp) +ExpUnit <- Nil + / Boolean + / String + / Number + / Dots + / Table + / Function + / Simple + +Simple <- {| Prefix (Sp Suffix)* |} + -> Simple +Prefix <- Sp ({} PL DirtyExp DirtyPR {}) + -> Paren + / Single +Single <- FreeName + -> Single +Suffix <- SuffixWithoutCall + / ({} PL SuffixCall DirtyPR {}) + -> Call +SuffixCall <- Sp ({} {| (COMMA / Exp)+ |} {}) + -> PackExpList + / %nil +SuffixWithoutCall + <- (DOT (Name / MissField)) + -> GetField + / ({} BL DirtyExp DirtyBR {}) + -> GetIndex + / (COLON (Name / MissMethod) NeedCall) + -> GetMethod + / ({} {| Table |} {}) + -> Call + / ({} {| String |} {}) + -> Call +NeedCall <- (!(Sp CallStart) {} -> MissPL)? +MissField <- {} -> MissField +MissMethod <- {} -> MissMethod +CallStart <- PL + / TL + / '"' + / "'" + / '[' '='* '[' + +DirtyExp <- Exp + / {} -> DirtyExp +MaybeExp <- Exp / MissExp +MissExp <- {} -> MissExp +ExpList <- Sp {| MaybeExp (Sp ',' MaybeExp)* |} + +Dots <- DOTS + -> VarArgs + +Table <- Sp ({} TL {| TableField* |} DirtyTR {}) + -> Table +TableField <- COMMA + / SEMICOLON + / NewIndex + / NewField + / Exp +Index <- ({} BL DirtyExp DirtyBR {}) + -> Index +NewIndex <- Sp ({} Index NeedAssign DirtyExp {}) + -> NewIndex +NewField <- Sp ({} MustName ASSIGN DirtyExp {}) + -> NewField + +Function <- Sp ({} FunctionBody {}) + -> Function +FuncArgs <- Sp ({} PL {| FuncArg+ |} DirtyPR {}) + -> FuncArgs + / PL DirtyPR %nil + / {} -> MissPL DirtyPR %nil +FuncArg <- DOTS + / Name + / COMMA +FunctionBody<- FUNCTION FuncArgs + {| (!END Action)* |} + NeedEnd + +-- 纯占位,修改了 `relabel.lua` 使重复定义不抛错 +Action <- !END . +]] + +grammar 'Action' [[ +Action <- Sp (CrtAction / UnkAction) +CrtAction <- Semicolon + / Do + / Break + / Return + / Label + / GoTo + / If + / For + / While + / Repeat + / NamedFunction + / LocalFunction + / Local + / Set + / Call + / ExpInAction +UnkAction <- ({} {Word+}) + -> UnknownAction + / ({} '//' {} (LongComment / ShortComment)) + -> CCommentPrefix + / ({} {. (!Sps !CrtAction .)*}) + -> UnknownAction +ExpInAction <- Sp ({} Exp {}) + -> ExpInAction + +Semicolon <- Sp ';' +SimpleList <- {| Simple (Sp ',' Simple)* |} + +Do <- Sp ({} + 'do' Cut + {| (!END Action)* |} + NeedEnd + {}) + -> Do + +Break <- Sp ({} BREAK {}) + -> Break + +Return <- Sp ({} RETURN ReturnExpList {}) + -> Return +ReturnExpList + <- Sp {| Exp (Sp ',' MaybeExp)* |} + / Sp {| !Exp !',' |} + / ExpList + +Label <- Sp ({} LABEL MustName DirtyLabel {}) + -> Label + +GoTo <- Sp ({} GOTO MustName {}) + -> GoTo + +If <- Sp ({} {| IfHead IfBody* |} NeedEnd {}) + -> If + +IfHead <- Sp ({} IfPart {}) -> IfBlock + / Sp ({} ElseIfPart {}) -> ElseIfBlock + / Sp ({} ElsePart {}) -> ElseBlock +IfBody <- Sp ({} ElseIfPart {}) -> ElseIfBlock + / Sp ({} ElsePart {}) -> ElseBlock +IfPart <- IF DirtyExp NeedThen + {| (!ELSEIF !ELSE !END Action)* |} +ElseIfPart <- ELSEIF DirtyExp NeedThen + {| (!ELSEIF !ELSE !END Action)* |} +ElsePart <- ELSE + {| (!ELSEIF !ELSE !END Action)* |} + +For <- Loop / In + +Loop <- Sp ({} LoopBody {}) + -> Loop +LoopBody <- FOR LoopArgs NeedDo + {} {| (!END Action)* |} + NeedEnd +LoopArgs <- MustName AssignOrEQ + ({} {| (COMMA / !DO !END Exp)* |} {}) + -> PackLoopArgs + +In <- Sp ({} InBody {}) + -> In +InBody <- FOR InNameList NeedIn InExpList NeedDo + {} {| (!END Action)* |} + NeedEnd +InNameList <- ({} {| (COMMA / !IN !DO !END Name)* |} {}) + -> PackInNameList +InExpList <- ({} {| (COMMA / !DO !DO !END Exp)* |} {}) + -> PackInExpList + +While <- Sp ({} WhileBody {}) + -> While +WhileBody <- WHILE DirtyExp NeedDo + {| (!END Action)* |} + NeedEnd + +Repeat <- Sp ({} RepeatBody {}) + -> Repeat +RepeatBody <- REPEAT + {| (!UNTIL Action)* |} + NeedUntil DirtyExp + +LocalAttr <- {| (Sp '<' Sp MustName Sp LocalAttrEnd)+ |} + -> LocalAttr +LocalAttrEnd<- '>' / {} -> MissGT +Local <- Sp ({} LOCAL LocalNameList ((AssignOrEQ ExpList) / %nil) {}) + -> Local +Set <- Sp ({} SimpleList AssignOrEQ ExpList {}) + -> Set +LocalNameList + <- {| LocalName (Sp ',' LocalName)* |} +LocalName <- (MustName LocalAttr?) + -> LocalName + +Call <- Simple + -> SimpleCall + +LocalFunction + <- Sp ({} LOCAL FunctionNamedBody {}) + -> LocalFunction + +NamedFunction + <- Sp ({} FunctionNamedBody {}) + -> NamedFunction +FunctionNamedBody + <- FUNCTION FuncName FuncArgs + {| (!END Action)* |} + NeedEnd +FuncName <- {| Single (Sp SuffixWithoutCall)* |} + -> Simple + / {} -> MissName %nil +]] + +--grammar 'EmmyLua' (emmy.grammar) + +grammar 'Lua' [[ +Lua <- Head? + {| Action* |} -> Lua + Sp +Head <- '#' (!%nl .)* +]] + +return function (self, lua, mode) + local gram = compiled[mode] or compiled['Lua'] + local r, _, pos = gram:match(lua) + if not r then + local err = errorpos(pos) + return nil, err + end + + return r +end diff --git a/server-beta/src/parser/guide.lua b/server-beta/src/parser/guide.lua new file mode 100644 index 00000000..d7f39652 --- /dev/null +++ b/server-beta/src/parser/guide.lua @@ -0,0 +1,175 @@ +local error = error + +_ENV = nil + +local m = {} + +local blockTypes = { + ['while'] = true, + ['in'] = true, + ['loop'] = true, + ['repeat'] = true, + ['do'] = true, + ['function'] = true, + ['ifblock'] = true, + ['elseblock'] = true, + ['elseifblock'] = true, + ['main'] = true, +} + +local breakBlockTypes = { + ['while'] = true, + ['in'] = true, + ['loop'] = true, + ['repeat'] = true, +} + +--- 寻找所在函数 +function m.getParentFunction(root, obj) + for _ = 1, 1000 do + obj = root[obj.parent] + if not obj then + break + end + local tp = obj.type + if tp == 'function' then + return obj + end + end + return nil +end + +--- 寻找所在区块 +function m.getBlock(root, obj) + for _ = 1, 1000 do + if not obj then + return nil + end + local tp = obj.type + if blockTypes[tp] then + return obj + end + obj = root[obj.parent] + end + error('guide.getBlock overstack') +end + +--- 寻找所在父区块 +function m.getParentBlock(root, obj) + for _ = 1, 1000 do + obj = root[obj.parent] + if not obj then + return nil + end + local tp = obj.type + if blockTypes[tp] then + return obj + end + end + error('guide.getParentBlock overstack') +end + +--- 寻找所在可break的父区块 +function m.getBreakBlock(root, obj) + for _ = 1, 1000 do + obj = root[obj.parent] + if not obj then + return nil + end + local tp = obj.type + if breakBlockTypes[tp] then + return obj + end + if tp == 'function' then + return nil + end + end + error('guide.getBreakBlock overstack') +end + +--- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。 +--- 如果函数是主函数,则返回`0, nil`。 +---@return table +---@return integer +function m.getFunctionVarArgs(root, func) + if func.type == 'main' then + return 0, nil + end + if func.type ~= 'function' then + return nil, nil + end + local args = root[func.args] + if not args then + return nil, nil + end + for i = 1, #args do + local arg = root[args[i]] + if arg.type == '...' then + return i, arg + end + end + return nil, nil +end + +--- 获取指定区块中可见的局部变量 +---@param root table +---@param block table +---@param name string {comment = '变量名'} +---@param pos integer {comment = '可见位置'} +function m.getLocal(root, block, name, pos) + block = m.getBlock(root, block) + for _ = 1, 1000 do + if not block then + return nil + end + local locals = block.locals + local res + if not locals then + goto CONTINUE + end + for i = 1, #locals do + local loc = root[locals[i]] + if loc.effect > pos then + break + end + if loc[1] == name then + if not res or res.effect < loc.effect then + res = loc + end + end + end + if res then + return res + end + ::CONTINUE:: + block = m.getParentBlock(root, block) + end + error('guide.getLocal overstack') +end + +--- 获取指定区块中可见的标签 +---@param root table +---@param block table +---@param name string {comment = '标签名'} +function m.getLabel(root, block, name) + block = m.getBlock(root, block) + for _ = 1, 1000 do + if not block then + return nil + end + local labels = block.labels + if labels then + local label = labels[name] + if label then + return root[label] + end + end + if block.type == 'function' then + return nil + end + block = m.getParentBlock(root, block) + end + error('guide.getLocal overstack') +end + +return m diff --git a/server-beta/src/parser/init.lua b/server-beta/src/parser/init.lua new file mode 100644 index 00000000..5eeb0da2 --- /dev/null +++ b/server-beta/src/parser/init.lua @@ -0,0 +1,11 @@ +local api = { + grammar = require 'parser.grammar', + parse = require 'parser.parse', + compile = require 'parser.compile', + split = require 'parser.split', + calcline = require 'parser.calcline', + lines = require 'parser.lines', + guide = require 'parser.guide', +} + +return api diff --git a/server-beta/src/parser/lines.lua b/server-beta/src/parser/lines.lua new file mode 100644 index 00000000..f2f076e1 --- /dev/null +++ b/server-beta/src/parser/lines.lua @@ -0,0 +1,187 @@ +local m = require 'lpeglabel' + +local function utf8_len(buf, start, finish) + local len, pos = utf8.len(buf, start, finish) + if len then + return len + end + return 1 + utf8_len(buf, start, pos-1) + utf8_len(buf, pos+1, finish) +end + +local function Line(start, line, finish) + line.start = start + line.finish = finish - 1 + return line +end + +local function Space(...) + local line = {...} + local sp = 0 + local tab = 0 + for i = 1, #line do + if line[i] == ' ' then + sp = sp + 1 + elseif line[i] == '\t' then + tab = tab + 1 + end + line[i] = nil + end + line.sp = sp + line.tab = tab + return line +end + +local parser = m.P{ +'Lines', +Lines = m.Ct(m.V'Line'^0 * m.V'LastLine'), +Line = m.Cp() * m.V'Indent' * (1 - m.V'Nl')^0 * m.Cp() * m.V'Nl' / Line, +LastLine= m.Cp() * m.V'Indent' * (1 - m.V'Nl')^0 * m.Cp() / Line, +Nl = m.P'\r\n' + m.S'\r\n', +Indent = m.C(m.S' \t')^0 / Space, +} + +local mt = {} +mt.__index = mt + +function mt:position(row, col, code) + if row < 1 then + return 1 + end + code = code or self.code + if row > #self then + if code == 'utf8' then + return utf8_len(self.buf) + 1 + else + return #self.buf + 1 + end + end + local line = self[row] + local next_line = self[row+1] + local start = line.start + local finish + if next_line then + finish = next_line.start - 1 + else + finish = #self.buf + 1 + end + local pos + if code == 'utf8' then + pos = utf8.offset(self.buf, col, start) or finish + else + pos = start + col - 1 + end + if pos < start then + pos = start + elseif pos > finish then + pos = finish + end + return pos +end + +local function isCharByte(byte) + if not byte then + return false + end + -- [0-9] + if byte >= 48 and byte <= 57 then + return true + end + -- [A-Z] + if byte >= 65 and byte <= 90 then + return true + end + -- [a-z] + if byte >= 97 and byte <= 122 then + return true + end + -- <utf8> + if byte >= 128 then + return true + end + return false +end + +function mt:positionAsChar(row, col, code) + local pos = self:position(row, col, code) + if isCharByte(self.buf:byte(pos, pos)) then + return pos + elseif isCharByte(self.buf:byte(pos+1, pos+1)) then + return pos + 1 + end + return pos +end + +function mt:rowcol(pos, code) + if pos < 1 then + return 1, 1 + end + code = code or self.code + if pos >= #self.buf + 1 then + local start = self[#self].start + if code == 'utf8' then + return #self, utf8_len(self.buf, start) + 1 + else + return #self, #self.buf - start + 2 + end + end + local min = 1 + local max = #self + for _ = 1, 100 do + if max == min then + local start = self[min].start + if code == 'utf8' then + return min, utf8_len(self.buf, start, pos) + else + return min, pos - start + 1 + end + end + local row = (max - min) // 2 + min + local start = self[row].start + if pos < start then + max = row + elseif pos > start then + local next_start = self[row + 1].start + if pos < next_start then + if code == 'utf8' then + return row, utf8_len(self.buf, start, pos) + else + return row, pos - start + 1 + end + elseif pos > next_start then + min = row + 1 + else + return row + 1, 1 + end + else + return row, 1 + end + end + error('rowcol failed!') +end + +function mt:line(i) + local start, finish = self:range(i) + return self.buf:sub(start, finish) +end + +function mt:range(i) + if i < 1 or i > #self then + return 0, 0 + end + return self[i].start, self[i].finish +end + +function mt:set_code(code) + self.code = code +end + +return function (self, buf, code) + local lines, err = parser:match(buf) + if not lines then + return nil, err + end + lines.buf = buf + lines.code = code + + return setmetatable(lines, mt) +end diff --git a/server-beta/src/parser/parse.lua b/server-beta/src/parser/parse.lua new file mode 100644 index 00000000..cec946c0 --- /dev/null +++ b/server-beta/src/parser/parse.lua @@ -0,0 +1,37 @@ +local ast = require 'parser.ast' + +return function (self, lua, mode, version) + local errs = {} + local state = { + version = version, + lua = lua, + emmy = {}, + root = {}, + pushError = function (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, + } + ast.init(state) + local suc, res, err = xpcall(self.grammar, debug.traceback, self, lua, mode) + ast.close() + if not suc then + return nil, res + end + if not res then + state.pushError(err) + return nil, errs + end + state.ast = res + return state, errs +end diff --git a/server-beta/src/parser/relabel.lua b/server-beta/src/parser/relabel.lua new file mode 100644 index 00000000..ac902403 --- /dev/null +++ b/server-beta/src/parser/relabel.lua @@ -0,0 +1,361 @@ +-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $ + +-- imported functions and modules +local tonumber, type, print, error = tonumber, type, print, error +local pcall = pcall +local setmetatable = setmetatable +local tinsert, concat = table.insert, table.concat +local rep = string.rep +local m = require"lpeglabel" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- pattern's metatable +local mt = getmetatable(mm.P(0)) + + + +-- No more global accesses after this point +_ENV = nil + + +local any = m.P(1) +local dummy = mm.P(false) + + +local errinfo = { + NoPatt = "no pattern found", + ExtraChars = "unexpected characters after the pattern", + + ExpPatt1 = "expected a pattern after '/'", + + ExpPatt2 = "expected a pattern after '&'", + ExpPatt3 = "expected a pattern after '!'", + + ExpPatt4 = "expected a pattern after '('", + ExpPatt5 = "expected a pattern after ':'", + ExpPatt6 = "expected a pattern after '{~'", + ExpPatt7 = "expected a pattern after '{|'", + + ExpPatt8 = "expected a pattern after '<-'", + + ExpPattOrClose = "expected a pattern or closing '}' after '{'", + + ExpNumName = "expected a number, '+', '-' or a name (no space) after '^'", + ExpCap = "expected a string, number, '{}' or name after '->'", + + ExpName1 = "expected the name of a rule after '=>'", + ExpName2 = "expected the name of a rule after '=' (no space)", + ExpName3 = "expected the name of a rule after '<' (no space)", + + ExpLab1 = "expected a label after '{'", + + ExpNameOrLab = "expected a name or label after '%' (no space)", + + ExpItem = "expected at least one item after '[' or '^'", + + MisClose1 = "missing closing ')'", + MisClose2 = "missing closing ':}'", + MisClose3 = "missing closing '~}'", + MisClose4 = "missing closing '|}'", + MisClose5 = "missing closing '}'", -- for the captures + + MisClose6 = "missing closing '>'", + MisClose7 = "missing closing '}'", -- for the labels + + MisClose8 = "missing closing ']'", + + MisTerm1 = "missing terminating single quote", + MisTerm2 = "missing terminating double quote", +} + +local function expect (pattern, label) + return pattern + m.T(label) +end + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then + error("undefined name: " .. id) + end + return c +end + + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0) + +local arrow = S * "<-" + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1") + + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2") + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then + error("name '" .. c .. "' undefined") + end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = defined + Range + m.C(any - m.P"\n") + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add) + / function (c, p) return c == "^" and any - p or p end + * expect("]", "MisClose8") + +local function adddef (t, k, exp) + if t[k] then + -- TODO 改了一下这里的代码,重复定义不会抛错 + --error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.Cf(m.V"Seq" * (S * "/" * expect(S * m.V"Seq", "ExpPatt1"))^0, mt.__add) ); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul); + Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len + + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * + ( S * ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * expect( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow) + + name * m.Cc"lab" + ), + "ExpNumName") + + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + m.Cg(Def / getdef * m.Cc(mt.__div)) + ), + "ExpCap") + + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)), + "ExpName1") + ) + )^0, function (a,b,f) if f == "lab" then return a + mm.T(b) else return f(a,b) end end ); + Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1") + + String / mm.P + + Class + + defined + + "%" * expect(m.P"{", "ExpNameOrLab") + * expect(S * m.V"Label", "ExpLab1") + * expect(S * "}", "MisClose7") / mm.T + + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5") + * expect(S * ":}", "MisClose2") + / function (n, p) return mm.Cg(p, n) end + + "=" * expect(name, "ExpName2") + / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * expect(m.V"Exp", "ExpPatt6") + * expect(S * "~}", "MisClose3") / mm.Cs + + "{|" * expect(m.V"Exp", "ExpPatt7") + * expect(S * "|}", "MisClose4") / mm.Ct + + "{" * expect(m.V"Exp", "ExpPattOrClose") + * expect(S * "}", "MisClose5") / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * expect(name, "ExpName3") + * expect(">", "MisClose6")) * m.Cb("G") / NT; + Label = num + name; + Definition = name * arrow * expect(m.V"Exp", "ExpPatt8"); + Grammar = m.Cg(m.Cc(true), "G") + * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0, + adddef) / mm.P; +} + +local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P + * S * expect(-any, "ExtraChars") + +local function lineno (s, i) + if i == 1 then return 1, 1 end + local adjustment = 0 + -- report the current line if at end of line, not the next + if s:sub(i,i) == '\n' then + i = i-1 + adjustment = 1 + end + local rest, num = s:sub(1,i):gsub("[^\n]*\n", "") + local r = #rest + return 1 + num, (r ~= 0 and r or 1) + adjustment +end + +local function calcline (s, i) + if i == 1 then return 1, 1 end + local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") + local col = #rest + return 1 + line, col ~= 0 and col or 1 +end + + +local function splitlines(str) + local t = {} + local function helper(line) tinsert(t, line) return "" end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + p = p .. " " -- for better reporting of column numbers in errors when at EOF + local ok, cp, label, poserr = pcall(function() return pattern:match(p, 1, defs) end) + if not ok and cp then + if type(cp) == "string" then + cp = cp:gsub("^[^:]+:[^:]+: ", "") + end + error(cp, 3) + end + if not cp then + local lines = splitlines(p) + local line, col = lineno(p, poserr) + local err = {} + tinsert(err, "L" .. line .. ":C" .. col .. ": " .. errinfo[label]) + tinsert(err, lines[line]) + tinsert(err, rep(" ", col-1) .. "^") + error("syntax error(s) in pattern\n" .. concat(err, "\n"), 3) + end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, + calcline = calcline +} + +return re diff --git a/server-beta/src/parser/split.lua b/server-beta/src/parser/split.lua new file mode 100644 index 00000000..6ce4a4e7 --- /dev/null +++ b/server-beta/src/parser/split.lua @@ -0,0 +1,9 @@ +local m = require 'lpeglabel' + +local NL = m.P'\r\n' + m.S'\r\n' +local LINE = m.C(1 - NL) + +return function (str) + local MATCH = m.Ct((LINE * NL)^0 * LINE) + return MATCH:match(str) +end |