most of macros implemented
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 23 Jul 2024 20:10:40 +0000 (13:10 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 23 Jul 2024 20:10:40 +0000 (13:10 -0700)
rust/src/macros.rs

index df3f568b30c58bc15c6114ed1fad50337b109e83..89397db2382a84bef2e9919dd3b6bad201224323 100644 (file)
@@ -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<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>,
@@ -729,9 +772,68 @@ fn is_macro_keyword(s: &Identifier) -> 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 {
@@ -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<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)> {
@@ -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<Identifier> {
+        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<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
     }
@@ -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<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,
@@ -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;