From c26addeaae49549d1ad18352e28048b2755a578e Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 23 Jul 2024 13:10:40 -0700 Subject: [PATCH] most of macros implemented --- rust/src/macros.rs | 326 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 296 insertions(+), 30 deletions(-) diff --git a/rust/src/macros.rs b/rust/src/macros.rs index df3f568b30..89397db238 100644 --- a/rust/src/macros.rs +++ b/rust/src/macros.rs @@ -1,6 +1,7 @@ use lazy_static::lazy_static; use num::Integer; use std::{ + cell::RefCell, cmp::Ordering, collections::{BTreeMap, HashMap, HashSet}, mem::take, @@ -86,16 +87,51 @@ pub enum MacroError { ExpectingIfEnd, /// Expecting macro variable name. - #[error("Expecting macro variable name following `!LET`.")] - ExpectingMacroVarName, - - /// Invalid `!LET` variable name. - #[error("Cannot use argument name or macro keyword {0} as `!LET` variable name.")] - BadLetVarName(Identifier), + #[error("Expecting macro variable name following `{0}`.")] + ExpectingMacroVarName(&'static str), + + /// Invalid macro variable name. + #[error("Cannot use argument name or macro keyword {name} as `{construct}` variable name.")] + BadMacroVarName { + name: Identifier, + construct: &'static str, + }, /// Expecting `=` following `!LET`. #[error("Expecting `=` following `!LET`.")] ExpectingEquals, + + /// Expecting `=` or `!IN` in `!DO` loop. + #[error("Expecting `=` or `!IN` in `!DO` loop.")] + ExpectingEqualsOrIn, + + /// Missing `!DOEND`. + #[error("Missing `!DOEND`.")] + MissingDoEnd, + + /// Bad numberic macro expression. + #[error("Macro expression must evaluate to a number (not {0:?})")] + BadNumericMacroExpression(String), + + /// Too many iteration for list-based loop. + #[error("`!DO` loop over list exceeded maximum number of iterations {0}. (Use `SET MITERATE` to change the limit.)")] + MiterateList(usize), + + /// Too many iteration for numerical loop. + #[error("Numerical `!DO` loop exceeded maximum number of iterations {0}. (Use `SET MITERATE` to change the limit.)")] + MiterateNumeric(usize), + + /// Expecting `!TO` in numerical `!DO` loop. + #[error("Expecting `!TO` in numerical `!DO` loop.")] + ExpectingTo, + + /// `!BY` value cannot be zero. + #[error("`!BY` value cannot be zero.")] + ZeroBy, + + /// `!BREAK` outside `!DO`. + #[error("`!BREAK` outside `!DO`.")] + BreakOutsideDo, } /// A PSPP macro as defined with `!DEFINE`. @@ -224,6 +260,13 @@ impl<'a> MacroTokens<'a> { } None } + fn take_macro_id(&mut self) -> Option<&Identifier> { + let result = self.0.get(0).map(|mt| mt.token.macro_id()).flatten(); + if result.is_some() { + self.advance(); + } + result + } fn advance(&mut self) -> &MacroToken { let (first, rest) = self.0.split_first().unwrap(); self.0 = rest; @@ -665,10 +708,10 @@ struct Expander<'a> { stack: Vec, // May macro calls be expanded? - expand: Option<&'a bool>, + expand: &'a RefCell, /// Variables from `!DO` and `!LET`. - vars: &'a mut BTreeMap, + vars: &'a RefCell>, // Only set if inside a `!DO` loop. If true, break out of the loop. break_: Option<&'a mut bool>, @@ -729,9 +772,68 @@ fn is_macro_keyword(s: &Identifier) -> bool { KEYWORDS.contains(s) } +enum DoInput { + List(Vec), + Up { first: f64, last: f64, by: f64 }, + Down { first: f64, last: f64, by: f64 }, + Empty, +} + +impl DoInput { + fn from_list(items: Vec) -> Self { + Self::List( + items + .into_iter() + .rev() + .take(MITERATE + 1) + .map(|mt| mt.syntax) + .collect(), + ) + } + + fn from_by(first: f64, last: f64, by: f64) -> Self { + if by > 0.0 && first <= last { + Self::Up { first, last, by } + } else if by > 0.0 && first <= last { + Self::Down { first, last, by } + } else { + Self::Empty + } + } +} + +impl Iterator for DoInput { + type Item = String; + + fn next(&mut self) -> Option { + match self { + DoInput::List(vec) => vec.pop(), + DoInput::Up { first, last, by } => { + if first <= last { + let value = *first; + *first += *by; + Some(format!("{value}")) + } else { + None + } + } + DoInput::Down { first, last, by } => { + if first >= last { + let value = *first; + *first += *by; + Some(format!("{value}")) + } else { + None + } + } + DoInput::Empty => None, + } + } +} + impl<'a> Expander<'a> { fn may_expand(&self) -> bool { - self.expand.map(|b| *b).unwrap_or(false) + *self.expand.borrow() } fn should_break(&self) -> bool { @@ -753,7 +855,7 @@ impl<'a> Expander<'a> { let param = &self.macro_.unwrap().parameters[param_idx]; let arg = &self.args.unwrap()[param_idx].as_ref().unwrap(); if self.may_expand() && param.expand_value { - let mut vars = BTreeMap::new(); + let vars = RefCell::new(BTreeMap::new()); let mut stack = take(&mut self.stack); stack.push(Frame { name: Some(param.name.clone()), @@ -761,7 +863,7 @@ impl<'a> Expander<'a> { }); let mut subexpander = Expander { stack, - vars: &mut vars, + vars: &vars, break_: None, macro_: None, args: None, @@ -804,7 +906,7 @@ impl<'a> Expander<'a> { input.advance(); return Some(s); } - if let Some(value) = self.vars.get(id) { + if let Some(value) = self.vars.borrow().get(id) { return Some(value.clone()); } @@ -896,6 +998,25 @@ impl<'a> Expander<'a> { self.evaluate_or(input) } + fn evaluate_number(&mut self, input: &mut MacroTokens) -> Option { + let s = self.evaluate_expression(input)?; + let mut tokens = Vec::new(); + tokenize_string(&s, self.mode, &mut tokens, self.error); + let ( + Some(MacroToken { + token: Token::Number(number), + .. + }), + 1, + ) = (tokens.get(0), tokens.len()) + else { + (self.error)(MacroError::BadNumericMacroExpression(s)); + return None; + }; + + Some(*number) + } + fn find_ifend_clause<'b>( input: &mut MacroTokens<'b>, ) -> Option<(MacroTokens<'b>, IfEndClause)> { @@ -963,20 +1084,20 @@ impl<'a> Expander<'a> { location: None, }); self.expand(&mut subinput, output); + self.stack.pop(); } *orig_input = input; true } - fn expand_let(&mut self, orig_input: &mut MacroTokens) -> bool { - let mut input = orig_input.clone(); - if !input.match_("!LET") { - return false; - } - - let Some(var_name) = input.0.get(0).map(|mt| mt.token.macro_id()).flatten() else { - (self.error)(MacroError::ExpectingMacroVarName); - return false; + fn take_macro_var_name( + &mut self, + input: &mut MacroTokens, + construct: &'static str, + ) -> Option { + let Some(var_name) = input.take_macro_id() else { + (self.error)(MacroError::ExpectingMacroVarName(construct)); + return None; }; if is_macro_keyword(var_name) || self @@ -985,9 +1106,25 @@ impl<'a> Expander<'a> { .flatten() .is_some() { - (self.error)(MacroError::BadLetVarName(var_name.clone())); + (self.error)(MacroError::BadMacroVarName { + name: var_name.clone(), + construct, + }); + None + } else { + Some(var_name.clone()) + } + } + + fn expand_let(&mut self, orig_input: &mut MacroTokens) -> bool { + let mut input = orig_input.clone(); + if !input.match_("!LET") { return false; } + + let Some(var_name) = self.take_macro_var_name(&mut input, "!LET") else { + return false; + }; input.advance(); if !input.match_("=") { @@ -998,7 +1135,116 @@ impl<'a> Expander<'a> { let Some(value) = self.evaluate_expression(&mut input) else { return false; }; - self.vars.insert(var_name.clone(), value); + self.vars.borrow_mut().insert(var_name.clone(), value); + *orig_input = input; + true + } + + fn find_doend<'b>(&mut self, input: &mut MacroTokens<'b>) -> Option> { + let input_copy = input.clone(); + let mut nesting = 0; + while !input.0.is_empty() { + if input.match_("!DO") { + nesting += 1; + } else if input.match_("!DOEND") { + if nesting == 0 { + return Some(MacroTokens( + &input_copy.0[..input_copy.0.len() - input.0.len() - 1], + )); + } + nesting -= 1; + } else { + input.advance(); + } + } + (self.error)(MacroError::MissingDoEnd); + return None; + } + + fn expand_do(&mut self, orig_input: &mut MacroTokens) -> bool { + let mut input = orig_input.clone(); + if !input.match_("!DO") { + return false; + } + + let Some(var_name) = self.take_macro_var_name(&mut input, "!DO") else { + return false; + }; + + let mut stack = take(&mut self.stack); + stack.push(Frame { + name: Some(Identifier::new("!DO").unwrap()), + location: None, + }); + let mut break_ = false; + let mut subexpander = Expander { + break_: Some(&mut break_), + stack, + vars: self.vars, + ..*self + }; + + let (items, miterate_error) = if input.match_("!IN") { + let Some(list) = self.evaluate_expression(&mut input) else { + return false; + }; + let mut items = Vec::new(); + tokenize_string(list.as_str(), self.mode, &mut items, &self.error); + ( + DoInput::from_list(items), + MacroError::MiterateList(MITERATE), + ) + } else if input.match_("=") { + let Some(first) = self.evaluate_number(&mut input) else { + return false; + }; + if !input.match_("!TO") { + (self.error)(MacroError::ExpectingTo); + return false; + } + let Some(last) = self.evaluate_number(&mut input) else { + return false; + }; + let by = if input.match_("!BY") { + let Some(by) = self.evaluate_number(&mut input) else { + return false; + }; + if by == 0.0 { + (self.error)(MacroError::ZeroBy); + return false; + } + by + } else { + 1.0 + }; + ( + DoInput::from_by(first, last, by), + MacroError::MiterateNumeric(MITERATE), + ) + } else { + (self.error)(MacroError::ExpectingEqualsOrIn); + return false; + }; + + let Some(body) = self.find_doend(&mut input) else { + return false; + }; + + for (i, item) in items.enumerate() { + if break_ { + break; + } + if i >= MITERATE { + (self.error)(miterate_error); + break; + } + let mut vars = self.vars.borrow_mut(); + if let Some(value) = vars.get_mut(&var_name) { + *value = item; + } else { + vars.insert(var_name.clone(), item); + } + } *orig_input = input; true } @@ -1007,7 +1253,7 @@ impl<'a> Expander<'a> { // Recursive macro calls. if self.may_expand() { if let Some(call) = Call::for_tokens(self.macros, &input.0, &self.error) { - let mut vars = BTreeMap::new(); + let vars = RefCell::new(BTreeMap::new()); let mut stack = take(&mut self.stack); stack.push(Frame { name: Some(call.0.macro_.name.clone()), @@ -1015,7 +1261,7 @@ impl<'a> Expander<'a> { }); let mut subexpander = Expander { break_: None, - vars: &mut vars, + vars: &vars, nesting_countdown: self.nesting_countdown.saturating_sub(1), stack, ..*self @@ -1058,7 +1304,7 @@ impl<'a> Expander<'a> { } // Variables set by `!DO` or `!LET`. - if let Some(value) = self.vars.get(id) { + if let Some(value) = self.vars.borrow().get(id) { tokenize_string(value.as_str(), self.mode, output, &self.error); input.advance(); return; @@ -1071,8 +1317,26 @@ impl<'a> Expander<'a> { if self.expand_let(input) { return; } + if self.expand_do(input) { + return; + } - todo!() + if input.match_("!BREAK") { + if let Some(ref mut break_) = self.break_ { + **break_ = true; + } else { + (self.error)(MacroError::BreakOutsideDo); + } + return; + } + + if input.match_("!ONEXPAND") { + *self.expand.borrow_mut() = true; + } else if input.match_("!OFFEXPAND") { + *self.expand.borrow_mut() = false; + } else { + output.push(input.advance().clone()); + } } } @@ -1100,7 +1364,8 @@ impl<'a> Call<'a> { F: Fn(MacroError) + 'a, { let error: Box = Box::new(error); - let mut vars = BTreeMap::new(); + let vars = RefCell::new(BTreeMap::new()); + let expand = RefCell::new(true); let mut me = Expander { macros: self.0.macros, error: &error, @@ -1118,9 +1383,9 @@ impl<'a> Call<'a> { location: Some(self.0.macro_.location.clone()), }, ], - vars: &mut vars, + vars: &vars, break_: None, - expand: None, + expand: &expand, }; let mut body = MacroTokens(&self.0.macro_.body); me.expand(&mut body, output); @@ -1128,3 +1393,4 @@ impl<'a> Call<'a> { } const MNEST: usize = 50; +const MITERATE: usize = 1000; -- 2.30.2