aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-01-08 12:34:47 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-01-09 16:01:39 +0100
commit8b1c270910fce13077864bf39d6e88971a6eb062 (patch)
tree2a441b58d46905f680749c1c074e9b55a27f0d6e /lib/xixanta
parent4f5710aa516ab149d132cdb5863db08f08c10563 (diff)
downloadtools.nes-8b1c270910fce13077864bf39d6e88971a6eb062.tar.gz
tools.nes-8b1c270910fce13077864bf39d6e88971a6eb062.zip
Implement the .include statement
This needed some heavy lifting when it comes to how files were located. This means that statements like .include/.incbin now take into consideration a new list made out of SourceInfo, which holds enough information to translate from which file a node comes from. This has also been added into errors, so they are more informative on what went wrong. In order to tests this, besides all the regular unit tests, a new e2e test has been added. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs6
-rw-r--r--lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs2
-rw-r--r--lib/xixanta/src/assembler.rs309
-rw-r--r--lib/xixanta/src/errors.rs61
-rw-r--r--lib/xixanta/src/lib.rs15
-rw-r--r--lib/xixanta/src/mapping.rs57
-rw-r--r--lib/xixanta/src/node.rs8
-rw-r--r--lib/xixanta/src/object.rs91
-rw-r--r--lib/xixanta/src/opcodes.rs1
-rw-r--r--lib/xixanta/src/parser.rs396
10 files changed, 660 insertions, 286 deletions
diff --git a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
index 9b9119b..a402998 100644
--- a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
+++ b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_assembler.rs
@@ -4,9 +4,5 @@ use libfuzzer_sys::fuzz_target;
use xixanta::assembler::assemble;
fuzz_target!(|data: &[u8]| {
- let _ = assemble(
- data,
- "empty",
- std::env::current_dir().unwrap().to_path_buf(),
- );
+ let _ = assemble(data, "empty", xixanta::SourceInfo::default());
});
diff --git a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
index 442dbbf..da8a350 100644
--- a/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
+++ b/lib/xixanta/fuzz/fuzz_targets/fuzz_target_parser.rs
@@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let mut parser = xixanta::parser::Parser::default();
- let _ = parser.parse(data);
+ let _ = parser.parse(data, xixanta::SourceInfo::default());
});
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 30547d2..d82be73 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1,15 +1,15 @@
use crate::errors::{ContextError, ContextErrorReason, Error, EvalError};
use crate::mapping::{get_mapping_configuration, Mapping};
-use crate::node::{ControlType, NodeType, OperationType, PNode, PString};
+use crate::node::{ControlType, NodeType, OperationType, PNode};
use crate::object::{Bundle, Context, Object, ObjectType};
use crate::opcodes::{AddressingMode, INSTRUCTIONS};
use crate::parser::Parser;
+use crate::SourceInfo;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::ops::Neg;
-use std::path::PathBuf;
/// The mode in which a literal is expressed.
#[derive(Clone, PartialEq)]
@@ -78,10 +78,8 @@ pub struct Assembler<'a> {
// Warnings that have accumulated over the run.
warnings: Vec<Error>,
- // Stack of directories. The last directory is the current one, whereas the
- // other elements come from previous contexts. This way we can implement a
- // file that imports another file which in turn imports another file, etc.
- directories: Vec<PathBuf>,
+ // TODO
+ sources: Vec<SourceInfo>,
}
#[derive(Debug)]
@@ -92,12 +90,12 @@ pub struct AssemblerResult {
}
/// Read the contents from the `reader` as a source file and produce a list of
-/// bundles that can be formatted as binary data. You also need to pass the
-/// initial working directory `init_directory`, as otherwise control statements
-/// like ".import" or ".incbin" wouldn't know how to resolve relative paths. You
-/// can specify the mapper to be used as an identifier in `mapping`, which will
-/// be handled via `get_mapping_configuration`.
-pub fn assemble(reader: impl Read, mapping: &str, init_directory: PathBuf) -> AssemblerResult {
+/// bundles that can be formatted as binary data. You also need to pass
+/// information of the source by means of `source`, as otherwise control
+/// statements like ".import" or ".incbin" wouldn't know how to resolve relative
+/// paths. You can specify the mapper to be used as an identifier in `mapping`,
+/// which will be handled via `get_mapping_configuration`.
+pub fn assemble(reader: impl Read, mapping: &str, source: SourceInfo) -> AssemblerResult {
let config = match get_mapping_configuration(mapping) {
Ok(config) => config,
Err(e) => {
@@ -107,35 +105,33 @@ pub fn assemble(reader: impl Read, mapping: &str, init_directory: PathBuf) -> As
global: true,
line: 0,
message: e,
+ source,
})],
warnings: vec![],
};
}
};
- assemble_with_mapping(reader, config, init_directory)
+ assemble_with_mapping(reader, config, source)
}
/// Read the contents from the `reader` as a source file and produce a list of
-/// bundles that can be formatted as binary data. You also need to pass the
-/// initial working directory `init_directory`, as otherwise control statements
-/// like ".import" or ".incbin" wouldn't know how to resolve relative paths. You
-/// also need to provide the `mapping` as handled internally. If you are unsure
-/// how to use it, just call `assemble`.
+/// bundles that can be formatted as binary data. You also need to pass
+/// information of the source by means of `source`, as otherwise control
+/// statements like ".import" or ".incbin" wouldn't know how to resolve relative
+/// paths. You also need to provide the `mapping` as handled internally. If you
+/// are unsure how to use it, just call `assemble`.
pub fn assemble_with_mapping(
reader: impl Read,
mapping: Vec<Mapping>,
- init_directory: PathBuf,
+ source: SourceInfo,
) -> AssemblerResult {
let mut asm = Assembler::new(mapping);
- // Push the initial directory into our stack of directories.
- asm.directories.push(init_directory);
-
// First of all, parse the input so we get a list of nodes we can work
// with.
let mut parser = Parser::default();
- if let Err(errors) = parser.parse(reader) {
+ if let Err(errors) = parser.parse(reader, source) {
return AssemblerResult {
bundles: vec![],
errors: errors.iter().map(|e| Error::Parse(e.clone())).collect(),
@@ -144,6 +140,7 @@ pub fn assemble_with_mapping(
}
let nodes = parser.nodes();
+ asm.sources = parser.sources;
// Build the context by iterating over the parsed nodes and checking
// where scopes start/end, evaluating values for variables, labels, etc.
@@ -208,27 +205,37 @@ impl<'a> Assembler<'a> {
procs_seen: 0,
repeats_seen: 0,
warnings: vec![],
- directories: vec![],
+ sources: vec![],
}
}
// Define a new variable by taking the given `id`. This variable will only
// be created if `id` is not empty. The function will error out if the given
// name is already taken.
- fn define_variable(&mut self, id: &PString) -> Result<(), ContextError> {
- if id.is_empty() {
+ fn define_variable(&mut self, node: &PNode) -> Result<(), ContextError> {
+ if node.value.is_empty() {
return Ok(());
}
- self.context.set_variable(
- id,
+ if let Err(message) = self.context.set_variable(
+ &node.value,
&Object::new(
self.current_mapping,
self.current_segment,
ObjectType::Address,
),
false,
- )
+ ) {
+ return Err(ContextError {
+ message,
+ line: node.value.line,
+ global: false,
+ source: self.source_for(node),
+ reason: ContextErrorReason::BadScope,
+ });
+ }
+
+ Ok(())
}
fn eval_context(&mut self, nodes: &'a [PNode]) -> Result<(), Vec<Error>> {
@@ -250,11 +257,12 @@ impl<'a> Assembler<'a> {
"using a named label ('{}') inside of a macro/repeat definition",
node.value.value
),
+ source: self.source_for(node),
global: false,
}));
continue;
}
- if let Err(err) = self.define_variable(&node.value) {
+ if let Err(err) = self.define_variable(node) {
errors.push(Error::Context(err));
}
}
@@ -264,6 +272,7 @@ impl<'a> Assembler<'a> {
message: "cannot have assignments inside of macro/repeat definitions"
.to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
}));
continue;
@@ -281,7 +290,13 @@ impl<'a> Assembler<'a> {
},
false,
) {
- errors.push(Error::Context(err));
+ errors.push(Error::Context(ContextError {
+ message: err,
+ line: node.value.line,
+ global: false,
+ source: self.source_for(node),
+ reason: ContextErrorReason::BadScope,
+ }));
}
}
Err(e) => errors.push(Error::Eval(e)),
@@ -293,6 +308,7 @@ impl<'a> Assembler<'a> {
message: format!("{} must be on the global scope", control_type),
line: node.value.line,
global: false,
+ source: self.source_for(node),
reason: ContextErrorReason::BadScope,
}));
continue;
@@ -327,13 +343,14 @@ impl<'a> Assembler<'a> {
message: "you cannot call '.proc' in this context".to_string(),
line: node.value.line,
global: false,
+ source: self.source_for(node),
reason: ContextErrorReason::BadStart,
}));
continue;
}
self.procs_seen += 1;
- let proc_name = &node.left.as_ref().unwrap().value;
+ let proc_name = &node.left.as_ref().unwrap();
if let Err(err) = self.define_variable(proc_name) {
errors.push(Error::Context(err));
}
@@ -350,6 +367,7 @@ impl<'a> Assembler<'a> {
errors.push(Error::Context(ContextError {
message: "you cannot call '.scope' in this context".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
reason: ContextErrorReason::BadStart,
}));
@@ -369,8 +387,14 @@ impl<'a> Assembler<'a> {
// If this control statement implies a context change, do it
// now.
- if let Err(err) = self.context.change_context(node) {
- errors.push(Error::Context(err));
+ if let Err(message) = self.context.change_context(node) {
+ errors.push(Error::Context(ContextError {
+ message,
+ line: node.value.line,
+ global: false,
+ source: self.source_for(node),
+ reason: ContextErrorReason::BadScope,
+ }));
}
// If this control statement actually has a body, go inside
@@ -395,7 +419,7 @@ impl<'a> Assembler<'a> {
// it's empty (i.e. anonymous label). In either case, the computed label
// will be pushed into the context's list of known labels with the current
// segment offset.
- fn apply_segment_offset_to_label(&mut self, id: &PString) -> Result<(), ContextError> {
+ fn apply_segment_offset_to_label(&mut self, node: &PNode) -> Result<(), ContextError> {
let segment = &self.mappings[self.current_mapping].segments[self.current_segment];
let value = segment.offset.to_le_bytes();
let object = Object {
@@ -413,8 +437,16 @@ impl<'a> Assembler<'a> {
object_type: ObjectType::Address,
};
- if !id.is_empty() {
- self.context.set_variable(id, &object, true)?;
+ if !node.value.is_empty() {
+ if let Err(message) = self.context.set_variable(&node.value, &object, true) {
+ return Err(ContextError {
+ message,
+ line: node.value.line,
+ global: false,
+ source: self.source_for(node),
+ reason: ContextErrorReason::BadScope,
+ });
+ }
}
self.context.add_label(&object);
@@ -433,7 +465,7 @@ impl<'a> Assembler<'a> {
// of the offset, the effective address will only be available
// after calling `Context::get_variable`
NodeType::Label => {
- if let Err(e) = self.apply_segment_offset_to_label(&node.value) {
+ if let Err(e) = self.apply_segment_offset_to_label(node) {
errors.push(Error::Context(e));
}
}
@@ -441,12 +473,18 @@ impl<'a> Assembler<'a> {
// introduces a new context. Hence, first act as a label, and
// then open up its inner context.
NodeType::Control(ControlType::StartProc) => {
- let proc_name = &node.left.as_ref().unwrap().value;
+ let proc_name = &node.left.as_ref().unwrap();
if let Err(e) = self.apply_segment_offset_to_label(proc_name) {
errors.push(Error::Context(e));
}
- if let Err(e) = self.context.change_context(node) {
- errors.push(Error::Context(e));
+ if let Err(message) = self.context.change_context(node) {
+ errors.push(Error::Context(ContextError {
+ message,
+ line: node.value.line,
+ global: false,
+ source: self.source_for(node),
+ reason: ContextErrorReason::BadScope,
+ }));
}
// And now go inside of its body if it exists (note that its
@@ -457,7 +495,8 @@ impl<'a> Assembler<'a> {
if args.is_empty() {
self.warnings.push(Error::Eval(EvalError {
line: node.value.line,
- message: format!("empty .proc '{}'", proc_name.value),
+ message: format!("empty .proc '{}'", proc_name.value.value),
+ source: self.source_for(node),
global: false,
}));
} else {
@@ -501,6 +540,7 @@ impl<'a> Assembler<'a> {
self.warnings.push(Error::Eval(EvalError {
line: node.value.line,
message: format!("empty .scope '{}'", scope_name.value),
+ source: self.source_for(node),
global: false,
}));
} else {
@@ -572,7 +612,12 @@ impl<'a> Assembler<'a> {
// Validate the mappings that have been evaluated before spitting it
// out.
if let Err(e) = crate::mapping::validate(&self.mappings) {
- return Err(vec![Error::Eval(e)]);
+ return Err(vec![Error::Eval(EvalError {
+ line: 0,
+ global: true,
+ message: e,
+ source: self.sources[0].clone(),
+ })]);
}
let mut res = vec![];
@@ -583,6 +628,7 @@ impl<'a> Assembler<'a> {
self.warnings.push(Error::Eval(EvalError {
line: 0,
message: format!("segment '{}' is empty", segment.name),
+ source: self.sources[0].clone(),
global: true,
}));
}
@@ -597,6 +643,7 @@ impl<'a> Assembler<'a> {
"exceeding segment size for '{}'; expecting {} bytes and {} bytes have already been seen",
mapping.name, mapping.size, mapping.offset,
),
+ source: self.sources[0].clone(),
global: false,
}));
}
@@ -625,6 +672,7 @@ impl<'a> Assembler<'a> {
"could not find a macro with the name '{}'",
node.value.value
),
+ source: self.source_for(node),
global: false,
})?;
@@ -639,6 +687,7 @@ impl<'a> Assembler<'a> {
"wrong number of arguments for '{}': {} required but {} given",
node.value.value, macro_args, given_args,
),
+ source: self.source_for(node),
global: false,
})]);
}
@@ -658,8 +707,18 @@ impl<'a> Assembler<'a> {
// Note that we overwrite the variable value from previous
// calls, just in case a macro is applied multiple times and we
// need to get the latest value.
- self.context
- .set_variable(&margs.next().unwrap().value, &obj, true)?;
+ if let Err(message) =
+ self.context
+ .set_variable(&margs.next().unwrap().value, &obj, true)
+ {
+ return Err(vec![Error::Context(ContextError {
+ line: node.value.line,
+ message,
+ source: self.source_for(node),
+ global: false,
+ reason: ContextErrorReason::BadScope,
+ })]);
+ }
}
}
@@ -674,6 +733,7 @@ impl<'a> Assembler<'a> {
self.warnings.push(Error::Eval(EvalError {
line: node.value.line,
message: format!("trying to apply empty macro '{}'", node.value.value),
+ source: self.source_for(node),
global: false,
}));
} else {
@@ -726,12 +786,13 @@ impl<'a> Assembler<'a> {
Err(EvalError {
message: "no prefix was given to operand".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
})
} else {
// This is actually a valid identifier! Try to fetch the
// variable.
- match self.evaluate_variable(&node.value) {
+ match self.evaluate_variable(node) {
Ok(v) => {
self.literal_mode = Some(LiteralMode::Hexadecimal);
Ok(v)
@@ -742,6 +803,7 @@ impl<'a> Assembler<'a> {
err.message
),
line: node.value.line,
+ source: self.source_for(node),
global: false,
}),
}
@@ -751,6 +813,7 @@ impl<'a> Assembler<'a> {
_ => Err(EvalError {
message: format!("unexpected '{}' expression type", node.node_type),
line: node.value.line,
+ source: self.source_for(node),
global: false,
}),
}
@@ -807,6 +870,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
global: false,
+ source: self.source_for(node),
message: "attempting to divide by zero".to_string(),
});
}
@@ -830,6 +894,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
global: false,
+ source: self.source_for(node),
message: "shift operator too big".to_string(),
});
}
@@ -842,6 +907,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
global: false,
+ source: self.source_for(node),
message: "shift operator too big".to_string(),
});
}
@@ -856,6 +922,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
global: false,
+ source: self.source_for(node),
message: "performing the operation would overflow a 16-bit integer".to_string(),
});
}
@@ -889,9 +956,10 @@ impl<'a> Assembler<'a> {
&self.mappings,
) {
Ok(object) => Ok(object.bundle),
- Err(e) => Err(EvalError {
+ Err(message) => Err(EvalError {
line: node.value.line,
- message: e.message,
+ message,
+ source: self.source_for(node),
global: false,
}),
}
@@ -929,12 +997,13 @@ impl<'a> Assembler<'a> {
size = 2;
}
_ => {
- if self.evaluate_variable(&node.value).is_ok() {
+ if self.evaluate_variable(node).is_ok() {
return Err(EvalError {
message: format!(
"you cannot use variables like '{}' in hexadecimal literals",
node.value.value
),
+ source: self.source_for(node),
line: node.value.line,
global: false,
});
@@ -942,6 +1011,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "expecting a number of 1 to 4 hexadecimal digits".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -970,6 +1040,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "missing binary digits to get a full byte".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
})
}
@@ -977,6 +1048,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "too many binary digits for a single byte".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
})
}
@@ -988,13 +1060,14 @@ impl<'a> Assembler<'a> {
let val = 1 << shift;
value += val;
} else if c != '0' {
- if self.evaluate_variable(&node.value).is_ok() {
+ if self.evaluate_variable(node).is_ok() {
return Err(EvalError {
message: format!(
"you cannot use variables like '{}' in binary literals",
string
),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1002,6 +1075,7 @@ impl<'a> Assembler<'a> {
message: format!("bad binary format for '{}'", string),
line: node.value.line,
global: false,
+ source: self.source_for(node),
});
}
}
@@ -1023,6 +1097,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "empty decimal literal".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1035,6 +1110,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "decimal value is too big".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1052,11 +1128,12 @@ impl<'a> Assembler<'a> {
in variable definitions",
string
),
+ source: self.source_for(node),
line: node.value.line,
global: false,
});
}
- match self.evaluate_variable(&node.value) {
+ match self.evaluate_variable(node) {
Ok(v) => return Ok(v),
Err(err) => {
return Err(EvalError {
@@ -1064,6 +1141,7 @@ impl<'a> Assembler<'a> {
"'{}' is not a decimal value and {} either",
c, err.message
),
+ source: self.source_for(node),
line: node.value.line,
global: false,
});
@@ -1079,6 +1157,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "decimal value is too big".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1111,6 +1190,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "literal cannot embed another literal".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1120,6 +1200,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "literal cannot embed another literal".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1140,21 +1221,21 @@ impl<'a> Assembler<'a> {
Some(c) => match c.to_digit(16) {
Some(c) => Ok(c as u8),
None => {
- if (c.is_alphabetic() || c == '_')
- && self.evaluate_variable(&source.value).is_ok()
- {
+ if (c.is_alphabetic() || c == '_') && self.evaluate_variable(source).is_ok() {
return Err(EvalError {
message: format!(
"you cannot use variables like '{}' in hexadecimal literals",
source.value.value
),
line: source.value.line,
+ source: self.source_for(source),
global: false,
});
}
Err(EvalError {
message: "could not convert digit to hexadecimal".to_string(),
line: source.value.line,
+ source: self.source_for(source),
global: false,
})
}
@@ -1162,6 +1243,7 @@ impl<'a> Assembler<'a> {
None => Err(EvalError {
message: "digit out of bounds".to_string(),
line: source.value.line,
+ source: self.source_for(source),
global: false,
}),
}
@@ -1171,8 +1253,20 @@ impl<'a> Assembler<'a> {
// This might just be a statement that changes the context (e.g.
// ".macro", ".proc", etc.). In this case change the context and leave
// early.
- if self.context.change_context(node)? {
- return Ok(());
+ match self.context.change_context(node) {
+ Ok(changed) => {
+ if changed {
+ return Ok(());
+ }
+ }
+ Err(message) => {
+ return Err(EvalError {
+ message,
+ line: node.value.line,
+ source: self.source_for(node),
+ global: false,
+ });
+ }
}
// Otherwise, check the function that could act as a statement that
@@ -1186,12 +1280,14 @@ impl<'a> Assembler<'a> {
NodeType::Control(ControlType::IncBin) => {
self.incbin(node.args.as_ref().unwrap().first().unwrap())
}
+ NodeType::Control(ControlType::IncludeSource) => Ok(()),
_ => Err(EvalError {
line: node.value.line,
message: format!(
"cannot handle control statement '{}' in this context",
node.value.value
),
+ source: self.source_for(node),
global: false,
}),
}
@@ -1211,6 +1307,7 @@ impl<'a> Assembler<'a> {
"path has to be written inside of double quotes ('{}' given instead)",
value,
),
+ source: self.source_for(node),
global: false,
});
}
@@ -1218,12 +1315,18 @@ impl<'a> Assembler<'a> {
// The '.incbin' control assumes that paths are relative to the
// directory of the current file. Hence, in order to make subsequent
// `File` operations work in this way, set the current directory now.
- if let Err(e) = std::env::set_current_dir(self.directories.last().unwrap()) {
- return Err(EvalError {
- line: node.value.line,
- message: format!("could not move to the directory of '{}': {}", value, e),
- global: false,
- });
+ match &self.sources.get(node.source) {
+ Some(source) => {
+ if let Err(e) = std::env::set_current_dir(&source.working_directory) {
+ return Err(EvalError {
+ line: node.value.line,
+ message: format!("could not move to the directory of '{}': {}", value, e),
+ source: self.source_for(node),
+ global: false,
+ });
+ }
+ }
+ None => panic!("mismatch on the node source"),
}
// Fetch the actual path.
@@ -1234,6 +1337,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: format!("could not include binary data: {}", e),
})
}
@@ -1252,12 +1356,14 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: format!("file '{}' is too big", path),
});
} else if metadata.len() == 0 {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: format!("trying to include an empty file ('{}')", path),
});
}
@@ -1266,6 +1372,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: format!("could not include binary data: {}", e),
})
}
@@ -1293,6 +1400,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: "pointless .repeat statement".to_string(),
}
.into());
@@ -1300,6 +1408,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
global: false,
line: node.value.line,
+ source: self.source_for(node),
message: "the number of iterations has to fit in a single byte".to_string(),
}
.into());
@@ -1314,6 +1423,7 @@ impl<'a> Assembler<'a> {
"first argument must be an integer, '{}' found instead",
first
),
+ source: self.source_for(node),
}
.into())
}
@@ -1325,6 +1435,7 @@ impl<'a> Assembler<'a> {
self.warnings.push(Error::Eval(EvalError {
line: node.value.line,
message: "empty .repeat statement".to_string(),
+ source: self.source_for(node),
global: false,
}));
return Ok(());
@@ -1335,7 +1446,7 @@ impl<'a> Assembler<'a> {
// If an index was given, set it now as a .repeat variable with the
// loop index.
if args.len() == 2 {
- self.context.set_variable(
+ if let Err(e) = self.context.set_variable(
&args.last().unwrap().value,
&Object {
bundle: Bundle::fill(i as u8),
@@ -1344,7 +1455,15 @@ impl<'a> Assembler<'a> {
object_type: ObjectType::Value,
},
true,
- )?;
+ ) {
+ return Err(EvalError {
+ line: node.value.line,
+ message: e,
+ source: self.source_for(node),
+ global: false,
+ }
+ .into());
+ }
}
// And push all the bundles from the inner code.
@@ -1364,6 +1483,7 @@ impl<'a> Assembler<'a> {
"cannot handle control statement '{}' as an expression in this context",
node.value.value
),
+ source: self.source_for(node),
global: false,
}),
}
@@ -1413,6 +1533,7 @@ impl<'a> Assembler<'a> {
line: arg.value.line,
message: "expecting an argument that fits into a byte"
.to_string(),
+ source: self.source_for(node),
global: false,
})
}
@@ -1434,6 +1555,7 @@ impl<'a> Assembler<'a> {
"expecting at least one argument for '{}'",
node.value.value.as_str(),
),
+ source: self.source_for(node),
global: false,
})
}
@@ -1456,7 +1578,8 @@ impl<'a> Assembler<'a> {
"segment declaration has to be written inside of double quotes ('{}' given instead)",
val,
),
- global: false,
+ source: self.source_for(node),
+ global: false,
});
}
@@ -1469,6 +1592,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
message: "segment name contains bad characters".to_string(),
+ source: self.source_for(node),
global: false,
});
}
@@ -1490,18 +1614,20 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
message: format!("unknown segment '{}'", name),
+ source: self.source_for(node),
global: false,
});
}
Ok(())
}
- fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> {
- match self.context.get_variable(id, &self.mappings) {
+ fn evaluate_variable(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
+ match self.context.get_variable(&node.value, &self.mappings) {
Ok(value) => Ok(value.bundle),
Err(e) => Err(EvalError {
- message: e.message,
- line: id.line,
+ message: e,
+ line: node.value.line,
+ source: self.source_for(node),
global: false,
}),
}
@@ -1532,15 +1658,17 @@ impl<'a> Assembler<'a> {
"cannot use {} addressing mode for the instruction '{}'",
mode, mnemonic
),
+ source: self.source_for(node),
line: node.value.line,
global: false,
- })
+ });
}
},
None => {
return Err(EvalError {
message: format!("unknown instruction {}", mnemonic),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1575,6 +1703,7 @@ impl<'a> Assembler<'a> {
"it has to be either X addressing or Y addressing, not all at once"
.to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1585,6 +1714,7 @@ impl<'a> Assembler<'a> {
message: "address can only be one byte long on indirect Y addressing"
.to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1593,6 +1723,7 @@ impl<'a> Assembler<'a> {
Err(EvalError {
message: "only the Y index is allowed on indirect Y addressing".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
})
}
@@ -1606,6 +1737,7 @@ impl<'a> Assembler<'a> {
"address can only be one byte long on indirect X addressing"
.to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1614,6 +1746,7 @@ impl<'a> Assembler<'a> {
Err(EvalError {
message: "only the X index is allowed on indirect X addressing".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
})
}
@@ -1623,6 +1756,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "expecting a full 16-bit address".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1644,6 +1778,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
message: "indexed addressing only works with addresses".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
});
}
@@ -1706,6 +1841,7 @@ impl<'a> Assembler<'a> {
_ => Err(EvalError {
message: "can only use X and Y as indices".to_string(),
line: node.value.line,
+ source: self.source_for(node),
global: false,
}),
}
@@ -1744,6 +1880,7 @@ impl<'a> Assembler<'a> {
_ => Err(EvalError {
message: "immediate is too big".to_string(),
line: left_arm.value.line,
+ source: self.source_for(base),
global: false,
}),
}
@@ -1755,6 +1892,7 @@ impl<'a> Assembler<'a> {
message: "left arm of instruction is neither an address nor an immediate"
.to_string(),
line: left_arm.value.line,
+ source: self.source_for(base),
global: false,
}),
}
@@ -1774,6 +1912,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
message: "you cannot branch to this location: it's too far away".to_string(),
+ source: self.source_for(node),
global: false,
});
}
@@ -1784,6 +1923,7 @@ impl<'a> Assembler<'a> {
return Err(EvalError {
line: node.value.line,
message: "you cannot branch to this location: it's too far away".to_string(),
+ source: self.source_for(node),
global: false,
});
}
@@ -1796,6 +1936,17 @@ impl<'a> Assembler<'a> {
Ok(())
}
+
+ // Builds a SourceInfo object based on the given node.
+ fn source_for(&self, node: &PNode) -> SourceInfo {
+ self.sources
+ .get(node.source)
+ .unwrap_or(&SourceInfo {
+ working_directory: self.sources[0].working_directory.clone(),
+ name: self.sources[0].name.clone(),
+ })
+ .clone()
+ }
}
#[cfg(test)]
@@ -1865,11 +2016,7 @@ mod tests {
// the assembler will freak out.
let real_line = minimal_header().to_string() + line;
- assemble_with_mapping(
- real_line.as_bytes(),
- empty(),
- std::env::current_dir().unwrap().to_path_buf(),
- )
+ assemble_with_mapping(real_line.as_bytes(), empty(), SourceInfo::default())
}
// Like `just_assemble` but it only returns bundles passed the header.
@@ -3313,7 +3460,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
assert_eq!(res.bundles.len(), 0x11);
@@ -3354,7 +3501,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
let bundles = &res.bundles[0x11..];
@@ -3427,7 +3574,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
let bundles = &res.bundles[0x12..]; // Ignoring HEADER + first two ONE
@@ -3474,7 +3621,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
let bundles = &res.bundles[0x11..]; // Ignoring HEADER + first nop
@@ -3528,7 +3675,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
let bundles = &res.bundles[0x10..];
@@ -3554,7 +3701,7 @@ lda #Variable
"#
.as_bytes(),
one_two().to_vec(),
- std::env::current_dir().unwrap().to_path_buf(),
+ SourceInfo::default(),
);
assert_eq!(
diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs
index ae55b83..3ca00bc 100644
--- a/lib/xixanta/src/errors.rs
+++ b/lib/xixanta/src/errors.rs
@@ -1,3 +1,4 @@
+use crate::SourceInfo;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
@@ -7,6 +8,18 @@ pub enum Error {
Eval(EvalError),
}
+impl From<ParseError> for Vec<Error> {
+ fn from(err: ParseError) -> Self {
+ vec![Error::Parse(err)]
+ }
+}
+
+impl From<ParseError> for Vec<ParseError> {
+ fn from(err: ParseError) -> Self {
+ vec![err]
+ }
+}
+
impl From<ContextError> for Vec<Error> {
fn from(err: ContextError) -> Self {
vec![Error::Context(err)]
@@ -33,13 +46,24 @@ impl fmt::Display for Error {
pub struct ParseError {
pub line: usize,
pub message: String,
+ pub source: SourceInfo,
}
impl std::error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{} (line {})", self.message, self.line + 1)
+ if self.source.name.is_empty() {
+ write!(f, "{} (line {})", self.message, self.line + 1)
+ } else {
+ write!(
+ f,
+ "{} ({}: line {})",
+ self.message,
+ self.source.name,
+ self.line + 1
+ )
+ }
}
}
@@ -61,6 +85,7 @@ pub struct ContextError {
pub reason: ContextErrorReason,
pub message: String,
pub global: bool,
+ pub source: SourceInfo,
}
impl std::error::Error for ContextError {}
@@ -68,9 +93,21 @@ impl std::error::Error for ContextError {}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.global {
- write!(f, "{}", self.message)
- } else {
+ if self.source.name.is_empty() {
+ write!(f, "{}", self.message)
+ } else {
+ write!(f, "{} ({})", self.message, self.source.name)
+ }
+ } else if self.source.name.is_empty() {
write!(f, "{} (line {})", self.message, self.line + 1)
+ } else {
+ write!(
+ f,
+ "{} ({}: line {})",
+ self.message,
+ self.source.name,
+ self.line + 1
+ )
}
}
}
@@ -80,6 +117,7 @@ pub struct EvalError {
pub line: usize,
pub message: String,
pub global: bool,
+ pub source: SourceInfo,
}
impl std::error::Error for EvalError {}
@@ -90,6 +128,7 @@ impl From<ContextError> for EvalError {
line: err.line,
message: err.message,
global: false,
+ source: SourceInfo::default(), // TODO
}
}
}
@@ -97,9 +136,21 @@ impl From<ContextError> for EvalError {
impl fmt::Display for EvalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.global {
- write!(f, "{}", self.message)
- } else {
+ if self.source.name.is_empty() {
+ write!(f, "{}", self.message)
+ } else {
+ write!(f, "{} ({})", self.message, self.source.name)
+ }
+ } else if self.source.name.is_empty() {
write!(f, "{} (line {})", self.message, self.line + 1)
+ } else {
+ write!(
+ f,
+ "{} ({}: line {})",
+ self.message,
+ self.source.name,
+ self.line + 1
+ )
}
}
}
diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs
index 0ad901f..95ead9c 100644
--- a/lib/xixanta/src/lib.rs
+++ b/lib/xixanta/src/lib.rs
@@ -1,6 +1,21 @@
#[macro_use]
extern crate lazy_static;
+#[derive(Clone, Debug, PartialEq)]
+pub struct SourceInfo {
+ pub working_directory: std::path::PathBuf,
+ pub name: String,
+}
+
+impl Default for SourceInfo {
+ fn default() -> Self {
+ SourceInfo {
+ working_directory: std::env::current_dir().unwrap().to_path_buf(),
+ name: "".to_string(),
+ }
+ }
+}
+
pub mod assembler;
pub mod errors;
pub mod node;
diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs
index 12b64c8..f86b513 100644
--- a/lib/xixanta/src/mapping.rs
+++ b/lib/xixanta/src/mapping.rs
@@ -1,4 +1,3 @@
-use crate::errors::EvalError;
use crate::object::Bundle;
use toml::{Table, Value};
@@ -279,18 +278,14 @@ fn validate_configuration(mappings: &[Mapping]) -> Result<(), String> {
/// Perform some sanity checks on the given `mappings`. Only call this function
/// after all bundles have been produced.
-pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> {
+pub fn validate(mappings: &[Mapping]) -> Result<(), String> {
// Guaranteed by `crate::mapping::assert` to be the header.
let header: &Segment = mappings.first().unwrap().segments.first().unwrap();
// Header must have at least six bytes with proper information provided by
// the programmer.
if header.len() < 6 {
- return Err(EvalError {
- line: 0,
- message: String::from("The header must contain at least 6 bytes"),
- global: true,
- });
+ return Err(String::from("The header must contain at least 6 bytes"));
}
// Now check that the length of the evaluated data matches the criteria
@@ -310,18 +305,16 @@ pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> {
});
if header_prg_rom_size < prg_rom_len {
- return Err(EvalError {
- line: 0,
- message: format!("PRG ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_prg_rom_size, prg_rom_len),
- global: true,
- });
+ return Err(format!(
+ "PRG ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated",
+ header_prg_rom_size, prg_rom_len
+ ));
}
if header_chr_rom_size < chr_rom_len {
- return Err(EvalError {
- line: 0,
- message: format!("CHR ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_chr_rom_size, chr_rom_len),
- global: true,
- });
+ return Err(format!(
+ "CHR ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated",
+ header_chr_rom_size, chr_rom_len
+ ));
}
Ok(())
@@ -329,39 +322,23 @@ pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> {
// Returns a tuple with the sizes for PRG and CHR ROM as described from the
// computed header. This also does some sanity checks on the header.
-fn parse_header(header: &Segment) -> Result<(usize, usize), EvalError> {
+fn parse_header(header: &Segment) -> Result<(usize, usize), String> {
let mut header_it = header.bundles.clone().into_iter();
// Validate the magic string: 'N', 'E', 'S', $1A
if header_it.next().unwrap().bytes[0] != b'N' {
- return Err(EvalError {
- line: 0,
- message: String::from("First byte of the header must be 'N'"),
- global: true,
- });
+ return Err(String::from("First byte of the header must be 'N'"));
}
if header_it.next().unwrap().bytes[0] != b'E' {
- return Err(EvalError {
- line: 0,
- message: String::from("Second byte of the header must be 'E'"),
- global: true,
- });
+ return Err(String::from("Second byte of the header must be 'E'"));
}
if header_it.next().unwrap().bytes[0] != b'S' {
- return Err(EvalError {
- line: 0,
- message: String::from("Third byte of the header must be 'S'"),
- global: true,
- });
+ return Err(String::from("Third byte of the header must be 'S'"));
}
if header_it.next().unwrap().bytes[0] != 26 {
- return Err(EvalError {
- line: 0,
- message: String::from(
- "Fourth byte of the header must be the MS-DOS termination character",
- ),
- global: true,
- });
+ return Err(String::from(
+ "Fourth byte of the header must be the MS-DOS termination character",
+ ));
}
Ok((
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index f919ade..1ec2ed9 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -140,6 +140,7 @@ pub enum ControlType {
IncBin,
StartRepeat,
EndRepeat,
+ IncludeSource,
}
impl fmt::Display for ControlType {
@@ -160,6 +161,7 @@ impl fmt::Display for ControlType {
ControlType::IncBin => write!(f, ".incbin"),
ControlType::StartRepeat => write!(f, ".repeat"),
ControlType::EndRepeat => write!(f, ".endrepeat"),
+ ControlType::IncludeSource => write!(f, ".include"),
}
}
}
@@ -305,7 +307,7 @@ impl NodeType {
}
}
-/// A Position Node. This is a node on a binary tree which holds a PString as a
+/// A Positioned Node. This is a node on a tree which holds a PString as a
/// value. The node type determines the actual representation of the value and
/// both childs (see the `NodeType` enum). Moreover, out of convenience, a node
/// also holds an optional list of arguments, which simplifies arrangements such
@@ -330,6 +332,10 @@ pub struct PNode {
/// Convenience list used by some node types in order to express a list of
/// optional arguments.
pub args: Option<Vec<PNode>>,
+
+ /// Index on the list of SourceInfo's maintained by both the parser and the
+ /// assembler.
+ pub source: usize,
}
/// Whether there is a body for a given node and whether it starts or ends it.
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs
index 0d8bcc6..77ab8a6 100644
--- a/lib/xixanta/src/object.rs
+++ b/lib/xixanta/src/object.rs
@@ -1,4 +1,3 @@
-use crate::errors::{ContextError, ContextErrorReason};
use crate::mapping::Mapping;
use crate::node::{ControlType, NodeType, PNode, PString};
use crate::opcodes::CONTROL_FUNCTIONS;
@@ -174,7 +173,7 @@ impl Context {
/// this `id` can be scoped or not, and this function will try to pick the
/// variable from the right scope. The value itself will be resolved if the
/// type is ObjectType::Address.
- pub fn get_variable(&self, id: &PString, mappings: &[Mapping]) -> Result<Object, ContextError> {
+ pub fn get_variable(&self, id: &PString, mappings: &[Mapping]) -> Result<Object, String> {
// First of all, figure out the name of the scope and the real name of
// the variable. If this was not scoped at all (None case when trying to
// rsplit by the "::" operator), then we assume on the current scope.
@@ -183,7 +182,7 @@ impl Context {
None => (self.name(), id.value.as_str()),
};
- self.get_variable_in_scope(id.line, scope_name, var_name, mappings)
+ self.get_variable_in_scope(scope_name, var_name, mappings)
}
// Get the `var_name` variable on the `scope_name` scope (or parents). For
@@ -191,11 +190,10 @@ impl Context {
// labels, and `line` when producing context errors.
fn get_variable_in_scope(
&self,
- line: usize,
scope_name: &str,
var_name: &str,
mappings: &[Mapping],
- ) -> Result<Object, ContextError> {
+ ) -> Result<Object, String> {
// And with that, the only thing left is to find the scope and the
// variable in it.
match self.map.get(scope_name) {
@@ -209,16 +207,11 @@ impl Context {
// the scope hierarchy to see if we can fetch it there. For
// that, though, we first prepare an error so we return the
// original one, not the propagated one (see below).
- let err = Err(ContextError {
- message: format!(
- "could not find variable '{}' in {}",
- var_name,
- self.to_human_with(scope_name)
- ),
- line,
- reason: ContextErrorReason::UnknownVariable,
- global: false,
- });
+ let err = Err(format!(
+ "could not find variable '{}' in {}",
+ var_name,
+ self.to_human_with(scope_name)
+ ));
// If we are already in the global context and the object
// was not found, just leave with an error.
@@ -231,9 +224,7 @@ impl Context {
// error so it better reflects the original scope where
// this was first attempted.
let parent = self.parent(scope_name);
- if let Ok(object) =
- self.get_variable_in_scope(line, parent, var_name, mappings)
- {
+ if let Ok(object) = self.get_variable_in_scope(parent, var_name, mappings) {
Ok(object)
} else {
err
@@ -241,12 +232,7 @@ impl Context {
}
}
},
- None => Err(ContextError {
- message: format!("did not find scope '{}'", scope_name),
- line,
- reason: ContextErrorReason::BadScope,
- global: false,
- }),
+ None => Err(format!("did not find scope '{}'", scope_name)),
}
}
@@ -255,11 +241,7 @@ impl Context {
///
/// NOTE: this function asserts that the given `object` is of type
/// ObjectType::Address, otherwise it doesn't make sense to call it.
- pub fn resolve_label(
- &self,
- mappings: &[Mapping],
- object: &Object,
- ) -> Result<Object, ContextError> {
+ pub fn resolve_label(&self, mappings: &[Mapping], object: &Object) -> Result<Object, String> {
assert!(matches!(object.object_type, ObjectType::Address));
let mut ret = object.clone();
@@ -271,12 +253,7 @@ impl Context {
// Avoid weird out of bound references for addresses.
if addr > u16::MAX as usize {
- return Err(ContextError {
- line: 0,
- message: format!("address {:x} is out of bounds", addr),
- reason: ContextErrorReason::Bounds,
- global: true,
- });
+ return Err(format!("address {:x} is out of bounds", addr));
}
let addr_bytes = (addr as u16).to_le_bytes();
@@ -295,23 +272,18 @@ impl Context {
id: &PString,
object: &Object,
overwrite: bool,
- ) -> Result<(), ContextError> {
+ ) -> Result<(), String> {
let scope_name = self.name().to_string();
let scope = self.map.get_mut(&scope_name).unwrap();
match scope.get_mut(&id.value) {
Some(sc) => {
if !overwrite {
- return Err(ContextError {
- message: format!(
- "'{}' already defined in {}: you cannot re-assign names",
- id.value,
- self.to_human()
- ),
- line: id.line,
- reason: ContextErrorReason::Redefinition,
- global: false,
- });
+ return Err(format!(
+ "'{}' already defined in {}: you cannot re-assign names",
+ id.value,
+ self.to_human()
+ ));
}
*sc = object.clone();
}
@@ -333,7 +305,7 @@ impl Context {
/// Change the current context given a `node`. Returns true if the context
/// has changed.
- pub fn change_context(&mut self, node: &PNode) -> Result<bool, ContextError> {
+ pub fn change_context(&mut self, node: &PNode) -> Result<bool, String> {
// The parser already guarantees that the control node is
// from a function that we already know, so calling `unwrap`
// is not dangerous.
@@ -406,7 +378,7 @@ impl Context {
rel: isize,
labels_seen: usize,
mappings: &[Mapping],
- ) -> Result<Object, ContextError> {
+ ) -> Result<Object, String> {
// Bound check: the given 'rel' parameter has a proper value.
assert!(
rel < 5 && rel > -5 && rel != 0,
@@ -416,12 +388,7 @@ impl Context {
// Bound check: you cannot reference a past label that doesn't exist.
// This is the programmer's to blame, not on us, so don't assert.
if labels_seen == 0 && rel < 0 {
- return Err(ContextError {
- line: 0,
- message: "cannot reference an unknown previous label".to_string(),
- reason: ContextErrorReason::Label,
- global: false,
- });
+ return Err("cannot reference an unknown previous label".to_string());
}
// Get the labels as referenced in the current context, and also the
@@ -437,12 +404,7 @@ impl Context {
// Bound check: is the programmer referencing an "out of bounds" label?
// If so then it's a mistake on their part.
if idx < 0 || idx >= labels.len() as isize {
- return Err(ContextError {
- line: 0,
- message: "cannot reference bogus label (out of bounds)".to_string(),
- reason: ContextErrorReason::Label,
- global: false,
- });
+ return Err("cannot reference bogus label (out of bounds)".to_string());
}
// Everything should be fine from here on, simply return the bundle that
@@ -466,14 +428,9 @@ impl Context {
}
// Pops out the latest context that was pushed.
- fn context_pop(&mut self, id: &PString) -> Result<(), ContextError> {
+ fn context_pop(&mut self, id: &PString) -> Result<(), String> {
if self.stack.is_empty() {
- return Err(ContextError {
- message: format!("missplaced '{}' statement", id.value),
- reason: ContextErrorReason::BadScope,
- line: id.line,
- global: false,
- });
+ return Err(format!("missplaced '{}' statement", id.value));
}
self.stack.truncate(self.stack.len() - 1);
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index 6c0aa0d..593d279 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -748,6 +748,7 @@ lazy_static! {
functions.insert(String::from(".incbin"), Control { control_type: ControlType::IncBin, has_identifier: None, required_args: Some((1, 1)), touches_context: false });
functions.insert(String::from(".repeat"), Control { control_type: ControlType::StartRepeat, has_identifier: Some(true), required_args: Some((1, 2)), touches_context: true });
functions.insert(String::from(".endrepeat"), Control { control_type: ControlType::EndRepeat, has_identifier: None, required_args: None, touches_context: true });
+ functions.insert(String::from(".include"), Control { control_type: ControlType::IncludeSource, has_identifier: None, required_args: Some((1, 1)), touches_context: false });
functions
};
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index 0757505..fc53b08 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -1,9 +1,11 @@
use crate::errors::ParseError;
-use crate::node::{NodeBodyType, NodeType, OperationType, PNode, PString};
+use crate::node::{ControlType, NodeBodyType, NodeType, OperationType, PNode, PString};
use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
+use crate::SourceInfo;
use rand::distributions::{Alphanumeric, DistString};
use std::cmp::Ordering;
use std::io::{self, BufRead, Read};
+use std::path;
/// The Parser struct holds basic data for the current parsing session.
#[derive(Default)]
@@ -35,22 +37,46 @@ pub struct Parser {
/// statements (e.g. '.endproc') go with their respective start ones (e.g.
/// '.proc'), and they don't close another block.
bodies: Vec<NodeType>,
+
+ /// List of sources that have been parsed for this parsing session. Consume
+ /// it after calling `parse` in order to get the list of sources that have
+ /// been evaluated. Note that the `source` index on each PNode points to
+ /// this list.
+ pub sources: Vec<SourceInfo>,
+
+ /// The index of the source that explicitely belongs to this session (and
+ /// not subsequent calls done afterwards). Initialized on `parse`.
+ current_source: usize,
}
impl Parser {
/// Parse the input from the given `reader`. You can then access the results
/// from the `nodes` field. Otherwise, a vector of ParseError's might be
/// returned.
- pub fn parse(&mut self, reader: impl Read) -> Result<(), Vec<ParseError>> {
+ pub fn parse(&mut self, reader: impl Read, source: SourceInfo) -> Result<(), Vec<ParseError>> {
let mut errors = Vec::new();
+ // Push the sources for the parsing session (note that this list might
+ // be initialized by the caller already). The `current_source` is simply
+ // the one we push upon initialization.
+ self.sources.push(SourceInfo {
+ working_directory: path::absolute(&source.working_directory)
+ .unwrap_or(source.working_directory),
+ name: source.name,
+ });
+ self.current_source = self.sources.len() - 1;
+
+ // Make sure that there is a first layer of nodes.
self.nodes.push(vec![]);
+ // This is a line parser (i.e. there cannot be statements which span
+ // more than one line). Hence, consume the reader line by line and parse
+ // each one.
for line in io::BufReader::new(reader).lines() {
match line {
Ok(l) => {
- if let Err(err) = self.parse_line(l.as_str()) {
- errors.push(err);
+ if let Err(mut err) = self.parse_line(l.as_str()) {
+ errors.append(&mut err);
}
}
Err(_) => errors.push(self.parser_error("could not get line")),
@@ -58,7 +84,7 @@ impl Parser {
self.line += 1;
}
- // Are there any more statements which are begging for a closing
+ // Are there any more statements which are still waiting for a closing
// statement? If so, then there's something wrong.
if !self.bodies.is_empty() {
errors.push(
@@ -91,7 +117,7 @@ impl Parser {
}
// Parse a single `line` and push the parsed nodes into `self.nodes`.
- fn parse_line(&mut self, line: &str) -> Result<(), ParseError> {
+ fn parse_line(&mut self, line: &str) -> Result<(), Vec<ParseError>> {
self.column = 0;
self.offset = 0;
@@ -132,6 +158,7 @@ impl Parser {
left: None,
right: None,
args: None,
+ source: self.current_source,
});
self.skip_whitespace(l);
@@ -147,7 +174,9 @@ impl Parser {
self.offset = 0;
(id, nt) = self.parse_identifier(l)?;
if nt == NodeType::Label {
- return Err(self.parser_error("cannot have multiple labels at the same location"));
+ return Err(self
+ .parser_error("cannot have multiple labels at the same location")
+ .into());
}
self.skip_whitespace(l);
@@ -210,8 +239,9 @@ impl Parser {
// there is something wrong (e.g. "Bad:+").
if base_offset != self.offset {
return Err(ParseError {
- line: self.line,
- message: "you cannot have a relative label inside of an identifier".to_string(),
+ line: self.line,
+ source: self.sources[self.current_source].clone(),
+ message: "you cannot have a relative label inside of an identifier".to_string(),
});
}
@@ -233,6 +263,7 @@ impl Parser {
if size > 4 {
return Err(ParseError {
line: self.line,
+ source: self.sources[self.current_source].clone(),
message: "you can only jump to a maximum of four relative labels".to_string(),
});
}
@@ -244,6 +275,7 @@ impl Parser {
if next == '+' { "forward" } else { "backward" };
return Err(ParseError {
line: self.line,
+ source: self.sources[self.current_source].clone(),
message: format!(
"{} relative label can only have '{}' characters",
msg, next
@@ -312,17 +344,17 @@ impl Parser {
// Parse the top-level statement as found on the given `line` which has a
// leading `id` positioned-string which may be an identifier.
- fn parse_statement(&mut self, line: &str, id: PString) -> Result<(), ParseError> {
+ fn parse_statement(&mut self, line: &str, id: PString) -> Result<(), Vec<ParseError>> {
// There are only two top-level statements: instructions and
// assignments. Other kinds of expressions can also be used in the
// middle of assignments or instructions, and so they have to be handled
// as common expressions. Whether expressions make sense at the
// different levels is something to be figured out by the assembler.
match INSTRUCTIONS.get(&id.value) {
- Some(_) => self.parse_instruction(line, id),
+ Some(_) => Ok(self.parse_instruction(line, id)?),
None => {
if line.contains('=') {
- self.parse_assignment(line, id)
+ Ok(self.parse_assignment(line, id)?)
} else {
self.parse_other(line, id)
}
@@ -426,6 +458,7 @@ impl Parser {
left,
right,
args: None,
+ source: self.current_source,
}));
// Do we have anything as a right arm?
@@ -454,6 +487,7 @@ impl Parser {
left,
right,
args: None,
+ source: self.current_source,
});
Ok(())
@@ -494,6 +528,7 @@ impl Parser {
left: Some(Box::new(left)),
right: None,
args: None,
+ source: self.current_source,
});
Ok(())
@@ -501,15 +536,42 @@ impl Parser {
// Parse statements which are neither an instruction nor an assignment. This
// includes stuff like control statements.
- fn parse_other(&mut self, line: &str, id: PString) -> Result<(), ParseError> {
+ fn parse_other(&mut self, line: &str, id: PString) -> Result<(), Vec<ParseError>> {
+ // The main job of this function is to parse the expression and
+ // afterwards deal with corner cases. So, let's first just parse this.
let node = self.parse_expression_with_identifier(id, line)?;
- let node_type = node.node_type.clone();
- let body_type = node.body_type();
+
+ // If this was an .include statement we have to handle it now as this is
+ // not a regular statement but more like a preprocessor statement which
+ // can lead to inner parsing sessions.
+ if matches!(
+ &node.node_type,
+ NodeType::Control(ControlType::IncludeSource)
+ ) {
+ // Validate that it's a top layer statement.
+ if self.nodes.len() > 1 {
+ return Err(ParseError {
+ line: node.value.line,
+ message: ".include statement cannot be inside of a code block".to_string(),
+ source: self.sources[self.current_source].clone(),
+ }
+ .into());
+ }
+
+ // Before including all the nodes from the referenced file, add the
+ // .include statement. This should be ignored by the assembler, but
+ // maybe other tools want to make use of it.
+ self.nodes.last_mut().unwrap().push(node.clone());
+
+ // And append nodes from the referenced source.
+ return self.include_source(&node);
+ }
// The given statement might be a start/end one (e.g. '.proc' and
// '.endproc'). In these cases there are some things to handle besides
// pushing the node into the current level of nodes.
- match body_type {
+ let node_type = node.node_type.clone();
+ match node.body_type() {
NodeBodyType::Starts => {
self.bodies.push(node_type.closing_type().unwrap());
self.nodes.last_mut().unwrap().push(node);
@@ -521,15 +583,18 @@ impl Parser {
let expected_close = match self.bodies.pop() {
Some(ec) => ec,
None => {
- return Err(
- self.parser_error(format!("unexpected '{}'", node_type).as_str())
- )
+ return Err(self
+ .parser_error(format!("unexpected '{}'", node_type).as_str())
+ .into())
}
};
if node_type != expected_close {
- return Err(self.parser_error(
- format!("expecting '{}', found '{}'", expected_close, node_type).as_str(),
- ));
+ return Err(self
+ .parser_error(
+ format!("expecting '{}', found '{}'", expected_close, node_type)
+ .as_str(),
+ )
+ .into());
}
// Note that empty bodies are possible. This is left
@@ -542,6 +607,7 @@ impl Parser {
left: None,
right: None,
args: Some(nodes),
+ source: self.current_source,
}));
self.nodes.last_mut().unwrap().push(node);
}
@@ -551,6 +617,98 @@ impl Parser {
Ok(())
}
+ // Consume the given `node` by assuming it's an `.include` statement. This
+ // will in turn produce a new parsing session for the given file if possible
+ // and push the parsed nodes from it to our current list.
+ fn include_source(&mut self, node: &PNode) -> Result<(), Vec<ParseError>> {
+ // First of all, parse the string as it was given and join it with our
+ // working directory. This way we construct the absolute path for the
+ // given file.
+ let file_path = self.fetch_path_from(node.args.as_ref().unwrap().first().unwrap())?;
+ let Some(current_source) = self.sources.get(self.current_source) else {
+ panic!("mismatch between the number of sources and the current one");
+ };
+ let abs_file = current_source.working_directory.join(file_path);
+
+ // Validate that this is really a path that points to a file.
+ let path = std::path::Path::new(&abs_file);
+ if !path.is_file() {
+ return Err(ParseError {
+ line: node.value.line,
+ source: current_source.clone(),
+ message: "expecting a file ({})".to_string(),
+ }
+ .into());
+ }
+
+ // And open the file. This is the object to be passed as a reader for
+ // the recursive `parse` call, but it also allows us to construct the
+ // SourceInfo for the next session because we need to point to its
+ // parent in the file system.
+ let file = match std::fs::File::open(path) {
+ Ok(f) => f,
+ Err(e) => {
+ return Err(ParseError {
+ line: node.value.line,
+ source: current_source.clone(),
+ message: format!("could not open source file: {}", e),
+ }
+ .into())
+ }
+ };
+ let Some(parent) = path.parent() else {
+ return Err(ParseError {
+ line: node.value.line,
+ source: current_source.clone(),
+ message: "could not find out the parent directory for file".to_string(),
+ }
+ .into());
+ };
+
+ // Set up a new parsing session, push the current sources, and call
+ // `parse`.
+ let mut parser = Parser {
+ sources: self.sources.clone(),
+ ..Default::default()
+ };
+ parser.parse(
+ file,
+ SourceInfo {
+ working_directory: parent.to_path_buf(),
+ name: path.file_name().unwrap().to_str().unwrap().to_string(),
+ },
+ )?;
+
+ // If everything was fine on the previous parsing session, append the
+ // nodes from it and also grab its sources, since the previous parsing
+ // session might have done further parsing iterations of its own.
+ self.nodes.last_mut().unwrap().append(&mut parser.nodes());
+ self.sources = parser.sources.clone();
+
+ Ok(())
+ }
+
+ // Returns a string containing the path being referenced in the given node.
+ // The path is assumed to be available directly inside of the value of the
+ // PNode.
+ fn fetch_path_from<'a>(&mut self, node: &'a PNode) -> Result<&'a str, ParseError> {
+ let value = &node.value.value;
+
+ // Validate the path literal.
+ if value.len() < 3 || !value.starts_with('"') || !value.ends_with('"') {
+ return Err(ParseError {
+ line: node.value.line,
+ source: self.sources[self.current_source].clone(),
+ message: format!(
+ "path has to be written inside of double quotes ('{}' given instead)",
+ value,
+ ),
+ });
+ }
+
+ Ok(value[1..value.len() - 1].trim())
+ }
+
// Parse any possible arguments for the given `line`. The offset is supposed
// to be at a point where arguments might appear, either between parens or
// not.
@@ -765,6 +923,7 @@ impl Parser {
left: None,
right: Some(Box::new(right)),
args: None,
+ source: self.current_source,
});
}
@@ -854,6 +1013,7 @@ impl Parser {
if next == '#' || (start != '#' && (next == '$' || next == '%')) {
return Err(ParseError {
line: id.line,
+ source: self.sources[self.current_source].clone(),
message: "bad literal syntax".to_string(),
});
}
@@ -880,6 +1040,7 @@ impl Parser {
left: None,
right: None,
args: None,
+ source: self.current_source,
}));
// Fetch a trimmed version of the string that comes after the
@@ -905,6 +1066,7 @@ impl Parser {
left,
right: Some(Box::new(right)),
args: None,
+ source: self.current_source,
});
}
@@ -919,6 +1081,7 @@ impl Parser {
left: None,
right: None,
args: if args.is_empty() { None } else { Some(args) },
+ source: self.current_source,
});
}
@@ -931,6 +1094,7 @@ impl Parser {
left: None,
right: None,
args: None,
+ source: self.current_source,
})
}
}
@@ -975,6 +1139,7 @@ impl Parser {
left: None,
right: None,
args: None,
+ source: self.current_source,
}));
}
@@ -997,6 +1162,7 @@ impl Parser {
left,
right: None,
args: if args.is_empty() { None } else { Some(args) },
+ source: self.current_source,
})
}
@@ -1020,6 +1186,7 @@ impl Parser {
if c.is_whitespace() {
return Err(ParseError {
line: id.line,
+ source: self.sources[self.current_source].clone(),
message: "numeric literals cannot have white spaces".to_string(),
});
}
@@ -1035,6 +1202,7 @@ impl Parser {
left: Some(Box::new(left)),
right: None,
args: None,
+ source: self.current_source,
})
}
@@ -1073,9 +1241,11 @@ impl Parser {
left: None,
right: None,
args: None,
+ source: self.current_source,
})),
right: None,
args: None,
+ source: self.current_source,
})
}
@@ -1083,6 +1253,7 @@ impl Parser {
fn parser_error(&self, msg: &str) -> ParseError {
ParseError {
message: String::from(msg),
+ source: self.sources[self.current_source].clone(),
line: self.line,
}
}
@@ -1122,7 +1293,7 @@ mod tests {
use crate::node::ControlType;
fn assert_one_valid(parser: &mut Parser, line: &str) {
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
assert!(parser.nodes.len() == 1);
}
@@ -1140,14 +1311,16 @@ mod tests {
#[test]
fn empty_line() {
let mut parser = Parser::default();
- assert!(parser.parse("".as_bytes()).is_ok());
+ assert!(parser.parse("".as_bytes(), SourceInfo::default()).is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
#[test]
fn spaced_line() {
let mut parser = Parser::default();
- assert!(parser.parse(" ".as_bytes()).is_ok());
+ assert!(parser
+ .parse(" ".as_bytes(), SourceInfo::default())
+ .is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
@@ -1155,7 +1328,7 @@ mod tests {
fn just_a_comment_line() {
for line in vec![";; This is a comment", " ;; Comment"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
assert_eq!(parser.nodes.last().unwrap().len(), 0);
}
}
@@ -1165,7 +1338,7 @@ mod tests {
#[test]
fn anonymous_label() {
let mut parser = Parser::default();
- assert!(parser.parse(":".as_bytes()).is_ok());
+ assert!(parser.parse(":".as_bytes(), SourceInfo::default()).is_ok());
let mut nodes = parser.nodes.last().unwrap();
assert_eq!(nodes.len(), 1);
@@ -1174,7 +1347,9 @@ mod tests {
assert_eq!(nodes.first().unwrap().value.end, 0);
parser = Parser::default();
- assert!(parser.parse(" :".as_bytes()).is_ok());
+ assert!(parser
+ .parse(" :".as_bytes(), SourceInfo::default())
+ .is_ok());
nodes = parser.nodes.last().unwrap();
assert_eq!(nodes.len(), 1);
@@ -1186,7 +1361,9 @@ mod tests {
#[test]
fn named_label() {
let mut parser = Parser::default();
- assert!(parser.parse("label:".as_bytes()).is_ok());
+ assert!(parser
+ .parse("label:".as_bytes(), SourceInfo::default())
+ .is_ok());
let mut nodes = parser.nodes.last().unwrap();
assert_eq!(nodes.len(), 1);
@@ -1195,7 +1372,9 @@ mod tests {
assert_eq!(nodes.first().unwrap().value.end, 5);
parser = Parser::default();
- assert!(parser.parse(" label:".as_bytes()).is_ok());
+ assert!(parser
+ .parse(" label:".as_bytes(), SourceInfo::default())
+ .is_ok());
nodes = parser.nodes.last().unwrap();
assert_eq!(nodes.len(), 1);
@@ -1209,7 +1388,7 @@ mod tests {
let line = "label: dex";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 2);
@@ -1229,7 +1408,7 @@ mod tests {
fn parse_pound_literal() {
for line in vec!["#20", " #20 ", " #20 ; Comment", " label: #20"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_eq!(node.node_type, NodeType::Literal);
@@ -1247,7 +1426,7 @@ mod tests {
fn parse_compound_literal() {
let line = "#$20";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_eq!(node.node_type, NodeType::Literal);
@@ -1273,7 +1452,7 @@ mod tests {
fn parse_variable_in_literal() {
let line = "#Variable";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_eq!(node.node_type, NodeType::Literal);
@@ -1293,7 +1472,7 @@ mod tests {
fn parse_paren_expression() {
let line = "ldx #(Variable)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let instr = parser.nodes.last().unwrap().last().unwrap();
assert_eq!(instr.node_type, NodeType::Instruction);
@@ -1318,14 +1497,18 @@ mod tests {
fn parse_bad_literals() {
for line in vec!["#", "#%", "$"].into_iter() {
let mut parser = Parser::default();
- let err = parser.parse(line.as_bytes()).unwrap_err();
+ let err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "invalid identifier");
}
for line in vec!["$ 2", "#% 2", "# 2"].into_iter() {
let mut parser = Parser::default();
- let err = parser.parse(line.as_bytes()).unwrap_err();
+ let err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -1335,7 +1518,9 @@ mod tests {
for line in vec!["##2", "#$$2", "$$2", "#$#$2", "#$#2", "###"].into_iter() {
let mut parser = Parser::default();
- let err = parser.parse(line.as_bytes()).unwrap_err();
+ let err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "bad literal syntax");
}
@@ -1355,7 +1540,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "dex");
@@ -1441,7 +1626,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "inc");
@@ -1468,7 +1653,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -1493,7 +1678,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -1510,7 +1695,9 @@ mod tests {
fn bad_indirect_addressing_x() {
let mut parser = Parser::default();
- let err = parser.parse("lda (Variable, x), y".as_bytes()).unwrap_err();
+ let err = parser
+ .parse("lda (Variable, x), y".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "bad indirect addressing");
}
@@ -1518,7 +1705,7 @@ mod tests {
fn indirect_addressing_y() {
for line in vec!["lda ($20), y"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -1537,7 +1724,7 @@ mod tests {
fn variable_in_instruction() {
let line = "lda Variable, x";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -1556,7 +1743,7 @@ mod tests {
fn variable_literal_in_instruction() {
let line = "lda #Variable, x";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -1576,7 +1763,7 @@ mod tests {
for var in vec!["Scope::Variable", "Scope::Inner::Variable"].into_iter() {
let line = format!("lda #{}", var);
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line.as_str(), "lda");
@@ -1596,7 +1783,9 @@ mod tests {
fn bad_variable_scoping() {
let mut parser = Parser::default();
- let err = parser.parse("adc #One:Variable".as_bytes()).unwrap_err();
+ let err = parser
+ .parse("adc #One:Variable".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"not expecting a label defined here"
@@ -1607,7 +1796,9 @@ mod tests {
fn reserved_mnemonic_name() {
let mut parser = Parser::default();
- let err = parser.parse("lda = $10".as_bytes()).unwrap_err();
+ let err = parser
+ .parse("lda = $10".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"cannot use the reserved mnemonic 'lda' as a variable name"
@@ -1619,7 +1810,7 @@ mod tests {
for label in vec![":+", ":++", ":+++ ", ":++++", ":-", ":--", ":---", ":----"].into_iter() {
let line = format!("jmp {}", label);
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line.as_str(), "jmp");
@@ -1640,28 +1831,36 @@ mod tests {
let mut parser = Parser::default();
let mut line = "jmp :+++++";
- let mut err = parser.parse(line.as_bytes()).unwrap_err();
+ let mut err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"you can only jump to a maximum of four relative labels"
);
line = "jmp :+-";
- err = parser.parse(line.as_bytes()).unwrap_err();
+ err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"forward relative label can only have '+' characters"
);
line = "jmp :-+-";
- err = parser.parse(line.as_bytes()).unwrap_err();
+ err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"backward relative label can only have '-' characters"
);
line = "jmp Identifier:++";
- err = parser.parse(line.as_bytes()).unwrap_err();
+ err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"you cannot have a relative label inside of an identifier"
@@ -1674,22 +1873,30 @@ mod tests {
fn bad_assignments() {
let mut parser = Parser::default();
- let mut err = parser.parse("abc = $10".as_bytes()).unwrap_err();
+ let mut err = parser
+ .parse("abc = $10".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"cannot use names which are valid hexadecimal values such as 'abc'"
);
parser = Parser::default();
- err = parser.parse("var =".as_bytes()).unwrap_err();
+ err = parser
+ .parse("var =".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
parser = Parser::default();
- err = parser.parse("var = ".as_bytes()).unwrap_err();
+ err = parser
+ .parse("var = ".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
parser = Parser::default();
- err = parser.parse("var = ; Comment".as_bytes()).unwrap_err();
+ err = parser
+ .parse("var = ; Comment".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
}
@@ -1699,7 +1906,7 @@ mod tests {
fn constant_expression_test() {
let line = "ldx #(4 * NUM_SPRITES)";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "ldx");
@@ -1724,7 +1931,7 @@ mod tests {
fn unary_operator_test() {
let line = "ldx #<NUM_SPRITES";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "ldx");
@@ -1751,7 +1958,7 @@ mod tests {
fn parse_control_no_args() {
for line in vec![".byte", " .byte", " label: .byte ; Comment"].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -1773,7 +1980,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(
@@ -1803,7 +2010,7 @@ mod tests {
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte");
@@ -1821,7 +2028,7 @@ mod tests {
fn parse_byte_with_character_literals() {
let line = ".byte 'N', 'E', 'S', $1A";
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let args = parser
.nodes
@@ -1873,7 +2080,7 @@ mod tests {
let real = String::from(line) + "\n.endscope";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes()).is_ok());
+ assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -1907,7 +2114,7 @@ mod tests {
let real = String::from(line) + "\n.endmacro";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes()).is_ok());
+ assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -1944,7 +2151,7 @@ mod tests {
let real = String::from(line) + "\n.endmacro";
let mut parser = Parser::default();
- assert!(parser.parse(real.as_bytes()).is_ok());
+ assert!(parser.parse(real.as_bytes(), SourceInfo::default()).is_ok());
let nodes = parser.nodes();
let node = &nodes[nodes.len() - 2];
@@ -1969,7 +2176,9 @@ mod tests {
#[test]
fn parse_control_unclosed() {
let mut parser = Parser::default();
- let err = parser.parse(".macro MACRO".as_bytes()).unwrap_err();
+ let err = parser
+ .parse(".macro MACRO".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -1980,7 +2189,9 @@ mod tests {
#[test]
fn parse_control_too_many_closes() {
let mut parser = Parser::default();
- let err = parser.parse(".endmacro".as_bytes()).unwrap_err();
+ let err = parser
+ .parse(".endmacro".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -1995,7 +2206,9 @@ mod tests {
.endscope
.endmacro"#;
let mut parser = Parser::default();
- let err = parser.parse(code.as_bytes()).unwrap_err();
+ let err = parser
+ .parse(code.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2014,7 +2227,7 @@ nop
inc $20
.endmacro"#;
let mut parser = Parser::default();
- assert!(parser.parse(code.as_bytes()).is_ok());
+ assert!(parser.parse(code.as_bytes(), SourceInfo::default()).is_ok());
let nodes = parser.nodes();
assert_eq!(nodes.len(), 2); // .macro and .endmacro
@@ -2047,7 +2260,9 @@ inc $20
fn parse_control_bad_number_args() {
for line in vec![".hibyte", ".hibyte($20, $22)"].into_iter() {
let mut parser = Parser::default();
- let err = parser.parse(line.as_bytes()).unwrap_err();
+ let err = parser
+ .parse(line.as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2060,7 +2275,7 @@ inc $20
fn parse_control_in_instructions() {
for line in vec!["lda #.hibyte($2010)", " label: lda #.hibyte $2010 "].into_iter() {
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2097,7 +2312,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2141,7 +2356,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Instruction, line, "lda");
@@ -2186,7 +2401,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Assignment, line, "lala");
@@ -2217,7 +2432,9 @@ inc $20
#[test]
fn parse_repeat_control() {
let mut parser = Parser::default();
- let err = parser.parse(".repeat\n.endrepeat".as_bytes()).unwrap_err();
+ let err = parser
+ .parse(".repeat\n.endrepeat".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(
err.first().unwrap().message,
"wrong number of arguments for function '.repeat'"
@@ -2225,7 +2442,10 @@ inc $20
let mut parser = Parser::default();
let err = parser
- .parse(".repeat 1, 2, 3\n.endrepeat".as_bytes())
+ .parse(
+ ".repeat 1, 2, 3\n.endrepeat".as_bytes(),
+ SourceInfo::default(),
+ )
.unwrap_err();
assert_eq!(
err.first().unwrap().message,
@@ -2237,7 +2457,7 @@ inc $20
parser = Parser::default();
let mut line = ".repeat 2\n.endrepeat";
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let mut control = parser.nodes.last().unwrap().first().unwrap();
assert_node(
control,
@@ -2263,7 +2483,7 @@ inc $20
parser = Parser::default();
line = ".repeat 2, I\n.endrepeat";
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
control = parser.nodes.last().unwrap().first().unwrap();
assert_node(
control,
@@ -2289,11 +2509,15 @@ inc $20
#[test]
fn parse_unknown_control() {
let mut parser = Parser::default();
- let mut err = parser.parse(".".as_bytes()).unwrap_err();
+ let mut err = parser
+ .parse(".".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "unknown function '.'");
parser = Parser::default();
- err = parser.parse(".whatever".as_bytes()).unwrap_err();
+ err = parser
+ .parse(".whatever".as_bytes(), SourceInfo::default())
+ .unwrap_err();
assert_eq!(err.first().unwrap().message, "unknown function '.whatever'");
}
@@ -2309,7 +2533,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Value, line, "MACRO_CALL");
@@ -2331,7 +2555,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -2352,7 +2576,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");
@@ -2376,7 +2600,7 @@ inc $20
.into_iter()
{
let mut parser = Parser::default();
- assert!(parser.parse(line.as_bytes()).is_ok());
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
let node = parser.nodes.last().unwrap().last().unwrap();
assert_node(node, NodeType::Call, line, "MACRO_CALL");