aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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]