1374 lines
34 KiB
Lua
1374 lines
34 KiB
Lua
---@class Parser
|
|
---@field lexer Lexer
|
|
Parser = {}
|
|
|
|
function Parser:new(lexer)
|
|
return setmetatable({
|
|
lexer = lexer
|
|
}, {__index = self})
|
|
end
|
|
|
|
function Parser:parse()
|
|
local parts = {}
|
|
local start
|
|
|
|
while true do
|
|
local tok, err = self.lexer:peek()
|
|
if not tok then
|
|
return nil, err
|
|
end
|
|
|
|
if not start then
|
|
start = tok.location:clone()
|
|
end
|
|
|
|
if tok.kind == "eof" then
|
|
break
|
|
end
|
|
|
|
local node, nerr = self:parseStatement()
|
|
if not node then
|
|
return nil, nerr
|
|
end
|
|
|
|
parts[#parts+1] = node
|
|
end
|
|
|
|
return Node:new("Program", parts, start)
|
|
end
|
|
|
|
function Parser:parseBlockOrStatement()
|
|
local checktok, checkerr = self.lexer:peek()
|
|
if not checktok then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checktok.kind == "symbol" and checktok.content == "{" then
|
|
local block, berr = self:parseBlock()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return block
|
|
end
|
|
|
|
local stmt, sterr = self:parseStatement()
|
|
if not stmt then
|
|
return nil, sterr
|
|
end
|
|
|
|
return stmt;
|
|
end
|
|
|
|
function Parser:parseBlock()
|
|
local starttok, sterr = self.lexer:peek()
|
|
if not starttok then
|
|
return nil, sterr
|
|
end
|
|
|
|
if starttok.kind ~= "symbol" or starttok.content ~= "{" then
|
|
return nil, Error:new("expected { to start block", "parser", starttok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local parts = {}
|
|
|
|
while true do
|
|
local detect, deterr = self.lexer:peek()
|
|
if not detect then
|
|
return nil, deterr
|
|
end
|
|
|
|
if detect.kind == "symbol" and detect.content == "}" then
|
|
break;
|
|
end
|
|
|
|
if detect.kind == "eof" and detect.content == "eof" then
|
|
return nil, Error:new("unexpected eof in a block", "parser", detect.location:clone())
|
|
end
|
|
|
|
local stmt, stmterr = self:parseStatement()
|
|
if not stmt then
|
|
return nil, stmterr
|
|
end
|
|
|
|
parts[#parts+1] = stmt
|
|
end
|
|
|
|
local check, cherr = self.lexer:peek()
|
|
if not check then
|
|
return nil, cherr
|
|
end
|
|
|
|
if check.kind ~= "symbol" or check.content ~= "}" then
|
|
return nil, Error:new("block ended in an invalid way", "parser", check.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new("Block", parts, starttok.location:clone())
|
|
end
|
|
|
|
function Parser:parseFunctionName()
|
|
local tok,terr = self.lexer:peek()
|
|
if not tok then
|
|
return nil, terr
|
|
end
|
|
|
|
if tok.kind ~= "identifier" then
|
|
return nil, Error:new("expected function name to start with identifier", "parser", tok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local node = Node:new('Variable', tok.content, tok.location:clone())
|
|
|
|
while true do -- woohoo more shit
|
|
local check, checkerr = self.lexer:peek()
|
|
if not check then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if check.kind == "symbol" and check.content == "." then
|
|
self.lexer:consume()
|
|
|
|
local idx,idxerr = self.lexer:peek()
|
|
if not idx then
|
|
return nil, idxerr
|
|
end
|
|
|
|
if idx.kind ~= "identifier" then
|
|
return nil, Error:new("expected identifier after . in function name", 'parser', idx.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new('Field', {node, idx.content}, check.location:clone());
|
|
elseif check.kind == "symbol" and check.content == "[" then
|
|
self.lexer:consume()
|
|
|
|
local expr,err = self:parseExpression()
|
|
if not expr then
|
|
return nil, err
|
|
end
|
|
|
|
local endcheck, endcheckerr = self.lexer:peek()
|
|
if not endcheck then
|
|
return nil, endcheckerr
|
|
end
|
|
|
|
if endcheck.kind ~= "symbol" or endcheck.content ~= "]" then
|
|
return nil, Error:new("expected ] to close indexing in function name", 'parser', endcheck.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new('Index', {node, expr}, check.location:clone())
|
|
elseif check.kind == "symbol" and check.content == ":" then
|
|
self.lexer:consume()
|
|
|
|
local methodname, methoderr = self.lexer:peek()
|
|
if not methodname then
|
|
return nil, methoderr
|
|
end
|
|
|
|
if methodname.kind ~= "identifier" then
|
|
return nil, Error:new("expected identifier for method name in function declaration", 'parser', methodname.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new("Method", {node, methodname.content}, check.location:clone())
|
|
break; -- can't have more afterward
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
|
|
return node;
|
|
end
|
|
|
|
function Parser:parseStatement()
|
|
local tok, err = self.lexer:peek()
|
|
if not tok then
|
|
return nil, err
|
|
end
|
|
|
|
if tok.kind == "keyword" and tok.content == "var" then
|
|
-- var decl
|
|
|
|
-- get rid of keyw
|
|
self.lexer:consume()
|
|
|
|
local checker,checkererr = self.lexer:peek()
|
|
if not checker then
|
|
return nil, checkererr
|
|
end
|
|
|
|
if checker.kind == "keyword" and checker.content == "fn" then
|
|
self.lexer:consume()
|
|
|
|
local fnname, fnnerr = self.lexer:peek()
|
|
if not fnname then
|
|
return nil, fnnerr
|
|
end
|
|
|
|
if fnname.kind ~= "identifier" then
|
|
return nil, Error:new("expected identifier function name after var fn", "parser", fnname.location:clone());
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local paren, parerr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, parerr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= "(" then
|
|
return nil, Error:new("expected ( to open parameter list for local function")
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local params = {}
|
|
|
|
while true do
|
|
local checktok, checkerr = self.lexer:peek()
|
|
if not checktok then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checktok.kind == "symbol" and checktok.content == ")" then
|
|
break;
|
|
elseif checktok.kind == "identifier" then
|
|
params[#params+1] = checktok.content
|
|
self.lexer:consume()
|
|
elseif checktok.kind == "eof" and checktok.content == "eof" then
|
|
break;
|
|
end
|
|
|
|
local nexttok, nexterr = self.lexer:peek()
|
|
if not nexttok then
|
|
return nil, nexterr
|
|
end
|
|
|
|
if nexttok.kind ~= "symbol" or nexttok.content ~= "," then
|
|
break;
|
|
end
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local clostok, closerr = self.lexer:peek()
|
|
if not clostok then
|
|
return nil, closerr
|
|
end
|
|
|
|
if clostok.kind ~= "symbol" or clostok.content ~= ")" then
|
|
return nil, Error:new("no closing parenthesis after local function parameters", "parser", clostok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("LocalFunctionDeclaration", {fnname, params, block}, tok.location:clone())
|
|
end
|
|
|
|
-- varnames
|
|
local varnames = {}
|
|
while true do
|
|
local vntok, vnerr = self.lexer:peek()
|
|
if not vntok then
|
|
return nil, vnerr
|
|
end
|
|
|
|
if vntok.kind == "identifier" then
|
|
self.lexer:consume()
|
|
varnames[#varnames+1] = {vntok.content, vntok.location:clone()}
|
|
else
|
|
return nil, Error:new("not an identifier in varname list in declaration", "parser", vntok.location:clone())
|
|
end
|
|
|
|
local commatok, commaerr = self.lexer:peek()
|
|
if not commatok then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if commatok.kind == "symbol" and commatok.content == "," then
|
|
self.lexer:consume()
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
local splittok, spliterr = self.lexer:peek()
|
|
if not splittok then
|
|
return nil, spliterr
|
|
end
|
|
|
|
if splittok.kind ~= "symbol" then
|
|
return nil, Error:new("expected = or ; after variable names in declaration", "parser", splittok.location:clone())
|
|
end
|
|
|
|
if splittok.content == ";" then
|
|
-- get rid
|
|
self.lexer:consume()
|
|
|
|
return Node:new("VariableDeclaration", {varnames, {}}, tok.location:clone())
|
|
end
|
|
|
|
if splittok.content == "=" then
|
|
-- get rid
|
|
self.lexer:consume()
|
|
|
|
local values = {}
|
|
|
|
while true do
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
values[#values+1] = expr
|
|
|
|
local commatok, commaerr = self.lexer:peek()
|
|
if not commatok then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if commatok.kind == "symbol" and commatok.content == "," then
|
|
self.lexer:consume()
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
local smc, smcerr = self.lexer:peek()
|
|
if not smc then
|
|
return nil, smcerr
|
|
end
|
|
|
|
if smc.kind ~= "symbol" or smc.content ~= ";" then
|
|
return nil, Error:new("expected semicolon after variable declaration", "parser", smc.location:clone())
|
|
end
|
|
|
|
self.lexer:consume()
|
|
|
|
return Node:new("VariableDeclaration", {varnames, values}, tok.location:clone())
|
|
end
|
|
|
|
return nil, Error:new("expected = or ; after variable names in declaration", "parser", splittok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "if" then
|
|
self.lexer:consume()
|
|
|
|
local checks = {}
|
|
|
|
local partok, parerr = self.lexer:peek()
|
|
if not partok then
|
|
return nil,parerr
|
|
end
|
|
|
|
if partok.kind ~= "symbol" or partok.content ~= "(" then
|
|
return nil, Error:new("expected ( after if keyword", "parser", partok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local condition,conderr = self:parseExpression()
|
|
if not condition then
|
|
return nil, conderr
|
|
end
|
|
|
|
local closepart, closeparerr = self.lexer:peek()
|
|
if not closepart then
|
|
return nil, closeparerr
|
|
end
|
|
|
|
if closepart.kind ~= "symbol" or closepart.content ~= ")" then
|
|
return nil, Error:new("expected ) to close if statement condition", 'parser', closepart.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local body, bodyerr = self:parseBlockOrStatement()
|
|
if not body then
|
|
return nil, bodyerr
|
|
end
|
|
|
|
checks[#checks+1] = {condition,body}
|
|
|
|
-- elseifs
|
|
|
|
while true do
|
|
local checkt, checkerr = self.lexer:peek()
|
|
if not checkt then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checkt.kind == "keyword" and checkt.content == "elseif" then
|
|
self.lexer:consume()
|
|
|
|
local partok2, parerr2 = self.lexer:peek()
|
|
if not partok2 then
|
|
return nil,parerr2
|
|
end
|
|
|
|
if partok2.kind ~= "symbol" or partok2.content ~= "(" then
|
|
return nil, Error:new("expected ( after elseif keyword", "parser", partok2.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local condition2,conderr2 = self:parseExpression()
|
|
if not condition2 then
|
|
return nil, conderr2
|
|
end
|
|
|
|
local closepart2, closeparerr2 = self.lexer:peek()
|
|
if not closepart2 then
|
|
return nil, closeparerr2
|
|
end
|
|
|
|
if closepart2.kind ~= "symbol" or closepart2.content ~= ")" then
|
|
return nil, Error:new("expected ) to close elseif statement condition", closepart2.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block,berr = self:parseBlockOrStatement();
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
checks[#checks+1] = {condition2, block}
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
|
|
local elset, elseerr = self.lexer:peek()
|
|
if not elset then
|
|
return nil, elseerr
|
|
end
|
|
|
|
local elseblock;
|
|
if elset.kind == "keyword" and elset.content == "else" then
|
|
self.lexer:consume()
|
|
|
|
local elseb, elseberr = self:parseBlockOrStatement();
|
|
if not elseb then
|
|
return nil, elseberr
|
|
end
|
|
|
|
elseblock = elseb
|
|
end
|
|
|
|
return Node:new("IfStatement", {checks, elseblock}, tok.location:clone());
|
|
elseif tok.kind == "keyword" and tok.content == "while" then
|
|
self.lexer:consume()
|
|
|
|
local paren, perr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, perr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= "(" then
|
|
return nil, Error:new("expected ( after start of while", "parser", paren.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local cond, conderr = self:parseExpression()
|
|
if not cond then
|
|
return nil, conderr
|
|
end
|
|
|
|
local closep, closeperr = self.lexer:peek()
|
|
if not closep then
|
|
return nil, closeperr
|
|
end
|
|
|
|
if closep.kind ~= "symbol" or closep.content ~= ")" then
|
|
return nil, Error:new("expected ) to close condition in while loop", 'parser', closep.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block,berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("While", {cond, block}, tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "for" then
|
|
self.lexer:consume()
|
|
|
|
local paren, perr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, perr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= "(" then
|
|
return nil, Error:new("expected ( after start of while", "parser", paren.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local decl, declerr = self:parseStatement(); -- auto removes semi!!
|
|
if not decl then
|
|
return nil, declerr
|
|
end
|
|
|
|
local check, checkerr = self:parseExpression();
|
|
if not check then
|
|
return nil, checkerr
|
|
end
|
|
|
|
local semi, semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= "symbol" or semi.content ~= ";" then
|
|
return nil, Error:new('expected semicolon after condition in for loop', 'parser', semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local modifier, moderr = self:parseStatement(); -- also removes a semi -> third semi is required!
|
|
if not modifier then
|
|
return nil, moderr
|
|
end
|
|
|
|
local close, closeerr = self.lexer:peek();
|
|
if not close then
|
|
return nil, closeerr
|
|
end
|
|
|
|
if close.kind ~= "symbol" or close.content ~= ")" then
|
|
return nil, Error:new("expected ) to close condition part of for loop", 'parser', close.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("For", {decl, check, modifier, block}, tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "fn" then
|
|
self.lexer:consume()
|
|
|
|
local fnname, fnnerr = self:parseFunctionName()
|
|
if not fnname then
|
|
return nil, fnnerr
|
|
end
|
|
|
|
local paren, parerr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, parerr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= "(" then
|
|
return nil, Error:new("expected ( to open parameter list for function", 'lexer', paren.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local params = {}
|
|
|
|
while true do
|
|
local checktok, checkerr = self.lexer:peek()
|
|
if not checktok then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checktok.kind == "symbol" and checktok.content == ")" then
|
|
break;
|
|
elseif checktok.kind == "identifier" then
|
|
params[#params+1] = checktok.content
|
|
self.lexer:consume()
|
|
elseif checktok.kind == "eof" and checktok.content == "eof" then
|
|
break;
|
|
end
|
|
|
|
local nexttok, nexterr = self.lexer:peek()
|
|
if not nexttok then
|
|
return nil, nexterr
|
|
end
|
|
|
|
if nexttok.kind ~= "symbol" or nexttok.content ~= "," then
|
|
break;
|
|
end
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local clostok, closerr = self.lexer:peek()
|
|
if not clostok then
|
|
return nil, closerr
|
|
end
|
|
|
|
if clostok.kind ~= "symbol" or clostok.content ~= ")" then
|
|
return nil, Error:new("no closing parenthesis after function parameters", "parser", clostok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("FunctionDeclaration", {fnname, params, block}, tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "return" then
|
|
self.lexer:consume()
|
|
|
|
local parts = {}
|
|
|
|
while true do
|
|
local chtok, cherr = self.lexer:peek()
|
|
if not chtok then
|
|
return nil, cherr
|
|
end
|
|
|
|
if chtok.kind == "symbol" and chtok.content == ";" then
|
|
break;
|
|
end
|
|
|
|
local expr, exprerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exprerr
|
|
end
|
|
|
|
parts[#parts+1] = expr
|
|
|
|
local checkt, checkerr = self.lexer:peek()
|
|
if not checkt then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checkt.kind ~= "symbol" or checkt.content ~= "," then
|
|
break;
|
|
end
|
|
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local semi, semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= 'symbol' or semi.content ~= ';' then
|
|
return nil, Error:new("expected ; after return statement", "parser", semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new('Return', parts, tok.location:clone());
|
|
elseif tok.kind == "keyword" and tok.content == "do" then
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
if block.kind == "Block" then
|
|
return block;
|
|
else
|
|
return Node:new("Block", {block}, tok.location:clone())
|
|
end
|
|
elseif (tok.kind == "symbol" and tok.content == "(") or tok.kind == "identifier" then
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
local nextok, nexerr = self.lexer:peek()
|
|
if not nextok then
|
|
return nil, nexerr
|
|
end
|
|
|
|
if nextok.kind == "symbol" and (nextok.content == "," or nextok.content == "=") then
|
|
-- assignment
|
|
|
|
local vars = {expr}
|
|
|
|
while true do
|
|
local tkn, tknerr = self.lexer:peek()
|
|
if not tkn then
|
|
return nil, tknerr
|
|
end
|
|
|
|
if tkn.kind == "symbol" and tkn.content == "," then
|
|
self.lexer:consume()
|
|
|
|
local checkt, checkerr = self.lexer:peek()
|
|
if not checkt then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if (checkt.kind == "symbol" and checkt.content == "(") or checkt.kind == "identifier" then
|
|
local nexpr, nerr = self:parseExpression()
|
|
if not nexpr then
|
|
return nil, nerr
|
|
end
|
|
|
|
vars[#vars+1] = nexpr
|
|
else
|
|
return nil, Error:new("expected expression starting with ( or an identifier after comma in assigment!", 'parser', checkt.location:clone())
|
|
end
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
|
|
local equaltok, equalerr = self.lexer:peek()
|
|
|
|
if not equaltok then
|
|
return nil, equalerr
|
|
end
|
|
|
|
if equaltok.kind ~= "symbol" or equaltok.content ~= "=" then
|
|
return nil, Error:new("expected = in assignment", 'parser', equaltok.location:clone())
|
|
end
|
|
self.lexer:consume() -- get rid of =
|
|
|
|
local values = {}
|
|
|
|
while true do
|
|
local assexpr, asserr = self:parseExpression()
|
|
if not assexpr then
|
|
return nil, asserr
|
|
end
|
|
|
|
values[#values+1] = assexpr;
|
|
|
|
local commatok, commaerr = self.lexer:peek()
|
|
if not commatok then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if commatok.kind == "symbol" and commatok.content == "," then
|
|
self.lexer:consume()
|
|
else
|
|
break;
|
|
end
|
|
end
|
|
|
|
local semi,semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= "symbol" or semi.content ~= ";" then
|
|
return nil, Error:new("expected semicolon after assignment", 'parser', semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new('Assignment', {vars, values}, equaltok.location:clone());
|
|
elseif nextok.kind == "symbol" and (nextok.content == '&&=' or nextok.content == "||=" or nextok.content == "+=" or nextok.content == "-=" or nextok.content == "*=" or nextok.content == "/=" or nextok.content == "%=" or nextok.content == "^=" or nextok.content == "..=") then
|
|
self.lexer:consume()
|
|
local operator = nextok.content:sub(1,-2) -- just the thing but without the =, whould work fine for all these cases
|
|
|
|
if expr.kind ~= "Variable" and expr.kind ~= "Field" and expr.kind ~= "Index" then
|
|
return nil, Error:new("can only use operator assignment on variables, fields or indexes", "parser", expr.location:clone())
|
|
end
|
|
|
|
local rhs,rhserr = self:parseExpression()
|
|
if not rhs then
|
|
return nil, rhserr
|
|
end
|
|
|
|
local semi,semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= "symbol" or semi.content ~= ";" then
|
|
return nil, Error:new("expected semicolon after operator assignment", 'parser', semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new("OperatorAssignment", {expr, operator, rhs}, nextok.location:clone())
|
|
end
|
|
|
|
if expr.kind ~= "Call" and expr.kind ~= "MethodCall" then
|
|
return nil, Error:new("loose expression must be a call or method call", 'parser', expr.location:clone())
|
|
end
|
|
|
|
local semi, semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= "symbol" or semi.content ~= ";" then
|
|
return nil, Error:new("expected semicolon after call or method call", 'parser', semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return expr
|
|
elseif tok.kind == "keyword" and tok.content == "break" then
|
|
self.lexer:consume()
|
|
|
|
local semi,semierr = self.lexer:peek()
|
|
if not semi then
|
|
return nil, semierr
|
|
end
|
|
|
|
if semi.kind ~= "symbol" or semi.content ~= ";" then
|
|
return nil, Error:new("expected semicolon after break", 'parser', semi.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new("Break", {}, tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "foreach" then
|
|
self.lexer:consume()
|
|
|
|
local openpar,operr = self.lexer:peek()
|
|
if not openpar then
|
|
return nil, operr
|
|
end
|
|
|
|
if openpar.kind ~= "symbol" or openpar.content ~= "(" then
|
|
return nil, Error:new("expected ( after foreach keyword", 'parser', openpar.location:clone());
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local varnames = {}
|
|
|
|
while true do
|
|
local vname,vnamerr = self.lexer:peek()
|
|
if not vname then
|
|
return nil, vnamerr
|
|
end
|
|
|
|
if vname.kind ~= "identifier" then
|
|
return nil, Error:new("expected identifier in varname list in foreach loop", 'parser', vname.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
varnames[#varnames+1] = vname.content;
|
|
|
|
local comma,commaerr = self.lexer:peek()
|
|
if not comma then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if comma.kind ~= "symbol" or comma.content ~= "," then
|
|
break
|
|
end
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local eqtok, eqerr = self.lexer:peek()
|
|
if not eqtok then
|
|
return nil, eqerr
|
|
end
|
|
|
|
if eqtok.kind ~= "symbol" or eqtok.content ~= "=" then
|
|
return nil, Error:new("expected = after varname list in foreach loop", 'parser', eqtok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local exprs = {}
|
|
|
|
while true do
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
exprs[#exprs+1] = expr
|
|
|
|
local commatok, commaerr = self.lexer:peek()
|
|
if not commatok then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if commatok.kind ~= "symbol" or commatok.content ~= "," then
|
|
break;
|
|
end
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local closepar, clperr = self.lexer:peek()
|
|
if not closepar then
|
|
return nil, clperr
|
|
end
|
|
|
|
if closepar.kind ~= "symbol" or closepar.content ~= ")" then
|
|
return nil, Error:new("expected ) to close expression list for foreach loop", 'parser', closepar.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement();
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("Foreach", {varnames, exprs, block}, tok.location:clone())
|
|
end
|
|
|
|
return nil, Error:new('invalid statement', 'parser', tok.location:clone());
|
|
end
|
|
|
|
function Parser:parseExpression()
|
|
return self:parseOperatorExpression()
|
|
end
|
|
|
|
---@param token Token
|
|
---@return number?
|
|
function Parser:prefixOperatorBindingPower(token)
|
|
if token.kind == "symbol" then
|
|
if token.content == "-" then
|
|
return 45
|
|
elseif token.content == "#" then
|
|
return 45
|
|
elseif token.content == "!" then
|
|
return 45
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
---@param token Token
|
|
---@return number?, number?
|
|
function Parser:infixOperatorBindingPower(token)
|
|
if token.kind == "symbol" then
|
|
if token.content == "+" or token.content == "-" then
|
|
return 10, 20
|
|
elseif token.content == "*" or token.content == "/" or token.content == "%" then
|
|
return 30, 40
|
|
elseif token.content == "^" then
|
|
return 60, 50
|
|
elseif token.content == ".." then
|
|
return 0, -10
|
|
elseif token.content == "<" or token.content == ">" or
|
|
token.content == "<=" or token.content == ">=" or
|
|
token.content == "!=" or token.content == "==" then
|
|
return -30, -20
|
|
elseif token.content == "&&" then
|
|
return -50, -40
|
|
elseif token.content == "||" then
|
|
return -70, -60
|
|
end
|
|
end
|
|
end
|
|
|
|
function Parser:parseOperatorExpression(min_bp, predlhs)
|
|
min_bp = min_bp or -math.huge
|
|
|
|
local tok, err = self.lexer:peek()
|
|
|
|
if not tok then
|
|
return nil, err
|
|
end
|
|
|
|
if tok.kind == "eof" then
|
|
return nil, Error:new("expected expression, but found EOF", "parser", tok.location:clone())
|
|
end
|
|
|
|
local lhs = predlhs
|
|
|
|
if (lhs == nil) and self:prefixOperatorBindingPower(tok) then
|
|
self.lexer:consume() -- eat the operator
|
|
|
|
local prefix_bp = self:prefixOperatorBindingPower(tok)
|
|
|
|
local rightthing, righterr = self:parseOperatorExpression(prefix_bp)
|
|
if not rightthing then
|
|
return nil, righterr
|
|
end
|
|
|
|
lhs = Node:new("UnaryOperator", {tok.content, rightthing}, tok.location:clone())
|
|
end
|
|
|
|
if lhs == nil then
|
|
local newlhs, newerr = self:parseRawExpression()
|
|
if not newlhs then
|
|
return nil, newerr
|
|
end
|
|
lhs = newlhs
|
|
end
|
|
|
|
while true do
|
|
local newtok, lexErr = self.lexer:peek()
|
|
|
|
if not newtok then
|
|
return nil, lexErr
|
|
end
|
|
|
|
local left_bp, right_bp = self:infixOperatorBindingPower(newtok)
|
|
|
|
if not left_bp or not right_bp then break end -- ain't an operator!!
|
|
|
|
if left_bp < min_bp then -- we don't
|
|
break
|
|
end
|
|
|
|
self.lexer:consume()
|
|
|
|
local rhs, rhserr = self:parseOperatorExpression(right_bp)
|
|
if not rhs then
|
|
return nil, rhserr
|
|
end
|
|
|
|
lhs = Node:new("BinaryOperator", {newtok.content, lhs, rhs}, newtok.location:clone())
|
|
end
|
|
|
|
return lhs
|
|
end
|
|
|
|
function Parser:parseTableLiteral()
|
|
local tok, tokerr = self.lexer:peek()
|
|
if not tok then
|
|
return nil, tokerr
|
|
end
|
|
|
|
if tok.kind ~= "symbol" or tok.content ~= "{" then
|
|
return nil, Error:new("expected { to start table literal", "parser", tok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local list_part = {}
|
|
local table_entries = {}
|
|
|
|
while true do
|
|
local check, checkerr = self.lexer:peek()
|
|
if not check then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if check.kind == "symbol" and check.content == "}" then
|
|
break;
|
|
end
|
|
|
|
-- it hasn't ended, let us continue.
|
|
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
local check2, check2err = self.lexer:peek()
|
|
if not check2 then
|
|
return nil, check2err
|
|
end
|
|
|
|
if check2.kind == "symbol" and check2.content == '=' then
|
|
-- table entry!
|
|
self.lexer:consume()
|
|
|
|
local set,seterr = self:parseExpression()
|
|
if not set then
|
|
return nil, seterr
|
|
end
|
|
|
|
table_entries[#table_entries+1] = {expr, set}
|
|
else
|
|
list_part[#list_part+1] = expr
|
|
end
|
|
|
|
local commatok, commaerr = self.lexer:peek()
|
|
if not commatok then
|
|
return nil, commaerr
|
|
end
|
|
|
|
if commatok.kind ~= "symbol" or commatok.content ~= "," then
|
|
break;
|
|
end
|
|
self.lexer:consume() -- remove comma
|
|
end
|
|
|
|
local closetok, closeerr = self.lexer:peek()
|
|
if not closetok then
|
|
return nil, closeerr;
|
|
end
|
|
|
|
if closetok.kind ~= "symbol" or closetok.content ~= "}" then
|
|
return nil, Error:new('expected } to close table literal', 'parser', closetok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
return Node:new("TableLiteral", {list_part, table_entries}, tok.location:clone())
|
|
end
|
|
|
|
function Parser:parseLambdaFunction()
|
|
local fn,fnerr = self.lexer:peek()
|
|
if not fn then
|
|
return nil, fnerr
|
|
end
|
|
|
|
if fn.kind ~= 'keyword' or fn.content ~= 'fn' then
|
|
return nil, Error:new('expected fn to start lambda function', 'parser', fn.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local paren, parerr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, parerr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= "(" then
|
|
return nil, Error:new("expected ( to open parameter list for lambda function", 'parser', paren.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local params = {}
|
|
|
|
while true do
|
|
local checktok, checkerr = self.lexer:peek()
|
|
if not checktok then
|
|
return nil, checkerr
|
|
end
|
|
|
|
if checktok.kind == "symbol" and checktok.content == ")" then
|
|
break;
|
|
elseif checktok.kind == "identifier" then
|
|
params[#params+1] = checktok.content
|
|
self.lexer:consume()
|
|
elseif checktok.kind == "eof" and checktok.content == "eof" then
|
|
break;
|
|
end
|
|
|
|
local nexttok, nexterr = self.lexer:peek()
|
|
if not nexttok then
|
|
return nil, nexterr
|
|
end
|
|
|
|
if nexttok.kind ~= "symbol" or nexttok.content ~= "," then
|
|
break;
|
|
end
|
|
self.lexer:consume()
|
|
end
|
|
|
|
local clostok, closerr = self.lexer:peek()
|
|
if not clostok then
|
|
return nil, closerr
|
|
end
|
|
|
|
if clostok.kind ~= "symbol" or clostok.content ~= ")" then
|
|
return nil, Error:new("no closing parenthesis after lambda function parameters", "parser", clostok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local block, berr = self:parseBlockOrStatement()
|
|
if not block then
|
|
return nil, berr
|
|
end
|
|
|
|
return Node:new("LambdaFunctionLiteral", {params, block}, fn.location:clone())
|
|
end
|
|
|
|
function Parser:parseRawExpression()
|
|
local tok, err = self.lexer:peek()
|
|
if not tok then
|
|
return nil, err
|
|
end
|
|
|
|
if tok.kind == "string" then
|
|
self.lexer:consume()
|
|
return Node:new("StringLiteral", tok.content, tok.location:clone())
|
|
elseif tok.kind == "number" then
|
|
self.lexer:consume()
|
|
return Node:new("NumberLiteral", tok.content, tok.location:clone())
|
|
elseif tok.kind == "identifier" then
|
|
self.lexer:consume()
|
|
local node = Node:new("Variable", tok.content, tok.location:clone())
|
|
|
|
return self:parseComplexExpression(node)
|
|
elseif tok.kind == "keyword" and tok.content == "true" then
|
|
self.lexer:consume()
|
|
return Node:new("BooleanLiteral", "true", tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "false" then
|
|
self.lexer:consume()
|
|
return Node:new("BooleanLiteral", "false", tok.location:clone())
|
|
elseif tok.kind == "keyword" and tok.content == "null" then
|
|
self.lexer:consume()
|
|
return Node:new("NullLiteral", nil, tok.location:clone())
|
|
elseif tok.kind == "symbol" and tok.content == "{" then
|
|
return self:parseTableLiteral();
|
|
elseif tok.kind == "keyword" and tok.content == "fn" then
|
|
return self:parseLambdaFunction();
|
|
elseif tok.kind == "symbol" and tok.content == "(" then
|
|
self.lexer:consume()
|
|
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
local closet, closerr = self.lexer:peek()
|
|
if not closet then
|
|
return nil, closerr
|
|
end
|
|
|
|
if closet.kind ~= "symbol" or closet.content ~= ")" then
|
|
return nil, Error:new('expected ) to close parenthesized expression', 'parser', closet.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local node = Node:new('ParenthesizedExpression', expr, tok.location:clone())
|
|
|
|
local newnode, nodeerr = self:parseComplexExpression(node)
|
|
if nodeerr then
|
|
return nil, nodeerr
|
|
end
|
|
|
|
return newnode
|
|
else
|
|
return nil, Error:new('invalid expression', 'parser', tok.location:clone())
|
|
end
|
|
end
|
|
|
|
function Parser:parseComplexExpression(node)
|
|
while true do
|
|
local tok, err = self.lexer:peek()
|
|
if not tok then
|
|
return nil, err
|
|
end
|
|
|
|
if tok.kind == "symbol" and tok.content == "." then
|
|
self.lexer:consume() -- bye .
|
|
|
|
local field, ferr = self.lexer:peek()
|
|
if not field then
|
|
return nil, ferr
|
|
end
|
|
|
|
if field.kind ~= "identifier" then
|
|
return nil, Error:new("expected identifier after . in complex expression", "parser", field.location:clone())
|
|
end
|
|
|
|
self.lexer:consume()
|
|
|
|
node = Node:new("Field", {node, field.content}, tok.location:clone())
|
|
elseif tok.kind == "symbol" and tok.content == "[" then
|
|
self.lexer:consume()
|
|
|
|
-- now we have *some* expression
|
|
local expr, exerr = self:parseExpression()
|
|
if not expr then
|
|
return nil, exerr
|
|
end
|
|
|
|
local close,closerr = self.lexer:peek()
|
|
if not close then
|
|
return nil, closerr
|
|
end
|
|
|
|
if close.kind ~= "symbol" or close.content ~= "]" then
|
|
return nil, Error:new("expected ] to close indexing in complex expression", "parser", close.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new("Index", {node, expr}, tok.location:clone())
|
|
elseif tok.kind == "symbol" and tok.content == ":" then
|
|
self.lexer:consume()
|
|
|
|
local methodname, methoderr = self.lexer:peek()
|
|
if not methodname then
|
|
return nil, methoderr
|
|
end
|
|
|
|
if methodname.kind ~= "identifier" then
|
|
return nil, Error:new('expected identifier for method name', 'parser', methodname.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local parentok, parenerr = self.lexer:peek()
|
|
if not parentok then
|
|
return nil, parenerr
|
|
end
|
|
|
|
if parentok.kind ~= "symbol" or parentok.content ~= '(' then
|
|
return nil, Error:new("expected ( to open method call argument list", 'parser', parentok.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
local parts = {}
|
|
while true do
|
|
local chtok, cherr = self.lexer:peek()
|
|
if not chtok then
|
|
return nil, cherr
|
|
end
|
|
|
|
if chtok.kind == "symbol" and chtok.content == ")" then
|
|
-- self.lexer:consume()
|
|
break;
|
|
end
|
|
|
|
local expr, experr = self:parseExpression()
|
|
if not expr then
|
|
return nil, experr
|
|
end
|
|
|
|
parts[#parts+1] = expr;
|
|
|
|
local ntok, nerr = self.lexer:peek()
|
|
if not ntok then
|
|
return nil, nerr
|
|
end
|
|
|
|
if ntok.kind == "symbol" and ntok.content == ")" then
|
|
break;
|
|
elseif ntok.kind == "symbol" and ntok.content == "," then
|
|
self.lexer:consume()
|
|
-- let it go!1!!
|
|
else
|
|
return nil, Error:new("expected ) or , after argument in method call", 'parser', ntok.location:clone())
|
|
end
|
|
end
|
|
|
|
local closeparen, closeparenerr = self.lexer:peek()
|
|
if not closeparen then
|
|
return nil, closeparenerr
|
|
end
|
|
|
|
if closeparen.kind ~= "symbol" or closeparen.content ~= ")" then
|
|
return nil, Error:new("expected ) to close arument list for method call", 'parser', closeparen.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new("MethodCall", {node, methodname.content, parts}, tok.location:clone())
|
|
elseif tok.kind == "symbol" and tok.content == "(" then
|
|
self.lexer:consume()
|
|
|
|
local parts = {}
|
|
while true do
|
|
local chtok, cherr = self.lexer:peek()
|
|
if not chtok then
|
|
return nil, cherr
|
|
end
|
|
|
|
if chtok.kind == "symbol" and chtok.content == ")" then
|
|
-- self.lexer:consume()
|
|
break;
|
|
end
|
|
|
|
local expr, experr = self:parseExpression()
|
|
if not expr then
|
|
return nil, experr
|
|
end
|
|
|
|
parts[#parts+1] = expr;
|
|
|
|
local ntok, nerr = self.lexer:peek()
|
|
if not ntok then
|
|
return nil, nerr
|
|
end
|
|
|
|
if ntok.kind == "symbol" and ntok.content == ")" then
|
|
break;
|
|
elseif ntok.kind == "symbol" and ntok.content == "," then
|
|
self.lexer:consume()
|
|
-- let it go!1!!
|
|
else
|
|
return nil, Error:new("expected ) or , after argument in call", 'parser', ntok.location:clone())
|
|
end
|
|
end
|
|
|
|
local paren, parenerr = self.lexer:peek()
|
|
if not paren then
|
|
return nil, parenerr
|
|
end
|
|
|
|
if paren.kind ~= "symbol" or paren.content ~= ")" then
|
|
return nil, Error:new("expected ) to close arument list for call", 'parser', paren.location:clone())
|
|
end
|
|
self.lexer:consume()
|
|
|
|
node = Node:new("Call", {node, parts}, tok.location:clone());
|
|
else
|
|
return node
|
|
end
|
|
end
|
|
end
|