#lang racket/base
(require (for-template "../lang/base.rkt")
(for-template "teach-runtime.rkt")
"teachhelp.rkt"
stepper/private/shared
racket/list
syntax/context
syntax/kerncase
syntax/stx)
(provide advanced-define/proc
advanced-lambda/proc
advanced-when/proc
advanced-unless/proc
advanced-set!/proc advanced-set!-continue/proc
advanced-case/proc
intermediate-local/proc
beginner-dots/proc)
(define (verify-boolean b where)
(if (or (eq? b #t) (eq? b #f))
b
(raise
(make-exn:fail:contract
(format "~a: question result is not true or false: ~e" where b)
(current-continuation-marks)))))
(define (stepper-ignore-checker stx)
(stepper-syntax-property stx 'stepper-skipto '(syntax-e cdr syntax-e cdr car)))
(define (make-name-inventer)
(lambda (id)
(datum->syntax id
(string->uninterned-symbol (symbol->string (syntax-e id)))
id)))
(define-struct expanding-for-intermediate-local ())
(define (teach-syntax-error form stx detail msg . args)
(let ([form (or form (first (flatten (syntax->datum stx))))]
[msg (apply format msg args)])
(if detail
(raise-syntax-error form msg stx detail)
(raise-syntax-error form msg stx))))
(define (teach-syntax-error* form stx details msg . args)
(let ([exn (with-handlers ([exn:fail:syntax?
(lambda (x) x)])
(apply teach-syntax-error form stx #f msg args))])
(raise
(make-exn:fail:syntax
(exn-message exn)
(exn-continuation-marks exn)
details))))
(define (ensure-expression stx k)
(if (memq (syntax-local-context) '(expression))
(k)
(stepper-syntax-property #`(begin0 #,stx) 'stepper-skipto skipto/second)))
(define (something-else/kw stx)
(if (identifier? stx)
"a keyword"
(something-else stx)))
(define (something-else v)
(let ([v (syntax-e v)])
(cond
[(number? v) "a number"]
[(string? v) "a string"]
[(list? v) "a part"]
[(struct? v) "an image"]
[else "something else"])))
(define (check-definitions-new who stx names defn assign)
(cond
[(eq? (syntax-local-context) 'top-level)
(with-syntax ([defn defn]
[who who])
(with-syntax ([(check ...)
(map (lambda (name)
(with-syntax ([name name])
(syntax/loc stx
(void) (check-top-level-not-defined 'who #'name))))
names)])
(stepper-syntax-property
(syntax/loc stx
(begin
check ...
defn))
'stepper-skipto
(cons 'syntax-e
(let loop ([l names])
(if (null? l)
`(syntax-e cdr car)
(cons 'cdr (loop (cdr l)))))))))]
[(memq (syntax-local-context) '(module module-begin))
(for-each (lambda (name)
(let ([b (identifier-binding name)])
(when b
(teach-syntax-error
(syntax-e name)
name
#f
"this name was defined previously and cannot be re-defined"))))
names)
(if assign
(with-syntax ([(name ...) (if (eq? assign #t)
names
assign)]
[made-up (gensym)]
[defn defn])
(with-syntax ([made-up-defn (stepper-syntax-property
(with-syntax ([set! (datum->syntax stx 'set!)])
(syntax (define made-up (lambda () (set! name 10) ...))))
'stepper-skip-completely
#t)])
(syntax/loc stx
(begin
made-up-defn defn))))
defn)]
[else defn]))
(define (check-definition-new who stx name defn assign)
(check-definitions-new who stx (list name) defn assign))
(define (check-single-result-expr exprs where enclosing-expr will-bind)
(check-single-expression where
"for the function body"
enclosing-expr
exprs
will-bind))
(define (check-single-expression who where stx exprs will-bind)
(when (null? exprs)
(teach-syntax-error
who
stx
#f
"expected an expression ~a, but nothing's there"
where))
(unless (null? (cdr exprs))
(when will-bind
(local-expand-for-error (car exprs) 'expression (cons (datum->syntax stx 'set!)
will-bind)))
(teach-syntax-error
who
stx
(cadr exprs)
"expected only one expression ~a, but found ~a extra part~a"
where
(sub1 (length exprs))
(if (> (length exprs) 2) "s" ""))))
(define (local-expand-for-error stx ctx stops)
(when (memq (syntax-local-context) '(expression))
(local-expand stx ctx stops)))
(define (bad-use-error name stx)
(teach-syntax-error
name
stx
#f
"expected an open parenthesis before ~a, but found none" name))
(define (check-defined-lambda rhs)
(syntax-case rhs ()
[(lam . _)
(and (identifier? #'lam)
(or (free-identifier=? #'lam #'beginner-lambda)
(free-identifier=? #'lam #'intermediate-pre-lambda)))
(syntax-case rhs ()
[(lam arg-seq lexpr ...)
(syntax-case (syntax arg-seq) () [(arg ...) #t][_else #f])
(let ([args (syntax->list (syntax arg-seq))])
(for-each (lambda (arg)
(unless (identifier? arg)
(teach-syntax-error
'lambda
rhs
arg
"expected a variable, but found ~a"
(something-else/kw arg))))
args)
(when (null? args)
(teach-syntax-error
'lambda
rhs
(syntax arg-seq)
"expected at least one variable after lambda, but found none"))
(let ([dup (check-duplicate-identifier args)])
(when dup
(teach-syntax-error
'lambda
rhs
dup
"found a variable that is used more than once: ~a"
(syntax-e dup))))
(check-single-result-expr (syntax->list (syntax (lexpr ...)))
#f
rhs
args)
'ok)]
[(lam args . _)
(teach-syntax-error
'lambda
rhs
(syntax args)
"expected at least one variable (in parentheses) after lambda, but found ~a"
(something-else (syntax args)))]
[(lam)
(teach-syntax-error
'lambda
rhs
#f
"expected at least one variable (in parentheses) after lambda, but nothing's there")]
[_else 'ok])]
[_else 'ok]))
(define (dots-error stx name)
(quasisyntax/loc stx
(error (quote (unsyntax name))
"expected a finished expression, but found a template")))
(define beginner-dots/proc
(make-set!-transformer
(lambda (stx)
(syntax-local-lift-expression (datum->syntax #'here 1 stx))
(syntax-case stx (set!)
[(set! form expr) (dots-error stx (syntax form))]
[(form . rest) (dots-error stx (syntax form))]
[form (dots-error stx stx)]))))
(define (intermediate-local/proc stx)
(ensure-expression
stx
(lambda ()
(syntax-case stx ()
[(_ (definition ...) . exprs)
(let ([defns (syntax->list (syntax (definition ...)))]
[int-def-ctx (build-expand-context (make-expanding-for-intermediate-local))])
(let* ([partly-expand (lambda (d)
(local-expand
d
int-def-ctx
(kernel-form-identifier-list)))]
[partly-expanded-defns
(map partly-expand defns)]
[flattened-defns
(let loop ([l partly-expanded-defns][origs defns])
(apply
append
(map (lambda (d orig)
(syntax-case d (begin define-values define-syntaxes)
[(begin defn ...)
(let ([l (map partly-expand (syntax->list (syntax (defn ...))))])
(loop l l))]
[(define-values . _)
(list d)]
[(define-syntaxes . _)
(list d)]
[_else
(teach-syntax-error
'local
stx
orig
"expected a definition, but found ~a"
(something-else orig))]))
l origs)))]
[val-defns
(apply
append
(map (lambda (partly-expanded)
(syntax-case partly-expanded (define-values)
[(define-values (id ...) expr)
(list partly-expanded)]
[_else
null]))
flattened-defns))]
[stx-defns
(apply
append
(map (lambda (partly-expanded)
(syntax-case partly-expanded (define-syntaxes)
[(define-syntaxes (id ...) expr)
(list partly-expanded)]
[_else
null]))
flattened-defns))]
[get-ids (lambda (l)
(apply
append
(map (lambda (partly-expanded)
(syntax-case partly-expanded ()
[(_ (id ...) expr)
(syntax->list (syntax (id ...)))]))
l)))]
[val-ids (get-ids val-defns)]
[stx-ids (get-ids stx-defns)])
(let ([dup (check-duplicate-identifier (append val-ids stx-ids))])
(when dup
(teach-syntax-error
'local
stx
dup
"~a was defined locally more than once"
(syntax-e dup)))
(let ([exprs (syntax->list (syntax exprs))])
(check-single-expression 'local
"after the local definitions"
stx
exprs
(append val-ids stx-ids)))
(with-syntax ([((d-v (def-id ...) def-expr) ...) val-defns]
[(stx-def ...) stx-defns])
(with-syntax ([(((tmp-id def-id/prop) ...) ...)
(map (lambda (def-ids)
(map (lambda (def-id)
(list
(stepper-syntax-property
(datum->syntax
#f
(string->uninterned-symbol
(symbol->string (syntax-e def-id))))
'stepper-orig-name
def-id)
(syntax-property
def-id
'bind-as-variable
#t)))
(syntax->list def-ids)))
(syntax->list (syntax ((def-id ...) ...))))])
(with-syntax ([(mapping ...)
(let ([mappers
(syntax->list
(syntax
((define-syntaxes (def-id/prop ...)
(values
(make-undefined-check
(quote-syntax check-not-undefined)
(quote-syntax tmp-id))
...))
...)))])
(map syntax-track-origin
mappers
val-defns
(syntax->list (syntax (d-v ...)))))])
(stepper-syntax-property
(quasisyntax/loc stx
(let ()
(#%stratified-body
(define #,(gensym) 1) mapping ...
stx-def ...
(define-values (tmp-id ...) def-expr)
...
. exprs)))
'stepper-hint
'comes-from-local)))))))]
[(_ def-non-seq . __)
(teach-syntax-error
'local
stx
(syntax def-non-seq)
"expected at least one definition (in square brackets) after local, but found ~a"
(something-else (syntax def-non-seq)))]
[(_)
(teach-syntax-error
'local
stx
#f
"expected at least one definition (in square brackets) after local, but nothing's there")]
[_else (bad-use-error 'local stx)]))))
(define (define/proc first-order? assign? stx lambda-stx)
(define (wrap-func-definition name argc k)
(wrap-func-definitions first-order?
'(procedure) (list name) (list argc)
(lambda (names)
(k (car names)))))
(define (check-function-defn-ok stx)
(when first-order?
(when (eq? 'top-level (syntax-local-context))
(teach-syntax-error
'define
stx
#f
"function definitions are not allowed in the interactions window; ~
they must be in the definitions window"))))
(unless (or (ok-definition-context)
(identifier? stx))
(teach-syntax-error
'define
stx
#f
"found a definition that is not at the top level"))
(syntax-case stx ()
[(_ name expr)
(identifier? (syntax name))
(let ([lam (syntax expr)])
(check-defined-lambda lam)
(syntax-case* (syntax expr) (beginner-lambda) (lambda (a b)
(free-identifier=? a lambda-stx))
[(beginner-lambda arg-seq lexpr ...)
(begin
(check-function-defn-ok stx)
(let-values ([(defn bind-names)
(wrap-func-definition
#'name
(length (syntax->list #'arg-seq))
(lambda (name)
(with-syntax ([name name])
(quasisyntax/loc
stx
(define name
#,(stepper-syntax-property
(syntax-track-origin
#`(lambda arg-seq
#,(stepper-syntax-property #`make-lambda-generative
'stepper-skip-completely #t)
lexpr ...)
lam
(syntax-local-introduce (car (syntax-e lam))))
'stepper-define-type
'lambda-define))))))])
(check-definition-new
'define
stx
#'name
defn
(and assign? bind-names))))]
[_else
(check-definition-new
'define
stx
(syntax name)
(quasisyntax/loc stx (define name expr))
(and assign? (list (syntax name))))]))]
[(_ name-seq expr ...)
(syntax-case (syntax name-seq) () [(name ...) #t][_else #f])
(let ([names (syntax->list (syntax name-seq))])
(check-function-defn-ok stx)
(when (null? names)
(teach-syntax-error
'define
stx
#f
"expected a name for the function, but nothing's there"))
(let loop ([names names][pos 0])
(unless (null? names)
(unless (identifier? (car names))
(teach-syntax-error
'define
stx
(car names)
"expected ~a, but found ~a"
(cond
[(zero? pos) "the name of the function"]
[else "a variable"])
(something-else/kw (car names))))
(loop (cdr names) (add1 pos))))
(when (null? (cdr names))
(teach-syntax-error
'define
stx
(syntax name-seq)
"expected at least one variable after the function name, but found none"))
(let ([dup (check-duplicate-identifier (cdr names))])
(when dup
(teach-syntax-error
'define
stx
dup
"found a variable that is used more than once: ~a"
(syntax-e dup))))
(check-single-result-expr (syntax->list (syntax (expr ...)))
#f
stx
#f)
(let-values ([(defn bind-names)
(wrap-func-definition
(car (syntax-e #'name-seq))
(length (cdr (syntax->list #'name-seq)))
(lambda (fn)
(with-syntax ([fn fn]
[args (cdr (syntax-e #'name-seq))])
(quasisyntax/loc stx
(define fn
#,(stepper-syntax-property
(stepper-syntax-property
(syntax/loc stx (lambda args expr ...))
'stepper-define-type
'shortened-proc-define)
'stepper-proc-define-name
#`fn))))))])
(check-definition-new
'define
stx
(car names)
defn
(and assign? bind-names))))]
[(_ name expr ...)
(identifier? (syntax name))
(let ([exprs (syntax->list (syntax (expr ...)))])
(check-single-expression 'define
(format "after the variable name ~a"
(syntax-e (syntax name)))
stx
exprs
#f))]
[(_ non-name expr ...)
(teach-syntax-error
'define
stx
(syntax non-name)
"expected a variable name, or a function name and its variables (in parentheses), but found ~a"
(something-else/kw (syntax non-name)))]
[(_)
(teach-syntax-error
'define
stx
#f
"expected a variable name, or a function name and its variables (in parentheses), but nothing's there")]
[_else
(bad-use-error 'define stx)]))
(define (wrap-func-definitions first-order? kinds names argcs k)
(if first-order?
(let ([name2s (map (make-name-inventer) names)])
(values (quasisyntax
(begin
#,@(map
(lambda (name name2 kind argc)
#`(define-syntax #,name
(make-first-order-function '#,kind
#,argc
(quote-syntax #,name2)
(quote-syntax #%app))))
names name2s kinds argcs)
#,(k name2s)))
name2s))
(values (k names)
names)))
(define (ok-definition-context)
(let ([ctx (syntax-local-context)])
(or (memq ctx '(top-level module module-begin))
(and (pair? ctx)
(expanding-for-intermediate-local? (car ctx))))))
(define (advanced-define/proc stx)
(syntax-case stx ()
[(_ (name) expr)
(and (identifier? (syntax name))
(ok-definition-context))
(check-definition-new
'define
stx
(syntax name)
(syntax/loc stx (define (name) expr))
(list #'name))]
[(_ (name) expr ...)
(and (identifier? (syntax name))
(ok-definition-context))
(check-single-result-expr (syntax->list (syntax (expr ...)))
#f
stx
(list #'name))]
[(_ . rest)
(define/proc #f #t stx #'beginner-lambda)]
[_else
(bad-use-error 'define stx)]))
(define (advanced-lambda/proc stx)
(ensure-expression
stx
(lambda ()
(syntax-case stx ()
[(_ (name ...) . exprs)
(let ([names (syntax->list (syntax (name ...)))])
(for-each (lambda (name)
(unless (identifier? name)
(teach-syntax-error
'lambda
stx
name
"expected a variable, but found ~a"
(something-else/kw name))))
names)
(let ([dup (check-duplicate-identifier names)])
(when dup
(teach-syntax-error
'lambda
stx
dup
"found a variable that is used more than once: ~a"
(syntax-e dup))))
(check-single-expression 'lambda
"for the function body"
stx
(syntax->list (syntax exprs))
names)
(syntax/loc stx (lambda (name ...) . exprs)))]
[(_ arg-non-seq . exprs)
(teach-syntax-error
'lambda
stx
(syntax arg-non-seq)
"expected at least one variable (in parentheses) after lambda, but found ~a"
(something-else (syntax arg-non-seq)))]
[(_)
(teach-syntax-error
'lambda
stx
#f
"expected at least one variable (in parentheses) after lambda, but nothing's there")]
[_else
(bad-use-error 'lambda stx)]))))
(define-values (advanced-when/proc advanced-unless/proc)
(let ([mk
(lambda (who target-stx)
(lambda (stx)
(ensure-expression
stx
(lambda ()
(syntax-case stx ()
[(_)
(teach-syntax-error
who
stx
#f
"expected a question and an answer, but nothing's there")]
[(_ q)
(teach-syntax-error
who
stx
#'q
"expected a question and an answer, but found only one part")]
[(_ q a)
(with-syntax ([who who]
[target target-stx])
(syntax/loc stx (target (verify-boolean q 'who) a)))]
[(_ . parts)
(teach-syntax-error*
who
stx
(syntax->list #'parts)
"expected a question and an answer, but found ~a parts" (length (syntax->list #'parts)))]
[_else
(bad-use-error who stx)])))))])
(values (mk 'when (quote-syntax when))
(mk 'unless (quote-syntax unless)))))
(define-values (advanced-set!/proc advanced-set!-continue/proc)
(let ([proc
(lambda (continuing?)
(lambda (stx)
(ensure-expression
stx
(lambda ()
(syntax-case stx ()
[(_ id expr ...)
(identifier? (syntax id))
(let ([exprs (syntax->list (syntax (expr ...)))])
(when ((with-handlers ([exn:fail? (lambda (exn) (lambda () #t))])
(let ([binding (syntax-local-value (syntax id))])
(if (set!-transformer? binding)
(lambda () #f) (lambda ()
(teach-syntax-error
'set!
stx
(syntax id)
"expected a variable after set!, but found a ~a" (syntax-e #'id)))))))
(when (eq? 'lexical (identifier-binding (syntax id)))
(teach-syntax-error
'set!
stx
(syntax id)
"expected a mutable variable after set!, but found a variable that cannot be modified: ~a"
(syntax-e #'id))))
(when continuing?
(let ([binding (identifier-binding #'id)])
(cond
[(and (not binding)
(syntax-source-module #'id))
(teach-syntax-error
#f
#'id
#f
"this variable is not defined")]
[(and (list? binding)
(or (not (module-path-index? (car binding)))
(let-values ([(path rel) (module-path-index-split (car binding))])
path)))
(teach-syntax-error
'set!
#'id
#f
"expected a mutable variable after set!, but found a variable that cannot be modified: ~a"
(syntax-e #'id))])))
(check-single-expression 'set!
"for the new value"
stx
exprs
null)
(if continuing?
(stepper-syntax-property
(quasisyntax/loc stx (begin #,(datum->syntax #'here `(set! ,#'id ,@(syntax->list #'(expr ...))) stx) set!-result))
'stepper-skipto
(append skipto/cdr
skipto/first))
(stepper-ignore-checker (quasisyntax/loc stx (#%app values #,(advanced-set!-continue/proc
(syntax/loc stx (_ id expr ...))))))))]
[(_ id . __)
(teach-syntax-error
'set!
stx
(syntax id)
"expected a variable after set!, but found ~a"
(something-else (syntax id)))]
[(_)
(teach-syntax-error
'set!
stx
#f
"expected a variable after set!, but nothing's there")]
[_else (bad-use-error 'set! stx)])))))])
(values (proc #f)
(proc #t))))
(define (advanced-case/proc stx)
(ensure-expression
stx
(lambda ()
(syntax-case stx ()
[(_)
(teach-syntax-error
'case
stx
#f
"expected an expression after case, but nothing's there")]
[(_ expr)
(teach-syntax-error
'case
stx
#f
"expected a clause with at least one choice (in parentheses) and an answer after the expression, but nothing's there")]
[(_ v-expr clause ...)
(let ([clauses (syntax->list (syntax (clause ...)))])
(for-each
(lambda (clause)
(syntax-case clause (beginner-else)
[(beginner-else answer ...)
(let ([lpos (memq clause clauses)])
(when (not (null? (cdr lpos)))
(teach-syntax-error
'case
stx
clause
"found an else clause that isn't the last clause ~
in its case expression"))
(let ([answers (syntax->list (syntax (answer ...)))])
(check-single-expression 'case
"for the answer in the case clause"
clause
answers
null)))]
[(choices answer ...)
(let ([choices (syntax choices)]
[answers (syntax->list (syntax (answer ...)))])
(syntax-case choices ()
[(elem ...)
(let ([elems (syntax->list (syntax (elem ...)))])
(for-each (lambda (e)
(let ([v (syntax-e e)])
(unless (or (number? v)
(symbol? v))
(teach-syntax-error
'case
stx
e
"expected a symbol (without its quote) or a number as a choice, but found ~a"
(something-else e)))))
elems))]
[_else (teach-syntax-error
'case
stx
choices
"expected at least one choice (in parentheses), but found ~a"
(something-else choices))])
(when (stx-null? choices)
(teach-syntax-error
'case
stx
choices
"expected a symbol (without its quote) or a number as a choice, but nothing's there"))
(check-single-expression 'case
"for the answer in the case clause"
clause
answers
null))]
[()
(teach-syntax-error
'case
stx
clause
"expected a clause with at least one choice (in parentheses) and an answer, but found an empty part")]
[_else
(teach-syntax-error
'case
stx
clause
"expected a clause with at least one choice (in parentheses) and an answer, but found ~a"
(something-else clause))]))
clauses)
(let ([clauses (let loop ([clauses clauses])
(cond
[(null? clauses)
(list
(syntax/loc stx
[else (error 'cases "the expression matched none of the choices")]))]
[(syntax-case (car clauses) (beginner-else)
[(beginner-else . _) (syntax/loc (car clauses) (else . _))]
[_else #f])
=>
(lambda (x) (cons x (cdr clauses)))]
[else (cons (car clauses) (loop (cdr clauses)))]))])
(with-syntax ([clauses clauses])
(syntax/loc stx (case v-expr . clauses)))))]
[_else (bad-use-error 'case stx)]))))