moonscript/moonscript.lua
2011-05-20 00:10:37 -07:00

224 lines
4.0 KiB
Lua

module("moonscript", package.seeall)
require"util"
require"lpeg"
require"moonscript.compile"
require"moonscript.dump"
require"moonscript.data"
local Stack = data.Stack
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
local R, S, V, P = lpeg.R, lpeg.S, lpeg.V, lpeg.P
local C, Ct, Cmt = lpeg.C, lpeg.Ct, lpeg.Cmt
local White = S" \t\n"^0
local Space = S" \t"^0
local Break = S"\n"
local Stop = Break + -1
local Indent = C(S"\t "^0) / count_indent
local Name = C(R("az", "AZ", "__") * R("az", "AZ", "__")^0) * Space
local Num = C(R("09")^1) / tonumber * Space
local FactorOp = lpeg.C(S"+-") * Space
local TermOp = lpeg.C(S"*/%") * Space
function wrap(fn)
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
function mark(name)
return function(...)
return name, ...
end
end
function got(what)
return Cmt("", function(...)
print("++ got "..what)
return true
end)
end
function flatten(tbl)
if #tbl == 1 then
return tbl[1]
end
return tbl
end
local build_grammar = wrap(function()
local err_msg = "Failed to compile, line:\n [%d] >> %s (%d)"
local line = 1
local function line_count(subject, pos, str)
for _ in str:gmatch("\n") do
line = line + 1
end
-- print(line, util.dump(str))
return true
end
local Space = Cmt(Space, line_count)
local Break = Cmt(Break, line_count)
local _indent = Stack(0) -- current indent
local function check_indent(str, pos, indent)
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
return word * Space
end
local function sym(chars)
return chars * Space
end
-- make sure name is not a keyword
local Name = Cmt(Name, function(str, pos, name)
if keywords[name] then return false end
return true, name
end)
local g = lpeg.P{
File,
File = Block^-1,
Block = Ct(Line * (Break^0 * Line)^0),
Line = Cmt(Indent, check_indent) * (Ct(If) + Exp),
Body = Break * InBlock + Ct(Line),
InBlock = #Cmt(Indent, advance_indent) * Block * OutBlock,
OutBlock = Cmt(P(""), pop_indent),
FunCall = Name * Ct(ExpList) / mark"fncall",
If = key"if" * Exp * Body / mark"if",
Assign = Ct(NameList) * sym"=" * Ct(ExpList) / mark"assign",
Exp = Ct(Value * (FactorOp * Value)^0) / flatten,
Value = Assign + FunLit + FunCall + Num + Name + TableLit,
TableLit = sym"{" * Ct(ExpList^-1) * sym"}" / mark"list",
FunLit = (sym"(" * Ct(NameList^-1) * sym")")^-1 * sym"->" * Body / mark"fndef",
NameList = Name * (sym"," * Name)^0,
ExpList = Exp * (sym"," * Exp)^0
}
return {
_g = White * g * White * -1,
match = function(self, str, ...)
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
local line_str = get_line(line) or ""
return nil, err_msg:format(line, line_str, _indent:top())
end
return tree
end
}
end)
local grammar = build_grammar()
local program = [[
if two_dads
do something
if yum
heckyes 23
print 2
print dadas
{1,2,3,4}
(a,b) ->
throw nuts
print 100
]]
local program = [[
hi = (a) -> print a
if true
hi 100
]]
local tree, err = grammar:match(program)
if not tree then error(err) end
if type(tree) == "table" then
-- dump.tree(tree)
-- print""
print(compile.tree(tree))
else
print "nothing..."
end
local program3 = [[
-- hello
class Hello
@something = 2323
hello: () ->
print 200
]]