aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-10-23 12:54:14 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-12 07:49:33 +0100
commitd492aa8271f9fb02351b1144733508194df67746 (patch)
treea035d7c495ebc6a12b01e0018b345760de24ba22 /lib/xixanta
parent4f24eb5e5754c4e1e2c6069bdaf1b0f34c856c1b (diff)
downloadtools.nes-d492aa8271f9fb02351b1144733508194df67746.tar.gz
tools.nes-d492aa8271f9fb02351b1144733508194df67746.zip
Re-work the support on labels, variables and jumps
As a way to firstly adapt on the latest changes from the parser since 184c39579227 ("Re-work the parser from scratch"), the assembler had to leave out some features on 16114b2ca358 ("Adapt the assembler to the changes on the parser"). This commit reintroduces support for settings labels, variables and referencing them, while also providing a more robust implementation at that. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/src/assembler.rs248
-rw-r--r--lib/xixanta/src/context.rs51
-rw-r--r--lib/xixanta/src/mapping.rs14
-rw-r--r--lib/xixanta/src/node.rs10
-rw-r--r--lib/xixanta/src/opcodes.rs1
-rw-r--r--lib/xixanta/src/parser.rs49
6 files changed, 283 insertions, 90 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 0425ede..4b496dc 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -10,7 +10,8 @@ use std::io::Read;
use std::ops::Range;
/// A Bundle represents a set of bytes that can be encoded as binary data.
-#[derive(Debug, Default, Clone)]
+/// TODO: maybe inside of Mapping?
+#[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Bundle {
/// The bytes which make up any encodable element for the application. The
/// capacity is of three bytes maximum, but the actual size is encoded in
@@ -30,6 +31,28 @@ pub struct Bundle {
/// Whether the cost in cycles is affected when crossing a page boundary.
pub affected_on_page: bool,
+
+ resolved: bool,
+}
+
+impl Bundle {
+ pub fn new(resolved: bool) -> Self {
+ Self {
+ resolved,
+ ..Default::default()
+ }
+ }
+
+ pub fn fill(value: u8) -> Self {
+ Self {
+ bytes: [value, 0, 0],
+ size: 1,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ resolved: true,
+ }
+ }
}
#[derive(Clone, PartialEq)]
@@ -55,12 +78,23 @@ pub struct Macro {
args: Vec<PString>,
}
+#[derive(Clone, Debug)]
+pub struct PendingNode {
+ segment: usize,
+ context: String,
+ bundle_index: usize,
+ node: PNode,
+}
+
pub struct Assembler {
context: Context,
literal_mode: Option<LiteralMode>,
stage: Stage,
macros: HashMap<String, Macro>,
can_bundle: bool,
+ segments: Vec<Segment>,
+ current_segment: usize,
+ pending: Vec<PendingNode>,
}
impl Assembler {
@@ -68,18 +102,15 @@ impl Assembler {
assert!(!segments.is_empty());
// TODO
- let mut offsets = HashMap::new();
- for segment in segments {
- offsets.insert(segment.name, 0);
- }
-
- // TODO
Self {
context: Context::new(),
literal_mode: None,
stage: Stage::Init,
macros: HashMap::new(),
can_bundle: true,
+ segments,
+ current_segment: 0,
+ pending: vec![],
}
}
@@ -103,7 +134,10 @@ impl Assembler {
// Finally convert the relevant nodes into binary bundles which can be
// used by the caller.
self.stage = Stage::Bundling;
- self.bundle(&parser.nodes)
+ self.bundle(&parser.nodes)?;
+
+ // TODO
+ self.crunch_and_resolve_pending()
}
pub fn eval_context(&mut self, nodes: &[PNode]) -> Result<(), Vec<Error>> {
@@ -111,8 +145,15 @@ impl Assembler {
let mut current_macro = None;
for (idx, node) in nodes.iter().enumerate() {
- // TODO: initilize labels on each scope.
match node.node_type {
+ NodeType::Label => {
+ if let Err(err) =
+ self.context
+ .set_variable(&node.value, &Bundle::default(), false)
+ {
+ errors.push(Error::Context(err));
+ }
+ }
NodeType::Assignment => {
// TODO: in fact, we cannot have assignments in many places.
if current_macro.is_some() {
@@ -125,7 +166,8 @@ impl Assembler {
}
match self.evaluate_node(node.left.as_ref().unwrap()) {
Ok(value) => {
- if let Err(err) = self.context.set_variable(&node.value, &value) {
+ if let Err(err) = self.context.set_variable(&node.value, &value, false)
+ {
errors.push(Error::Context(err));
}
}
@@ -186,27 +228,47 @@ impl Assembler {
}
}
- pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<Vec<Bundle>, Vec<Error>> {
- let mut bundles = Vec::new();
+ pub fn bundle(&mut self, nodes: &Vec<PNode>) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
for node in nodes {
match node.node_type {
+ NodeType::Label => {
+ let segment = &self.segments[self.current_segment];
+ let value = (segment.start as usize + segment.offset).to_le_bytes();
+ let bundle = Bundle {
+ bytes: [value[0], value[1], value[2]],
+ size: 2,
+ address: 0,
+ cycles: 0,
+ affected_on_page: false,
+ resolved: true,
+ };
+
+ if let Err(err) = self.context.set_variable(&node.value, &bundle, true) {
+ errors.push(Error::Context(err));
+ }
+ }
NodeType::Instruction => {
if self.can_bundle {
+ self.literal_mode = None;
match self.evaluate_node(node) {
- Ok(bundle) => bundles.push(bundle),
+ Ok(bundle) => {
+ if let Err(e) = self.push_bundle(bundle, node) {
+ errors.push(Error::Eval(e));
+ }
+ }
Err(e) => errors.push(Error::Eval(e)),
}
}
}
NodeType::Control => {
- if let Err(e) = self.evaluate_control_statement(node, &mut bundles) {
+ if let Err(e) = self.evaluate_control_statement(node) {
errors.push(Error::Eval(e));
}
}
NodeType::Value | NodeType::Call => {
- if let Err(e) = self.bundle_call(node, nodes, &mut bundles) {
+ if let Err(e) = self.bundle_call(node, nodes) {
errors.push(Error::Eval(e));
}
}
@@ -216,18 +278,43 @@ impl Assembler {
}
if errors.is_empty() {
- Ok(bundles)
+ Ok(())
} else {
Err(errors)
}
}
- fn bundle_call(
- &mut self,
- node: &PNode,
- nodes: &[PNode],
- bundles: &mut Vec<Bundle>,
- ) -> Result<(), EvalError> {
+ // TODO: maybe split?
+ pub fn crunch_and_resolve_pending(&mut self) -> Result<Vec<Bundle>, Vec<Error>> {
+ let mut errors = vec![];
+
+ for pn in self.pending.clone() {
+ self.context.force_context_switch(&pn.context);
+
+ match self.evaluate_node(&pn.node) {
+ Ok(bundle) => {
+ let current = &mut self.segments[pn.segment];
+ current.bundles[pn.bundle_index] = bundle;
+ }
+ Err(e) => errors.push(Error::Eval(e)),
+ }
+
+ self.context.force_context_pop();
+ }
+
+ let mut res = vec![];
+ for segment in &mut self.segments {
+ res.append(&mut segment.bundles);
+
+ if let Some(_fill) = segment.fill {
+ // TODO
+ }
+ }
+
+ Ok(res)
+ }
+
+ fn bundle_call(&mut self, node: &PNode, nodes: &[PNode]) -> Result<(), EvalError> {
// Get the macro object for the given identifier.
let mcr = self
.macros
@@ -267,7 +354,7 @@ impl Assembler {
for (idx, arg) in args.unwrap().iter().enumerate() {
let bundle = self.evaluate_node(arg)?;
self.context
- .set_variable(margs.nth(idx).unwrap(), &bundle)?;
+ .set_variable(margs.nth(idx).unwrap(), &bundle, false)?;
}
}
@@ -277,12 +364,41 @@ impl Assembler {
.get(mcr.nodes.start..=mcr.nodes.end)
.unwrap_or_default()
{
- bundles.push(self.evaluate_node(node)?);
+ let bundle = self.evaluate_node(node)?;
+ self.push_bundle(bundle, node)?;
}
Ok(())
}
+ // TODO: move
+ fn push_bundle(&mut self, bundle: Bundle, node: &PNode) -> Result<(), EvalError> {
+ let current = &mut self.segments[self.current_segment];
+ current.offset += bundle.size as usize;
+
+ if current.offset > current.size {
+ return Err(EvalError {
+ line: 0,
+ message: format!(
+ "exceeding segment size for '{}' ({} bytes)",
+ current.name, current.size
+ ),
+ });
+ }
+
+ if !bundle.resolved {
+ self.pending.push(PendingNode {
+ segment: self.current_segment,
+ context: self.context.name().to_string(),
+ bundle_index: current.bundles.len(),
+ node: node.to_owned(),
+ });
+ }
+ current.bundles.push(bundle);
+
+ Ok(())
+ }
+
fn evaluate_node(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
match node.node_type {
NodeType::Instruction => Ok(self.evaluate_instruction(node)?),
@@ -381,6 +497,7 @@ impl Assembler {
address: 0,
cycles: 0,
affected_on_page: false,
+ resolved: true,
})
}
@@ -427,6 +544,7 @@ impl Assembler {
address: 0,
cycles: 0,
affected_on_page: false,
+ resolved: true,
}),
}
}
@@ -498,6 +616,7 @@ impl Assembler {
address: 0,
cycles: 0,
affected_on_page: false,
+ resolved: true,
})
}
@@ -569,11 +688,7 @@ impl Assembler {
}
}
- fn evaluate_control_statement(
- &mut self,
- node: &PNode,
- res: &mut Vec<Bundle>,
- ) -> Result<(), EvalError> {
+ fn evaluate_control_statement(&mut self, node: &PNode) -> Result<(), EvalError> {
let changed;
// This might just be a statement that changes the context (e.g.
@@ -588,8 +703,8 @@ impl Assembler {
// produces bundles.
let function = node.value.value.as_str();
match function {
- ".byte" | ".db" => self.push_evaluated_arguments(node, res, 1),
- ".addr" | ".dw" => self.push_evaluated_arguments(node, res, 2),
+ ".byte" | ".db" => self.push_evaluated_arguments(node, 1),
+ ".addr" | ".word" | ".dw" => self.push_evaluated_arguments(node, 2),
_ => Err(EvalError {
line: node.value.line,
message: format!(
@@ -639,12 +754,7 @@ impl Assembler {
Ok(bundle)
}
- fn push_evaluated_arguments(
- &mut self,
- node: &PNode,
- res: &mut Vec<Bundle>,
- nbytes: u8,
- ) -> Result<(), EvalError> {
+ fn push_evaluated_arguments(&mut self, node: &PNode, nbytes: u8) -> Result<(), EvalError> {
match &node.args {
Some(args) => {
for arg in args {
@@ -674,7 +784,7 @@ impl Assembler {
_ => panic!("bad argument when evaluating arguments"),
}
}
- res.push(bundle);
+ self.push_bundle(bundle, node)?;
}
}
None => {
@@ -704,7 +814,7 @@ impl Assembler {
fn evaluate_instruction(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
let (mode, mut bundle) = match &node.left {
Some(_) => self.get_addressing_mode_and_bytes(node)?,
- None => (AddressingMode::Implied, Bundle::default()),
+ None => (AddressingMode::Implied, Bundle::new(true)),
};
let mnemonic = node.value.value.to_lowercase();
@@ -749,7 +859,7 @@ impl Assembler {
} else if node.right.is_some() {
self.get_from_indexed(node)
} else {
- self.get_from_left(left.as_ref().unwrap())
+ self.get_from_left(node, left.as_ref().unwrap())
}
}
@@ -856,9 +966,13 @@ impl Assembler {
}
}
- fn get_from_left(&mut self, left_arm: &PNode) -> Result<(AddressingMode, Bundle), EvalError> {
+ fn get_from_left(
+ &mut self,
+ base: &PNode,
+ left_arm: &PNode,
+ ) -> Result<(AddressingMode, Bundle), EvalError> {
if left_arm.value.value.to_lowercase().trim() == "a" {
- return Ok((AddressingMode::Implied, Bundle::default()));
+ return Ok((AddressingMode::Implied, Bundle::new(true)));
}
let val = self.evaluate_node(left_arm)?;
@@ -872,10 +986,13 @@ impl Assembler {
}
Some(LiteralMode::Plain) => {
if val.size > 1 {
- Err(EvalError {
- message: "immediate is too big".to_string(),
- line: left_arm.value.line,
- })
+ match base.value.value.as_str() {
+ "jmp" | "jsr" => Ok((AddressingMode::Absolute, val)),
+ _ => Err(EvalError {
+ message: "immediate is too big".to_string(),
+ line: left_arm.value.line,
+ }),
+ }
} else {
Ok((AddressingMode::Immediate, val))
}
@@ -1388,7 +1505,41 @@ lda #Scope::Variable
}
// Labels & branching
- // TODO
+
+ #[test]
+ fn same_segment_labels() {
+ let mut asm = Assembler::new(EMPTY.to_vec());
+ let res = asm
+ .assemble(
+ r#"
+nop
+@hello:
+ jmp @hello
+ jmp @end
+@end:
+ nop
+"#
+ .as_bytes(),
+ )
+ .unwrap();
+
+ assert_eq!(res.len(), 4);
+
+ // jmp @hello
+ assert_eq!(res[1].size, 3);
+ assert_eq!(res[1].bytes[0], 0x4C);
+ assert_eq!(res[1].bytes[1], 0x01);
+ assert_eq!(res[1].bytes[2], 0x00);
+
+ // jmp @end
+ assert_eq!(res[2].size, 3);
+ assert_eq!(res[2].bytes[0], 0x4C);
+ assert_eq!(res[2].bytes[1], 0x07);
+ assert_eq!(res[2].bytes[2], 0x00);
+ }
+
+ // TODO: anonymous jumps
+ // TODO: labels & anonymous beq/blt/etc.
// Control statements
@@ -1613,4 +1764,7 @@ MACRO(1)
you cannot re-assign variables."
);
}
+
+ // Segments
+ // TODO: segments as is, fill data, jmp's
}
diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs
index 3fceab9..bccac12 100644
--- a/lib/xixanta/src/context.rs
+++ b/lib/xixanta/src/context.rs
@@ -59,28 +59,35 @@ impl Context {
}
}
- /// Sets a value for a new variable defined in the assignment `node`.
- pub fn set_variable(&mut self, id: &PString, bundle: &Bundle) -> Result<(), ContextError> {
+ /// Sets a value for a variable defined in the assignment `node`. If
+ /// `overwrite` is set to true, then this value will be set even if the
+ /// variable already existed, otherwise it will return a ContextError
+ pub fn set_variable(
+ &mut self,
+ id: &PString,
+ bundle: &Bundle,
+ overwrite: bool,
+ ) -> Result<(), ContextError> {
let scope_name = self.name().to_string();
let scope = self.map.get_mut(&scope_name).unwrap();
match scope.get_mut(&id.value) {
- Some(_) => {
- return Err(ContextError {
- message: format!(
- "'{}' already defined in {}: you cannot re-assign variables",
- id.value,
- self.to_human()
- ),
- line: id.line,
- reason: ContextErrorReason::Redefinition,
- })
+ Some(sc) => {
+ if !overwrite {
+ return Err(ContextError {
+ message: format!(
+ "'{}' already defined in {}: you cannot re-assign variables",
+ id.value,
+ self.to_human()
+ ),
+ line: id.line,
+ reason: ContextErrorReason::Redefinition,
+ });
+ }
+ *sc = bundle.clone();
}
None => {
- self.map.insert(
- scope_name,
- HashMap::from([(id.value.clone(), bundle.to_owned())]),
- );
+ scope.insert(id.value.clone(), bundle.to_owned());
}
}
@@ -121,6 +128,16 @@ impl Context {
}
}
+ pub fn force_context_switch(&mut self, name: &String) {
+ self.stack.push(name.to_owned());
+ }
+
+ pub fn force_context_pop(&mut self) {
+ if !self.stack.is_empty() {
+ self.stack.truncate(self.stack.len() - 1);
+ }
+ }
+
// Pushes a new context given a `node`, which holds the identifier of the
// new scope.
fn context_push(&mut self, id: &PNode) {
@@ -150,7 +167,7 @@ impl Context {
}
// Returns the name of the current context.
- fn name(&self) -> &str {
+ pub fn name(&self) -> &str {
match self.stack.last() {
Some(name) => name,
None => GLOBAL_CONTEXT,
diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs
index 9d313a5..238f39d 100644
--- a/lib/xixanta/src/mapping.rs
+++ b/lib/xixanta/src/mapping.rs
@@ -1,34 +1,46 @@
+use crate::assembler::Bundle;
+
lazy_static! {
pub static ref EMPTY: Vec<Segment> = vec![Segment {
name: String::from("CODE"),
start: 0x0000,
size: 0xFFFF,
+ offset: 0,
fill: None,
+ bundles: vec![],
}];
pub static ref NROM: Vec<Segment> = vec![
Segment {
name: String::from("HEADER"),
start: 0x0000,
size: 0x0010,
+ offset: 0,
fill: Some(0x00),
+ bundles: vec![],
},
Segment {
name: String::from("VECTORS"),
start: 0xFFFA,
size: 0x0006,
+ offset: 0,
fill: Some(0x00),
+ bundles: vec![],
},
Segment {
name: String::from("CODE"),
start: 0x8000,
size: 0x7FFA,
+ offset: 0,
fill: Some(0x00),
+ bundles: vec![],
},
Segment {
name: String::from("CHARS"),
start: 0x0000,
size: 0x2000,
+ offset: 0,
fill: Some(0x00),
+ bundles: vec![],
}
];
}
@@ -38,7 +50,9 @@ pub struct Segment {
pub name: String,
pub start: u16,
pub size: usize,
+ pub offset: usize,
pub fill: Option<usize>,
+ pub bundles: Vec<Bundle>,
}
/* TODO
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index 18a5402..f55ca11 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -1,5 +1,4 @@
use std::fmt;
-use std::ops::Range;
/// A Positioned String. That is, a String which also has information on the
/// line number and the column range.
@@ -11,14 +10,17 @@ pub struct PString {
/// Line number where it has been found.
pub line: usize,
- /// The column range where it has been found.
- pub range: Range<usize>,
+ /// The start column where it has been found.
+ pub start: usize,
+
+ /// The end column where it has been found.
+ pub end: usize,
}
impl PString {
/// Returns true if the string has either an empty value or an empty range.
pub fn is_empty(&self) -> bool {
- self.value.is_empty() || self.range.is_empty()
+ self.value.is_empty() || (self.start == self.end)
}
/// Returns an empty tuple if the string contains a valid identifier, or a
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index 9198e3a..0e7f379 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -727,6 +727,7 @@ lazy_static! {
functions.insert(String::from(".db"), Control { has_identifier: false, required_args: None, touches_context: false });
functions.insert(String::from(".word"), Control { has_identifier: false, required_args: None, touches_context: false });
functions.insert(String::from(".dw"), Control { has_identifier: false, required_args: None, touches_context: false });
+ functions.insert(String::from(".addr"), Control { has_identifier: false, required_args: None, touches_context: false });
functions
};
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index c379518..ec15edd 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -3,7 +3,6 @@ use crate::node::{NodeType, PNode, PString};
use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
use std::cmp::Ordering;
use std::io::{self, BufRead, Read};
-use std::ops::Range;
/// The Parser struct holds basic data for the current parsing session.
#[derive(Default)]
@@ -248,7 +247,8 @@ impl Parser {
PString {
value,
line: self.line,
- range: Range { start, end },
+ start,
+ end,
},
nt,
));
@@ -264,10 +264,8 @@ impl Parser {
PString {
value: id,
line: self.line,
- range: Range {
- start,
- end: self.column,
- },
+ start,
+ end: self.column,
},
NodeType::Value,
))
@@ -755,7 +753,7 @@ impl Parser {
// of expressions like '#.hibyte'. Then skip whitespaces for super
// ugly statements such as '# 20'. This is ugly but we should permit
// it. A later linter can yell at a programmer for this.
- self.column = id.range.start;
+ self.column = id.start;
self.offset = 0;
self.next();
self.skip_whitespace(line);
@@ -825,7 +823,7 @@ mod tests {
assert_eq!(node.node_type, nt);
assert_eq!(
node.value.value.as_str(),
- line.get(node.value.range.clone()).unwrap()
+ line.get(node.value.start..node.value.end).unwrap()
);
assert_eq!(node.value.value.as_str(), value);
}
@@ -863,15 +861,15 @@ mod tests {
assert!(parser.parse(":".as_bytes()).is_ok());
assert_eq!(parser.nodes.len(), 1);
assert!(parser.nodes.first().unwrap().value.value.is_empty());
- assert_eq!(parser.nodes.first().unwrap().value.range.start, 0);
- assert_eq!(parser.nodes.first().unwrap().value.range.end, 0);
+ assert_eq!(parser.nodes.first().unwrap().value.start, 0);
+ assert_eq!(parser.nodes.first().unwrap().value.end, 0);
parser = Parser::default();
assert!(parser.parse(" :".as_bytes()).is_ok());
assert_eq!(parser.nodes.len(), 1);
assert!(parser.nodes.first().unwrap().value.value.is_empty());
- assert_eq!(parser.nodes.first().unwrap().value.range.start, 2);
- assert_eq!(parser.nodes.first().unwrap().value.range.end, 2);
+ assert_eq!(parser.nodes.first().unwrap().value.start, 2);
+ assert_eq!(parser.nodes.first().unwrap().value.end, 2);
}
#[test]
@@ -880,15 +878,15 @@ mod tests {
assert!(parser.parse("label:".as_bytes()).is_ok());
assert_eq!(parser.nodes.len(), 1);
assert_eq!(parser.nodes.first().unwrap().value.value, "label");
- assert_eq!(parser.nodes.first().unwrap().value.range.start, 0);
- assert_eq!(parser.nodes.first().unwrap().value.range.end, 5);
+ assert_eq!(parser.nodes.first().unwrap().value.start, 0);
+ assert_eq!(parser.nodes.first().unwrap().value.end, 5);
parser = Parser::default();
assert!(parser.parse(" label:".as_bytes()).is_ok());
assert_eq!(parser.nodes.len(), 1);
assert_eq!(parser.nodes.first().unwrap().value.value, "label");
- assert_eq!(parser.nodes.first().unwrap().value.range.start, 2);
- assert_eq!(parser.nodes.first().unwrap().value.range.end, 7);
+ assert_eq!(parser.nodes.first().unwrap().value.start, 2);
+ assert_eq!(parser.nodes.first().unwrap().value.end, 7);
}
#[test]
@@ -901,8 +899,8 @@ mod tests {
// Label.
assert_eq!(parser.nodes.first().unwrap().value.value, "label");
- assert_eq!(parser.nodes.first().unwrap().value.range.start, 0);
- assert_eq!(parser.nodes.first().unwrap().value.range.end, 5);
+ assert_eq!(parser.nodes.first().unwrap().value.start, 0);
+ assert_eq!(parser.nodes.first().unwrap().value.end, 5);
// Instruction
assert_node(
@@ -929,7 +927,7 @@ mod tests {
let left = node.left.clone().unwrap();
assert_eq!(left.node_type, NodeType::Value);
assert_eq!(left.value.value, "20");
- assert_eq!(line.get(left.value.range).unwrap(), "20");
+ assert_eq!(line.get(left.value.start..left.value.end).unwrap(), "20");
}
}
@@ -947,12 +945,16 @@ mod tests {
let inner = node.left.clone().unwrap();
assert_eq!(inner.node_type, NodeType::Literal);
assert_eq!(inner.value.value, "$20");
- assert_eq!(line.get(inner.value.range).unwrap(), "$20");
+ assert_eq!(line.get(inner.value.start..inner.value.end).unwrap(), "$20");
let innerinner = inner.left.clone().unwrap();
assert_eq!(innerinner.node_type, NodeType::Value);
assert_eq!(innerinner.value.value, "20");
- assert_eq!(line.get(innerinner.value.range).unwrap(), "20");
+ assert_eq!(
+ line.get(innerinner.value.start..innerinner.value.end)
+ .unwrap(),
+ "20"
+ );
}
#[test]
@@ -969,7 +971,10 @@ mod tests {
let inner = node.left.clone().unwrap();
assert_eq!(inner.node_type, NodeType::Value);
assert_eq!(inner.value.value, "Variable");
- assert_eq!(line.get(inner.value.range).unwrap(), "Variable");
+ assert_eq!(
+ line.get(inner.value.start..inner.value.end).unwrap(),
+ "Variable"
+ );
}
#[test]