diff --git a/cogs/match/match-tests.scm b/cogs/match/match-tests.scm new file mode 100644 index 000000000..2c53a2042 --- /dev/null +++ b/cogs/match/match-tests.scm @@ -0,0 +1,117 @@ +(provide __module__) +(define __module__ 'match-tests) + +(define (test name input expected) + (assert! (equal? input expected)) + (if (equal? input expected) + (begin + (display "> ") + (display name) + (display " ... ") + (display "OK") + (newline)) + (begin + (display "> ") + (display name) + (display " ... ") + (display "FAILED") + (newline) + (display " Expected: ") + (display expected) + (display ", Found ") + (displayln input)))) + +; ;; a => 1 +; ;; x => '(2 3 4 5) +(test "Basic ellipses matching works" + (match '(1 2 3 4 5) + [(list a x ...) x]) + (list 2 3 4 5)) + +(test "Ellipses matches until next value" + (match (list 1 2 3 4 5) + [(list first rest ... last) rest]) + (list 2 3 4)) + +(test "match list of constants" + (match (list 1 2 3 4 5) + [(list 1 2 3 4 5) 'case1]) + 'case1) + +(test "match binds free vars" + (match (list 1 2 3 4 5) + [(list x 2 y 4 z) (+ x y z)]) + (+ 1 3 5)) + +(test "match binds to first matching case" + (match (list 1 2 3 4 5) + [?x 'case1] + [(list ?a ?b ?c ?d ?e) 'case2]) + 'case1) + +(test "match constant" + (match 10 + [10 'case1]) + 'case1) + +(test "takes else case when nothing matches" + (match (list 1 (list 2 (list 3))) + [(list x y z) 'case1] + [24 'case2] + [else 'case3]) + 'case3) + +(test "Empty list matches empty list" + (match '() + [() 'found-empty-list!] + [(list x xs...) 'found-list!]) + 'found-empty-list!) + +(test "match nested list" + (match (list (list 1 2) 3 (list 4 (list 5 6))) + [(list (list a 2) b (list c (list 5 6))) (+ a b c)]) + (+ 1 3 4)) + +(test "wildcards work" + (match (list 1 2 3 4 5) + [(list 1 2 _ _ a) a]) + 5) + +;; Generic optimization passes? +(test "pattern matching against anonymous function application" + (match '((lambda (x) 10) 20) + + [(list (list 'lambda (list var) body) arg) body]) + 10) + +(define (quoted? x) + (and (list? x) (not (null? x)) (equal? (car x) 'quote))) + +(define (constant? x) + (or (number? x) (quoted? x))) + +(define (identify-sequential-maps expr) + (match expr + ;; Matching against quasiquotes, should help with real pattern matching? + [`(map ,func1 (map ,func2 ,lst)) + `(map (lambda (x) + + (,func2 (,func1 x))) + ,lst)] + [_ expr])) + +(identify-sequential-maps '(map add1 (range 0 100))) +(identify-sequential-maps '(map add1 (map sub1 (range 0 100)))) + +(define my-expr + '(define (foo-bar x) + ((lambda (y) 100) x))) + +(define (remove-constant-anonymous-functions expr) + (match expr + [`((lambda (,var) ,body) ,@args) (if (constant? body) body expr)] + [(list args ...) (map remove-constant-anonymous-functions args)] + [_ expr])) + +; Nano pass framework for rewriting and incrementally lowering! +(remove-constant-anonymous-functions my-expr) diff --git a/cogs/match/match.scm b/cogs/match/match.scm new file mode 100644 index 000000000..61f981f71 --- /dev/null +++ b/cogs/match/match.scm @@ -0,0 +1,21 @@ +(provide match-let*) + +(define-syntax match-let* + (syntax-rules () + [(match-let* () + body ...) + + (begin + body ...)] + + [(match-let* ([pat expr] rest ...) + body ...) + + (match expr + [pat + (match-let* (rest ...) + body ...)])])) + +;; Example: +; (match-let* ([(list a b) '(1 2)] [(list x ...) (list 1 2 3 4)]) +; (list b a x)) diff --git a/crates/steel-core/src/compiler/modules.rs b/crates/steel-core/src/compiler/modules.rs index bcc358c70..2bea79f52 100644 --- a/crates/steel-core/src/compiler/modules.rs +++ b/crates/steel-core/src/compiler/modules.rs @@ -97,7 +97,8 @@ declare_builtins!( "#%private/steel/print" => "../scheme/print.scm", "#%private/steel/control" => "../scheme/modules/parameters.scm", "#%private/steel/reader" => "../scheme/modules/reader.scm", - "#%private/steel/stdlib" => "../scheme/stdlib.scm" + "#%private/steel/stdlib" => "../scheme/stdlib.scm", + "#%private/steel/match" => "../scheme/modules/match.scm" ); create_prelude!( @@ -105,8 +106,10 @@ create_prelude!( "#%private/steel/contract", "#%private/steel/print", "#%private/steel/reader", + "#%private/steel/match", for_syntax "#%private/steel/control", for_syntax "#%private/steel/contract" + // for_syntax "#%private/steel/match" ); pub static STEEL_HOME: Lazy> = Lazy::new(|| std::env::var("STEEL_HOME").ok()); @@ -538,6 +541,39 @@ impl ModuleManager { let kernel_macros_in_scope: HashSet<_> = module.provides_for_syntax.iter().cloned().collect(); + // let defmacros_exported: HashSet<_> = module. + + // dbg!(&kernel_macros_in_scope); + + let module_name = module.name.to_str().unwrap().to_string(); + + if let Some(kernel) = kernel.as_mut() { + if kernel.exported_defmacros(&module_name).is_some() { + lifted_kernel_environments.insert( + module_name.clone(), + KernelDefMacroSpec { + env: module_name, + exported: None, + name_mangler: name_mangler.clone(), + }, + ); + } + } + + // TODO: This isn't right - only check if there are defmacro things + // that we need to lift - just check the values that are in the defmacros + // environment in the kernel + // if !kernel_macros_in_scope.is_empty() { + // lifted_kernel_environments.insert( + // module_name.clone(), + // KernelDefMacroSpec { + // env: module_name, + // exported: None, + // name_mangler: name_mangler.clone(), + // }, + // ); + // } + ast = ast .into_iter() .map(|x| { @@ -612,14 +648,14 @@ impl ModuleManager { name_mangler.visit(&mut first_round_expanded); } - lifted_kernel_environments.insert( - module_name.clone(), - KernelDefMacroSpec { - env: module_name, - exported: None, - name_mangler: name_mangler.clone(), - }, - ); + // lifted_kernel_environments.insert( + // module_name.clone(), + // KernelDefMacroSpec { + // env: module_name, + // exported: None, + // name_mangler: name_mangler.clone(), + // }, + // ); Ok(first_round_expanded) } else { diff --git a/crates/steel-core/src/parser/kernel.rs b/crates/steel-core/src/parser/kernel.rs index aa089da6c..d159bcde3 100644 --- a/crates/steel-core/src/parser/kernel.rs +++ b/crates/steel-core/src/parser/kernel.rs @@ -25,9 +25,7 @@ use steel_parser::expr_list; use super::{ ast::{ExprKind, TryFromSteelValVisitorForExprKind}, interner::InternedString, - replace_idents::RewriteSpan, span_visitor::get_span, - visitors::ConsumingVisitor, }; thread_local! { @@ -459,6 +457,15 @@ impl Kernel { // todo!("Run through every expression, and memoize them by calling (set! (make-memoize ))") } + pub fn exported_defmacros(&self, environment: &str) -> Option> { + self.transformers + .set + .read() + .unwrap() + .get(environment) + .cloned() + } + pub fn contains_syntax_object_macro( &self, ident: &InternedString, @@ -539,9 +546,18 @@ impl Kernel { .call_function_with_args(function, vec![syntax_objects]) .map_err(|x| x.set_span(span))?; + // dbg!(&result); + // This shouldn't be lowering all the way. It should just be back to list right? TryFromSteelValVisitorForExprKind::root(&result) - // TODO: We don't want this forever, but for now its okay - .and_then(|x| RewriteSpan::new(span).visit(x)) + + // let span = result.as_ref().map(get_span); + + // dbg!(&span); + + // result + + // TODO: We don't want this forever, but for now its okay + // .and_then(|x| RewriteSpan::new(span).visit(x)) } } diff --git a/crates/steel-core/src/parser/replace_idents.rs b/crates/steel-core/src/parser/replace_idents.rs index 5f15dfa74..559b0231d 100644 --- a/crates/steel-core/src/parser/replace_idents.rs +++ b/crates/steel-core/src/parser/replace_idents.rs @@ -141,6 +141,10 @@ impl<'a> ReplaceExpressions<'a> { fn expand_ellipses(&mut self, vec_exprs: Vec) -> Result> { if let Some(ellipses_pos) = vec_exprs.iter().position(check_ellipses) { + if ellipses_pos == 0 { + return Ok(vec_exprs); + } + let variable_to_lookup = vec_exprs.get(ellipses_pos - 1).ok_or_else( throw!(BadSyntax => "macro expansion failed, could not find variable when expanding ellipses") )?; @@ -153,14 +157,27 @@ impl<'a> ReplaceExpressions<'a> { .. }, }) => { - let rest = self.bindings.get(var).ok_or_else(throw!(BadSyntax => format!("macro expansion failed at finding the variable when expanding ellipses: {var}")))?; + // let rest = self.bindings.get(var).ok_or_else(throw!(BadSyntax => format!("macro expansion failed at finding the variable when expanding ellipses: {var}")))?; + + let rest = if let Some(rest) = self.bindings.get(var) { + rest + } else { + return Ok(vec_exprs); + }; let list_of_exprs = if let ExprKind::List(list_of_exprs) = rest { list_of_exprs } else { - let res = self.fallback_bindings.get(var).ok_or_else(throw!(BadSyntax => format!("macro expansion failed at finding the variable when expanding ellipses: {var}")))?.list_or_else( - throw!(BadSyntax => "macro expansion failed, expected list of expressions, found: {}, within {}", rest, super::ast::List::new(vec_exprs.clone())) - )?; + let res = if let Some(res) = self.fallback_bindings.get(var) { + res.list_or_else( + throw!(BadSyntax => "macro expansion failed, expected list of expressions, found: {}, within {}", rest, super::ast::List::new(vec_exprs.clone())))? + } else { + return Ok(vec_exprs); + }; + + // let res = self.fallback_bindings.get(var).ok_or_else(throw!(BadSyntax => format!("macro expansion failed at finding the variable when expanding ellipses: {var}")))?.list_or_else( + // throw!(BadSyntax => "macro expansion failed, expected list of expressions, found: {}, within {}", rest, super::ast::List::new(vec_exprs.clone())) + // )?; res }; diff --git a/crates/steel-core/src/primitives.rs b/crates/steel-core/src/primitives.rs index bfea42fd4..3b76b6c55 100644 --- a/crates/steel-core/src/primitives.rs +++ b/crates/steel-core/src/primitives.rs @@ -28,6 +28,7 @@ pub use lists::UnRecoverableResult; use crate::values::closed::HeapRef; use crate::values::lists::List; +use crate::values::structs::UserDefinedStruct; pub use control::ControlOperations; pub use fs::fs_module; pub use io::IoFunctions; @@ -417,6 +418,22 @@ impl<'a, L: PrimitiveAsRef<'a>, R: PrimitiveAsRef<'a>> PrimitiveAsRef<'a> for Ei } } +impl<'a> PrimitiveAsRef<'a> for &'a UserDefinedStruct { + fn primitive_as_ref(val: &'a SteelVal) -> crate::rvals::Result { + Self::maybe_primitive_as_ref(val).ok_or_else( + crate::throw!(ConversionError => format!("Cannot convert value to struct: {}", val)), + ) + } + + fn maybe_primitive_as_ref(val: &'a SteelVal) -> Option { + if let SteelVal::CustomStruct(s) = val { + Some(s) + } else { + None + } + } +} + impl<'a> PrimitiveAsRef<'a> for &'a Gc> { #[inline(always)] fn primitive_as_ref(val: &'a SteelVal) -> crate::rvals::Result { diff --git a/crates/steel-core/src/rvals.rs b/crates/steel-core/src/rvals.rs index c0e18c64f..f1089282e 100644 --- a/crates/steel-core/src/rvals.rs +++ b/crates/steel-core/src/rvals.rs @@ -546,6 +546,7 @@ impl AsRefMutSteelVal for T { impl ast::TryFromSteelValVisitorForExprKind { pub fn visit_syntax_object(&mut self, value: &Syntax) -> Result { let span = value.span; + // dbg!(&span); // let source = self.source.clone(); match &value.syntax { // Mutual recursion case diff --git a/crates/steel-core/src/scheme/kernel.scm b/crates/steel-core/src/scheme/kernel.scm index 58e812edc..3cc4938b8 100644 --- a/crates/steel-core/src/scheme/kernel.scm +++ b/crates/steel-core/src/scheme/kernel.scm @@ -109,7 +109,10 @@ ; (displayln raw) (if (empty? raw) raw (map syntax->datum raw)))) - (struct-impl struct-name fields options)) + (define result (struct-impl struct-name fields options)) + + (syntax/loc result + (syntax-span expr))) ;; Macro for creating a new struct, in the form of: ;; `(struct (fields ...) options ...)` diff --git a/crates/steel-core/src/scheme/modules/match.scm b/crates/steel-core/src/scheme/modules/match.scm new file mode 100644 index 000000000..3dc2ba5bf --- /dev/null +++ b/crates/steel-core/src/scheme/modules/match.scm @@ -0,0 +1,333 @@ +;; These _don't_ need to be provided for syntax. +;; However in the mean time, this will work. +(provide match + match-define) + +;; ------------------- match functions ---------------------- + +;; +(define (collect-until-last-p input collected) + (if (null? (cdr input)) + (list (car input) collected) + (collect-until-last-p (cdr input) (cons (car input) collected)))) + +(define (collect-until-last input) + (collect-until-last-p input '())) + +(define (quoted? x) + (and (list? x) (not (null? x)) (equal? (car x) 'quote))) + +(begin-for-syntax + + (define *gensym-counter* 0) + (define (gensym) + (set! *gensym-counter* (+ 1 *gensym-counter*)) + (string->symbol (string-append "##gensym" (to-string *gensym-counter*)))) + + (define (gensym-ident identifier) + (concat-symbols (gensym) 'match identifier)) + + (define (compile-cons-to-list pattern depth) + (cond + [(and (list? pattern) + (not (null? pattern)) + (= (length pattern) 3) + (equal? (car pattern) 'cons) + (list? (caddr pattern)) + (equal? (caaddr pattern) 'append)) + + (define first (cadr pattern)) + (define first-depth (if (and (list? first) (not (quoted? first))) 0 (+ 1 depth))) + + (define rest (cadr (caddr pattern))) + + (if (= depth 0) + (cons 'list + (cons (compile-cons-to-list (cadr pattern) first-depth) + (cons (compile-cons-to-list rest (+ 1 depth)) '(...)))) + + (cons (compile-cons-to-list (cadr pattern) first-depth) + (cons (compile-cons-to-list rest depth) '(...))))] + + [(and (list? pattern) + (not (null? pattern)) + (= (length pattern) 3) + (equal? (car pattern) 'cons) + (equal? (caddr pattern) '(quote ()))) + + (define first (cadr pattern)) + (define first-depth (if (and (list? first) (not (quoted? first))) 0 (+ 1 depth))) + + (if (= depth 0) + (cons 'list (cons (compile-cons-to-list (cadr pattern) first-depth) '())) + (cons (compile-cons-to-list (cadr pattern) first-depth) '()))] + + [(and (list? pattern) + (not (null? pattern)) + (= (length pattern) 3) + (equal? (car pattern) 'cons) + (list? (caddr pattern)) + (equal? (caaddr pattern) 'cons)) + + (define first (cadr pattern)) + (define rest (caddr pattern)) + + (define first-depth (if (and (list? first) (not (quoted? first))) 0 (+ 1 depth))) + (define rest-depth (if (and (list? rest) (not (quoted? rest))) 0 (+ 1 depth))) + + ; (displayln "GETTING HERE with" first " and " rest " " first-depth " " rest-depth " " depth) + + (if (= depth 0) + (cons 'list + (cons (compile-cons-to-list (cadr pattern) first-depth) + (compile-cons-to-list (caddr pattern) (+ 1 depth)))) + + (cons (compile-cons-to-list (cadr pattern) first-depth) + (compile-cons-to-list (caddr pattern) depth)))] + + [else pattern])) + + (define var? symbol?) + + (define (ignore? x) + (equal? x '_)) + + (define (quoted? x) + (and (list? x) (not (null? x)) (equal? (car x) 'quote))) + + (define (many? x) + (and + (symbol? x) + (let ([str (symbol->string x)]) + ; (and (starts-with? str "?") + (ends-with? + str + "...") ;; TODO: Convert this to a separate symbol, something like "a ..." rather than "a..." + ; ) + ))) + + (define (starts-with-many? pat) + (and (>= (length pat) 2) (equal? (cadr pat) '...))) + + (define (equal-or-insert hm key value) + (define existing-value (hash-try-get hm key)) + (if existing-value (if (equal? existing-value value) hm #f) (hash-insert hm key value))) + + (define (number->symbol n) + (~> n number->string string->symbol)) + + ;; TODO: Insert a check to remove the `list` part from the pattern if the cdr-depth is 0? + (define (match-p-syntax pattern + input + final-body-expr + depth + bound-vars + check-var? + cdr-depth + introduced-identifiers) + + (cond + [(quoted? pattern) `(and (equal? ,pattern ,input) ,final-body-expr)] + [(and (list? pattern) (not (null? pattern)) (= cdr-depth 0)) + (cond + [(equal? (car pattern) 'list) + `(if (list? ,input) + ;; Recur after dropping the list + ,(match-p-syntax (cdr pattern) + input + final-body-expr + depth + bound-vars + check-var? + (+ 1 cdr-depth) + introduced-identifiers) + #f)] + + [else (error "list pattern must start with `list - found " (car pattern))])] + + [(and (list? pattern) (not (null? pattern)) (starts-with-many? pattern)) + (if (null? (cddr pattern)) + (begin + (vector-push! introduced-identifiers (car pattern)) + + `(let ([,(car pattern) ,input]) ,final-body-expr)) + (begin + + (vector-push! introduced-identifiers (car (cdr pattern))) + (vector-push! introduced-identifiers (car pattern)) + + `(let ([collected (collect-until-last ,input)]) + ,(if (null? (cdddr pattern)) + `(let ([,(car (cdr pattern)) (car collected)] + [,(car pattern) (reverse (car (cdr collected)))]) + + ,final-body-expr) + + #f))))] + + ;; If the pattern is to be ignored, just return the body - the automatically match + [(ignore? pattern) final-body-expr] + + ;; If this is a free variable, bind against it. + ;; Note: We currently don't have the ability to check if this is a free variable + ;; within the context of the macro expansion + [(var? pattern) + + (if check-var? + `(if (equal? ,pattern ,input) ,final-body-expr #f) + (begin + ;; Keep track of the introduced identifiers + (vector-push! introduced-identifiers pattern) + + `(let ([,pattern ,input]) ,final-body-expr)))] + + ;; If the pattern is the same, just return whether they match + [(atom? pattern) `(and (equal? ,pattern ,input) ,final-body-expr)] + + ;; If there is no pattern, just return whether the pattern and input match + [(null? pattern) `(and (null? ,input) ,final-body-expr)] + + ;; TODO: Not sure how we can even get here? + ; (displayln "getting here!") + [(null? input) #f] + + [else + + (define cdr-input-depth (gensym-ident (concat-symbols 'cdr-input (number->symbol depth)))) + (define car-input-depth + (gensym-ident (concat-symbols 'car-input (number->symbol (+ 1 depth))))) + + ;; If the pattern is an atom, then we're going to bind the pattern here! + (define car-pattern-is-atom? (atom? (car pattern))) + (define should-check-var? + (and car-pattern-is-atom? (hashset-contains? bound-vars (car pattern)))) + + (define remaining + (match-p-syntax + (cdr pattern) + cdr-input-depth + final-body-expr + depth + (if car-pattern-is-atom? (hashset-insert bound-vars (car pattern)) bound-vars) + should-check-var? + ;; Increment the cdr depth since we're traversing across the list + (+ 1 cdr-depth) + introduced-identifiers)) + + (if remaining + + `(if (not (null? ,input)) + ;; Save our spot in the recursion so we don't have to recompute a bunch + ;; of stuff + (let ([,cdr-input-depth (cdr ,input)] [,car-input-depth (car ,input)]) + ,(match-p-syntax + (car pattern) + car-input-depth + remaining + (+ 1 depth) + (if car-pattern-is-atom? (hashset-insert bound-vars (car pattern)) bound-vars) + should-check-var? + 0 + introduced-identifiers)) + #f) + + #f)])) + + (define (go-match pattern input final-body-expr introduced-identifiers) + (define compile-pattern (compile-cons-to-list pattern 0)) + (match-p-syntax compile-pattern input final-body-expr 0 (hashset) #f 0 introduced-identifiers))) + +(defmacro (single-match-define expression) + (define unwrapped (syntax-e expression)) + (define variable (syntax->datum (second unwrapped))) + (define pattern (syntax->datum (third unwrapped))) + (define body (list-ref unwrapped 3)) + (define introduced-identifiers (mutable-vector)) + (define res (go-match pattern variable body introduced-identifiers)) + ;; I _think_ this drains the values from the vector into the list? + (define list-identifiers (reverse (mutable-vector->list introduced-identifiers))) + (define temp (gensym)) + (define final-expr + `(define-values (,@list-identifiers) + (let ([,temp ,(go-match pattern variable `(list ,@list-identifiers) (mutable-vector))]) + (if (not (equal? #f)) + ,temp + (error-with-span (quote ,(syntax-span (third unwrapped))) + "Unable to match the given expression: " + ,variable + "to any of the patterns"))))) + ; (displayln (syntax-span expression)) + ; (displayln final-expr) + (syntax/loc final-expr + (syntax-span expression))) + +;; Match a single pattern +(defmacro (single-match expression) + (define unwrapped (syntax-e expression)) + ;; Unwrapping entirely, not what we want! We want to + ;; wrap it back up with the span of the original definition! + (define variable (syntax->datum (second unwrapped))) + (define pattern (syntax->datum (third unwrapped))) + (define body (list-ref unwrapped 3)) + ;; Keep track of all of the identifiers that this + ;; expression introduces + ;; TODO: Keep one top level around and clear each time. Then + ;; we won't keep around any garbage + (define introduced-identifiers (mutable-vector)) + (define res (go-match pattern variable body introduced-identifiers)) + (syntax/loc res + (syntax-span expression))) + +;; ----------------- match! syntax -------------------- + +;; TODO add case for the remaining - when there is no else case given +;; and it should just error out +(define-syntax match-dispatch + (syntax-rules (else) + ;; Explicitly giving an else case + [(match-dispatch expr [else e0 e1 ...]) + (begin + e0 + e1 ...)] + ;; Generic recursive case + [(match-dispatch expr [p1 e2 ...] c0 c1 ...) + (let ([match? (single-match expr + p1 + (begin + e2 ...))]) + (if (not (equal? #f match?)) + match? + + (match-dispatch expr c0 c1 ...)))] + ;; When there isn't an else case given, the last case + ;; Should include a failure mode + [(match-dispatch expr (p1 e2 ...)) + (let ([match? (single-match expr + p1 + (begin + e2 ...))]) + (if (not (equal? #f match?)) + match? + (error! "Unable to match expression: " expr " to any of the given patterns")))])) + +(define-syntax match-define + (syntax-rules () + [(match-define pattern expr) + (let ([evald-expr expr]) (single-match-define evald-expr pattern 'empty-body))])) + +(define-syntax match + (syntax-rules () + [(match expr + pat) + (let ([evald-expr expr]) (match-dispatch evald-expr pat))] + [(match expr + pat + pats ...) + (let ([evald-expr expr]) (match-dispatch evald-expr pat pats ...))])) + +; (match (list 10 20 30 40 50) +; [(?x 20 ?y 40 ?z) (+ ?x ?y ?z)]) + +;; --------------------- match! tests ------------------------ + +; (displayln "--------------------- match! tests ----------------------") diff --git a/crates/steel-core/src/steel_vm/engine.rs b/crates/steel-core/src/steel_vm/engine.rs index 5bcd75001..d287cff9d 100644 --- a/crates/steel-core/src/steel_vm/engine.rs +++ b/crates/steel-core/src/steel_vm/engine.rs @@ -1445,6 +1445,7 @@ impl Engine { pub fn run_raw_program(&mut self, program: RawProgramWithSymbols) -> Result> { let executable = self.raw_program_to_executable(program)?; + self.virtual_machine.run_executable(&executable) } diff --git a/crates/steel-core/src/steel_vm/primitives.rs b/crates/steel-core/src/steel_vm/primitives.rs index b9ad7648a..0f21325b5 100644 --- a/crates/steel-core/src/steel_vm/primitives.rs +++ b/crates/steel-core/src/steel_vm/primitives.rs @@ -1450,6 +1450,15 @@ pub fn black_box(_: &[SteelVal]) -> Result { Ok(SteelVal::Void) } +#[steel_derive::function(name = "struct->list")] +pub fn struct_to_list(value: &UserDefinedStruct) -> Result { + if value.is_transparent() { + Ok(SteelVal::ListV(value.fields.clone().into())) + } else { + Ok(SteelVal::BoolV(false)) + } +} + fn meta_module() -> BuiltInModule { let mut module = BuiltInModule::new("steel/meta"); module @@ -1478,9 +1487,7 @@ fn meta_module() -> BuiltInModule { "#%struct-property-ref", |value: &UserDefinedStruct, key: SteelVal| UserDefinedStruct::get(value, &key), ) - // .register_value("struct-ref", struct_ref()) - // .register_value("struct->list", struct_to_list()) - // .register_value("struct->vector", struct_to_vector()) + .register_native_fn_definition(STRUCT_TO_LIST_DEFINITION) .register_value("expand!", SteelVal::FuncV(super::meta::expand_macros)) .register_value("read!", SteelVal::FuncV(super::meta::read)) .register_value( @@ -1576,6 +1583,7 @@ fn syntax_module() -> BuiltInModule { .register_fn("syntax->datum", crate::rvals::Syntax::syntax_datum) .register_fn("syntax-loc", crate::rvals::Syntax::syntax_loc) .register_fn("syntax/loc", crate::rvals::Syntax::new) + .register_fn("syntax-span", crate::rvals::Syntax::syntax_loc) .register_fn("#%syntax/raw", crate::rvals::Syntax::proto) .register_fn("syntax-e", crate::rvals::Syntax::syntax_e) .register_value("syntax?", gen_pred!(SyntaxObject)); diff --git a/crates/steel-core/src/steel_vm/vm.rs b/crates/steel-core/src/steel_vm/vm.rs index 77936fc21..1d0f395ca 100644 --- a/crates/steel-core/src/steel_vm/vm.rs +++ b/crates/steel-core/src/steel_vm/vm.rs @@ -398,7 +398,6 @@ impl SteelThread { instructions, constant_map, spans, - // struct_functions, .. } = program; @@ -413,11 +412,6 @@ impl SteelThread { self.constant_map = DEFAULT_CONSTANT_MAP.with(|x| x.clone()); result - - // TODO - // self.global_env.print_diagnostics(); - - // todo!("Initialize structs and build the program"); } pub(crate) fn call_function( @@ -1449,12 +1443,8 @@ impl<'a> VmCore<'a> { // println!("Multi arity: {:?}", closure.is_multi_arity); let prev_length = self.thread.stack.len(); - // self.stack_index.push(prev_length); - - // if self.stack_frames let instructions = closure.body_exp(); - // let spans = closure.spans(); // TODO: self.thread.stack_frames.push(StackFrame::new( @@ -1462,7 +1452,6 @@ impl<'a> VmCore<'a> { Gc::clone(closure), 0, instructions.clone(), - // spans.clone(), )); self.sp = prev_length; @@ -1475,10 +1464,12 @@ impl<'a> VmCore<'a> { self.adjust_stack_for_multi_arity(closure, argument_count, &mut 0)?; - // self.function_stack - // .push(CallContext::new(Gc::clone(closure))); + let res = self.call_with_instructions_and_reset_state(instructions); - self.call_with_instructions_and_reset_state(instructions) + // Clean up the stack now + self.thread.stack.truncate(prev_length); + + res } // Calling convention diff --git a/crates/steel-core/src/tests/success/matcher.scm b/crates/steel-core/src/tests/success/matcher.scm index 46d87805f..a3b32c188 100644 --- a/crates/steel-core/src/tests/success/matcher.scm +++ b/crates/steel-core/src/tests/success/matcher.scm @@ -49,8 +49,7 @@ (define remaining (match-p (cdr pattern) (cdr input) bindings)) (if remaining (match-p (car pattern) (car input) remaining) #f)])) -(define (match pattern - input) +(define (match-b pattern input) (match-p pattern input (hash))) ;; ---------------- tests -------------------- @@ -80,148 +79,86 @@ (displayln "--------------------- match tests ----------------------") ;; Matches a pattern explicitly -(test "Simple match" - (match '?x - '(1 2 3 4)) - (hash '?x '(1 2 3 4))) +(test "Simple match" (match-b '?x '(1 2 3 4)) (hash '?x '(1 2 3 4))) ;; If the pattern match fails, return false -(test "Pattern match fails returns false" - (match '(10 2 ?z 5) - '(1 2 3 4)) - #f) +(test "Pattern match fails returns false" (match-b '(10 2 ?z 5) '(1 2 3 4)) #f) ;; If the pattern fails because we didn't match exactly, bail -(test "Pattern fails because constants don't match exactly" - (match '(1 2 3 4 5) - '(1 2 3 4)) - #f) +(test "Pattern fails because constants don't match exactly" (match-b '(1 2 3 4 5) '(1 2 3 4)) #f) ;; Should fail -(test "Lengths unequal fails" - (match '(?x ?y ?z 4 5) - '(1 2 3 4)) - #f) +(test "Lengths unequal fails" (match-b '(?x ?y ?z 4 5) '(1 2 3 4)) #f) ;; Should succeed with x y z bound to 1 2 3 (test "Successful pattern match on simple list" - (match '(?x ?y ?z 4 5) - '(1 2 3 4 5)) + (match-b '(?x ?y ?z 4 5) '(1 2 3 4 5)) (hash '?x 1 '?y 2 '?z 3)) ;; Should succed with x y z bound to 1 2 3 -(test "Nested patterns match" - (match '(?x (?y ?z)) - '(1 (2 3))) - (hash '?x 1 '?y 2 '?z 3)) +(test "Nested patterns match" (match-b '(?x (?y ?z)) '(1 (2 3))) (hash '?x 1 '?y 2 '?z 3)) ;; Also should work (test "Deep nested pattern" - (match '(?x (?y (?z (?applesauce ?bananas)))) - '(1 (2 (3 (4 5))))) + (match-b '(?x (?y (?z (?applesauce ?bananas)))) '(1 (2 (3 (4 5))))) (hash '?x 1 '?y 2 '?z 3 '?applesauce 4 '?bananas 5)) ;; Also should work (test "Deep nested pattern with list matching" - (match '(?x (?y (?z (?applesauce ?bananas)))) - '(1 (2 (3 (4 (1 2 3 4 5)))))) + (match-b '(?x (?y (?z (?applesauce ?bananas)))) '(1 (2 (3 (4 (1 2 3 4 5)))))) (hash '?x 1 '?y 2 '?z 3 '?applesauce 4 '?bananas '(1 2 3 4 5))) ;; Match the bindings (test "Pattern variables once bound retain their value" - (match '(?x ?y ?x) - '(1 2 1)) + (match-b '(?x ?y ?x) '(1 2 1)) (hash '?x 1 '?y 2)) ;; Should fail since x doesn't match what was there at first -(test "Matching fails when variable has two different values" - (match '(?x ?y ?x) - '(1 2 3)) - #f) +(test "Matching fails when variable has two different values" (match-b '(?x ?y ?x) '(1 2 3)) #f) ;; Shouldn't fail, should ignore whatever is in the second position (test "Wildcard ignores the matching at that position" - (match '(?x _ 3) - '(1 (1 2 3) 3)) + (match-b '(?x _ 3) '(1 (1 2 3) 3)) (hash '?x 1)) ;; a => 1 ;; x => '(2 3 4 5) (test "Basic ellipses matching works" - (match '(?a ?x...) - '(1 2 3 4 5)) + (match-b '(?a ?x...) '(1 2 3 4 5)) (hash '?a 1 '?x... '(2 3 4 5))) (test "Ellipses matches to empty list" - (match '(?first ?rest...) - '(1)) + (match-b '(?first ?rest...) '(1)) (hash '?first 1 '?rest... '())) (test "Ellipses matches until next value" - (match '(?first ?rest... ?last) - '(1 2 3 4 5)) + (match-b '(?first ?rest... ?last) '(1 2 3 4 5)) (hash '?first 1 '?rest... '(2 3 4) '?last 5)) ; TODO this should error out as illegal pattern (test "Ellipses does not match multiple characters at the end" - (match '(?first ?rest... ?second-last ?last) - '(1 2 3 4 5 6)) + (match-b '(?first ?rest... ?second-last ?last) '(1 2 3 4 5 6)) #f ; (hash '?first 1 '?rest... '(2 3 4) '?last 5 '?last 6) ) (test "Ellipses with nested pattern" - (match '(?x (?y ?z (?foo ?bar...))) - '(1 (2 3 (4 5 6 7 8 9 10)))) + (match-b '(?x (?y ?z (?foo ?bar...))) '(1 (2 3 (4 5 6 7 8 9 10)))) (hash '?x 1 '?y 2 '?z 3 '?foo 4 '?bar... '(5 6 7 8 9 10))) -(test "Empty pattern matches empty list" - (match '() - '()) - (hash)) - -(test "Empty pattern fails on non empty list" - (match '() - '(1 2 3)) - #f) - -(test "Single variable with empty list" - (match '?x - '()) - (hash '?x '())) - -(test "Constant matches constant" - (match (list 1 2 3) - [list - 1 - 2 - 3]) - (hash)) - -(test "List pattern does not match constant" - (match (list 1 2 3 4 5) - 10) - #f) - -(test "Constant pattern does not match list" - (match 10 - [list - 1 - 2 - 3 - 4 - 5]) - #f) - -(test "Wildcard passes" - (match '_ - [list - 1 - 2 - 3 - 4 - 5]) - (hash)) +(test "Empty pattern matches empty list" (match-b '() '()) (hash)) + +(test "Empty pattern fails on non empty list" (match-b '() '(1 2 3)) #f) + +(test "Single variable with empty list" (match-b '?x '()) (hash '?x '())) + +(test "Constant matches constant" (match-b (list 1 2 3) [list 1 2 3]) (hash)) + +(test "List pattern does not match constant" (match-b (list 1 2 3 4 5) 10) #f) + +(test "Constant pattern does not match list" (match-b 10 [list 1 2 3 4 5]) #f) + +(test "Wildcard passes" (match-b '_ [list 1 2 3 4 5]) (hash)) ;; ----------------- match! syntax -------------------- @@ -259,8 +196,7 @@ e1 ...)] ;; Generic recursive case [(match-dispatch expr [p1 e2 ...] c0 c1 ...) - (let ([match? (match (syntax->pattern p1) - expr)]) + (let ([match? (match-b (syntax->pattern p1) expr)]) (if match? (syntax-pattern->lets p1 match? @@ -270,8 +206,7 @@ ;; When there isn't an else case given, the last case ;; Should include a failure mode [(match-dispatch expr (p1 e2 ...)) - (let ([match? (match (syntax->pattern p1) - expr)]) + (let ([match? (match-b (syntax->pattern p1) expr)]) (if match? (syntax-pattern->lets p1 match? @@ -281,13 +216,8 @@ (define-syntax match! (syntax-rules () - [(match expr - pat) - (let ([evald-expr expr]) (match-dispatch evald-expr pat))] - [(match expr - pat - pats ...) - (let ([evald-expr expr]) (match-dispatch evald-expr pat pats ...))])) + [(match! expr pat) (let ([evald-expr expr]) (match-dispatch evald-expr pat))] + [(match! expr pat pats ...) (let ([evald-expr expr]) (match-dispatch evald-expr pat pats ...))])) ;; --------------------- match! tests ------------------------