diff options
| author | Miquel Sabaté Solà <mssola@mssola.com> | 2026-02-02 19:56:21 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mssola@mssola.com> | 2026-02-02 19:56:21 +0100 |
| commit | 930ad1cc4422f19251ea77a625816e3aebcc6eea (patch) | |
| tree | 6223b56dfc21a7b4278364e953e2457267518b7f /lib | |
| parent | e6f6f514cd11e9a512abb00d16b93e800e861c58 (diff) | |
| download | tools.nes-930ad1cc4422f19251ea77a625816e3aebcc6eea.tar.gz tools.nes-930ad1cc4422f19251ea77a625816e3aebcc6eea.zip | |
xixanta: add context from macro expansions
The way macro expansions work is that the evaluated bundles replace the
current node, but this throws away the context of the line, the source,
etc. from the original line of code.
Add a stack that is pushed/popped when expanding macros, and are then
cloned for each pending node and error. This way, errors have full
context of the original code and can display backtraces for macro
expansion.
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 119 | ||||
| -rw-r--r-- | lib/xixanta/src/lib.rs | 33 | ||||
| -rw-r--r-- | lib/xixanta/src/parser.rs | 15 |
3 files changed, 163 insertions, 4 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 57202bf..0d4e132 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -6,8 +6,8 @@ use crate::node::{ use crate::object::{Bundle, Context, Object, ObjectType}; use crate::opcodes::{AddressingMode, INSTRUCTIONS}; use crate::parser::Parser; -use crate::Error; use crate::SourceInfo; +use crate::{Error, ExpandedFrom}; use std::cmp::Ordering; use std::collections::HashMap; use std::fs::File; @@ -57,6 +57,7 @@ struct PendingNode { bundle_index: usize, node: PNode, labels_seen: usize, + macro_context: Vec<ExpandedFrom>, } /// Memory range that can be identified by a name. @@ -135,6 +136,13 @@ struct Assembler<'a> { // The amount of bytes to reserve for the next variable assignment. asan_next_reserve: usize, + + // Stack of macro expansions. That is, every time a macro is expanded + // (i.e. via 'bundle_call'), the node that performed the expansion is + // stacked here. This is then picked up via structs like 'PendingNode' and + // 'Error' so the programmer gets information on all the macro expansions + // that happened before reaching a given error. + macro_context: Vec<ExpandedFrom>, } /// The result to be given at the end of `assembler::assemble` and @@ -205,6 +213,7 @@ pub fn assemble( line: 0, message: e, source: source.clone(), + expanded_from: vec![], }], warnings: vec![], mappings: vec![], @@ -372,6 +381,7 @@ impl<'a> Assembler<'a> { asan_enabled: false, asan_next_ignore: false, asan_next_reserve: 1, + macro_context: vec![], } } @@ -382,6 +392,7 @@ impl<'a> Assembler<'a> { global: true, line: 0, source: self.sources.first().unwrap().clone(), + expanded_from: self.macro_context.clone(), message: "empty variable name".to_string(), }); } @@ -408,6 +419,7 @@ impl<'a> Assembler<'a> { global: true, line: 0, source: self.sources.first().unwrap().clone(), + expanded_from: self.macro_context.clone(), message: err, }) } else { @@ -436,6 +448,7 @@ impl<'a> Assembler<'a> { message, line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -469,6 +482,7 @@ impl<'a> Assembler<'a> { node.value.value ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); continue; @@ -491,6 +505,7 @@ impl<'a> Assembler<'a> { id.value, ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -506,6 +521,7 @@ impl<'a> Assembler<'a> { .to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); continue; @@ -541,6 +557,7 @@ impl<'a> Assembler<'a> { message: err, line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -556,6 +573,7 @@ impl<'a> Assembler<'a> { message: format!("{control_type} must be on the global scope"), line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); continue; @@ -590,6 +608,7 @@ impl<'a> Assembler<'a> { message: "you cannot call '.proc' in this context".to_string(), line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); continue; @@ -614,6 +633,7 @@ impl<'a> Assembler<'a> { message: "you cannot call '.scope' in this context".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); continue; @@ -637,6 +657,7 @@ impl<'a> Assembler<'a> { message, line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -713,6 +734,7 @@ impl<'a> Assembler<'a> { message, line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -752,6 +774,7 @@ impl<'a> Assembler<'a> { message, line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -767,6 +790,7 @@ impl<'a> Assembler<'a> { message: format!("empty .proc '{}'", proc_name.value.value), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } else { self.bundle(args)?; @@ -816,6 +840,7 @@ impl<'a> Assembler<'a> { message: format!("empty .scope '{}'", scope_name.value), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } else { self.bundle(args)?; @@ -918,6 +943,7 @@ impl<'a> Assembler<'a> { line: 0, message: format!("variable {range} is unused"), source: self.sources[0].clone(), + expanded_from: self.macro_context.clone(), global: true, }); continue; @@ -936,6 +962,7 @@ impl<'a> Assembler<'a> { line: 0, global: true, message: "out of internal RAM".to_string(), + expanded_from: self.macro_context.clone(), source: self.sources[0].clone(), }); } @@ -948,6 +975,7 @@ impl<'a> Assembler<'a> { line: 0, global: true, message: "out of working RAM".to_string(), + expanded_from: self.macro_context.clone(), source: self.sources[0].clone(), }); } @@ -979,6 +1007,7 @@ impl<'a> Assembler<'a> { line: 0, global: true, message: format!("The variable {range} conflicts with {existing}",), + expanded_from: self.macro_context.clone(), source: self.sources[0].clone(), }); } @@ -1003,6 +1032,7 @@ impl<'a> Assembler<'a> { line: 0, global: true, message: e, + expanded_from: self.macro_context.clone(), source: self.sources[0].clone(), }]); } @@ -1016,6 +1046,7 @@ impl<'a> Assembler<'a> { line: 0, message: format!("segment '{}' is empty", segment.name), source: self.sources[0].clone(), + expanded_from: self.macro_context.clone(), global: true, }); } @@ -1031,6 +1062,7 @@ impl<'a> Assembler<'a> { mapping.name, mapping.size, mapping.offset, ), source: self.sources[0].clone(), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1061,6 +1093,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: val.to_string(), }), EchoKind::Error => { @@ -1068,6 +1101,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: val.to_string(), } .into()); @@ -1086,6 +1120,7 @@ impl<'a> Assembler<'a> { "could not find a macro with the name '{}'", node.value.value ), + expanded_from: self.macro_context.clone(), source: self.source_for(node), global: false, })?; @@ -1101,6 +1136,7 @@ impl<'a> Assembler<'a> { "wrong number of arguments for '{}': {} required but {} given", node.value.value, macro_args, given_args, ), + expanded_from: self.macro_context.clone(), source: self.source_for(node), global: false, } @@ -1135,6 +1171,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, } .into()); @@ -1154,10 +1191,16 @@ impl<'a> Assembler<'a> { line: node.value.line, message: format!("trying to apply empty macro '{}'", node.value.value), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } else { + self.macro_context.push(ExpandedFrom { + line: node.value.line, + source: self.source_for(node), + }); self.bundle(inner)?; + let _ = self.macro_context.pop(); } Ok(()) } @@ -1176,6 +1219,7 @@ impl<'a> Assembler<'a> { bundle_index: current.segments[self.current_segment].bundles.len(), node: node.to_owned(), labels_seen: self.context.labels_seen(), + macro_context: self.macro_context.clone(), }); } current.segments[self.current_segment].bundles.push(bundle); @@ -1206,6 +1250,7 @@ impl<'a> Assembler<'a> { message: "invalid identifier".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }) } else { @@ -1220,6 +1265,7 @@ impl<'a> Assembler<'a> { message: err.message, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }), } @@ -1230,6 +1276,7 @@ impl<'a> Assembler<'a> { message: format!("unexpected '{}' expression type", node.node_type), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }), } @@ -1293,6 +1340,7 @@ impl<'a> Assembler<'a> { line: node.value.line, global: false, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "attempting to divide by zero".to_string(), }); } @@ -1325,6 +1373,7 @@ impl<'a> Assembler<'a> { line: node.value.line, global: false, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "shift operator too big".to_string(), }); } @@ -1338,6 +1387,7 @@ impl<'a> Assembler<'a> { line: node.value.line, global: false, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "shift operator too big".to_string(), }); } @@ -1377,6 +1427,7 @@ impl<'a> Assembler<'a> { line: node.value.line, global: false, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "performing the operation would overflow a 16-bit integer".to_string(), }); } @@ -1422,6 +1473,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }), } @@ -1430,6 +1482,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), message: "trying to evaluate a relative reference as a bare value".to_string(), }), } @@ -1472,6 +1525,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), line: node.value.line, + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1479,6 +1533,7 @@ impl<'a> Assembler<'a> { message: "expecting a number of 1 to 4 hexadecimal digits".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1508,6 +1563,7 @@ impl<'a> Assembler<'a> { message: "missing binary digits to get a full byte".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }) } @@ -1516,6 +1572,7 @@ impl<'a> Assembler<'a> { message: "too many binary digits for a single byte".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }) } @@ -1534,6 +1591,7 @@ impl<'a> Assembler<'a> { ), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1541,6 +1599,7 @@ impl<'a> Assembler<'a> { message: format!("bad binary format for '{string}'"), line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), source: self.source_for(node), }); } @@ -1564,6 +1623,7 @@ impl<'a> Assembler<'a> { message: "empty decimal literal".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1577,6 +1637,7 @@ impl<'a> Assembler<'a> { message: "decimal value is too big".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1595,6 +1656,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), line: node.value.line, + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1608,6 +1670,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), line: node.value.line, + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1623,6 +1686,7 @@ impl<'a> Assembler<'a> { message: "decimal value is too big".to_string(), line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1658,6 +1722,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } else if val.starts_with('%') { @@ -1668,6 +1733,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } else { @@ -1696,6 +1762,7 @@ impl<'a> Assembler<'a> { line: source.value.line, source: self.source_for(source), global: false, + expanded_from: self.macro_context.clone(), }); } Err(Error { @@ -1703,6 +1770,7 @@ impl<'a> Assembler<'a> { line: source.value.line, source: self.source_for(source), global: false, + expanded_from: self.macro_context.clone(), }) } }, @@ -1711,6 +1779,7 @@ impl<'a> Assembler<'a> { line: source.value.line, source: self.source_for(source), global: false, + expanded_from: self.macro_context.clone(), }), } } @@ -1731,6 +1800,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), } .into()); } @@ -1763,6 +1833,7 @@ impl<'a> Assembler<'a> { node.value.value ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, } .into()), @@ -1783,6 +1854,7 @@ impl<'a> Assembler<'a> { "path has to be written inside of double quotes ('{value}' given instead)", ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1797,6 +1869,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: format!("could not move to the directory of '{value}': {e}"), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -1813,6 +1886,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: format!("could not include binary data: {e}"), }) } @@ -1832,6 +1906,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: format!("file '{path}' is too big"), }); } else if metadata.len() == 0 { @@ -1839,6 +1914,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: format!("trying to include an empty file ('{path}')"), }); } @@ -1848,6 +1924,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: format!("could not include binary data: {e}"), }) } @@ -1877,6 +1954,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "pointless .repeat statement".to_string(), } .into()); @@ -1885,6 +1963,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "the number of iterations has to fit in a single byte".to_string(), } .into()); @@ -1896,6 +1975,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, message: format!("first argument must be an integer, '{first}' found instead"), + expanded_from: self.macro_context.clone(), source: self.source_for(node), } .into()) @@ -1909,6 +1989,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: "empty .repeat statement".to_string(), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); return Ok(()); @@ -1937,6 +2018,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: e, source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, } .into()); @@ -1998,6 +2080,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: "empty body".to_string(), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } else if self.stage == Stage::Context { @@ -2020,6 +2103,7 @@ impl<'a> Assembler<'a> { "cannot handle control statement '{}' as an expression in this context", node.value.value ), + expanded_from: self.macro_context.clone(), source: self.source_for(node), global: false, }), @@ -2089,6 +2173,7 @@ impl<'a> Assembler<'a> { message: "expecting an argument that fits into a byte" .to_string(), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }) } @@ -2111,6 +2196,7 @@ impl<'a> Assembler<'a> { node.value.value.as_str(), ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }) } @@ -2131,6 +2217,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "you are trying to reserve too much memory".to_string(), }); } @@ -2140,6 +2227,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "empty .res statement".to_string(), }); } else if iterations == 1 { @@ -2147,6 +2235,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "pointless .res statement, prefer using .byte".to_string(), }); } @@ -2161,6 +2250,7 @@ impl<'a> Assembler<'a> { global: false, line: node.value.line, source: self.source_for(node), + expanded_from: self.macro_context.clone(), message: "fill value must fit into a single byte".to_string(), }); } @@ -2193,6 +2283,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: "unpermitted empty argument".to_string(), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -2203,6 +2294,7 @@ impl<'a> Assembler<'a> { "declaration has to be written inside of double quotes ('{val}' given instead)", ), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -2236,6 +2328,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: "segment name contains bad characters".to_string(), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -2258,6 +2351,7 @@ impl<'a> Assembler<'a> { line: node.value.line, message: format!("unknown segment '{name}'"), source: self.source_for(node), + expanded_from: self.macro_context.clone(), global: false, }); } @@ -2296,6 +2390,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }), } } @@ -2336,6 +2431,7 @@ impl<'a> Assembler<'a> { source: self.source_for(node), line: node.value.line, global: false, + expanded_from: self.macro_context.clone(), }); } }, @@ -2345,6 +2441,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } @@ -2380,6 +2477,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } @@ -2392,6 +2490,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } self.asan_check_arm(evaluated_node, &val)?; @@ -2402,6 +2501,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }) } None => match left.right.as_ref() { @@ -2417,6 +2517,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } self.asan_check_arm(evaluated_node, &val)?; @@ -2427,6 +2528,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }) } None => { @@ -2438,6 +2540,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } if !matches!(node.value.value.as_str(), "jmp" | "jsr") { @@ -2463,6 +2566,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } @@ -2530,6 +2634,7 @@ impl<'a> Assembler<'a> { line: node.value.line, source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }), } } @@ -2575,6 +2680,7 @@ impl<'a> Assembler<'a> { line: left_arm.value.line, source: self.source_for(base), global: false, + expanded_from: self.macro_context.clone(), }), } } else { @@ -2587,6 +2693,7 @@ impl<'a> Assembler<'a> { line: left_arm.value.line, source: self.source_for(base), global: false, + expanded_from: self.macro_context.clone(), }), } } @@ -2628,6 +2735,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } Ok(()) @@ -2646,6 +2754,7 @@ impl<'a> Assembler<'a> { message: "accessing a memory region without a proper name".to_string(), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); return Ok(()); }; @@ -2671,6 +2780,7 @@ impl<'a> Assembler<'a> { message: format!("out of bounds memory access for {range}"), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } @@ -2685,6 +2795,7 @@ impl<'a> Assembler<'a> { message: "accessing a memory region without using a variable".to_string(), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); Ok(()) @@ -2708,6 +2819,7 @@ impl<'a> Assembler<'a> { message: "you cannot use a negative value for a memory variable".to_string(), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } @@ -2727,6 +2839,7 @@ impl<'a> Assembler<'a> { message: format!("you are assigning the zero-page variable {range} to something outside of zero page"), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } else if bundle.size != 2 { @@ -2742,6 +2855,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } else if actual_name.starts_with("wr_") { let value = bundle.value() as usize; @@ -2757,6 +2871,7 @@ impl<'a> Assembler<'a> { ), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } } @@ -2780,6 +2895,7 @@ impl<'a> Assembler<'a> { message: "you cannot branch to this location: it's too far away".to_string(), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } diff.to_le_bytes()[0] @@ -2791,6 +2907,7 @@ impl<'a> Assembler<'a> { message: "you cannot branch to this location: it's too far away".to_string(), source: self.source_for(node), global: false, + expanded_from: self.macro_context.clone(), }); } diff.to_le_bytes()[0] diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs index 8515e23..a45868f 100644 --- a/lib/xixanta/src/lib.rs +++ b/lib/xixanta/src/lib.rs @@ -17,6 +17,20 @@ impl Default for SourceInfo { } } +/// Contains all the information relevant for a macro expansion. That is, +/// whenever expanding a macro call, the assembler is expected to create a new +/// 'ExpandedFrom' object with all the relevant information so a node underneath +/// can refer to it whenever an error occurred (e.g. 'I detected an error here +/// but I was coming from an expansion from "there"'). +#[derive(Clone, Debug, PartialEq)] +pub struct ExpandedFrom { + /// The source from which the expansion happened. + pub source: SourceInfo, + + /// The line in the 'source' where the macro expansion happened. + pub line: usize, +} + /// Error provides an interface used throughout this crate in which an error /// message of type String is located through a line and an associated /// SourceInfo. If global is set to true, then `line` does not matter. @@ -32,6 +46,11 @@ pub struct Error { /// Information on the file where the error was found. pub source: SourceInfo, + /// Stack of macro expansions before reaching to the error. This is used for + /// the 'Display' trait, so the expansion context is rewinded and shown to + /// the user. + pub expanded_from: Vec<ExpandedFrom>, + /// Human-readable representation of the error. Don't use this field /// directly, prefer its `std::fmt::Display` implementation. Hence, calling /// `.to_string()` on it is most probably the way to go. @@ -57,13 +76,21 @@ impl std::fmt::Display for Error { } else if self.source.name.is_empty() { write!(f, "{} (line {})", self.message, self.line + 1) } else { - write!( - f, + let mut msg = vec![format!( "{} ({}: line {})", self.message, self.source.name, self.line + 1 - ) + )]; + for ef in &self.expanded_from { + msg.push(format!( + " >> expanded from {}: line {}.", + ef.source.name, + ef.line + 1 + )); + } + + write!(f, "{}", msg.join("\n")) } } } diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index cf01133..7a7e3f0 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -242,6 +242,7 @@ impl Parser { global: false, source: self.sources[self.current_source].clone(), message: "expecting a number formatted with a leading '$' sign".to_string(), + expanded_from: vec![], }) } } @@ -257,6 +258,7 @@ impl Parser { global: false, source: self.sources[self.current_source].clone(), message: "expecting a number formatted with a leading '$' sign".to_string(), + expanded_from: vec![], }); } let Ok(val) = usize::from_str_radix(arg.get(1..).unwrap_or("0000"), 16) else { @@ -264,6 +266,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "could not parse asan:reserve number".to_string(), }); }; @@ -272,6 +275,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "bad asan:reserve number, should be higher than $01".to_string(), }); } @@ -460,6 +464,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "you cannot have a relative label inside of an identifier".to_string(), }); } @@ -484,6 +489,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "you can only jump to a maximum of four relative labels".to_string(), }); } @@ -497,6 +503,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: format!("{msg} relative label can only have '{next}' characters"), }); } @@ -801,6 +808,7 @@ impl Parser { line: node.value.line, global: false, message: ".include statement cannot be inside of a code block".to_string(), + expanded_from: vec![], source: self.sources[self.current_source].clone(), } .into()); @@ -963,6 +971,7 @@ impl Parser { line: node.value.line, global: false, source: current_source.clone(), + expanded_from: vec![], message: ".include statements expect a file as the argument".to_string(), } .into()); @@ -980,6 +989,7 @@ impl Parser { line: node.value.line, global: false, source: current_source.clone(), + expanded_from: vec![], message: format!("could not open source file '{file_path}': {e}"), } .into()) @@ -990,6 +1000,7 @@ impl Parser { line: node.value.line, global: false, source: current_source.clone(), + expanded_from: vec![], message: format!("could not find out the parent directory for file '{file_path}'"), } .into()); @@ -1030,6 +1041,7 @@ impl Parser { line: node.value.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: format!( "path has to be written inside of double quotes ('{value}' given instead)", ), @@ -1443,6 +1455,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "bad literal syntax".to_string(), }); } @@ -1645,6 +1658,7 @@ impl Parser { line: self.line, global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], message: "numeric literals cannot have white spaces".to_string(), }); } @@ -1744,6 +1758,7 @@ impl Parser { message: String::from(msg), global: false, source: self.sources[self.current_source].clone(), + expanded_from: vec![], line: self.line, } } |
