use std::path::PathBuf;
use crate::common::{primary_label, secondary_label, Diagnostic, InternalError, BUG_URL};
use crate::common::{FileId, Span};
use codespan_reporting::diagnostic::{Diagnostic as CsDiagnostic, Label};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirectiveKind {
Input,
Output,
PrintSize,
}
impl std::fmt::Display for DirectiveKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
DirectiveKind::Input => ".input",
DirectiveKind::Output => ".output",
DirectiveKind::PrintSize => ".printsize",
})
}
}
fn dup_labels(span: Span, prior: Span, here: &str, first: &str) -> Vec<Label<FileId>> {
[
primary_label(span).map(|l| l.with_message(here)),
secondary_label(prior).map(|l| l.with_message(first)),
]
.into_iter()
.flatten()
.collect()
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ParseError {
#[error("syntax error: {message}")]
Syntax { span: Span, message: String },
#[error("duplicate declaration of relation `{name}`")]
DuplicateDecl {
span: Span,
prior: Span,
name: String,
},
#[error("duplicate attribute `{name}` in relation `{relation}`")]
DuplicateAttribute {
span: Span,
prior: Span,
relation: String,
name: String,
},
#[error("duplicate {kind} directive for relation `{name}`")]
DuplicateDirective {
span: Span,
prior: Span,
kind: DirectiveKind,
name: String,
},
#[error("{kind} directive references undeclared relation `{name}`")]
UndeclaredInDirective {
span: Span,
kind: DirectiveKind,
name: String,
},
#[error("iterative list references undeclared relation `{name}`")]
UndeclaredInIterativeList { span: Span, name: String },
#[error("loop condition references undeclared relation `{name}`")]
UndeclaredLoopCondition { span: Span, name: String },
#[error("rule references undeclared relation `{name}`")]
UndeclaredInRule { span: Span, name: String },
#[error("fact references undeclared relation `{name}`")]
UndeclaredInFact { span: Span, name: String },
#[error("`loop`/`fixpoint` blocks require `--mode extend-batch` or `extend-inc`")]
LoopBlockInStandardMode { span: Span },
#[error(
"loop condition relation `{name}` must be nullary, but is declared with arity {arity}"
)]
NonNullaryLoopCondition {
span: Span,
name: String,
arity: usize,
},
#[error("`_` placeholder is not allowed in arguments to UDF `{udf_name}`")]
PlaceholderInUdf { span: Span, udf_name: String },
#[error("failed to read included file `{}`: {source}", path.display())]
IncludeIo {
span: Span,
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("circular include of `{}`", path.display())]
CircularInclude {
span: Span,
path: PathBuf,
chain: Vec<PathBuf>,
},
#[error(transparent)]
Internal(#[from] InternalError),
}
impl ParseError {
pub(crate) fn syntax_from_pest(
err: &pest::error::Error<crate::parser::Rule>,
file: FileId,
) -> Self {
use pest::error::InputLocation;
let (start, end) = match err.location {
InputLocation::Pos(p) => (p as u32, p as u32),
InputLocation::Span((s, e)) => (s as u32, e as u32),
};
ParseError::Syntax {
span: Span::new(file, start, end),
message: err.variant.message().into_owned(),
}
}
}
impl Diagnostic for ParseError {
fn to_diagnostic(&self) -> CsDiagnostic<FileId> {
if let ParseError::Internal(e) = self {
return e.to_diagnostic();
}
let base = CsDiagnostic::error().with_message(self.to_string());
match self {
ParseError::DuplicateDecl { span, prior, .. } => {
base.with_labels(dup_labels(*span, *prior, "redeclared here", "first declared here"))
}
ParseError::DuplicateDirective { span, prior, .. } => base.with_labels(dup_labels(
*span,
*prior,
"duplicate directive",
"first directive here",
)),
ParseError::DuplicateAttribute { span, prior, .. } => base.with_labels(dup_labels(
*span,
*prior,
"duplicate attribute here",
"first declared here",
)),
ParseError::UndeclaredInDirective { span, name, .. } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![format!(
"add a `.decl {name}(...)` before this directive"
)]),
ParseError::UndeclaredInIterativeList { span, name } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![format!(
"either `.decl {name}(...)` it, or drop `{name}` from the iterative list"
)]),
ParseError::UndeclaredLoopCondition { span, name } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![format!(
"declare `{name}` as a nullary relation with `.decl {name}()` and derive it inside the loop"
)]),
ParseError::UndeclaredInRule { span, name }
| ParseError::UndeclaredInFact { span, name } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![format!(
"add a matching `.decl {name}(...)` declaration, or remove the reference"
)]),
ParseError::CircularInclude { span, chain, .. } => {
let mut diag = base.with_labels(primary_label(*span).into_iter().collect());
if !chain.is_empty() {
let shown: Vec<String> = chain.iter().map(|p| p.display().to_string()).collect();
diag = diag.with_notes(vec![format!("include chain: {}", shown.join(" → "))]);
}
diag
}
ParseError::Syntax { span, .. }
| ParseError::LoopBlockInStandardMode { span }
| ParseError::NonNullaryLoopCondition { span, .. }
| ParseError::PlaceholderInUdf { span, .. }
| ParseError::IncludeIo { span, .. } => {
base.with_labels(primary_label(*span).into_iter().collect())
}
ParseError::Internal(_) => unreachable!("handled above"),
}
}
fn is_internal(&self) -> bool {
matches!(self, ParseError::Internal(_))
}
}
pub(crate) fn grammar_bug(detail: impl Into<String>) -> ParseError {
ParseError::Internal(InternalError::new("parser", detail, BUG_URL))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{emit, BoxError};
use crate::common::SourceMap;
fn make_sm_with(text: &str) -> (SourceMap, FileId) {
let mut sm = SourceMap::new();
let f = sm.add("t.dl".into(), text.into());
(sm, f)
}
fn render(err: ParseError, sm: &SourceMap) -> String {
let err: BoxError = err.into();
let mut buf: Vec<u8> = Vec::new();
emit(&err, sm, &mut buf).unwrap();
String::from_utf8(buf).unwrap()
}
#[test]
fn duplicate_decl_labels_both_sites() {
let (sm, f) = make_sm_with(".decl Foo(x: int)\n.decl Foo(y: int)\n");
let out = render(
ParseError::DuplicateDecl {
span: Span::new(f, 24, 27),
prior: Span::new(f, 6, 9),
name: "Foo".into(),
},
&sm,
);
assert!(out.contains("duplicate declaration"), "got: {out}");
assert!(out.contains("redeclared here"), "got: {out}");
assert!(out.contains("first declared here"), "got: {out}");
}
#[test]
fn undeclared_in_directive_includes_help_note() {
let (sm, f) = make_sm_with(".input Bar(filename=\"b.csv\")\n");
let out = render(
ParseError::UndeclaredInDirective {
span: Span::new(f, 7, 10),
kind: DirectiveKind::Input,
name: "Bar".into(),
},
&sm,
);
assert!(out.contains(".input"), "got: {out}");
assert!(out.contains("undeclared"), "got: {out}");
assert!(out.contains("add a `.decl Bar"), "got: {out}");
}
#[test]
fn internal_variant_renders_bug_note() {
let (sm, _) = make_sm_with("");
let out = render(grammar_bug("ghosts in the AST"), &sm);
assert!(out.contains("bug"), "got: {out}");
assert!(out.contains("ghosts in the AST"), "got: {out}");
assert!(out.contains(BUG_URL), "got: {out}");
}
}