mighty

The mighty programming language, compiler and tools (WIP)
Log | Files | Refs

parser.lua (8587B)



local parser = { pos = 1 }
local ast = require("ast")
local utils = require("utils")

function parser:peek()
	return self.tokens[self.pos] or 0
end

function parser:nextpeek()
	return self.tokens[self.pos + 1] or 0
end

function parser:advance()
	self.pos = self.pos + 1
end

function parser:expect(kind, extramsg)
	local current = self.tokens[self.pos]
	if current.kind ~= kind then
		unhandled(current, "EXPECTED kind: " .. kind, extramsg)
	end
	parser:advance()
	return current
end

function parser:match_ident()
	local current = self.tokens[self.pos]
	if current.kind ~= "ident" then
		unhandled(current, "EXPECTED an IDENT")
	end
	return current
end
-----------------------

function parser:parse_postfix()
	local node = parse_primary()

	while true do
		if parser:peek().kind == TK.PLUSPLUS then
			return ast.postfix(OPER.POSTINCR, node)
		elseif parser:peek().kind == TK.MINUSMINUS then
			return ast.postfix(OPER.POSTDECR, node)
		elseif parser:peek().kind == TK.L_BRACKET then -- parse inside brackets[]
			local index = parser:parse_expression()
			parser:expect(TK.R_BRACKET)
			return ast.subscript(node, index)
		elseif parser:peek().kind == TK.L_PAREN then
			-- arguments
			local args = parser:parse_arguments()
			return ast.call(node, args)
		elseif parser:peek().kind == TK.L_BRACE then -- parse name{} we don't hold grammar for this...
		else
			break
			-- todo .member and potentially arrow operators here
		end
	end
end

function parser:parse_unary()
	local inner = 0

	if parser:peek().kind == TK.MINUS then
		parser:advance()
		return ast.unary(OPER.MINUS, parser:parse_unary())
	elseif parser:peek().kind == TK.MINUSMINUS then
		parser:advance()
		return ast.unary(OPER.PREDECR, parser:parse_unary())
	elseif parser:peek().kind == TK.BANG then
		parser:advance()
		return ast.unary(OPER.BANG, parser:parse_unary())
		-- TODO add others '~a' '$a' '*a' '^a' '@a' '&a' ?
	elseif parser:peek().kind == TK.PLUSPLUS then
		parser:advance()
		return ast.unary(OPER.PREINCR, parser:parse_unary())
	elseif parser:peek().kind == TK.PLUS then
		parser:advance()
		return parser:parse_unary()
	else
		return parser:parse_postfix()
	end
end

function parser:parse_term()
	return parser:parse_unary()
end

function parser:parse_multiplicative()
	local node = parse_term()

	while true do
		if parser:peek().kind == TK.STAR then
			local rhs = parse_unary()
			node = ast.binary(OP_MUL, node, rhs)
		elseif parser:peek().kind == TK.SLASH then
			local rhs = parse_unary()
			node = ast.binary(OP_DIV, node, rhs)
		elseif parser:peek().kind == TK.PERCENT then
			local rhs = parse_unary()
			node = ast.binary(OP_MOD, node, rhs)
		else
			break
		end
	end
	return node
end

function parser:parse_additive()
	local node = parse_multiplicative()

	while true do
		if parser:peek().kind == TK.PLUS then
			local rhs = parse_multiplicative()
			node = ast.binary(OP.PLUS, node, rhs)
		elseif parser:peek().kind == TK.MINUS then
			local rhs = parse_multiplicative()
			node = ast.binary(OP.MINUS, node, rhs)
		else
			break
		end
	end
	return node
end

function parser:parse_relational()
	local node = parse_additive()

	while true do
		if parse:peek().kind == TK.LT then
			local rhs = parser:parse_additive()
			node = ast.binary(OP.LT, node, rhs)
		elseif parse:peek().kind == TK.LTEQ then
			local rhs = parser:parse_additive()
			node = ast.binary(OP.LT_EQ, node, rhs)
			-- todo more relationals
		else
			break
		end
	end
	return node
end

function parser:parse_equality()
	local node = parser:parse_relational()

	while true do
		if parser:peek().kind == TK.EQEQ then
			local rhs = parser:parse_relational()
			node = ast.binary(OP.EQUALITY, node, rhs)
		-- elseif next == TK.BANGEQ then
		-- todo inequality
		else
			break
		end
	end
	return node
end

function parser:parse_assignment_expr()
	local left = parser:parse_equality()

	if parser:peek().kind == TK.EQ then
		local right = parser:parse_assignment_expr()
		-- todo check fail assignment target
		-- todo make binary node
	end

	return left
end

function parser:parse_expr()
	return parser:parse_assignment_expr()
end

function parser:parse_var()
	print("todo parse_var")
end
function parser:parse_while()
	print("todo parse_while")
end
function parser:parse_for()
	print("todo parse_for")
end
function parser:parse_return()
	print("todo parse_return")
end
function parser:parse_as_cast()
	print("todo parse_as_cast")
end

function parser:parse_arguments()
	parser:expect(TK.L_PAREN) -- swallow (
	local args = {}
	while parser:peek().kind ~= TK.R_PAREN do -- call parameters
		local expr = parser:parse_expr()
		args[#args + 1] = expr
		if parser:peek().kind == TK.COMMA then
			parser:advance()
		else
			break
		end
		parser:advance()
	end
	print(parser:peek().kind)
	parser:expect(TK.R_PAREN)
	return args
end

function parser:parse_call()
	-- parse ident, parse parameters
	local callee = parser:expect(TK.IDENT)
	print("parse_call")
	local args = parser:parse_arguments()
	return ast.call(callee, args)
end

function parser:parse_statement()
	local first = parser:peek().kind
	local second = parser:nextpeek().kind
	if first == TK.IDENT then
		if second == TK.L_PAREN then
			print("parsing a call statement")
			return parser:parse_call()
		elseif second == TK.IDENT then
			return parser:parse_var()
		else
			print("parse_statement unhandled ident start with: " .. second)
		end
	elseif first == TK.KEYWORD then
		local kw = parser:peek()
		if kw.lexeme == "while" then
			return parser:parse_while()
		elseif kw.lexeme == "for" then
			return parser:parse_for()
		elseif kw.lexeme == "return" then
			return parser:parse_return()
		elseif kw.lexeme == "as" then
			return parser:parse_as_cast()
		else
			print("parse_statement unhandled keyword yet: " .. kw.lexeme)
		end
	end
	return { name = "idk" }
end

function parser:parse_function(is_exported)
	local name = "unamed function"
	local parameters = {}
	local returntypes = {}
	local statements = {}
	local token = parser:peek()
	parser:advance() -- consume fx/fn
	name = parser:expect(TK.IDENT)
	parser:expect(TK.L_PAREN)
	while parser:peek().kind ~= TK.R_PAREN do -- parameters
		local kind = parser:expect(TK.IDENT, "missing kind in param")
		local name = parser:expect(TK.IDENT, "missing name in param")
		parameters[#parameters + 1] = { kind = kind, name = name }
		if parser:peek().kind == TK.COMMA then
			parser:advance()
		else
			break
		end
	end
	parser:expect(TK.R_PAREN)
	while parser:peek().kind ~= TK.L_BRACE do -- return type(s)
		local kind = parser:expect(TK.IDENT, "rettype no ident")
		returntypes[#returntypes + 1] = { kind = kind }
		if parser:peek().kind == TK.COMMA then
			parser:advance()
		else
			break
		end
	end
	parser:expect(TK.COLONCOLON, "no rbrace start of function")

	while parser:peek().lexeme ~= "end" do
		::continue::
		local tok = parser:peek()
		if tok.kind == TK.EOS then
			parser:advance()
			goto continue
		end
		-- function body statements'
		print("parsing a body statement " .. parser:peek().lexeme)
		local stmt = parser:parse_statement()
		statements[#statements + 1] = stmt
		parser:advance()
	end
	local node = ast.func(name, parameters, ret, statements)
	return node
end

function parser:parse_use()
	print("parse use")
	self:advance()
	local lib = parser:match_ident()
	self:advance()
	self:expect(TK.EOS, "use statement found without a end statement \\n|;")
	local node = ast.use(lib)
	return node
end

function unhandled(token, extramsg)
	print(
		"\27[33mat "
			.. token.file
			.. " L"
			.. token.line
			.. ":"
			.. token.col
			.. "\n\t UNHANDLED TOKEN: "
			.. token.lexeme
			.. "\t "
			.. (extramsg or "")
	)
	print(" |> " .. debug.traceback() .. "\27[0m")
end

function parser:parse_program()
	local nodes = {}
	while self:peek() ~= 0 do
		local token = self:peek()
		local kind = token.kind
		local word = token.lexeme
		print("peek:", word)
		if kind == TK.EOS then
			-- nothing, advance
		elseif kind == TK.KEYWORD and word == "use" then
			nodes[#nodes + 1] = self:parse_use()
		elseif kind == TK.KEYWORD and word == "fx" then
			nodes[#nodes + 1] = self:parse_function(true)
		elseif kind == TK.KEYWORD and word == "fn" then
			nodes[#nodes + 1] = self:parse_function(false)
		else
			unhandled(token)
			break
		end
		self:advance()
	end
	return nodes
end

function parser:parse(file, src, tokens)
	self.file = file
	self.src = src
	self.tokens = tokens
	self.pos = 1
	local i = 1

	local nodes = self:parse_program()

	utils.print_table(nodes)

	-- while i <= #tokens do
	-- 	local t = tokens[i]
	-- 	t2 = tokens[i + 1] or 0
	-- 	i = i + 1
	-- end
end

return parser