lparse.lua
local util = import "libutil"
local setFrom, setMulti, words
= util.setFrom, util.setMulti, util.words
local function ALWAYS(thing) return function() return thing end end
local alwaysTrue = ALWAYS(true)
local alwaysEmpty = ALWAYS ""
local function alwaysNil() end
local function newVar(name, scope, anchor)
local self = {name = name, scope = scope, def = anchor, global = scope.global}
scope.vars[name] = self
return self
end
local function newScope(outer, isFunction)
local self = {
fnDepth = outer.fnDepth + (isFunction and 1 or 0),
depth = outer.depth + 1,
vars = setmetatable({}, {__index = outer.vars}),
parent = outer
}
if isFunction then newVar("return", self) end
return self
end
local function newSemiScope(scope)
return {
fnDepth = scope.fnDepth,
depth = scope.depth,
vars = setmetatable({}, {__index = scope.vars}),
parent = scope.parent
}
end
local function lookupVar(name, scope, isSet, globalScope)
local rv = scope.vars[name]
if rv then
if scope.fnDepth ~= rv.scope.fnDepth then rv.closed = not rv.global end
else
rv = newVar(name, globalScope)
if not isSet or scope.fnDepth > 1 then rv.badGlobal = true end
end
scope.vars[name] = rv
if isSet then rv.set = true else rv.used = true end
return rv
end
local function newGlobalScope(globals)
local vars = {}
local globalScope = {
fnDepth = 0,
depth = 0,
global = true,
vars = vars
}
for k in globals do vars[k] = newVar(k, globalScope, "STDLIB") end
return globalScope
end
local keyword = setFrom [[
and break do else elseif end
false for function if in local
nil not or repeat return then
true until while
+ - * / ^ =
~= <= >= < > ==
( ) { } [ ]
; : , . .. ...
]]
local synch = setFrom [[
break do else elseif end for
function if local repeat return until
while EOF
]]
local expectUntil = setFrom [[until]]
local expectElseif = setFrom [[elseif else end]]
local expectEnd = setFrom [[end]]
local expectEOF = setFrom [[EOF]]
return function(pkg)
function pkg.parse(lex, globals)
local ntag, Tag, Tagval = 0, {}, {}
local ttype, token, lasttag
local luapats
local globalScope = newGlobalScope(globals)
local currentScope = newScope(globalScope, true)
newVar("return", currentScope)
local function newTag(tt, val)
ntag = ntag + 1
Tag[ntag], Tagval[ntag] = tt, val or lex.token()
return ntag
end
local function tagAfter(tt, val)
local idx = lasttag + 1
for i = ntag, idx, -1 do Tag[i + 1], Tagval[i + 1] = Tag[i], Tagval[i] end
Tag[idx], Tagval[idx], ntag = tt, val, ntag + 1
return idx
end
local function setTag(tt)
Tag[lasttag] = tt
end
local function tag() return Tag[lasttag] end
local function setTagData(val)
if Tag[lasttag] ~= "missing" then
Tagval[lasttag] = val
end
end
local function tagData() return Tagval[lasttag] end
local function invent(toke)
lasttag = tagAfter("missing", toke)
end
local function badchar(self)
if self.match1"." then
newTag "invalid"
return self.lex(luapats)
end
end
local function next()
ttype, token = lex.lex(luapats, badchar)
if not ttype then ttype = "EOF" end
end
local function accept() lasttag = newTag(ttype, token); next(); return lasttag end
local function peek(tt) return ttype == tt end
local function match(tt) if ttype == tt then return accept() end end
local function mustMatch(tt) if ttype == tt then return accept() else invent(tt) end end
local function matchSet(ttab) local tt = ttab[ttype]; if tt then accept(); return tt() end end
local function matchNewVar(scope)
if ttype == "id" then
lasttag = newTag("var", token)
setTagData(newVar(token, scope, lasttag))
next()
return true
end
end
local function mustMatchKey()
if ttype == "id" then lasttag = newTag("key", token); next()
else invent "<key>"
end
return lasttag
end
local function matchVarList(scope)
if not matchNewVar(scope) then return 0, match "..." end
local rv = 1
while match "," do
rv = rv + 1
if not matchNewVar(scope) then
if match "..." then return rv - 1, lasttag
else invent "<var>"
end
end
end
return rv
end
local function defKey() if tag() == "key" then setTag "keydef" end end
local function useVar(isDef)
setTag(isDef and "varset" or "var")
setTagData(lookupVar(tagData(), currentScope, isDef, globalScope))
end
do
local function numberExtend(self)
self.match1("^E[-+]?%d+")
return "number"
end
local escapepats = {
["^\\%d%d?%d?"] = ALWAYS "escape",
["^\\."] = ALWAYS "escape",
["^[^'\"\\\n]*"] = ALWAYS "string",
["^([\"'])"] = function(_, c) return "string", c end
}
local function stringExtender(q)
return function(self)
newTag "string"
for f, r in self.tokens(escapepats, alwaysNil) do
if r == q then return "string"
else newTag(f)
end
end
newTag("missing", q)
return "string"
end
end
local function longExtend(self, ttype)
local depth = 1
while depth > 0 do
self.match1("^[^%[%]]*")
if self.match1("^%[%[") then depth = depth + 1
elseif self.match1("^%]%]") then depth = depth - 1
elseif not self.match1(".") then
newTag(ttype)
newTag("missing", string.rep("]]", depth))
self.setToken(-1)
break
end
end
return ttype
end
local function checkSymbol(self, op)
if keyword[op] then return op, op
else newTag("invalid", op); return self.lex(luapats, badchar)
end
end
luapats = {
["^([%a_][%w_]*)"] = function(_, word) return (keyword[word] and word or "id"), word end,
["^%s+"] = function(self)
newTag "whitespace"
return self.lex(luapats, badchar)
end,
["^%-%-"] = function(self)
if self.match1("^%[%[") then
longExtend(self, "comment")
else self.match1 "[^\n]*"
end
newTag "comment"
return self.lex(luapats, badchar)
end,
["^#"] = function(self)
self.match1 "[^\n]*"
newTag "preprocessor"
return self.lex(luapats, badchar)
end,
["^%[%["] = function(self) return longExtend(self, "string") end,
["^%d+%.?%d*"] = numberExtend,
["^%.%d+"] = numberExtend,
["^([(){}%[%]+%-*/^;:,])"]= checkSymbol,
["^([<>=~]=?)"] = checkSymbol,
["^(%.+)"] = checkSymbol,
["^'"] = stringExtender "'",
['^"'] = stringExtender '"',
}
end
local binop = setFrom [[+ - * / ^ ~= <= >= < > == .. and or]]
local unop = setFrom [[- not]]
local const = setFrom [[number string false nil true]]
local exprValTab, exprBinopTab, exprPostTab, exprCallTab, exprPostOnlyTab, exprCallOnlyTab =
{}, {}, {}, {}, {}, {}
local function parseExpr() return matchSet(exprValTab) end
local function mustParseExpr()
local fn = exprValTab[ttype]
if fn then accept(); return fn()
else invent "<expr>"
end
end
local function parseBinop()
local fn = exprBinopTab[ttype]
if fn then accept(); return fn()
else return true
end
end
local function parsePostfix()
local fn = exprPostTab[ttype]
if fn then accept(); return fn()
else return true
end
end
local function parseCall()
local fn = exprCallTab[ttype]
if fn then accept(); return fn()
else invent "()"; return parsePostfix()
end
end
local function parsePostfixOnly(orthis)
local fn = exprPostOnlyTab[ttype]
if fn then if orthis == "var" then useVar() end; accept(); return fn()
else return orthis
end
end
local function parseCallOnly()
local fn = exprCallOnlyTab[ttype]
if fn then accept(); return fn()
else invent "()"; return parsePostfixOnly "call"
end
end
local function parseSimpleTermStartingParen()
mustParseExpr()
mustMatch ")"
return parsePostfixOnly "expr"
end
local function parseSimpleTermStartingId()
return parsePostfixOnly "var"
end
local function parseExprList()
if parseExpr() then while match "," do mustParseExpr() end end
return true
end
local function mustParseExprList()
if parseExpr() then while match "," do mustParseExpr() end
else invent "<expr>"
end
end
local function parseTableConstructor()
repeat
local doit, isId = mustParseExpr, match "id"
if isId then if peek "=" then setTag "keydef"; accept()
else useVar(); doit = parsePostfix
end
elseif match "[" then mustParseExpr(); mustMatch "]"; mustMatch "="
elseif match "}" then return true
end
doit()
until not (match "," or match ";")
mustMatch "}"
return true
end
local stmtTab = {}
local function inventSomething(expect)
local invented = "end"
if not expect[invented] then for k in expect do invented = k; break end end
invent(invented)
return invented
end
local function popScope(tt)
setTagData(currentScope)
currentScope = currentScope.parent
return tt
end
local function parseChunk(expect)
local endfound
repeat
for notend in matchSet, stmtTab do
match ";"
if notend then
if endfound then return popScope(inventSomething(expect)) end
else
endfound = true
end
end
if expect[ttype] then
local tt = ttype
accept()
return popScope(tt)
end
if ttype == "EOF" then return popScope(inventSomething(expect)) end
newTag("skip", "")
repeat accept() until synch[ttype]
lasttag = tagAfter("endskip", "")
until nil
end
local function parseFunctionBody(scope, colon)
local nvar, ellipsis
currentScope = scope
if colon then
newVar("self", scope, colon)
end
if match "(" then
nvar, ellipsis = matchVarList(currentScope)
mustMatch ")"
else invent "()"
end
if ellipsis then newVar("arg", scope, ellipsis) end
parseChunk(expectEnd)
return true
end
for k in unop do exprValTab[k] = mustParseExpr end
for k in const do exprValTab[k] = parseBinop end
for k in binop do exprPostTab[k] = mustParseExpr; exprBinopTab[k] = mustParseExpr end
do
local function fakeEqual() setTag "meantequal"; return mustParseExpr() end
exprPostTab["="] = fakeEqual
exprBinopTab["="] = fakeEqual
end
exprValTab["{"] = function() parseTableConstructor(); return parseBinop() end
exprValTab["function"] = function()
local scope = newScope(currentScope, true)
setTagData(scope)
parseFunctionBody(scope)
return parseBinop() end
exprValTab["("] = function() mustParseExpr(); mustMatch ")"; return parsePostfix() end
exprValTab["id"] = function() useVar(); return parsePostfix() end
exprPostTab["string"] = parsePostfix
exprPostTab["{"] = function() parseTableConstructor(); return parsePostfix() end
exprPostTab["("] = function() parseExprList(); mustMatch ")"; return parsePostfix() end
exprPostTab["["] = function() mustParseExpr(); mustMatch "]"; return parsePostfix() end
exprPostTab["."] = function() mustMatchKey(); return parsePostfix() end
exprPostTab[":"] = function() mustMatchKey(); return parseCall() end
exprCallTab["string"] = parsePostfix
exprCallTab["{"] = function() parseTableConstructor(); return parsePostfix() end
exprCallTab["("] = function() parseExprList(); mustMatch ")"; return parsePostfix() end
exprPostOnlyTab["string"] = function() return parsePostfixOnly "call" end
exprPostOnlyTab["{"] = function() parseTableConstructor(); return parsePostfixOnly "call" end
exprPostOnlyTab["("] = function() parseExprList(); mustMatch ")"; return parsePostfixOnly "call" end
exprPostOnlyTab["["] = function() mustParseExpr(); mustMatch "]"; return parsePostfixOnly "index" end
exprPostOnlyTab["."] = function() mustMatchKey(); return parsePostfixOnly "key" end
exprPostOnlyTab[":"] = function() mustMatchKey(); return parseCallOnly() end
exprCallOnlyTab["string"] = function() return parsePostfixOnly "call" end
exprCallOnlyTab["{"] = function() parseTableConstructor(); return parsePostfixOnly "call" end
exprCallOnlyTab["("] = function() parseExprList(); mustMatch ")"; return parsePostfixOnly "call" end
do
local callOrAssignTab
local function parseAssignOrCall(termParser)
if not callOrAssignTab[termParser()]() then
while match "," do
if match "id" then termParser = parseSimpleTermStartingId
elseif match "(" then termParser = parseSimpleTermStartingParen
else invent "= <val>"; return
end
if callOrAssignTab[termParser()](true) then invent ".<key>" end
end
mustMatch "="
mustParseExprList()
end
return true
end
callOrAssignTab = {
expr = function(wantsAssign) if not wantsAssign then invent "()" end; return true end,
var = function() useVar(true) end,
key = function() defKey() end,
index = alwaysNil,
call = alwaysTrue
}
stmtTab["("] = function() return parseAssignOrCall(parseSimpleTermStartingParen) end
stmtTab["id"] = function() return parseAssignOrCall(parseSimpleTermStartingId) end
end
stmtTab["break"] = function()
local s = currentScope