From 25b921a1bf708456ff6d1227e39c035be897bab5 Mon Sep 17 00:00:00 2001 From: xarantolus Date: Thu, 21 Mar 2024 08:31:33 +0100 Subject: [PATCH 1/2] Evaluate memory region expressions --- src/eval.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/memory.rs | 61 +++++++++++++++++++++++++++---- src/script.rs | 7 ++++ tests/bootloader.ld | 6 ++++ 5 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 src/eval.rs create mode 100644 tests/bootloader.ld diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..201c2fa --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,87 @@ +use crate::expressions::{BinaryOperator, Expression}; + +pub fn evaluate_expression(expr: Expression) -> Result { + Ok(match expr { + Expression::Number(n) => n, + Expression::BinaryOp { + left, + operator, + right, + } => { + let left = evaluate_expression(*left)?; + let right = evaluate_expression(*right)?; + match operator { + BinaryOperator::Plus => left.wrapping_add(right), + BinaryOperator::Minus => left.wrapping_sub(right), + BinaryOperator::Multiply => left.wrapping_mul(right), + BinaryOperator::Divide => left.wrapping_div(right), + _ => return Err(format!("Binary operator {:?} not supported", operator)), + } + } + _ => return Err(format!("Expression {:?} not supported", expr)), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use nom::combinator::map_res; + use BinaryOperator::*; + + #[test] + fn test_evaluate_expression() { + assert_eq!(evaluate_expression(Expression::Number(42)), Ok(42)); + + assert_eq!( + evaluate_expression(Expression::BinaryOp { + left: Box::new(Expression::Number(42)), + operator: Plus, + right: Box::new(Expression::Number(42)) + }), + Ok(84) + ); + assert_eq!( + evaluate_expression(Expression::BinaryOp { + left: Box::new(Expression::Number(42)), + operator: Minus, + right: Box::new(Expression::Number(42)) + }), + Ok(0) + ); + assert_eq!( + evaluate_expression(Expression::BinaryOp { + left: Box::new(Expression::Number(42)), + operator: Multiply, + right: Box::new(Expression::Number(42)) + }), + Ok(1764) + ); + assert_eq!( + evaluate_expression(Expression::BinaryOp { + left: Box::new(Expression::Number(42)), + operator: Divide, + right: Box::new(Expression::Number(42)) + }), + Ok(1) + ); + } + + fn expr_result(input: &str, expected: u64) { + assert_done!( + map_res(crate::expressions::expression, evaluate_expression)(input), + expected + ); + } + + #[test] + fn test_parsed_expressions() { + expr_result("42 - (20 + 21)", 1); + expr_result("42 - (4 * 8)", 10); + expr_result("42", 42); + expr_result("42 + 42", 84); + expr_result("42 - 42", 0); + expr_result("42 * 42", 1764); + expr_result("42 / 42", 1); + expr_result("0x2000000 + (4k * 4)", 0x2000000 + (4 * 1024 * 4)); + } +} diff --git a/src/lib.rs b/src/lib.rs index e5fa9bd..f1f3578 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ mod utils; #[macro_use] mod whitespace; mod commands; +mod eval; mod expressions; mod idents; mod memory; diff --git a/src/memory.rs b/src/memory.rs index 8b30cc8..6f69944 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -2,13 +2,14 @@ use idents::symbol; use nom::{ branch::alt, bytes::complete::{tag, take_until}, - combinator::opt, + combinator::{map_res, opt}, sequence::{delimited, tuple}, IResult, }; -use numbers::number; use whitespace::opt_space; +use crate::{eval::evaluate_expression, expressions::expression}; + #[derive(Debug, PartialEq)] pub struct Region { pub name: String, @@ -37,15 +38,15 @@ pub fn region(input: &str) -> IResult<&str, Region> { origin, wsc!(tag("=")), ))(input)?; - let (input, org) = number(input)?; + let (input, origin) = map_res(expression, evaluate_expression)(input)?; let (input, _) = tuple((wsc!(tag(",")), length, wsc!(tag("="))))(input)?; - let (input, len) = number(input)?; + let (input, length) = map_res(expression, evaluate_expression)(input)?; Ok(( input, Region { name: name.into(), - origin: org, - length: len, + origin, + length, }, )) } @@ -73,4 +74,52 @@ mod tests { } ); } + + #[test] + fn test_region_expr() { + assert_done!( + region("FLASH : ORIGIN = 0x08000000, LENGTH = 8K"), + Region { + name: "FLASH".into(), + origin: 0x08000000, + length: 8 * 1024, + } + ); + + assert_done!( + region("RAM : ORIGIN = 0x20000000 + 8K, LENGTH = 640K"), + Region { + name: "RAM".into(), + origin: 0x20000000 + 8 * 1024, + length: 640 * 1024, + } + ); + + assert_done!( + region("RAM : ORIGIN = 0x20000000, LENGTH = 640K - 8K"), + Region { + name: "RAM".into(), + origin: 0x20000000, + length: 640 * 1024 - 8 * 1024, + } + ); + + assert_done!( + region("RAM : ORIGIN = 0x20000000 + 8K - 4K, LENGTH = 640K - 8K + 4K"), + Region { + name: "RAM".into(), + origin: 0x20000000 + 4 * 1024, + length: 640 * 1024 - 4 * 1024, + } + ); + + assert_done!( + region("RAM: ORIGIN = 0x20000000 + 8K - 4K, LENGTH = 640K - 8K + 4K"), + Region { + name: "RAM".into(), + origin: 0x20000000 + 4 * 1024, + length: 640 * 1024 - 4 * 1024, + } + ); + } } diff --git a/src/script.rs b/src/script.rs index 2cdc9d9..386f915 100644 --- a/src/script.rs +++ b/src/script.rs @@ -63,6 +63,13 @@ mod tests { assert_done_vec!(parse(" /* hello */ "), 0); } + #[test] + fn test_bootloader() { + let input = include_str!("../tests/bootloader.ld"); + let res = parse(&input); + assert!(!res.unwrap().1.is_empty()); + } + #[test] fn test_parse() { for entry in fs::read_dir("tests").unwrap() { diff --git a/tests/bootloader.ld b/tests/bootloader.ld new file mode 100644 index 0000000..6ce7eb3 --- /dev/null +++ b/tests/bootloader.ld @@ -0,0 +1,6 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 8K + + RAM : ORIGIN = 0x20000000 + 256K, LENGTH = 640K - 256K +} From d17b9c23ba22f2dfd3a3d99e691c1581a18cf6db Mon Sep 17 00:00:00 2001 From: Yann Diorcet Date: Thu, 5 Jun 2025 14:50:10 +0200 Subject: [PATCH 2/2] Add expression evaluation --- src/eval.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++-- src/memory.rs | 58 +++++++++++++++++++++++++++ src/script.rs | 106 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 243 insertions(+), 20 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 201c2fa..7c868be 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,15 +1,67 @@ use crate::expressions::{BinaryOperator, Expression}; -pub fn evaluate_expression(expr: Expression) -> Result { +use crate::script::PARSE_STATE; + +fn _evaluate_expression(expr: &Expression) -> Result { Ok(match expr { - Expression::Number(n) => n, + Expression::Number(n) => *n, + Expression::Ident(s) => { + return PARSE_STATE.with_borrow(|state| { + for item in &state.items { + if let crate::RootItem::Statement(stmt) = item { + if let crate::Statement::Assign { + name, expression, .. + } = stmt + { + if name == s { + return _evaluate_expression(&**expression); + } + } + } + } + Err(format!("Variable {:?} not found", s)) + }); + } + Expression::Call { + function, + arguments, + } => { + return match function.as_str() { + "ORIGIN" | "LENGTH" => { + if arguments.len() != 1 { + return Err(format!("function {:?} only support 1 argument", function)); + } + if let Expression::Ident(s) = &arguments[0] { + return PARSE_STATE.with_borrow(|state| { + for item in &state.items { + if let crate::RootItem::Memory { regions } = item { + for region in regions { + if region.name == *s { + return Ok(match function.as_str() { + "ORIGIN" => region.origin, + "LENGTH" => region.length, + _ => unreachable!(), + }); + } + } + } + } + Err(format!("Variable {:?} not found", s)) + }); + } else { + return Err(format!("function {:?} argument must be string", function)); + } + } + _ => Err(format!("function {:?} not supported", function)), + } + } Expression::BinaryOp { left, operator, right, } => { - let left = evaluate_expression(*left)?; - let right = evaluate_expression(*right)?; + let left = _evaluate_expression(&**left)?; + let right = _evaluate_expression(&**right)?; match operator { BinaryOperator::Plus => left.wrapping_add(right), BinaryOperator::Minus => left.wrapping_sub(right), @@ -22,8 +74,14 @@ pub fn evaluate_expression(expr: Expression) -> Result { }) } +pub fn evaluate_expression(expr: Expression) -> Result { + _evaluate_expression(&expr) +} + #[cfg(test)] mod tests { + use crate::{script::clear_state, AssignOperator, Region, RootItem, Statement}; + use super::*; use nom::combinator::map_res; use BinaryOperator::*; @@ -83,5 +141,38 @@ mod tests { expr_result("42 * 42", 1764); expr_result("42 / 42", 1); expr_result("0x2000000 + (4k * 4)", 0x2000000 + (4 * 1024 * 4)); + + clear_state(); + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Statement(Statement::Assign { + name: "A".into(), + operator: AssignOperator::Equals, + expression: Box::new(Expression::Number(11)), + })); + }); + expr_result("A * 2", 22); + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Statement(Statement::Assign { + name: "B".into(), + operator: AssignOperator::Equals, + expression: Box::new(Expression::BinaryOp { + left: Box::new(Expression::Number(2)), + operator: BinaryOperator::Plus, + right: Box::new(Expression::Number(4)), + }), + })); + }); + expr_result("A * B", 66); + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Memory { + regions: vec![Region { + name: String::from("AA"), + origin: 66, + length: 12, + }], + }); + }); + expr_result("ORIGIN(AA)", 66); + expr_result("LENGTH(AA)", 12); } } diff --git a/src/memory.rs b/src/memory.rs index 6f69944..9449774 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -55,6 +55,11 @@ pub fn region(input: &str) -> IResult<&str, Region> { mod tests { use memory::*; + use crate::{ + script::{clear_state, PARSE_STATE}, + AssignOperator, Expression, RootItem, Statement, + }; + #[test] fn test_region() { assert_done!( @@ -121,5 +126,58 @@ mod tests { length: 640 * 1024 - 4 * 1024, } ); + + clear_state(); + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Statement(Statement::Assign { + name: "A".into(), + operator: AssignOperator::Equals, + expression: Box::new(Expression::Number(11)), + })); + }); + + assert_done!( + region("rom (rx) : ORIGIN = 0, LENGTH = A"), + Region { + name: "rom".into(), + origin: 0, + length: 11, + } + ); + + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Memory { + regions: vec![Region { + name: String::from("FLASH"), + origin: 0x08000000, + length: 256 * 1024, + }], + }); + }); + + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Statement(Statement::Assign { + name: "FLASH_SIZE".into(), + operator: AssignOperator::Equals, + expression: Box::new(Expression::Number(256 * 1024)), + })); + }); + + PARSE_STATE.with_borrow_mut(|state| { + state.items.push(RootItem::Statement(Statement::Assign { + name: "EEPROM_SIZE".into(), + operator: AssignOperator::Equals, + expression: Box::new(Expression::Number(4 * 1024)), + })); + }); + + assert_done!( + region("EEPROM_FLASH (r) : ORIGIN = ORIGIN(FLASH) + FLASH_SIZE - EEPROM_SIZE, LENGTH = EEPROM_SIZE"), + Region { + name: "EEPROM_FLASH".into(), + origin: 0x803F000, + length: 4 * 1024, + } + ); } } diff --git a/src/script.rs b/src/script.rs index 386f915..73ee10f 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,10 +1,10 @@ +use std::cell::RefCell; + use commands::{command, Command}; use memory::region; use memory::Region; use nom::branch::alt; use nom::bytes::complete::tag; -use nom::combinator::map; -use nom::multi::many1; use nom::sequence::tuple; use nom::IResult; use sections::section_command; @@ -12,6 +12,15 @@ use sections::SectionCommand; use statements::{statement, Statement}; use whitespace::opt_space; +thread_local! { + pub(crate) static PARSE_STATE: RefCell = RefCell::new(ParseState::default()); +} + +#[derive(Debug, Default)] +pub struct ParseState { + pub items: Vec, +} + #[derive(Debug, PartialEq)] pub enum RootItem { Statement(Statement), @@ -20,34 +29,99 @@ pub enum RootItem { Sections { list: Vec }, } -fn statement_item(input: &str) -> IResult<&str, RootItem> { - map(statement, |stmt| RootItem::Statement(stmt))(input) +fn statement_item(input: &str) -> IResult<&str, ()> { + let (input, stmt) = statement(input)?; + PARSE_STATE.with_borrow_mut(|s| s.items.push(RootItem::Statement(stmt))); + Ok((input, ())) } -fn command_item(input: &str) -> IResult<&str, RootItem> { - map(command, |cmd| RootItem::Command(cmd))(input) +fn command_item(input: &str) -> IResult<&str, ()> { + let (input, cmd) = command(input)?; + PARSE_STATE.with_borrow_mut(|s| s.items.push(RootItem::Command(cmd))); + Ok((input, ())) } -fn memory_item(input: &str) -> IResult<&str, RootItem> { - let (input, _) = tuple((tag("MEMORY"), wsc!(tag("{"))))(input)?; - let (input, regions) = many1(wsc!(region))(input)?; +fn memory_item(input: &str) -> IResult<&str, ()> { + let (mut input, _) = tuple((tag("MEMORY"), wsc!(tag("{"))))(input)?; + PARSE_STATE.with_borrow_mut(|s| { + s.items.push(RootItem::Memory { + regions: Vec::new(), + }) + }); + loop { + match wsc!(region)(input) { + Ok((next_input, region_item)) => { + PARSE_STATE.with_borrow_mut(|s| { + if let Some(RootItem::Memory { regions }) = s.items.last_mut() { + regions.push(region_item); + } + }); + input = next_input; + } + Err(nom::Err::Error(_)) | Err(nom::Err::Incomplete(_)) => break, + Err(e) => return Err(e), + } + } let (input, _) = tag("}")(input)?; - Ok((input, RootItem::Memory { regions: regions })) + Ok((input, ())) } -fn sections_item(input: &str) -> IResult<&str, RootItem> { - let (input, _) = tuple((tag("SECTIONS"), wsc!(tag("{"))))(input)?; - let (input, sections) = many1(wsc!(section_command))(input)?; +fn sections_item(input: &str) -> IResult<&str, ()> { + let (mut input, _) = tuple((tag("SECTIONS"), wsc!(tag("{"))))(input)?; + PARSE_STATE.with_borrow_mut(|s| s.items.push(RootItem::Sections { list: Vec::new() })); + loop { + match wsc!(section_command)(input) { + Ok((next_input, section_item)) => { + PARSE_STATE.with_borrow_mut(|s| { + if let Some(RootItem::Sections { list }) = s.items.last_mut() { + list.push(section_item); + } + }); + input = next_input; + } + Err(nom::Err::Error(_)) | Err(nom::Err::Incomplete(_)) => break, + Err(e) => return Err(e), + } + } let (input, _) = tag("}")(input)?; - Ok((input, RootItem::Sections { list: sections })) + Ok((input, ())) } -fn root_item(input: &str) -> IResult<&str, RootItem> { +fn root_item(input: &str) -> IResult<&str, ()> { alt((statement_item, memory_item, sections_item, command_item))(input) } +pub(crate) fn clear_state() { + // Reset thread-local state + PARSE_STATE.with_borrow_mut(|state| { + *state = ParseState::default(); + }); +} + pub fn parse(input: &str) -> IResult<&str, Vec> { - alt((many1(wsc!(root_item)), map(opt_space, |_| vec![])))(input) + clear_state(); + + let mut input = input; + loop { + // Try to parse a root_item, skipping optional whitespace before it + match wsc!(root_item)(input) { + Ok((next_input, ())) => { + input = next_input; + } + Err(nom::Err::Error(_)) | Err(nom::Err::Incomplete(_)) => { + // No more root_items found, stop the loop + break; + } + Err(e) => return Err(e), + } + } + + // Skip trailing optional whitespace + let (input, _) = opt_space(input)?; + + let items = PARSE_STATE.with(|s| std::mem::take(&mut *s.borrow_mut())); + + Ok((input, items.items)) } #[cfg(test)]