diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72d5c006..f65d6a84 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: fail-fast: true matrix: racket-variant: ['BC', 'CS'] - racket-version: ['8.3', 'stable'] + racket-version: ['8.5', 'stable'] experimental: [false] include: - racket-version: 'current' diff --git a/profile/forms.rkt b/profile/forms.rkt index 5cd5a383..2b16cb43 100644 --- a/profile/forms.rkt +++ b/profile/forms.rkt @@ -10,7 +10,12 @@ utility macros `run-benchmark` or `run-summary-benchmark`, and provides it one of the helper functions `check-value` (to invoke the form with a single value each time during benchmarking) or `check-values` (to invoke the form with multiple values each time -during benchmarking). +during benchmarking). Note that at the moment, as a hack for convenience, +`run-benchmark` expects a function with the name of the form being +benchmarked _prefixed with tilde_. This is to avoid name collisions +between this function and the Qi form with the same name. Basically, +just follow one of the numerous examples in this module to see what +this is referring to. 2. Require the submodule in the `main` submodule with an appropriate prefix (see other examples) @@ -23,79 +28,79 @@ for the forms are run. (module one-of? "forms-base.rkt" (provide run) - (define (one-of? v) + (define (~one-of? v) ((☯ (one-of? 3 5 7)) v)) (define (run) - (run-benchmark one-of? + (run-benchmark ~one-of? check-value 100000))) (module and "forms-base.rkt" (provide run) - (define (and v) + (define (~and v) ((☯ (and positive? integer?)) v)) (define (run) - (run-benchmark and + (run-benchmark ~and check-value 200000))) (module or "forms-base.rkt" (provide run) - (define (or v) + (define (~or v) ((☯ (or positive? integer?)) v)) (define (run) - (run-benchmark or + (run-benchmark ~or check-value 200000))) (module not "forms-base.rkt" (provide run) - (define (not v) + (define (~not v) ((☯ (not integer?)) v)) (define (run) - (run-benchmark not + (run-benchmark ~not check-value 200000))) (module and% "forms-base.rkt" (provide run) - (define (and% a b) + (define (~and% a b) ((☯ (and% positive? integer?)) a b)) (define (run) - (run-benchmark and% + (run-benchmark ~and% check-two-values 200000))) (module or% "forms-base.rkt" (provide run) - (define (or% a b) + (define (~or% a b) ((☯ (or% positive? integer?)) a b)) (define (run) - (run-benchmark or% + (run-benchmark ~or% check-two-values 200000))) (module group "forms-base.rkt" (provide run) - (define (group . vs) + (define (~group . vs) (apply (☯ (~> (group 2 + _) (group 3 + _) @@ -104,27 +109,27 @@ for the forms are run. vs)) (define (run) - (run-benchmark group + (run-benchmark ~group check-values 200000))) (module count "forms-base.rkt" (provide run) - (define (count . vs) + (define (~count . vs) (apply (☯ count) vs)) (define (run) - (run-benchmark count + (run-benchmark ~count check-values 1000000))) (module relay "forms-base.rkt" (provide run) - (define (relay . vs) + (define (~relay . vs) (apply (☯ (== add1 sub1 @@ -139,14 +144,14 @@ for the forms are run. vs)) (define (run) - (run-benchmark relay + (run-benchmark ~relay check-values 50000))) (module relay* "forms-base.rkt" (provide run) - (define (relay* . vs) + (define (~relay* . vs) (apply (☯ (==* add1 sub1 @@ -155,40 +160,40 @@ for the forms are run. vs)) (define (run) - (run-benchmark relay* + (run-benchmark ~relay* check-values 50000))) (module amp "forms-base.rkt" (provide run) - (define (amp . vs) + (define (~amp . vs) (apply (☯ (>< sqr)) vs)) (define (run) - (run-benchmark amp + (run-benchmark ~amp check-values 300000))) (module ground "forms-base.rkt" (provide run) - (define (ground . vs) + (define (~ground . vs) (apply (☯ ⏚) vs)) (define (run) - (run-benchmark ground + (run-benchmark ~ground check-values 200000))) (module thread "forms-base.rkt" (provide run) - (define (thread . vs) + (define (~thread . vs) (apply (☯ (~> (+ 5) add1 @@ -204,14 +209,14 @@ for the forms are run. vs)) (define (run) - (run-benchmark thread + (run-benchmark ~thread check-values 200000))) (module thread-right "forms-base.rkt" (provide run) - (define (thread-right . vs) + (define (~thread-right . vs) (apply (☯ (~>> (+ 5) add1 @@ -227,251 +232,251 @@ for the forms are run. vs)) (define (run) - (run-benchmark thread-right + (run-benchmark ~thread-right check-values 200000))) (module crossover "forms-base.rkt" (provide run) - (define (crossover . vs) + (define (~crossover . vs) (apply (☯ X) vs)) (define (run) - (run-benchmark crossover + (run-benchmark ~crossover check-values 200000))) (module all "forms-base.rkt" (provide run) - (define (all . vs) + (define (~all . vs) (apply (☯ (all positive?)) vs)) (define (run) - (run-benchmark all + (run-benchmark ~all check-values 200000))) (module any "forms-base.rkt" (provide run) - (define (any . vs) + (define (~any . vs) (apply (☯ (any positive?)) vs)) (define (run) - (run-benchmark any + (run-benchmark ~any check-values 200000))) (module none "forms-base.rkt" (provide run) - (define (none . vs) + (define (~none . vs) (apply (☯ (none positive?)) vs)) (define (run) - (run-benchmark none + (run-benchmark ~none check-values 200000))) (module all? "forms-base.rkt" (provide run) - (define (all? . vs) + (define (~all? . vs) (apply (☯ all?) vs)) (define (run) - (run-benchmark all? + (run-benchmark ~all? check-values 200000))) (module any? "forms-base.rkt" (provide run) - (define (any? . vs) + (define (~any? . vs) (apply (☯ any?) vs)) (define (run) - (run-benchmark any? + (run-benchmark ~any? check-values 200000))) (module none? "forms-base.rkt" (provide run) - (define (none? . vs) + (define (~none? . vs) (apply (☯ none?) vs)) (define (run) - (run-benchmark none? + (run-benchmark ~none? check-values 200000))) (module collect "forms-base.rkt" (provide run) - (define (collect . vs) + (define (~collect . vs) (apply (☯ ▽) vs)) (define (run) - (run-benchmark collect + (run-benchmark ~collect check-values 1000000))) (module sep "forms-base.rkt" (provide run) - (define (sep v) + (define (~sep v) ((☯ △) v)) (define (run) - (run-benchmark sep + (run-benchmark ~sep check-list 1000000))) (module gen "forms-base.rkt" (provide run) - (define (gen . vs) + (define (~gen . vs) (apply (☯ (gen 1 2 3)) vs)) (define (run) - (run-benchmark gen + (run-benchmark ~gen check-values 1000000))) (module esc "forms-base.rkt" (provide run) - (define (esc . vs) + (define (~esc . vs) (apply (☯ (esc (λ args args))) vs)) (define (run) - (run-benchmark esc + (run-benchmark ~esc check-values 1000000))) (module AND "forms-base.rkt" (provide run) - (define (AND . vs) + (define (~AND . vs) (apply (☯ AND) vs)) (define (run) - (run-benchmark AND + (run-benchmark ~AND check-values 200000))) (module OR "forms-base.rkt" (provide run) - (define (OR . vs) + (define (~OR . vs) (apply (☯ OR) vs)) (define (run) - (run-benchmark OR + (run-benchmark ~OR check-values 200000))) (module NOT "forms-base.rkt" (provide run) - (define (NOT v) + (define (~NOT v) ((☯ NOT) v)) (define (run) - (run-benchmark NOT + (run-benchmark ~NOT check-value 200000))) (module NAND "forms-base.rkt" (provide run) - (define (NAND . vs) + (define (~NAND . vs) (apply (☯ NAND) vs)) (define (run) - (run-benchmark NAND + (run-benchmark ~NAND check-values 200000))) (module NOR "forms-base.rkt" (provide run) - (define (NOR . vs) + (define (~NOR . vs) (apply (☯ NOR) vs)) (define (run) - (run-benchmark NOR + (run-benchmark ~NOR check-values 200000))) (module XOR "forms-base.rkt" (provide run) - (define (XOR . vs) + (define (~XOR . vs) (apply (☯ XOR) vs)) (define (run) - (run-benchmark XOR + (run-benchmark ~XOR check-values 200000))) (module XNOR "forms-base.rkt" (provide run) - (define (XNOR . vs) + (define (~XNOR . vs) (apply (☯ XNOR) vs)) (define (run) - (run-benchmark XNOR + (run-benchmark ~XNOR check-values 200000))) (module tee "forms-base.rkt" (provide run) - (define (tee v) + (define (~tee v) ((☯ (-< add1 sub1 sqr)) v)) (define (run) - (run-benchmark tee + (run-benchmark ~tee check-value 200000))) @@ -534,36 +539,36 @@ for the forms are run. (module if "forms-base.rkt" (provide run) - (define (if . vs) + (define (~if . vs) (apply (☯ (if < 'hi 'bye)) vs)) (define (run) - (run-benchmark if + (run-benchmark ~if check-values 500000))) (module when "forms-base.rkt" (provide run) - (define (when . vs) + (define (~when . vs) (apply (☯ (when < 'hi)) vs)) (define (run) - (run-benchmark when + (run-benchmark ~when check-values 500000))) (module unless "forms-base.rkt" (provide run) - (define (unless . vs) + (define (~unless . vs) (apply (☯ (unless < 'hi)) vs)) (define (run) - (run-benchmark unless + (run-benchmark ~unless check-values 500000))) @@ -598,34 +603,34 @@ for the forms are run. (module sieve "forms-base.rkt" (provide run) - (define (sieve . vs) + (define (~sieve . vs) (apply (☯ (sieve positive? 'hi 'bye)) vs)) (define (run) - (run-benchmark sieve + (run-benchmark ~sieve check-values 100000))) (module partition "forms-base.rkt" (provide run) - (define (partition . vs) + (define (~partition . vs) (apply (flow (partition [negative? *] [zero? count] [positive? +])) vs)) (define (run) - (run-benchmark partition check-values 100000))) + (run-benchmark ~partition check-values 100000))) (module gate "forms-base.rkt" (provide run) - (define (gate . vs) + (define (~gate . vs) (apply (☯ (gate <)) vs)) (define (run) - (run-benchmark gate + (run-benchmark ~gate check-values 500000))) @@ -681,12 +686,12 @@ for the forms are run. (module inverter "forms-base.rkt" (provide run) - (define (inverter . vs) + (define (~inverter . vs) (apply (☯ inverter) vs)) (define (run) - (run-benchmark inverter + (run-benchmark ~inverter check-values 200000))) @@ -722,127 +727,127 @@ for the forms are run. (module select "forms-base.rkt" (provide run) - (define (select . vs) + (define (~select . vs) (apply (☯ (select 3 5 8)) vs)) (define (run) - (run-benchmark select + (run-benchmark ~select check-values 20000))) (module block "forms-base.rkt" (provide run) - (define (block . vs) + (define (~block . vs) (apply (☯ (block 3 5 8)) vs)) (define (run) - (run-benchmark block + (run-benchmark ~block check-values 20000))) (module bundle "forms-base.rkt" (provide run) - (define (bundle . vs) + (define (~bundle . vs) (apply (☯ (bundle (3 5 8) + -)) vs)) (define (run) - (run-benchmark bundle + (run-benchmark ~bundle check-values 20000))) (module effect "forms-base.rkt" (provide run) - (define (effect . vs) + (define (~effect . vs) (apply (☯ (effect + +)) vs)) (define (run) - (run-benchmark effect + (run-benchmark ~effect check-values 200000))) (module live? "forms-base.rkt" (provide run) - (define (live? . vs) + (define (~live? . vs) (apply (☯ live?) vs)) (define (run) - (run-benchmark live? + (run-benchmark ~live? check-values 500000))) (module rectify "forms-base.rkt" (provide run) - (define (rectify . vs) + (define (~rectify . vs) (apply (☯ (rectify #f)) vs)) (define (run) - (run-benchmark rectify + (run-benchmark ~rectify check-values 500000))) (module pass "forms-base.rkt" (provide run) - (define (pass . vs) + (define (~pass . vs) (apply (☯ (pass odd?)) vs)) (define (run) - (run-benchmark pass + (run-benchmark ~pass check-values 200000))) (module foldl "forms-base.rkt" (provide run) - (define (>> . vs) + (define (~foldl . vs) (apply (☯ (>> +)) vs)) (define (run) - (run-benchmark >> + (run-benchmark ~foldl check-values 200000))) (module foldr "forms-base.rkt" (provide run) - (define (<< . vs) + (define (~foldr . vs) (apply (☯ (<< +)) vs)) (define (run) - (run-benchmark << + (run-benchmark ~foldr check-values 200000))) (module loop "forms-base.rkt" (provide run) - (define (loop . vs) + (define (~loop . vs) (apply (☯ (loop live? sqr)) vs)) (define (run) - (run-benchmark loop + (run-benchmark ~loop check-values 100000))) (module loop2 "forms-base.rkt" (provide run) - (define (loop2 . vs) + (define (~loop2 . vs) ((☯ (~> (loop2 (~> 1> (not null?)) sqr +))) @@ -850,7 +855,7 @@ for the forms are run. 0)) (define (run) - (run-benchmark loop2 + (run-benchmark ~loop2 check-values 100000))) @@ -860,12 +865,12 @@ for the forms are run. (require (only-in racket/base [apply b:apply])) - (define (apply . vs) + (define (~apply . vs) (b:apply (☯ apply) (cons + vs))) (define (run) - (run-benchmark apply + (run-benchmark ~apply check-values 300000))) @@ -874,13 +879,13 @@ for the forms are run. ;; TODO: this uses a lot of other things besides `clos` and is ;; likely not a reliable indicator - (define (clos . vs) + (define (~clos . vs) (apply (☯ (~> (-< (~> 5 (clos *)) _) apply)) vs)) (define (run) - (run-benchmark clos + (run-benchmark ~clos check-values 100000))) diff --git a/profile/report.rkt b/profile/report.rkt index 82ed4b2d..decf6e1a 100644 --- a/profile/report.rkt +++ b/profile/report.rkt @@ -145,7 +145,7 @@ "clos" clos:run)) (program (main) - ;; Note: could use try-order? with hash-keys if support is dropped for Racket 8.3 + ;; TODO: could use try-order? with hash-keys if support is dropped for Racket 8.3 (define fs (~>> (env) hash-keys (sort <))) (define forms-data (for/list ([f (in-list fs)]) (match-let ([(list name ms) ((hash-ref env f))]) diff --git a/profile/util.rkt b/profile/util.rkt index 64720c82..17fd1c80 100644 --- a/profile/util.rkt +++ b/profile/util.rkt @@ -24,7 +24,9 @@ racket/function racket/format syntax/parse/define - (for-syntax racket/base) + (for-syntax racket/base + (only-in racket/string + string-trim)) qi) (define-flow average @@ -86,8 +88,18 @@ ;; and report the time taken. (define-syntax-parse-rule (run-benchmark f-name runner n-times) #:with name (datum->syntax #'f-name - (symbol->string - (syntax->datum #'f-name))) + ;; this is because of the name collision between + ;; Racket functions and Qi forms, now that the latter + ;; are provided as identifiers in the qi binding space. + ;; Using a standard prefix (i.e. ~) in the naming and then + ;; detecting that, trimming it, here, is pretty hacky. + ;; One alternative could be to broaden the run-benchmark + ;; macro to support a name argument, but that seems like + ;; more work. It would be better to be able to introspect + ;; these somehow. + (string-trim (symbol->string + (syntax->datum #'f-name)) + "~")) (let ([ms (measure runner f-name n-times)]) (list name ms))) diff --git a/qi-doc/scribblings/field-guide.scrbl b/qi-doc/scribblings/field-guide.scrbl index 604a2144..68427dd2 100644 --- a/qi-doc/scribblings/field-guide.scrbl +++ b/qi-doc/scribblings/field-guide.scrbl @@ -8,18 +8,24 @@ racket]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - qi/probe - (only-in racket/list range) - racket/string - (for-syntax syntax/parse - racket/base)) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + qi/probe + (only-in racket/list range) + racket/string + (for-syntax syntax/parse + racket/base)) + '(define (sqr x) + (* x x))))))) @title{Field Guide} diff --git a/qi-doc/scribblings/forms.scrbl b/qi-doc/scribblings/forms.scrbl index 0a7cc2c8..68656a79 100644 --- a/qi-doc/scribblings/forms.scrbl +++ b/qi-doc/scribblings/forms.scrbl @@ -7,15 +7,21 @@ racket]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range first rest) - racket/string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range first rest) + racket/string) + '(define (sqr x) + (* x x))))))) @(define diagram-eval (make-base-eval)) @(diagram-eval '(require metapict)) diff --git a/qi-doc/scribblings/interface.scrbl b/qi-doc/scribblings/interface.scrbl index 4a291967..d7eebdb6 100644 --- a/qi-doc/scribblings/interface.scrbl +++ b/qi-doc/scribblings/interface.scrbl @@ -10,16 +10,22 @@ ->string)]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range) - racket/string) - '(define ->string number->string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range) + racket/string) + '(define ->string number->string) + '(define (sqr x) + (* x x))))))) @title{Language Interface} diff --git a/qi-doc/scribblings/intro.scrbl b/qi-doc/scribblings/intro.scrbl index 00db872c..ea9f07ca 100644 --- a/qi-doc/scribblings/intro.scrbl +++ b/qi-doc/scribblings/intro.scrbl @@ -7,15 +7,21 @@ racket]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range) - racket/string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range) + racket/string) + '(define (sqr x) + (* x x))))))) @title{Introduction and Usage} diff --git a/qi-doc/scribblings/macros.scrbl b/qi-doc/scribblings/macros.scrbl index f82be5cc..77ce7807 100644 --- a/qi-doc/scribblings/macros.scrbl +++ b/qi-doc/scribblings/macros.scrbl @@ -9,16 +9,22 @@ syntax/parse/define]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range first rest) - (for-syntax syntax/parse racket/base) - racket/string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range first rest) + (for-syntax syntax/parse racket/base) + racket/string) + '(define (sqr x) + (* x x))))))) @title[#:tag "Qi_Macros"]{Qi Macros} diff --git a/qi-doc/scribblings/principles.scrbl b/qi-doc/scribblings/principles.scrbl index 5cba4950..c32740ef 100644 --- a/qi-doc/scribblings/principles.scrbl +++ b/qi-doc/scribblings/principles.scrbl @@ -8,15 +8,21 @@ racket]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range) - racket/string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range) + racket/string) + '(define (sqr x) + (* x x))))))) @(use-mathjax) diff --git a/qi-doc/scribblings/tutorial.scrbl b/qi-doc/scribblings/tutorial.scrbl index d273d8ec..1936aad5 100644 --- a/qi-doc/scribblings/tutorial.scrbl +++ b/qi-doc/scribblings/tutorial.scrbl @@ -8,16 +8,22 @@ racket]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range) - (only-in racket/function curry) - racket/string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range) + (only-in racket/function curry) + racket/string) + '(define (sqr x) + (* x x))))))) @title{Tutorial} diff --git a/qi-doc/scribblings/using-qi.scrbl b/qi-doc/scribblings/using-qi.scrbl index a8b0fb95..3b8dd4b3 100644 --- a/qi-doc/scribblings/using-qi.scrbl +++ b/qi-doc/scribblings/using-qi.scrbl @@ -10,18 +10,24 @@ ->string)]] @(define eval-for-docs - (parameterize ([sandbox-output 'string] - [sandbox-error-output 'string] - [sandbox-memory-limit #f]) - (make-evaluator 'racket/base - '(require qi - (only-in racket/list range) - racket/string - relation) - '(define ->number string->number) - '(define ->string number->string) - '(define (sqr x) - (* x x))))) + ;; The "trusted" sandbox configuration is needed possibly + ;; because of the interaction of binding spaces with + ;; sandbox evaluator. For more context, see the Qi wiki + ;; "Qi Compiler Sync Sept 2 2022." + (call-with-trusted-sandbox-configuration + (lambda () + (parameterize ([sandbox-output 'string] + [sandbox-error-output 'string] + [sandbox-memory-limit #f]) + (make-evaluator 'racket/base + '(require qi + (only-in racket/list range) + racket/string + relation) + '(define ->number string->number) + '(define ->string number->string) + '(define (sqr x) + (* x x))))))) @title{When Should I Use Qi?} diff --git a/qi-lib/flow.rkt b/qi-lib/flow.rkt index 43e86433..1a403275 100644 --- a/qi-lib/flow.rkt +++ b/qi-lib/flow.rkt @@ -1,7 +1,9 @@ #lang racket/base (provide flow - ☯) + ☯ + (all-from-out "flow/extended/expander.rkt") + (all-from-out "flow/extended/forms.rkt")) (require syntax/parse/define (prefix-in fancy: fancy-app) @@ -11,9 +13,10 @@ (for-syntax racket/base syntax/parse (only-in "private/util.rkt" - report-syntax-error) - "flow/extended/expander.rkt") + report-syntax-error)) + "flow/extended/expander.rkt" "flow/core/compiler.rkt" + "flow/extended/forms.rkt" (only-in "private/util.rkt" define-alias)) @@ -40,7 +43,6 @@ in the flow macro. ;; error handling catch-all [(_ expr0 expr ...+) (report-syntax-error - 'flow - (syntax->datum #'(expr0 expr ...)) + this-syntax "(flow flo)" "flow expects a single flow specification, but it received many.")]) diff --git a/qi-lib/flow/core/compiler.rkt b/qi-lib/flow/core/compiler.rkt index 9ca71361..5b4f44f2 100644 --- a/qi-lib/flow/core/compiler.rkt +++ b/qi-lib/flow/core/compiler.rkt @@ -7,11 +7,7 @@ racket/match "syntax.rkt" "../aux-syntax.rkt" - (only-in "../../private/util.rkt" - report-syntax-error)) - (only-in "../../macro.rkt" - qi-macro? - qi-macro-transformer) + racket/format) "impl.rkt" racket/function (prefix-in fancy: fancy-app)) @@ -27,20 +23,6 @@ (define-syntax (qi0->racket stx) (syntax-parse (cadr (syntax->list stx)) - ;; Check first whether the form is a macro. If it is, expand it. - ;; This is prioritized over other forms so that extensions may - ;; override built-in Qi forms. - [stx - #:with (~or* (m:id expr ...) m:id) #'stx - #:do [(define space-m ((make-interned-syntax-introducer 'qi) #'m))] - #:when (qi-macro? (syntax-local-value space-m (λ () #f))) - #:with expanded (syntax-local-apply-transformer - (qi-macro-transformer (syntax-local-value space-m)) - space-m - 'expression - #f - #'stx) - #'(qi0->racket expanded)] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Core language forms ;;;; @@ -68,9 +50,9 @@ #'list] ;; predicates [(~or* (~datum AND) (~datum &)) ; NOTE: technically not core - #'(qi0->racket (>> (and (select 2) (select 1)) #t))] + #'(qi0->racket (>> (and (select 2) (select 1)) (gen #t)))] [(~or* (~datum OR) (~datum ∥)) ; NOTE: technically not core - #'(qi0->racket (<< (or (select 1) (select 2)) #f))] + #'(qi0->racket (<< (or (select 1) (select 2)) (gen #f)))] [(~or* (~datum NOT) (~datum !)) #'not] [(~datum XOR) @@ -98,14 +80,14 @@ [e:loop-form (loop-parser #'e)] [((~datum loop2) pred:clause mapex:clause combex:clause) #'(letrec ([loop2 (qi0->racket (if pred - (~> (== (-< cdr - (~> car mapex)) _) + (~> (== (-< (esc cdr) + (~> (esc car) mapex)) _) (group 1 _ combex) - loop2) + (esc loop2)) (select 2)))]) loop2)] ;; towards universality - [(~datum apply) + [(~datum appleye) #'call] [e:clos-form (clos-parser #'e)] ;; escape hatch for racket expressions or anything @@ -113,22 +95,15 @@ [((~datum esc) ex:expr) #'ex] - ;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Non-core forms ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;; - ;;; Miscellaneous - ;; backwards compat macro extensibility via Racket macros - [((~var ext-form (starts-with "qi:")) expr ...) - #'(ext-form expr ...)] - - ;; a literal is interpreted as a flow generating it - [e:literal (literal-parser #'e)] ; TODO: how would we write this as a macro? - ;; Partial application with syntactically pre-supplied arguments ;; in a blanket template - [e:blanket-template-form (blanket-template-form-parser #'e)] + ;; Note: at this point it's already been parsed/validated + ;; by the expander and we don't need to worry about checking + ;; the syntax at the compiler level + [((~datum #%blanket-template) e) + (blanket-template-form-parser this-syntax)] ;; Fine-grained template-based application ;; This handles templates that indicate a specific number of template @@ -137,11 +112,11 @@ ;; it, we simply use the #%app macro provided by fancy-app instead of the ;; implicit one used for function application in racket/base. ;; "prarg" = "pre-supplied argument" - [(prarg-pre ... (~datum _) prarg-post ...) + [((~datum #%fine-template) (prarg-pre ... (~datum _) prarg-post ...)) #'(fancy:#%app prarg-pre ... _ prarg-post ...)] ;; Pre-supplied arguments without a template - [(natex prarg ...+) + [((~datum #%partial-application) (natex prarg ...+)) ;; we use currying instead of templates when a template hasn't ;; explicitly been indicated since in such cases, we cannot ;; always infer the appropriate arity for a template (e.g. it @@ -150,10 +125,7 @@ #:do [(define chirality (syntax-property this-syntax 'chirality))] (if (and chirality (eq? chirality 'right)) #'(curry natex prarg ...) - #'(curryr natex prarg ...))] - - ;; literally indicated function identifier - [natex:expr #'natex])) + #'(curryr natex prarg ...))])) ;; The form-specific parsers, which are delegated to from ;; the qi0->racket macro: @@ -179,32 +151,24 @@ the DSL. (define (sep-parser stx) (syntax-parse stx [_:id - #'(qi0->racket (if list? - (apply values _) - (raise-argument-error '△ - "list?" - _)))] + #'(qi0->racket (if (esc list?) + (#%fine-template (apply values _)) + (#%fine-template (raise-argument-error '△ + "list?" + _))))] [(_ onex:clause) #'(λ (v . vs) - ((qi0->racket (~> △ (>< (apply (qi0->racket onex) _ vs)))) v))])) + ((qi0->racket (~> △ (>< (#%fine-template (apply (qi0->racket onex) _ vs))))) v))])) (define (select-parser stx) (syntax-parse stx - [(_ n:number ...) #'(qi0->racket (-< (esc (arg n)) ...))] - [(_ arg ...) ; error handling catch-all - (report-syntax-error 'select - (syntax->datum #'(arg ...)) - "(select ...)")])) + [(_ n:number ...) #'(qi0->racket (-< (esc (arg n)) ...))])) (define (block-parser stx) (syntax-parse stx [(_ n:number ...) #'(qi0->racket (~> (esc (except-args n ...)) - △))] - [(_ arg ...) ; error handling catch-all - (report-syntax-error 'block - (syntax->datum #'(arg ...)) - "(block ...)")])) + △))])) (define (group-parser stx) (syntax-parse stx @@ -216,11 +180,9 @@ the DSL. n)] [_:id #'(λ (n selection-flo remainder-flo . vs) - (apply (qi0->racket (group n selection-flo remainder-flo)) vs))] - [(_ arg ...) ; error handling catch-all - (report-syntax-error 'group - (syntax->datum #'(arg ...)) - "(group racket> racket>)")])) + (apply (qi0->racket (group n + (esc selection-flo) + (esc remainder-flo))) vs))])) (define (sieve-parser stx) (syntax-parse stx @@ -233,13 +195,9 @@ the DSL. ;; sieve can be a core form once bindings ;; are introduced into the language #'(λ (condition sonex ronex . args) - (apply (qi0->racket (-< (~> (pass condition) sonex) - (~> (pass (not condition)) ronex))) - args))] - [(_ arg ...) ; error handling catch-all - (report-syntax-error 'sieve - (syntax->datum #'(arg ...)) - "(sieve racket> racket> racket>)")])) + (apply (qi0->racket (-< (~> (pass (esc condition)) (esc sonex)) + (~> (pass (not (esc condition))) (esc ronex)))) + args))])) (define (try-parser stx) (syntax-parse stx @@ -253,11 +211,7 @@ the DSL. ;; error via a binding / syntax parameter (apply (qi0->racket error-handler-flo) args))] ...) - (apply (qi0->racket flo) args)))] - [(_ arg ...) - (report-syntax-error 'try - (syntax->datum #'(arg ...)) - "(try [error-predicate-flo error-handler-flo] ...)")])) + (apply (qi0->racket flo) args)))])) (define (if-parser stx) (syntax-parse stx @@ -286,7 +240,7 @@ the DSL. [(_ ((~datum while) tilex:clause) ((~datum then) thenex:clause)) #'(λ (f . args) - (apply (qi0->racket (feedback (while tilex) (then thenex) f)) + (apply (qi0->racket (feedback (while tilex) (then thenex) (esc f))) args))] [(_ ((~datum while) tilex:clause) onex:clause) #'(qi0->racket (feedback (while tilex) (then _) onex))] @@ -299,7 +253,7 @@ the DSL. [(_ n:expr ((~datum then) thenex:clause)) #'(λ (f . args) - (apply (qi0->racket (feedback n (then thenex) f)) args))] + (apply (qi0->racket (feedback n (then thenex) (esc f))) args))] [(_ n:expr onex:clause) #'(qi0->racket (feedback n (then _) onex))] [(_ onex:clause) @@ -307,7 +261,7 @@ the DSL. (apply (qi0->racket (feedback n onex)) args))] [_:id #'(λ (n flo . args) - (apply (qi0->racket (feedback n flo)) + (apply (qi0->racket (feedback n (esc flo))) args))])) (define (tee-parser stx) @@ -336,13 +290,7 @@ the DSL. [_:id #'(qi0->racket ==)] [(_ onex:clause) - #'(qi0->racket (loop onex))] - [(_ onex0:clause onex:clause ...) - (report-syntax-error - 'amp - (syntax->datum #'(onex0 onex ...)) - "(>< flo)" - "amp expects a single qi0->racket specification, but it received many.")])) + #'(qi0->racket (loop onex))])) (define (pass-parser stx) (syntax-parse stx @@ -389,9 +337,12 @@ the DSL. [(_ pred:clause mapex:clause) #'(qi0->racket (loop pred mapex _ ⏚))] [(_ mapex:clause) - #'(qi0->racket (loop #t mapex _ ⏚))] + #'(qi0->racket (loop (gen #t) mapex _ ⏚))] [_:id #'(λ (predf mapf combf retf . args) - (apply (qi0->racket (loop predf mapf combf retf)) + (apply (qi0->racket (loop (esc predf) + (esc mapf) + (esc combf) + (esc retf))) args))])) (define (clos-parser stx) @@ -418,13 +369,15 @@ the DSL. (define (blanket-template-form-parser stx) (syntax-parse stx ;; "prarg" = "pre-supplied argument" - [(natex prarg-pre ...+ (~datum __) prarg-post ...+) + [((~datum #%blanket-template) + (natex prarg-pre ...+ (~datum __) prarg-post ...+)) #'(curry (curryr natex prarg-post ...) prarg-pre ...)] - [(natex prarg-pre ...+ (~datum __)) + [((~datum #%blanket-template) (natex prarg-pre ...+ (~datum __))) #'(curry natex prarg-pre ...)] - [(natex (~datum __) prarg-post ...+) + [((~datum #%blanket-template) + (natex (~datum __) prarg-post ...+)) #'(curryr natex prarg-post ...)] - [(natex (~datum __)) + [((~datum #%blanket-template) (natex (~datum __))) #'natex]))) diff --git a/qi-lib/flow/core/syntax.rkt b/qi-lib/flow/core/syntax.rkt index 0901b862..8fead4de 100644 --- a/qi-lib/flow/core/syntax.rkt +++ b/qi-lib/flow/core/syntax.rkt @@ -15,7 +15,6 @@ fold-left-form fold-right-form loop-form - blanket-template-form clos-form) (require syntax/parse) @@ -115,11 +114,6 @@ See comments in flow.rkt for more details. (pattern ((~datum loop) arg ...))) -(define-syntax-class blanket-template-form - ;; "prarg" = "pre-supplied argument" - (pattern - (natex prarg-pre ... (~datum __) prarg-post ...))) - (define-syntax-class clos-form (pattern (~datum clos)) diff --git a/qi-lib/flow/extended/expander.rkt b/qi-lib/flow/extended/expander.rkt index e6a2d796..bf68cc13 100644 --- a/qi-lib/flow/extended/expander.rkt +++ b/qi-lib/flow/extended/expander.rkt @@ -1,6 +1,164 @@ #lang racket/base -(provide expand-flow) +(provide (for-syntax expand-flow + qi-macro) + (for-space qi + (all-defined-out) + (rename-out [ground ⏚] + [thread ~>] + [relay ==] + [tee -<] + [amp ><] + [sep △] + [collect ▽]))) -(define (expand-flow stx) - stx) +(require bindingspec + (for-syntax "../aux-syntax.rkt" + "syntax.rkt" + racket/base + syntax/parse + "../../private/util.rkt")) + +(define-hosted-syntaxes + ;; Declare a compile-time datatype by which qi macros may + ;; be identified. + (extension-class qi-macro + #:binding-space qi) + (nonterminal floe + ;; Check first whether the form is a macro. If it is, expand it. + ;; This is prioritized over other forms so that extensions may + ;; override built-in Qi forms. + #:allow-extension qi-macro + #:binding-space qi + (gen e:expr ...) + ;; Ad hoc expansion rule to allow _ to be used in application + ;; position in a template. + ;; Without it, (_ v ...) would be treated as an error since + ;; _ is an unrelated form of the core language having different + ;; semantics. The expander would assume it is a syntax error + ;; from that perspective. + (~> ((~literal _) arg ...) #'(#%fine-template (_ arg ...))) + _ + ground + (thread f:floe ...) + (relay f:floe ...) + relay + (tee f:floe ...) + tee + amp + (amp f:floe) + (~>/form (amp f0:clause f:clause ...) + ;; potentially pull out as a phase 1 function + ;; just a stopgap until better error messages + (report-syntax-error + this-syntax + "(>< flo)" + "amp expects a single flow specification, but it received many.")) + pass + (pass f:floe) + sep + (sep f:floe) + collect + AND + OR + NOT + XOR + (and f:floe ...) + (or f:floe ...) + (not f:floe) + (select e:expr ...) + (~>/form (select arg ...) + (report-syntax-error this-syntax + "(select ...)")) + (block e:expr ...) + (~>/form (block arg ...) + (report-syntax-error this-syntax + "(block ...)")) + (group n:expr e1:floe e2:floe) + group + (~>/form (group arg ...) + (report-syntax-error this-syntax + "(group )")) + (if consequent:floe + alternative:floe) + (if condition:floe + consequent:floe + alternative:floe) + (sieve condition:floe + sonex:floe + ronex:floe) + sieve + (~>/form (sieve arg ...) + (report-syntax-error this-syntax + "(sieve )")) + (try flo:floe + [error-condition-flo:floe error-handler-flo:floe] + ...+) + (~>/form (try arg ...) + (report-syntax-error this-syntax + "(try [error-predicate-flo error-handler-flo] ...)")) + >> + (>> fn:floe init:floe) + (>> fn:floe) + << + (<< fn:floe init:floe) + (<< fn:floe) + (feedback ((~datum while) tilex:floe) + ((~datum then) thenex:floe) + onex:floe) + (feedback ((~datum while) tilex:floe) + ((~datum then) thenex:floe)) + (feedback ((~datum while) tilex:floe) onex:floe) + (feedback ((~datum while) tilex:floe)) + (feedback n:expr + ((~datum then) thenex:floe) + onex:floe) + (feedback n:expr + ((~datum then) thenex:floe)) + (feedback n:expr onex:floe) + (feedback onex:floe) + feedback + (loop pred:floe mapex:floe combex:floe retex:floe) + (loop pred:floe mapex:floe combex:floe) + (loop pred:floe mapex:floe) + (loop mapex:floe) + loop + (loop2 pred:floe mapex:floe combex:floe) + appleye + (~> (~literal apply) #'appleye) + clos + (clos onex:floe) + (esc ex:expr) + ;; backwards compat macro extensibility via Racket macros + (~> ((~var ext-form (starts-with "qi:")) expr ...) + #'(esc (ext-form expr ...))) + ;; a literal is interpreted as a flow generating it + (~> val:literal + #'(gen val)) + ;; Certain rules of the language aren't determined by the "head" + ;; position, so naively, these can't be core forms. In order to + ;; treat them as core forms, we tag them at the expander level + ;; by wrapping them with #%-prefixed forms, similar to Racket's + ;; approach to a similiar case - "interposition points." These + ;; new forms can then be treated as core forms in the compiler. + (~> f:blanket-template-form + #'(#%blanket-template f)) + (#%blanket-template (arg:any-stx ...)) + (~> f:fine-template-form + #'(#%fine-template f)) + (#%fine-template (arg:any-stx ...)) + ;; The core rule must come before the tagging rule here since + ;; the former as a production of the latter would still match + ;; the latter (i.e. it is still a parenthesized expression), + ;; which would lead to infinite code generation. + (#%partial-application (arg:any-stx ...)) + (~> f:partial-application-form + #'(#%partial-application f)) + ;; literally indicated function identifier + ;; TODO: make this id rather than expr once + ;; everything else is stable + (~> f:expr #'(esc f)))) + +(begin-for-syntax + (define (expand-flow stx) + ((nonterminal-expander floe) stx))) diff --git a/qi-lib/flow/extended/forms.rkt b/qi-lib/flow/extended/forms.rkt index 6bb66b10..78bc3c5b 100644 --- a/qi-lib/flow/extended/forms.rkt +++ b/qi-lib/flow/extended/forms.rkt @@ -1,46 +1,13 @@ #lang racket/base (provide (for-space qi - one-of? - all - any - none - NOR - NAND - XNOR - any? - all? - none? - and% - or% - thread-right - ~>> - crossover - X - relay* - ==* - bundle - when - unless - switch - partition - gate - fanout - count - live? - rectify - 1> - 2> - 3> - 4> - 5> - 6> - 7> - 8> - 9> - inverter - effect - ε)) + (all-defined-out) + ;; defining and using a `define-qi-alias` form + ;; would be a more direct way to do this + (rename-out [thread-right ~>>] + [crossover X] + [relay* ==*] + [effect ε]))) (require (for-syntax racket/base syntax/parse @@ -48,6 +15,7 @@ "syntax.rkt" "../aux-syntax.rkt" "../../private/util.rkt") + "expander.rkt" "../../macro.rkt" "impl.rkt") @@ -98,28 +66,14 @@ (define-qi-syntax-rule (thread-right onex:right-threading-clause ...) (~> onex.chiral ...)) -;; TODO: do it as an alias? -;; (define-qi-alias ~>> thread-right) - -(define-qi-syntax-rule (~>> arg ...) - (thread-right arg ...)) - (define-qi-syntax-parser crossover [_:id #'(~> ▽ reverse △)]) -;; TODO: alias -(define-qi-syntax-parser X - [_:id #'crossover]) - (define-qi-syntax-parser relay* [(_ onex:clause ... rest-onex:clause) #:with len #`#,(length (syntax->list #'(onex ...))) #'(group len (== onex ...) rest-onex)]) -;; TODO: alias -(define-qi-syntax-rule (==* onex ...) - (relay* onex ...)) - (define-qi-syntax-rule (bundle (n:number ...) selection-onex:clause remainder-onex:clause) @@ -263,7 +217,3 @@ [(_ sidex:clause) #'(-< (~> sidex ⏚) _)]) - -;; TODO: alias -(define-qi-syntax-rule (ε arg ...) - (effect arg ...)) diff --git a/qi-lib/flow/extended/syntax.rkt b/qi-lib/flow/extended/syntax.rkt index 084ae105..20067ab2 100644 --- a/qi-lib/flow/extended/syntax.rkt +++ b/qi-lib/flow/extended/syntax.rkt @@ -2,7 +2,11 @@ (provide conjux-clause disjux-clause - right-threading-clause) + right-threading-clause + blanket-template-form + fine-template-form + partial-application-form + any-stx) (require syntax/parse "../aux-syntax.rkt" @@ -33,3 +37,24 @@ (pattern onex:clause #:with chiral (make-right-chiral #'onex))) + +;; Note these are used in the expander instead of in the compiler. +;; That's why they don't need the tag (i.e. they don't look for +;; #%blanket-template, #%fine-template, or #%partial-application) +(define-syntax-class blanket-template-form + ;; "prarg" = "pre-supplied argument" + (pattern + (natex prarg-pre ... (~datum __) prarg-post ...))) + +(define-syntax-class fine-template-form + ;; "prarg" = "pre-supplied argument" + (pattern + (prarg-pre ... (~datum _) prarg-post ...))) + +(define-syntax-class partial-application-form + ;; "prarg" = "pre-supplied argument" + (pattern + (natex prarg ...+))) + +(define-syntax-class any-stx + (pattern _)) diff --git a/qi-lib/info.rkt b/qi-lib/info.rkt index aec2a73a..d4d4cc6a 100644 --- a/qi-lib/info.rkt +++ b/qi-lib/info.rkt @@ -3,7 +3,10 @@ (define version "3.0") (define collection "qi") (define deps '("base" - ("fancy-app" #:version "1.1"))) + ("fancy-app" #:version "1.1") + ;; this git URL should be changed to a named package spec + ;; once bindingspec is on the package index + "git://github.com/michaelballantyne/bindingspec.git#main")) (define build-deps '()) (define clean '("compiled" "private/compiled")) (define pkg-authors '(countvajhula)) diff --git a/qi-lib/macro.rkt b/qi-lib/macro.rkt index a90e02ad..ffdaf456 100644 --- a/qi-lib/macro.rkt +++ b/qi-lib/macro.rkt @@ -3,23 +3,22 @@ (provide define-qi-syntax define-qi-syntax-rule define-qi-syntax-parser - define-qi-alias define-qi-foreign-syntaxes - (for-syntax qi-macro? - qi-macro-transformer - qi-macro)) + (for-syntax qi-macro)) (require (for-syntax racket/base syntax/parse racket/format racket/match racket/list) + (only-in "flow/extended/expander.rkt" + qi-macro + esc) racket/format syntax/parse/define syntax/parse) (begin-for-syntax - (struct qi-macro [transformer]) (define (foreign-template-arg-indices tmpl) ;; return a list of indices corresponding to @@ -96,9 +95,9 @@ transformer)])) ;; TODO: get this to work -(define-syntax define-qi-alias - (syntax-parser - [(_ alias:id name:id) #'(define-qi-syntax alias (make-rename-transformer #'name))])) +;; (define-syntax define-qi-alias +;; (syntax-parser +;; [(_ alias:id name:id) #'(define-qi-syntax alias (make-rename-transformer #'name))])) (define-syntax define-qi-syntax-rule (syntax-parser diff --git a/qi-lib/main.rkt b/qi-lib/main.rkt index c0209af8..b81c616e 100644 --- a/qi-lib/main.rkt +++ b/qi-lib/main.rkt @@ -5,14 +5,10 @@ qi/macro qi/on qi/switch - qi/threading - qi/flow/extended/forms)) + qi/threading)) (require qi/flow - (except-in qi/macro - qi-macro-transformer - qi-macro?) + qi/macro qi/on qi/switch - qi/threading - qi/flow/extended/forms) + qi/threading) diff --git a/qi-lib/private/util.rkt b/qi-lib/private/util.rkt index 1dd34c61..4a6c329a 100644 --- a/qi-lib/private/util.rkt +++ b/qi-lib/private/util.rkt @@ -6,21 +6,25 @@ (require racket/string racket/format + racket/match syntax/parse/define (for-syntax racket/base syntax/parse/lib/function-header)) -(define (report-syntax-error name args usage . msgs) - (raise-syntax-error name - (~a "Syntax error in " - (list* name args) - "\n" - "Usage:\n" - " " usage - (if (null? msgs) - "" - (string-append "\n" - (string-join msgs "\n")))))) +(define (report-syntax-error stx usage . msgs) + (match (syntax->datum stx) + [(cons name args) + (raise-syntax-error name + (~a "Syntax error in " + (list* name args) + "\n" + "Usage:\n" + " " usage + (if (null? msgs) + "" + (string-append "\n" + (string-join msgs "\n")))) + stx)])) (define-syntax-parse-rule (define-alias alias:id name:id) (define-syntax alias (make-rename-transformer #'name))) diff --git a/qi-lib/switch.rkt b/qi-lib/switch.rkt index a6b8ce7d..aeb9a5df 100644 --- a/qi-lib/switch.rkt +++ b/qi-lib/switch.rkt @@ -1,6 +1,10 @@ #lang racket/base -(provide switch +;; This name juggling is necessary since the Racket macros would +;; otherwise collide with the Qi forms with the same name in the qi +;; binding space, since Qi forms are now exported literals and not simply +;; matched as datum patterns as they were formerly. +(provide (rename-out [%switch switch]) switch-lambda switch-λ λ01 @@ -16,7 +20,7 @@ define-alias params-parser)) -(define-syntax-parser switch +(define-syntax-parser %switch [(_ args:subject clause ...) #'(on args @@ -30,7 +34,7 @@ [(_ args:formals expr:expr ...) #:with ags (params-parser #'args) #'(lambda args - (switch ags + (%switch ags expr ...))]) (define-alias λ01 switch-lambda) diff --git a/qi-lib/threading.rkt b/qi-lib/threading.rkt index 7c20effe..88874b76 100644 --- a/qi-lib/threading.rkt +++ b/qi-lib/threading.rkt @@ -1,20 +1,24 @@ #lang racket/base -(provide ~> - ~>>) +;; This name juggling is necessary since the Racket macros would +;; otherwise collide with the Qi forms with the same name in the qi +;; binding space, since Qi forms are now exported literals and not simply +;; matched as datum patterns as they were formerly. +(provide (rename-out [%~> ~>] + [%~>> ~>>])) (require syntax/parse/define (for-syntax racket/base (only-in "private/util.rkt" report-syntax-error) "flow/aux-syntax.rkt") + "flow.rkt" "on.rkt") -(define-syntax-parser ~> +(define-syntax-parser %~> [(_ (arg0 arg ...+) (~or* (~datum sep) (~datum △)) clause:clause ...) ;; catch a common usage error - (report-syntax-error '~> - (syntax->datum #'((arg0 arg ...) sep clause ...)) + (report-syntax-error this-syntax "(~> (arg ...) flo ...)" "Attempted to separate multiple values." "Note that the inputs to ~> must be wrapped in parentheses.")] @@ -22,11 +26,10 @@ #:with ags (attribute args.args) #'(on ags (~> clause ...))]) -(define-syntax-parser ~>> +(define-syntax-parser %~>> [(_ (arg0 arg ...+) (~or* (~datum sep) (~datum △)) clause:clause ...) ;; catch a common usage error - (report-syntax-error '~>> - (syntax->datum #'((arg0 arg ...) sep clause ...)) + (report-syntax-error this-syntax "(~>> (arg ...) flo ...)" "Attempted to separate multiple values." "Note that the inputs to ~>> must be wrapped in parentheses.")] diff --git a/qi-test/tests/flow.rkt b/qi-test/tests/flow.rkt index a7b8132c..1fa14625 100644 --- a/qi-test/tests/flow.rkt +++ b/qi-test/tests/flow.rkt @@ -10,7 +10,8 @@ racket/list racket/string racket/function - "private/util.rkt") + (except-in "private/util.rkt" + add-two)) ;; used in the "language extension" tests for `qi:*` (define-syntax-rule (qi:square flo) @@ -395,6 +396,10 @@ "p" "q") "abpq" "right-threading without template") + (check-equal? ((☯ (~>> △ (sort < #:key identity))) + (list 2 1 3)) + (list 1 2 3) + "right-threading with keyword arg") ;; TODO: propagate threading side to nested clauses ;; (check-equal? (on ("p" "q") ;; (~>> (>< (string-append "a" "b")) @@ -849,37 +854,37 @@ 1 -3 5) (list 1 1 5 5 -3) "sieve with arity-increasing clause") - (check-equal? (~> (1 2 -3 4) - (-< (gen positive? + (☯ (+ 2))) _) - sieve - ▽) + (check-equal? ((☯ (~> (-< (gen positive? + (☯ (+ 2))) _) + sieve + ▽)) + 1 2 -3 4) (list 7 -1) "pure control form of sieve")) (test-suite - "partition" - (check-equal? ((flow (~> (partition) collect))) - (list) - "base partition case") - (check-equal? ((flow (partition [positive? +])) - -1 2 1 1 -2 2) - 6 - "partition composes ~> and pass") - (check-equal? ((flow (~> (partition [positive? +] - [zero? (-< count (gen "zero"))] - [negative? *]) collect)) - -1 0 2 1 1 -2 0 0 2) - (list 6 3 "zero" 2)) - (check-equal? ((flow (~> (partition [positive? +] - [zero? (-< count (gen "zero"))] - [negative? *]) collect)) - -1 2 1 1 -2 2) - (list 6 0 "zero" 2) - "some partition bodies have no inputs") - (check-equal? ((flow (~> (partition [(and positive? (> 1)) +] - [_ list]) collect)) - -1 2 1 1 -2 2) - (list 4 (list -1 1 1 -2)) - "partition bodies can be flows")) + "partition" + (check-equal? ((flow (~> (partition) collect))) + (list) + "base partition case") + (check-equal? ((flow (partition [positive? +])) + -1 2 1 1 -2 2) + 6 + "partition composes ~> and pass") + (check-equal? ((flow (~> (partition [positive? +] + [zero? (-< count (gen "zero"))] + [negative? *]) collect)) + -1 0 2 1 1 -2 0 0 2) + (list 6 3 "zero" 2)) + (check-equal? ((flow (~> (partition [positive? +] + [zero? (-< count (gen "zero"))] + [negative? *]) collect)) + -1 2 1 1 -2 2) + (list 6 0 "zero" 2) + "some partition bodies have no inputs") + (check-equal? ((flow (~> (partition [(and positive? (> 1)) +] + [_ list]) collect)) + -1 2 1 1 -2 2) + (list 4 (list -1 1 1 -2)) + "partition bodies can be flows")) (test-suite "gate" (check-equal? ((☯ (gate positive?)) @@ -925,31 +930,32 @@ 9)) (test-suite "fanout" - (check-equal? (~> (5) (fanout 3) ▽) + (check-equal? ((☯ (~> (fanout 3) ▽)) + 5) (list 5 5 5)) - (check-equal? (~> (2 3) (fanout 3) ▽) + (check-equal? ((☯ (~> (fanout 3) ▽)) 2 3) (list 2 3 2 3 2 3)) - (check-equal? (~> (3 "a") fanout string-append) + (check-equal? ((☯ (~> fanout string-append)) 3 "a") "aaa" "control form of fanout") - (check-equal? (~> (3 "a" "b") fanout string-append) + (check-equal? ((☯ (~> fanout string-append)) 3 "a" "b") "ababab" "control form of fanout") - (check-equal? (~> (5) (fanout (add1 2)) ▽) + (check-equal? ((☯ (~> (fanout (add1 2)) ▽)) 5) (list 5 5 5) "arbitrary racket expressions and not just literals") (check-equal? (let ([n 3]) - (~> (5) (fanout n) ▽)) + ((☯ (~> (fanout n) ▽)) 5)) (list 5 5 5) "arbitrary racket expressions and not just literals") - (check-equal? (~> (2 3) (fanout 0) ▽) + (check-equal? ((☯ (~> (fanout 0) ▽)) 2 3) null "N=0 produces no values.") - (check-equal? (~> () (fanout 3) ▽) + (check-equal? ((☯ (~> (fanout 3) ▽))) null "No inputs produces no outputs.") (check-exn exn:fail:contract? - (thunk (~> (-1 3) fanout ▽)) + (thunk ((☯ (~> fanout ▽)) -1 3)) "Negative N signals an error.")) (test-suite "inverter" @@ -965,7 +971,7 @@ 5) 625 "(feedback N flo)") - (check-equal? (~> (3 5) (feedback add1)) + (check-equal? ((☯ (~> (feedback add1))) 3 5) 8 "(feedback flo) consumes the first input as N") (check-equal? ((☯ (feedback 5 (then sqr) add1)) @@ -1163,16 +1169,16 @@ (check-true ((☯ live?) 3 4 5)) (check-true ((☯ live?) 5)) (check-false ((☯ live?))) - (check-true (~> (1 2) live?)) - (check-false (~> (1 2) ⏚ live?))) + (check-true ((☯ (~> live?)) 1 2)) + (check-false ((☯ (~> ⏚ live?)) 1 2))) (test-suite "rectify" - (check-equal? (~> (3 4 5) (rectify 'boo) ▽) (list 3 4 5)) - (check-equal? (~> (5) (rectify 'boo)) 5) - (check-equal? (~> () (rectify 'boo)) 'boo) - (check-equal? (~> (1 2) (rectify #f) ▽) (list 1 2)) - (check-equal? (~> (1 2) ⏚ (rectify #f)) #f))) + (check-equal? ((☯ (~> (rectify 'boo) ▽)) 3 4 5) (list 3 4 5)) + (check-equal? ((☯ (~> (rectify 'boo))) 5) 5) + (check-equal? ((☯ (~> (rectify 'boo)))) 'boo) + (check-equal? ((☯ (~> (rectify #f) ▽)) 1 2) (list 1 2)) + (check-equal? ((☯ (~> ⏚ (rectify #f))) 1 2) #f))) (test-suite "higher-order flows" @@ -1323,7 +1329,7 @@ "language extension" (test-suite "qi:" - (check-equal? (~> (2 3) + (qi:square sqr)) + (check-equal? ((☯ (~> + (qi:square sqr))) 2 3) 625))) (test-suite diff --git a/qi-test/tests/util.rkt b/qi-test/tests/util.rkt index c3fd8123..ffa87a15 100644 --- a/qi-test/tests/util.rkt +++ b/qi-test/tests/util.rkt @@ -14,8 +14,7 @@ (test-suite "report-syntax-error" (check-exn exn:fail:syntax? - (thunk (report-syntax-error 'dummy - (list 1 2 3) + (thunk (report-syntax-error #'(dummy 1 2 3) "blah: blah" "Use it" "like"