use lazy_static::lazy_static;
use num::Integer;
use std::{
+ cell::RefCell,
cmp::Ordering,
collections::{BTreeMap, HashMap, HashSet},
mem::take,
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`.
}
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;
stack: Vec<Frame>,
// May macro calls be expanded?
- expand: Option<&'a bool>,
+ expand: &'a RefCell<bool>,
/// Variables from `!DO` and `!LET`.
- vars: &'a mut BTreeMap<Identifier, String>,
+ vars: &'a RefCell<BTreeMap<Identifier, String>>,
// Only set if inside a `!DO` loop. If true, break out of the loop.
break_: Option<&'a mut bool>,
KEYWORDS.contains(s)
}
+enum DoInput {
+ List(Vec<String>),
+ Up { first: f64, last: f64, by: f64 },
+ Down { first: f64, last: f64, by: f64 },
+ Empty,
+}
+
+impl DoInput {
+ fn from_list(items: Vec<MacroToken>) -> 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<Self::Item> {
+ 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 {
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()),
});
let mut subexpander = Expander {
stack,
- vars: &mut vars,
+ vars: &vars,
break_: None,
macro_: None,
args: None,
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());
}
self.evaluate_or(input)
}
+ fn evaluate_number(&mut self, input: &mut MacroTokens) -> Option<f64> {
+ 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)> {
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<Identifier> {
+ let Some(var_name) = input.take_macro_id() else {
+ (self.error)(MacroError::ExpectingMacroVarName(construct));
+ return None;
};
if is_macro_keyword(var_name)
|| self
.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_("=") {
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<MacroTokens<'b>> {
+ 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
}
// 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()),
});
let mut subexpander = Expander {
break_: None,
- vars: &mut vars,
+ vars: &vars,
nesting_countdown: self.nesting_countdown.saturating_sub(1),
stack,
..*self
}
// 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;
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());
+ }
}
}
F: Fn(MacroError) + 'a,
{
let error: Box<dyn Fn(MacroError) + 'a> = 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,
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);
}
const MNEST: usize = 50;
+const MITERATE: usize = 1000;