#lang s-exp "../lang/base.rkt" ;; Like the big whalesong language, but with additional ASL restrictions. (current-print-mode "constructor") (require (for-syntax racket/base syntax/stx racket/match)) (require "cs019-pre-base.rkt") (provide (rename-out [cs019-lambda lambda] [cs019-define define] [cs019-when when] [cs019-unless unless] [cs019-case case] [cs019-local local] [cs019-dots ..] [cs019-dots ...] [cs019-dots ....] [cs019-dots .....] [cs019-dots ......] )) (define-syntax λ (make-rename-transformer #'cs019-lambda)) (require "private/sigs-patched.rkt") (provide [all-from-out "private/sigs-patched.rkt"]) (provide Image$) (define Image$ (Sig: image?)) (require "../lang/posn.rkt") (provide [all-from-out "../lang/posn.rkt"]) (define Posn$ (Sig: posn?)) (provide Posn$) (require (prefix-in whalesong: "../lang/whalesong.rkt")) (provide (except-out (filtered-out (lambda (name) (match name [(regexp #rx"^whalesong:(.+)$" (list _ real-name)) real-name] [else #f])) (except-out (all-from-out "../lang/whalesong.rkt") whalesong:if whalesong:cond whalesong:case whalesong:member whalesong:memq whalesong:define whalesong:lambda whalesong:unless whalesong:when whalesong:local whalesong:first whalesong:rest whalesong:second whalesong:third whalesong:fourth whalesong:fifth whalesong:sixth whalesong:seventh whalesong:eighth whalesong:ninth ))) string-ith replicate int->string string->int explode implode string-numeric? string-alphabetic? string-whitespace? string-upper-case? string-lower-case?) (require "lists.rkt") (provide (all-from-out "lists.rkt")) (require "../image.rkt") (provide (all-from-out "../image.rkt")) (require "../web-world.rkt") (provide (all-from-out "../web-world.rkt")) (define View$ (Sig: view?)) (provide View$) (define Event$ (Sig: event?)) (provide Event$) (require "../resource.rkt") (provide (all-from-out "../resource.rkt")) (define Resource$ (Sig: resource?)) (provide Resource$) (define-for-syntax (local-expand-for-error stx ctx stops) ;; This function should only be called in an 'expression ;; context. In case we mess up, avoid bogus error messages. (when (memq (syntax-local-context) '(expression)) (local-expand stx ctx stops))) ;; Raise a syntax error: (define-for-syntax (teach-syntax-error form stx detail msg . args) (let ([form (if (eq? form '|function call|) form #f)] ; extract name from stx [msg (apply format msg args)]) (if detail (raise-syntax-error form msg stx detail) (raise-syntax-error form msg stx)))) (define-for-syntax (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)))) ;; The syntax error when a form's name doesn't follow a "(" (define-for-syntax (bad-use-error name stx) (teach-syntax-error name stx #f "found a use of `~a' that does not follow an open parenthesis" name)) (define-for-syntax (something-else v) (let ([v (syntax-e v)]) (cond [(number? v) "a number"] [(string? v) "a string"] [else "something else"]))) ;; verify-boolean is inserted to check for boolean results: (define-for-syntax (verify-boolean b where) (with-syntax ([b b] [where where]) (quasisyntax/loc #'b (let ([bv b]) (if (or (eq? bv #t) (eq? bv #f)) bv #,(syntax/loc #'b (whalesong:#%app raise (make-exn:fail:contract (format "~a: question result is not true or false: ~e" 'where bv) (current-continuation-marks))))))))) (define-syntax (-cond stx) (syntax-case stx () [(_) (teach-syntax-error 'cond stx #f "expected a question--answer clause after `cond', but nothing's there")] [(_ clause ...) (let* ([clauses (syntax->list (syntax (clause ...)))] [check-preceding-exprs (lambda (stop-before) (let/ec k (for-each (lambda (clause) (if (eq? clause stop-before) (k #t) (syntax-case clause () [(question answer) (begin (unless (and (identifier? (syntax question)) (free-identifier=? (syntax question) #'else)) (local-expand-for-error (syntax question) 'expression null)) (local-expand-for-error (syntax answer) 'expression null))]))) clauses)))]) (let ([checked-clauses (map (lambda (clause) (syntax-case clause (else) [(else answer) (let ([lpos (memq clause clauses)]) (when (not (null? (cdr lpos))) (teach-syntax-error 'cond stx clause "found an `else' clause that isn't the last clause ~ in its `cond' expression")) (with-syntax ([new-test (syntax #t) ]) (syntax/loc clause (new-test answer))))] [(question answer) (with-syntax ([verified (verify-boolean #'question 'cond)]) (syntax/loc clause (verified answer)))] [() (check-preceding-exprs clause) (teach-syntax-error 'cond stx clause "expected a question--answer clause, but found an empty clause")] [(question?) (check-preceding-exprs clause) (teach-syntax-error 'cond stx clause "expected a clause with a question and answer, but found a clause with only one part")] [(question? answer? ...) (check-preceding-exprs clause) (let ([parts (syntax->list clause)]) ;; to ensure the illusion of left-to-right checking, make sure ;; the question and first answer (if any) are ok: (unless (and (identifier? (car parts)) (free-identifier=? (car parts) #'else)) (local-expand-for-error (car parts) 'expression null)) (unless (null? (cdr parts)) (local-expand-for-error (cadr parts) 'expression null)) ;; question and answer (if any) are ok, raise a count-based exception: (teach-syntax-error* 'cond stx parts "expected a clause with one question and one answer, but found a clause with ~a parts" (length parts)))] [_else (teach-syntax-error 'cond stx clause "expected a question--answer clause, but found ~a" (something-else clause))])) clauses)]) ;; Add `else' clause for error (always): (let ([clauses (append checked-clauses (list (with-syntax ([error-call (syntax/loc stx (whalesong:#%app raise (make-exn:fail:contract "cond: all question results were false" (current-continuation-marks))))]) (syntax [else error-call]))))]) (with-syntax ([clauses clauses]) (syntax/loc stx (cond . clauses))))))] [_else (bad-use-error 'cond stx)])) (provide (rename-out [-cond cond])) (define-syntax (-if stx) (syntax-case stx () [(_ test then else) (with-syntax ([new-test (verify-boolean #'test 'if)]) (syntax/loc stx (if new-test then else)))] [(_ . rest) (let ([n (length (syntax->list (syntax rest)))]) (teach-syntax-error 'if stx #f "expected one question expression and two answer expressions, but found ~a expression~a" (if (zero? n) "no" n) (if (= n 1) "" "s")))] [_else (bad-use-error 'if stx)])) (provide (rename-out [-if if])) (define 1-LET "1-letter string") (define 1-LETTER (format "~a" 1-LET)) (define 1-LETTER* (format "list of ~as" 1-LET)) (define NAT "natural number") ;; Symbol Any -> Boolean ;; is this a 1-letter string? (define (1-letter? tag s) (unless (string? s) (err tag "expected a ~a, but received a string: ~e" 1-LETTER s)) (= (string-length s) 1)) ;; Symbol Any -> Boolean ;; is s a list of 1-letter strings ;; effect: not a list, not a list of strings (define (1-letter*? tag s) (unless (list? s) (err tag "expected a ~a, but received: ~e" 1-LETTER* s)) (for-each (lambda (c) (unless (string? c) (err tag "expected a ~a, but received: ~e" 1-LETTER* c))) s) (andmap (compose (lambda (x) (= x 1)) string-length) s)) (define (err tag msg-format . args) (raise (make-exn:fail:contract (apply format (string-append (symbol->string tag) ": " msg-format) args) (current-continuation-marks)))) (define (a-or-an after) (if (member (string-ref (format "~a" after) 0) '(#\a #\e #\i #\o #\u)) "an" "a")) (define cerr (case-lambda [(tag check-result format-msg actual) (unless check-result (err tag (string-append "expected " (a-or-an format-msg) " " format-msg ", but received ~e") actual))] [(tag check-result format-msg actual snd) (unless check-result (err tag (string-append "expected " (a-or-an format-msg) " " format-msg " for the ~a argument, but received ~e") snd actual))])) (define string-ith (lambda (s n) (define f "exact integer in [0, length of the given string]") (cerr 'string-ith (string? s) "string" s "first") (cerr 'string-ith (and (number? n) (integer? n) (>= n 0)) NAT n "second") (let ([l (string-length s)]) (cerr 'string-ith (< n l) f n "second")) (string (string-ref s n)))) (define replicate (lambda (n s1) (cerr 'replicate (and (number? n) (exact-integer? n) (>= n 0)) NAT n) (cerr 'replicate (string? s1) "string" s1) (apply string-append (build-list n (lambda (i) s1))))) (define int->string (lambda (i) (cerr 'int->string (and (exact-integer? i) (or (<= 0 i 55295) (<= 57344 i 1114111))) "exact integer in [0,55295] or [57344 1114111]" i) (string (integer->char i)))) (define string->int (lambda (s) (cerr 'string->int (1-letter? 'string->int s) 1-LETTER s) (char->integer (string-ref s 0)))) (define explode (lambda (s) (cerr 'explode (string? s) "string" s) (map string (string->list s)))) (define implode (lambda (los) (cerr 'implode (1-letter*? 'implode los) 1-LETTER* los) (apply string-append los))) (define string-numeric? ;; is this: (number? (string->number s)) enough? (lambda (s1) (cerr 'string-numeric? (string? s1) "string" s1) (andmap char-numeric? (string->list s1)))) (define string-alphabetic? (lambda (s1) (cerr 'string-alphabetic? (string? s1) "string" s1) (andmap char-alphabetic? (string->list s1)))) (define string-whitespace? (lambda (s) (cerr 'string-upper-case? (string? s) "string" s) (andmap char-whitespace? (string->list s)))) (define string-upper-case? (lambda (s) (cerr 'string-upper-case? (string? s) "string" s) (andmap char-upper-case? (string->list s)))) (define string-lower-case? (lambda (s) (cerr 'string-lower-case? (string? s) "string" s) (andmap char-lower-case? (string->list s)))) ;; ASL's member returns booleans. (define (-member x L) (cond [(eq? (member x L) #f) #f] [else #t])) ;; as does memq (define (-memq x L) (cond [(eq? (memq x L) #f) #f] [else #t])) (provide (rename-out [-member member] [-member member?] [-memq memq]))