aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta/src')
-rw-r--r--lib/xixanta/src/assembler.rs37
-rw-r--r--lib/xixanta/src/node.rs9
-rw-r--r--lib/xixanta/src/object.rs12
-rw-r--r--lib/xixanta/src/parser.rs57
4 files changed, 94 insertions, 21 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 91f1e14..3cf1e0b 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -523,7 +523,10 @@ impl<'a> Assembler<'a> {
accessed: 1,
};
- if let Err(err) = self.context.set_variable(&var_name, &var_value, false) {
+ if let Err(err) = self
+ .context
+ .set_variable(&var_name, &var_value, false, false)
+ {
Err(Error {
global: true,
line: 0,
@@ -557,6 +560,7 @@ impl<'a> Assembler<'a> {
accessed: 0,
},
false,
+ matches!(node.node_type, NodeType::Label(true)),
) {
return Err(Error {
message,
@@ -585,7 +589,7 @@ impl<'a> Assembler<'a> {
// context so it's known. The actual value cannot be computed
// right now as we don't know the segment size where it belongs
// yet.
- NodeType::Label => {
+ NodeType::Label(_) => {
// There's no good reason to declare a named label inside of
// a macro. If that's the case, just error out.
if (self.macros_seen > 0 || self.repeats_seen > 0) && !node.value.is_empty() {
@@ -689,6 +693,7 @@ impl<'a> Assembler<'a> {
accessed: 0,
},
false,
+ false,
) {
errors.push(Error {
message: err,
@@ -909,7 +914,12 @@ impl<'a> Assembler<'a> {
};
if !node.value.is_empty()
- && let Err(message) = self.context.set_variable(&node.value, &object, true)
+ && let Err(message) = self.context.set_variable(
+ &node.value,
+ &object,
+ true,
+ matches!(node.node_type, NodeType::Label(true)),
+ )
{
return Err(Error {
message,
@@ -938,7 +948,7 @@ impl<'a> Assembler<'a> {
// segment. Note that this is only the offset from the beginning
// of the offset, the effective address will only be available
// after calling `Context::get_variable`
- NodeType::Label => {
+ NodeType::Label(_) => {
if let Err(e) = self.apply_segment_offset_to_label(node, ObjectType::Address) {
errors.push(e);
}
@@ -1150,11 +1160,16 @@ impl<'a> Assembler<'a> {
// value.
let mut dobj = d.obj.clone();
dobj.bundle = o;
- self.context.set_variable(&d.id, &dobj, true)
+ self.context.set_variable(
+ &d.id,
+ &dobj,
+ true,
+ matches!(n.node_type, NodeType::Label(true)),
+ )
}
// No attached node, we can proceed by setting the
// variable as is.
- None => self.context.set_variable(&d.id, &d.obj, true),
+ None => self.context.set_variable(&d.id, &d.obj, true, false),
};
}
}
@@ -1714,7 +1729,7 @@ impl<'a> Assembler<'a> {
// calls, just in case a macro is applied multiple times and we
// need to get the latest value.
let id = &margs.next().unwrap().value;
- if let Err(message) = self.context.set_variable(id, &obj, true) {
+ if let Err(message) = self.context.set_variable(id, &obj, true, false) {
return Err(Error {
line: node.value.line,
message,
@@ -2618,6 +2633,7 @@ impl<'a> Assembler<'a> {
accessed: 0,
},
true,
+ false,
)
{
return Err(Error {
@@ -3099,7 +3115,12 @@ impl<'a> Assembler<'a> {
// again. If the variable could not be set, then it's not
// that big of a deal at this stage.
value.bundle = bundle.clone();
- let _ = self.context.set_variable(&node.value, &value, true);
+ let _ = self.context.set_variable(
+ &node.value,
+ &value,
+ true,
+ matches!(node.node_type, NodeType::Label(true)),
+ );
// And return the computed bundle.
Ok(bundle)
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index 649f333..357b9e0 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -319,8 +319,10 @@ pub enum NodeType {
/// '$'. The `left` node contains the inner expression.
Literal,
- /// A label statement, which only sets the `value`, the name of the label.
- Label,
+ /// A label statement, which only sets the `value`, the name of the
+ /// label. If the inner boolean is true, then the label is considered to be
+ /// global, otherwise scopes apply.
+ Label(bool),
/// A macro call. Note that a Value might also encode this, but when a Call
/// has been detected, then there is no doubt on it.
@@ -347,7 +349,8 @@ impl fmt::Display for NodeType {
NodeType::Control(control_type) => write!(f, "control function ({control_type})"),
NodeType::ControlBody => write!(f, "control function body"),
NodeType::Literal => write!(f, "literal"),
- NodeType::Label => write!(f, "label"),
+ NodeType::Label(true) => write!(f, "(global) label"),
+ NodeType::Label(false) => write!(f, "(scoped) label"),
NodeType::Call => write!(f, "call"),
NodeType::Fallthrough => write!(f, "fallthrough"),
NodeType::Operation(op) => match op {
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs
index 6209afd..cc8908c 100644
--- a/lib/xixanta/src/object.rs
+++ b/lib/xixanta/src/object.rs
@@ -329,14 +329,22 @@ impl Context {
/// Sets a value for an object identified by `id`. If `overwrite` is set to
/// true, then this value will be set even if the id already existed,
- /// otherwise it will return a ContextError
+ /// otherwise it will return a ContextError. If 'force_global' is set to
+ /// true, then the variable will be defined at the global context, not the
+ /// current one.
pub fn set_variable(
&mut self,
id: &PString,
object: &Object,
overwrite: bool,
+ force_global: bool,
) -> Result<(), String> {
- let scope_name = self.name().to_string();
+ let scope_name = if force_global {
+ GLOBAL_CONTEXT
+ } else {
+ self.name()
+ }
+ .to_string();
let scope = self.map.get_mut(&scope_name).unwrap();
match scope.get_mut(&id.value) {
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index abcf1e8..a71c6f6 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -536,7 +536,7 @@ impl Parser {
// identifier and finally fall through.
self.offset = 0;
let (mut id, mut nt) = self.parse_identifier(l, true)?;
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
self.nodes.last_mut().unwrap().push(PNode {
node_type: nt,
value: id,
@@ -558,7 +558,7 @@ impl Parser {
// for it and fall through.
self.offset = 0;
(id, nt) = self.parse_identifier(l, true)?;
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
return Err(self
.parser_error("cannot have multiple labels at the same location")
.into());
@@ -624,10 +624,11 @@ impl Parser {
// statement. Returns a PString representing this identifier on success,
// plus a hint on whether the identifier belongs to a label or not.
fn parse_identifier(&mut self, line: &str, dot: bool) -> Result<(PString, NodeType), Error> {
- let start = self.column;
- let base_offset = self.offset;
+ let mut start = self.column;
+ let mut base_offset = self.offset;
let mut nt = NodeType::Value;
let mut first_seen = false;
+ let mut global_label = false;
// For the general case we just need to iterate until a whitespace
// character or an inline comment is found. Then our PString object is
@@ -645,6 +646,25 @@ impl Parser {
if c == '.' && (!dot || first_seen) {
return Err(self.parser_error("cannot have a '.' in this context"));
}
+
+ // If this is the first character, then allow a leading '#'
+ // character to denote a global label. Note that the '#' symbol will
+ // be skipped in the resulting name, and the start/end positions
+ // from the node will also reflect the '#' symbol being skipped.
+ if c == '#' && !first_seen {
+ global_label = true;
+
+ // We don't want the '#' symbol to be considered part of the
+ // name, as it's just a literal from the syntax, not the name
+ // itself.
+ base_offset += 1;
+ start += 1;
+
+ // Nothing else to be done, just jump into the next character.
+ first_seen = true;
+ self.next();
+ continue;
+ }
first_seen = true;
// Check for the end of the identifier. For this, it's easier to
@@ -732,18 +752,26 @@ impl Parser {
}
}
// Regular label (e.g. "label:").
- _ => nt = NodeType::Label,
+ _ => nt = NodeType::Label(global_label),
}
}
}
// If there are no characters left, check if it was a regular label.
None => {
if c == ':' {
- nt = NodeType::Label;
+ nt = NodeType::Label(global_label);
}
}
}
+ // Detect bogus names starting with '#' but not being an actual
+ // label.
+ if global_label && !matches!(nt, NodeType::Label(true)) {
+ return Err(
+ self.parser_error("identifier starts with a '#' but it's not a label")
+ );
+ }
+
// The value of the identifier is whatever we have picked up
// along the parsing.
let value = String::from(line.get(base_offset..self.offset).unwrap_or("").trim());
@@ -755,7 +783,7 @@ impl Parser {
// regular labels because we want to ignore to extra ':'
// character in the end.
let end = match nt {
- NodeType::Label => {
+ NodeType::Label(_) => {
self.next();
self.column - 1
}
@@ -1729,7 +1757,7 @@ impl Parser {
return Err(self.parser_error("invalid identifier"));
}
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
Err(self.parser_error("not expecting a label defined here"))
} else {
Ok(PNode {
@@ -2219,6 +2247,19 @@ mod tests {
assert_node(nodes.last().unwrap(), NodeType::Instruction, line, "dex")
}
+ #[test]
+ fn bad_pound_label() {
+ let mut parser = Parser::default();
+ let err = parser
+ .parse("#fakelabel = 1".as_bytes(), &SourceInfo::default())
+ .unwrap_err();
+
+ assert_eq!(
+ err.first().unwrap().message,
+ "identifier starts with a '#' but it's not a label"
+ );
+ }
+
// Literals
#[test]