2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
module("moonscript.parse", package.seeall)
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-29 04:27:31 +00:00
|
|
|
local util = require"moonscript.util"
|
|
|
|
|
2011-05-16 05:04:38 +00:00
|
|
|
require"lpeg"
|
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
local compile = require"moonscript.compile"
|
|
|
|
local dump = require"moonscript.dump"
|
|
|
|
local data = require"moonscript.data"
|
2011-05-18 07:45:27 +00:00
|
|
|
|
2011-05-21 22:26:46 +00:00
|
|
|
local ntype = compile.ntype
|
2011-05-22 07:58:17 +00:00
|
|
|
local trim = util.trim
|
2011-05-21 22:26:46 +00:00
|
|
|
|
2011-05-18 07:45:27 +00:00
|
|
|
local Stack = data.Stack
|
2011-05-16 08:53:44 +00:00
|
|
|
|
2011-05-17 07:59:58 +00:00
|
|
|
local function count_indent(str)
|
|
|
|
local sum = 0
|
|
|
|
for v in str:gmatch("[\t ]") do
|
|
|
|
if v == ' ' then sum = sum + 1 end
|
|
|
|
if v == '\t' then sum = sum + 4 end
|
|
|
|
end
|
|
|
|
return sum
|
|
|
|
end
|
|
|
|
|
2011-05-16 05:04:38 +00:00
|
|
|
local R, S, V, P = lpeg.R, lpeg.S, lpeg.V, lpeg.P
|
2011-05-23 05:12:21 +00:00
|
|
|
local C, Ct, Cmt, Cg, Cb, Cc = lpeg.C, lpeg.Ct, lpeg.Cmt, lpeg.Cg, lpeg.Cb, lpeg.Cc
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-20 07:10:37 +00:00
|
|
|
local White = S" \t\n"^0
|
2011-05-22 19:19:13 +00:00
|
|
|
local _Space = S" \t"^0
|
2011-05-18 07:45:27 +00:00
|
|
|
local Break = S"\n"
|
|
|
|
local Stop = Break + -1
|
2011-05-17 07:59:58 +00:00
|
|
|
local Indent = C(S"\t "^0) / count_indent
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-22 19:19:13 +00:00
|
|
|
local Comment = P"--" * (1 - S"\n")^0 * #Stop
|
|
|
|
local Space = _Space * Comment^-1
|
2011-05-22 07:58:17 +00:00
|
|
|
|
2011-05-24 06:58:10 +00:00
|
|
|
local _Name = C(R("az", "AZ", "__") * R("az", "AZ", "09", "__")^0)
|
|
|
|
local Name = Space * _Name
|
2011-05-21 22:26:46 +00:00
|
|
|
local Num = Space * C(R("09")^1) / tonumber
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-22 07:58:17 +00:00
|
|
|
local FactorOp = Space * C(S"+-")
|
|
|
|
local TermOp = Space * C(S"*/%")
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
local function wrap(fn)
|
2011-05-16 08:53:44 +00:00
|
|
|
local env = getfenv(fi)
|
|
|
|
|
|
|
|
return setfenv(fn, setmetatable({}, {
|
|
|
|
__index = function(self, name)
|
|
|
|
local value = env[name]
|
|
|
|
if value ~= nil then return value end
|
|
|
|
|
|
|
|
if name:match"^[A-Z][A-Za-z0-9]*$" then
|
|
|
|
local v = V(name)
|
|
|
|
rawset(self, name, v)
|
|
|
|
return v
|
|
|
|
end
|
|
|
|
error("unknown variable referenced: "..name)
|
|
|
|
end
|
|
|
|
}))
|
|
|
|
end
|
|
|
|
|
2011-05-21 16:36:26 +00:00
|
|
|
function extract_line(str, start_pos)
|
|
|
|
str = str:sub(start_pos)
|
|
|
|
m = str:match"^(.-)\n"
|
|
|
|
if m then return m end
|
|
|
|
return str:match"^.-$"
|
|
|
|
end
|
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
local function mark(name)
|
2011-05-16 08:53:44 +00:00
|
|
|
return function(...)
|
2011-05-21 08:11:25 +00:00
|
|
|
return {name, ...}
|
2011-05-16 05:04:38 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
local function got(what)
|
2011-05-21 16:36:26 +00:00
|
|
|
return Cmt("", function(str, pos, ...)
|
|
|
|
local cap = {...}
|
|
|
|
print("++ got "..what, "["..extract_line(str, pos).."]")
|
2011-05-18 07:45:27 +00:00
|
|
|
return true
|
2011-05-20 07:10:37 +00:00
|
|
|
end)
|
2011-05-18 07:45:27 +00:00
|
|
|
end
|
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
local function flatten(tbl)
|
2011-05-16 08:53:44 +00:00
|
|
|
if #tbl == 1 then
|
|
|
|
return tbl[1]
|
|
|
|
end
|
|
|
|
return tbl
|
|
|
|
end
|
2011-05-16 05:04:38 +00:00
|
|
|
|
2011-05-21 08:11:25 +00:00
|
|
|
local function flatten_or_mark(name)
|
|
|
|
return function(tbl)
|
|
|
|
if #tbl == 1 then return tbl[1] end
|
|
|
|
table.insert(tbl, 1, name)
|
|
|
|
return tbl
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-16 08:53:44 +00:00
|
|
|
local build_grammar = wrap(function()
|
2011-05-21 16:36:26 +00:00
|
|
|
local err_msg = "Failed to parse, line:\n [%d] >> %s (%d)"
|
2011-05-18 07:45:27 +00:00
|
|
|
|
|
|
|
local _indent = Stack(0) -- current indent
|
|
|
|
|
2011-05-21 22:26:46 +00:00
|
|
|
local last_pos = 0 -- used to know where to report error
|
2011-05-18 07:45:27 +00:00
|
|
|
local function check_indent(str, pos, indent)
|
2011-05-21 16:36:26 +00:00
|
|
|
last_pos = pos
|
2011-05-18 07:45:27 +00:00
|
|
|
return _indent:top() == indent
|
|
|
|
end
|
|
|
|
|
|
|
|
local function advance_indent(str, pos, indent)
|
|
|
|
if indent > _indent:top() then
|
|
|
|
_indent:push(indent)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function pop_indent(str, pos)
|
|
|
|
if not _indent:pop() then error("unexpected outdent") end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local keywords = {}
|
|
|
|
local function key(word)
|
|
|
|
keywords[word] = true
|
2011-05-21 22:26:46 +00:00
|
|
|
return Space * word
|
2011-05-18 07:45:27 +00:00
|
|
|
end
|
2011-05-17 07:59:58 +00:00
|
|
|
|
2011-05-28 04:27:59 +00:00
|
|
|
local function op(word)
|
|
|
|
if word:match("^%w*$") then
|
|
|
|
keywords[word] = true
|
|
|
|
end
|
|
|
|
return Space * C(word)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2011-05-19 07:32:31 +00:00
|
|
|
local function sym(chars)
|
2011-05-21 22:26:46 +00:00
|
|
|
return Space * chars
|
|
|
|
end
|
|
|
|
|
|
|
|
local function symx(chars)
|
|
|
|
return chars
|
|
|
|
end
|
|
|
|
|
|
|
|
local function flatten_func(callee, args)
|
|
|
|
if #args == 0 then return callee end
|
|
|
|
|
|
|
|
args = {"call", args}
|
|
|
|
if ntype(callee) == "chain" then
|
|
|
|
table.insert(callee, args)
|
|
|
|
return callee
|
|
|
|
end
|
|
|
|
|
|
|
|
return {"chain", callee, args}
|
2011-05-19 07:32:31 +00:00
|
|
|
end
|
|
|
|
|
2011-05-23 02:28:25 +00:00
|
|
|
local function wrap_func_arg(value)
|
|
|
|
return {"call", {value}}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2011-05-22 05:00:31 +00:00
|
|
|
-- makes sure the last item in a chain is an index
|
2011-05-24 07:15:11 +00:00
|
|
|
local _assignable = { index = true, dot = true}
|
2011-05-22 05:00:31 +00:00
|
|
|
local function check_assignable(str, pos, value)
|
2011-05-24 07:15:11 +00:00
|
|
|
if ntype(value) == "chain" and _assignable[ntype(value[#value])]
|
2011-05-22 05:00:31 +00:00
|
|
|
or type(value) == "string"
|
|
|
|
then
|
|
|
|
return true, value
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2011-05-28 07:21:25 +00:00
|
|
|
local Name = sym"@" * Name / mark"self" + Name + "..."
|
2011-05-28 04:27:59 +00:00
|
|
|
|
2011-05-20 07:10:37 +00:00
|
|
|
-- make sure name is not a keyword
|
2011-05-21 22:26:46 +00:00
|
|
|
local Name = Cmt(Name, function(str, pos, name)
|
2011-05-20 07:10:37 +00:00
|
|
|
if keywords[name] then return false end
|
|
|
|
return true, name
|
|
|
|
end)
|
|
|
|
|
2011-05-23 02:28:25 +00:00
|
|
|
local function simple_string(delim, x)
|
|
|
|
return C(symx(delim)) * C((P('\\'..delim) + (1 - S('\n'..delim)))^0) * sym(delim) / mark"string"
|
2011-05-22 07:58:17 +00:00
|
|
|
end
|
|
|
|
|
2011-05-28 07:21:25 +00:00
|
|
|
-- wrap if statement if there is a conditional decorator
|
|
|
|
local function wrap_if(stm, cond)
|
|
|
|
if cond then
|
|
|
|
local pass, fail = unpack(cond)
|
|
|
|
if fail then fail = {"else", {fail}} end
|
|
|
|
return {"if", cond[2], {stm}, fail}
|
|
|
|
end
|
|
|
|
return stm
|
|
|
|
end
|
|
|
|
|
2011-05-29 21:40:40 +00:00
|
|
|
local function wrap_decorator(stm, dec)
|
|
|
|
if not dec then return stm end
|
|
|
|
|
|
|
|
local arg = {stm, dec}
|
|
|
|
|
|
|
|
if dec[1] == "if" then
|
|
|
|
local _, cond, fail = unpack(dec)
|
|
|
|
if fail then fail = {"else", {fail}} end
|
|
|
|
stm = {"if", cond, {stm}, fail}
|
|
|
|
elseif dec[1] == "comprehension" then
|
|
|
|
local _, clauses = unpack(dec)
|
|
|
|
stm = {"comprehension", stm, clauses}
|
|
|
|
end
|
|
|
|
|
|
|
|
return stm
|
|
|
|
end
|
|
|
|
|
2011-05-22 07:58:17 +00:00
|
|
|
local function check_lua_string(str, pos, right, left)
|
|
|
|
return #left == #right
|
|
|
|
end
|
|
|
|
|
2011-05-16 08:53:44 +00:00
|
|
|
local g = lpeg.P{
|
2011-05-20 07:10:37 +00:00
|
|
|
File,
|
2011-05-22 07:58:17 +00:00
|
|
|
File = Block + Ct"",
|
2011-05-21 16:36:26 +00:00
|
|
|
Block = Ct(Line * (Break^1 * Line)^0),
|
2011-05-22 19:19:13 +00:00
|
|
|
Line = Cmt(Indent, check_indent) * Statement + _Space * Comment,
|
2011-05-28 07:21:25 +00:00
|
|
|
|
2011-05-29 21:40:40 +00:00
|
|
|
Statement = (Import + If + While + Exp * Space) * (
|
|
|
|
-- statement decorators
|
|
|
|
key"if" * Exp * (key"else" * Exp)^-1 * Space / mark"if" +
|
|
|
|
CompInner / mark"comprehension"
|
|
|
|
)^-1 / wrap_decorator,
|
2011-05-19 07:32:31 +00:00
|
|
|
|
2011-05-21 16:36:26 +00:00
|
|
|
Body = Break * InBlock + Ct(Statement),
|
2011-05-19 07:32:31 +00:00
|
|
|
|
2011-05-18 07:45:27 +00:00
|
|
|
InBlock = #Cmt(Indent, advance_indent) * Block * OutBlock,
|
2011-05-21 16:36:26 +00:00
|
|
|
OutBlock = Cmt("", pop_indent),
|
|
|
|
|
2011-05-24 05:43:40 +00:00
|
|
|
Import = key"import"* Ct(ImportNameList) * key"from" * Exp / mark"import",
|
2011-05-28 04:27:59 +00:00
|
|
|
ImportName = (Ct(sym":" / trim * Name) + Name),
|
2011-05-24 05:43:40 +00:00
|
|
|
ImportNameList = ImportName * (sym"," * ImportName)^0,
|
|
|
|
|
|
|
|
NameList = Name * (sym"," * Name)^0,
|
|
|
|
|
2011-05-23 08:16:49 +00:00
|
|
|
If = key"if" * Exp * key"then"^-1 * Body *
|
|
|
|
((Break * Cmt(Indent, check_indent))^-1 * key"elseif" * Exp * key"then"^-1 * Body / mark"elseif")^0 *
|
|
|
|
((Break * Cmt(Indent, check_indent))^-1 * key"else" * Body / mark"else")^-1 / mark"if",
|
|
|
|
|
|
|
|
While = key"while" * Exp * key"do"^-1 * Body / mark"while",
|
2011-05-20 07:10:37 +00:00
|
|
|
|
2011-05-28 06:21:29 +00:00
|
|
|
Comprehension = sym"[" * Exp *
|
|
|
|
Ct((key"for" * Ct(NameList) * key"in" * Exp / mark"for") * CompClause^0) *
|
2011-05-28 04:27:59 +00:00
|
|
|
sym"]" / mark"comprehension",
|
|
|
|
|
2011-05-29 21:40:40 +00:00
|
|
|
CompInner = Ct(CompFor * CompClause^0),
|
|
|
|
CompFor = key"for" * Ct(NameList) * key"in" * Exp / mark"for",
|
|
|
|
CompClause = CompFor + key"when" * Exp / mark"when",
|
2011-05-28 06:21:29 +00:00
|
|
|
|
2011-05-23 02:28:25 +00:00
|
|
|
Assign = Ct(AssignableList) * sym"=" * Ct(TableBlock + ExpList) / mark"assign",
|
2011-05-22 05:00:31 +00:00
|
|
|
|
2011-05-28 04:27:59 +00:00
|
|
|
-- we can ignore precedence for now
|
|
|
|
OtherOps = op"or" + op"and" + op"<=" + op">=" + op"~=" + op"!=" + op"==" + op".." + op"<" + op">",
|
|
|
|
|
2011-05-22 05:00:31 +00:00
|
|
|
Assignable = Cmt(Chain, check_assignable) + Name,
|
|
|
|
AssignableList = Assignable * (sym"," * Assignable)^0,
|
2011-05-28 04:27:59 +00:00
|
|
|
Exp = Ct(Factor * (OtherOps * Factor)^0) / flatten_or_mark"exp",
|
|
|
|
Factor = Ct(Term * (FactorOp * Term)^0) / flatten_or_mark"exp",
|
2011-05-21 08:11:25 +00:00
|
|
|
Term = Ct(Value * (TermOp * Value)^0) / flatten_or_mark"exp",
|
2011-05-21 22:26:46 +00:00
|
|
|
|
2011-05-28 07:21:25 +00:00
|
|
|
Value =
|
|
|
|
sym"-" * Exp / mark"minus" +
|
|
|
|
sym"#" * Exp / mark"length" +
|
|
|
|
sym"not" * Exp / mark"not" +
|
|
|
|
TableLit +
|
2011-05-28 04:27:59 +00:00
|
|
|
Comprehension +
|
2011-05-24 06:58:10 +00:00
|
|
|
ColonChain +
|
|
|
|
Ct(KeyValueList) / mark"table" +
|
2011-05-23 05:12:21 +00:00
|
|
|
Assign + FunLit + String +
|
|
|
|
(Chain + Callable) * Ct(ExpList^0) / flatten_func + Num,
|
2011-05-22 07:58:17 +00:00
|
|
|
|
2011-05-23 02:28:25 +00:00
|
|
|
String = Space * DoubleString + Space * SingleString + LuaString,
|
|
|
|
SingleString = simple_string("'"),
|
|
|
|
DoubleString = simple_string('"'),
|
2011-05-22 07:58:17 +00:00
|
|
|
|
|
|
|
LuaString = Cg(LuaStringOpen, "string_open") * Cb"string_open" * P"\n"^-1 *
|
|
|
|
C((1 - Cmt(C(LuaStringClose) * Cb"string_open", check_lua_string))^0) *
|
|
|
|
C(LuaStringClose) / mark"string",
|
|
|
|
|
|
|
|
LuaStringOpen = sym"[" * P"="^0 * "[" / trim,
|
|
|
|
LuaStringClose = "]" * P"="^0 * "]",
|
2011-05-21 22:26:46 +00:00
|
|
|
|
2011-05-22 19:19:13 +00:00
|
|
|
Callable = Name + Parens / mark"parens",
|
2011-05-21 22:26:46 +00:00
|
|
|
Parens = sym"(" * Exp * sym")",
|
|
|
|
|
2011-05-24 06:58:10 +00:00
|
|
|
FnArgs = symx"(" * Ct(ExpList^-1) * sym")",
|
|
|
|
|
|
|
|
-- chain that starts with colon expression (for precedence over table literal)
|
|
|
|
ColonChain =
|
|
|
|
Callable * ColonCall * (ChainItem)^0 / mark"chain",
|
|
|
|
|
2011-05-22 05:00:31 +00:00
|
|
|
-- a list of funcalls and indexs on a callable
|
2011-05-24 06:58:10 +00:00
|
|
|
Chain = Callable * ChainItem^1 / mark"chain",
|
|
|
|
|
|
|
|
ChainItem =
|
|
|
|
Invoke +
|
|
|
|
symx"[" * Exp/mark"index" * sym"]" +
|
|
|
|
symx"." * _Name/mark"dot" +
|
|
|
|
ColonCall,
|
|
|
|
|
|
|
|
ColonCall = symx":" * (_Name * Invoke) / mark"colon",
|
|
|
|
Invoke = FnArgs/mark"call" +
|
|
|
|
SingleString / wrap_func_arg +
|
|
|
|
DoubleString / wrap_func_arg,
|
2011-05-20 07:10:37 +00:00
|
|
|
|
2011-05-23 05:12:21 +00:00
|
|
|
TableValue = KeyValue + Ct(Exp),
|
2011-05-19 07:32:31 +00:00
|
|
|
|
2011-05-23 05:12:21 +00:00
|
|
|
TableLit = sym"{" * White *
|
|
|
|
Ct((TableValue * ((sym"," + Break) * White * TableValue)^0)^-1) * sym","^-1 *
|
|
|
|
White * sym"}" / mark"table",
|
|
|
|
|
|
|
|
TableBlockInner = Ct(KeyValueLine * (Break^1 * KeyValueLine)^0),
|
2011-05-23 02:28:25 +00:00
|
|
|
|
|
|
|
TableBlock = Break * #Cmt(Indent, advance_indent) * TableBlockInner * OutBlock / mark"table",
|
|
|
|
|
|
|
|
KeyValue = Ct((Name + sym"[" * Exp * sym"]") * symx":" * (Exp + TableBlock)),
|
2011-05-23 05:12:21 +00:00
|
|
|
KeyValueList = KeyValue * (sym"," * KeyValue)^0,
|
|
|
|
KeyValueLine = Cmt(Indent, check_indent) * KeyValueList * sym","^-1,
|
2011-05-23 02:28:25 +00:00
|
|
|
|
2011-05-24 07:15:11 +00:00
|
|
|
FunLit = (sym"(" * Ct(NameList^-1) * sym")" + Ct("")) *
|
|
|
|
(sym"->" * Cc"slim" + sym"=>" * Cc"fat") *
|
|
|
|
(Body + Ct"") / mark"fndef",
|
2011-05-19 07:32:31 +00:00
|
|
|
|
2011-05-20 07:10:37 +00:00
|
|
|
NameList = Name * (sym"," * Name)^0,
|
|
|
|
ExpList = Exp * (sym"," * Exp)^0
|
2011-05-16 08:53:44 +00:00
|
|
|
}
|
2011-05-18 07:45:27 +00:00
|
|
|
|
2011-05-17 07:59:58 +00:00
|
|
|
return {
|
2011-05-20 07:10:37 +00:00
|
|
|
_g = White * g * White * -1,
|
2011-05-17 07:59:58 +00:00
|
|
|
match = function(self, str, ...)
|
2011-05-21 16:36:26 +00:00
|
|
|
local function pos_to_line(pos)
|
|
|
|
local line = 1
|
|
|
|
for _ in str:sub(1, pos):gmatch("\n") do
|
|
|
|
line = line + 1
|
|
|
|
end
|
|
|
|
return line
|
|
|
|
end
|
|
|
|
|
2011-05-17 07:59:58 +00:00
|
|
|
local function get_line(num)
|
|
|
|
for line in str:gmatch("(.-)[\n$]") do
|
|
|
|
if num == 1 then return line end
|
|
|
|
num = num - 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local tree = self._g:match(str, ...)
|
|
|
|
if not tree then
|
2011-05-21 16:36:26 +00:00
|
|
|
local line_no = pos_to_line(last_pos)
|
|
|
|
local line_str = get_line(line_no)
|
|
|
|
return nil, err_msg:format(line_no, line_str, _indent:top())
|
2011-05-17 07:59:58 +00:00
|
|
|
end
|
|
|
|
return tree
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2011-05-16 08:53:44 +00:00
|
|
|
end)
|
|
|
|
|
2011-05-20 08:08:36 +00:00
|
|
|
-- parse a string
|
|
|
|
-- returns tree, or nil and error message
|
|
|
|
function string(str)
|
|
|
|
local g = build_grammar()
|
2011-05-28 04:27:59 +00:00
|
|
|
return g:match(str)
|
2011-05-20 08:08:36 +00:00
|
|
|
end
|
|
|
|
|
2011-05-16 05:04:38 +00:00
|
|
|
local program3 = [[
|
2011-05-19 07:32:31 +00:00
|
|
|
-- hello
|
2011-05-16 05:04:38 +00:00
|
|
|
class Hello
|
2011-05-28 04:27:59 +00:00
|
|
|
@something: 2323
|
2011-05-16 05:04:38 +00:00
|
|
|
|
|
|
|
hello: () ->
|
|
|
|
print 200
|
|
|
|
]]
|
|
|
|
|