moonscript/moonscript/parse.lua

261 lines
5.1 KiB
Lua
Raw Normal View History

2011-05-16 05:04:38 +00:00
module("moonscript.parse", package.seeall)
2011-05-16 05:04:38 +00:00
require"util"
require"lpeg"
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-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-18 07:45:27 +00:00
local C, Ct, Cmt = lpeg.C, lpeg.Ct, lpeg.Cmt
2011-05-16 05:04:38 +00:00
2011-05-20 07:10:37 +00:00
local White = S" \t\n"^0
2011-05-16 08:53:44 +00:00
local Space = S" \t"^0
2011-05-21 22:26:46 +00:00
local ASpace = S" \t"^1
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-21 22:26:46 +00:00
local Name = Space * C(R("az", "AZ", "__") * R("az", "AZ", "__")^0)
local Num = Space * C(R("09")^1) / tonumber
2011-05-16 05:04:38 +00:00
2011-05-21 22:26:46 +00:00
local FactorOp = Space * lpeg.C(S"+-")
local TermOp = Space * lpeg.C(S"*/%")
2011-05-16 05:04:38 +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
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
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
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-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-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-16 08:53:44 +00:00
local g = lpeg.P{
2011-05-20 07:10:37 +00:00
File,
File = Block^-1,
2011-05-21 16:36:26 +00:00
Block = Ct(Line * (Break^1 * Line)^0),
Line = Cmt(Indent, check_indent) * Statement,
Statement = Ct(If) + Exp,
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-20 07:10:37 +00:00
If = key"if" * Exp * Body / mark"if",
Assign = Ct(NameList) * sym"=" * Ct(ExpList) / mark"assign",
2011-05-18 07:45:27 +00:00
2011-05-21 08:11:25 +00:00
Exp = Ct(Term * (FactorOp * Term)^0) / flatten_or_mark"exp",
Term = Ct(Value * (TermOp * Value)^0) / flatten_or_mark"exp",
2011-05-21 22:26:46 +00:00
Value = Assign + FunLit + (FunCall + Callable) * Ct(ExpList^0) / flatten_func + Num,
Callable = Name + Parens,
Parens = sym"(" * Exp * sym")",
-- a plain function/index
FunCall = Callable * (symx"(" * Ct(ExpList^-1)/mark"call" * sym")" + symx"[" * Exp/mark"index" * sym"]")^1 / mark"chain",
2011-05-20 07:10:37 +00:00
TableLit = sym"{" * Ct(ExpList^-1) * sym"}" / mark"list",
2011-05-19 07:32:31 +00:00
2011-05-21 16:36:26 +00:00
FunLit = (sym"(" * Ct(NameList^-1) * sym")" + Ct("")) * sym"->" * (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)
local grammar = build_grammar()
2011-05-16 05:04:38 +00:00
-- parse a string
-- returns tree, or nil and error message
function string(str)
local g = build_grammar()
return grammar:match(str)
end
2011-05-18 07:45:27 +00:00
2011-05-16 05:04:38 +00:00
local program = [[
2011-05-18 07:45:27 +00:00
if two_dads
do something
if yum
heckyes 23
print 2
print dadas
2011-05-19 07:32:31 +00:00
{1,2,3,4}
(a,b) ->
throw nuts
print 100
2011-05-16 05:04:38 +00:00
]]
2011-05-20 07:10:37 +00:00
local program = [[
hi = (a) -> print a
if true
hi 100
]]
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
@something = 2323
hello: () ->
print 200
]]