// Port of jmespath/exceptions.py.
//
// Python models the errors as a class hierarchy rooted at
// `JMESPathError(ValueError)`. In MoonBit they are the constructors of a
// single `JMESPathError` suberror. The class hierarchy is recoverable with
// the predicates `is_parse_error`, `is_arity_error`, ... The rendered
// messages (`Show`) are the same as Python's `str(exception)`.
///|
/// All errors raised while compiling or evaluating a JMESPath expression.
///
/// Constructors mirror the exception classes of jmespath.py:
///
/// - `ParseError` (`exceptions.ParseError`): `token_value` is the `str()` of
/// the offending token's value, `token_type` is upper-cased like upstream.
/// - `IncompleteExpressionError`: after the parser fills in the expression,
/// `lex_position` is the length of the expression and `token_value` /
/// `token_type` are `None`, just like `set_expression` upstream.
/// - `LexerError`
/// - `ArityError` / `VariadictArityError` (sic, upstream spelling)
/// - `JMESPathTypeError`
/// - `EmptyExpressionError`
/// - `UnknownFunctionError`
///
/// Python can additionally leak *builtin* exceptions from the interpreter
/// (for example `ValueError: slice step cannot be zero`, or a `TypeError`
/// when `<` compares a number with a string). These are modelled by the
/// `ValueError`, `TypeError` and `OverflowError` constructors.
pub(all) suberror JMESPathError {
ParseError(
lex_position~ : Int,
token_value~ : String,
token_type~ : String,
msg~ : String,
expression~ : String?
)
IncompleteExpressionError(
lex_position~ : Int,
token_value~ : String?,
token_type~ : String?,
expression~ : String?
)
LexerError(
lexer_position~ : Int,
lexer_value~ : String,
message~ : String,
expression~ : String?
)
ArityError(
expected_arity~ : Int,
actual_arity~ : Int,
function_name~ : String
)
VariadictArityError(
expected_arity~ : Int,
actual_arity~ : Int,
function_name~ : String
)
JMESPathTypeError(
function_name~ : String,
current_value~ : Value,
actual_type~ : String,
expected_types~ : Array[String]
)
EmptyExpressionError
UnknownFunctionError(String)
/// Python's builtin `ValueError` (e.g. "slice step cannot be zero").
ValueError(String)
/// Python's builtin `TypeError` (e.g. unorderable comparison).
TypeError(String)
/// Python's builtin `OverflowError` (e.g. `ceil` of infinity).
OverflowError(String)
}
///|
fn optional_str(s : String?) -> String {
match s {
Some(s) => s
None => "None"
}
}
///|
fn pluralize(word : String, count : Int) -> String {
if count == 1 {
word
} else {
word + "s"
}
}
///|
pub impl Show for JMESPathError with fn output(self, logger) {
match self {
ParseError(lex_position~, token_value~, token_type~, msg~, expression~) => {
// lex_position + 1 to account for the starting double quote char.
let underline = " ".repeat(lex_position + 1) + "^"
logger.write_string(
"\{msg}: Parse error at column \{lex_position}, token \"\{token_value}\" (\{token_type}), for expression:\n\"\{optional_str(expression)}\"\n\{underline}",
)
}
IncompleteExpressionError(lex_position~, expression~, ..) => {
let underline = " ".repeat(lex_position + 1) + "^"
logger.write_string(
"Invalid jmespath expression: Incomplete expression:\n\"\{optional_str(expression)}\"\n\{underline}",
)
}
LexerError(lexer_position~, message~, expression~, ..) => {
let underline = " ".repeat(lexer_position) + "^"
logger.write_string(
"Bad jmespath expression: \{message}:\n\{optional_str(expression)}\n\{underline}",
)
}
ArityError(expected_arity~, actual_arity~, function_name~) =>
logger.write_string(
"Expected \{expected_arity} \{pluralize("argument", expected_arity)} for function \{function_name}(), received \{actual_arity}",
)
VariadictArityError(expected_arity~, actual_arity~, function_name~) =>
logger.write_string(
"Expected at least \{expected_arity} \{pluralize("argument", expected_arity)} for function \{function_name}(), received \{actual_arity}",
)
JMESPathTypeError(
function_name~,
current_value~,
actual_type~,
expected_types~
) =>
logger.write_string(
"In function \{function_name}(), invalid type for value: \{current_value}, expected one of: \{py_repr_str_list(expected_types)}, received: \"\{actual_type}\"",
)
EmptyExpressionError =>
logger.write_string("Invalid JMESPath expression: cannot be empty.")
UnknownFunctionError(msg) => logger.write_string(msg)
ValueError(msg) => logger.write_string(msg)
TypeError(msg) => logger.write_string(msg)
OverflowError(msg) => logger.write_string(msg)
}
}
///|
/// `isinstance(e, ParseError)` (includes lexer, incomplete-expression and
/// arity errors, which subclass `ParseError` upstream).
pub fn JMESPathError::is_parse_error(self : JMESPathError) -> Bool {
self
is (ParseError(..)
| IncompleteExpressionError(..)
| LexerError(..)
| ArityError(..)
| VariadictArityError(..))
}
///|
/// `isinstance(e, ArityError)` (includes `VariadictArityError`).
pub fn JMESPathError::is_arity_error(self : JMESPathError) -> Bool {
self is (ArityError(..) | VariadictArityError(..))
}
///|
/// Whether upstream raises this as a subclass of Python's `ValueError`.
/// Every error except the builtin `TypeError` / `OverflowError` ones is.
pub fn JMESPathError::is_value_error(self : JMESPathError) -> Bool {
!(self is (TypeError(_) | OverflowError(_)))
}
///|
/// Raised by `loads` (Python's `json.JSONDecodeError`). `pos`, `lineno` and
/// `colno` count code points, like Python.
pub(all) suberror JSONDecodeError {
JSONDecodeError(msg~ : String, doc~ : String, pos~ : Int)
}
///|
pub impl Show for JSONDecodeError with fn output(self, logger) {
let JSONDecodeError(msg~, doc~, pos~) = self
// Python: lineno = doc.count('\n', 0, pos) + 1
// colno = pos - doc.rfind('\n', 0, pos)
let mut lineno = 1
let mut last_nl = -1
for i, c in doc.to_array() {
if i >= pos {
break
}
if c == '\n' {
lineno += 1
last_nl = i
}
}
let colno = pos - last_nl
logger.write_string("\{msg}: line \{lineno} column \{colno} (char \{pos})")
}
///|
pub extend JMESPathError with Show::{to_string}
///|
pub extend JSONDecodeError with Show::{to_string}