commit 1c21f55c341c5db7e4f233bcbb5a249853d9d1ec
parent 61c765ee45b79fe75852bd724033a4ae95936d6f
Author: knutsen <samuel@knutsen.co>
Date: Sat, 20 Mar 2021 01:18:07 +0000
Added macros: define, ifdef and log.
Diffstat:
M | src/parse/expander.rs | | | 323 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
1 file changed, 248 insertions(+), 75 deletions(-)
diff --git a/src/parse/expander.rs b/src/parse/expander.rs
@@ -1,7 +1,7 @@
-use super::parser::{ParseNode, ParseTree, Node};
+use super::parser::{self, ParseNode, ParseTree, Node};
use super::tokens::Site;
-use std::{fmt, path::Path, error::Error};
+use std::{fmt, path::{Path, PathBuf}, ffi::OsString, error::Error};
use colored::*;
@@ -29,15 +29,25 @@ impl Error for ExpansionError { }
use std::collections::HashMap;
-#[allow(dead_code)]
+#[derive(Clone)]
struct Macro {
name : String,
params : Vec<String>,
- body : ParseNode
+ body : Vec<ParseNode>
}
+impl Macro {
+ fn new(name : &str) -> Macro {
+ Macro {
+ name: name.to_string(),
+ params: Vec::new(),
+ body: Vec::new()
+ }
+ }
+}
+
+#[derive(Clone)]
struct ExpansionContext {
- #[allow(dead_code)] // TODO: User macros!
definitions : HashMap<String, Macro>
}
@@ -47,90 +57,248 @@ impl ExpansionContext {
fn expand_invocation(&mut self, name : &str,
site : &Site,
params : Vec<ParseNode>)
- -> Result<ParseTree, ExpansionError> {
- match name {
- "include" => {
- let path_node = if let [ p ] = params.as_slice() {
- p
+ -> Result<ParseTree, ExpansionError> { match name {
+ // Some macros are lazy (e.g. `ifdef`), so each macro has to
+ // expand the macros in its arguments individually.
+ "define" => {
+ let (head, nodes) = if let [head, nodes@..] = params.as_slice() {
+ (head, nodes)
+ } else {
+ return Err(ExpansionError::new(
+ &format!("`define` macro takes at least \
+ two (2) arguments ({} were given.", params.len()),
+ site));
+ };
+
+ // If head is atomic, we assign to a 'variable'.
+ let def_macro = if let Some(variable) = head.atomic() {
+ let mut definition = Macro::new(&variable.value);
+ for node in nodes {
+ definition.body.push(node.clone());
+ }
+ definition
+ } else { // Otherwise, we are assigning to a 'function'.
+ let (name, params) = if let ParseNode::List(call) = head {
+ let (name, params) = if let [name, params@..] = call.as_slice() {
+ (name, params)
+ } else {
+ return Err(ExpansionError::new(
+ "`define` function definition must at \
+ least have a name.", site));
+ };
+ let mut arguments = Vec::with_capacity(params.len());
+ for node in params { // Verify params are symbols.
+ if let ParseNode::Symbol(param) = node {
+ arguments.push(param.value.clone());
+ } else {
+ return Err(ExpansionError::new(
+ "`define` function arguments must be \
+ symbols/identifers.", site));
+ };
+ }
+ if let ParseNode::Symbol(name) = name {
+ (name.value.clone(), arguments)
+ } else {
+ return Err(ExpansionError::new(
+ "`define` function name must be \
+ a symbol/identifier.", site));
+ }
} else {
return Err(ExpansionError::new(
- &format!("Incorrect number of arguments \
- to `{}' macro. Got {}, expected {}.",
- name, params.len(), 1),
- site));
+ "First argument of `define` macro must be a list \
+ or variable name/identifier.", site));
};
- let path = if let Some(node) = path_node.atomic() {
- node.value
+ let mut definition = Macro::new(&name);
+ definition.params = params;
+ for node in nodes {
+ definition.body.push(node.clone());
+ }
+ definition
+ };
+
+ self.definitions.insert(def_macro.name.clone(), def_macro);
+
+ Ok(vec![])
+ },
+ "ifdef" => {
+ if params.len() < 2 || params.len() > 3 {
+ eprintln!("{:?}", params);
+ return Err(ExpansionError::new(&format!("`ifdef` takes one (1) \
+ condition and one (1) consequent, a third optional \
+ alternative expression may also be provided, but \
+ `ifdef` was given {} arguments.", params.len()),
+ site));
+ }
+ let symbol = if let Some(node) = params[0].atomic() {
+ node.value
+ } else {
+ return Err(ExpansionError::new("The first argument to \
+ `ifdef` must be a symbol/name.", ¶ms[0].site()));
+ };
+
+ if self.definitions.contains_key(&symbol) {
+ Ok(self.expand_node(params[1].clone())?)
+ } else {
+ if let Some(alt) = params.get(2) {
+ Ok(self.expand_node(alt.clone())?)
} else {
- return Err(ExpansionError::new(
- &format!("Bad argument to `{}' macro.\n\
- Expected a path, but did not get any value
- that could be interpreted as a path.", name),
- site))
- };
+ Ok(vec![])
+ }
+ }
+ },
+ "include" => {
+ let params = self.expand_nodes(params)?;
+ let path_node = if let [ p ] = params.as_slice() {
+ p
+ } else {
+ return Err(ExpansionError::new(
+ &format!("Incorrect number of arguments \
+ to `{}' macro. Got {}, expected {}.",
+ name, params.len(), 1),
+ site));
+ };
- // Open file, and parse contents!
- let tree = match super::parse_file_noexpand(&Path::new(&path)) {
- Ok(tree) => tree,
- Err(error) => {
- eprintln!("{}", error);
- return Err(ExpansionError::new(
- &format!("Error parsing file `{}' from \
- `include' macro invocation.", path),
- site))
- }
- };
+ let path = if let Some(node) = path_node.atomic() {
+ node.value
+ } else {
+ return Err(ExpansionError::new(
+ &format!("Bad argument to `{}' macro.\n\
+ Expected a path, but did not get any value
+ that could be interpreted as a path.", name),
+ site))
+ };
- // Build new (expanded) tree, with result of previous
- // parse, while recursively expanding each branch in the
- // tree too, as they are added.
- let mut expanded_tree = Vec::with_capacity(tree.len());
- for branch in tree {
- expanded_tree.extend(self.expand_node(branch)?);
+ // Open file, and parse contents!
+ let path = Path::new(&path);
+ let tree = match super::parse_file_noexpand(&path) {
+ Ok(tree) => tree,
+ Err(error) => {
+ let err = ExpansionError::new(
+ &format!("{}", error), site);
+ // Try with `.sex` extensions appended.
+ let mut with_ext = PathBuf::from(path);
+ let filename = path.file_name().ok_or(err.clone())?;
+ with_ext.pop();
+
+ let mut new_filename = OsString::new();
+ new_filename.push(filename);
+ new_filename.push(".sex");
+
+ with_ext.push(new_filename);
+ match super::parse_file_noexpand(&with_ext) {
+ Ok(tree) => tree,
+ Err(_) => return Err(err)
+ }
}
- Ok(expanded_tree)
- },
- "date" => {
- let date_format = if let [ p ] = params.as_slice() {
- p
- } else {
- return Err(ExpansionError::new(
- &format!("`{}' macro only expects one formatting argument.",
- name),
- site))
- };
+ };
- let (date_format, site) = if let Some(node) = date_format.atomic() {
- (node.value, node.site)
- } else {
- return Err(ExpansionError::new(
- &format!("`{}' macro needs string (or atomic) \
- formatting argument.", name),
- site))
- };
+ // Build new (expanded) tree, with result of previous
+ // parse, while recursively expanding each branch in the
+ // tree too, as they are added.
+ let mut expanded_tree = Vec::with_capacity(tree.len());
+ for branch in tree {
+ expanded_tree.extend(self.expand_node(branch)?);
+ }
+ Ok(expanded_tree)
+ },
+ "date" => {
+ let params = self.expand_nodes(params)?;
+ let date_format = if let [ p ] = params.as_slice() {
+ p
+ } else {
+ return Err(ExpansionError::new(
+ &format!("`{}' macro only expects one formatting argument.",
+ name),
+ site))
+ };
- let now = chrono::Local::now();
- let formatted = now.format(&date_format).to_string();
- Ok(vec![ParseNode::String(Node::new(&formatted, &site))])
- },
- _ => Err(ExpansionError::new(
- &format!("Macro not found (`{}').", name),
+ let (date_format, site) = if let Some(node) = date_format.atomic() {
+ (node.value, node.site)
+ } else {
+ return Err(ExpansionError::new(
+ &format!("`{}' macro needs string (or atomic) \
+ formatting argument.", name),
site))
+ };
+
+ let now = chrono::Local::now();
+ let formatted = now.format(&date_format).to_string();
+ Ok(vec![ParseNode::String(Node::new(&formatted, &site))])
+ },
+ "log" => {
+ let mut words = Vec::with_capacity(params.len());
+ for param in self.expand_nodes(params)? {
+ if let Some(word) = param.atomic() {
+ words.push(word.value.clone());
+ } else {
+ return Err(ExpansionError::new("`log` should only take \
+ arguments that are either symbols, strings or numbers.",
+ ¶m.site()));
+ }
+ }
+ eprintln!("{} {} {}: {}", "[#]".bold(), "log".bold().yellow(),
+ site, words.join(" "));
+ Ok(vec![])
}
- }
+ name => {
+ let params = self.expand_nodes(params)?;
+
+ let mac = if let Some(mac) = self.definitions.get(name) {
+ mac
+ } else {
+ return Err(ExpansionError::new(
+ &format!("Macro not found (`{}').", name), site))
+ };
+
+ // Instance of expansion subcontext.
+ let mut subcontext = self.clone();
+ // Check enough arguments were given.
+ if params.len() != mac.params.len() {
+ return Err(ExpansionError::new(
+ &format!("`%{}` macro expects {} arguments, \
+ but {} were given.", &mac.name, mac.params.len(),
+ params.len()), site));
+ }
+ // Define arguments for body.
+ for i in 0..params.len() {
+ let mut arg_macro = Macro::new(&mac.params[i]);
+ arg_macro.body.push(params[i].clone());
+ subcontext.definitions.insert(mac.params[i].clone(), arg_macro);
+ }
+ // Expand body.
+ subcontext.expand_nodes(mac.body.clone())
+ }
+ }}
pub fn expand_node(&mut self, node : ParseNode)
-> Result<ParseTree, ExpansionError> {
match node {
- ParseNode::Symbol(ref _sym) => {
+ ParseNode::Symbol(ref sym) => {
// Check if symbol starts with %... and replace it
// with it's defined value.
- Ok(vec![node])
+ if sym.value.starts_with("%") {
+ let name = &sym.value[1..];
+ if let Some(def) = self.definitions.get(name) {
+ if !def.params.is_empty() { // Should not be a function.
+ return Err(ExpansionError::new(
+ &format!("`{}` is a macro that takes arguments, \
+ and cannot be used as a variable.", name),
+ &sym.site))
+ }
+ Ok(def.body.clone())
+ } else { // Not found.
+ Err(ExpansionError::new(
+ &format!("No such macro, `{}`.", name),
+ &sym.site))
+ }
+ } else {
+ Ok(vec![node])
+ }
},
ParseNode::List(list) => {
- // Check for macro invocation (%... _ _ _).
+ // Check for macro invocation (%_ _ _ _).
// Recurse over every element.
let len = list.len();
let mut call = list.into_iter();
@@ -141,7 +309,8 @@ impl ExpansionContext {
// Rebuild node...
let name = &sym.value[1..];
let Node { site, .. } = sym;
- let params = call.collect();
+ // Clean macro arguments from whitespace tokens.
+ let params = parser::strip(&call.collect(), false);
return self.expand_invocation(name, site, params);
}
}
@@ -164,16 +333,20 @@ impl ExpansionContext {
_ => Ok(vec![node])
}
}
+
+ fn expand_nodes(&mut self, tree : ParseTree) -> Result<ParseTree, ExpansionError> {
+ let mut expanded = Vec::with_capacity(tree.len());
+ for branch in tree {
+ expanded.extend(self.expand_node(branch)?);
+ }
+ Ok(expanded)
+ }
}
+
/// Macro-expansion phase.
/// Macros start with `%...'.
pub fn expand(tree : ParseTree) -> Result<ParseTree, ExpansionError> {
let mut context = ExpansionContext::new();
-
- let mut expanded = Vec::with_capacity(tree.len());
- for branch in tree {
- expanded.extend(context.expand_node(branch)?);
- }
- Ok(expanded)
+ context.expand_nodes(tree)
}