Add stage 06: Lua bootstrap

The goal of stage 06 is to try parse zig synax in lua. I pulled in
lpeglable 1.2.0 and parser-gen off github to get started. All of this
needs to be cleaned up rather soon.

Lua boostraps using tcc and musl from the previous stage. Since musl
0.6.0 doesn't support dynamic linking this build of lua doesn't support
shared libraries. I couldn't easily patch musl with dlopen and friends
so instead I link statically and call deps with c api.
This commit is contained in:
Dawid Sobczak 2023-07-06 11:48:59 +01:00
parent 2ae045cf8a
commit e6b88d5a0f
170 changed files with 72518 additions and 2 deletions

View file

@ -0,0 +1,128 @@
local m = require"lpeglabel"
local re = require"relabel"
local labels = {
{"ExpTermFirst", "expected an expression"},
{"ExpTermOp", "expected a term after the operator"},
{"MisClose", "missing a closing ')' after the expression"},
}
local function labelindex(labname)
for i, elem in ipairs(labels) do
if elem[1] == labname then
return i
end
end
error("could not find label: " .. labname)
end
local errors, subject
local function expect(patt, labname)
local i = labelindex(labname)
return patt + m.T(i)
end
local num = m.R("09")^1 / tonumber
local op = m.S("+-")
local function compute(tokens)
local result = tokens[1]
for i = 2, #tokens, 2 do
if tokens[i] == '+' then
result = result + tokens[i+1]
elseif tokens[i] == '-' then
result = result - tokens[i+1]
else
error('unknown operation: ' .. tokens[i])
end
end
return result
end
local g = m.P {
"Exp",
Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
OperandFirst = expect(m.V"Term", "ExpTermFirst"),
Operand = expect(m.V"Term", "ExpTermOp"),
Term = num + m.V"Group",
Group = "(" * m.V"Exp" * expect(")", "MisClose"),
}
function recorderror(pos, lab)
local line, col = re.calcline(subject, pos)
table.insert(errors, { line = line, col = col, msg = labels[lab][2] })
end
function record (labname)
return (m.Cp() * m.Cc(labelindex(labname))) / recorderror
end
function sync (p)
return (-p * m.P(1))^0
end
function defaultValue (p)
return p or m.Cc(1000)
end
local grec = m.P {
"S",
S = m.Rec(m.V"A", m.V"ErrExpTermFirst", labelindex("ExpTermFirst")), -- default value is 0
A = m.Rec(m.V"Sg", m.V"ErrExpTermOp", labelindex("ExpTermOp")),
Sg = m.Rec(g, m.V"ErrMisClose", labelindex("MisClose")),
ErrExpTermFirst = record("ExpTermFirst") * sync(op + ")") * defaultValue(),
ErrExpTermOp = record("ExpTermOp") * sync(op + ")") * defaultValue(),
ErrMisClose = record("MisClose") * sync(m.P")") * defaultValue(m.P""),
}
local function eval(input)
errors = {}
io.write("Input: ", input, "\n")
subject = input
local result, label, suffix = grec:match(input)
io.write("Syntactic errors found: " .. #errors, "\n")
if #errors > 0 then
local out = {}
for i, err in ipairs(errors) do
local pos = err.col
local msg = err.msg
table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
end
print(table.concat(out, "\n"))
end
io.write("Result = ")
return result
end
print(eval "90-70-(5)+3")
--> 20
print(eval "15+")
--> 2 + 0
print(eval "-2")
--> 0 - 2
print(eval "1+3+-9")
--> 1 + 3 + [0] - 9
print(eval "1+()3+")
--> 1 + ([0]) [3 +] [0]
print(eval "8-(2+)-5")
--> 8 - (2 + [0]) - 5
print(eval "()")
print(eval "")
print(eval "1+()+")
print(eval "1+(")
print(eval "3)")
print(eval "11+())3")

View file

@ -0,0 +1,122 @@
local m = require"lpeglabel"
local re = require"relabel"
local num = m.R("09")^1 / tonumber
local op = m.S("+-")
local labels = {}
local nlabels = 0
local function newError(lab, msg, psync, pcap)
nlabels = nlabels + 1
psync = psync or m.P(-1)
pcap = pcap or m.P""
labels[lab] = { id = nlabels, msg = msg, psync = psync, pcap = pcap }
end
newError("ExpTermFirst", "expected an expression", op + ")", m.Cc(1000))
newError("ExpTermOp", "expected a term after the operator", op + ")", m.Cc(1000))
newError("MisClose", "missing a closing ')' after the expression", m.P")")
newError("Extra", "extra characters found after the expression")
local errors, subject
local function expect(patt, labname)
local i = labels[labname].id
return patt + m.T(i)
end
local function compute(tokens)
local result = tokens[1]
for i = 2, #tokens, 2 do
if tokens[i] == '+' then
result = result + tokens[i+1]
elseif tokens[i] == '-' then
result = result - tokens[i+1]
else
error('unknown operation: ' .. tokens[i])
end
end
return result
end
local g = m.P {
"Exp",
Exp = m.Ct(m.V"OperandFirst" * (m.C(op) * m.V"Operand")^0) / compute,
OperandFirst = expect(m.V"Term", "ExpTermFirst"),
Operand = expect(m.V"Term", "ExpTermOp"),
Term = num + m.V"Group",
Group = "(" * m.V"Exp" * expect(")", "MisClose"),
}
function recorderror(pos, lab)
local line, col = re.calcline(subject, pos)
table.insert(errors, { line = line, col = col, msg = labels[lab].msg })
end
function record (labname)
return (m.Cp() * m.Cc(labname)) / recorderror
end
function sync (p)
return (-p * m.P(1))^0
end
function defaultValue (p)
return p or m.Cc(1000)
end
local grec = g * expect(m.P(-1), "Extra")
for k, v in pairs(labels) do
grec = m.Rec(grec, record(k) * sync(v.psync) * v.pcap, v.id)
end
local function eval(input)
errors = {}
io.write("Input: ", input, "\n")
subject = input
local result, label, suffix = grec:match(input)
io.write("Syntactic errors found: " .. #errors, "\n")
if #errors > 0 then
local out = {}
for i, err in ipairs(errors) do
local pos = err.col
local msg = err.msg
table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
end
print(table.concat(out, "\n"))
end
io.write("Result = ")
return result
end
print(eval "90-70-(5)+3")
--> 18
print(eval "15+")
--> 2 + 0
print(eval "-2")
--> 0 - 2
print(eval "1+3+-9")
--> 1 + 3 + [0] - 9
print(eval "1+()3+")
--> 1 + ([0]) [+] 3 + [0]
print(eval "8-(2+)-5")
--> 8 - (2 + [0]) - 5
print(eval "()")
print(eval "")
print(eval "1+()+")
print(eval "1+(")
print(eval "3)")
print(eval "11+()3")
--> 1 + ([0]) [+] 3 + [0]

View file

@ -0,0 +1,14 @@
local m = require'lpeglabel'
function matchPrint(p, s)
local r, lab, sfail = p:match(s)
print("r: ", r, "lab: ", lab, "sfail: ", sfail)
end
local p = m.P"a"^0 * m.P"b" + m.P"c"
matchPrint(p, "abc") --> r: 3 lab: nil sfail: nil
matchPrint(p, "c") --> r: 2 lab: nil sfail: nil
matchPrint(p, "aac") --> r: nil lab: 0 sfail: c
matchPrint(p, "xxc") --> r: nil lab: 0 sfail: xxc

View file

@ -0,0 +1,33 @@
local m = require'lpeglabel'
local re = require'relabel'
local id = m.R'az'^1
local g = m.P{
"S",
S = m.V"Id" * m.V"List",
List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List",
Id = m.V"Sp" * id + m.T(1),
Comma = m.V"Sp" * "," + m.T(2),
Sp = m.S" \n\t"^0,
}
function mymatch (g, s)
local r, e, sfail = g:match(s)
if not r then
local line, col = re.calcline(s, #s - #sfail)
local msg = "Error at line " .. line .. " (col " .. col .. ")"
if e == 1 then
return r, msg .. ": expecting an identifier before '" .. sfail .. "'"
elseif e == 2 then
return r, msg .. ": expecting ',' before '" .. sfail .. "'"
else
return r, msg
end
end
return r
end
print(mymatch(g, "one,two"))
print(mymatch(g, "one two"))
print(mymatch(g, "one,\n two,\nthree,"))

View file

@ -0,0 +1,39 @@
local m = require'lpeglabel'
local re = require'relabel'
local terror = {}
local function newError(s)
table.insert(terror, s)
return #terror
end
local errUndef = newError("undefined")
local errId = newError("expecting an identifier")
local errComma = newError("expecting ','")
local id = m.R'az'^1
local g = m.P{
"S",
S = m.V"Id" * m.V"List",
List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List",
Id = m.V"Sp" * id + m.T(errId),
Comma = m.V"Sp" * "," + m.T(errComma),
Sp = m.S" \n\t"^0,
}
function mymatch (g, s)
local r, e, sfail = g:match(s)
if not r then
local line, col = re.calcline(s, #s - #sfail)
local msg = "Error at line " .. line .. " (col " .. col .. "): "
return r, msg .. terror[e] .. " before '" .. sfail .. "'"
end
return r
end
print(mymatch(g, "one,two"))
print(mymatch(g, "one two"))
print(mymatch(g, "one,\n two,\nthree,"))

View file

@ -0,0 +1,67 @@
local m = require'lpeglabel'
local re = require'relabel'
local terror = {}
local function newError(s)
table.insert(terror, s)
return #terror
end
local errUndef = newError("undefined")
local errId = newError("expecting an identifier")
local errComma = newError("expecting ','")
local id = m.R'az'^1
local g = m.P{
"S",
S = m.V"Id" * m.V"List",
List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List",
Id = m.V"Sp" * id + m.T(errId),
Comma = m.V"Sp" * "," + m.T(errComma),
Sp = m.S" \n\t"^0,
}
local subject, errors
function recorderror(pos, lab)
local line, col = re.calcline(subject, pos)
table.insert(errors, { line = line, col = col, msg = terror[lab] })
end
function record (lab)
return (m.Cp() * m.Cc(lab)) / recorderror
end
function sync (p)
return (-p * m.P(1))^0
end
local grec = m.P{
"S",
S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId),
ErrComma = record(errComma) * sync(id),
ErrId = record(errId) * sync(m.P",")
}
function mymatch (g, s)
errors = {}
subject = s
local r, e, sfail = g:match(s)
if #errors > 0 then
local out = {}
for i, err in ipairs(errors) do
local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
table.insert(out, msg)
end
return nil, table.concat(out, "\n") .. "\n"
end
return r
end
print(mymatch(grec, "one,two"))
print(mymatch(grec, "one two three"))
print(mymatch(grec, "1,\n two, \n3,"))
print(mymatch(grec, "one\n two123, \nthree,"))

View file

@ -0,0 +1,79 @@
local m = require'lpeglabel'
local re = require'relabel'
local terror = {}
local function newError(s)
table.insert(terror, s)
return #terror
end
local errUndef = newError("undefined")
local errId = newError("expecting an identifier")
local errComma = newError("expecting ','")
local id = m.R'az'^1
local g = m.P{
"S",
S = m.V"Id" * m.V"List",
List = -m.P(1) + m.V"Comma" * m.V"Id" * m.V"List",
Id = m.V"Sp" * m.C(id) + m.T(errId),
Comma = m.V"Sp" * "," + m.T(errComma),
Sp = m.S" \n\t"^0,
}
local subject, errors
function recorderror(pos, lab)
local line, col = re.calcline(subject, pos)
table.insert(errors, { line = line, col = col, msg = terror[lab] })
end
function record (lab)
return (m.Cp() * m.Cc(lab)) / recorderror
end
function sync (p)
return (-p * m.P(1))^0
end
function defaultValue ()
return m.Cc"NONE"
end
local grec = m.P{
"S",
S = m.Rec(m.Rec(g, m.V"ErrComma", errComma), m.V"ErrId", errId),
ErrComma = record(errComma) * sync(id),
ErrId = record(errId) * sync(m.P",") * defaultValue(),
}
function mymatch (g, s)
errors = {}
subject = s
io.write("Input: ", s, "\n")
local r = { g:match(s) }
io.write("Captures (separated by ';'): ")
for k, v in pairs(r) do
io.write(v .. "; ")
end
io.write("\nSyntactic errors found: " .. #errors)
if #errors > 0 then
io.write("\n")
local out = {}
for i, err in ipairs(errors) do
local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
table.insert(out, msg)
end
io.write(table.concat(out, "\n"))
end
print("\n")
return r
end
mymatch(grec, "one,two")
mymatch(grec, "one two three")
mymatch(grec, "1,\n two, \n3,")
mymatch(grec, "one\n two123, \nthree,")

View file

@ -0,0 +1,30 @@
local re = require 'relabel'
local g = re.compile[[
S <- Id List
List <- !. / Comma Id List
Id <- Sp [a-z]+ / %{2}
Comma <- Sp ',' / %{3}
Sp <- %s*
]]
function mymatch (g, s)
local r, e, sfail = g:match(s)
if not r then
local line, col = re.calcline(s, #s - #sfail)
local msg = "Error at line " .. line .. " (col " .. col .. ")"
if e == 1 then
return r, msg .. ": expecting an identifier before '" .. sfail .. "'"
elseif e == 2 then
return r, msg .. ": expecting ',' before '" .. sfail .. "'"
else
return r, msg
end
end
return r
end
print(mymatch(g, "one,two"))
print(mymatch(g, "one two"))
print(mymatch(g, "one,\n two,\nthree,"))

View file

@ -0,0 +1,71 @@
local re = require 'relabel'
local errinfo = {
{"errUndef", "undefined"},
{"errId", "expecting an identifier"},
{"errComma", "expecting ','"},
}
local errmsgs = {}
local labels = {}
for i, err in ipairs(errinfo) do
errmsgs[i] = err[2]
labels[err[1]] = i
end
re.setlabels(labels)
local g = re.compile[[
S <- Id List
List <- !. / Comma Id List
Id <- Sp {[a-z]+} / %{errId}
Comma <- Sp ',' / %{errComma}
Sp <- %s*
]]
local errors
function recorderror (subject, pos, label)
local line, col = re.calcline(subject, pos)
table.insert(errors, { line = line, col = col, msg = errmsgs[labels[label]] })
return true
end
function sync (p)
return '( !(' .. p .. ') .)*'
end
local grec = re.compile(
"S <- %g //{errComma} ErrComma //{errId} ErrId" .. "\n" ..
"ErrComma <- ('' -> 'errComma' => recorderror) " .. sync('[a-z]+') .. "\n" ..
"ErrId <- ('' -> 'errId' => recorderror) " .. sync('","') .. "-> default"
, {g = g, recorderror = recorderror, default = "NONE"})
function mymatch (g, s)
errors = {}
subject = s
io.write("Input: ", s, "\n")
local r = { g:match(s) }
io.write("Captures (separated by ';'): ")
for k, v in pairs(r) do
io.write(v .. "; ")
end
io.write("\nSyntactic errors found: " .. #errors)
if #errors > 0 then
io.write("\n")
local out = {}
for i, err in ipairs(errors) do
local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
table.insert(out, msg)
end
io.write(table.concat(out, "\n"))
end
print("\n")
return r
end
mymatch(grec, "one,two")
mymatch(grec, "one two three")
mymatch(grec, "1,\n two, \n3,")
mymatch(grec, "one\n two123, \nthree,")

View file

@ -0,0 +1,151 @@
local re = require 'relabel'
local terror = {}
local function newError(l, msg)
table.insert(terror, { l = l, msg = msg} )
end
newError("errSemi", "Error: missing ';'")
newError("errExpIf", "Error: expected expression after 'if'")
newError("errThen", "Error: expected 'then' keyword")
newError("errCmdSeq1", "Error: expected at least a command after 'then'")
newError("errCmdSeq2", "Error: expected at least a command after 'else'")
newError("errEnd", "Error: expected 'end' keyword")
newError("errCmdSeqRep", "Error: expected at least a command after 'repeat'")
newError("errUntil", "Error: expected 'until' keyword")
newError("errExpRep", "Error: expected expression after 'until'")
newError("errAssignOp", "Error: expected ':=' in assigment")
newError("errExpAssign", "Error: expected expression after ':='")
newError("errReadName", "Error: expected an identifier after 'read'")
newError("errWriteExp", "Error: expected expression after 'write'")
newError("errSimpExp", "Error: expected '(', ID, or number after '<' or '='")
newError("errTerm", "Error: expected '(', ID, or number after '+' or '-'")
newError("errFactor", "Error: expected '(', ID, or number after '*' or '/'")
newError("errExpFac", "Error: expected expression after '('")
newError("errClosePar", "Error: expected ')' after expression")
local labelCode = {}
for k, v in ipairs(terror) do
labelCode[v.l] = k
end
re.setlabels(labelCode)
local g = re.compile[[
Tiny <- CmdSeq
CmdSeq <- (Cmd (SEMICOLON / ErrSemi)) (Cmd (SEMICOLON / ErrSemi))*
Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd
IfCmd <- IF (Exp / ErrExpIf) (THEN / ErrThen) (CmdSeq / ErrCmdSeq1) (ELSE (CmdSeq / ErrCmdSeq2) / '') (END / ErrEnd)
RepeatCmd <- REPEAT (CmdSeq / ErrCmdSeqRep) (UNTIL / ErrUntil) (Exp / ErrExpRep)
AssignCmd <- NAME (ASSIGNMENT / ErrAssignOp) (Exp / ErrExpAssign)
ReadCmd <- READ (NAME / ErrReadName)
WriteCmd <- WRITE (Exp / ErrWriteExp)
Exp <- SimpleExp ((LESS / EQUAL) (SimpleExp / ErrSimpExp) / '')
SimpleExp <- Term ((ADD / SUB) (Term / ErrTerm))*
Term <- Factor ((MUL / DIV) (Factor / ErrFactor))*
Factor <- OPENPAR (Exp / ErrExpFac) (CLOSEPAR / ErrClosePar) / NUMBER / NAME
ErrSemi <- %{errSemi}
ErrExpIf <- %{errExpIf}
ErrThen <- %{errThen}
ErrCmdSeq1 <- %{errCmdSeq1}
ErrCmdSeq2 <- %{errCmdSeq2}
ErrEnd <- %{errEnd}
ErrCmdSeqRep <- %{errCmdSeqRep}
ErrUntil <- %{errUntil}
ErrExpRep <- %{errExpRep}
ErrAssignOp <- %{errAssignOp}
ErrExpAssign <- %{errExpAssign}
ErrReadName <- %{errReadName}
ErrWriteExp <- %{errWriteExp}
ErrSimpExp <- %{errSimpExp}
ErrTerm <- %{errTerm}
ErrFactor <- %{errFactor}
ErrExpFac <- %{errExpFac}
ErrClosePar <- %{errClosePar}
ADD <- Sp '+'
ASSIGNMENT <- Sp ':='
CLOSEPAR <- Sp ')'
DIV <- Sp '/'
IF <- Sp 'if'
ELSE <- Sp 'else'
END <- Sp 'end'
EQUAL <- Sp '='
LESS <- Sp '<'
MUL <- Sp '*'
NAME <- Sp !RESERVED [a-z]+
NUMBER <- Sp [0-9]+
OPENPAR <- Sp '('
READ <- Sp 'read'
REPEAT <- Sp 'repeat'
SEMICOLON <- Sp ';'
SUB <- Sp '-'
THEN <- Sp 'then'
UNTIL <- Sp 'until'
WRITE <- Sp 'write'
RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+
Sp <- %s*
]]
local function mymatch(g, s)
local r, e, sfail = g:match(s)
if not r then
local line, col = re.calcline(s, #s - #sfail)
local msg = "Error at line " .. line .. " (col " .. col .. "): "
return r, msg .. terror[e].msg
end
return r
end
local s = [[
n := 5;
f := 1;
repeat
f := f + n;
n := n - 1
until (n < 1);
write f;]]
print(mymatch(g, s))
s = [[
n := 5;
f := 1;
repeat
f := f + n;
n := n - 1;
until (n < 1);
read ;]]
print(mymatch(g, s))
s = [[
if a < 1 then
b := 2;
else
b := 3;]]
print(mymatch(g, s))
s = [[
n := 5;
f := 1;
repeat
f := f + n;
n := n - 1;
untill (n < 1);
]]
print(mymatch(g, s))
s = [[
n := 5;
f := 1;
repeat
f := f + n;
n := n - 1;
3 (n < 1);
]]
print(mymatch(g, s))
print(mymatch(g, "a : 2"))
print(mymatch(g, "a := (2"))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
local errors = {}
local function new_error (label, msg)
table.insert(errors, { label = label, msg = msg })
end
new_error("Number", "malformed <number>")
new_error("String", "malformed <string>")
new_error("LongString", "unfinished long string")
new_error("LongComment", "unfinished long comment")
new_error("MissingOP", "missing '('")
new_error("MissingCP", "missing ')'")
new_error("MissingCC", "missing '}'")
new_error("MissingCB", "missing ']'")
new_error("UnionType", "expecting <type> after '|'")
new_error("FunctionType", "expecting <type> after '->'")
new_error("MethodType", "expecting <type> after '=>'")
new_error("TupleType", "expecting <type> after ','")
new_error("Type", "expecting <type> after ':'")
new_error("TypeDecEnd", "missing 'end' in type declaration")
new_error("TypeAliasName", "expecting <name> after 'typealias'")
new_error("MissingEqTypeAlias", "missing '=' in 'typealias'")
new_error("DotIndex", "expecting <name> after '.'")
new_error("MethodName", "expecting <name> after ':'")
new_error("Then", "missing 'then'")
new_error("IfEnd", "missing 'end' to close if statement")
new_error("WhileDo", "missing 'do' in while statement")
new_error("WhileEnd", "missing 'end' to close while statement")
new_error("BlockEnd", "missing 'end' to close block")
new_error("ForDo", "missing 'do' in for statement")
new_error("ForEnd", "missing 'end' to close for statement")
new_error("Until", "missing 'until' in repeat statement")
new_error("FuncEnd", "missing 'end' to close function declaration")
new_error("ParList", "expecting '...'")
new_error("MethodCall", "expecting '(' for method call")
new_error("Label1", "expecting <name> after '::'")
new_error("Label2", "expecting '::' to close label declaration")
new_error("LocalAssign1", "expecting expression list after '='")
new_error("LocalAssign2", "invalid local declaration")
new_error("ForGen", "expecting 'in'")
new_error("LocalFunc", "expecting <name> in local function declaration")
new_error("RetStat", "invalid statement after 'return'")
new_error("ElseIf", "expecting <exp> after 'elseif'")
new_error("SubExpr_1", "malformed 'or' expression")
new_error("SubExpr_2", "malformed 'and' expression")
new_error("SubExpr_3", "malformed relational expression")
new_error("SubExpr_4", "malformed '|' expression")
new_error("SubExpr_5", "malformed '~' expression")
new_error("SubExpr_6", "malformed '&' expression")
new_error("SubExpr_7", "malformed shift expression")
new_error("SubExpr_8", "malformed '..' expression")
new_error("SubExpr_9", "malformed addition expression")
new_error("SubExpr_10", "malformed multiplication expression")
new_error("SubExpr_11", "malformed unary expression")
new_error("SubExpr_12", "malformed '^' expression")
new_error("Stat", "invalid statement")
local labels = {}
for k, v in ipairs(errors) do
labels[v.label] = k
end
return {
errors = errors,
labels = labels,
}

View file

@ -0,0 +1,105 @@
local tllexer = {}
local lpeg = require "lpeglabel"
lpeg.locale(lpeg)
local tlerror = require "tlerror"
function tllexer.try (pat, label)
return pat + lpeg.T(tlerror.labels[label])
end
local function setffp (s, i, t, n)
if not t.ffp or i > t.ffp then
t.ffp = i
t.list = {}
t.list[n] = true
t.expected = "'" .. n .. "'"
elseif i == t.ffp then
if not t.list[n] then
t.list[n] = true
t.expected = "'" .. n .. "', " .. t.expected
end
end
return false
end
local function updateffp (name)
return lpeg.Cmt(lpeg.Carg(1) * lpeg.Cc(name), setffp)
end
tllexer.Shebang = lpeg.P("#") * (lpeg.P(1) - lpeg.P("\n"))^0 * lpeg.P("\n")
local Space = lpeg.space^1
local Equals = lpeg.P("=")^0
local Open = "[" * lpeg.Cg(Equals, "init") * "[" * lpeg.P("\n")^-1
local Close = "]" * lpeg.C(Equals) * "]"
local CloseEQ = lpeg.Cmt(Close * lpeg.Cb("init"),
function (s, i, a, b) return a == b end)
local LongString = Open * (lpeg.P(1) - CloseEQ)^0 * tllexer.try(Close, "LongString") /
function (s, o) return s end
local LongStringCm1 = Open * (lpeg.P(1) - CloseEQ)^0 * Close /
function (s, o) return s end
local Comment = lpeg.Rec(lpeg.P"--" * #Open * (LongStringCm1 / function() return end + lpeg.T(tlerror.labels["LongString"])),
lpeg.T(tlerror.labels["LongComment"]), tlerror.labels["LongString"]) +
lpeg.P("--") * (lpeg.P(1) - lpeg.P("\n"))^0
tllexer.Skip = (Space + Comment)^0
local idStart = lpeg.alpha + lpeg.P("_")
local idRest = lpeg.alnum + lpeg.P("_")
local Keywords = lpeg.P("and") + "break" + "do" + "elseif" + "else" + "end" +
"false" + "for" + "function" + "goto" + "if" + "in" +
"local" + "nil" + "not" + "or" + "repeat" + "return" +
"then" + "true" + "until" + "while"
tllexer.Reserved = Keywords * -idRest
local Identifier = idStart * idRest^0
tllexer.Name = -tllexer.Reserved * Identifier * -idRest
function tllexer.token (pat, name)
return pat * tllexer.Skip + updateffp(name) * lpeg.P(false)
end
function tllexer.symb (str)
return tllexer.token(lpeg.P(str), str)
end
function tllexer.kw (str)
return tllexer.token(lpeg.P(str) * -idRest, str)
end
local Hex = (lpeg.P("0x") + lpeg.P("0X")) * tllexer.try(lpeg.xdigit^1, "Number")
local Expo = lpeg.S("eE") * lpeg.S("+-")^-1 * tllexer.try(lpeg.digit^1, "Number")
local Float = (((lpeg.digit^1 * lpeg.P(".") * lpeg.digit^0 * tllexer.try(-lpeg.P("."), "Number")) +
(lpeg.P(".") * lpeg.digit^1)) * Expo^-1) +
(lpeg.digit^1 * Expo)
local Int = lpeg.digit^1
tllexer.Number = Hex + Float + Int
local ShortString = lpeg.P('"') *
((lpeg.P('\\') * lpeg.P(1)) + (lpeg.P(1) - lpeg.P('"')))^0 *
tllexer.try(lpeg.P('"'), "String") +
lpeg.P("'") *
((lpeg.P("\\") * lpeg.P(1)) + (lpeg.P(1) - lpeg.P("'")))^0 *
tllexer.try(lpeg.P("'"), "String")
tllexer.String = LongString + ShortString
-- for error reporting
tllexer.OneWord = tllexer.Name +
tllexer.Number +
tllexer.String +
tllexer.Reserved +
lpeg.P("...") +
lpeg.P(1)
return tllexer

View file

@ -0,0 +1,20 @@
local tlparser = require "tlparser"
local function getcontents(filename)
file = assert(io.open(filename, "r"))
contents = file:read("*a")
file:close()
return contents
end
if #arg ~= 1 then
print ("Usage: lua tlp.lua <file>")
os.exit(1)
end
local filename = arg[1]
local subject = getcontents(filename)
local r, msg = tlparser.parse(subject, filename, false, true)
if not r then print(msg) end
os.exit(0)

View file

@ -0,0 +1,245 @@
local tlparser = {}
local lpeg = require "lpeglabel"
lpeg.locale(lpeg)
local tllexer = require "tllexer"
local tlerror = require "tlerror"
local function chainl1 (pat, sep, label)
return pat * (sep * tllexer.try(pat, label))^0
end
local G = lpeg.P { "TypedLua";
TypedLua = tllexer.Shebang^-1 * tllexer.Skip * lpeg.V("Chunk") * tllexer.try(-1, "Stat");
-- type language
Type = lpeg.V("NilableType");
NilableType = lpeg.V("UnionType") * tllexer.symb("?")^-1;
UnionType = lpeg.V("PrimaryType") * (tllexer.symb("|") * tllexer.try(lpeg.V("PrimaryType"), "UnionType"))^0;
PrimaryType = lpeg.V("LiteralType") +
lpeg.V("BaseType") +
lpeg.V("NilType") +
lpeg.V("ValueType") +
lpeg.V("AnyType") +
lpeg.V("SelfType") +
lpeg.V("FunctionType") +
lpeg.V("TableType") +
lpeg.V("VariableType");
LiteralType = tllexer.token("false", "false") +
tllexer.token("true", "true") +
tllexer.token(tllexer.Number, "Number") +
tllexer.token(tllexer.String, "String");
BaseType = tllexer.token("boolean", "boolean") +
tllexer.token("number", "number") +
tllexer.token("string", "string") +
tllexer.token("integer", "integer");
NilType = tllexer.token("nil", "nil");
ValueType = tllexer.token("value", "value");
AnyType = tllexer.token("any", "any");
SelfType = tllexer.token("self", "self");
FunctionType = lpeg.V("InputType") * tllexer.symb("->") * tllexer.try(lpeg.V("NilableTuple"), "FunctionType");
MethodType = lpeg.V("InputType") * tllexer.symb("=>") * tllexer.try(lpeg.V("NilableTuple"), "MethodType");
InputType = tllexer.symb("(") * lpeg.V("TupleType")^-1 * tllexer.try(tllexer.symb(")"), "MissingCP");
NilableTuple = lpeg.V("UnionlistType") * tllexer.symb("?")^-1;
UnionlistType = lpeg.V("OutputType") * (tllexer.symb("|") * tllexer.try(lpeg.V("OutputType"), "UnionType"))^0;
OutputType = tllexer.symb("(") * lpeg.V("TupleType")^-1 * tllexer.try(tllexer.symb(")"), "MissingCP");
TupleType = lpeg.V("Type") * (tllexer.symb(",") * tllexer.try(lpeg.V("Type"), "TupleType"))^0 * tllexer.symb("*")^-1;
TableType = tllexer.symb("{") * lpeg.V("TableTypeBody")^-1 * tllexer.try(tllexer.symb("}"), "MissingCC");
TableTypeBody = lpeg.V("RecordType") +
lpeg.V("HashType") +
lpeg.V("ArrayType");
RecordType = lpeg.V("RecordField") * (tllexer.symb(",") * lpeg.V("RecordField"))^0 *
(tllexer.symb(",") * (lpeg.V("HashType") + lpeg.V("ArrayType")))^-1;
RecordField = tllexer.kw("const")^-1 *
lpeg.V("LiteralType") * tllexer.symb(":") * tllexer.try(lpeg.V("Type"), "Type");
HashType = lpeg.V("KeyType") * tllexer.symb(":") * tllexer.try(lpeg.V("FieldType"), "Type");
ArrayType = lpeg.V("FieldType");
KeyType = lpeg.V("BaseType") + lpeg.V("ValueType") + lpeg.V("AnyType");
FieldType = lpeg.V("Type");
VariableType = tllexer.token(tllexer.Name, "Name");
RetType = lpeg.V("NilableTuple") +
lpeg.V("Type");
Id = tllexer.token(tllexer.Name, "Name");
TypeDecId = (tllexer.kw("const") * lpeg.V("Id")) +
lpeg.V("Id");
IdList = lpeg.V("TypeDecId") * (tllexer.symb(",") * tllexer.try(lpeg.V("TypeDecId"), "TupleType"))^0;
IdDec = lpeg.V("IdList") * tllexer.symb(":") * tllexer.try((lpeg.V("Type") + lpeg.V("MethodType")), "Type");
IdDecList = (lpeg.V("IdDec")^1)^-1;
TypeDec = tllexer.token(tllexer.Name, "Name") * lpeg.V("IdDecList") * tllexer.try(tllexer.kw("end"), "TypeDecEnd");
Interface = tllexer.kw("interface") * lpeg.V("TypeDec") +
tllexer.kw("typealias") *
tllexer.try(tllexer.token(tllexer.Name, "Name"), "TypeAliasName") *
tllexer.try(tllexer.symb("="), "MissingEqTypeAlias") * lpeg.V("Type");
-- parser
Chunk = lpeg.V("Block");
StatList = (tllexer.symb(";") + lpeg.V("Stat"))^0;
Var = lpeg.V("Id");
TypedId = tllexer.token(tllexer.Name, "Name") * (tllexer.symb(":") * tllexer.try(lpeg.V("Type"), "Type"))^-1;
FunctionDef = tllexer.kw("function") * lpeg.V("FuncBody");
FieldSep = tllexer.symb(",") + tllexer.symb(";");
Field = ((tllexer.symb("[") * lpeg.V("Expr") * tllexer.try(tllexer.symb("]"), "MissingCB")) +
(tllexer.token(tllexer.Name, "Name"))) *
tllexer.symb("=") * lpeg.V("Expr") +
lpeg.V("Expr");
TField = (tllexer.kw("const") * lpeg.V("Field")) +
lpeg.V("Field");
FieldList = (lpeg.V("TField") * (lpeg.V("FieldSep") * lpeg.V("TField"))^0 *
lpeg.V("FieldSep")^-1)^-1;
Constructor = tllexer.symb("{") * lpeg.V("FieldList") * tllexer.try(tllexer.symb("}"), "MissingCC");
NameList = lpeg.V("TypedId") * (tllexer.symb(",") * lpeg.V("TypedId"))^0;
ExpList = lpeg.V("Expr") * (tllexer.symb(",") * lpeg.V("Expr"))^0;
FuncArgs = tllexer.symb("(") *
(lpeg.V("Expr") * (tllexer.symb(",") * lpeg.V("Expr"))^0)^-1 *
tllexer.try(tllexer.symb(")"), "MissingCP") +
lpeg.V("Constructor") +
tllexer.token(tllexer.String, "String");
OrOp = tllexer.kw("or");
AndOp = tllexer.kw("and");
RelOp = tllexer.symb("~=") +
tllexer.symb("==") +
tllexer.symb("<=") +
tllexer.symb(">=") +
tllexer.symb("<") +
tllexer.symb(">");
BOrOp = tllexer.symb("|");
BXorOp = tllexer.symb("~") * -lpeg.P("=");
BAndOp = tllexer.symb("&");
ShiftOp = tllexer.symb("<<") +
tllexer.symb(">>");
ConOp = tllexer.symb("..");
AddOp = tllexer.symb("+") +
tllexer.symb("-");
MulOp = tllexer.symb("*") +
tllexer.symb("//") +
tllexer.symb("/") +
tllexer.symb("%");
UnOp = tllexer.kw("not") +
tllexer.symb("-") +
tllexer.symb("~") +
tllexer.symb("#");
PowOp = tllexer.symb("^");
Expr = lpeg.V("SubExpr_1");
SubExpr_1 = chainl1(lpeg.V("SubExpr_2"), lpeg.V("OrOp"), "SubExpr_1");
SubExpr_2 = chainl1(lpeg.V("SubExpr_3"), lpeg.V("AndOp"), "SubExpr_2");
SubExpr_3 = chainl1(lpeg.V("SubExpr_4"), lpeg.V("RelOp"), "SubExpr_3");
SubExpr_4 = chainl1(lpeg.V("SubExpr_5"), lpeg.V("BOrOp"), "SubExpr_4");
SubExpr_5 = chainl1(lpeg.V("SubExpr_6"), lpeg.V("BXorOp"), "SubExpr_5");
SubExpr_6 = chainl1(lpeg.V("SubExpr_7"), lpeg.V("BAndOp"), "SubExpr_6");
SubExpr_7 = chainl1(lpeg.V("SubExpr_8"), lpeg.V("ShiftOp"), "SubExpr_7");
SubExpr_8 = lpeg.V("SubExpr_9") * lpeg.V("ConOp") * tllexer.try(lpeg.V("SubExpr_8"), "SubExpr_8") +
lpeg.V("SubExpr_9");
SubExpr_9 = chainl1(lpeg.V("SubExpr_10"), lpeg.V("AddOp"), "SubExpr_9");
SubExpr_10 = chainl1(lpeg.V("SubExpr_11"), lpeg.V("MulOp"), "SubExpr_10");
SubExpr_11 = lpeg.V("UnOp") * tllexer.try(lpeg.V("SubExpr_11"), "SubExpr_11") +
lpeg.V("SubExpr_12");
SubExpr_12 = lpeg.V("SimpleExp") * (lpeg.V("PowOp") * tllexer.try(lpeg.V("SubExpr_11"), "SubExpr_12"))^-1;
SimpleExp = tllexer.token(tllexer.Number, "Number") +
tllexer.token(tllexer.String, "String") +
tllexer.kw("nil") +
tllexer.kw("false") +
tllexer.kw("true") +
tllexer.symb("...") +
lpeg.V("FunctionDef") +
lpeg.V("Constructor") +
lpeg.V("SuffixedExp");
SuffixedExp = lpeg.V("PrimaryExp") * (
(tllexer.symb(".") * tllexer.try(tllexer.token(tllexer.Name, "Name"), "DotIndex")) / "index" +
(tllexer.symb("[") * lpeg.V("Expr") * tllexer.try(tllexer.symb("]"), "MissingCB")) / "index" +
(tllexer.symb(":") * tllexer.try(tllexer.token(tllexer.Name, "Name"), "MethodName") * tllexer.try(lpeg.V("FuncArgs"), "MethodCall")) / "call" +
lpeg.V("FuncArgs") / "call")^0 / function (...) local l = {...}; return l[#l] end;
PrimaryExp = lpeg.V("Var") / "var" +
tllexer.symb("(") * lpeg.V("Expr") * tllexer.try(tllexer.symb(")"), "MissingCP");
Block = lpeg.V("StatList") * lpeg.V("RetStat")^-1;
IfStat = tllexer.kw("if") * lpeg.V("Expr") * tllexer.try(tllexer.kw("then"), "Then") * lpeg.V("Block") *
(tllexer.kw("elseif") * tllexer.try(lpeg.V("Expr"), "ElseIf") * tllexer.try(tllexer.kw("then"), "Then") * lpeg.V("Block"))^0 *
(tllexer.kw("else") * lpeg.V("Block"))^-1 *
tllexer.try(tllexer.kw("end"), "IfEnd");
WhileStat = tllexer.kw("while") * lpeg.V("Expr") *
tllexer.try(tllexer.kw("do"), "WhileDo") * lpeg.V("Block") * tllexer.try(tllexer.kw("end"), "WhileEnd");
DoStat = tllexer.kw("do") * lpeg.V("Block") * tllexer.try(tllexer.kw("end"), "BlockEnd");
ForBody = tllexer.try(tllexer.kw("do"), "ForDo") * lpeg.V("Block");
ForNum = lpeg.V("Id") * tllexer.symb("=") * lpeg.V("Expr") * tllexer.symb(",") *
lpeg.V("Expr") * (tllexer.symb(",") * lpeg.V("Expr"))^-1 *
lpeg.V("ForBody");
ForGen = lpeg.V("NameList") * tllexer.try(tllexer.kw("in"), "ForGen") *
lpeg.V("ExpList") * lpeg.V("ForBody");
ForStat = tllexer.kw("for") * (lpeg.V("ForNum") + lpeg.V("ForGen")) * tllexer.try(tllexer.kw("end"), "ForEnd");
RepeatStat = tllexer.kw("repeat") * lpeg.V("Block") *
tllexer.try(tllexer.kw("until"), "Until") * lpeg.V("Expr");
FuncName = lpeg.V("Id") * (tllexer.symb(".") *
(tllexer.token(tllexer.Name, "Name")))^0 *
(tllexer.symb(":") * (tllexer.token(tllexer.Name, "Name")))^-1;
ParList = lpeg.V("NameList") * (tllexer.symb(",") * tllexer.try(lpeg.V("TypedVarArg"), "ParList"))^-1 +
lpeg.V("TypedVarArg");
TypedVarArg = tllexer.symb("...") * (tllexer.symb(":") * tllexer.try(lpeg.V("Type"), "Type"))^-1;
FuncBody = tllexer.try(tllexer.symb("("), "MissingOP") * lpeg.V("ParList")^-1 * tllexer.try(tllexer.symb(")"), "MissingCP") *
(tllexer.symb(":") * tllexer.try(lpeg.V("RetType"), "Type"))^-1 *
lpeg.V("Block") * tllexer.try(tllexer.kw("end"), "FuncEnd");
FuncStat = tllexer.kw("const")^-1 *
tllexer.kw("function") * lpeg.V("FuncName") * lpeg.V("FuncBody");
LocalFunc = tllexer.kw("function") *
tllexer.try(lpeg.V("Id"), "LocalFunc") * lpeg.V("FuncBody");
LocalAssign = lpeg.V("NameList") * tllexer.symb("=") * tllexer.try(lpeg.V("ExpList"), "LocalAssign1") +
lpeg.V("NameList") * (#(-tllexer.symb("=") * (lpeg.V("Stat") + -1)) * lpeg.P(true)) + lpeg.T(tlerror.labels["LocalAssign2"]);
LocalStat = tllexer.kw("local") *
(lpeg.V("LocalTypeDec") + lpeg.V("LocalFunc") + lpeg.V("LocalAssign"));
LabelStat = tllexer.symb("::") * tllexer.try(tllexer.token(tllexer.Name, "Name"), "Label1") * tllexer.try(tllexer.symb("::"), "Label2");
BreakStat = tllexer.kw("break");
GoToStat = tllexer.kw("goto") * tllexer.token(tllexer.Name, "Name");
RetStat = tllexer.kw("return") * tllexer.try(-lpeg.V("Stat"), "RetStat") *
(lpeg.V("Expr") * (tllexer.symb(",") * lpeg.V("Expr"))^0)^-1 *
tllexer.symb(";")^-1;
TypeDecStat = lpeg.V("Interface");
LocalTypeDec = lpeg.V("TypeDecStat");
LVar = (tllexer.kw("const") * lpeg.V("SuffixedExp")) +
lpeg.V("SuffixedExp");
ExprStat = lpeg.Cmt(lpeg.V("LVar") * lpeg.V("Assignment"),
function (s, i, ...)
local l = {...}
local i = 1
while l[i] ~= "=" do
local se = l[i]
if se ~= "var" and se ~= "index" then return false end
i = i + 1
end
return true
end) +
lpeg.Cmt(lpeg.V("SuffixedExp"),
function (s, i, se)
if se ~= "call" then return false end
return true
end);
Assignment = ((tllexer.symb(",") * lpeg.V("LVar"))^1)^-1 * (tllexer.symb("=") / "=") * lpeg.V("ExpList");
Stat = lpeg.V("IfStat") + lpeg.V("WhileStat") + lpeg.V("DoStat") + lpeg.V("ForStat") +
lpeg.V("RepeatStat") + lpeg.V("FuncStat") + lpeg.V("LocalStat") +
lpeg.V("LabelStat") + lpeg.V("BreakStat") + lpeg.V("GoToStat") +
lpeg.V("TypeDecStat") + lpeg.V("ExprStat");
}
local function lineno (s, i)
if i == 1 then return 1, 1 end
local rest, num = s:sub(1,i):gsub("[^\n]*\n", "")
local r = #rest
return 1 + num, r ~= 0 and r or 1
end
function tlparser.parse (subject, filename, strict, integer)
local errorinfo = {}
lpeg.setmaxstack(1000)
local ast, label, suffix = lpeg.match(G, subject, nil, errorinfo, strict, integer)
if not ast then
local line, col = lineno(subject, string.len(subject) - string.len(suffix))
local error_msg = string.format("%s:%d:%d: ", filename, line, col)
if label ~= 0 then
error_msg = error_msg .. tlerror.errors[label].msg
else
local u = lpeg.match(lpeg.C(tllexer.OneWord) + lpeg.Cc("EOF"), subject, errorinfo.ffp)
error_msg = error_msg .. string.format("unexpected '%s', expecting %s", u, errorinfo.expected)
end
return nil, error_msg
else
return true
end
end
return tlparser