1077 lines
33 KiB
Racket
1077 lines
33 KiB
Racket
#lang scheme/base
|
|
|
|
(require syntax/docprovide)
|
|
|
|
(require test-engine/scheme-tests
|
|
(lib "test-info.scm" "test-engine")
|
|
scheme/class)
|
|
|
|
(require deinprogramm/contract/module-begin
|
|
deinprogramm/contract/contract
|
|
deinprogramm/contract/contract-test-engine
|
|
(except-in deinprogramm/contract/contract-syntax property))
|
|
|
|
(require (for-syntax scheme/base)
|
|
(for-syntax stepper/private/shared))
|
|
|
|
(require deinprogramm/define-record-procedures)
|
|
|
|
(require (only-in lang/private/teachprims beginner-equal? beginner-equal~?))
|
|
|
|
(require (for-syntax deinprogramm/syntax-checkers))
|
|
|
|
(require (rename-in deinprogramm/quickcheck/quickcheck
|
|
(property quickcheck:property)))
|
|
|
|
(provide provide lib planet rename-out require #%datum #%module-begin #%top-interaction) ; so we can use this as a language
|
|
|
|
(provide cons) ; hack, for the stepper
|
|
|
|
(provide (all-from-out deinprogramm/define-record-procedures))
|
|
(provide (all-from-out test-engine/scheme-tests))
|
|
(provide contract define-contract :
|
|
-> mixed one-of predicate combined)
|
|
|
|
(provide number real rational integer natural
|
|
boolean true false
|
|
string symbol
|
|
empty-list
|
|
chocolate-cookie
|
|
unspecific
|
|
property)
|
|
|
|
(define-syntax provide/rename
|
|
(syntax-rules ()
|
|
((provide/rename (here there) ...)
|
|
(begin
|
|
(provide (rename-out (here there))) ...))))
|
|
|
|
(provide/rename
|
|
(DMdA-define define)
|
|
(DMdA-let let)
|
|
(DMdA-let* let*)
|
|
(DMdA-letrec letrec)
|
|
(DMdA-lambda lambda)
|
|
(DMdA-cond cond)
|
|
(DMdA-if if)
|
|
(DMdA-else else)
|
|
(DMdA-begin begin)
|
|
(DMdA-and and)
|
|
(DMdA-or or)
|
|
(DMdA-dots ..)
|
|
(DMdA-dots ...)
|
|
(DMdA-dots ....)
|
|
(DMdA-dots .....)
|
|
(DMdA-dots ......)
|
|
(DMdA-app #%app)
|
|
(DMdA-top #%top)
|
|
(DMdA-set! set!)
|
|
(module-begin DMdA-module-begin))
|
|
|
|
(provide DMdA-advanced-lambda
|
|
DMdA-advanced-define)
|
|
|
|
(provide for-all ==>
|
|
check-property
|
|
expect expect-within expect-member-of expect-range)
|
|
|
|
(provide quote)
|
|
|
|
(provide-and-document
|
|
procedures
|
|
("Zahlen"
|
|
(number? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine Zahl ist")
|
|
|
|
(= (number number number ... -> boolean)
|
|
"Zahlen auf Gleichheit testen")
|
|
(< (real real real ... -> boolean)
|
|
"Zahlen auf kleiner-als testen")
|
|
(> (real real real ... -> boolean)
|
|
"Zahlen auf größer-als testen")
|
|
(<= (real real real ... -> boolean)
|
|
"Zahlen auf kleiner-gleich testen")
|
|
(>= (real real real ... -> boolean)
|
|
"Zahlen auf größer-gleich testen")
|
|
|
|
(+ (number number number ... -> number)
|
|
"Summe berechnen")
|
|
(- (number number ... -> number)
|
|
"bei mehr als einem Argument Differenz zwischen der ersten und der Summe aller weiteren Argumente berechnen; bei einem Argument Zahl negieren")
|
|
(* (number number number ... -> number)
|
|
"Produkt berechnen")
|
|
(/ (number number number ... -> number)
|
|
"das erste Argument durch das Produkt aller weiteren Argumente berechnen")
|
|
(max (real real ... -> real)
|
|
"Maximum berechnen")
|
|
(min (real real ... -> real)
|
|
"Minimum berechnen")
|
|
(quotient (integer integer -> integer)
|
|
"ganzzahlig dividieren")
|
|
(remainder (integer integer -> integer)
|
|
"Divisionsrest berechnen")
|
|
(modulo (integer integer -> integer)
|
|
"Divisionsmodulo berechnen")
|
|
(sqrt (number -> number)
|
|
"Quadratwurzel berechnen")
|
|
(expt (number number -> number)
|
|
"Potenz berechnen (erstes Argument hoch zweites Argument)")
|
|
(abs (real -> real)
|
|
"Absolutwert berechnen")
|
|
|
|
;; fancy numeric
|
|
(exp (number -> number)
|
|
"Exponentialfunktion berechnen (e hoch Argument)")
|
|
(log (number -> number)
|
|
"natürlichen Logarithmus (Basis e) berechnen")
|
|
|
|
;; trigonometry
|
|
(sin (number -> number)
|
|
"Sinus berechnen (Argument in Radian)")
|
|
(cos (number -> number)
|
|
"Cosinus berechnen (Argument in Radian)")
|
|
(tan (number -> number)
|
|
"Tangens berechnen (Argument in Radian)")
|
|
(asin (number -> number)
|
|
"Arcussinus berechnen (in Radian)")
|
|
(acos (number -> number)
|
|
"Arcuscosinus berechnen (in Radian)")
|
|
(atan (number -> number)
|
|
"Arcustangens berechnen (in Radian)")
|
|
|
|
(exact? (number -> boolean)
|
|
"feststellen, ob eine Zahl exakt ist")
|
|
|
|
(integer? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine ganze Zahl ist")
|
|
(natural? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine natürliche Zahl (inkl. 0) ist")
|
|
|
|
(zero? (number -> boolean)
|
|
"feststellen, ob eine Zahl Null ist")
|
|
(positive? (number -> boolean)
|
|
"feststellen, ob eine Zahl positiv ist")
|
|
(negative? (number -> boolean)
|
|
"feststellen, ob eine Zahl negativ ist")
|
|
(odd? (integer -> boolean)
|
|
"feststellen, ob eine Zahl ungerade ist")
|
|
(even? (integer -> boolean)
|
|
"feststellen, ob eine Zahl gerade ist")
|
|
|
|
(lcm (natural natural ... -> natural)
|
|
"kleinstes gemeinsames Vielfaches berechnen")
|
|
|
|
(gcd (natural natural ... -> natural)
|
|
"größten gemeinsamen Teiler berechnen")
|
|
|
|
(rational? (%a -> boolean)
|
|
"feststellen, ob eine Zahl rational ist")
|
|
|
|
(numerator (rational -> integer)
|
|
"Zähler eines Bruchs berechnen")
|
|
|
|
(denominator (rational -> natural)
|
|
"Nenner eines Bruchs berechnen")
|
|
|
|
(inexact? (number -> boolean)
|
|
"feststellen, ob eine Zahl inexakt ist")
|
|
|
|
(real? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine reelle Zahl ist")
|
|
|
|
(floor (real -> integer)
|
|
"nächste ganze Zahl unterhalb einer rellen Zahlen berechnen")
|
|
|
|
(ceiling (real -> integer)
|
|
"nächste ganze Zahl oberhalb einer rellen Zahlen berechnen")
|
|
|
|
(round (real -> integer)
|
|
"relle Zahl auf eine ganze Zahl runden")
|
|
|
|
(complex? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine komplexe Zahl ist")
|
|
|
|
(make-polar (real real -> number)
|
|
"komplexe Zahl aus Abstand zum Ursprung und Winkel berechnen")
|
|
|
|
(real-part (number -> real)
|
|
"reellen Anteil einer komplexen Zahl extrahieren")
|
|
|
|
(imag-part (number -> real)
|
|
"imaginären Anteil einer komplexen Zahl extrahieren")
|
|
|
|
(magnitude (number -> real)
|
|
"Abstand zum Ursprung einer komplexen Zahl berechnen")
|
|
|
|
(angle (number -> real)
|
|
"Winkel einer komplexen Zahl berechnen")
|
|
|
|
(exact->inexact (number -> number)
|
|
"eine Zahl durch eine inexakte Zahl annähern")
|
|
|
|
(inexact->exact (number -> number)
|
|
"eine Zahl durch eine exakte Zahl annähern")
|
|
|
|
;; "Odds and ends"
|
|
|
|
(number->string (number -> string)
|
|
"Zahl in Zeichenkette umwandeln")
|
|
|
|
(string->number (string -> (mixed number (one-of #f)))
|
|
"Zeichenkette in Zahl umwandeln, falls möglich")
|
|
|
|
(random (natural -> natural)
|
|
"eine natürliche Zufallszahl berechnen, die kleiner als das Argument ist")
|
|
|
|
(current-seconds (-> natural)
|
|
"aktuelle Zeit in Sekunden seit einem unspezifizierten Startzeitpunkt berechnen"))
|
|
|
|
("boolesche Werte"
|
|
(boolean? (%a -> boolean)
|
|
"feststellen, ob ein Wert ein boolescher Wert ist")
|
|
|
|
((DMdA-not not) (boolean -> boolean)
|
|
"booleschen Wert negieren")
|
|
|
|
(boolean=? (boolean boolean -> boolean)
|
|
"Booleans auf Gleichheit testen")
|
|
|
|
(true? (%a -> boolean)
|
|
"feststellen, ob ein Wert #t ist")
|
|
(false? (%a -> boolean)
|
|
"feststellen, ob ein Wert #f ist"))
|
|
|
|
("Listen"
|
|
(empty list "die leere Liste")
|
|
(make-pair (%a (list %a) -> (list %a))
|
|
"erzeuge ein Paar aus Element und Liste")
|
|
(pair? (%a -> boolean)
|
|
"feststellen, ob ein Wert ein Paar ist")
|
|
(empty? (%a -> boolean)
|
|
"feststellen, ob ein Wert die leere Liste ist")
|
|
|
|
(first ((list %a) -> %a)
|
|
"erstes Element eines Paars extrahieren")
|
|
(rest ((list %a) -> (list %a))
|
|
"Rest eines Paars extrahieren")
|
|
|
|
(list (%a ... -> (list %a))
|
|
"Liste aus den Argumenten konstruieren")
|
|
|
|
(length ((list %a) -> natural)
|
|
"Länge einer Liste berechnen")
|
|
|
|
(fold ((%b (%a %b -> %b) (list %a) -> %b)
|
|
"Liste einfalten."))
|
|
|
|
((DMdA-append append) ((list %a) ... -> (list %a))
|
|
"mehrere Listen aneinanderhängen")
|
|
|
|
(list-ref ((list %a) natural -> %a)
|
|
"das Listenelement an der gegebenen Position extrahieren")
|
|
|
|
(reverse ((list %a) -> (list %a))
|
|
"Liste in umgekehrte Reihenfolge bringen"))
|
|
|
|
("Schokokekse"
|
|
(make-chocolate-cookie (number number -> chocolate-cookie)
|
|
"Schokokeks aus Schoko- und Keks-Anteil konstruieren")
|
|
(chocolate-cookie? (%a -> boolean)
|
|
"feststellen, ob ein Wert ein Schokokeks ist")
|
|
(chocolate-cookie-chocolate (chocolate-cookie -> number)
|
|
"Schoko-Anteil eines Schokokekses extrahieren")
|
|
(chocolate-cookie-cookie (chocolate-cookie -> number)
|
|
"Keks-Anteil eines Schokokekses extrahieren"))
|
|
|
|
;; #### Zeichen sollten noch dazu, Vektoren wahrscheinlich auch
|
|
|
|
("Zeichenketten"
|
|
(string? (%a -> boolean)
|
|
"feststellen, ob ein Wert eine Zeichenkette ist")
|
|
|
|
(string=? (string string string ... -> boolean)
|
|
"Zeichenketten auf Gleichheit testen")
|
|
(string<? (string string string ... -> boolean)
|
|
"Zeichenketten lexikografisch auf kleiner-als testen")
|
|
(string>? (string string string ... -> boolean)
|
|
"Zeichenketten lexikografisch auf größer-als testen")
|
|
(string<=? (string string string ... -> boolean)
|
|
"Zeichenketten lexikografisch auf kleiner-gleich testen")
|
|
(string>=? (string string string ... -> boolean)
|
|
"Zeichenketten lexikografisch auf größer-gleich testen")
|
|
|
|
(string-append (string string ... -> string)
|
|
"Hängt Zeichenketten zu einer Zeichenkette zusammen")
|
|
|
|
(strings-list->string ((list string) -> string)
|
|
"Eine Liste von Zeichenketten in eine Zeichenkette umwandeln")
|
|
|
|
(string->strings-list (string -> (list string))
|
|
"Eine Zeichenkette in eine Liste von Zeichenketten mit einzelnen Zeichen umwandeln")
|
|
|
|
(string-length (string -> natural)
|
|
"Liefert Länge einer Zeichenkette"))
|
|
|
|
("Symbole"
|
|
(symbol? (%a -> boolean)
|
|
"feststellen, ob ein Wert ein Symbol ist")
|
|
(symbol->string (symbol -> string)
|
|
"Symbol in Zeichenkette umwandeln")
|
|
(string->symbol (string -> symbol)
|
|
"Zeichenkette in Symbol umwandeln"))
|
|
|
|
("Verschiedenes"
|
|
(equal? (%a %b -> boolean)
|
|
"zwei Werte auf Gleichheit testen")
|
|
(eq? (%a %b -> boolean)
|
|
"zwei Werte auf Selbheit testen")
|
|
((DMdA-write-string write-string) (string -> unspecific)
|
|
"Zeichenkette in REPL ausgeben")
|
|
(write-newline (-> unspecific)
|
|
"Zeilenumbruch ausgeben")
|
|
(violation (string -> unspecific)
|
|
"Programmm mit Fehlermeldung abbrechen")
|
|
|
|
(map ((%a -> %b) (list %a) -> (list %b))
|
|
"Prozedur auf alle Elemente einer Liste anwenden, Liste der Resultate berechnen")
|
|
(for-each ((%a -> %b) (list %a) -> unspecific)
|
|
"Prozedur von vorn nach hinten auf alle Elemente einer Liste anwenden")
|
|
(apply (procedure (list %a) -> %b)
|
|
"Prozedur auf Liste ihrer Argumente anwenden")))
|
|
|
|
(define (make-pair f r)
|
|
(when (and (not (null? r))
|
|
(not (pair? r)))
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "Zweites Argument zu make-pair ist keine Liste, sondern ~e" r))
|
|
(current-continuation-marks))))
|
|
(cons f r))
|
|
|
|
(define (first l)
|
|
(when (not (pair? l))
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "Argument zu first kein Paar, sondern ~e" l))
|
|
(current-continuation-marks))))
|
|
(car l))
|
|
|
|
(define (rest l)
|
|
(when (not (pair? l))
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "Argument zu rest kein Paar, sondern ~e" l))
|
|
(current-continuation-marks))))
|
|
(cdr l))
|
|
|
|
(define empty '())
|
|
|
|
(define (empty? obj)
|
|
(null? obj))
|
|
|
|
(define (DMdA-append . args)
|
|
(let loop ((args args)
|
|
(seen-rev '()))
|
|
(when (not (null? args))
|
|
(let ((arg (car args)))
|
|
(when (and (not (null? arg))
|
|
(not (pair? arg)))
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "Argument zu append keine Liste, sondern ~e; restliche Argumente:~a"
|
|
arg
|
|
(apply string-append
|
|
(map (lambda (arg)
|
|
(format " ~e" arg))
|
|
(append (reverse seen-rev)
|
|
(list '<...>)
|
|
(cdr args))))))
|
|
(current-continuation-marks))))
|
|
(loop (cdr args)
|
|
(cons arg seen-rev)))))
|
|
|
|
(apply append args))
|
|
|
|
(define fold
|
|
(lambda (unit combine lis)
|
|
(cond
|
|
((empty? lis) unit)
|
|
((pair? lis)
|
|
(combine (first lis)
|
|
(fold unit combine (rest lis)))))))
|
|
|
|
;; This is copied from collects/lang/private/beginner-funs.ss
|
|
;; Test-suite support (require is really an effect
|
|
;; to make sure that it's loaded)
|
|
(require "test-suite.ss")
|
|
|
|
(define-for-syntax (binding-in-this-module? b)
|
|
(and (list? b)
|
|
(module-path-index? (car b))
|
|
(let-values (((path base) (module-path-index-split (car b))))
|
|
(and (not path) (not base)))))
|
|
|
|
(define-for-syntax (transform-DMdA-define stx mutable?)
|
|
(unless (memq (syntax-local-context) '(module top-level))
|
|
(raise-syntax-error
|
|
#f "Define muss ganz außen stehen" stx))
|
|
(syntax-case stx ()
|
|
((DMdA-define)
|
|
(raise-syntax-error
|
|
#f "Definition ohne Operanden" stx))
|
|
((DMdA-define v)
|
|
(raise-syntax-error
|
|
#f "Define erwartet zwei Operanden, nicht einen" stx))
|
|
((DMdA-define var expr)
|
|
(begin
|
|
(check-for-id!
|
|
(syntax var)
|
|
"Der erste Operand der Definition ist kein Bezeichner")
|
|
|
|
(let ((binding (identifier-binding (syntax var))))
|
|
(when binding
|
|
(if (binding-in-this-module? binding)
|
|
(raise-syntax-error
|
|
#f
|
|
"Zweite Definition für denselben Namen"
|
|
stx)
|
|
(raise-syntax-error
|
|
#f
|
|
"Dieser Name gehört einer eingebauten Prozedur und kann nicht erneut definiert werden" (syntax var)))))
|
|
(if mutable?
|
|
(with-syntax
|
|
((dummy-def (stepper-syntax-property
|
|
(syntax (define dummy (lambda () (set! var 'dummy))))
|
|
'stepper-skip-completely
|
|
#t)))
|
|
(syntax/loc stx
|
|
(begin
|
|
dummy-def
|
|
(define var expr))))
|
|
(syntax/loc stx (define var expr)))))
|
|
((DMdA-define v e1 e2 e3 ...)
|
|
(raise-syntax-error
|
|
#f "Definition mit mehr als zwei Operanden" stx))))
|
|
|
|
(define-syntax (DMdA-define stx)
|
|
(transform-DMdA-define stx #f))
|
|
|
|
(define-syntax (DMdA-advanced-define stx)
|
|
(transform-DMdA-define stx #t))
|
|
|
|
(define-syntax (DMdA-let stx)
|
|
(syntax-case stx ()
|
|
((DMdA-let () body)
|
|
(syntax/loc stx body))
|
|
((DMdA-let ((var expr) ...) body)
|
|
(begin
|
|
(check-for-id-list!
|
|
(syntax->list (syntax (var ...)))
|
|
"Kein Bezeichner in Let-Bindung")
|
|
(syntax/loc stx ((lambda (var ...) body) expr ...))))
|
|
((DMdA-let ((var expr) ...) body1 body2 ...)
|
|
(raise-syntax-error
|
|
#f "Let-Ausdruck hat mehr als einen Ausdruck als Rumpf" stx))
|
|
((DMdA-let expr ...)
|
|
(raise-syntax-error
|
|
#f "Let-Ausdruck erwartet eine Liste von Bindungen (Paare aus Name und Ausdruck) und einen Rumpf" stx))))
|
|
|
|
(define-syntax (DMdA-let* stx)
|
|
(syntax-case stx ()
|
|
((DMdA-let* () body)
|
|
(syntax/loc stx body))
|
|
((DMdA-let* ((var1 expr1) (var2 expr2) ...) body)
|
|
(begin
|
|
(check-for-id!
|
|
(syntax var1)
|
|
"Kein Bezeichner in Let*-Bindung")
|
|
(syntax/loc stx ((lambda (var1)
|
|
(DMdA-let* ((var2 expr2) ...) body))
|
|
expr1))))
|
|
((DMdA-let* ((var expr) ...) body1 body2 ...)
|
|
(raise-syntax-error
|
|
#f "Let*-Ausdruck hat mehr als einen Ausdruck als Rumpf" stx))
|
|
((DMdA-let* expr ...)
|
|
(raise-syntax-error
|
|
#f "Let*-Ausdruck erwartet eine Liste von Bindungen (Paare aus Name und Ausdruck) und einen Rumpf" stx))))
|
|
|
|
(define-syntax (DMdA-letrec stx)
|
|
(syntax-case stx ()
|
|
((DMdA-letrec ((var expr) ...) body)
|
|
(begin
|
|
(check-for-id-list!
|
|
(syntax->list (syntax (var ...)))
|
|
"Kein Bezeichner in letrec-Bindung")
|
|
(syntax/loc stx (letrec ((var expr) ...) body))))
|
|
((DMdA-letrec ((var expr) ...) body1 body2 ...)
|
|
(raise-syntax-error
|
|
#f "Letrec hat mehr als einen Ausdruck als Rumpf" stx))))
|
|
|
|
(define-syntax (DMdA-lambda stx)
|
|
(syntax-case stx ()
|
|
((DMdA-lambda (var ...) body)
|
|
(begin
|
|
(check-for-id-list!
|
|
(syntax->list (syntax (var ...)))
|
|
"Kein Bezeichner als Parameter der Lambda-Abstraktion")
|
|
(syntax/loc stx (lambda (var ...) body))))
|
|
((DMdA-lambda (var ...) body1 body2 ...)
|
|
(raise-syntax-error
|
|
#f "Lambda-Abstraktion hat mehr als einen Ausdruck als Rumpf" stx))
|
|
((DMdA-lambda var body ...)
|
|
(identifier? (syntax var))
|
|
(raise-syntax-error
|
|
#f "Um die Parameter einer Lambda-Abstraktion gehören Klammern" (syntax var)))
|
|
((DMdA-lambda var ...)
|
|
(raise-syntax-error
|
|
#f "Fehlerhafte Lambda-Abstraktion" stx))))
|
|
|
|
(define-syntax (DMdA-advanced-lambda stx)
|
|
(syntax-case stx ()
|
|
((DMdA-lambda (var ...) body)
|
|
(begin
|
|
(check-for-id-list!
|
|
(syntax->list (syntax (var ...)))
|
|
"Kein Bezeichner als Parameter der Lambda-Abstraktion")
|
|
(syntax/loc stx (lambda (var ...) body))))
|
|
((DMdA-lambda (var ... . rest) body)
|
|
(begin
|
|
(check-for-id-list!
|
|
(syntax->list (syntax (var ...)))
|
|
"Kein Bezeichner als Parameter der Lambda-Abstraktion")
|
|
(check-for-id!
|
|
(syntax rest)
|
|
"Kein Bezeichner als Restlisten-Parameter der Lambda-Abstraktion")
|
|
(syntax/loc stx (lambda (var ... . rest) body))))
|
|
((DMdA-lambda (var ...) body1 body2 ...)
|
|
(raise-syntax-error
|
|
#f "Lambda-Abstraktion hat mehr als einen Ausdruck als Rumpf" stx))
|
|
((DMdA-lambda var ...)
|
|
(raise-syntax-error
|
|
#f "Fehlerhafte Lambda-Abstraktion" stx))))
|
|
|
|
(define-syntax (DMdA-begin stx)
|
|
(syntax-case stx ()
|
|
((DMdA-begin)
|
|
(raise-syntax-error
|
|
#f "Begin-Ausdruck braucht mindestens einen Operanden" stx))
|
|
((DMdA-begin expr1 expr2 ...)
|
|
(syntax/loc stx (begin expr1 expr2 ...)))))
|
|
|
|
(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)))
|
|
|
|
(define-for-syntax (ensure-expression stx k)
|
|
(if (memq (syntax-local-context) '(expression))
|
|
(k)
|
|
(stepper-syntax-property #`(begin0 #,stx) 'stepper-skipto skipto/second)))
|
|
|
|
;; A consistent pattern for stepper-skipto:
|
|
(define-for-syntax (stepper-ignore-checker stx)
|
|
(stepper-syntax-property stx 'stepper-skipto '(syntax-e cdr syntax-e cdr car)))
|
|
|
|
;; 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))))
|
|
|
|
;; 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
|
|
"`~a' wurde an einer Stelle gefunden, die keiner offenen Klammer folgt"
|
|
name))
|
|
|
|
;; Use for messages "expected ..., found <something else>"
|
|
(define-for-syntax (something-else v)
|
|
(let ([v (syntax-e v)])
|
|
(cond
|
|
[(number? v) "eine Zahl"]
|
|
[(string? v) "eine Zeichenkette"]
|
|
[else "etwas anderes"])))
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; cond
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-syntax (DMdA-cond stx)
|
|
(ensure-expression
|
|
stx
|
|
(lambda ()
|
|
(syntax-case stx ()
|
|
[(_)
|
|
(teach-syntax-error
|
|
'cond
|
|
stx
|
|
#f
|
|
"Frage und eine Antwort nach `cond' erwartet, aber da ist nichts")]
|
|
[(_ 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) #'DMdA-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 (DMdA-else)
|
|
[(DMdA-else answer)
|
|
(let ([lpos (memq clause clauses)])
|
|
(when (not (null? (cdr lpos)))
|
|
(teach-syntax-error
|
|
'cond
|
|
stx
|
|
clause
|
|
"`else'-Test gefunden, der nicht am Ende des `cond'-Ausdrucks steht"))
|
|
(with-syntax ([new-test (stepper-syntax-property (syntax #t) 'stepper-else #t)])
|
|
(syntax/loc clause (new-test answer))))]
|
|
[(question answer)
|
|
(with-syntax ([verified (stepper-ignore-checker (syntax (verify-boolean question 'cond)))])
|
|
(syntax/loc clause (verified answer)))]
|
|
[()
|
|
(check-preceding-exprs clause)
|
|
(teach-syntax-error
|
|
'cond
|
|
stx
|
|
clause
|
|
"Test und Ausdruck in Zweig erwartet, aber Zweig leer")]
|
|
[(question?)
|
|
(check-preceding-exprs clause)
|
|
(teach-syntax-error
|
|
'cond
|
|
stx
|
|
clause
|
|
"Zweig mit Test und Ausdruck erwartet, aber Zweig enthält nur eine Form")]
|
|
[(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) #'DMdA-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
|
|
clause
|
|
"Zweig mit Test und Ausdruck erwartet, aber Zweig enthält ~a Formen"
|
|
(length parts)))]
|
|
[_else
|
|
(teach-syntax-error
|
|
'cond
|
|
stx
|
|
clause
|
|
"Zweig mit Test und Ausdruck erwartet, aber ~a gefunden"
|
|
(something-else clause))]))
|
|
clauses)])
|
|
;; Add `else' clause for error (always):
|
|
(let ([clauses (append checked-clauses
|
|
(list
|
|
(with-syntax ([error-call (syntax/loc stx (error 'cond "alle Tests ergaben #f"))])
|
|
(syntax [else error-call]))))])
|
|
(with-syntax ([clauses clauses])
|
|
(syntax/loc stx (cond . clauses))))))]
|
|
[_else (bad-use-error 'cond stx)]))))
|
|
|
|
(define-syntax DMdA-else
|
|
(make-set!-transformer
|
|
(lambda (stx)
|
|
(define (bad expr)
|
|
(teach-syntax-error
|
|
'else
|
|
expr
|
|
#f
|
|
"hier nicht erlaubt, weil kein Test in `cond'-Zweig"))
|
|
(syntax-case stx (set! x)
|
|
[(set! e expr) (bad #'e)]
|
|
[(e . expr) (bad #'e)]
|
|
[e (bad stx)]))))
|
|
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; if
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-syntax (DMdA-if stx)
|
|
(ensure-expression
|
|
stx
|
|
(lambda ()
|
|
(syntax-case stx ()
|
|
[(_ test then else)
|
|
(with-syntax ([new-test (stepper-ignore-checker (syntax (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
|
|
"Test und zwei Ausdrücke erwartet, aber ~a Form~a gefunden"
|
|
(if (zero? n) "keine" n)
|
|
(if (= n 1) "" "en")))]
|
|
[_else (bad-use-error 'if stx)]))))
|
|
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; or, and
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-syntaxes (DMdA-or DMdA-and)
|
|
(let ([mk
|
|
(lambda (where)
|
|
(let ([stepper-tag (case where
|
|
[(or) 'comes-from-or]
|
|
[(and) 'comes-from-and])])
|
|
(with-syntax ([swhere where])
|
|
(lambda (stx)
|
|
(ensure-expression
|
|
stx
|
|
(lambda ()
|
|
(syntax-case stx ()
|
|
[(_ . clauses)
|
|
(let ([n (length (syntax->list (syntax clauses)))])
|
|
(let loop ([clauses-consumed 0]
|
|
[remaining (syntax->list #`clauses)])
|
|
(if (null? remaining)
|
|
(case where
|
|
[(or) #`#f]
|
|
[(and) #`#t])
|
|
(stepper-syntax-property
|
|
(stepper-syntax-property
|
|
(quasisyntax/loc
|
|
stx
|
|
(if #,(stepper-ignore-checker (quasisyntax/loc stx (verify-boolean #,(car remaining) 'swhere)))
|
|
#,@(case where
|
|
[(or) #`(#t
|
|
#,(loop (+ clauses-consumed 1) (cdr remaining)))]
|
|
[(and) #`(#,(loop (+ clauses-consumed 1) (cdr remaining))
|
|
#f)])))
|
|
'stepper-hint
|
|
stepper-tag)
|
|
'stepper-and/or-clauses-consumed
|
|
clauses-consumed))))]
|
|
[_else (bad-use-error where stx)])))))))])
|
|
(values (mk 'or) (mk 'and))))
|
|
|
|
;; verify-boolean is inserted to check for boolean results:
|
|
(define (verify-boolean b where)
|
|
(if (or (eq? b #t) (eq? b #f))
|
|
b
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "~a: Testresultat ist nicht boolesch: ~e" where b))
|
|
(current-continuation-marks)))))
|
|
|
|
(define (DMdA-not b)
|
|
(verify-boolean b 'not)
|
|
(not b))
|
|
|
|
(define (boolean=? a b)
|
|
(verify-boolean a 'boolean=?)
|
|
(verify-boolean b 'boolean=?)
|
|
(eq? a b))
|
|
|
|
(define-syntax (DMdA-app stx)
|
|
(syntax-case stx ()
|
|
((_)
|
|
(raise-syntax-error
|
|
#f "Zusammengesetzte Form ohne Operator" (syntax/loc stx ())))
|
|
((_ datum1 datum2 ...)
|
|
(let ((scm-datum (syntax->datum (syntax datum1))))
|
|
(or (number? scm-datum)
|
|
(boolean? scm-datum)
|
|
(string? scm-datum)
|
|
(char? scm-datum)))
|
|
(raise-syntax-error #f "Operator darf kein Literal sein" (syntax datum1)))
|
|
((_ datum1 datum2 ...)
|
|
(syntax/loc stx (#%app datum1 datum2 ...)))))
|
|
|
|
(define-syntax (DMdA-top stx)
|
|
(syntax-case stx ()
|
|
((_ . id)
|
|
;; If we're in a module, we'll need to check that the name
|
|
;; is bound....
|
|
(if (and (not (identifier-binding #'id))
|
|
(syntax-source-module #'id))
|
|
;; ... but it might be defined later in the module, so
|
|
;; delay the check.
|
|
(stepper-ignore-checker
|
|
(syntax/loc stx (#%app values (DMdA-top-continue id))))
|
|
(syntax/loc stx (#%top . id))))))
|
|
|
|
(define-syntax (DMdA-top-continue stx)
|
|
(syntax-case stx ()
|
|
[(_ id)
|
|
;; If there's still no binding, it's an "unknown name" error.
|
|
(if (not (identifier-binding #'id))
|
|
(raise-syntax-error #f "Ungebundene Variable" (syntax/loc stx id))
|
|
;; Don't use #%top here; id might have become bound to something
|
|
;; that isn't a value.
|
|
#'id)]))
|
|
|
|
(define (DMdA-write-string s)
|
|
(when (not (string? s))
|
|
(error "Argument von write-string ist keine Zeichenkette"))
|
|
(display s))
|
|
|
|
(define (write-newline)
|
|
(newline))
|
|
|
|
(define-record-procedures chocolate-cookie
|
|
make-chocolate-cookie chocolate-cookie?
|
|
(chocolate-cookie-chocolate chocolate-cookie-cookie))
|
|
|
|
(define (violation text)
|
|
(error text))
|
|
|
|
(define (string->strings-list s)
|
|
(map (lambda (c) (make-string 1 c)) (string->list s)))
|
|
|
|
(define (strings-list->string l)
|
|
(if (null? l)
|
|
""
|
|
(string-append (car l) (strings-list->string (cdr l)))))
|
|
|
|
(define integer (contract/arbitrary arbitrary-integer (predicate integer?)))
|
|
(define number (contract/arbitrary arbitrary-real (predicate number?)))
|
|
(define rational (contract/arbitrary arbitrary-rational (predicate rational?)))
|
|
(define real (contract/arbitrary arbitrary-real (predicate real?)))
|
|
|
|
(define (natural? x)
|
|
(and (integer? x)
|
|
(not (negative? x))))
|
|
|
|
(define natural (contract/arbitrary arbitrary-natural (predicate natural?)))
|
|
|
|
(define boolean (contract/arbitrary arbitrary-boolean (predicate boolean?)))
|
|
|
|
(define (true? x)
|
|
(eq? x #t))
|
|
|
|
(define (false? x)
|
|
(eq? x #f))
|
|
|
|
(define true (contract (one-of #f)))
|
|
(define false (contract (one-of #f)))
|
|
|
|
(define string (contract/arbitrary arbitrary-printable-ascii-string (predicate string?)))
|
|
(define symbol (contract/arbitrary arbitrary-symbol (predicate symbol?)))
|
|
(define empty-list (contract (one-of empty)))
|
|
|
|
(define unspecific (contract (predicate (lambda (_) #t))))
|
|
|
|
;; aus collects/lang/private/teach.ss
|
|
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; dots (.. and ... and .... and ..... and ......)
|
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Syntax Identifier -> Expression
|
|
;; Produces an expression which raises an error reporting unfinished code.
|
|
(define-for-syntax (dots-error stx name)
|
|
(quasisyntax/loc stx
|
|
(error (quote (unsyntax name))
|
|
"Fertiger Ausdruck erwartet, aber da sind noch Ellipsen")))
|
|
|
|
;; Expression -> Expression
|
|
;; Transforms unfinished code (... and the like) to code
|
|
;; raising an appropriate error.
|
|
(define-syntax DMdA-dots
|
|
(make-set!-transformer
|
|
(lambda (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-syntaxes (DMdA-set! DMdA-set!-continue)
|
|
(let ((proc
|
|
(lambda (continuing?)
|
|
(lambda (stx)
|
|
(ensure-expression
|
|
stx
|
|
(lambda ()
|
|
(syntax-case stx ()
|
|
((_ id expr)
|
|
(identifier? (syntax id))
|
|
(begin
|
|
;; Check that id isn't syntax, and not lexical.
|
|
((with-handlers ((exn:fail? (lambda (exn) void)))
|
|
;; First try syntax:
|
|
;; If it's a transformer binding, then it can take care of itself...
|
|
(if (set!-transformer? (syntax-local-value (syntax id)))
|
|
void ;; no lex check wanted
|
|
(lambda ()
|
|
(raise-syntax-error
|
|
#f
|
|
"Nach set! wird eine gebundene Variable erwartet, aber da ist ein Schlüsselwort."
|
|
stx)))))
|
|
;; If we're in a module, we'd like to check here whether
|
|
;; the identier is bound, but we need to delay that check
|
|
;; in case the id is defined later in the module. So only
|
|
;; do this in continuing mode:
|
|
(when continuing?
|
|
(when (and (not (identifier-binding #'id))
|
|
(syntax-source-module #'id))
|
|
(raise-syntax-error #f "Ungebundene Variable" #'id)))
|
|
(if continuing?
|
|
(syntax/loc stx (set! id expr))
|
|
(stepper-ignore-checker (syntax/loc stx (#%app values (DMdA-set!-continue id expr)))))))
|
|
((_ id expr)
|
|
(raise-syntax-error
|
|
#f
|
|
"Nach set! wird eine Variable aber da ist etwas anderes."
|
|
#'id))
|
|
((_ id)
|
|
(raise-syntax-error
|
|
#f
|
|
"Nach set! wird eine Variable und ein Ausdruck erwartet - der Ausdruck fehlt."
|
|
stx))
|
|
((_)
|
|
(raise-syntax-error
|
|
#f
|
|
"Nach set! wird eine Variable und ein Ausdruck erwartet, aber da ist nichts."
|
|
stx))
|
|
(_else
|
|
(raise-syntax-error
|
|
#f
|
|
"Inkorrekter set!-Ausdruck."
|
|
stx)))))))))
|
|
(values (proc #f)
|
|
(proc #t))))
|
|
|
|
; QuickCheck
|
|
|
|
(define-syntax (for-all stx)
|
|
(syntax-case stx ()
|
|
((_ (?clause ...) ?body)
|
|
(with-syntax ((((?id ?arb) ...)
|
|
(map (lambda (pr)
|
|
(syntax-case pr ()
|
|
((?id ?contract)
|
|
(identifier? #'?id)
|
|
(with-syntax ((?error-call
|
|
(syntax/loc #'?contract (error "Vertrag hat keinen Generator"))))
|
|
#'(?id
|
|
(or (contract-arbitrary (contract ?contract))
|
|
?error-call))))
|
|
(_
|
|
(raise-syntax-error #f "inkorrekte `for-all'-Klausel - sollte die Form (id contr) haben"
|
|
pr))))
|
|
(syntax->list #'(?clause ...)))))
|
|
|
|
(stepper-syntax-property #'(quickcheck:property
|
|
((?id ?arb) ...) ?body)
|
|
'stepper-skip-completely
|
|
#t)))
|
|
((_ ?something ?body)
|
|
(raise-syntax-error #f "keine Klauseln der Form (id contr)"
|
|
stx))
|
|
((_ ?thing1 ?thing2 ?thing3 ?things ...)
|
|
(raise-syntax-error #f "zuviele Operanden"
|
|
stx))))
|
|
|
|
(define-syntax (check-property stx)
|
|
(unless (memq (syntax-local-context) '(module top-level))
|
|
(raise-syntax-error
|
|
#f "`check-property' muss ganz außen stehen" stx))
|
|
(syntax-case stx ()
|
|
((_ ?prop)
|
|
(stepper-syntax-property
|
|
(check-expect-maker stx #'check-property-error #'?prop '()
|
|
'comes-from-check-property)
|
|
'stepper-replace
|
|
#'#t))
|
|
(_ (raise-syntax-error #f "`check-property' erwartet einen einzelnen Operanden"
|
|
stx))))
|
|
|
|
(define (check-property-error test src-info test-info)
|
|
(let ((info (send test-info get-info)))
|
|
(send info add-check)
|
|
(with-handlers ((exn:fail?
|
|
(lambda (e)
|
|
(send info property-error e src-info)
|
|
(raise e))))
|
|
(call-with-values
|
|
(lambda ()
|
|
(with-handlers
|
|
((exn:assertion-violation?
|
|
(lambda (e)
|
|
;; minor kludge to produce comprehensible error message
|
|
(if (eq? (exn:assertion-violation-who e) 'coerce->result-generator)
|
|
(raise (make-exn:fail (string-append "Wert muß Eigenschaft oder boolesch sein: "
|
|
((error-value->string-handler)
|
|
(car (exn:assertion-violation-irritants e))
|
|
100))
|
|
(exn-continuation-marks e)))
|
|
(raise e)))))
|
|
(quickcheck-results (test))))
|
|
(lambda (ntest stamps result)
|
|
(if (check-result? result)
|
|
(begin
|
|
(send info property-failed result src-info)
|
|
#f)
|
|
#t))))))
|
|
|
|
(define (expect v1 v2)
|
|
(quickcheck:property () (beginner-equal? v1 v2)))
|
|
|
|
(define (ensure-real who n val)
|
|
(unless (real? val)
|
|
(raise
|
|
(make-exn:fail:contract
|
|
(string->immutable-string
|
|
(format "~a Argument ~e zu `~a' keine reelle Zahl." n val who))
|
|
(current-continuation-marks)))))
|
|
|
|
(define (expect-within v1 v2 epsilon)
|
|
(ensure-real 'expect-within "Drittes" epsilon)
|
|
(quickcheck:property () (beginner-equal~? v1 v2 epsilon)))
|
|
|
|
(define (expect-range val min max)
|
|
(ensure-real 'expect-range "Erstes" val)
|
|
(ensure-real 'expect-range "Zweites" min)
|
|
(ensure-real 'expect-range "Drittes" max)
|
|
(quickcheck:property ()
|
|
(and (<= min val)
|
|
(<= val max))))
|
|
|
|
(define (expect-member-of val . candidates)
|
|
(quickcheck:property ()
|
|
(ormap (lambda (cand)
|
|
(beginner-equal? val cand))
|
|
candidates)))
|
|
|
|
(define property (contract (predicate (lambda (x)
|
|
(or (boolean? x)
|
|
(property? x))))))
|
|
|