diff --git a/crates/decodal-cli/src/main.rs b/crates/decodal-cli/src/main.rs index 024e946..9809ba1 100644 --- a/crates/decodal-cli/src/main.rs +++ b/crates/decodal-cli/src/main.rs @@ -4,19 +4,22 @@ use std::{ process::ExitCode, }; -use decodal_core::{Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceLoader, Span}; +use decodal_core::{ + Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceId, SourceLoader, Span, + format_diagnostic_with, +}; fn main() -> ExitCode { match run() { Ok(()) => ExitCode::SUCCESS, Err(error) => { - print_diagnostic(&error); + eprintln!("{error}"); ExitCode::FAILURE } } } -fn run() -> Result<(), Diagnostic> { +fn run() -> Result<(), String> { let mut args = env::args().skip(1); let first = args.next().unwrap_or_else(|| String::from("--help")); match first.as_str() { @@ -51,12 +54,19 @@ fn run() -> Result<(), Diagnostic> { } } -fn materialize_path(path: &str) -> Result { - let root = read_root_source(path)?; +fn materialize_path(path: &str) -> Result { + let root = read_root_source(path).map_err(format_raw_diagnostic)?; + let root_name = root.name.clone(); let mut engine = Engine::new(FsLoader); - let module = engine.add_root_source(root.key, root.name, &root.source)?; - let value = engine.eval_module(module)?; - engine.materialize(&value) + let module = engine + .add_root_source(root.key, root.name, &root.source) + .map_err(|error| format_diagnostic_with_root(&error, &root_name))?; + let value = engine + .eval_module(module) + .map_err(|error| engine.format_diagnostic(&error))?; + engine + .materialize(&value) + .map_err(|error| engine.format_diagnostic(&error)) } fn read_root_source(path: &str) -> Result { @@ -139,24 +149,12 @@ fn print_help() { println!(" decodal - Read DCDL source from stdin"); } -fn print_diagnostic(error: &Diagnostic) { - eprintln!( - "error[{kind:?}] {source}:{start}..{end}: {message}", - kind = error.kind, - source = error.span.source.0, - start = error.span.start, - end = error.span.end, - message = error.message, - ); - for label in &error.labels { - eprintln!( - " note {source}:{start}..{end}: {message}", - source = label.span.source.0, - start = label.span.start, - end = label.span.end, - message = label.message, - ); - } +fn format_diagnostic_with_root(error: &Diagnostic, root_name: &str) -> String { + format_diagnostic_with(error, |source| (source == SourceId(0)).then_some(root_name)) +} + +fn format_raw_diagnostic(error: Diagnostic) -> String { + format_diagnostic_with(&error, |_| None) } fn print_data(data: &Data, indent: usize) { diff --git a/crates/decodal-core/src/eval.rs b/crates/decodal-core/src/eval.rs index 6220ad6..dc52280 100644 --- a/crates/decodal-core/src/eval.rs +++ b/crates/decodal-core/src/eval.rs @@ -87,6 +87,17 @@ impl Engine { self.materialize_with_path(value, &mut Vec::new()) } + pub fn source_name(&self, source: SourceId) -> Option<&str> { + self.modules + .iter() + .find(|module| module.source == source) + .map(|module| module.name.as_str()) + } + + pub fn format_diagnostic(&self, diagnostic: &Diagnostic) -> String { + format_diagnostic_with(diagnostic, |source| self.source_name(source)) + } + fn materialize_with_path( &mut self, value: &RuntimeValue, @@ -1163,6 +1174,39 @@ fn primitive_type(name: &str) -> Option { } } +pub fn format_diagnostic_with<'a>( + diagnostic: &Diagnostic, + mut source_name: impl FnMut(SourceId) -> Option<&'a str>, +) -> String { + let mut out = format!( + "{:?} at {}:{}..{}: {}", + diagnostic.kind, + format_source(diagnostic.span.source, &mut source_name), + diagnostic.span.start, + diagnostic.span.end, + diagnostic.message, + ); + for label in &diagnostic.labels { + out.push_str(&format!( + "\nnote at {}:{}..{}: {}", + format_source(label.span.source, &mut source_name), + label.span.start, + label.span.end, + label.message, + )); + } + out +} + +fn format_source<'a>( + source: SourceId, + source_name: &mut impl FnMut(SourceId) -> Option<&'a str>, +) -> String { + source_name(source) + .map(String::from) + .unwrap_or_else(|| format!("source {}", source.0)) +} + fn constraint_primary_span(constraints: &[ConstraintEntry]) -> Span { constraints .first() diff --git a/crates/decodal-core/src/lib.rs b/crates/decodal-core/src/lib.rs index 25504ab..b81c058 100644 --- a/crates/decodal-core/src/lib.rs +++ b/crates/decodal-core/src/lib.rs @@ -17,7 +17,7 @@ pub use ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, Param}; pub use constraints::normalize_constraints; pub use diagnostic::{Diagnostic, DiagnosticKind, Result}; pub use embedding::{HostField, HostValue}; -pub use eval::Engine; +pub use eval::{Engine, format_diagnostic_with}; pub use lexer::{Lexer, Token, TokenKind}; pub use module::{EmptyLoader, LoadedSource, Module, SourceLoader}; pub use parser::{ParseOutput, Parser, SourceForm, parse_source, parse_source_with_source_id}; diff --git a/crates/decodal-wasm/src/lib.rs b/crates/decodal-wasm/src/lib.rs index a67d12f..bf465d3 100644 --- a/crates/decodal-wasm/src/lib.rs +++ b/crates/decodal-wasm/src/lib.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; use decodal_core::{ - Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceLoader, Span, + Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceId, SourceLoader, + Span, format_diagnostic_with, }; use wasm_bindgen::prelude::*; @@ -24,11 +25,18 @@ fn encode_result(result: Result) -> String { fn evaluate_inner(source: &str) -> Result { let mut engine = Engine::new(EmptyLoader); - let module = engine - .add_root_source("playground", "playground", source) - .map_err(format_diagnostic)?; - let value = engine.eval_module(module).map_err(format_diagnostic)?; - let data = engine.materialize(&value).map_err(format_diagnostic)?; + let module = match engine.add_root_source("playground", "playground", source) { + Ok(module) => module, + Err(error) => return Err(format_diagnostic_with_root(&error, "playground")), + }; + let value = match engine.eval_module(module) { + Ok(value) => value, + Err(error) => return Err(engine.format_diagnostic(&error)), + }; + let data = match engine.materialize(&value) { + Ok(data) => data, + Err(error) => return Err(engine.format_diagnostic(&error)), + }; Ok(format_data(&data, 0)) } @@ -48,11 +56,18 @@ fn evaluate_project_inner(entry: &str, files_json: &str) -> Result module, + Err(error) => return Err(format_diagnostic_with_root(&error, &entry)), + }; + let value = match engine.eval_module(module) { + Ok(value) => value, + Err(error) => return Err(engine.format_diagnostic(&error)), + }; + let data = match engine.materialize(&value) { + Ok(data) => data, + Err(error) => return Err(engine.format_diagnostic(&error)), + }; Ok(format_data(&data, 0)) } @@ -124,22 +139,10 @@ fn normalize_path(path: &str) -> Option { } } -fn format_diagnostic(diagnostic: decodal_core::Diagnostic) -> String { - let mut out = format!( - "{:?} at {}:{}..{}: {}", - diagnostic.kind, - diagnostic.span.source.0, - diagnostic.span.start, - diagnostic.span.end, - diagnostic.message, - ); - for label in diagnostic.labels { - out.push_str(&format!( - "\nnote at {}:{}..{}: {}", - label.span.source.0, label.span.start, label.span.end, label.message, - )); - } - out +fn format_diagnostic_with_root(diagnostic: &decodal_core::Diagnostic, root_name: &str) -> String { + format_diagnostic_with(diagnostic, |source| { + (source == SourceId(0)).then_some(root_name) + }) } fn format_data(data: &Data, indent: usize) -> String { @@ -237,4 +240,17 @@ mod tests { let output = evaluate_project_inner("main.dcdl", files).unwrap(); assert!(output.contains("\"port\": 9443")); } + + #[test] + fn project_diagnostics_use_virtual_file_names() { + let files = r#"{ + "main.dcdl":"let dep = import \"./schemas/service.dcdl\"; in dep.Service & { port = 80; }", + "schemas/service.dcdl":"Service = { port = Int & > 443 default 8443; }" + }"#; + let error = evaluate_project_inner("main.dcdl", files).unwrap_err(); + assert!(error.contains("main.dcdl:")); + assert!(error.contains("schemas/service.dcdl:")); + assert!(!error.contains("source 0:")); + assert!(!error.contains("source 1:")); + } } diff --git a/doc/manual/souce/design/diagnostics-and-fallback.md b/doc/manual/souce/design/diagnostics-and-fallback.md index 40323b4..871bbf1 100644 --- a/doc/manual/souce/design/diagnostics-and-fallback.md +++ b/doc/manual/souce/design/diagnostics-and-fallback.md @@ -20,6 +20,7 @@ DiagnosticLabel { ``` `span` は primary location を示す。 +表示時には `Span.source` を source id のまま出すのではなく、可能な限り file path や virtual file name に解決する。 `labels` は同じ error に関係する追加 location を示す。 合成や materialize の失敗では、衝突した constraint、value、default、または処理中 field path を label に含める。 diff --git a/site/decodal-site/src/wasm/decodal_wasm_bg.wasm b/site/decodal-site/src/wasm/decodal_wasm_bg.wasm index 25635b8..7387400 100644 Binary files a/site/decodal-site/src/wasm/decodal_wasm_bg.wasm and b/site/decodal-site/src/wasm/decodal_wasm_bg.wasm differ