wisp
 
(Arne Babenhauserheide)
2017-02-04: add enter three witches, precompiled

add enter three witches, precompiled

diff --git a/examples/enter-three-witches.scm b/examples/enter-three-witches.scm
new file mode 100644
--- /dev/null
+++ b/examples/enter-three-witches.scm
@@ -0,0 +1,267 @@
+#!/usr/bin/env sh
+(# -*- wisp -*-)
+(guile -L $(dirname $(dirname $(realpath "$0"))) -c '(import (wisp-scheme) (language wisp spec))')
+(exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples enter-three-witches) main)' -s "$0" "$@")
+; !#
+
+(define-module (examples enter-three-witches)
+    #:export (introduced-names ->string show colortable color say-words say-name say Speak Speak-indirect Enter Scene))
+
+(use-modules (ice-9 optargs)
+              (srfi srfi-1)
+              (system syntax))
+
+;; FIXME: This version currently does not allow using the same first
+;; name several times. It will need a more refined macro generator
+;; which first gathers all the necessary definitions and then builds
+;; them.
+
+(define introduced-names '())
+
+(define (->string x)
+       (cond
+         ((symbol? x)
+           (symbol->string x))
+         ((number? x)
+           (format #f "~a" x))
+         (else
+           (format #f "~A" x))))
+
+
+(define (show str)
+      (let lp ((chars (string->list str)))
+          (cond 
+            ((null? chars)
+              #t)
+            (else
+              (display (car chars))
+              (usleep 60000)
+              (lp (cdr chars))))))
+
+
+(define colortable
+    `(
+      (#f . "\x1b[0m")
+      (black . "\x1b[1;30m")
+      (red . "\x1b[1;31m")
+      (green . "\x1b[1;32m")
+      (yellow . "\x1b[1;33m")
+      (blue . "\x1b[1;34m")
+      (magenta . "\x1b[1;35m")
+      (cyan . "\x1b[1;36m")
+      (white . "\x1b[1;37m")))
+      
+
+(define (color col)
+       "helper function to colorize the input"
+       (cond
+         ((assoc col colortable)
+           (format #t (assoc-ref colortable col))
+           #f)
+         (else
+           (format #t (assoc-ref colortable #f))
+           #f)))
+
+(define-syntax say-words 
+    (lambda (x)
+        (syntax-case x ()
+            ((_ (((word words ...))) (() lines ...))
+              #`(begin
+                  (let ((w `word))
+                    (cond
+                     ((equal? w #f)
+                      #f)
+                     ((equal? w '..)
+                      (show "."))
+                     (else
+                      (show " ")
+                      (show (->string w)))))
+                  (say-words (((words ...))) (() lines ...))))
+            ((_ ((())) (() lines ...))
+              #`(begin
+                 (usleep 200000)
+                 (newline)
+                 (say-words (lines ...))))
+            ;; lines of form ,(...)
+            ((_ ((unq (word words ...)) lines ...))
+              #`(begin if (equal 'unquote `unq))
+              #`(begin ; add an extra level of parens
+                 (show " ")
+                 (say-words ((((unq (word words ...))))) (() lines ...))))
+            ((_ ((word words ...) lines ...))
+              #`(begin
+                 (show " ")
+                 (say-words (((word words ...))) (() lines ...))))
+            ((_ (() lines ...))
+              #`(say-words (lines ...)))
+            ((_ (lines ...))
+              #`(begin
+                 (newline))))))
+
+
+(define (say-name nameparts)
+       (let 
+         ;; symbols starting with : are not treated as part of the
+         ;; name. They can be used as actor instructions
+         ((pure-name (remove (lambda (x) (string-prefix? ":" (symbol->string x))) (remove pair? nameparts))))
+         (if (not (member pure-name introduced-names))
+              (error 
+                (format #f "Tried to use ~A who did not Enter. Introduced names: ~A" 
+                  pure-name introduced-names))))
+       (format #t "~A\n"
+         (string-join (map symbol->string nameparts))))
+
+
+(define-syntax say
+  (lambda (x)
+    (syntax-case x ()
+      ((_ nameparts lines)
+        #`(begin
+           (say-name nameparts)
+           (say-words lines))))))
+
+
+(define-syntax Speak
+ (lambda (x)
+  (with-ellipsis :::
+   (syntax-case x ()
+     ;; Support form for modifiers: enclose by double parens (used later)
+     ((_ (((name :::))) ((mod :::)) (word :::) line :::)
+         #`(begin
+            (say-name (quasiquote (name ::: mod :::)))
+            (say-words ((word :::) line :::))))
+     ;; extend mod keywords
+     ((_ (((name :::))) ((mod :::)) modifier line :::)
+         ;; extend the modifier keyword list
+         #`(Speak (((name :::))) ((mod ::: modifier)) line :::))
+     ;; say form without modifier
+     ((_ (((name :::))) (word :::) line :::)
+         #`(Speak (((name :::))) (()) (word :::) line :::))
+     ;; first modifier keyword after the name
+     ((_ (((name :::))) modifier line :::)
+         ;; append to mod helper form
+         #`(Speak (((name :::))) ((modifier)) line :::))
+     ;; Strip the name from lines with empty arguments
+     ((_ (((name :::))) symbol :::)
+         #`(begin #t symbol :::))))))
+
+
+(define-syntax Speak-indirect
+    (lambda (x)
+        (syntax-case x ()
+            ;; Adjust name and lines for Speak for the case where I
+            ;; cannot match for the whole name.
+            ;; input: (((name1 name2 ... (word ...) ...)))
+            
+            ;; grab the lines one by one from the back
+            ((_ (((symbols ... (word ...)))) lines ...)
+              #`(Speak-indirect (((symbols ...))) (word ...) lines ...))
+            ;; start with the last line
+            ((_ (((symbols ... (word ...)))))
+              #`(Speak-indirect (((symbols ...))) (word ...)))
+            ;; no more lines remain at the end: the rest must be the name
+            ((_ (((name ...))) lines ...)
+              #`(Speak (((name ...))) lines ...)))))
+
+
+(define-syntax Enter
+ (lambda (x)
+  (syntax-case x ()
+   ((_ (name more ...) b ...)
+     ; new binding: only create it if the binding is not already a macro
+     (not (eq? 'macro (syntax-local-binding (syntax name))))
+     #'(begin
+       ;; process the name: define special syntax for this name (only
+       ;; for the first word of the name, the correctness of the rest
+       ;; of the words is checked at runtime in the say procedure)
+       (define-syntax name
+        (lambda (y)
+         (with-ellipsis :::
+          (syntax-case y (more ...)
+           ; just forward matching rules to Speak
+           ((_ more ... symbol :::)
+             #'(Speak (((name more ...))) symbol :::))
+           ((_ symbols :::)
+               ; this does not correctly make the second name part of
+               ; the name, preventing differentiation between name and
+               ; modifier
+               #`(Speak-indirect (((name symbols :::)))))))))
+       ;; process the rest of the names
+       (Enter b ...)
+       ;; record that the name was introduced. I do not see a way to do
+       ;; this directly in the compiler, therefore it is checked later
+       ;; during runtime.
+       (set! introduced-names (cons '(name more ...) introduced-names))))
+       ;; add debug output, must be added it here, not in front
+       ; write 
+       ;   quote : list Enter (name more ...) b ...
+       ; newline
+   ((_ (name more ...) b ...)
+     ; existing binding: Just allow using this.
+     #'(begin
+        (Enter b ...)
+        (set! introduced-names (cons '(name more ...) introduced-names))))
+   ((_ b ...)
+     #'(begin #t)))))
+
+
+(define-syntax Scene
+  (lambda (x)
+    (syntax-case x ()
+      ((_ thisscene args ...)
+        (with-syntax ((c (datum->syntax x (module-name (current-module)))))
+          #`(begin ; FIXME: this currently requires the Scene identifier to be a valid symbol -> cannot use "Scene 1"
+             (module-re-export! (current-module)
+               (module-map (λ (x y) x)
+                  (module-import-interface (current-module) 'Scene ))); ensure that all symbols remain available
+             (define-module (scene thisscene))
+             (import c)
+             #t))))))
+
+
+(define (main args)
+  (Enter (First Witch)
+          (Second Witch)
+          (Third Witch)
+          (First Eldritch))
+
+  (First Witch
+      (When shall we three meet again)
+      (In ,(color 'cyan) thunder, ,(color #f) ,(color 'white) lightning, ,(color #f) or in ,(color 'blue) rain? ,(color #f)))
+  
+  (Second Witch :resolute
+      (When the hurlyburly's done, (we ,(+ 1 2)) ); inline-code is allowed!
+      (When the ,(color 'red) battle's ,(color #f) 
+         lost and won. )); ,(read-char) ; and executed when the word is shown
+
+  (Third Witch
+      (That will be ere the set of ,(color 'yellow) sun ,(color #f) ..))
+      ; .. can be used for a . without preceding space. It MUST be
+      ; used to get a trailing .
+
+  (First Eldritch :crazy
+      (,(color 'magenta) gnignigni! ,(color #f)))
+
+  (Enter (Second Eldritch))
+  
+  (Second Eldritch :quick
+      (,(color 'black) Guh!)
+      ; . :goo ; invalid ⇒ would be an error
+      ; . foo ; invalid ⇒ would be an error
+      (Moo ,(color #f))))
+
+;; Making the name longer throws an Error, but only at runtime:
+;  Second Eldritch shoo
+;      Guh!
+;; ⇒ ERROR: Tried to use (Second Eldritch shoo) who did not Enter. Introduced names: ((Second Eldritch) (First Witch) (Second Witch) (Third Witch) (First Eldritch))
+
+;; Adding one who did not enter throws an Error, but only at runtime:
+;  Third Eldritch
+;      Guh!
+;; ⇒ ERROR: Tried to use (Third Eldritch) who did not Enter. Introduced names: ((Second Eldritch) (First Witch) (Second Witch) (Third Witch) (First Eldritch))
+
+
+
+
+
+