diff --git a/CLAUDE.md b/CLAUDE.md index 10f11256..7fcbb83e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,7 @@ Source → [Lexer] → Tokens → [Parser] → AST → [Analyser] → Annotated ### Git Workflow - Prefer short commit messages, only use multiple lines in case of unrelated changes - Pull request titles must start with an emoji +- Branch names use category prefixes: `feature/`, `bugfix/`, `housekeeping/`, etc. ### Crate Layout diff --git a/Cargo.lock b/Cargo.lock index b0d646b6..9e46ea0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.8.12" @@ -138,30 +123,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "backtrace-ext" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" -dependencies = [ - "backtrace", -] - [[package]] name = "benches" version = "0.0.0" @@ -321,6 +282,16 @@ dependencies = [ "error-code", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -788,12 +759,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "half" version = "2.7.1" @@ -990,12 +955,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "is_ci" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1129,36 +1088,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "backtrace", - "backtrace-ext", - "cfg-if", - "miette-derive", - "owo-colors", - "supports-color 3.0.2", - "supports-hyperlinks", - "supports-unicode", - "terminal_size", - "textwrap", - "unicode-width 0.1.14", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "minimad" version = "0.14.0" @@ -1168,15 +1097,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.1.1" @@ -1206,14 +1126,13 @@ version = "0.3.0" dependencies = [ "anyhow", "clap", + "codespan-reporting", "itertools 0.14.0", - "miette", "ndc_core", "ndc_interpreter", "ndc_lexer", "ndc_lsp", "ndc_stdlib", - "owo-colors", "rustyline", "strsim", "termimad", @@ -1412,15 +1331,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1442,16 +1352,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "owo-colors" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" -dependencies = [ - "supports-color 2.1.0", - "supports-color 3.0.2", -] - [[package]] name = "page_size" version = "0.6.0" @@ -1724,12 +1624,6 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1961,37 +1855,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "supports-color" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" -dependencies = [ - "is-terminal", - "is_ci", -] - -[[package]] -name = "supports-color" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" -dependencies = [ - "is_ci", -] - -[[package]] -name = "supports-hyperlinks" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" - -[[package]] -name = "supports-unicode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" - [[package]] name = "syn" version = "2.0.117" @@ -2020,6 +1883,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termimad" version = "0.34.1" @@ -2036,16 +1908,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "terminal_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" -dependencies = [ - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "tests" version = "0.3.0" @@ -2055,16 +1917,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" -dependencies = [ - "unicode-linebreak", - "unicode-width 0.2.2", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -2249,12 +2101,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - [[package]] name = "unicode-segmentation" version = "1.12.0" diff --git a/Cargo.toml b/Cargo.toml index 4244322e..447c0c3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ ndc_stdlib = { path = "ndc_stdlib" } num = "0.4.3" once_cell = "1.21.3" ordered-float = "5.1.0" -owo-colors = { version = "4.3.0", features = ["supports-colors"] } yansi = { version = "1.0.1", features = ["detect-tty", "detect-env"] } rand = "0.10.0" rand_chacha = "0.10.0" diff --git a/ndc_bin/Cargo.toml b/ndc_bin/Cargo.toml index 4398dc77..1d589452 100644 --- a/ndc_bin/Cargo.toml +++ b/ndc_bin/Cargo.toml @@ -13,13 +13,12 @@ anyhow.workspace = true clap.workspace = true itertools.workspace = true strsim.workspace = true -miette = { version = "7.6.0", features = ["fancy"] } +codespan-reporting = "0.11.1" ndc_lexer.workspace = true ndc_interpreter.workspace = true ndc_stdlib.workspace = true ndc_core.workspace = true ndc_lsp.workspace = true -owo-colors.workspace = true yansi.workspace = true rustyline.workspace = true termimad = "0.34.1" diff --git a/ndc_bin/src/diagnostic.rs b/ndc_bin/src/diagnostic.rs index 89375a4b..05efe767 100644 --- a/ndc_bin/src/diagnostic.rs +++ b/ndc_bin/src/diagnostic.rs @@ -1,80 +1,70 @@ -use miette::{Diagnostic, LabeledSpan, SourceSpan}; +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use codespan_reporting::files::SimpleFile; +use codespan_reporting::term; +use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use ndc_interpreter::InterpreterError; use ndc_lexer::Span; -use std::fmt; -fn span_to_source_span(span: Span) -> SourceSpan { - (span.offset(), span.end() - span.offset()).into() +fn span_to_range(span: Span) -> std::ops::Range { + span.offset()..span.end() } -pub struct NdcReport { - message: String, - span: Option, - label: &'static str, - help: Option, -} - -impl fmt::Display for NdcReport { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.message) - } -} - -impl fmt::Debug for NdcReport { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl std::error::Error for NdcReport {} - -impl Diagnostic for NdcReport { - fn help<'a>(&'a self) -> Option> { - self.help - .as_ref() - .map(|h| -> Box { Box::new(h) }) - } - - fn labels(&self) -> Option + '_>> { - self.span.map(|s| -> Box> { - Box::new(std::iter::once(LabeledSpan::at(s, self.label))) - }) +fn into_diagnostic(err: InterpreterError) -> Diagnostic<()> { + match err { + InterpreterError::Lexer { cause } => { + let mut d = Diagnostic::error() + .with_code("lexer") + .with_message(cause.to_string()) + .with_labels(vec![ + Label::primary((), span_to_range(cause.span())).with_message("here"), + ]); + if let Some(help) = cause.help_text() { + d = d.with_notes(vec![help.to_owned()]); + } + d + } + InterpreterError::Parser { cause } => { + let mut d = Diagnostic::error() + .with_code("parser") + .with_message(cause.to_string()) + .with_labels(vec![ + Label::primary((), span_to_range(cause.span())).with_message("here"), + ]); + if let Some(help) = cause.help_text() { + d = d.with_notes(vec![help.to_owned()]); + } + d + } + InterpreterError::Resolver { cause } => Diagnostic::error() + .with_code("resolver") + .with_message(cause.to_string()) + .with_labels(vec![ + Label::primary((), span_to_range(cause.span())).with_message("related to this"), + ]), + InterpreterError::Compiler { cause } => Diagnostic::error() + .with_code("compiler") + .with_message(cause.to_string()) + .with_labels(vec![ + Label::primary((), span_to_range(cause.span())).with_message("related to this"), + ]), + InterpreterError::Vm(err) => { + let mut d = Diagnostic::error() + .with_code("vm") + .with_message(&err.message); + if let Some(span) = err.span { + d = d.with_labels(vec![ + Label::primary((), span_to_range(span)).with_message("related to this"), + ]); + } + d + } } } -impl From for NdcReport { - fn from(err: InterpreterError) -> Self { - match err { - InterpreterError::Lexer { cause } => Self { - message: format!("LexerError: {}", cause), - span: Some(span_to_source_span(cause.span())), - label: "here", - help: cause.help_text().map(str::to_owned), - }, - InterpreterError::Parser { cause } => Self { - message: format!("ParserError: {}", cause), - span: Some(span_to_source_span(cause.span())), - label: "here", - help: cause.help_text().map(str::to_owned), - }, - InterpreterError::Resolver { cause } => Self { - message: format!("ResolverError: {}", cause), - span: Some(span_to_source_span(cause.span())), - label: "related to this", - help: None, - }, - InterpreterError::Compiler { cause } => Self { - message: format!("CompilerError: {}", cause), - span: Some(span_to_source_span(cause.span())), - label: "related to this", - help: None, - }, - InterpreterError::Vm(err) => Self { - message: format!("VMError: {}", err.message), - span: err.span.map(span_to_source_span), - label: "related to this", - help: None, - }, - } - } +pub fn emit_error(filename: &str, source: &str, err: InterpreterError) { + let diagnostic = into_diagnostic(err); + let file = SimpleFile::new(filename, source); + let writer = StandardStream::stderr(ColorChoice::Auto); + let config = term::Config::default(); + let _ = term::emit(&mut writer.lock(), &config, &file, &diagnostic); } diff --git a/ndc_bin/src/highlighter.rs b/ndc_bin/src/highlighter.rs index f374f95d..b9098024 100644 --- a/ndc_bin/src/highlighter.rs +++ b/ndc_bin/src/highlighter.rs @@ -1,37 +1,13 @@ use itertools::Itertools; -use miette::{ - SpanContents, - highlighters::{Highlighter, HighlighterState}, -}; use ndc_lexer::{Lexer, Token}; -use owo_colors::{Rgb, Style, Styled}; +use yansi::{Paint, Painted}; -#[derive(Default)] -pub struct AndycppHighlighter {} +pub(crate) struct AndycppHighlighter; -impl Highlighter for AndycppHighlighter { - fn start_highlighter_state<'h>( - &'h self, - _source: &dyn SpanContents<'_>, - ) -> Box { - Box::new(AndycppHighlighterState {}) - } -} - -pub(crate) struct AndycppHighlighterState {} - -const NUMBER_LITERAL_COLOR: Rgb = Rgb(253, 151, 31); -const PARENTHESES_COLOR: Rgb = Rgb(229, 181, 103); - -const LITERAL_COLOR: Rgb = Rgb(140, 182, 255); -const STRING_LITERAL_COLOR: Rgb = Rgb(70, 200, 128); -// 33b1ff -const IDENTIFIER_COLOR: Rgb = Rgb(51, 177, 255); - -impl HighlighterState for AndycppHighlighterState { - fn highlight_line<'s>(&mut self, line: &'s str) -> Vec> { +impl AndycppHighlighter { + pub fn highlight_line(line: &str) -> Vec> { let Ok(tokens) = Lexer::new(line).collect::, _>>() else { - return vec![Style::new().red().style(line)]; + return vec![line.red()]; }; let mut start = 0; @@ -50,22 +26,22 @@ impl HighlighterState for AndycppHighlighterState { let substring = &line[range.start..(range.start + range.len())]; let colored = match &token.token { - Token::String(_) => Style::new().color(STRING_LITERAL_COLOR).style(substring), + Token::String(_) => substring.rgb(70, 200, 128), Token::BigInt(_) | Token::Int64(_) | Token::Float64(_) | Token::Complex(_) | Token::True - | Token::False => Style::new().color(NUMBER_LITERAL_COLOR).style(substring), + | Token::False => substring.rgb(253, 151, 31), Token::LeftSquareBracket | Token::RightSquareBracket | Token::LeftCurlyBracket | Token::RightCurlyBracket | Token::LeftParentheses | Token::RightParentheses - | Token::MapOpen => Style::new().color(PARENTHESES_COLOR).style(substring), - Token::Identifier(_) => Style::new().color(IDENTIFIER_COLOR).style(substring), - _ => Style::new().color(LITERAL_COLOR).bold().style(substring), + | Token::MapOpen => substring.rgb(229, 181, 103), + Token::Identifier(_) => substring.rgb(51, 177, 255), + _ => substring.rgb(140, 182, 255).bold(), }; out.push(colored); diff --git a/ndc_bin/src/main.rs b/ndc_bin/src/main.rs index f302a304..d6068a76 100644 --- a/ndc_bin/src/main.rs +++ b/ndc_bin/src/main.rs @@ -3,9 +3,8 @@ use crate::docs::docs; use anyhow::{Context, anyhow}; use clap::{Parser, Subcommand}; -use highlighter::{AndycppHighlighter, AndycppHighlighterState}; -use miette::{NamedSource, highlighters::HighlighterState}; -use ndc_interpreter::{Interpreter, InterpreterError}; +use highlighter::AndycppHighlighter; +use ndc_interpreter::Interpreter; use std::path::PathBuf; use std::process; use std::{fs, io::Write}; @@ -22,9 +21,6 @@ mod highlighter; #[command(version, long_version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), ")"))] #[command(about = "An interpreter for the Andy C++ language")] struct Cli { - #[arg(short = 'C', long, default_value_t = 1)] - context_lines: usize, - #[command(subcommand)] command: Option, } @@ -109,20 +105,6 @@ impl TryFrom for Action { fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let context_lines = cli.context_lines; - - miette::set_hook(Box::new(move |_| { - Box::new( - miette::MietteHandlerOpts::new() - .terminal_links(true) - .color(true) - .unicode(true) - .context_lines(context_lines) - .with_syntax_highlighting(AndycppHighlighter::default()) - .build(), - ) - }))?; - let action: Action = cli.command.unwrap_or_default().try_into()?; match action { @@ -136,27 +118,24 @@ fn main() -> anyhow::Result<()> { let mut interpreter = Interpreter::new(); interpreter.configure(ndc_stdlib::register); - match into_miette_result(interpreter.eval(&string)) { - // we can just ignore successful runs because we have print statements - Ok(_final_value) => {} - Err(report) => { - let source = - NamedSource::new(filename.expect("filename must exist"), string.clone()); - let report = report.with_source_code(source); - eprintln!("{:?}", report); - - process::exit(1); - } + if let Err(err) = interpreter.eval(&string) { + diagnostic::emit_error(&filename.expect("filename must exist"), &string, err); + process::exit(1); } } Action::DisassembleFile(path) => { + let filename = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("") + .to_string(); let string = fs::read_to_string(path)?; let mut interpreter = Interpreter::new(); interpreter.configure(ndc_stdlib::register); match interpreter.disassemble_str(&string) { Ok(output) => print!("{output}"), Err(e) => { - eprintln!("{:?}", miette::Report::new(diagnostic::NdcReport::from(e))); + diagnostic::emit_error(&filename, &string, e); process::exit(1); } } @@ -164,8 +143,7 @@ fn main() -> anyhow::Result<()> { Action::HighlightFile(path) => { let string = fs::read_to_string(path)?; - let mut highlighter = AndycppHighlighterState {}; - let out = highlighter.highlight_line(&string); + let out = AndycppHighlighter::highlight_line(&string); for styled in out { print!("{}", styled); } @@ -196,10 +174,6 @@ fn start_lsp() { } } -pub fn into_miette_result(result: Result) -> miette::Result { - result.map_err(|e| miette::Report::new(diagnostic::NdcReport::from(e))) -} - #[cfg(test)] mod test { use clap::CommandFactory; diff --git a/ndc_bin/src/repl.rs b/ndc_bin/src/repl.rs index 7f8cd98c..817fc222 100644 --- a/ndc_bin/src/repl.rs +++ b/ndc_bin/src/repl.rs @@ -1,6 +1,5 @@ #![allow(clippy::print_stdout, clippy::print_stderr)] use itertools::Itertools; -use miette::highlighters::HighlighterState; use ndc_interpreter::Interpreter; use rustyline::Helper; use rustyline::config::Configurer; @@ -9,16 +8,14 @@ use rustyline::highlight::CmdKind; use rustyline::{ColorMode, Completer, Editor, Hinter, Validator}; use std::borrow::Cow; -use crate::highlighter::AndycppHighlighterState; -use crate::into_miette_result; +use crate::highlighter::AndycppHighlighter; #[derive(Helper, Completer, Hinter, Validator)] -struct RustlylineHelper {} +struct RustylineHelper {} -impl rustyline::highlight::Highlighter for RustlylineHelper { +impl rustyline::highlight::Highlighter for RustylineHelper { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { - let mut state = AndycppHighlighterState {}; - let out = state.highlight_line(line); + let out = AndycppHighlighter::highlight_line(line); Cow::Owned(out.into_iter().join("")) } @@ -29,7 +26,7 @@ impl rustyline::highlight::Highlighter for RustlylineHelper { } pub fn run() -> anyhow::Result<()> { - let h = RustlylineHelper {}; + let h = RustylineHelper {}; let mut rl = Editor::new()?; rl.set_color_mode(ColorMode::Enabled); @@ -44,16 +41,15 @@ pub fn run() -> anyhow::Result<()> { let _ = rl.add_history_entry(line.as_str()); // Run the line we just read through the interpreter - match into_miette_result(interpreter.eval(line.as_str())) { + match interpreter.eval(line.as_str()) { Ok(value) => { let output = value.to_string(); if !output.is_empty() { println!("{output}") } } - Err(report) => { - let report = report.with_source_code(line.clone()); - eprintln!("{report:?}") + Err(err) => { + crate::diagnostic::emit_error("", &line, err); } } }