fix: unquote not being truly recursive

Before we were skipping checks for unquotes within specials, which
themselves contain lists.  Quote is still broken, funnily enough.
This commit is contained in:
Roman Godmaire 2024-05-12 15:00:06 -04:00
parent 02463197c3
commit 6c08af02a1
6 changed files with 54 additions and 43 deletions

View file

@ -34,6 +34,14 @@ pub struct RawEnvironment {
}
impl Environment {
pub fn get_node(&self, ident: &str) -> Option<Node> {
match self.get(ident) {
Some(Value::NativeFunc(_)) => Some(Node::Symbol(ident.to_string())),
Some(Value::Node(val)) => Some(val.clone()),
None => None,
}
}
pub fn get(&self, ident: &str) -> Option<Value> {
if let Some(val) = self.0.current.borrow().get(ident) {
return Some(val.clone());

View file

@ -6,12 +6,20 @@ use mute_parser::Node;
use super::Value;
pub(super) fn standard() -> HashMap<String, Value> {
[(
"test",
inline! {
(fn* () (println "Hello, world!"))
},
)]
[
(
"defmacro",
inline! {
(macro* (name args body) ~(define (,name (macro* ,args ,body))))
},
),
(
"defun",
inline! {
(macro* (name args body) ~(define (,name (fn* ,args ,body))) )
},
),
]
.into_iter()
.map(|(k, v)| (k.to_string(), Value::Node(v)))
.collect()

View file

@ -14,7 +14,15 @@ pub fn eval(env: &Environment, ast: Vec<Node>) -> Result<Vec<Node>> {
};
if let Node::List(list) = &node {
if let Some(Node::Macro(body)) = list.first() {
let first = match list.first() {
Some(Node::Symbol(ident)) => match env.get_node(ident) {
m @ Some(Node::Macro(_)) => m,
_ => None,
},
node => node.cloned(),
};
if let Some(Node::Macro(body)) = first {
let mut body = body.iter();
let parameters = body.next().ok_or_else(|| todo!())?;
let parameters = read_parameters(parameters.clone())?;
@ -89,7 +97,7 @@ pub fn eval_node(env: &Environment, ast_node: Node) -> Result<Node> {
args.collect(),
body.cloned().collect::<Vec<Node>>(),
)? {
nodes if nodes.len() == 1 => nodes[0].to_owned(),
nodes if nodes.len() == 1 => eval_node(env, nodes[0].to_owned())?,
_ => Err(Error::MacroExpansion)?,
}
}
@ -183,14 +191,26 @@ pub fn eval_node(env: &Environment, ast_node: Node) -> Result<Node> {
Node::Quote(node) => *node,
Node::Quasiquote(node) => {
fn unquote(env: &Environment, node: Node) -> Result<Node> {
match node {
Node::List(list) => {
let list = list
macro_rules! unquote {
($variant:ident, $list:ident) => {{
let list = $list
.into_iter()
.map(|node| unquote(env, node))
.collect::<Result<Vec<Node>>>()?;
Ok(Node::List(list))
}
Ok(Node::$variant(list))
}};
}
match node {
Node::List(list) => unquote!(List, list),
Node::Define(list) => unquote!(Define, list),
Node::Let(list) => unquote!(Let, list),
Node::Function(list) => unquote!(Function, list),
Node::Macro(list) => unquote!(Macro, list),
Node::If(list) => unquote!(If, list),
Node::Do(list) => unquote!(Do, list),
Node::Eval(list) => unquote!(Do, list),
Node::Apply(list) => unquote!(Apply, list),
Node::Unquote(_) => eval_node(env, node),
node => Ok(node),
}
@ -221,15 +241,10 @@ pub fn eval_node(env: &Environment, ast_node: Node) -> Result<Node> {
eval_node(env, Node::List(nodes))?
}
Node::Symbol(sym) => match env
.get(&sym)
Node::Symbol(sym) => env
.get_node(&sym)
.ok_or_else(|| Error::NotInEnv(sym.clone()))?
.to_owned()
{
crate::env::Value::Node(node) => node,
// HACK: This feels so wrong
crate::env::Value::NativeFunc(_) => Node::Symbol(sym),
},
.to_owned(),
Node::Keyword(_)
| Node::Int(_)

View file

@ -94,6 +94,7 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, Error> {
'\'' => Token::Quote,
'`' => Token::Quasiquote,
'~' => Token::Quasiquote,
',' => Token::Unquote,
'+' => Token::Plus,
@ -215,9 +216,6 @@ fn read_ident(input: &mut Peekable<Chars>, first: char) -> Token {
"let*" => Token::Let,
"eval" => Token::Eval,
"apply" => Token::Apply,
"quote" => Token::Quote,
"quasiquote" => Token::Quasiquote,
"unquote" => Token::Unquote,
"if" => Token::If,
"do" => Token::Do,
ident => Token::Ident(ident.to_owned()),

View file

@ -30,15 +30,6 @@ fn next_statement(tokens: &mut TokenIter) -> Result<Option<Node>> {
None => return Ok(None),
};
macro_rules! read_quote_operator {
($type:expr) => {{
tokens.next(); // Munch opened
let quote = read_quote(tokens, $type)?;
tokens.next(); // Munch closed
quote
}};
}
let node = match tok {
Token::LeftParen => match tokens.peek().ok_or(Error::UnclosedParenthesis)? {
Token::Function => Node::Function(read_body(tokens)?),
@ -50,10 +41,6 @@ fn next_statement(tokens: &mut TokenIter) -> Result<Option<Node>> {
Token::If => Node::If(read_body(tokens)?),
Token::Do => Node::Do(read_body(tokens)?),
Token::Quote => read_quote_operator!("quote"),
Token::Quasiquote => read_quote_operator!("quasiquote"),
Token::Unquote => read_quote_operator!("unquote"),
_ => read_list(tokens, Token::RightParen)?,
},
@ -155,11 +142,6 @@ fn read_body(tokens: &mut TokenIter) -> Result<Vec<Node>> {
tokens.next(); // Munch special
while tokens.peek() != Some(&Token::RightParen) {
if let Some(node) = next_statement(tokens)? {
body.push(node);
continue;
}
match next_statement(tokens)? {
Some(node) => body.push(node),
None => Err(Error::UnclosedParenthesis)?,

View file

@ -100,7 +100,7 @@ impl std::fmt::Display for Node {
.unwrap_or_default()
),
Node::Define(body) => write!(f, "(define! {})", reduce_list(body)),
Node::Define(body) => write!(f, "(define {})", reduce_list(body)),
Node::Let(body) => write!(f, "(let* {})", reduce_list(body)),
Node::Macro(body) => write!(f, "(macro* {})", reduce_list(body)),
Node::Function(body) => write!(f, "(fn* {})", reduce_list(body)),