1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 17:59:30 +00:00

Candran 0.3.0

* Added @ self alias
* Added short anonymous functions declaration
* Made assignment operators works in every direction, except up, down,
behind and below, because this would be hard to visualize.
* Moved files around.
* Error rewriting.
* Discover the amazing can commandline tool, which includes a fantastic°
REPL and program running abilities.
* Added functions which plagiarize Lua.
* Added 0.1.0 to the version number.
* If you still love pineapple flavored bread, don't hesitate to show
your feelings.

Also, the tests are out of date. Sad.

°: not really.
This commit is contained in:
Étienne Fildadut 2017-08-16 22:33:44 +02:00
parent 2a1e293aa5
commit 4af2b41a0d
17 changed files with 2413 additions and 1865 deletions

126
lib/cmdline.lua Normal file
View file

@ -0,0 +1,126 @@
-- started: 2008-04-12 by Shmuel Zeigerman
-- license: public domain
local ipairs,pairs,setfenv,tonumber,loadstring,type =
ipairs,pairs,setfenv,tonumber,loadstring,type
local tinsert, tconcat = table.insert, table.concat
local function commonerror (msg)
return nil, ("[cmdline]: " .. msg)
end
local function argerror (msg, numarg)
msg = msg and (": " .. msg) or ""
return nil, ("[cmdline]: bad argument #" .. numarg .. msg)
end
local function iderror (numarg)
return argerror("ID not valid", numarg)
end
local function idcheck (id)
return id:match("^[%a_][%w_]*$") and true
end
--[[------------------------------------------------------------------------
Syntax:
t_out = getparam(t_in [,options] [,params])
Parameters:
t_in: table - list of string arguments to be processed in order
(usually it is the `arg' table created by the Lua interpreter).
* if an argument begins with $, the $ is skipped and the rest is inserted
into the array part of the output table.
* if an argument begins with -, the rest is a sequence of variables
(separated by commas or semicolons) that are all set to true;
example: -var1,var2 --> var1,var2 = true,true
* if an argument begins with !, the rest is a Lua chunk;
example: !a=(40+3)*5;b=20;name="John";window={w=600,h=480}
* if an argument contains =, then it is an assignment in the form
var1,...=value (no space is allowed around the =)
* if value begins with $, the $ is skipped, the rest is a string
example: var1,var2=$ --> var1,var2 = "",""
example: var1,var2=$125 --> var1,var2 = "125","125"
example: var1,var2=$$125 --> var1,var2 = "$125","$125"
* if value is convertible to number, it is a number
example: var1,var2=125 --> var1,var2 = 125,125
* if value is true of false, it is a boolean
example: var1=false --> var1 = false
* otherwise it is a string
example: name=John --> name = "John"
* if an argument neither begins with one of the special characters (-,!,$),
nor contains =, it is inserted as is into the array part of the output
table.
options (optional): a list of names of all command-line options and parameters
permitted in the application; used to check that each found option
is valid; no checks are done if not supplied.
params (optional): a list of names of all command-line parameters required
by the application; used to check that each required parameter is present;
no checks are done if not supplied.
Returns:
On success: the output table, e.g. { [1]="./myfile.txt", name="John", age=40 }
On error: nil followed by error message string.
--]]------------------------------------------------------------------------
return function(t_in, options, params)
local t_out = {}
for i,v in ipairs(t_in) do
local prefix, command = v:sub(1,1), v:sub(2)
if prefix == "$" then
tinsert(t_out, command)
elseif prefix == "-" then
for id in command:gmatch"[^,;]+" do
if not idcheck(id) then return iderror(i) end
t_out[id] = true
end
elseif prefix == "!" then
local f, err = loadstring(command)
if not f then return argerror(err, i) end
setfenv(f, t_out)()
elseif v:find("=") then
local ids, val = v:match("^([^=]+)%=(.*)") -- no space around =
if not ids then return argerror("invalid assignment syntax", i) end
if val == "false" then
val = false
elseif val == "true" then
val = true
else
val = val:sub(1,1)=="$" and val:sub(2) or tonumber(val) or val
end
for id in ids:gmatch"[^,;]+" do
if not idcheck(id) then return iderror(i) end
t_out[id] = val
end
else
tinsert(t_out, v)
end
end
if options then
local lookup, unknown = {}, {}
for _,v in ipairs(options) do lookup[v] = true end
for k,_ in pairs(t_out) do
if lookup[k]==nil and type(k)=="string" then tinsert(unknown, k) end
end
if #unknown > 0 then
return commonerror("unknown options: " .. tconcat(unknown, ", "))
end
end
if params then
local missing = {}
for _,v in ipairs(params) do
if t_out[v]==nil then tinsert(missing, v) end
end
if #missing > 0 then
return commonerror("missing parameters: " .. tconcat(missing, ", "))
end
end
return t_out
end

20
lib/lua-parser/LICENSE Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Andre Murbach Maidl
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

127
lib/lua-parser/README.md Normal file
View file

@ -0,0 +1,127 @@
lua-parser
==========
[![Build Status](https://travis-ci.org/andremm/lua-parser.svg?branch=master)](https://travis-ci.org/andremm/lua-parser)
This is a Lua 5.3 parser written with [LPegLabel](https://github.com/sqmedeiros/lpeglabel) that
generates an AST in a format that is similar to the one specified by [Metalua](https://github.com/fab13n/metalua-parser).
The parser uses LPegLabel to provide more specific error messages.
Requirements
------------
lua >= 5.1
lpeglabel >= 1.0.0
API
---
The package `lua-parser` has two modules: `lua-parser.parser`
and `lua-parser.pp`.
The module `lua-parser.parser` implements the function `parser.parse`:
* `parser.parse (subject, filename)`
Both subject and filename should be strings.
It tries to parse subject and returns the AST in case of success.
It returns **nil** plus an error message in case of error.
In case of error, the parser uses the string filename to build an
error message.
The module `lua-parser.pp` implements a pretty printer to the AST and
a dump function:
* `pp.tostring (ast)`
It converts the AST to a string and returns this string.
* `pp.print (ast)`
It converts the AST to a string and prints this string.
* `pp.dump (ast[, i])`
It dumps the AST to the screen.
The parameter **i** sets the indentation level.
AST format
----------
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil
| `Dots
| `Boolean{ <boolean> }
| `Number{ <number> }
| `String{ <string> }
| `Function{ { `Id{ <string> }* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
opid: -- includes additional operators from Lua 5.3 and all relational operators
'add' | 'sub' | 'mul' | 'div'
| 'idiv' | 'mod' | 'pow' | 'concat'
| 'band' | 'bor' | 'bxor' | 'shl' | 'shr'
| 'eq' | 'ne' | 'lt' | 'gt' | 'le' | 'ge'
| 'and' | 'or' | 'unm' | 'len' | 'bnot' | 'not'
Usage
--------
**Code example for parsing a string:**
local parser = require "lua-parser.parser"
local pp = require "lua-parser.pp"
if #arg ~= 1 then
print("Usage: parse.lua <string>")
os.exit(1)
end
local ast, error_msg = parser.parse(arg[1], "example.lua")
if not ast then
print(error_msg)
os.exit(1)
end
pp.print(ast)
os.exit(0)
**Running the above code example using a string without syntax error:**
$ lua parse.lua "for i=1, 10 do print(i) end"
{ `Fornum{ `Id "i", `Number "1", `Number "10", { `Call{ `Id "print", `Id "i" } } } }
**Running the above code example using a string with syntax error:**
$ lua parse.lua "for i=1, 10 do print(i) "
example.lua:1:24: syntax error, expected 'end' to close the for loop

495
lib/lua-parser/parser.lua Normal file
View file

@ -0,0 +1,495 @@
--[[
This module implements a parser for Lua 5.3 with LPeg,
and generates an Abstract Syntax Tree that is similar to the one generated by Metalua.
For more information about Metalua, please, visit:
https://github.com/fab13n/metalua-parser
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} (opid? = opid?)? {expr+} } -- lhs1, lhs2... op=op e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil
| `Dots
| `Boolean{ <boolean> }
| `Number{ <number> }
| `String{ <string> }
| `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
opid: -- includes additional operators from Lua 5.3 and all relational operators
'add' | 'sub' | 'mul' | 'div'
| 'idiv' | 'mod' | 'pow' | 'concat'
| 'band' | 'bor' | 'bxor' | 'shl' | 'shr'
| 'eq' | 'ne' | 'lt' | 'gt' | 'le' | 'ge'
| 'and' | 'or' | 'unm' | 'len' | 'bnot' | 'not'
]]
local lpeg = require "lpeglabel"
lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Cs, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Cs, lpeg.Ct
local Lc, T = lpeg.Lc, lpeg.T
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
local space = lpeg.space
-- error message auxiliary functions
local labels = {
{ "ErrExtra", "unexpected character(s), expected EOF" },
{ "ErrInvalidStat", "unexpected token, invalid start of statement" },
{ "ErrEndIf", "expected 'end' to close the if statement" },
{ "ErrExprIf", "expected a condition after 'if'" },
{ "ErrThenIf", "expected 'then' after the condition" },
{ "ErrExprEIf", "expected a condition after 'elseif'" },
{ "ErrThenEIf", "expected 'then' after the condition" },
{ "ErrEndDo", "expected 'end' to close the do block" },
{ "ErrExprWhile", "expected a condition after 'while'" },
{ "ErrDoWhile", "expected 'do' after the condition" },
{ "ErrEndWhile", "expected 'end' to close the while loop" },
{ "ErrUntilRep", "expected 'until' at the end of the repeat loop" },
{ "ErrExprRep", "expected a conditions after 'until'" },
{ "ErrForRange", "expected a numeric or generic range after 'for'" },
{ "ErrEndFor", "expected 'end' to close the for loop" },
{ "ErrExprFor1", "expected a starting expression for the numeric range" },
{ "ErrCommaFor", "expected ',' to split the start and end of the range" },
{ "ErrExprFor2", "expected an ending expression for the numeric range" },
{ "ErrExprFor3", "expected a step expression for the numeric range after ','" },
{ "ErrInFor", "expected '=' or 'in' after the variable(s)" },
{ "ErrEListFor", "expected one or more expressions after 'in'" },
{ "ErrDoFor", "expected 'do' after the range of the for loop" },
{ "ErrDefLocal", "expected a function definition or assignment after local" },
{ "ErrNameLFunc", "expected a function name after 'function'" },
{ "ErrEListLAssign", "expected one or more expressions after '='" },
{ "ErrEListAssign", "expected one or more expressions after '='" },
{ "ErrFuncName", "expected a function name after 'function'" },
{ "ErrNameFunc1", "expected a function name after '.'" },
{ "ErrNameFunc2", "expected a method name after ':'" },
{ "ErrOParenPList", "expected '(' for the parameter list" },
{ "ErrCParenPList", "expected ')' to close the parameter list" },
{ "ErrEndFunc", "expected 'end' to close the function body" },
{ "ErrParList", "expected a variable name or '...' after ','" },
{ "ErrLabel", "expected a label name after '::'" },
{ "ErrCloseLabel", "expected '::' after the label" },
{ "ErrGoto", "expected a label after 'goto'" },
{ "ErrRetList", "expected an expression after ',' in the return statement" },
{ "ErrVarList", "expected a variable name after ','" },
{ "ErrExprList", "expected an expression after ','" },
{ "ErrOrExpr", "expected an expression after 'or'" },
{ "ErrAndExpr", "expected an expression after 'and'" },
{ "ErrRelExpr", "expected an expression after the relational operator" },
{ "ErrBOrExpr", "expected an expression after '|'" },
{ "ErrBXorExpr", "expected an expression after '~'" },
{ "ErrBAndExpr", "expected an expression after '&'" },
{ "ErrShiftExpr", "expected an expression after the bit shift" },
{ "ErrConcatExpr", "expected an expression after '..'" },
{ "ErrAddExpr", "expected an expression after the additive operator" },
{ "ErrMulExpr", "expected an expression after the multiplicative operator" },
{ "ErrUnaryExpr", "expected an expression after the unary operator" },
{ "ErrPowExpr", "expected an expression after '^'" },
{ "ErrExprParen", "expected an expression after '('" },
{ "ErrCParenExpr", "expected ')' to close the expression" },
{ "ErrNameIndex", "expected a field name after '.'" },
{ "ErrExprIndex", "expected an expression after '['" },
{ "ErrCBracketIndex", "expected ']' to close the indexing expression" },
{ "ErrNameMeth", "expected a method name after ':'" },
{ "ErrMethArgs", "expected some arguments for the method call (or '()')" },
{ "ErrArgList", "expected an expression after ',' in the argument list" },
{ "ErrCParenArgs", "expected ')' to close the argument list" },
{ "ErrCBraceTable", "expected '}' to close the table constructor" },
{ "ErrEqField", "expected '=' after the table key" },
{ "ErrExprField", "expected an expression after '='" },
{ "ErrExprFKey", "expected an expression after '[' for the table key" },
{ "ErrCBracketFKey", "expected ']' to close the table key" },
{ "ErrDigitHex", "expected one or more hexadecimal digits after '0x'" },
{ "ErrDigitDeci", "expected one or more digits after the decimal point" },
{ "ErrDigitExpo", "expected one or more digits for the exponent" },
{ "ErrQuote", "unclosed string" },
{ "ErrHexEsc", "expected exactly two hexadecimal digits after '\\x'" },
{ "ErrOBraceUEsc", "expected '{' after '\\u'" },
{ "ErrDigitUEsc", "expected one or more hexadecimal digits for the UTF-8 code point" },
{ "ErrCBraceUEsc", "expected '}' after the code point" },
{ "ErrEscSeq", "invalid escape sequence" },
{ "ErrCloseLStr", "unclosed long string" },
}
local function throw(label)
label = "Err" .. label
for i, labelinfo in ipairs(labels) do
if labelinfo[1] == label then
return T(i)
end
end
error("Label not found: " .. label)
end
local function expect (patt, label)
return patt + throw(label)
end
-- regular combinators and auxiliary functions
local function token (patt)
return patt * V"Skip"
end
local function sym (str)
return token(P(str))
end
local function kw (str)
return token(P(str) * -V"IdRest")
end
local function tagC (tag, patt)
return Ct(Cg(Cp(), "pos") * Cg(Cc(tag), "tag") * patt)
end
local function unaryOp (op, e)
return { tag = "Op", pos = e.pos, [1] = op, [2] = e }
end
local function binaryOp (e1, op, e2)
if not op then
return e1
else
return { tag = "Op", pos = e1.pos, [1] = op, [2] = e1, [3] = e2 }
end
end
local function sepBy (patt, sep, label)
if label then
return patt * Cg(sep * expect(patt, label))^0
else
return patt * Cg(sep * patt)^0
end
end
local function chainOp (patt, sep, label)
return Cf(sepBy(patt, sep, label), binaryOp)
end
local function commaSep (patt, label)
return sepBy(patt, sym(","), label)
end
local function tagDo (block)
block.tag = "Do"
return block
end
local function fixFuncStat (func)
if func[1].is_method then table.insert(func[2][1], 1, { tag = "Id", [1] = "self" }) end
func[1] = {func[1]}
func[2] = {func[2]}
return func
end
local function addDots (params, dots)
if dots then table.insert(params, dots) end
return params
end
local function insertIndex (t, index)
return { tag = "Index", pos = t.pos, [1] = t, [2] = index }
end
local function markMethod(t, method)
if method then
return { tag = "Index", pos = t.pos, is_method = true, [1] = t, [2] = method }
end
return t
end
local function makeIndexOrCall (t1, t2)
if t2.tag == "Call" or t2.tag == "Invoke" then
local t = { tag = t2.tag, pos = t1.pos, [1] = t1 }
for k, v in ipairs(t2) do
table.insert(t, v)
end
return t
end
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
end
local function fixAnonymousMethodParams(t1, t2)
if t1 == ":" then
t1 = t2
table.insert(t1, 1, { tag = "Id", "self" })
end
return t1
end
-- grammar
local G = { V"Lua",
Lua = V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra");
Shebang = P"#!" * (P(1) - P"\n")^0;
Block = tagC("Block", V"Stat"^0 * V"RetStat"^-1);
Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
+ V"FuncCall" + V"Assignment" + sym(";") + -V"BlockEnd" * throw("InvalidStat");
BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + -1;
IfStat = tagC("If", V"IfPart" * V"ElseIfPart"^0 * V"ElsePart"^-1 * expect(kw("end"), "EndIf"));
IfPart = kw("if") * expect(V"Expr", "ExprIf") * expect(kw("then"), "ThenIf") * V"Block";
ElseIfPart = kw("elseif") * expect(V"Expr", "ExprEIf") * expect(kw("then"), "ThenEIf") * V"Block";
ElsePart = kw("else") * V"Block";
DoStat = kw("do") * V"Block" * expect(kw("end"), "EndDo") / tagDo;
WhileStat = tagC("While", kw("while") * expect(V"Expr", "ExprWhile") * V"WhileBody");
WhileBody = expect(kw("do"), "DoWhile") * V"Block" * expect(kw("end"), "EndWhile");
RepeatStat = tagC("Repeat", kw("repeat") * V"Block" * expect(kw("until"), "UntilRep") * expect(V"Expr", "ExprRep"));
ForStat = kw("for") * expect(V"ForNum" + V"ForIn", "ForRange") * expect(kw("end"), "EndFor");
ForNum = tagC("Fornum", V"Id" * sym("=") * V"NumRange" * V"ForBody");
NumRange = expect(V"Expr", "ExprFor1") * expect(sym(","), "CommaFor") *expect(V"Expr", "ExprFor2")
* (sym(",") * expect(V"Expr", "ExprFor3"))^-1;
ForIn = tagC("Forin", V"NameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForBody = expect(kw("do"), "DoFor") * V"Block";
LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())));
Assignment = tagC("Set", V"VarList" * V"BinOp"^-1 * (sym("=") / "=") * V"BinOp"^-1 * expect(V"ExprList", "EListAssign"));
FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat;
FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex)
* (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod;
FuncBody = tagC("Function", V"FuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList");
ParList = V"NamedParList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots
+ Ct(tagC("Dots", sym("...")))
+ Ct(Cc()); -- Cc({}) generates a bug since the {} would be shared across parses
NamedParList = tagC("NamedParList", commaSep(V"NamedPar"));
NamedPar = tagC("ParPair", V"ParKey" * expect(sym("="), "EqField") * expect(V"Expr", "ExprField"))
+ V"Id";
ParKey = V"Id" * #("=" * -P"=");
LabelStat = tagC("Label", sym("::") * expect(V"Name", "Label") * expect(sym("::"), "CloseLabel"));
GoToStat = tagC("Goto", kw("goto") * expect(V"Name", "Goto"));
BreakStat = tagC("Break", kw("break"));
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1 * sym(";")^-1);
NameList = tagC("NameList", commaSep(V"Id"));
VarList = tagC("VarList", commaSep(V"VarExpr", "VarList"));
ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList"));
Expr = V"OrExpr";
OrExpr = chainOp(V"AndExpr", V"OrOp", "OrExpr");
AndExpr = chainOp(V"RelExpr", V"AndOp", "AndExpr");
RelExpr = chainOp(V"BOrExpr", V"RelOp", "RelExpr");
BOrExpr = chainOp(V"BXorExpr", V"BOrOp", "BOrExpr");
BXorExpr = chainOp(V"BAndExpr", V"BXorOp", "BXorExpr");
BAndExpr = chainOp(V"ShiftExpr", V"BAndOp", "BAndExpr");
ShiftExpr = chainOp(V"ConcatExpr", V"ShiftOp", "ShiftExpr");
ConcatExpr = V"AddExpr" * (V"ConcatOp" * expect(V"ConcatExpr", "ConcatExpr"))^-1 / binaryOp;
AddExpr = chainOp(V"MulExpr", V"AddOp", "AddExpr");
MulExpr = chainOp(V"UnaryExpr", V"MulOp", "MulExpr");
UnaryExpr = V"UnaryOp" * expect(V"UnaryExpr", "UnaryExpr") / unaryOp
+ V"PowExpr";
PowExpr = V"SimpleExpr" * (V"PowOp" * expect(V"UnaryExpr", "PowExpr"))^-1 / binaryOp;
SimpleExpr = tagC("Number", V"Number")
+ tagC("String", V"String")
+ tagC("Nil", kw("nil"))
+ tagC("Boolean", kw("false") * Cc(false))
+ tagC("Boolean", kw("true") * Cc(true))
+ tagC("Dots", sym("..."))
+ V"FuncDef"
+ V"Table"
+ V"SuffixedExpr";
FuncCall = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Call" or exp.tag == "Invoke", exp end);
VarExpr = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Id" or exp.tag == "Index", exp end);
SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"Call")^0, makeIndexOrCall);
PrimaryExpr = V"SelfId" * (V"SelfCall" + V"SelfIndex")
+ V"Id"
+ tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr"));
Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"));
Call = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs")))
+ tagC("Call", V"FuncArgs");
SelfIndex = tagC("DotIndex", V"StrId");
SelfCall = tagC("Invoke", Cg(V"StrId" * V"FuncArgs"));
ShortFuncDef = tagC("Function", V"ShortFuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
ShortFuncParams = (sym(":") / ":")^-1 * sym("(") * V"ParList" * sym(")") / fixAnonymousMethodParams;
FuncDef = (kw("function") * V"FuncBody") + V"ShortFuncDef";
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")
+ V"Table"
+ tagC("String", V"String");
Table = tagC("Table", sym("{") * V"FieldList"^-1 * expect(sym("}"), "CBraceTable"));
FieldList = sepBy(V"Field", V"FieldSep") * V"FieldSep"^-1;
Field = tagC("Pair", V"FieldKey" * expect(sym("="), "EqField") * expect(V"Expr", "ExprField"))
+ V"Expr";
FieldKey = sym("[" * -P(S"=[")) * expect(V"Expr", "ExprFKey") * expect(sym("]"), "CBracketFKey")
+ V"StrId" * #("=" * -P"=");
FieldSep = sym(",") + sym(";");
SelfId = tagC("Id", sym"@" / "self");
Id = tagC("Id", V"Name") + V"SelfId";
StrId = tagC("String", V"Name");
-- lexer
Skip = (V"Space" + V"Comment")^0;
Space = space^1;
Comment = P"--" * V"LongStr" / function () return end
+ P"--" * (P(1) - P"\n")^0;
Name = token(-V"Reserved" * C(V"Ident"));
Reserved = V"Keywords" * -V"IdRest";
Keywords = P"and" + "break" + "do" + "elseif" + "else" + "end"
+ "false" + "for" + "function" + "goto" + "if" + "in"
+ "local" + "nil" + "not" + "or" + "repeat" + "return"
+ "then" + "true" + "until" + "while";
Ident = V"IdStart" * V"IdRest"^0;
IdStart = alpha + P"_";
IdRest = alnum + P"_";
Number = token((V"Hex" + V"Float" + V"Int") / tonumber);
Hex = (P"0x" + "0X") * expect(xdigit^1, "DigitHex");
Float = V"Decimal" * V"Expo"^-1
+ V"Int" * V"Expo";
Decimal = digit^1 * "." * digit^0
+ P"." * -P"." * expect(digit^1, "DigitDeci");
Expo = S"eE" * S"+-"^-1 * expect(digit^1, "DigitExpo");
Int = digit^1;
String = token(V"ShortStr" + V"LongStr");
ShortStr = P'"' * Cs((V"EscSeq" + (P(1)-S'"\n'))^0) * expect(P'"', "Quote")
+ P"'" * Cs((V"EscSeq" + (P(1)-S"'\n"))^0) * expect(P"'", "Quote");
EscSeq = P"\\" / "" -- remove backslash
* ( P"a" / "\a"
+ P"b" / "\b"
+ P"f" / "\f"
+ P"n" / "\n"
+ P"r" / "\r"
+ P"t" / "\t"
+ P"v" / "\v"
+ P"\n" / "\n"
+ P"\r" / "\n"
+ P"\\" / "\\"
+ P"\"" / "\""
+ P"\'" / "\'"
+ P"z" * space^0 / ""
+ digit * digit^-2 / tonumber / string.char
+ P"x" * expect(C(xdigit * xdigit), "HexEsc") * Cc(16) / tonumber / string.char
+ P"u" * expect("{", "OBraceUEsc")
* expect(C(xdigit^1), "DigitUEsc") * Cc(16)
* expect("}", "CBraceUEsc")
/ tonumber
/ (utf8 and utf8.char or string.char) -- true max is \u{10FFFF}
-- utf8.char needs Lua 5.3
-- string.char works only until \u{FF}
+ throw("EscSeq")
);
LongStr = V"Open" * C((P(1) - V"CloseEq")^0) * expect(V"Close", "CloseLStr") / function (s, eqs) return s end;
Open = "[" * Cg(V"Equals", "openEq") * "[" * P"\n"^-1;
Close = "]" * C(V"Equals") * "]";
Equals = P"="^0;
CloseEq = Cmt(V"Close" * Cb("openEq"), function (s, i, closeEq, openEq) return #openEq == #closeEq end);
OrOp = kw("or") / "or";
AndOp = kw("and") / "and";
RelOp = sym("~=") / "ne"
+ sym("==") / "eq"
+ sym("<=") / "le"
+ sym(">=") / "ge"
+ sym("<") / "lt"
+ sym(">") / "gt";
BOrOp = sym("|") / "bor";
BXorOp = sym("~" * -P"=") / "bxor";
BAndOp = sym("&") / "band";
ShiftOp = sym("<<") / "shl"
+ sym(">>") / "shr";
ConcatOp = sym("..") / "concat";
AddOp = sym("+") / "add"
+ sym("-") / "sub";
MulOp = sym("*") / "mul"
+ sym("//") / "idiv"
+ sym("/") / "div"
+ sym("%") / "mod";
UnaryOp = kw("not") / "not"
+ sym("-") / "unm"
+ sym("#") / "len"
+ sym("~") / "bnot";
PowOp = sym("^") / "pow";
BinOp = V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp";
}
local parser = {}
local validator = require("lib.lua-parser.validator")
local validate = validator.validate
local syntaxerror = validator.syntaxerror
function parser.parse (subject, filename)
local errorinfo = { subject = subject, filename = filename }
lpeg.setmaxstack(1000)
local ast, label, sfail = lpeg.match(G, subject, nil, errorinfo)
if not ast then
local errpos = #subject-#sfail+1
local errmsg = labels[label][2]
return ast, syntaxerror(errorinfo, errpos, errmsg)
end
return validate(ast, errorinfo)
end
return parser

327
lib/lua-parser/pp.lua Normal file
View file

@ -0,0 +1,327 @@
--[[
This module impements a pretty printer to the AST
]]
local pp = {}
local block2str, stm2str, exp2str, var2str
local explist2str, varlist2str, parlist2str, fieldlist2str
local function iscntrl (x)
if (x >= 0 and x <= 31) or (x == 127) then return true end
return false
end
local function isprint (x)
return not iscntrl(x)
end
local function fixed_string (str)
local new_str = ""
for i=1,string.len(str) do
char = string.byte(str, i)
if char == 34 then new_str = new_str .. string.format("\\\"")
elseif char == 92 then new_str = new_str .. string.format("\\\\")
elseif char == 7 then new_str = new_str .. string.format("\\a")
elseif char == 8 then new_str = new_str .. string.format("\\b")
elseif char == 12 then new_str = new_str .. string.format("\\f")
elseif char == 10 then new_str = new_str .. string.format("\\n")
elseif char == 13 then new_str = new_str .. string.format("\\r")
elseif char == 9 then new_str = new_str .. string.format("\\t")
elseif char == 11 then new_str = new_str .. string.format("\\v")
else
if isprint(char) then
new_str = new_str .. string.format("%c", char)
else
new_str = new_str .. string.format("\\%03d", char)
end
end
end
return new_str
end
local function name2str (name)
return string.format('"%s"', name)
end
local function boolean2str (b)
return string.format('"%s"', tostring(b))
end
local function number2str (n)
return string.format('"%s"', tostring(n))
end
local function string2str (s)
return string.format('"%s"', fixed_string(s))
end
function var2str (var)
local tag = var.tag
local str = "`" .. tag
if tag == "Id" then -- `Id{ <string> }
str = str .. " " .. name2str(var[1])
elseif tag == "Index" then -- `Index{ expr expr }
str = str .. "{ "
str = str .. exp2str(var[1]) .. ", "
str = str .. exp2str(var[2])
str = str .. " }"
else
error("expecting a variable, but got a " .. tag)
end
return str
end
function varlist2str (varlist)
local l = {}
for k, v in ipairs(varlist) do
l[k] = var2str(v)
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function parlist2str (parlist)
local l = {}
local len = #parlist
local is_vararg = false
if len > 0 and parlist[len].tag == "Dots" then
is_vararg = true
len = len - 1
end
local i = 1
while i <= len do
l[i] = var2str(parlist[i])
i = i + 1
end
if is_vararg then
l[i] = "`" .. parlist[i].tag
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function fieldlist2str (fieldlist)
local l = {}
for k, v in ipairs(fieldlist) do
local tag = v.tag
if tag == "Pair" then -- `Pair{ expr expr }
l[k] = "`" .. tag .. "{ "
l[k] = l[k] .. exp2str(v[1]) .. ", " .. exp2str(v[2])
l[k] = l[k] .. " }"
else -- expr
l[k] = exp2str(v)
end
end
if #l > 0 then
return "{ " .. table.concat(l, ", ") .. " }"
else
return ""
end
end
function exp2str (exp)
local tag = exp.tag
local str = "`" .. tag
if tag == "Nil" or
tag == "Dots" then
elseif tag == "Boolean" then -- `Boolean{ <boolean> }
str = str .. " " .. boolean2str(exp[1])
elseif tag == "Number" then -- `Number{ <number> }
str = str .. " " .. number2str(exp[1])
elseif tag == "String" then -- `String{ <string> }
str = str .. " " .. string2str(exp[1])
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block }
str = str .. "{ "
str = str .. parlist2str(exp[1]) .. ", "
str = str .. block2str(exp[2])
str = str .. " }"
elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
str = str .. fieldlist2str(exp)
elseif tag == "Op" then -- `Op{ opid expr expr? }
str = str .. "{ "
str = str .. name2str(exp[1]) .. ", "
str = str .. exp2str(exp[2])
if exp[3] then
str = str .. ", " .. exp2str(exp[3])
end
str = str .. " }"
elseif tag == "Paren" then -- `Paren{ expr }
str = str .. "{ " .. exp2str(exp[1]) .. " }"
elseif tag == "Call" then -- `Call{ expr expr* }
str = str .. "{ "
str = str .. exp2str(exp[1])
if exp[2] then
for i=2, #exp do
str = str .. ", " .. exp2str(exp[i])
end
end
str = str .. " }"
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
str = str .. "{ "
str = str .. exp2str(exp[1]) .. ", "
str = str .. exp2str(exp[2])
if exp[3] then
for i=3, #exp do
str = str .. ", " .. exp2str(exp[i])
end
end
str = str .. " }"
elseif tag == "Id" or -- `Id{ <string> }
tag == "Index" then -- `Index{ expr expr }
str = var2str(exp)
else
error("expecting an expression, but got a " .. tag)
end
return str
end
function explist2str (explist)
local l = {}
for k, v in ipairs(explist) do
l[k] = exp2str(v)
end
if #l > 0 then
return "{ " .. table.concat(l, ", ") .. " }"
else
return ""
end
end
function stm2str (stm)
local tag = stm.tag
local str = "`" .. tag
if tag == "Do" then -- `Do{ stat* }
local l = {}
for k, v in ipairs(stm) do
l[k] = stm2str(v)
end
str = str .. "{ " .. table.concat(l, ", ") .. " }"
elseif tag == "Set" then -- `Set{ {lhs+} {expr+} }
str = str .. "{ "
str = str .. varlist2str(stm[1]) .. ", "
str = str .. explist2str(stm[2])
str = str .. " }"
elseif tag == "While" then -- `While{ expr block }
str = str .. "{ "
str = str .. exp2str(stm[1]) .. ", "
str = str .. block2str(stm[2])
str = str .. " }"
elseif tag == "Repeat" then -- `Repeat{ block expr }
str = str .. "{ "
str = str .. block2str(stm[1]) .. ", "
str = str .. exp2str(stm[2])
str = str .. " }"
elseif tag == "If" then -- `If{ (expr block)+ block? }
str = str .. "{ "
local len = #stm
if len % 2 == 0 then
local l = {}
for i=1,len-2,2 do
str = str .. exp2str(stm[i]) .. ", " .. block2str(stm[i+1]) .. ", "
end
str = str .. exp2str(stm[len-1]) .. ", " .. block2str(stm[len])
else
local l = {}
for i=1,len-3,2 do
str = str .. exp2str(stm[i]) .. ", " .. block2str(stm[i+1]) .. ", "
end
str = str .. exp2str(stm[len-2]) .. ", " .. block2str(stm[len-1]) .. ", "
str = str .. block2str(stm[len])
end
str = str .. " }"
elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block }
str = str .. "{ "
str = str .. var2str(stm[1]) .. ", "
str = str .. exp2str(stm[2]) .. ", "
str = str .. exp2str(stm[3]) .. ", "
if stm[5] then
str = str .. exp2str(stm[4]) .. ", "
str = str .. block2str(stm[5])
else
str = str .. block2str(stm[4])
end
str = str .. " }"
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
str = str .. "{ "
str = str .. varlist2str(stm[1]) .. ", "
str = str .. explist2str(stm[2]) .. ", "
str = str .. block2str(stm[3])
str = str .. " }"
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
str = str .. "{ "
str = str .. varlist2str(stm[1])
if #stm[2] > 0 then
str = str .. ", " .. explist2str(stm[2])
else
str = str .. ", " .. "{ }"
end
str = str .. " }"
elseif tag == "Localrec" then -- `Localrec{ ident expr }
str = str .. "{ "
str = str .. "{ " .. var2str(stm[1][1]) .. " }, "
str = str .. "{ " .. exp2str(stm[2][1]) .. " }"
str = str .. " }"
elseif tag == "Goto" or -- `Goto{ <string> }
tag == "Label" then -- `Label{ <string> }
str = str .. "{ " .. name2str(stm[1]) .. " }"
elseif tag == "Return" then -- `Return{ <expr>* }
str = str .. explist2str(stm)
elseif tag == "Break" then
elseif tag == "Call" then -- `Call{ expr expr* }
str = str .. "{ "
str = str .. exp2str(stm[1])
if stm[2] then
for i=2, #stm do
str = str .. ", " .. exp2str(stm[i])
end
end
str = str .. " }"
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
str = str .. "{ "
str = str .. exp2str(stm[1]) .. ", "
str = str .. exp2str(stm[2])
if stm[3] then
for i=3, #stm do
str = str .. ", " .. exp2str(stm[i])
end
end
str = str .. " }"
else
error("expecting a statement, but got a " .. tag)
end
return str
end
function block2str (block)
local l = {}
for k, v in ipairs(block) do
l[k] = stm2str(v)
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function pp.tostring (t)
assert(type(t) == "table")
return block2str(t)
end
function pp.print (t)
assert(type(t) == "table")
print(pp.tostring(t))
end
function pp.dump (t, i)
if i == nil then i = 0 end
io.write(string.format("{\n"))
io.write(string.format("%s[tag] = %s\n", string.rep(" ", i+2), t.tag or "nil"))
io.write(string.format("%s[pos] = %s\n", string.rep(" ", i+2), t.pos or "nil"))
for k,v in ipairs(t) do
io.write(string.format("%s[%s] = ", string.rep(" ", i+2), tostring(k)))
if type(v) == "table" then
pp.dump(v,i+2)
else
io.write(string.format("%s\n", tostring(v)))
end
end
io.write(string.format("%s}\n", string.rep(" ", i)))
end
return pp

74
lib/lua-parser/scope.lua Normal file
View file

@ -0,0 +1,74 @@
--[[
This module implements functions that handle scoping rules
]]
local scope = {}
function scope.lineno (s, i)
if i == 1 then return 1, 1 end
local l, lastline = 0, ""
s = s:sub(1, i) .. "\n"
for line in s:gmatch("[^\n]*[\n]") do
l = l + 1
lastline = line
end
local c = lastline:len() - 1
return l, c ~= 0 and c or 1
end
function scope.new_scope (env)
if not env.scope then
env.scope = 0
else
env.scope = env.scope + 1
end
local scope = env.scope
env.maxscope = scope
env[scope] = {}
env[scope]["label"] = {}
env[scope]["local"] = {}
env[scope]["goto"] = {}
end
function scope.begin_scope (env)
env.scope = env.scope + 1
end
function scope.end_scope (env)
env.scope = env.scope - 1
end
function scope.new_function (env)
if not env.fscope then
env.fscope = 0
else
env.fscope = env.fscope + 1
end
local fscope = env.fscope
env["function"][fscope] = {}
end
function scope.begin_function (env)
env.fscope = env.fscope + 1
end
function scope.end_function (env)
env.fscope = env.fscope - 1
end
function scope.begin_loop (env)
if not env.loop then
env.loop = 1
else
env.loop = env.loop + 1
end
end
function scope.end_loop (env)
env.loop = env.loop - 1
end
function scope.insideloop (env)
return env.loop and env.loop > 0
end
return scope

View file

@ -0,0 +1,394 @@
--[[
This module impements a validator for the AST
]]
local scope = require "lib.lua-parser.scope"
local lineno = scope.lineno
local new_scope, end_scope = scope.new_scope, scope.end_scope
local new_function, end_function = scope.new_function, scope.end_function
local begin_loop, end_loop = scope.begin_loop, scope.end_loop
local insideloop = scope.insideloop
-- creates an error message for the input string
local function syntaxerror (errorinfo, pos, msg)
local l, c = lineno(errorinfo.subject, pos)
local error_msg = "%s:%d:%d: syntax error, %s"
return string.format(error_msg, errorinfo.filename, l, c, msg)
end
local function exist_label (env, scope, stm)
local l = stm[1]
for s=scope, 0, -1 do
if env[s]["label"][l] then return true end
end
return false
end
local function set_label (env, label, pos)
local scope = env.scope
local l = env[scope]["label"][label]
if not l then
env[scope]["label"][label] = { name = label, pos = pos }
return true
else
local msg = "label '%s' already defined at line %d"
local line = lineno(env.errorinfo.subject, l.pos)
msg = string.format(msg, label, line)
return nil, syntaxerror(env.errorinfo, pos, msg)
end
end
local function set_pending_goto (env, stm)
local scope = env.scope
table.insert(env[scope]["goto"], stm)
return true
end
local function verify_pending_gotos (env)
for s=env.maxscope, 0, -1 do
for k, v in ipairs(env[s]["goto"]) do
if not exist_label(env, s, v) then
local msg = "no visible label '%s' for <goto>"
msg = string.format(msg, v[1])
return nil, syntaxerror(env.errorinfo, v.pos, msg)
end
end
end
return true
end
local function set_vararg (env, is_vararg)
env["function"][env.fscope].is_vararg = is_vararg
end
local traverse_stm, traverse_exp, traverse_var
local traverse_block, traverse_explist, traverse_varlist, traverse_parlist
function traverse_parlist (env, parlist)
local len = #parlist
local is_vararg = false
if len > 0 and parlist[len].tag == "Dots" then
is_vararg = true
end
set_vararg(env, is_vararg)
return true
end
local function traverse_function (env, exp)
new_function(env)
new_scope(env)
local status, msg = traverse_parlist(env, exp[1])
if not status then return status, msg end
status, msg = traverse_block(env, exp[2])
if not status then return status, msg end
end_scope(env)
end_function(env)
return true
end
local function traverse_op (env, exp)
local status, msg = traverse_exp(env, exp[2])
if not status then return status, msg end
if exp[3] then
status, msg = traverse_exp(env, exp[3])
if not status then return status, msg end
end
return true
end
local function traverse_paren (env, exp)
local status, msg = traverse_exp(env, exp[1])
if not status then return status, msg end
return true
end
local function traverse_table (env, fieldlist)
for k, v in ipairs(fieldlist) do
local tag = v.tag
if tag == "Pair" then
local status, msg = traverse_exp(env, v[1])
if not status then return status, msg end
status, msg = traverse_exp(env, v[2])
if not status then return status, msg end
else
local status, msg = traverse_exp(env, v)
if not status then return status, msg end
end
end
return true
end
local function traverse_vararg (env, exp)
if not env["function"][env.fscope].is_vararg then
local msg = "cannot use '...' outside a vararg function"
return nil, syntaxerror(env.errorinfo, exp.pos, msg)
end
return true
end
local function traverse_call (env, call)
local status, msg = traverse_exp(env, call[1])
if not status then return status, msg end
for i=2, #call do
status, msg = traverse_exp(env, call[i])
if not status then return status, msg end
end
return true
end
local function traverse_invoke (env, invoke)
local status, msg = traverse_exp(env, invoke[1])
if not status then return status, msg end
for i=3, #invoke do
status, msg = traverse_exp(env, invoke[i])
if not status then return status, msg end
end
return true
end
local function traverse_assignment (env, stm)
local status, msg = traverse_varlist(env, stm[1])
if not status then return status, msg end
status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_break (env, stm)
if not insideloop(env) then
local msg = "<break> not inside a loop"
return nil, syntaxerror(env.errorinfo, stm.pos, msg)
end
return true
end
local function traverse_forin (env, stm)
begin_loop(env)
new_scope(env)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
status, msg = traverse_block(env, stm[3])
if not status then return status, msg end
end_scope(env)
end_loop(env)
return true
end
local function traverse_fornum (env, stm)
local status, msg
begin_loop(env)
new_scope(env)
status, msg = traverse_exp(env, stm[2])
if not status then return status, msg end
status, msg = traverse_exp(env, stm[3])
if not status then return status, msg end
if stm[5] then
status, msg = traverse_exp(env, stm[4])
if not status then return status, msg end
status, msg = traverse_block(env, stm[5])
if not status then return status, msg end
else
status, msg = traverse_block(env, stm[4])
if not status then return status, msg end
end
end_scope(env)
end_loop(env)
return true
end
local function traverse_goto (env, stm)
local status, msg = set_pending_goto(env, stm)
if not status then return status, msg end
return true
end
local function traverse_if (env, stm)
local len = #stm
if len % 2 == 0 then
for i=1, len, 2 do
local status, msg = traverse_exp(env, stm[i])
if not status then return status, msg end
status, msg = traverse_block(env, stm[i+1])
if not status then return status, msg end
end
else
for i=1, len-1, 2 do
local status, msg = traverse_exp(env, stm[i])
if not status then return status, msg end
status, msg = traverse_block(env, stm[i+1])
if not status then return status, msg end
end
local status, msg = traverse_block(env, stm[len])
if not status then return status, msg end
end
return true
end
local function traverse_label (env, stm)
local status, msg = set_label(env, stm[1], stm.pos)
if not status then return status, msg end
return true
end
local function traverse_let (env, stm)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_letrec (env, stm)
local status, msg = traverse_exp(env, stm[2][1])
if not status then return status, msg end
return true
end
local function traverse_repeat (env, stm)
begin_loop(env)
local status, msg = traverse_block(env, stm[1])
if not status then return status, msg end
status, msg = traverse_exp(env, stm[2])
if not status then return status, msg end
end_loop(env)
return true
end
local function traverse_return (env, stm)
local status, msg = traverse_explist(env, stm)
if not status then return status, msg end
return true
end
local function traverse_while (env, stm)
begin_loop(env)
local status, msg = traverse_exp(env, stm[1])
if not status then return status, msg end
status, msg = traverse_block(env, stm[2])
if not status then return status, msg end
end_loop(env)
return true
end
function traverse_var (env, var)
local tag = var.tag
if tag == "Id" then -- `Id{ <string> }
return true
elseif tag == "Index" then -- `Index{ expr expr }
local status, msg = traverse_exp(env, var[1])
if not status then return status, msg end
status, msg = traverse_exp(env, var[2])
if not status then return status, msg end
return true
else
error("expecting a variable, but got a " .. tag)
end
end
function traverse_varlist (env, varlist)
for k, v in ipairs(varlist) do
local status, msg = traverse_var(env, v)
if not status then return status, msg end
end
return true
end
function traverse_exp (env, exp)
local tag = exp.tag
if tag == "Nil" or
tag == "Boolean" or -- `Boolean{ <boolean> }
tag == "Number" or -- `Number{ <number> }
tag == "String" then -- `String{ <string> }
return true
elseif tag == "Dots" then
return traverse_vararg(env, exp)
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block }
return traverse_function(env, exp)
elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
return traverse_table(env, exp)
elseif tag == "Op" then -- `Op{ opid expr expr? }
return traverse_op(env, exp)
elseif tag == "Paren" then -- `Paren{ expr }
return traverse_paren(env, exp)
elseif tag == "Call" then -- `Call{ expr expr* }
return traverse_call(env, exp)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, exp)
elseif tag == "Id" or -- `Id{ <string> }
tag == "Index" then -- `Index{ expr expr }
return traverse_var(env, exp)
else
error("expecting an expression, but got a " .. tag)
end
end
function traverse_explist (env, explist)
for k, v in ipairs(explist) do
local status, msg = traverse_exp(env, v)
if not status then return status, msg end
end
return true
end
function traverse_stm (env, stm)
local tag = stm.tag
if tag == "Do" then -- `Do{ stat* }
return traverse_block(env, stm)
elseif tag == "Set" then -- `Set{ {lhs+} {expr+} }
return traverse_assignment(env, stm)
elseif tag == "While" then -- `While{ expr block }
return traverse_while(env, stm)
elseif tag == "Repeat" then -- `Repeat{ block expr }
return traverse_repeat(env, stm)
elseif tag == "If" then -- `If{ (expr block)+ block? }
return traverse_if(env, stm)
elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block }
return traverse_fornum(env, stm)
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
return traverse_forin(env, stm)
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
return traverse_let(env, stm)
elseif tag == "Localrec" then -- `Localrec{ ident expr }
return traverse_letrec(env, stm)
elseif tag == "Goto" then -- `Goto{ <string> }
return traverse_goto(env, stm)
elseif tag == "Label" then -- `Label{ <string> }
return traverse_label(env, stm)
elseif tag == "Return" then -- `Return{ <expr>* }
return traverse_return(env, stm)
elseif tag == "Break" then
return traverse_break(env, stm)
elseif tag == "Call" then -- `Call{ expr expr* }
return traverse_call(env, stm)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, stm)
else
error("expecting a statement, but got a " .. tag)
end
end
function traverse_block (env, block)
local l = {}
new_scope(env)
for k, v in ipairs(block) do
local status, msg = traverse_stm(env, v)
if not status then return status, msg end
end
end_scope(env)
return true
end
local function traverse (ast, errorinfo)
assert(type(ast) == "table")
assert(type(errorinfo) == "table")
local env = { errorinfo = errorinfo, ["function"] = {} }
new_function(env)
set_vararg(env, true)
local status, msg = traverse_block(env, ast)
if not status then return status, msg end
end_function(env)
status, msg = verify_pending_gotos(env)
if not status then return status, msg end
return ast
end
return { validate = traverse, syntaxerror = syntaxerror }

40
lib/util.can Normal file
View file

@ -0,0 +1,40 @@
local util = {}
function util.search(modpath, exts={"can", "lua"})
for _, ext in ipairs(exts) do
for path in package.path:gmatch("[^;]+") do
local fpath = path:gsub("%.lua", "."..ext):gsub("%?", (modpath:gsub("%.", "/")))
local f = io.open(fpath)
if f then
f:close()
return fpath
end
end
end
end
function util.load(str, name, env)
if _VERSION == "Lua 5.1" then
local fn, err = loadstring(str, name)
if not fn then return fn, err end
return env ~= nil and setfenv(fn, env) or fn
else
if env then
return load(str, name, nil, env)
else
return load(str, name)
end
end
end
function util.merge(...)
local r = {}
for _, t in ipairs({...}) do
for k, v in pairs(t) do
r[k] = v
end
end
return r
end
return util