diff --git a/src/ast.rs b/src/ast.rs index bfb576b..fed47c0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -42,13 +42,6 @@ pub struct Binding { pub span: Span, } -#[derive(Debug, Clone, PartialEq)] -pub struct CondClause { - pub test: Expr, - pub value: Expr, - pub span: Span, -} - #[derive(Debug, Clone, PartialEq)] pub enum Pattern { Wildcard(Span), @@ -96,12 +89,6 @@ pub enum Expr { span: Span, }, - Cond { - clauses: Vec, - else_body: Option>, - span: Span, - }, - Let { bindings: Vec, body: Vec, @@ -182,7 +169,6 @@ impl Expr { | Expr::Symbol(_, s) | Expr::Keyword(_, s) => *s, Expr::If { span, .. } - | Expr::Cond { span, .. } | Expr::Let { span, .. } | Expr::Lambda { span, .. } | Expr::Call { span, .. } @@ -328,27 +314,6 @@ mod tests { assert!(matches!(expr, Expr::If { .. })); } - #[test] - fn cond_expr() { - let expr = Expr::Cond { - clauses: vec![CondClause { - test: Expr::Bool(true, span(6, 10)), - value: Expr::Int(1, span(11, 12)), - span: span(6, 12), - }], - else_body: Some(Box::new(Expr::Int(0, span(19, 20)))), - span: span(0, 21), - }; - assert_eq!(expr.span(), span(0, 21)); - if let Expr::Cond { - clauses, else_body, .. - } = &expr - { - assert_eq!(clauses.len(), 1); - assert!(else_body.is_some()); - } - } - #[test] fn let_expr() { let expr = Expr::Let { diff --git a/src/macro_expand.rs b/src/macro_expand.rs index b4e88d8..a29aaf8 100644 --- a/src/macro_expand.rs +++ b/src/macro_expand.rs @@ -5,7 +5,7 @@ use crate::ast::{self, Binding, Expr, TopForm}; use crate::diagnostics::Diagnostic; use crate::lexer; use crate::parser; -use crate::source::{FileId, Span}; +use crate::source::FileId; use crate::types::{SyntaxValue, expr_to_syntax, syntax_to_expr}; const MAX_EXPANSION_DEPTH: usize = 64; @@ -153,12 +153,6 @@ fn expand_expr( } match expr { - Expr::Cond { - clauses, - else_body, - span, - } => expand_cond(clauses, else_body, span, registry, diagnostics, depth), - Expr::Call { func, args, span } => { if let Expr::Symbol(ref name, _) = *func && let Some(macro_def) = registry.get(name.as_str()) @@ -724,31 +718,6 @@ fn rename_syntax_fn(items: Vec, renames: &HashMap) SyntaxValue::List(result) } -fn expand_cond( - clauses: Vec, - else_body: Option>, - span: Span, - registry: &HashMap, - diagnostics: &mut Vec, - depth: usize, -) -> Expr { - let mut result = match else_body { - Some(e) => expand_expr(*e, registry, diagnostics, depth), - None => Expr::Nil(span), - }; - - for clause in clauses.into_iter().rev() { - result = Expr::If { - test: Box::new(expand_expr(clause.test, registry, diagnostics, depth)), - then_branch: Box::new(expand_expr(clause.value, registry, diagnostics, depth)), - else_branch: Box::new(result), - span, - }; - } - - result -} - #[cfg(test)] mod tests { use super::*; @@ -758,77 +727,6 @@ mod tests { Span::new(FileId::new(0), start, end) } - #[test] - fn expand_cond_to_nested_if() { - let input = vec![TopForm::Expr(Expr::Cond { - clauses: vec![ - ast::CondClause { - test: Expr::Bool(true, span(0, 4)), - value: Expr::Int(1, span(5, 6)), - span: span(0, 6), - }, - ast::CondClause { - test: Expr::Bool(false, span(7, 12)), - value: Expr::Int(2, span(13, 14)), - span: span(7, 14), - }, - ], - else_body: Some(Box::new(Expr::Int(3, span(21, 22)))), - span: span(0, 23), - })]; - - let (result, diags) = expand(input); - assert!(diags.is_empty()); - if let TopForm::Expr(Expr::If { - test, - then_branch, - else_branch, - .. - }) = &result[0] - { - assert!(matches!(test.as_ref(), Expr::Bool(true, _))); - assert!(matches!(then_branch.as_ref(), Expr::Int(1, _))); - assert!(matches!(else_branch.as_ref(), Expr::If { .. })); - - if let Expr::If { - test: inner_test, - then_branch: inner_then, - else_branch: inner_else, - .. - } = else_branch.as_ref() - { - assert!(matches!(inner_test.as_ref(), Expr::Bool(false, _))); - assert!(matches!(inner_then.as_ref(), Expr::Int(2, _))); - assert!(matches!(inner_else.as_ref(), Expr::Int(3, _))); - } else { - panic!("expected nested if"); - } - } else { - panic!("expected if expression"); - } - } - - #[test] - fn expand_cond_no_else() { - let input = vec![TopForm::Expr(Expr::Cond { - clauses: vec![ast::CondClause { - test: Expr::Bool(true, span(0, 4)), - value: Expr::Int(1, span(5, 6)), - span: span(0, 6), - }], - else_body: None, - span: span(0, 7), - })]; - - let (result, diags) = expand(input); - assert!(diags.is_empty()); - if let TopForm::Expr(Expr::If { else_branch, .. }) = &result[0] { - assert!(matches!(else_branch.as_ref(), Expr::Nil(_))); - } else { - panic!("expected if expression"); - } - } - #[test] fn expand_and_to_if() { let input = vec![TopForm::Expr(Expr::Call { @@ -951,14 +849,13 @@ mod tests { name: "test".into(), params: vec![], return_type: None, - body: vec![Expr::Cond { - clauses: vec![ast::CondClause { - test: Expr::Bool(true, span(0, 4)), - value: Expr::Int(1, span(5, 6)), - span: span(0, 6), - }], - else_body: Some(Box::new(Expr::Int(0, span(13, 14)))), - span: span(0, 15), + body: vec![Expr::Call { + func: Box::new(Expr::Symbol("and".into(), span(1, 4))), + args: vec![ + Expr::Bool(true, span(5, 9)), + Expr::Bool(false, span(10, 15)), + ], + span: span(0, 16), }], span: span(0, 20), }]; diff --git a/src/parser.rs b/src/parser.rs index da08237..4073100 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,4 @@ -use crate::ast::{ - Binding, CondClause, Expr, Field, MatchClause, Param, Pattern, TopForm, TypeExpr, Variant, -}; +use crate::ast::{Binding, Expr, Field, MatchClause, Param, Pattern, TopForm, TypeExpr, Variant}; use crate::diagnostics::{Diagnostic, Label}; use crate::lexer::{Token, TokenKind}; use crate::source::{FileId, Span}; @@ -420,7 +418,6 @@ impl<'a> Parser<'a> { match name.as_str() { "if" => return self.parse_if(open_span), "let" => return self.parse_let(open_span), - "cond" => return self.parse_cond(open_span), "match" => return self.parse_match(open_span), "fn" => return self.parse_lambda(open_span), "spawn" => return self.parse_spawn(open_span), @@ -609,34 +606,6 @@ impl<'a> Parser<'a> { Some(body) } - fn parse_cond(&mut self, open_span: Span) -> Option { - self.pos += 1; - - let mut clauses = Vec::new(); - let mut else_body = None; - - while !self.at_end() && !self.check(|k| matches!(k, TokenKind::RightParen)) { - if self.check(|k| matches!(k, TokenKind::Keyword(s) if s == "else")) { - self.pos += 1; - else_body = Some(Box::new(self.parse_expr()?)); - break; - } - - let test = self.parse_expr()?; - let value = self.parse_expr()?; - let span = Span::new(test.span().file, test.span().start, value.span().end); - clauses.push(CondClause { test, value, span }); - } - - let close_span = self.expect_right_paren(open_span)?; - - Some(Expr::Cond { - clauses, - else_body, - span: Span::new(open_span.file, open_span.start, close_span.end), - }) - } - fn parse_match(&mut self, open_span: Span) -> Option { self.pos += 1; @@ -1402,54 +1371,44 @@ mod tests { } #[test] - fn cond_basic() { + fn cond_parses_as_call() { let (forms, diags) = parse_source("(cond (< x 0) \"neg\" (> x 0) \"pos\")"); assert!(diags.is_empty()); - if let TopForm::Expr(Expr::Cond { - clauses, else_body, .. - }) = &forms[0] - { - assert_eq!(clauses.len(), 2); - assert!(matches!(&clauses[0].test, Expr::Call { .. })); - assert!(matches!(&clauses[0].value, Expr::String(s, _) if s == "neg")); - assert!(matches!(&clauses[1].test, Expr::Call { .. })); - assert!(matches!(&clauses[1].value, Expr::String(s, _) if s == "pos")); - assert!(else_body.is_none()); + if let TopForm::Expr(Expr::Call { func, args, .. }) = &forms[0] { + assert!(matches!(func.as_ref(), Expr::Symbol(s, _) if s == "cond")); + assert_eq!(args.len(), 4); + assert!(matches!(&args[0], Expr::Call { .. })); + assert!(matches!(&args[1], Expr::String(s, _) if s == "neg")); + assert!(matches!(&args[2], Expr::Call { .. })); + assert!(matches!(&args[3], Expr::String(s, _) if s == "pos")); } else { - panic!("expected cond expression"); + panic!("expected call expression"); } } #[test] - fn cond_with_else() { + fn cond_with_else_parses_as_call() { let (forms, diags) = parse_source("(cond (< x 0) \"neg\" :else \"zero\")"); assert!(diags.is_empty()); - if let TopForm::Expr(Expr::Cond { - clauses, else_body, .. - }) = &forms[0] - { - assert_eq!(clauses.len(), 1); - assert!(else_body.is_some()); - assert!( - matches!(else_body.as_ref().unwrap().as_ref(), Expr::String(s, _) if s == "zero") - ); + if let TopForm::Expr(Expr::Call { func, args, .. }) = &forms[0] { + assert!(matches!(func.as_ref(), Expr::Symbol(s, _) if s == "cond")); + assert_eq!(args.len(), 4); + assert!(matches!(&args[2], Expr::Keyword(s, _) if s == "else")); + assert!(matches!(&args[3], Expr::String(s, _) if s == "zero")); } else { - panic!("expected cond expression"); + panic!("expected call expression"); } } #[test] - fn cond_empty() { + fn cond_empty_parses_as_call() { let (forms, diags) = parse_source("(cond)"); assert!(diags.is_empty()); - if let TopForm::Expr(Expr::Cond { - clauses, else_body, .. - }) = &forms[0] - { - assert!(clauses.is_empty()); - assert!(else_body.is_none()); + if let TopForm::Expr(Expr::Call { func, args, .. }) = &forms[0] { + assert!(matches!(func.as_ref(), Expr::Symbol(s, _) if s == "cond")); + assert!(args.is_empty()); } else { - panic!("expected cond expression"); + panic!("expected call expression"); } } diff --git a/src/typechecker.rs b/src/typechecker.rs index 7eef9c0..dc88b01 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -56,11 +56,6 @@ impl Checker { body, span, } => self.check_let(bindings, body, *span), - ast::Expr::Cond { - clauses, - else_body, - span, - } => self.check_cond(clauses, else_body.as_deref(), *span), ast::Expr::Lambda { params, return_type, @@ -489,60 +484,6 @@ impl Checker { }) } - fn check_cond( - &mut self, - clauses: &[ast::CondClause], - else_body: Option<&ast::Expr>, - span: Span, - ) -> Option { - let else_expr = if let Some(e) = else_body { - self.check_expr(e)? - } else { - hir::Expr::Nil(span) - }; - - let mut result = else_expr; - - for clause in clauses.iter().rev() { - let checked_test = self.check_expr(&clause.test)?; - - if checked_test.ty() != &VexType::Bool { - self.diagnostics.push(Diagnostic::error( - format!("cond test must be Bool, found {}", checked_test.ty()), - checked_test.span(), - )); - return None; - } - - let checked_value = self.check_expr(&clause.value)?; - - let ty = match VexType::types_compatible(checked_value.ty(), result.ty()) { - Some(merged) => merged, - None => { - self.diagnostics.push(Diagnostic::error( - format!( - "cond branches have different types: {} and {}", - checked_value.ty(), - result.ty() - ), - clause.span, - )); - return None; - } - }; - - result = hir::Expr::If { - test: Box::new(checked_test), - then_branch: Box::new(checked_value), - else_branch: Box::new(result), - span: clause.span, - ty, - }; - } - - Some(result) - } - fn check_lambda( &mut self, params: &[ast::Param], @@ -1856,7 +1797,11 @@ mod tests { fn check_source(source: &str) -> (hir::Module, Vec) { let (tokens, _) = lex(source, FileId::new(0)); let (ast, _) = parse(&tokens); - check(&ast) + let (expanded, expand_diags) = crate::macro_expand::expand(ast); + if !expand_diags.is_empty() { + return (hir::Module { top_forms: vec![] }, expand_diags); + } + check(&expanded) } #[test] diff --git a/src/types.rs b/src/types.rs index 95feaee..e67a71e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -222,20 +222,6 @@ pub fn expr_to_syntax(expr: &ast::Expr) -> SyntaxValue { SyntaxValue::Sym("splice".into()), expr_to_syntax(inner), ]), - ast::Expr::Cond { - clauses, else_body, .. - } => { - let mut items = vec![SyntaxValue::Sym("cond".into())]; - for clause in clauses { - items.push(expr_to_syntax(&clause.test)); - items.push(expr_to_syntax(&clause.value)); - } - if let Some(body) = else_body { - items.push(SyntaxValue::Kw("else".into())); - items.push(expr_to_syntax(body)); - } - SyntaxValue::List(items) - } ast::Expr::Match { scrutinee, clauses, .. } => { diff --git a/stdlib/prelude.vx b/stdlib/prelude.vx index 2b8b60a..21cfccb 100644 --- a/stdlib/prelude.vx +++ b/stdlib/prelude.vx @@ -5,3 +5,13 @@ (list (quote let) (list (quote tmp) a) (list (quote if) (quote tmp) (quote tmp) b))) + +(defmacro cond [& clauses] + (if (empty? clauses) + (quote nil) + (if (keyword? (first clauses)) + (first (rest clauses)) + (list (quote if) + (first clauses) + (first (rest clauses)) + (cons (quote cond) (rest (rest clauses)))))))