aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-23 15:45:49 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-23 15:45:49 +0100
commit31ac0e214d43da3bb705c8f345f5afcb922cefac (patch)
tree23234809c26a0e0bce208436328ca5f991576047
parent4115d83eb537ffbd81e2fd8d12790a8fad769199 (diff)
downloadtools.nes-31ac0e214d43da3bb705c8f345f5afcb922cefac.tar.gz
tools.nes-31ac0e214d43da3bb705c8f345f5afcb922cefac.zip
Make more explicit segment/macros are only global
Force .segment and .macro statements to be on the global scope since this is how they are meant. Hence, if the programmer tries to do this, just error out. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
-rw-r--r--lib/xixanta/src/assembler.rs61
-rw-r--r--lib/xixanta/src/node.rs30
2 files changed, 69 insertions, 22 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index d24a555..42842a7 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1,4 +1,4 @@
-use crate::errors::{ContextError, Error, EvalError};
+use crate::errors::{ContextError, ContextErrorReason, Error, EvalError};
use crate::mapping::Mapping;
use crate::node::{ControlType, NodeType, OperationType, PNode, PString};
use crate::object::{Bundle, Context, Object, ObjectType};
@@ -44,7 +44,7 @@ pub enum Stage {
}
#[derive(Clone, Debug)]
-pub struct Macro {
+pub struct CodeBlock {
nodes: Range<usize>,
args: Vec<PString>,
}
@@ -63,7 +63,7 @@ pub struct Assembler {
context: Context,
literal_mode: Option<LiteralMode>,
stage: Stage,
- macros: HashMap<String, Macro>,
+ macros: HashMap<String, CodeBlock>,
can_bundle: bool,
mappings: Vec<Mapping>,
current_mapping: usize,
@@ -207,21 +207,29 @@ impl Assembler {
}
}
NodeType::Control(control_type) => {
+ if !self.context.is_global() && control_type.must_be_global() {
+ errors.push(Error::Context(ContextError {
+ message: format!("{} must be on the global scope", control_type),
+ line: node.value.line,
+ global: false,
+ reason: ContextErrorReason::BadScope,
+ }));
+ continue;
+ }
+
// TODO: prevent nesting of control statements depending on
// a definition (e.g. .macro's cannot be nested inside of
// another control statement, but .if yes).
match control_type {
ControlType::StartMacro => {
- // TODO: macros are only on the global scope.
- //
// TODO: boy this is ugly. In fact, this stupid shit if
// current_macro might not be relevant anymore.
current_macro = Some(&node.left.as_ref().unwrap().value);
// TODO: watch out for weird shit on the name of arguments.
self.macros
.entry(node.left.as_ref().unwrap().value.value.clone())
- .or_insert(Macro {
+ .or_insert(CodeBlock {
nodes: Range {
start: idx + 1,
end: idx + 1,
@@ -1239,19 +1247,6 @@ impl Assembler {
});
}
- // You cannot change the segment if you are not in the global context.
- if !self.context.is_global() {
- return Err(EvalError {
- line: node.value.line,
- message: format!(
- "cannot switch to segment '{}' if we are still inside of a scope ('{}')",
- name,
- self.context.name()
- ),
- global: false,
- });
- }
-
// Find the segment being referenced and update the
// `self.current_segment` accordingly.
let mut found = false;
@@ -2917,6 +2912,31 @@ WRITE_PPU_DATA $20B9, $04
assert_eq!(res[6].bytes[2], 0x20);
}
+ #[test]
+ fn start_macro_inside_of_scope() {
+ let mut asm = Assembler::new(empty());
+ asm.mappings[0].segments[0].bundles = minimal_header();
+ asm.mappings[0].offset = 6;
+ asm.current_mapping = 1;
+ let res = &asm
+ .assemble(
+ std::env::current_dir().unwrap().to_path_buf(),
+ r#".scope Some
+.macro WRITE_PPU_DATA
+ bit $2002
+.endmacro
+.endscope
+"#
+ .as_bytes(),
+ )
+ .unwrap_err();
+
+ assert_eq!(
+ res.first().unwrap().to_string(),
+ ".macro must be on the global scope (line 2)"
+ );
+ }
+
// Segments
#[test]
@@ -3153,8 +3173,7 @@ code:
assert_eq!(
res.first().unwrap().to_string(),
- "cannot switch to segment 'CODE' \
- if we are still inside of a scope ('Vars') (line 3)"
+ ".segment must be on the global scope (line 3)"
);
}
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index f6f3d09..9739c9d 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -140,6 +140,34 @@ pub enum ControlType {
IncBin,
}
+impl fmt::Display for ControlType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ControlType::Hibyte => write!(f, ".hibyte"),
+ ControlType::Lobyte => write!(f, ".lobyte"),
+ ControlType::StartMacro => write!(f, ".macro"),
+ ControlType::EndMacro => write!(f, ".endmacro"),
+ ControlType::StartProc => write!(f, ".proc"),
+ ControlType::EndProc => write!(f, ".endproc"),
+ ControlType::StartScope => write!(f, ".scope"),
+ ControlType::EndScope => write!(f, ".endscope"),
+ ControlType::Segment => write!(f, ".segment"),
+ ControlType::Byte => write!(f, ".byte/.db"),
+ ControlType::Word => write!(f, ".word/.dw"),
+ ControlType::Addr => write!(f, ".addr"),
+ ControlType::IncBin => write!(f, ".incbin"),
+ }
+ }
+}
+
+impl ControlType {
+ /// Returns true if the type of control statement requires it to be in the
+ /// global scope.
+ pub fn must_be_global(&self) -> bool {
+ matches!(self, ControlType::StartMacro | ControlType::Segment)
+ }
+}
+
/// The type of operation being used.
#[derive(Debug, Clone, PartialEq)]
pub enum OperationType {
@@ -213,7 +241,7 @@ impl fmt::Display for NodeType {
NodeType::Instruction => write!(f, "instruction"),
NodeType::Indirection => write!(f, "indirection"),
NodeType::Assignment => write!(f, "assignment"),
- NodeType::Control(_) => write!(f, "control function"),
+ NodeType::Control(control_type) => write!(f, "control function ({})", control_type),
NodeType::Literal => write!(f, "literal"),
NodeType::Label => write!(f, "label"),
NodeType::Call => write!(f, "call"),