CC-Tweaked/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/init.lua

173 lines
4.8 KiB
Lua

--[[- The main entrypoint to our Lua parser
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
@local
]]
local expect = require "cc.expect".expect
local lex_one = require "cc.internal.syntax.lexer".lex_one
local parser = require "cc.internal.syntax.parser"
local error_printer = require "cc.internal.error_printer"
local error_sentinel = {}
local function make_context(input)
expect(1, input, "string")
local context = {}
local lines = { 1 }
function context.line(pos) lines[#lines + 1] = pos end
function context.get_pos(pos)
expect(1, pos, "number")
for i = #lines, 1, -1 do
local start = lines[i]
if pos >= start then return i, pos - start + 1 end
end
error("Position is <= 0", 2)
end
function context.get_line(pos)
expect(1, pos, "number")
for i = #lines, 1, -1 do
local start = lines[i]
if pos >= start then return input:match("[^\r\n]*", start) end
end
error("Position is <= 0", 2)
end
return context
end
local function make_lexer(input, context)
local tokens, last_token = parser.tokens, parser.tokens.COMMENT
local pos = 1
return function()
while true do
local token, start, finish = lex_one(context, input, pos)
if not token then return tokens.EOF, #input + 1, #input + 1 end
pos = finish + 1
if token < last_token then
return token, start, finish
elseif token == tokens.ERROR then
error(error_sentinel)
end
end
end
end
local function parse(input, start_symbol)
expect(1, input, "string")
expect(2, start_symbol, "number")
local context = make_context(input)
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
error_printer(context, msg)
error(error_sentinel)
end
local ok, err = pcall(parser.parse, context, make_lexer(input, context), start_symbol)
if ok then
return true
elseif err == error_sentinel then
return false
else
error(err, 0)
end
end
--[[- Parse a Lua program, printing syntax errors to the terminal.
@tparam string input The string to parse.
@treturn boolean Whether the string was successfully parsed.
]]
local function parse_program(input) return parse(input, parser.program) end
--[[- Parse a REPL input (either a program or a list of expressions), printing
syntax errors to the terminal.
@tparam string input The string to parse.
@treturn boolean Whether the string was successfully parsed.
]]
local function parse_repl(input)
expect(1, input, "string")
local context = make_context(input)
local last_error = nil
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
last_error = msg
error(error_sentinel)
end
local lexer = make_lexer(input, context)
local parsers = {}
for i, start_code in ipairs { parser.repl_exprs, parser.program } do
parsers[i] = coroutine.create(parser.parse)
assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code))
end
-- Run all parsers together in parallel, feeding them one token at a time.
-- Once all parsers have failed, report the last failure (corresponding to
-- the longest parse).
local ok, err = pcall(function()
local parsers_n = #parsers
while true do
local token, start, finish = lexer()
local all_failed = true
for i = 1, parsers_n do
local parser = parsers[i]
if parser then
local ok, err = coroutine.resume(parser, token, start, finish)
if ok then
-- This parser accepted our input, succeed immediately.
if coroutine.status(parser) == "dead" then return end
all_failed = false -- Otherwise continue parsing.
elseif err ~= error_sentinel then
-- An internal error occurred: propagate it.
error(err, 0)
else
-- The parser failed, stub it out so we don't try to continue using it.
parsers[i] = false
end
end
end
if all_failed then error(error_sentinel) end
end
end)
if ok then
return true
elseif err == error_sentinel then
error_printer(context, last_error)
return false
else
error(err, 0)
end
end
return {
parse_program = parse_program,
parse_repl = parse_repl,
}