use std::{fmt::Display, ops::Range};
#[derive(Debug, Clone)]
pub struct Position {
line: usize,
col: usize,
span: Range<usize>
}
impl Position {
pub(crate) fn new(line: usize, col: usize, span: Range<usize>) -> Self {
Self {
line,
col,
span,
}
}
pub fn line(&self) -> usize {
self.line
}
pub fn col(&self) -> usize {
self.col
}
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
}
impl Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{},{}", self.line, self.col)
}
}
#[derive(Debug, Copy, Clone)]
pub enum Severity {
Warning,
Error
}
impl Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Severity::Warning => "warning",
Severity::Error => "error",
})
}
}
#[derive(Debug)]
pub struct CompilerMessage {
pos: Option<Position>,
severity: Severity,
info: Problem
}
impl CompilerMessage {
pub(crate) fn new(info: Problem, severity: Severity, pos: Option<Position>) -> Self {
Self { pos, severity, info }
}
pub fn pos(&self) -> Option<Position> {
self.pos.clone()
}
pub fn severity(&self) -> Severity {
self.severity
}
pub fn info(&self) -> &Problem {
&self.info
}
pub fn consume(self) -> (Option<Position>, Severity, Problem) {
(self.pos, self.severity, self.info)
}
pub fn code(&self) -> &'static str {
self.info.code()
}
pub fn message(&self) -> String {
self.info.message()
}
pub fn inline_message(&self) -> Option<String> {
self.info.inline_message()
}
pub fn hint(&self) -> Option<String> {
self.info.hint()
}
pub fn is_error(&self) -> bool {
matches!(self.severity, Severity::Error)
}
pub fn is_warning(&self) -> bool {
matches!(self.severity, Severity::Warning)
}
}
#[derive(Debug)]
pub enum Problem {
TemporalAssignPipeRedefinesVariable(String),
TemporalAssignPipeRedefinesConstant(String),
AccessPathStartsWithIndex,
AccessPathStartsWithSlice,
AnonValueAssignment,
ConstantReassignment(String),
ConstantRedefinition(String),
DuplicateParameter(String),
DynamicKeyBlockMultiElement,
EmptyCondition,
EmptyFunctionBody(String),
ExpectedToken(String),
FallibleOptionalArgAccess(String),
FileNotFound(String),
FileSystemError(String),
FloatLiteralOutOfRange,
IntegerLiteralOutOfRange,
InvalidEscapeChar(char),
InvalidEscapeCodePoint(String),
InvalidHint,
InvalidHintOn(&'static str),
InvalidIdentifier(String),
InvalidKeyword(String),
InvalidParameter(String),
InvalidParamOrder(String, String),
InvalidRequireArgumentToken,
InvalidShorthandVariable,
InvalidSink,
InvalidSinkOn(&'static str),
InvalidSliceBound(String),
MissingFunctionBody,
MissingIdentifier,
MissingLeftOperand,
MissingOperand,
MissingRequireArgument,
MissingRightOperand,
MultipleVariadicParams,
NestedFunctionDefMarkedConstant,
NothingToPipe,
UnclosedAccessor,
UnclosedBlock,
UnclosedCondition,
UnclosedFunctionBody,
UnclosedFunctionCall,
UnclosedFunctionSignature,
UnclosedList,
UnclosedMap,
UnclosedParens,
UnclosedStringLiteral,
UnclosedTuple,
UnexpectedToken(String),
UnusedFunction(String),
UnusedParameter(String),
UnusedVariable(String),
WeightNotAllowed,
}
macro_rules! rmsg {
($msg:literal) => {
$msg.to_string()
};
($format:literal, $($args:expr),+) => {
format!($format, $($args,)+)
};
}
impl Problem {
fn code(&self) -> &'static str {
macro_rules! rcode {
($code:literal) => {
concat!("R", stringify!($code))
}
}
match self {
Self::UnexpectedToken(_) => rcode!(0000),
Self::ExpectedToken(_) => rcode!(0001),
Self::IntegerLiteralOutOfRange => rcode!(0002),
Self::FloatLiteralOutOfRange => rcode!(0003),
Self::InvalidEscapeChar(_) => rcode!(0004),
Self::InvalidEscapeCodePoint(_) => rcode!(0005),
Self::UnclosedBlock => rcode!(0006),
Self::UnclosedFunctionCall => rcode!(0007),
Self::UnclosedFunctionSignature => rcode!(0008),
Self::UnclosedAccessor => rcode!(0009),
Self::UnclosedStringLiteral => rcode!(0010),
Self::UnclosedList => rcode!(0011),
Self::UnclosedMap => rcode!(0012),
Self::UnclosedTuple => rcode!(0013),
Self::UnclosedParens => rcode!(0014),
Self::UnclosedCondition => rcode!(0015),
Self::InvalidParamOrder(..) => rcode!(0021),
Self::MissingFunctionBody => rcode!(0022),
Self::UnclosedFunctionBody => rcode!(0023),
Self::InvalidParameter(_) => rcode!(0024),
Self::DuplicateParameter(_) => rcode!(0025),
Self::MultipleVariadicParams => rcode!(0026),
Self::TemporalAssignPipeRedefinesVariable(_) => rcode!(0027),
Self::TemporalAssignPipeRedefinesConstant(_) => rcode!(0028),
Self::DynamicKeyBlockMultiElement => rcode!(0040),
Self::AnonValueAssignment => rcode!(0060),
Self::MissingIdentifier => rcode!(0061),
Self::InvalidIdentifier(_) => rcode!(0062),
Self::AccessPathStartsWithIndex => rcode!(0063),
Self::AccessPathStartsWithSlice => rcode!(0064),
Self::InvalidSliceBound(_) => rcode!(0065),
Self::NothingToPipe => rcode!(0066),
Self::FallibleOptionalArgAccess(_) => rcode!(0067),
Self::InvalidShorthandVariable => rcode!(0068),
Self::ConstantReassignment(_) => rcode!(0100),
Self::ConstantRedefinition(_) => rcode!(0101),
Self::InvalidSink | Self::InvalidSinkOn(_) => rcode!(0130),
Self::InvalidHint | Self::InvalidHintOn(_) => rcode!(0131),
Self::InvalidKeyword(_) => rcode!(0200),
Self::WeightNotAllowed => rcode!(0201),
Self::MissingRequireArgument => rcode!(0202),
Self::InvalidRequireArgumentToken => rcode!(0203),
Self::EmptyCondition => rcode!(0204),
Self::MissingOperand => rcode!(0250),
Self::MissingLeftOperand => rcode!(0251),
Self::MissingRightOperand => rcode!(0252),
Self::UnusedVariable(_) => rcode!(1000),
Self::UnusedParameter(_) => rcode!(1002),
Self::UnusedFunction(_) => rcode!(1003),
Self::EmptyFunctionBody(_) => rcode!(1004),
Self::NestedFunctionDefMarkedConstant => rcode!(1005),
Self::FileNotFound(_) => rcode!(2100),
Self::FileSystemError(_) => rcode!(2101),
}
}
fn message(&self) -> String {
match self {
Self::AccessPathStartsWithIndex => rmsg!("access paths cannot start with an index; consider using a variable or anonymous value here instead"),
Self::AccessPathStartsWithSlice => rmsg!("access paths cannot start with a slice; consider using a variable or anonymous value here instead"),
Self::AnonValueAssignment => rmsg!("can't assign a value to an expression; try assigning to a child of it instead"),
Self::ConstantReassignment(cname) => rmsg!("reassignment of known constant '{}'", cname),
Self::ConstantRedefinition(cname) => rmsg!("redefinition of known constant '{}'", cname),
Self::DuplicateParameter(pname) => rmsg!("duplicate parameter '{}' in function signature", pname),
Self::DynamicKeyBlockMultiElement => rmsg!("dynamic key blocks can't have more than one element; if branching is desired, create an inner block"),
Self::EmptyCondition => rmsg!("condition cannot be empty"),
Self::EmptyFunctionBody(fname) => rmsg!("function '{}' is empty", fname),
Self::ExpectedToken(token) => rmsg!("expected token: '{}'", token),
Self::FallibleOptionalArgAccess(argname) => rmsg!("access to optional argument '{}' can fail; add a fallback to the accessor or specify a default argument", argname),
Self::FileNotFound(file) => rmsg!("file not found: '{}'", file),
Self::FileSystemError(err) => rmsg!("filesystem error: {}", err),
Self::FloatLiteralOutOfRange => rmsg!("float literal is out of range for the `float` type; consider changing it (or if applicable, using a string instead)"),
Self::IntegerLiteralOutOfRange => rmsg!("integer literal is out of range for the `int` type; consider changing it (or if applicable, using a string instead)"),
Self::InvalidEscapeChar(c) => rmsg!("invalid escape character: '{}'", c),
Self::InvalidEscapeCodePoint(cp) => rmsg!("invalid code point in unicode escape: '{}'", cp),
Self::InvalidHint => rmsg!("hint is not valid"),
Self::InvalidHintOn(elname) => rmsg!("hint is not valid on {}", elname),
Self::InvalidIdentifier(idname) => rmsg!("'{}' is not a valid identifier; identifiers may only use alphanumerics, underscores, and hyphens (but cannot be only digits)", idname),
Self::InvalidKeyword(kw) => rmsg!("invalid keyword: '@{}'", kw),
Self::InvalidParameter(pname) => rmsg!("invalid parameter '{}'; must be a valid identifier or '*'", pname),
Self::InvalidParamOrder(first, second) => rmsg!("{} is not allowed after {}", second, first),
Self::InvalidRequireArgumentToken => rmsg!("@require path should be a string literal"),
Self::InvalidShorthandVariable => rmsg!("invalid shorthand; only variable getters are supported"),
Self::InvalidSink => rmsg!("sink is not valid"),
Self::InvalidSinkOn(elname) => rmsg!("sink is not valid on {}", elname),
Self::InvalidSliceBound(token) => rmsg!("invalid slice bound: '{}'", token),
Self::MissingFunctionBody => rmsg!("missing body in function definition"),
Self::MissingIdentifier => rmsg!("identifier required but is missing"),
Self::MissingLeftOperand => rmsg!("expected left-hand operand"),
Self::MissingOperand => rmsg!("expected operand"),
Self::MissingRequireArgument => rmsg!("missing argument for @require"),
Self::MissingRightOperand => rmsg!("expected right-hand operand"),
Self::MultipleVariadicParams => rmsg!("multiple variadic parameters are not allowed"),
Self::NestedFunctionDefMarkedConstant => rmsg!("nested function definition can't be made constant; function will be mutable"),
Self::NothingToPipe => rmsg!("no pipe value is available in this scope"),
Self::UnclosedAccessor => rmsg!("unclosed accessor; expected '>'"),
Self::UnclosedBlock => rmsg!("unclosed block; expected '}'"),
Self::UnclosedCondition => rmsg!("unclosed condition; expected ':'"),
Self::UnclosedFunctionBody => rmsg!("unclosed function body; expected '}}'"),
Self::UnclosedFunctionCall => rmsg!("unclosed function call; expected ']'"),
Self::UnclosedFunctionSignature => rmsg!("unclosed function signature; expected ']' followed by body block"),
Self::UnclosedList => rmsg!("unclosed list initializer; expected ')'"),
Self::UnclosedMap => rmsg!("unclosed map initializer; expected ')'"),
Self::UnclosedParens => rmsg!("unclosed parenthetical; expected ')'"),
Self::UnclosedStringLiteral => rmsg!("unclosed string literal"),
Self::UnclosedTuple => rmsg!("unclosed tuple; expected ')'"),
Self::UnexpectedToken(token) => rmsg!("unexpected token: '{}'", token),
Self::UnusedFunction(fname) => rmsg!("function '{}' is defined but never used", fname),
Self::UnusedParameter(pname) => rmsg!("parameter '{}' is never used", pname),
Self::UnusedVariable(vname) => rmsg!("variable '{}' is defined but never used", vname),
Self::WeightNotAllowed => rmsg!("@weight is not allowed in this context"),
Self::TemporalAssignPipeRedefinesVariable(vname) => rmsg!("temporal assignment pipe could redefine variable '{}'", vname),
Self::TemporalAssignPipeRedefinesConstant(vname) => rmsg!("temporal assignment pipe could redefine constant '{}'", vname),
}
}
fn inline_message(&self) -> Option<String> {
Some(match self {
Self::AccessPathStartsWithIndex => rmsg!("nothing to index"),
Self::AccessPathStartsWithSlice => rmsg!("nothing to slice"),
Self::DuplicateParameter(_) => rmsg!("rename parameter to something unique"),
Self::DynamicKeyBlockMultiElement => rmsg!("multiple elements not allowed here"),
Self::ExpectedToken(token) => rmsg!("expected '{}'", token),
Self::InvalidHint | Self::InvalidHintOn(_) => rmsg!("hint not allowed here"),
Self::InvalidIdentifier(_) => rmsg!("invalid identifier"),
Self::InvalidParameter(_) => rmsg!("invalid parameter"),
Self::InvalidParamOrder(_, second) => rmsg!("{} is not valid in this position", second),
Self::InvalidSink | Self::InvalidSinkOn(_) => rmsg!("sink not allowed here"),
Self::MissingFunctionBody => rmsg!("function body should follow"),
Self::MissingIdentifier => rmsg!("missing identifier"),
Self::MultipleVariadicParams => rmsg!("remove extra variadic parameter"),
Self::NestedFunctionDefMarkedConstant => rmsg!("use '$' here instead"),
Self::NothingToPipe => rmsg!("no previous output to consume"),
Self::UnclosedAccessor => rmsg!("no matching '>' found"),
Self::UnclosedBlock => rmsg!("no matching '}' found"),
Self::UnclosedFunctionBody => rmsg!("no matching '}' found"),
Self::UnclosedFunctionCall => rmsg!("no matching ']' found"),
Self::UnclosedFunctionSignature => rmsg!("no matching ']' found"),
Self::UnclosedList => rmsg!("no matching ')' found"),
Self::UnclosedMap => rmsg!("no matching ')' found"),
Self::UnclosedStringLiteral => rmsg!("string literal needs closing delimiter"),
_ => return None
})
}
fn hint(&self) -> Option<String> {
None
}
}
impl Display for Problem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message())
}
}