///|
/// Every way a shrubbery source can be rejected.
///
/// A closed enum with the parameters spelled out, rather than a string: a
/// consumer that wants to react to a particular failure — an editor offering a
/// fix, a test asserting on a case — matches on the constructor and never
/// parses prose. `reference_text` reproduces the Racket wording verbatim, which
/// is what the error-parity oracle compares, and `Diagnostic::to_report` is
/// free to say something better without breaking it.
pub(all) enum ErrorKind {
// --- lexical -------------------------------------------------------------
/// Anything the scanner could not make a token of.
ReadError
/// `keyword` distinguishes `~#{` from `#{`, which the reference words
/// differently.
ExpectedSExp(keyword~ : Bool)
ExpectedSExpClose(keyword~ : Bool)
ExpectedSExpOnlyWhitespace(keyword~ : Bool)
SExpMustNotBePair
// --- indentation ---------------------------------------------------------
/// Two columns that neither extends. See `@column.ColumnOrder`.
IncomparableIndentation
/// `missing_colon_hint` is the reference's longer wording, used where a `:`
/// on the previous line would have made the line legal.
WrongIndentation(missing_colon_hint~ : Bool)
AltBeforeGroupColumn
// --- delimiters ----------------------------------------------------------
DidNotFindMatching(expected~ : String)
UnexpectedCloser(found~ : String)
ExpectedCloser(expected~ : String, found~ : String)
ExpectedQuoteAfterGuillemet
ExpectedParenAfterGuillemet
ExpectedGuillemetClose
// --- separators ----------------------------------------------------------
/// `paren_immed` is whether the comma was already inside an opener-closer
/// pair; the reference explains the rule only when it was not.
MisplacedComma(paren_immed~ : Bool)
MissingCommaBeforeGroup
MisplacedSemicolon
MultiGroupSpliceNotAllowed
// --- blocks and alternatives ---------------------------------------------
/// `after` is the token that opened the block, `:` or `|`.
EmptyBlock(after~ : String, could_empty_if_start~ : Bool)
NoTermsAfterGuillemet
UnnecessaryColonBeforeBar
MisplacedBar
MisplacedGuillemet
NotOnSameLine(preceding~ : String)
// --- comments and continuations ------------------------------------------
MisplacedGroupComment
NoGroupForTermComment
ContinuationFollowedByToken
// --- at-notation ---------------------------------------------------------
MissingTermAfterAt
WhitespaceAfterAt
InvalidAfterAt
AtArgCannotStartBracket
MissingCloserForAtContent
AtCommentOutsideBody
EmptyGroupAfterAt
BlockAfterMidGroupAt
SecondGroupAfterAt
} derive(Eq)
///|
/// What the reference says, byte for byte.
///
/// This is the measuring instrument, not the message: the error-parity oracle
/// compares it against Racket's `exn-message`, so it must not be improved. If a
/// wording here reads oddly — `a another`, below, is upstream's typo — that is
/// the point.
pub fn ErrorKind::reference_text(self : ErrorKind) -> String {
let within = "within parentheses, brackets, or braces"
match self {
ReadError => "read error"
ExpectedSExp(keyword~) =>
if keyword {
"expected S-expression identifier after `~#{`"
} else {
"expected S-expression after `#{`"
}
ExpectedSExpClose(keyword~) =>
if keyword {
"expected `}` after S-expression identifier for keyword"
} else {
"expected `}` after S-expression"
}
ExpectedSExpOnlyWhitespace(keyword~) =>
if keyword {
"expected only whitespace or `}` after S-expression identifier for keyword"
} else {
"expected only whitespace or `}` after S-expression"
}
SExpMustNotBePair => "S-expression in `#{` and `}` must not be a pair"
IncomparableIndentation => "incomparable indentation due to mixed tabs"
WrongIndentation(missing_colon_hint~) =>
if missing_colon_hint {
"wrong indentation (or missing `:` on previous line)"
} else {
"wrong indentation"
}
AltBeforeGroupColumn =>
"alternative cannot start before group's initial column"
// `~s` in the reference, so the closer arrives already quoted.
DidNotFindMatching(expected~) =>
"did not find matching \{write_string(expected)}"
UnexpectedCloser(found~) => "unexpected closer `\{found}`"
ExpectedCloser(expected~, found~) =>
"expected closer `\{expected}`, found `\{found}`"
ExpectedQuoteAfterGuillemet => "expected closing \"'\" after closing \"»\""
ExpectedParenAfterGuillemet =>
"expected a closing `)` immediately after closing `»`"
ExpectedGuillemetClose => "expected `»`"
MisplacedComma(paren_immed~) =>
if paren_immed {
"misplaced comma"
} else {
"misplaced comma (not immediately \{within})"
}
MissingCommaBeforeGroup => "missing comma before new group (\{within})"
MisplacedSemicolon => "misplaced semicolon (immediately \{within})"
MultiGroupSpliceNotAllowed =>
"multi-group splice not allowed (immediately \{within})"
EmptyBlock(after~, could_empty_if_start~) => {
let extra = if could_empty_if_start {
", at top, or in an opener-closer pair"
} else {
""
}
"empty block not allowed after `\{after}` (except with `«` and `»`\{extra})"
}
NoTermsAfterGuillemet => "no terms allowed after `»` within a group"
UnnecessaryColonBeforeBar => "unnecessary `:` before `|`"
MisplacedBar => "misplaced `|`"
MisplacedGuillemet => "misplaced `«`"
NotOnSameLine(preceding~) =>
"not on the same line as preceding `\{preceding}`"
MisplacedGroupComment => "misplaced group comment"
NoGroupForTermComment => "no group for term comment"
// Upstream's typo, reproduced deliberately.
ContinuationFollowedByToken =>
"line-continuing '\\' is followed by a another token on the same line"
MissingTermAfterAt => "missing term after `@`"
WhitespaceAfterAt => "whitespace is invalid after `@`"
InvalidAfterAt => "invalid after `@`"
AtArgCannotStartBracket => "argument position for `@` cannot start `[`"
MissingCloserForAtContent => "missing closer for `@` content"
AtCommentOutsideBody =>
"comments using `@//` are allowed only within `@` body"
EmptyGroupAfterAt => "empty group after `@` within `«` and `»`"
BlockAfterMidGroupAt =>
"block not allowed after mid-group `@` within `«` and `»`"
SecondGroupAfterAt =>
"second group not allowed after `@` within `«` and `»`"
}
}
///|
/// Racket's `~s` for a string: double quotes, with `\` and `"` escaped.
fn write_string(s : String) -> String {
let buf = StringBuilder()
buf.write_char('"')
for c in s {
if c == '"' || c == '\\' {
buf.write_char('\\')
}
buf.write_char(c)
}
buf.write_char('"')
buf.to_string()
}
///|
/// A stable identifier for this failure.
///
/// Stable is the operative word: it is what a test asserts on and what a
/// consumer suppresses by, so it has to survive rewording the message — which
/// is exactly why it is not derived from one.
pub fn ErrorKind::code(self : ErrorKind) -> String {
let name = match self {
ReadError => "read_error"
ExpectedSExp(_) => "expected_s_expression"
ExpectedSExpClose(_) => "expected_s_expression_close"
ExpectedSExpOnlyWhitespace(_) => "expected_s_expression_end"
SExpMustNotBePair => "s_expression_must_not_be_pair"
IncomparableIndentation => "incomparable_indentation"
WrongIndentation(_) => "wrong_indentation"
AltBeforeGroupColumn => "alternative_before_group_column"
DidNotFindMatching(_) => "did_not_find_matching"
UnexpectedCloser(_) => "unexpected_closer"
ExpectedCloser(_) => "expected_closer"
ExpectedQuoteAfterGuillemet => "expected_quote_after_guillemet"
ExpectedParenAfterGuillemet => "expected_paren_after_guillemet"
ExpectedGuillemetClose => "expected_guillemet_close"
MisplacedComma(_) => "misplaced_comma"
MissingCommaBeforeGroup => "missing_comma"
MisplacedSemicolon => "misplaced_semicolon"
MultiGroupSpliceNotAllowed => "multi_group_splice"
EmptyBlock(_) => "empty_block"
NoTermsAfterGuillemet => "terms_after_guillemet"
UnnecessaryColonBeforeBar => "unnecessary_colon_before_bar"
MisplacedBar => "misplaced_bar"
MisplacedGuillemet => "misplaced_guillemet"
NotOnSameLine(_) => "not_on_same_line"
MisplacedGroupComment => "misplaced_group_comment"
NoGroupForTermComment => "no_group_for_term_comment"
ContinuationFollowedByToken => "continuation_followed_by_token"
MissingTermAfterAt => "missing_term_after_at"
WhitespaceAfterAt => "whitespace_after_at"
InvalidAfterAt => "invalid_after_at"
AtArgCannotStartBracket => "at_argument_bracket"
MissingCloserForAtContent => "missing_at_closer"
AtCommentOutsideBody => "at_comment_outside_body"
EmptyGroupAfterAt => "empty_group_after_at"
BlockAfterMidGroupAt => "block_after_mid_group_at"
SecondGroupAfterAt => "second_group_after_at"
}
"shrubbery::" + name
}