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

595 lines
20 KiB
Lua

--[[- The error messages reported by our lexer and 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.
:::
This provides a list of factory methods which take source positions and produce
appropriate error messages targeting that location. These error messages can
then be displayed to the user via @{cc.internal.error_printer}.
@local
]]
local pretty = require "cc.pretty"
local expect = require "cc.expect".expect
local tokens = require "cc.internal.syntax.parser".tokens
local function annotate(start_pos, end_pos, msg)
if msg == nil and (type(end_pos) == "string" or type(end_pos) == "table" or type(end_pos) == "nil") then
end_pos, msg = start_pos, end_pos
end
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, msg, "string", "table", "nil")
return { tag = "annotate", start_pos = start_pos, end_pos = end_pos, msg = msg or "" }
end
--- Format a string as a non-highlighted block of code.
--
-- @tparam string msg The code to format.
-- @treturn cc.pretty.Doc The formatted code.
local function code(msg) return pretty.text(msg, colours.lightGrey) end
--- Maps tokens to a more friendly version.
local token_names = setmetatable({
-- Specific tokens.
[tokens.IDENT] = "identifier",
[tokens.NUMBER] = "number",
[tokens.STRING] = "string",
[tokens.EOF] = "end of file",
-- Symbols and keywords
[tokens.ADD] = code("+"),
[tokens.AND] = code("and"),
[tokens.BREAK] = code("break"),
[tokens.CBRACE] = code("}"),
[tokens.COLON] = code(":"),
[tokens.COMMA] = code(","),
[tokens.CONCAT] = code(".."),
[tokens.CPAREN] = code(")"),
[tokens.CSQUARE] = code("]"),
[tokens.DIV] = code("/"),
[tokens.DO] = code("do"),
[tokens.DOT] = code("."),
[tokens.DOTS] = code("..."),
[tokens.ELSE] = code("else"),
[tokens.ELSEIF] = code("elseif"),
[tokens.END] = code("end"),
[tokens.EQ] = code("=="),
[tokens.EQUALS] = code("="),
[tokens.FALSE] = code("false"),
[tokens.FOR] = code("for"),
[tokens.FUNCTION] = code("function"),
[tokens.GE] = code(">="),
[tokens.GT] = code(">"),
[tokens.IF] = code("if"),
[tokens.IN] = code("in"),
[tokens.LE] = code("<="),
[tokens.LEN] = code("#"),
[tokens.LOCAL] = code("local"),
[tokens.LT] = code("<"),
[tokens.MOD] = code("%"),
[tokens.MUL] = code("*"),
[tokens.NE] = code("~="),
[tokens.NIL] = code("nil"),
[tokens.NOT] = code("not"),
[tokens.OBRACE] = code("{"),
[tokens.OPAREN] = code("("),
[tokens.OR] = code("or"),
[tokens.OSQUARE] = code("["),
[tokens.POW] = code("^"),
[tokens.REPEAT] = code("repeat"),
[tokens.RETURN] = code("return"),
[tokens.SEMICOLON] = code(";"),
[tokens.SUB] = code("-"),
[tokens.THEN] = code("then"),
[tokens.TRUE] = code("true"),
[tokens.UNTIL] = code("until"),
[tokens.WHILE] = code("while"),
}, { __index = function(_, name) error("No such token " .. tostring(name), 2) end })
local errors = {}
--------------------------------------------------------------------------------
-- Lexer errors
--------------------------------------------------------------------------------
--[[- A string which ends without a closing quote.
@tparam number start_pos The start position of the string.
@tparam number end_pos The end position of the string.
@tparam string quote The kind of quote (`"` or `'`).
@return The resulting parse error.
]]
function errors.unfinished_string(start_pos, end_pos, quote)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, quote, "string")
return {
"This string is not finished. Are you missing a closing quote (" .. code(quote) .. ")?",
annotate(start_pos, "String started here."),
annotate(end_pos, "Expected a closing quote here."),
}
end
--[[- A string which ends with an escape sequence (so a literal `"foo\`). This
is slightly different from @{unfinished_string}, as we don't want to suggest
adding a quote.
@tparam number start_pos The start position of the string.
@tparam number end_pos The end position of the string.
@tparam string quote The kind of quote (`"` or `'`).
@return The resulting parse error.
]]
function errors.unfinished_string_escape(start_pos, end_pos, quote)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, quote, "string")
return {
"This string is not finished.",
annotate(start_pos, "String started here."),
annotate(end_pos, "An escape sequence was started here, but with nothing following it."),
}
end
--[[- A long string was never finished.
@tparam number start_pos The start position of the long string delimiter.
@tparam number end_pos The end position of the long string delimiter.
@tparam number ;em The length of the long string delimiter, excluding the first `[`.
@return The resulting parse error.
]]
function errors.unfinished_long_string(start_pos, end_pos, len)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, len, "number")
return {
"This string was never finished.",
annotate(start_pos, end_pos, "String was started here."),
"We expected a closing delimiter (" .. code("]" .. ("="):rep(len - 1) .. "]") .. ") somewhere after this string was started.",
}
end
--[[- Malformed opening to a long string (i.e. `[=`).
@tparam number start_pos The start position of the long string delimiter.
@tparam number end_pos The end position of the long string delimiter.
@tparam number len The length of the long string delimiter, excluding the first `[`.
@return The resulting parse error.
]]
function errors.malformed_long_string(start_pos, end_pos, len)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, len, "number")
return {
"Incorrect start of a long string.",
annotate(start_pos, end_pos),
"Tip: If you wanted to start a long string here, add an extra " .. code("[") .. " here.",
}
end
--[[- Malformed nesting of a long string.
@tparam number start_pos The start position of the long string delimiter.
@tparam number end_pos The end position of the long string delimiter.
@return The resulting parse error.
]]
function errors.nested_long_str(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
code("[[") .. " cannot be nested inside another " .. code("[[ ... ]]"),
annotate(start_pos, end_pos),
}
end
--[[- A malformed numeric literal.
@tparam number start_pos The start position of the number.
@tparam number end_pos The end position of the number.
@return The resulting parse error.
]]
function errors.malformed_number(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"This isn't a valid number.",
annotate(start_pos, end_pos),
"Numbers must be in one of the following formats: " .. code("123") .. ", "
.. code("3.14") .. ", " .. code("23e35") .. ", " .. code("0x01AF") .. ".",
}
end
--[[- A long comment was never finished.
@tparam number start_pos The start position of the long string delimiter.
@tparam number end_pos The end position of the long string delimiter.
@tparam number len The length of the long string delimiter, excluding the first `[`.
@return The resulting parse error.
]]
function errors.unfinished_long_comment(start_pos, end_pos, len)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
expect(3, len, "number")
return {
"This comment was never finished.",
annotate(start_pos, end_pos, "Comment was started here."),
"We expected a closing delimiter (" .. code("]" .. ("="):rep(len - 1) .. "]") .. ") somewhere after this comment was started.",
}
end
--[[- `&&` was used instead of `and`.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.wrong_and(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"Unexpected character.",
annotate(start_pos, end_pos),
"Tip: Replace this with " .. code("and") .. " to check if both values are true.",
}
end
--[[- `||` was used instead of `or`.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.wrong_or(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"Unexpected character.",
annotate(start_pos, end_pos),
"Tip: Replace this with " .. code("or") .. " to check if either value is true.",
}
end
--[[- `!=` was used instead of `~=`.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.wrong_ne(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"Unexpected character.",
annotate(start_pos, end_pos),
"Tip: Replace this with " .. code("~=") .. " to check if two values are not equal.",
}
end
--[[- An unexpected character was used.
@tparam number pos The position of this character.
@return The resulting parse error.
]]
function errors.unexpected_character(pos)
expect(1, pos, "number")
return {
"Unexpected character.",
annotate(pos, "This character isn't usable in Lua code."),
}
end
--------------------------------------------------------------------------------
-- Expression parsing errors
--------------------------------------------------------------------------------
--[[- A fallback error when we expected an expression but received another token.
@tparam number token The token id.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.expected_expression(token, start_pos, end_pos)
expect(1, token, "number")
expect(2, start_pos, "number")
expect(3, end_pos, "number")
return {
"Unexpected " .. token_names[token] .. ". Expected an expression.",
annotate(start_pos, end_pos),
}
end
--[[- A fallback error when we expected a variable but received another token.
@tparam number token The token id.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.expected_var(token, start_pos, end_pos)
expect(1, token, "number")
expect(2, start_pos, "number")
expect(3, end_pos, "number")
return {
"Unexpected " .. token_names[token] .. ". Expected a variable name.",
annotate(start_pos, end_pos),
}
end
--[[- `=` was used in an expression context.
@tparam number start_pos The start position of the `=` token.
@tparam number end_pos The end position of the `=` token.
@return The resulting parse error.
]]
function errors.use_double_equals(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"Unexpected " .. code("=") .. " in expression.",
annotate(start_pos, end_pos),
"Tip: Replace this with " .. code("==") .. " to check if two values are equal.",
}
end
--[[- `=` was used after an expression inside a table.
@tparam number start_pos The start position of the `=` token.
@tparam number end_pos The end position of the `=` token.
@return The resulting parse error.
]]
function errors.table_key_equals(start_pos, end_pos)
expect(1, start_pos, "number")
expect(2, end_pos, "number")
return {
"Unexpected " .. code("=") .. " in expression.",
annotate(start_pos, end_pos),
"Tip: Wrap the preceding expression in " .. code("[") .. " and " .. code("]") .. " to use it as a table key.",
}
end
--[[- There is a trailing comma in this list of function arguments.
@tparam number token The token id.
@tparam number token_start The start position of the token.
@tparam number token_end The end position of the token.
@tparam number prev The start position of the previous entry.
@treturn table The resulting parse error.
]]
function errors.missing_table_comma(token, token_start, token_end, prev)
expect(1, token, "number")
expect(2, token_start, "number")
expect(3, token_end, "number")
expect(4, prev, "number")
return {
"Unexpected " .. token_names[token] .. " in table.",
annotate(token_start, token_end),
annotate(prev + 1, prev + 1, "Are you missing a comma here?"),
}
end
--[[- There is a trailing comma in this list of function arguments.
@tparam number comma_start The start position of the `,` token.
@tparam number comma_end The end position of the `,` token.
@tparam number paren_start The start position of the `)` token.
@tparam number paren_end The end position of the `)` token.
@treturn table The resulting parse error.
]]
function errors.trailing_call_comma(comma_start, comma_end, paren_start, paren_end)
expect(1, comma_start, "number")
expect(2, comma_end, "number")
expect(3, paren_start, "number")
expect(4, paren_end, "number")
return {
"Unexpected " .. code(")") .. " in function call.",
annotate(paren_start, paren_end),
annotate(comma_start, comma_end, "Tip: Try removing this " .. code(",") .. "."),
}
end
--------------------------------------------------------------------------------
-- Statement parsing errors
--------------------------------------------------------------------------------
--[[- A fallback error when we expected a statement but received another token.
@tparam number token The token id.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.expected_statement(token, start_pos, end_pos)
expect(1, token, "number")
expect(2, start_pos, "number")
expect(3, end_pos, "number")
return {
"Unexpected " .. token_names[token] .. ". Expected a statement.",
annotate(start_pos, end_pos),
}
end
--[[- `local function` was used with a table identifier.
@tparam number local_start The start position of the `local` token.
@tparam number local_end The end position of the `local` token.
@tparam number dot_start The start position of the `.` token.
@tparam number dot_end The end position of the `.` token.
@return The resulting parse error.
]]
function errors.local_function_dot(local_start, local_end, dot_start, dot_end)
expect(1, local_start, "number")
expect(2, local_end, "number")
expect(3, dot_start, "number")
expect(4, dot_end, "number")
return {
"Cannot use " .. code("local function") .. " with a table key.",
annotate(dot_start, dot_end, code(".") .. " appears here."),
annotate(local_start, local_end, "Tip: " .. "Try removing this " .. code("local") .. " keyword."),
}
end
--[[- A statement of the form `x.y z`
@tparam number pos The position right after this name.
@return The resulting parse error.
]]
function errors.standalone_name(pos)
expect(1, pos, "number")
return {
"Unexpected symbol after name.",
annotate(pos),
"Did you mean to assign this or call it as a function?",
}
end
--[[- A statement of the form `x.y`. This is similar to @{standalone_name}, but
when the next token is on another line.
@tparam number pos The position right after this name.
@return The resulting parse error.
]]
function errors.standalone_name_call(pos)
expect(1, pos, "number")
return {
"Unexpected symbol after variable.",
annotate(pos + 1, "Expected something before the end of the line."),
"Tip: Use " .. code("()") .. " to call with no arguments.",
}
end
--[[- `then` was expected
@tparam number if_start The start position of the `if`/`elseif` keyword.
@tparam number if_end The end position of the `if`/`elseif` keyword.
@tparam number token_pos The current token position.
@return The resulting parse error.
]]
function errors.expected_then(if_start, if_end, token_pos)
expect(1, if_start, "number")
expect(2, if_end, "number")
expect(3, token_pos, "number")
return {
"Expected " .. code("then") .. " after if condition.",
annotate(if_start, if_end, "If statement started here."),
annotate(token_pos, "Expected " .. code("then") .. " before here."),
}
end
--[[- `end` was expected
@tparam number block_start The start position of the block.
@tparam number block_end The end position of the block.
@tparam number token The current token position.
@tparam number token_start The current token position.
@tparam number token_end The current token position.
@return The resulting parse error.
]]
function errors.expected_end(block_start, block_end, token, token_start, token_end)
return {
"Unexpected " .. token_names[token] .. ". Expected " .. code("end") .. " or another statement.",
annotate(block_start, block_end, "Block started here."),
annotate(token_start, token_end, "Expected end of block here."),
}
end
--[[- An unexpected `end` in a statement.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.unexpected_end(start_pos, end_pos)
return {
"Unexpected " .. code("end") .. ".",
annotate(start_pos, end_pos),
"Your program contains more " .. code("end") .. "s than needed. Check " ..
"each block (" .. code("if") .. ", " .. code("for") .. ", " ..
code("function") .. ", ...) only has one " .. code("end") .. ".",
}
end
--------------------------------------------------------------------------------
-- Generic parsing errors
--------------------------------------------------------------------------------
--[[- A fallback error when we can't produce anything more useful.
@tparam number token The token id.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.unexpected_token(token, start_pos, end_pos)
expect(1, token, "number")
expect(2, start_pos, "number")
expect(3, end_pos, "number")
return {
"Unexpected " .. token_names[token] .. ".",
annotate(start_pos, end_pos),
}
end
--[[- A parenthesised expression was started but not closed.
@tparam number open_start The start position of the opening bracket.
@tparam number open_end The end position of the opening bracket.
@tparam number tok_start The start position of the opening bracket.
@return The resulting parse error.
]]
function errors.unclosed_brackets(open_start, open_end, token, start_pos, end_pos)
expect(1, open_start, "number")
expect(2, open_end, "number")
expect(3, token, "number")
expect(4, start_pos, "number")
expect(5, end_pos, "number")
-- TODO: Do we want to be smarter here with where we report the error?
return {
"Unexpected " .. token_names[token] .. ". Are you missing a closing bracket?",
annotate(open_start, open_end, "Brackets were opened here."),
annotate(start_pos, end_pos, "Unexpected " .. token_names[token] .. " here."),
}
end
--[[- Expected `(` to open our function arguments.
@tparam number token The token id.
@tparam number start_pos The start position of the token.
@tparam number end_pos The end position of the token.
@return The resulting parse error.
]]
function errors.expected_function_args(token, start_pos, end_pos)
return {
"Unexpected " .. token_names[token] .. ". Expected " .. code("(") .. " to start function arguments.",
annotate(start_pos, end_pos),
}
end
return errors