diff --git a/openvaf/basedb/src/lib.rs b/openvaf/basedb/src/lib.rs index e83830ef..2994b34b 100644 --- a/openvaf/basedb/src/lib.rs +++ b/openvaf/basedb/src/lib.rs @@ -221,6 +221,15 @@ impl SourceProvider for SourceProviderDelegate<'_> { fn file_id(&self, path: VfsPath) -> FileId { self.0.file_id(path) } + + fn allocate_virtual_file(&self, path: &str, contents: Arc) -> FileId { + let path = VfsPath::new_virtual_path(path.to_owned()); + let file_id = self.0.vfs().write().ensure_file_id(path); + self.0.vfs().write().set_file_contents(file_id, contents.to_string().into()); + // Ensure subsequent salsa `file_text` reads see the contents immediately. + // Preprocess already mutates VFS via `file_id` / includes; this matches that pattern. + file_id + } } #[macro_export] diff --git a/openvaf/preprocessor/src/grammar.rs b/openvaf/preprocessor/src/grammar.rs index 97f22a6f..c42f16cb 100644 --- a/openvaf/preprocessor/src/grammar.rs +++ b/openvaf/preprocessor/src/grammar.rs @@ -212,15 +212,21 @@ fn parse_macro_token<'a>( } if p.at(PreprocessorToken::CompilerDirective) { - if p.compiler_directive() == CompilerDirective::Macro { - let (call, range) = parse_macro_call(p, err, args, sm, end); - dst.push(ParsedToken { range, kind: ParsedTokenKind::MacroCall(call) }); - } else { - // TODO nicer error? - err.push(PreprocessorDiagnostic::UnexpectedToken(CtxSpan { - ctx: p.ctx, - range: p.current_range(), - })) + match p.compiler_directive() { + // `` `__FILE__ `` / `` `__LINE__ `` must be stored like macros so they + // expand at the call site of the enclosing `` `define ``. Treating them + // as unexpected without bumping the parser would spin forever. + CompilerDirective::Macro | CompilerDirective::File | CompilerDirective::Line => { + let (call, range) = parse_macro_call(p, err, args, sm, end); + dst.push(ParsedToken { range, kind: ParsedTokenKind::MacroCall(call) }); + } + _ => { + err.push(PreprocessorDiagnostic::UnexpectedToken(CtxSpan { + ctx: p.ctx, + range: p.current_range(), + })); + p.bump(); + } } return; } diff --git a/openvaf/preprocessor/src/lib.rs b/openvaf/preprocessor/src/lib.rs index f9bd4fe2..7a7c4f0c 100644 --- a/openvaf/preprocessor/src/lib.rs +++ b/openvaf/preprocessor/src/lib.rs @@ -71,6 +71,11 @@ pub trait SourceProvider { fn file_text(&self, file: FileId) -> Result, FileReadError>; fn file_path(&self, file: FileId) -> VfsPath; fn file_id(&self, path: VfsPath) -> FileId; + + /// Allocate a virtual file whose contents become the expansion text for a + /// preprocessor-generated token (e.g. `` `__FILE__ `` / `` `__LINE__ ``). + /// Token spans must point at real `FileId` text for the green tree builder. + fn allocate_virtual_file(&self, path: &str, contents: Arc) -> FileId; } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/openvaf/preprocessor/src/parser.rs b/openvaf/preprocessor/src/parser.rs index 707fe630..1c9cb99f 100755 --- a/openvaf/preprocessor/src/parser.rs +++ b/openvaf/preprocessor/src/parser.rs @@ -306,6 +306,9 @@ impl<'a, 'd> Parser<'a, 'd> { "`endif" => CompilerDirective::EndIf, "`undef" => CompilerDirective::Undef, "`resetall" => CompilerDirective::ResetAll, + // VAMS-2023 §10.7 / IEEE 1364: expand to string / decimal literals. + "`__FILE__" => CompilerDirective::File, + "`__LINE__" => CompilerDirective::Line, _ => CompilerDirective::Macro, } } @@ -334,5 +337,9 @@ pub enum CompilerDirective { EndIf, Undef, ResetAll, + /// `` `__FILE__ `` — expands to a string literal of the current input path. + File, + /// `` `__LINE__ `` — expands to a decimal literal of the current line number. + Line, Macro, } diff --git a/openvaf/preprocessor/src/processor.rs b/openvaf/preprocessor/src/processor.rs index a9cb641d..14e26a4e 100755 --- a/openvaf/preprocessor/src/processor.rs +++ b/openvaf/preprocessor/src/processor.rs @@ -6,7 +6,7 @@ use ahash::AHashMap; use stdx::{impl_debug_display, impl_idx_from}; use text_size::{TextRange, TextSize}; use tokens::parser::SyntaxKind; -use tokens::SyntaxKind::{L_PAREN, R_PAREN}; +use tokens::SyntaxKind::{INT_NUMBER, L_PAREN, R_PAREN, STR_LIT}; // use tracing::{debug, debug_span, trace}; use typed_index_collections::{TiSlice, TiVec}; use vfs::{FileId, VfsPath}; @@ -25,6 +25,8 @@ pub(crate) struct Processor<'a> { arena: &'a ScopedTextArea, macros: AHashMap<&'a str, Macro<'a>>, include_dirs: Arc<[VfsPath]>, + /// Monotonic id for virtual expansion files allocated for `` `__FILE__ `` / `` `__LINE__ ``. + expand_seq: u32, } impl<'a> Processor<'a> { @@ -51,6 +53,7 @@ impl<'a> Processor<'a> { arena: storage, sources, include_dirs: sources.include_dirs(root_file), + expand_seq: 0, }; Ok(res) } @@ -149,6 +152,17 @@ impl<'a> Processor<'a> { dst: &mut Vec, errors: &mut Diagnostics, ) { + // `` `__FILE__ `` / `` `__LINE__ `` may appear inside `` `define `` bodies as + // nested macro calls; expand them at the nested call site. + if call.name == "__FILE__" { + self.expand_file_line(true, span, dst); + return; + } + if call.name == "__LINE__" { + self.expand_file_line(false, span, dst); + return; + } + // TODO track recursion // let parent_ctx_span = self.source_map.ctx_data(span.ctx).decl.range.start(); @@ -196,6 +210,53 @@ impl<'a> Processor<'a> { } } + /// Expand `` `__FILE__ `` (`is_file`) or `` `__LINE__ `` to a literal token whose + /// text lives in a freshly allocated virtual file (green-tree text is always + /// sliced from a `FileId`). + fn expand_file_line(&mut self, is_file: bool, call_site: CtxSpan, dst: &mut Vec) { + // Direct uses (and uses inside `` `include ``d files) report the current + // input file. When expanding from a user-macro body the context decl is a + // subrange of a file, and we report the macro invocation site instead so + // idioms like `` `define LOC `__FILE__, `__LINE__ `` are useful. + let loc_span = { + let ctx_data = self.source_map.ctx_data(call_site.ctx); + let decl = ctx_data.decl; + let whole_file = self + .sources + .file_text(decl.file) + .ok() + .map(|src| decl.range == TextRange::up_to(TextSize::of(&*src))) + .unwrap_or(true); + if !whole_file { + ctx_data.call_site.unwrap_or(call_site) + } else { + call_site + } + }; + let filespan = loc_span.to_file_span(&self.source_map); + let lit_text: Arc = if is_file { + let path = self.sources.file_path(filespan.file); + format!("\"{}\"", escape_pp_string(&path.to_string())).into() + } else { + let src = + self.sources.file_text(filespan.file).expect("SourceContext file must be readable"); + let line = line_number_1based(&src, filespan.range.start()); + line.to_string().into() + }; + + let seq = self.expand_seq; + self.expand_seq = seq.wrapping_add(1); + let virt_path = format!("//{}/{}", if is_file { "file" } else { "line" }, seq); + let file = self.sources.allocate_virtual_file(&virt_path, lit_text.clone()); + let lit_text = self.arena.ensure(lit_text); + let range = TextRange::up_to(TextSize::of(lit_text)); + let ctx = self.source_map.add_ctx(FileSpan { file, range }, call_site); + dst.push(Token { + kind: if is_file { STR_LIT } else { INT_NUMBER }, + span: CtxSpan { range, ctx }, + }); + } + pub(crate) fn process_file(&mut self, mut p: Parser<'a, '_>, err: &mut Diagnostics) { while !p.at(PreprocessorToken::Eof) { self.process_token(&mut p, err) @@ -268,6 +329,16 @@ impl<'a> Processor<'a> { }); p.bump(); } + CompilerDirective::File => { + let span = p.current_span(); + p.bump(); + self.expand_file_line(true, span, p.dst); + } + CompilerDirective::Line => { + let span = p.current_span(); + p.bump(); + self.expand_file_line(false, span, p.dst); + } CompilerDirective::Macro => { let (call, range) = parse_macro_call(p, err, &[], &mut self.source_map, p.end()); @@ -286,6 +357,26 @@ impl<'a> Processor<'a> { } } +/// 1-based line number of `offset` in `src` (newlines before the offset). +fn line_number_1based(src: &str, offset: TextSize) -> u32 { + let idx: usize = offset.into(); + let idx = idx.min(src.len()); + 1 + src[..idx].bytes().filter(|&b| b == b'\n').count() as u32 +} + +/// Escape a path for embedding inside a Verilog string literal. +fn escape_pp_string(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + match c { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + c => out.push(c), + } + } + out +} + pub(crate) type MacroArgs<'s> = TiVec>, TextRange)>; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] diff --git a/openvaf/preprocessor/src/test_data/file_line_across_include.tokens b/openvaf/preprocessor/src/test_data/file_line_across_include.tokens new file mode 100644 index 00000000..62297c55 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_across_include.tokens @@ -0,0 +1,20 @@ +COMMENT +WHITESPACE +SYSFUN +L_PAREN +STR_LIT +COMMA +WHITESPACE +INT_NUMBER +R_PAREN +SEMICOLON +WHITESPACE +SYSFUN +L_PAREN +STR_LIT +COMMA +WHITESPACE +INT_NUMBER +R_PAREN +SEMICOLON +WHITESPACE diff --git a/openvaf/preprocessor/src/test_data/file_line_across_include_expanded.va b/openvaf/preprocessor/src/test_data/file_line_across_include_expanded.va new file mode 100644 index 00000000..00fbce09 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_across_include_expanded.va @@ -0,0 +1,3 @@ +// included +$display("/inc.va", 2); +$display("/parent.va", 2); diff --git a/openvaf/preprocessor/src/test_data/file_line_directives.tokens b/openvaf/preprocessor/src/test_data/file_line_directives.tokens new file mode 100644 index 00000000..e1125c25 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_directives.tokens @@ -0,0 +1,13 @@ +WHITESPACE +SYSFUN +L_PAREN +STR_LIT +COMMA +WHITESPACE +STR_LIT +COMMA +WHITESPACE +INT_NUMBER +R_PAREN +SEMICOLON +WHITESPACE diff --git a/openvaf/preprocessor/src/test_data/file_line_directives_expanded.va b/openvaf/preprocessor/src/test_data/file_line_directives_expanded.va new file mode 100644 index 00000000..78a51672 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_directives_expanded.va @@ -0,0 +1,2 @@ + +$display("at %s:%d", "/macro_expansion_test.va", 2); diff --git a/openvaf/preprocessor/src/test_data/file_line_inside_define.tokens b/openvaf/preprocessor/src/test_data/file_line_inside_define.tokens new file mode 100644 index 00000000..87f411f9 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_inside_define.tokens @@ -0,0 +1,10 @@ +WHITESPACE +SYSFUN +L_PAREN +STR_LIT +COMMA +WHITESPACE +INT_NUMBER +R_PAREN +SEMICOLON +WHITESPACE diff --git a/openvaf/preprocessor/src/test_data/file_line_inside_define_expanded.va b/openvaf/preprocessor/src/test_data/file_line_inside_define_expanded.va new file mode 100644 index 00000000..2f0a8d37 --- /dev/null +++ b/openvaf/preprocessor/src/test_data/file_line_inside_define_expanded.va @@ -0,0 +1,2 @@ + +$display("/macro_expansion_test.va", 3); diff --git a/openvaf/preprocessor/src/tests.rs b/openvaf/preprocessor/src/tests.rs index b4a6efe3..19085a99 100644 --- a/openvaf/preprocessor/src/tests.rs +++ b/openvaf/preprocessor/src/tests.rs @@ -41,6 +41,10 @@ impl SourceProvider for TestSourceProvider { fn file_id(&self, path: VfsPath) -> FileId { self.vfs.borrow_mut().ensure_file_id(path) } + + fn allocate_virtual_file(&self, path: &str, contents: Arc) -> FileId { + self.vfs.borrow_mut().add_virt_file(path, contents.to_string().into()) + } } fn check_prepocessor(sources: TestSourceProvider, root_file: FileId, test_name: &'static str) { @@ -181,3 +185,50 @@ fn source_map_triple_replacement() { "source_map_triple_replacement", ) } + +/// VAMS-2023 §10.7: `` `__FILE__ `` / `` `__LINE__ `` expand to string / decimal +/// literals of the current input file and line. +#[test] +fn file_line_directives() { + // Keep the directives on known lines so the expanded decimals are stable. + // Line 1 is blank after the raw-string newline; line 2 is the display call. + check_prepocessor_single_file( + r#" +$display("at %s:%d", `__FILE__, `__LINE__); +"#, + "file_line_directives", + ) +} + +/// After `` `include ``, `` `__FILE__ `` / `` `__LINE__ `` must report the included +/// file; once the include ends they revert to the parent. +#[test] +fn file_line_across_include() { + let sources = TestSourceProvider::new(vec![]); + let root = { + let mut vfs = sources.vfs.borrow_mut(); + vfs.add_virt_file( + "/inc.va", + concat!("// included\n", "$display(`__FILE__, `__LINE__);\n").to_owned().into(), + ); + vfs.add_virt_file( + "/parent.va", + concat!("`include \"inc.va\"\n", "$display(`__FILE__, `__LINE__);\n") + .to_owned() + .into(), + ) + }; + check_prepocessor(sources, root, "file_line_across_include"); +} + +/// Nested appearance inside a `` `define `` body expands at the call site. +#[test] +fn file_line_inside_define() { + check_prepocessor_single_file( + r#" +`define LOC `__FILE__, `__LINE__ +$display(`LOC); +"#, + "file_line_inside_define", + ) +}