diff options
author | murphy <murphy@rubychan.de> | 2007-01-01 16:28:39 +0000 |
---|---|---|
committer | murphy <murphy@rubychan.de> | 2007-01-01 16:28:39 +0000 |
commit | 383acf60e6e8f61ee165e28ab71dd43757129e2b (patch) | |
tree | 04d07eb8151dfba1f7e7b49abc19067816c29d32 /test | |
parent | da09c7c6bb1267996db7751e775d08742256f3f2 (diff) | |
download | coderay-383acf60e6e8f61ee165e28ab71dd43757129e2b.tar.gz |
Added Scheme tests.
Diffstat (limited to 'test')
-rw-r--r-- | test/scanners/scheme/pleac.in.scm | 5141 | ||||
-rw-r--r-- | test/scanners/scheme/strange.in.scm | 38 |
2 files changed, 5179 insertions, 0 deletions
diff --git a/test/scanners/scheme/pleac.in.scm b/test/scanners/scheme/pleac.in.scm new file mode 100644 index 0000000..7c8c4a5 --- /dev/null +++ b/test/scanners/scheme/pleac.in.scm @@ -0,0 +1,5141 @@ +;;; -*- scheme -*- + +;;; @@PLEAC@@_NAME +;;; @@SKIP@@ Guile 1.8 + +;;; @@PLEAC@@_WEB +;;; @@SKIP@@ http://www.gnu.org/software/guile/ + +;;; @@PLEAC@@_INTRO +;;; @@SKIP@@ Sections 1 - 3, and 7 - 9, largely completed using Guile 1.5; subsequent additions use Guile 1.8. + +;;; @@PLEAC@@_APPENDIX +;;; @@SKIP@@ General-purpose, custom functions that might be used in several sections, appear here + +;; Helper which aims to reduce code clutter by: +;; * Replacing the oft-used, '(display item) (newline)' combination +;; * Avoiding overuse of '(string-append)' for simple output tasks +(define (print item . rest) + (let ((all-item (cons item rest))) + (for-each + (lambda (item) (display item) (display " ")) + all-item)) + (newline)) + +;; ------------ + +;; Slightly modified version of '(qx)' from Chapter 4 +(use-modules (ice-9 popen) (srfi srfi-1) (srfi srfi-13)) + +(define (drain-output port) + (let loop ((chars '()) + (next (read-char port))) + (if (eof-object? next) + ; Modified to not return last 'line' with newline + (list->string (reverse! (cdr chars))) + (loop (cons next chars) + (read-char port))))) + +(define (qx pipeline) + (let* ((pipe (open-input-pipe pipeline)) + (output (drain-output pipe))) + (close-pipe pipe) + output)) + +;; ------------ + +;; @@PLEAC@@_1.0 +(define string "\\n") ; two characters, \ and an n +(define string "\n") ; a "newline" character +(define string "Jon \"Maddog\" Orwant") ; literal double quotes +(define string "Jon 'Maddog' Orwant") ; literal single quotes + +(define a "This is a multiline here document +terminated by a closing double quote") + +;; @@PLEAC@@_1.1 +;; Use substring + +(substring str start end) +(substring str start) + +;; You can fill portions of a string with another string + +(substring-move-right! str start end newstring newstart) +(substring-move-left! str start end newstring newstart) + +;; Guile has a separate character type, and you can treat strings as a +;; character array. + +(string-ref str pos) +(string-set! str pos char) +(string-fill! str char) +(substring-fill! str start end char) + +(define s "This is what you have") +(define first (substring s 0 1)) ; "T" +(define start (substring s 5 7)) ; "is" +(define rest (substring s 13)) ; "you have" +(define last (substring s (1- (string-length s)))) ; "e" +(define end (substring s (- (string-length s) 4))) ; "have" +(define piece (let ((len (string-length s))) + (substring s (- len 8) (- len 5)))) ; "you" + + +;;; Or use the string library SRFI-13 +(use-modules (srfi srfi-13)) + +(define s "This is what you have") +(define first (string-take s 1)) ; "T" +(define start (xsubstring s 5 7)) ; "is" +(define rest (xsubstring s 13 -1)) ; "you have" +(define last (string-take-right s 1)) ; "e" +(define end (string-take-right s 4)) ; "have" +(define piece (xsubstring s -8 -5)) ; "you" + +;; Mutation of different sized strings is not allowed. You have to +;; use set! to change the variable. + +(set! s (string-replace s "wasn't" 5 7)) +;; This wasn't what you have +(set! s (string-replace s "ondrous" 13 25)) +;; This wasn't wondrous +(set! s (string-take-right s (1- (string-length s)))) +;; his wasn't wondrous +(set! s (string-take s 9)) + +;; @@PLEAC@@_1.2 +(define a (or b c)) +(define a (if (defined? b) b c)) +(define a (or (and (defined? b) b) c)) + +;; @@PLEAC@@_1.3 +;; This doesn't really make sense in Scheme... temporary variables are +;; a natural construct and cheap. If you want to swap variables in a +;; block without introducing any new variable names, you can use let: + +(let ((a b) (b a)) + ;; ... + ) + +(let ((alpha beta) (beta production) (production alpha)) + ;; ... + ) + +;; @@PLEAC@@_1.4 +(define num (char->integer char)) +(define char (integer->char num)) + +(use-modules (srfi srfi-13)) +(let ((str "sample")) + (display (string-join + (map number->string + (map char->integer (string->list str))) " ")) + (newline)) + +(let ((lst '(115 97 109 112 108 101))) + (display (list->string (map integer->char lst))) + (newline)) + +(letrec ((next (lambda (c) (integer->char (1+ (char->integer c)))))) + (let* ((hal "HAL") + (ibm (list->string (map next (string->list hal))))) + (display ibm) + (newline))) + +;; @@PLEAC@@_1.5 +;; Convert the string to a list of characters +(map proc + (string->list str)) + +(use-modules (srfi srfi-1)) +(format #t "unique chars are: ~A\n" + (apply string (sort (delete-duplicates + (string->list "an apple a day")) char<?))) + +(let* ((str "an apple a day") + (sum (apply + (map char->integer (string->list str))))) + (format #t "sum is ~A\n" sum)) + +;;; or use string-fold/string-map/string-for-each from SRFI-13 +(use-modules (srfi srfi-13)) + +(let* ((str "an apple a day") + (sum (string-fold (lambda (c acc) (+ acc (char->integer c))) + 0 str))) + (format #t "sum is ~A\n" sum)) + +#!/usr/local/bin/guile -s +!# +;; sum - compute 16-bit checksum of all input files +(use-modules (srfi srfi-13)) +(define (checksum p) + (let loop ((line (read-line p 'concat)) (sum 0)) + (if (eof-object? line) + (format #t "~A ~A\n" sum (port-filename p)) + (let ((line-sum (string-fold (lambda (c acc) + (+ acc (char->integer c))) + 0 line))) + (loop (read-line p 'concat) (modulo (+ sum line-sum) + (1- (expt 2 16)))))))) +(let ((args (cdr (command-line)))) + (if (null? args) + (checksum (current-input-port)) + (for-each (lambda (f) (call-with-input-file f checksum)) args))) + +#!/usr/local/bin/guile -s +!# +;; slowcat - emulate a s l o w line printer +(use-modules (ice-9 regex) (srfi srfi-2) (srfi srfi-13)) +(define args (cdr (command-line))) +(define delay 1) +(and-let* ((p (pair? args)) + (m (string-match "^-([0-9]+)$" (car args)))) + (set! delay (string->number (match:substring m 1))) + (set! args (cdr args))) +(define (slowcat p) + (let loop ((line (read-line p 'concat))) + (cond ((not (eof-object? line)) + (string-for-each + (lambda (c) (display c) (usleep (* 5 delay))) line) + (loop (read-line p 'concat)))))) +(if (null? args) + (slowcat (current-input-port)) + (for-each (lambda (f) (call-with-input-file f slowcat)) args)) + +;; @@PLEAC@@_1.6 +(define revbytes (list->string (reverse (string->list str)))) + +;;; Or from SRFI-13 +(use-modules (srfi srfi-13)) +(define revbytes (string-reverse str)) +(string-reverse! str) ; modifies in place + +(define revwords (string-join (reverse (string-tokenize str)) " ")) + +(with-input-from-file "/usr/share/dict/words" + (lambda () + (do ((word (read-line) (read-line))) + ((eof-object? word)) + (if (and (> (string-length word) 5) + (string=? word (string-reverse word))) + (write-line word))))) + +;; A little too verbose on the command line +;; guile --use-srfi=13 -c '(with-input-from-file "/usr/share/dict/words" (lambda () (do ((word (read-line) (read-line))) ((eof-object? word)) (if (and (> (string-length word) 5) (string=? word (string-reverse word))) (write-line word)))))' + +;; @@PLEAC@@_1.7 +;; Use regexp-substitute/global +(regexp-substitute/global + #f "([^\t]*)(\t+)" str + (lambda (m) + (let* ((pre-string (match:substring m 1)) + (pre-len (string-length pre-string)) + (match-len (- (match:end m 2) (match:start m 2)))) + (string-append + pre-string + (make-string + (- (* match-len 8) + (modulo pre-len 8)) + #\space)))) + 'post) + +;; @@PLEAC@@_1.8 +;; just interpolate $abc in strings: +(define (varsubst str) + (regexp-substitute/global #f "\\$(\\w+)" str + 'pre (lambda (m) (eval (string->symbol (match:substring m 1)) + (current-module))) + 'post)) + +;; interpolate $abc with error messages: +(define (safe-varsubst str) + (regexp-substitute/global #f "\\$(\\w+)" str + 'pre (lambda (m) + (catch #t + (lambda () (eval (string->symbol (match:substring m 1)) + (current-module))) + (lambda args + (format #f "[NO VARIABLE: ~A]" (match:substring m 1))))) + 'post)) + +;; interpolate ${(any (scheme expression))} in strings: +(define (interpolate str) + (regexp-substitute/global #f "\\${([^{}]+)}" str + 'pre (lambda (m) (eval-string (match:substring m 1))) 'post)) + +;; @@PLEAC@@_1.9 +(use-modules (srfi srfi-13)) + +(string-upcase "bo beep") ; BO PEEP +(string-downcase "JOHN") ; john +(string-titlecase "bo") ; Bo +(string-titlecase "JOHN") ; John + +(string-titlecase "thIS is a loNG liNE") ; This Is A Long Line + +#!/usr/local/bin/guile -s +!# +;; randcap: filter to randomly capitalize 20% of the time +(use-modules (srfi srfi-13)) +(seed->random-state (current-time)) +(define (randcap p) + (let loop ((line (read-line p 'concat))) + (cond ((not (eof-object? line)) + (display (string-map (lambda (c) + (if (= (random 5) 0) + (char-upcase c) + (char-downcase c))) + line)) + (loop (read-line p 'concat)))))) +(let ((args (cdr (command-line)))) + (if (null? args) + (randcap (current-input-port)) + (for-each (lambda (f) (call-with-input-file f randcap)) args))) + +;; @@PLEAC@@_1.10 +;; You can do this with format. Lisp/Scheme format is a little +;; different from what you may be used to with C/Perl style printf +;; (actually far more powerful) , but if you keep in mind that we use +;; ~ instead of %, and , instead of . for the prefix characters, you +;; won't have trouble getting used to Guile's format. + +(format #f "I have ~A guanacos." n) + +;; @@PLEAC@@_1.11 +(define var " + your text + goes here") + +(use-modules (ice-9 regexp)) +(set! var (regexp-substitute/global #f "\n +" var 'pre "\n" 'post)) + +(use-modules (srfi srfi-13)) +(set! var (string-join (map string-trim (string-tokenize var #\newline)) "\n")) + +(use-modules (ice-9 regexp) (srfi srfi-13) (srfi srfi-14)) +(define (dequote str) + (let* ((str (if (char=? (string-ref str 0) #\newline) + (substring str 1) str)) + (lines (string-tokenize str #\newline)) + (rx (let loop ((leader (car lines)) (lst (cdr lines))) + (cond ((string= leader "") + (let ((pos (or (string-skip (car lines) + char-set:whitespace) 0))) + (make-regexp (format #f "^[ \\t]{1,~A}" pos) + regexp/newline))) + ((null? lst) + (make-regexp (string-append "^[ \\t]*" + (regexp-quote leader)) + regexp/newline)) + (else + (let ((pos (or (string-prefix-length leader (car lst)) 0))) + (loop (substring leader 0 pos) (cdr lst)))))))) + (regexp-substitute/global #f rx str 'pre 'post))) + +;; @@PLEAC@@_1.12 +(use-modules (srfi srfi-13)) + +(define text "Folding and splicing is the work of an editor, +not a mere collection of silicon +and +mobile electrons!") + +(define (wrap str max-col) + (let* ((words (string-tokenize str)) + (all '()) + (first (car words)) + (col (string-length first)) + (line (list first))) + (for-each + (lambda (x) + (let* ((len (string-length x)) + (new-col (+ col len 1))) + (cond ((> new-col max-col) + (set! all (cons (string-join (reverse! line) " ") all)) + (set! line (list x)) + (set! col len)) + (else + (set! line (cons x line)) + (set! col new-col))))) + (cdr words)) + (set! all (cons (string-join (reverse! line) " ") all)) + (string-join (reverse! all) "\n"))) + +(display (wrap text 20)) + +;; @@PLEAC@@_1.13 +(define str "Mom said, \"Don't do that.\"") +(set! str (regexp-substitute/global #f "['\"]" str 'pre "\\" + match:substring 'post)) +(set! str (regexp-substitute/global #f "[^A-Z]" str 'pre "\\" + match:substring 'post)) +(set! str (string-append "this " (regexp-substitute/global + #f "\W" "is a test!" 'pre "\\" + match:substring 'post))) + +;; @@PLEAC@@_1.14 +(use-modules (srfi srfi-13)) + +(define str " space ") +(string-trim str) ; "space " +(string-trim-right str) ; " space" +(string-trim-both str) ; "space" + +;; @@PLEAC@@_1.15 +(use-modules (srfi srfi-2) (srfi srfi-13) (ice-9 format)) + +(define parse-csv + (let* ((csv-match (string-join '("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\",?" + "([^,]+),?" + ",") + "|")) + (csv-rx (make-regexp csv-match))) + (lambda (text) + (let ((start 0) + (result '())) + (let loop ((start 0)) + (and-let* ((m (regexp-exec csv-rx text start))) + (set! result (cons (or (match:substring m 1) + (match:substring m 3)) + result)) + (loop (match:end m)))) + (reverse result))))) + +(define line "XYZZY,\"\",\"O'Reilly, Inc\",\"Wall, Larry\",\"a \\\"glug\\\" bit,\",5,\"Error, Core Dumped\"") + +(do ((i 0 (1+ i)) + (fields (parse-csv line) (cdr fields))) + ((null? fields)) + (format #t "~D : ~A\n" i (car fields))) + +;; @@PLEAC@@_1.16 +(use-modules (srfi srfi-13) (srfi srfi-14)) + +;; Knuth's soundex algorithm from The Art of Computer Programming, Vol 3 +(define soundex + (letrec ((chars "AEIOUYBFPVCGJKQSXZDTLMNR") + (nums "000000111122222222334556") + (skipchars (string->char-set "HW")) + (trans (lambda (c) + (let ((i (string-index chars c))) + (if i (string-ref nums i) c))))) + (lambda (str) + (let* ((ustr (string-upcase str)) + (f (string-ref ustr 0)) + (skip (trans f))) + (let* ((mstr (string-map trans (string-delete ustr skipchars 1))) + (dstr (string-map (lambda (c) + (cond ((eq? c skip) #\0) + (else (set! skip c) c))) + mstr)) + (zstr (string-delete dstr #\0))) + (substring (string-append (make-string 1 f) zstr "000") 0 4)))))) + +(soundex "Knuth") ; K530 +(soundex "Kant") ; K530 +(soundex "Lloyd") ; L300 +(soundex "Ladd") ; L300 + +;; @@PLEAC@@_1.17 +#!/usr/local/bin/guile -s +!# + +(use-modules (srfi srfi-13) + (srfi srfi-14) + (ice-9 rw) + (ice-9 regex)) + +(define data "analysed => analyzed +built-in => builtin +chastized => chastised +commandline => command-line +de-allocate => deallocate +dropin => drop-in +hardcode => hard-code +meta-data => metadata +multicharacter => multi-character +multiway => multi-way +non-empty => nonempty +non-profit => nonprofit +non-trappable => nontrappable +pre-define => predefine +preextend => pre-extend +re-compiling => recompiling +reenter => re-enter +turnkey => turn-key") + +(define input (if (null? (cdr (command-line))) + (current-input-port) + (open-input-file (cadr (command-line))))) + +(let* ((newline-char-set (string->char-set "\n")) + (assoc-char-set (string->char-set " =>")) + (dict (map + (lambda (line) + (string-tokenize line assoc-char-set)) + (string-tokenize data newline-char-set))) + (dict-match (string-join (map car dict) "|"))) + (let loop ((line (read-line input))) + (cond ((not (eof-object? line)) + (regexp-substitute/global + (current-output-port) dict-match line + 'pre + (lambda (x) + (cadr (assoc (match:substring x 0) dict))) + 'post) + (loop (read-line input 'concat)))))) + +(close-port input) + +;; @@PLEAC@@_2.1 +;; Strings and numbers are separate data types in Scheme, so this +;; isn't as important as it is in Perl. More often you would use the +;; type predicates, string? and number?. + +(if (string-match "[^\\d]" str) (display "has nondigits")) +(or (string-match "^\\d+$" str) (display "not a natural number")) +(or (string-match "^-?\\d+$" str) (display "not an integer")) +(or (string-match "^[\\-+]?\\d+$" str) (display "not an integer")) +(or (string-match "^-?\\d+\.?\d*$" str) (display "not a decimal number")) +(or (string-match "^-?(\d+(\.\d*)?|\.\d+)$" str) + (display "not a decimal number")) +(or (string-match "^([+-]?)(\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$" str) + (display "not a C float")) + +(define num1 (string->number str)) + +(define num2 (read)) + +;; @@PLEAC@@_2.2 +;; (approx-equal? num1 num2 accuracy) : returns #t if num1 and num2 are +;; equal to accuracy number of decimal places +(define (approx-equal? num1 num2 accuracy) + (< (abs (- num1 num2)) (expt 10.0 (- accuracy)))) + +(define wage 536) ;; $5.36/hour +(define week (* 40 wage)) ;; $214.40 +(format #t "One week's wage is: $~$\n" (/ week 100.0)) + +;; @@PLEAC@@_2.3 +(round num) ;; rounds to inexact whole number +(inexact->exact num) ;; rounds to exact integer + +;; You can also use format to convert numbers to more precisely +;; formatted strings. Note Guile has a builtin format which is a more +;; limited version of that found in the (ice-9 format) module, to save +;; load time. Basically, if you are doing anything you couldn't do +;; with a series of (display), (write) and (newline), then you'll need +;; to use the module. +(use-modules (ice-9 format)) + +(define a 0.255) +(define b (/ (round (* 100.0 a)) 100.0)) +(format #t "Unrounded: ~F\nRounded: ~F\n" a b) +(format #t "Unrounded: ~F\nRounded: ~,2F\n" a a) + +(define a '(3.3 3.5 3.7 -3.3)) +(display "number\tint\tfloor\tceil\n") +(for-each + (lambda (n) + (format #t "~,1F\t~,1F\t~,1F\t~,1F\n" + n (round n) (floor n) (ceiling n))) + a) + +;; @@PLEAC@@_2.4 +;; numbers are radix independent internally, so you usually only +;; convert on output, however to convert strings: +(define (dec->bin num) + (number->string (string->number num 10) 2)) + +(define (bin->dec num) + (number->string (string->number num 2) 10)) + +(define num (bin->dec "0110110")) ; 54 +(define binstr (dec->bin "54")) ; 110110 + +;; @@PLEAC@@_2.5 +;; do is the most general loop iterator +(do ((i x (1+ i))) ; var init-value step-value + ((> i y)) ; end when true + ;; i is set to every integer from x to y, inclusive + ;; ... + ) + +;; Guile also offers a while loop +(let ((i x)) + (while (<= i y) + ;; i is set to every integer from x to y, inclusive + ; ... + (set! i (1+ i)))) + +;; named let is another common loop +(let loop ((i x)) + (cond ((<= i y) + ;; i is set to every integer from x to y, step-size 7 + ;; ... + (loop (+ i 7))))) ; tail-recursive call + +(display "Infancy is: ") +(do ((i 0 (1+ i))) + ((> i 2)) + (format #t "~A " i)) +(newline) + +(display "Toddling is: ") +(let ((i 3)) + (while (<= i 4) + (format #t "~A " i) + (set! i (1+ i)))) +(newline) + +(display "Childhood is: ") +(let loop ((i 5)) + (cond ((<= i 12) + (format #t "~A " i) + (loop (1+ i))))) +(newline) + +;; @@PLEAC@@_2.6 +;; format can output roman numerals - use ~:@R + +(use-modules (ice-9 format)) + +(format #t "Roman for ~R is ~:@R\n" 15 15) + +;; @@PLEAC@@_2.7 +(random 5) ; an integer from 0 to 4 +(random 5.0) ; an inexact real in the range [0,5) + +;; char sets from SRFI-14 and string-unfold from SRFI-13 make a quick +;; way to generate passwords + +(use-modules (srfi srfi-13) (srfi srfi-14)) + +(define chars (char-set->string char-set:graphic)) +(define size (char-set-size char-set:graphic)) +(define password + (string-unfold (lambda (x) (= x 8)) + (lambda (x) (string-ref chars (random size))) + 1+ 0)) + +;; @@PLEAC@@_2.8 +;; if you're working with random numbers you'll probably want to set +;; the random seed + +(seed->random-state (current-time)) + +;; you can also save random states and pass them to any of the above +;; random functions + +(define state (copy-random-state)) +(random:uniform) +;; 0.939377327721761 +(random:uniform state) +;; 0.939377327721761 + +;; @@PLEAC@@_2.9 +;; @@INCOMPLETE@@ +;; very inefficient +(use-modules (ice-9 rw)) +(define make-true-random + (letrec ((bufsize 8) + (accum (lambda (c acc) (+ (* 256 acc) + (char->integer c)))) + (getbuf (lambda () + (call-with-input-file "/dev/urandom" + (lambda (p) + (let ((buf (make-string bufsize))) + (read-string!/partial buf p) + buf)))))) + (lambda (rand-proc) + (lambda args + (let ((state (seed->random-state (string-fold accum 0 (getbuf))))) + (apply rand-proc (append args (list state)))))))) + +(define urandom (make-true-random random)) +(define urandom:exp (make-true-random random:exp)) +(define urandom:normal (make-true-random random:normal)) +(define urandom:uniform (make-true-random random:uniform)) + +;; @@PLEAC@@_2.10 +;; Guile offers a number of random distributions + +(random:exp) ; an inexact real in an exponential dist with mean 1 +(random:normal) ; an inexact real in a standard normal distribution +(random:uniform) ; a uniformly distributed inexact real in [0,1) + +;; There are also functions to fill vectors with random distributions + +;; Fills vector v with inexact real random numbers the sum of whose +;; squares is equal to 1.0. +(random:hollow-sphere! v) + +;; Fills vector v with inexact real random numbers that are +;; independent and standard normally distributed (i.e., with mean 0 +;; and variance 1). +(random:normal-vector! v) + +;; Fills vector v with inexact real random numbers the sum of whose +;; squares is less than 1.0. +(random:solid-sphere! v) + +;; @@PLEAC@@_2.11 +;; Guile's trigonometric functions use radians. + +(define pi 3.14159265358979) + +(define (degrees->radians deg) + (* pi (/ deg 180.0))) + +(define (radians->degrees rad) + (* 180.0 (/ rad pi))) + +(define (degree-sine deg) + (sin (degrees->radians deg))) + +;; @@PLEAC@@_2.12 + +;; Guile provides the following standard trigonometric functions (and +;; their hyperbolic equivalents), defined for all real and complex +;; numbers: + +(sin z) +(cos z) +(tan z) +(asin z) +(acos z) +(atan z) + +(acos 3.7) ; 0.0+1.9826969446812i + +;; @@PLEAC@@_2.13 +;; Guile provides log in base e and 10 natively, defined for any real +;; or complex numbers: + +(log z) ; natural logarithm +(log10 z) ; base-10 logarithm + +;; For other bases, divide by the log of the base: + +(define (log-base n z) + (/ (log z) (log n))) + +;; To avoid re-computing (log n) for a base you want to use +;; frequently, you can create a custom log function: + +(define (make-log-base n) + (let ((divisor (log n))) + (lambda (z) (/ (log z) divisor)))) + +(define log2 (make-log-base 2)) + +(log2 1024) + +;; @@PLEAC@@_2.14 +;; In addition to simple vectors, Guile has builtin support for +;; uniform arrays of an arbitrary dimension. + +;; a rows x cols integer matrix +(define a (make-array 0 rows cols)) +(array-set! a 3 row col) +(array-ref a row col) + +;; a 3D matrix of reals +(define b (make-array 0.0 x y z)) + +;; a literal boolean truth table for logical and +'#2((#f #f) (#f #t)) + +;; simple matrix multiplication + +(define (matrix-mult m1 m2) + (let* ((d1 (array-dimensions m1)) + (d2 (array-dimensions m2)) + (m1rows (car d1)) + (m1cols (cadr d1)) + (m2rows (car d2)) + (m2cols (cadr d2))) + (if (not (= m1cols m2rows)) + (error 'index-error "matrices don't match")) + (let ((result (make-array 0 m1rows m2cols))) + (do ((i 0 (1+ i))) + ((= i m1rows)) + (do ((j 0 (1+ j))) + ((= j m2cols)) + (do ((k 0 (1+ k))) + ((= k m1cols)) + (array-set! result (+ (array-ref result i j) + (* (array-ref m1 i k) + (array-ref m2 k j))) + i j)))) + result))) + +(matrix-mult '#2((3 2 3) (5 9 8)) '#2((4 7) (9 3) (8 1))) + +;; @@PLEAC@@_2.15 +;; Guile has builtin support for complex numbers: + +(define i 0+1i) ; 0.0+1.0i +(define i (sqrt -1)) ; 0.0+1.0i + +(complex? i) ; #t +(real-part i) ; 0.0 +(imag-part i) ; 1.0 + +(* 3+5i 2-2i) ; 16+4i +(sqrt 3+4i) ; 2+i + +;; Classic identity: -e^(pi*i) => 1 +(inexact->exact (real-part (- (exp (* pi 0+1i))))) ; 1 + +;; @@PLEAC@@_2.16 +;; You can type in literal numbers in alternate radixes: + +#b01101101 ; 109 in binary +#o155 ; 109 in octal +#d109 ; 109 in decimal +#x6d ; 109 in hexadecimal + +;; number->string and string->number also take an optional radix: + +(define number (string->number hexadecimal 16)) +(define number (string->number octal 8)) + +;; format will also output in different radixes: + +(format #t "~B ~O ~D ~X\n" num num num num) + +;; converting Unix file permissions read from stdin: + +(let loop ((perm (read-line))) + (cond ((not (eof-object? perm)) + (format #t "The decimal value is ~D\n" (string->number perm 8)) + (loop (read-line))))) + +;; @@PLEAC@@_2.17 +;; once again, format is our friend :) +(use-modules (ice-9 format)) + +;; the : prefix to the D directive causes commas to be output every +;; three digits. +(format #t "~:D\n" (random 10000000000000000)) +; => 2,301,267,079,619,540 + +;; the third prefix arg to the D directive is the separator character +;; to use instead of a comma, useful for European style numbers: +(format #t "~,,'.:D\n" (random 10000000000000000)) +; => 6.486.470.447.356.534 + +;; the F directive, however, does not support grouping by commas. to +;; achieve this, we can format the integer and fractional parts +;; separately: +(define (commify num) + (let ((int (inexact->exact (truncate num)))) + (if (= num int) + (format #f "~:D" int) + (string-append (format #f "~:D" int) + (let ((str (format #f "~F" num))) + (substring str (or (string-index str #\.) + (string-length str)))))))) + +;; @@PLEAC@@_2.18 +;; format can handle simple 's' plurals with ~p, and 'y/ies' plurals +;; with the @ prefix: + +(format #t "It took ~D hour~P\n" hours hours) + +(format #t "It took ~D centur~@P\n" centuries centuries) + +(define noun-plural + (let* ((suffixes '(("ss" . "sses") + ("ph" . "phes") + ("sh" . "shes") + ("ch" . "ches") + ("z" . "zes") + ("ff" . "ffs") + ("f" . "ves") + ("ey" . "eys") + ("y" . "ies") + ("ix" . "ices") + ("s" . "ses") + ("x" . "xes") + ("ius" . "ii"))) + (suffix-match + (string-append "(" (string-join (map car suffixes) "|") ")$")) + (suffix-rx (make-regexp suffix-match))) + (lambda (noun) + (let ((m (regexp-exec suffix-rx noun))) + (if m + (string-append (regexp-substitute #f m 'pre) + (cdr (assoc (match:substring m) suffixes))) + (string-append noun "s")))))) + +;; @@PLEAC@@_2.19 +#!/usr/local/bin/guile -s +!# + +;; very naive factoring algorithm +(define (factor n) + (let ((factors '()) + (limit (inexact->exact (round (sqrt n)))) + (twos 0)) + ;; factor out 2's + (while (even? n) + (set! n (ash n -1)) + (set! twos (1+ twos))) + (if (> twos 0) (set! factors (list (cons 2 twos)))) + ;; factor out odd primes + (let loop ((i 3)) + (let ((r (remainder n i))) + (cond ((= r 0) + (set! n (quotient n i)) + (let* ((old-val (assv i factors)) + (new-val (if old-val (1+ (cdr old-val)) 1))) + (set! factors (assv-set! factors i new-val))) + (loop i)) + ((< i limit) + (loop (+ 2 i)))))) + ;; remainder + (if (> n 1) (set! factors (cons (cons n 1) factors))) + (reverse! factors))) + +;; pretty print a term of a factor +(define (pp-term pair) + (if (= (cdr pair) 1) + (number->string (car pair)) + (format #f "~A^~A" (car pair) (cdr pair)))) + +;; factor each number given on the command line +(for-each + (lambda (n) + (let ((factors (factor n))) + (format #t "~A = ~A" n (pp-term (car factors))) + (for-each + (lambda (x) (format #t " * ~A" (pp-term x))) + (cdr factors)) + (newline))) + (map string->number (cdr (command-line)))) + +;; @@PLEAC@@_3.0 +;; Use the builtin POSIX time functions + +;; get the current time +(current-time) ; number of seconds since the epoch +(gettimeofday) ; pair of seconds and microseconds since the epoch + +;; create a time object from an integer (e.g. returned by current-time) +(localtime time) ; in localtime +(gmtime time) ; in UTC + +;; get/set broken down components of a time object + +(tm:sec time) (set-tm:sec time secs) ; seconds (0-59) +(tm:min time) (set-tm:min time mins) ; minutes (0-59) +(tm:hour time) (set-tm:hour time hours) ; hours (0-23) +(tm:mday time) (set-tm:mday time mday) ; day of the month (1-31) +(tm:mon time) (set-tm:mon time month) ; month (0-11) +(tm:year time) (set-tm:year time year) ; year minus 1900 (70-) +(tm:wday time) (set-tm:wday time wday) ; day of the week (0-6) + ; where Sunday is 0 +(tm:yday time) (set-tm:yday time yday) ; day of year (0-365) +(tm:isdst time) (set-tm:isdst time isdst) ; daylight saving indicator + ; 0 for "no", > 0 for "yes", + ; < 0 for "unknown" +(tm:gmtoff time) (set-tm:gmtoff time off) ; time zone offset in seconds + ; west of UTC (-46800 to 43200) +(tm:zone time) (set-tm:zone time zone) ; Time zone label (a string), + ; not necessarily unique. + +(format #t "Today is day ~A of the current year.\n" + (tm:yday (localtime (current-time)))) + +;; Or use SRFI-19 - Time and Date Procedures +(use-modules (srfi srfi-19)) + +(define now (current-date)) ; immutable once created + +(date-nanosecond now) ; 0-9,999,999 +(date-second now) ; 0-60 (60 represents a leap second) +(date-minute now) ; 0-59 +(date-hour now) ; 0-23 +(date-day now) ; 0-31 +(date-month now) ; 1-12 +(date-year now) ; integer representing the year +(date-year-day now) ; day of year (Jan 1 is 1, etc.) +(date-week-day now) ; day of week (Sunday is 0, etc.) +(date-week-number now start) ; week of year, ignoring a first partial week + ; start is the first day of week as above +(date-zone-offset now) ; integer number of seconds east of GMT + +(format #t "Today is day ~A of the current year.\n" + (date-year-day (current-date))) + +;; @@PLEAC@@_3.1 +;; using format and POSIX time components +(use-modules (ice-9 format)) +(let ((now (localtime (current-time)))) + (format #t "The current date is ~4'0D ~2'0D ~2'0D\n" + (+ 1900 (tm:year now)) (tm:mon now) (tm:mday now))) + +;; using format and SRFI-19 time components +(use-modules (srfi srfi-19) (ice-9 format)) +(let ((now (current-date))) + (format #t "The current date is ~4'0d-~2'0D-~2'0D\n" + (date-year now) (date-month now) (date-day now))) + +;; using POSIX strftime with a libc time format string +(display (strftime "%Y-%m-%d\n" (localtime (current-time)))) + +;; @@PLEAC@@_3.2 +;; set the individual components of a time struct and use mktime +(define time (localtime (current-time))) +(set-tm:mday time mday) +(set-tm:mon time mon) +(set-tm:year time year) +(car (mktime time)) ; mktime returns a (epoch-seconds . time) pair + +;; or use SRFI-19's make-date and date->time-monotonic +(use-modules (srfi srfi-19)) +(date->time-monotonic + (make-date nanosecond second minute hour day month year zone-offset)) + +;; @@PLEAC@@_3.3 +;; use localtime or gmtime with the accessors mentioned in the +;; introduction to this chapter +(let ((time (localtime seconds))) ; or gmtime + (format #t "Dateline: ~2'0d:~2'0d:~2'0d-~4'0d/~2'0d/~2'0d\n" + (tm:hour time) (tm:min time) (tm:sec time) + (+ 1900 (tm:year time)) (1+ (tm:mon time)) (tm:mday time))) + +;; or use SRFI-19 +(use-modules (srfi srfi-19)) +(let* ((time (make-time time-monotonic nanosecond second))) + (display (date->string (time-monotonic->date time) "~T-~1\n"))) + +;; @@PLEAC@@_3.4 +;; just add or subtract epoch seconds +(define when (+ now difference)) +(define then (- now difference)) + +;; if you have DMYHMS values, you can convert them to times or add +;; them as seconds: +(define birthtime 96176750) +(define interval (+ 5 ; 5 seconds + (* 17 60) ; 17 minutes + (* 2 60 60) ; 2 hours + (* 55 60 60 24))) ; and 55 days +(define then (+ birthtime interval)) +(format #t "Then is ~A\n" (strftime "%a %b %d %T %Y" (localtime then))) + +;; @@PLEAC@@_3.5 +;; subtract the epoch seconds: +(define bree 361535725) +(define nat 96201950) +(define difference (- bree nat)) +(format #t "There were ~A seconds between Nat and Bree\n" difference) + +;; or use SRFI-19's time arithmetic procedures: +(use-modules (srfi srfi-19)) +(define time1 (make-time time-monotonic nano1 sec1)) +(define time2 (make-time time-monotonic nano2 sec2)) +(define duration (time-difference time1 time2)) +(time=? (subtract-duration time1 duration) time2) ; #t +(time=? (add-duration time2 duration) time1) ; #t + +;; @@PLEAC@@_3.6 +;; convert to a SRFI-19 date and use the accessors +(use-modules (srfi srfi-19)) +(date-day date) +(date-year-day date) +(date-week-day date) +(date-week-number date start-day-of-week) + +;; @@PLEAC@@_3.7 +;; use the strptime function: +(define time-pair (strptime "%Y-%m-%d" "1998-06-03")) +(format #t "Time is ~A\n." (strftime "%b %d, %Y" (car time-pair))) + +;; or use SRFI-19's string->date: +(use-modules (srfi srfi-19)) +(define date (string->date "1998-06-03" "~Y-~m-~d")) +(format #t "Time is ~A.\n" (date->string date)) + +;; @@PLEAC@@_3.8 +;; use the already seen strftime: +(format #t "strftime gives: ~A\n" + (strftime "%A %D" (localtime (current-time)))) + +;; or SRFI-19's date->string: +(use-modules (srfi srfi-19)) +(format #t "default date->string gives: ~A\n" (date->string (current-date))) +(format #t "date->string gives: ~A\n" + (date->string (current-date) "~a ~b ~e ~H:~M:~S ~z ~Y")) + +;; @@PLEAC@@_3.9 +;; gettimeofday will return seconds and microseconds: +(define t0 (gettimeofday)) +;; do your work here +(define t1 (gettimeofday)) +(format #t "You took ~A seconds and ~A microseconds\n" + (- (car t1) (car t0)) (- (cdr t1) (cdr t0))) + +;; you can also get more detailed info about the real and processor +;; times: +(define runtime (times)) +(tms:clock runtime) ; the current real time +(tms:utime runtime) ; the CPU time units used by the calling process +(tms:stime runtime) ; the CPU time units used by the system on behalf + ; of the calling process. +(tms:cutime runtime) ; the CPU time units used by terminated child + ; processes of the calling process, whose status + ; has been collected (e.g., using `waitpid'). +(tms:cstime runtime) ; the CPU times units used by the system on + ; behalf of terminated child processes + +;; you can also use the time module to time execution: +(use-modules (ice-9 time)) +(time (sleep 3)) +;; clock utime stime cutime cstime gctime +;; 3.01 0.00 0.00 0.00 0.00 0.00 +;; 0 + +;; @@PLEAC@@_3.10 +(sleep i) ; sleep for i seconds +(usleep i) ; sleep for i microseconds (not available on all platforms) + +;; @@PLEAC@@_4.0 +(define nested '("this" "that" "the" "other")) +(define nested '("this" "that" ("the" "other"))) +(define tune '("The" "Star-Spangled" "Banner")) + +;; @@PLEAC@@_4.1 +(define a '("quick" "brown" "fox")) +(define a '("Why" "are" "you" "teasing" "me?")) + +(use-modules (srfi srfi-13)) +(define lines + (map string-trim + (string-tokenize "\ + The boy stood on the burning deck, + It was as hot as glass." + #\newline))) + +(define bigarray + (with-input-from-file "mydatafile" + (lambda () + (let loop ((lines '()) + (next-line (read-line))) + (if (eof-object? next-line) + (reverse lines) + (loop (cons next-line lines) + (read-line))))))) + +(define banner "The Mines of Moria") + +(define name "Gandalf") +(define banner + (string-append "Speak, " name ", and enter!")) +(define banner + (format #f "Speak, ~A, and welcome!" name)) + +;; Advanced shell-like function is provided by guile-scsh, the Guile +;; port of SCSH, the Scheme shell. Here we roll our own using the +;; pipe primitives that come with core Guile. +(use-modules (ice-9 popen)) + +(define (drain-output port) + (let loop ((chars '()) + (next (read-char port))) + (if (eof-object? next) + (list->string (reverse! chars)) + (loop (cons next chars) + (read-char port))))) + +(define (qx pipeline) + (let* ((pipe (open-input-pipe pipeline)) + (output (drain-output pipe))) + (close-pipe pipe) + output)) + +(define his-host "www.perl.com") +(define host-info (qx (format #f "nslookup ~A" his-host))) + +(define perl-info (qx (format #f "ps ~A" (getpid)))) +(define shell-info (qx "ps $$")) + +(define banner '("Costs" "only" "$4.95")) +(define brax (map string (string->list "()<>{}[]"))) +(define rings (string-tokenize "Nenya Narya Vilya")) +(define tags (string-tokenize "LI TABLE TR TD A IMG H1 P")) +(define sample + (string-tokenize "The vertical bar (|) looks and behaves like a pipe.")) +(define ships '("Niña" "Pinta" "Santa MarÃa")) + +;; @@PLEAC@@_4.2 +(define array '("red" "yellow" "green")) + +(begin + (display "I have ") + (for-each display array) + (display " marbles.\n")) +;; I have redyellowgreen marbles. + +(begin + (display "I have ") + (for-each (lambda (colour) + (display colour) + (display " ")) + array) + (display "marbles.\n")) +;; I have red yellow green marbles. + +;; commify - insertion of commas into list output +(define (commify strings) + (let ((len (length strings))) + (case len + ((0) "") + ((1) (car strings)) + ((2) (string-append (car strings) " and " (cadr strings))) + ((3) (string-append (car strings) ", " + (cadr strings) ", and " + (caddr strings))) + (else + (string-append (car strings) ", " + (commify (cdr strings))))))) + +(define lists '(("just one thing") + ("Mutt" "Jeff") + ("Peter" "Paul" "Mary") + ("To our parents" "Mother Theresa" "God") + ("pastrami" "ham and cheese" "peanut butter and jelly" "tuna") + ("recycle tired, old phrases" "ponder big, happy thoughts") + ("recycle tired, old phrases" + "ponder big, happy thoughts" + "sleep and dream peacefully"))) + +(for-each (lambda (list) + (display "The list is: ") + (display (commify list)) + (display ".\n")) + lists) + +;; The list is: just one thing. +;; The list is: Mutt and Jeff. +;; The list is: Peter, Paul, and Mary. +;; The list is: To our parents, Mother Theresa, and God. +;; The list is: pastrami, ham and cheese, peanut butter and jelly, and tuna. +;; The list is: recycle tired, old phrases and ponder big, happy thoughts. +;; The list is: recycle tired, old phrases, ponder big, happy thoughts, and sleep and dream peacefully. + +;; @@PLEAC@@_4.3 +;;----------------------------- + +;; Scheme does not normally grow and shrink arrays in the way that +;; Perl can. The more usual operations are adding and removing from +;; the head of a list using the `cons' and `cdr' procedures. +;; However ... +(define (grow/shrink list new-size) + (let ((size (length list))) + (cond ((< size new-size) + (grow/shrink (cons "" list) new-size)) + ((> size new-size) + (grow/shrink (cdr list) new-size)) + (else list)))) + +(define (element list i) + (list-ref list (- (length list) i 1))) + +(define (set-element list i value) + (if (>= i (length list)) + (set! list (grow/shrink list (- i 1)))) + (set-car! (list-cdr-ref list (- (length list) i 1))) + list) + +(define (what-about list) + (let ((len (length list))) + (format #t "The array now has ~A elements.\n" len) + (format #t "The index of the last element is ~A.\n" (- len 1)) + (format #t "Element #3 is `~A'.\n" (if (> len 3) + (element list 3) + "")))) + +;; In the emulation of Perl arrays implemented here, the elements are +;; in reverse order when compared to normal Scheme lists. +(define people (reverse '("Crosby" "Stills" "Nash" "Young"))) +(what-about people) +;;----------------------------- +;; The array now has 4 elements. +;; The index of the last element is 3. +;; Element #3 is `Young'. +;;----------------------------- +(set! people (grow/shrink people 3)) +(what-about people) +;;----------------------------- +;; The array now has 3 elements. +;; The index of the last element is 2. +;; Element #3 is `'. +;;----------------------------- +(set! people (grow/shrink people 10001)) +(what-about people) +;;----------------------------- +;; The array now has 10001 elements. +;; The index of the last element is 10000. +;; Element #3 is `'. +;;----------------------------- + +;; @@PLEAC@@_4.4 +; Using a 'list' i.e. chain of pairs +(define *mylist* '(1 2 3)) + +; Apply procedure to each member of 'mylist' +(for-each + (lambda (item) (print item)) + *mylist*) + +;; ------------ + +; Using a 'vector' i.e. one-dimensional array +(define *bad-users* '#("lou" "mo" "sterling" "john")) + +(define (complain user) + (print "You're a *bad user*," user)) + +(array-for-each + (lambda (user) (complain user)) + *bad-users*) + +;; ------------ + +; Could probably get away with sorting a list of strings ... +(define *sorted-environ* + (sort (environ) string<?)) + +(for-each + (lambda (var) (display var) (newline)) + *sorted-environ*) + +;; ---- + +; ... but the intent here is to sort a hash table, so we'll use +; an 'assoc', Scheme's native dictionary type, which is really +; nothing more than a list of conses / dotted pairs [hash tables +; will be used in later examples] +(define (cons->env-string a) + (string-append (car a) "=" (cdr a))) + +(define (env-string->cons s) + (let ((key-value (string-split s #\=))) + (cons (car key-value) (cadr key-value)))) + +(define *sorted-environ-assoc* + (sort + (map + (lambda (var) (env-string->cons var)) + (environ)) + (lambda (left right) (string<? (car left) (car right))) )) + +(for-each + (lambda (var) + (print (car var) "=" (cdr var))) + *sorted-environ-assoc*) + +;; ---------------------------- + +(define *MAX-QUOTA* 100) + +(define (get-all-users) ...) +(define (get-usage user) ...) +(define (complain user) ...) + +(for-each + (lambda (user) + (let ((disk-usage (get-usage user))) + (if (> disk-usage *MAX-QUOTA*) + (complain user)))) + (get-all-users)) + +;; ---------------------------- + +(for-each + (lambda (user) (if (string=? user "tchrist") (print user))) + (string-split (qx "who|cut -d' ' -f1|uniq") #\newline)) + +;; ---------------------------- + +(use-modules (srfi srfi-13) (srfi srfi-14)) + +(do ((line (read-line) (read-line))) + ((eof-object? line)) + (for-each + (lambda (word) (print (string-reverse word))) + (string-tokenize line char-set:graphic))) + +;; ---------------------------- + +; Updates vector in-place [accepts variable number of vectors] +; See also the library function, 'array-map-in-order!' and its +; brethren +(define (vector-map-in-order! proc vec . rest) + (let ((all-vec (cons vec rest))) + (for-each + (lambda (vec) + (let ((end (vector-length vec))) + (let loop ((idx 0)) + (cond + ((= idx end) '()) + (else + (vector-set! vec idx (apply proc (list (vector-ref vec idx)))) + (loop (+ idx 1)))) ))) + all-vec))) + +;; ---- + +; A non-mutating version - illustration only, as library routines +; [SRFI-43 and built-ins] should be preferred +(define (vector-map-in-order proc vec . rest) + (let* ((all-vec (cons vec rest)) + (new-vec-len (reduce + 0 (map vector-length all-vec))) + (new-vec (make-vector new-vec-len)) + (new-vec-idx 0)) + (let loop ((all-vec all-vec)) + (cond + ((= new-vec-idx new-vec-len) new-vec) + (else + (array-for-each + (lambda (element) + (vector-set! new-vec new-vec-idx (apply proc (list element))) + (set! new-vec-idx (+ new-vec-idx 1))) + (car all-vec)) + (loop (cdr all-vec)) ))) )) + +;; ------------ + +(define *array* '#(1 2 3)) + +(array-for-each + (lambda (item) + (print "i =" item)) + *array*) + +;; ------------ + +(define *array* '#(1 2 3)) + +(array-for-each + (lambda (item) + (print "i =" item)) + *array*) + +; Since a 'vector' is mutable, in-place updates allowed +(vector-map-in-order! + (lambda (item) (- item 1)) + *array*) + +(print *array*) + +;; ------------ + +(define *a* '#(0.5 3)) +(define *b* '#(0 1)) + +(vector-map-in-order! + (lambda (item) (* item 7)) + *a* *b*) + +(print *a* *b*) + +;; ---------------------------- + +; Using 'for-each' to iterate over several container items is a +; simple matter of passing a list of those items e.g. a list of +; strings, or of arrays etc. +; +; However, complications arise when: +; * Heterogenous list of items e.g. list contains all of arrays, +; hashes, strings, etc. Necesitates different handling based on type +; * Item needs updating. It is not possible to alter the item reference +; and updating an item's internals is only possible if the relevant +; mutating procedures are implemented e.g. specified string characters +; may be altered in-place, but character deletion requires a new be +; created [i.e. altering the item reference], so is not possible + +(define *scalar* "123 ") +(define *array* '#(" 123 " "456 ")) +(define *hash* (list (cons "key1" "123 ") (cons "key2" " 456"))) + +; Illustrates iteration / handling of heterogenous types +(for-each + (lambda (item) + (cond + ((string? item) (do-stuff-with-string item)) + ((vector? item) (do-stuff-with-vector item)) + ((pair? item) (do-stuff-with-hash item)) + (else (print "unknown type")))) + (list *scalar* *array* *hash*)) + +; So, for item-replacement-based updating you need to use explicit +; iteration e.g. 'do' loop, or recursion [as is done in the code for +; 'vector-map-in-order!'] - examples in next section. Or, you could +; create a new 'for-each' type control structure using Scheme's +; macro facility [example not shown] + +;; @@PLEAC@@_4.5 +(define *array* '#(1 2 3)) + +;; ---- + +; Whilst a 'vector' is mutable, 'array-for-each' passes only a copy +; of each cell, thus there is no way to perform updates +(array-for-each + (lambda (item) + ... do some non-array-mutating task with 'item'...) + *array*) + +;; ------------ + +; For mutating operations, use one of the mutating 'array-map-...' routines +; or the custom, 'vector-map-in-order!' +(vector-map-in-order! + (lambda (item) + ... do some array-mutating task with 'item'...) + *array*) + +;; ------------ + +; Alternatively, use 'do' to iterate over the array and directly update +(let ((vector-length (vector-length *array*))) + (do ((i 0 (+ i 1))) + ((= i vector-length)) + ... do some array-mutating task with current array element ...)) + +;; ------------ + +; Alternatively, use a 'named let' to iterate over array and directly update +(let ((vector-length (vector-length *array*))) + (let loop ((i 0)) + (cond + ((= i vector-length) '()) + (else + ... do some array-mutating task with current array element ... + (loop (+ i 1)))) )) + +;; ---------------------------- + +(define *fruits* '#("Apple" "Blackberry")) + +;; ------------ + +(array-for-each + (lambda (fruit) + (print fruit "tastes good in a pie.")) + *fruits*) + +;; ------------ + +(let ((vector-length (vector-length *fruits*))) + (do ((i 0 (+ i 1))) + ((= i vector-length)) + (print (vector-ref *fruits* i) "tastes good in a pie.") )) + +;; ---------------------------- + +(define *rogue-cats* '("Blacky" "Ginger" "Puss")) + +(define *name-list* (acons 'felines *rogue-cats* '())) + +;; ------------ + +(for-each + (lambda (cat) + (print cat "purrs hypnotically..")) + (cdr (assoc 'felines *name-list*))) + +;; ------------ + +(let loop ((felines (cdr (assoc 'felines *name-list*)))) + (cond + ((null? felines) '()) + (else + (print (car felines) "purrs hypnotically..") + (loop (cdr felines))))) + +;; @@PLEAC@@_4.6 +(use-modules (srfi srfi-1)) + +; Simplest [read: least code] means of removing duplicates is to use +; SRFI-1's 'delete-duplicates' routine + +(define *non-uniq-num-list* '(1 2 3 1 2 3)) +(define *uniq* (delete-duplicates *my-non-uniq-num-list*) + +;; ------------ + +(use-modules (srfi srfi-1)) + +; Another simple alternative is to use SRFI-1's 'lset-union' routine. In +; general, the 'lset-...' routines: +; - convenient, but not fast; probably best avoided for 'large' sets +; - operate on standard lists, so simple matter of type-converting arrays and such +; - care needs to be taken in choosing the needed equality function + +(define *non-uniq-string-list* '("abc" "def" "ghi" "abc" "def" "ghi")) +(define *uniq* (lset-union string=? *non-uniq-string-list* *non-uniq-string-list*)) + +;; ---- + +(define *non-uniq-sym-list* '('a 'b 'c 'a 'b 'c)) +(define *uniq* (lset-union equal? *my-non-uniq-sym-list* *my-non-uniq-sym-list*)) + +;; ---- + +(define *non-uniq-num-list* '(1 2 3 1 2 3)) +(define *uniq* (lset-union = *my-non-uniq-num-list* *my-non-uniq-num-list*)) + +;; ---------------------------- + +;; Perl Cookbook-based examples - illustrative only, *not* recommended approaches + +(use-modules (srfi srfi-1)) + +(define *list* '(1 2 3 1 2 7 8 1 8 2 1 3)) +(define *seen* '()) + +; Use hash to filter out unique items +(for-each + (lambda (item) + (if (not (assoc-ref *seen* item)) + (set! *seen* (assoc-set! *seen* item #t)))) + *list*) + +; Generate list of unique items +(define *uniq* + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + *seen*)) + +;; ------------ + +(define *list* '(1 2 3 1 2 7 8 1 8 2 1 3)) +(define *seen* '()) + +; Build list of unique items by checking set membership +(for-each + (lambda (item) + (if (not (member item *seen*)) + (set! *seen* (cons item *seen*)))) + *list*) + +;; ------------ + +(define *users* + (sort + (string-split (qx "who|cut -d' ' -f1") #\newline) + string<?)) + +(define *seen* '()) + +; Build list of unique users by checking set membership +(for-each + (lambda (user) + (if (not (member user *seen*)) + (set! *seen* (cons item *seen*)))) + *list*) + +;; @@PLEAC@@_4.7 +; All problems in this section involve, at core, set difference +; operations. Thus, the most compact and straightforward approach is +; to utilise SRFI-1's 'lset-difference' routine + +(use-modules (srfi srfi-1)) + +(define *a* '(1 3 5 6 7 8)) +(define *b* '(2 3 5 7 9)) + +; *difference* contains elements in *a* but not in *b*: 1 6 8 +(define *difference* (lset-difference = *a* *b*)) + +; *difference* contains elements in *b* but not in *a*: 2 9 +(set! *difference* (lset-difference = *b* *a*)) + +;; ---------------------------- + +;; Perl Cookbook-based example - illustrative only, *not* recommended approaches + +(use-modules (srfi srfi-1)) + +(define *a* '(1 3 5 6 7 8)) +(define *b* '(2 3 5 7 9)) + +(define *a-only* '()) + +; Build list of items in *a* but not in *b* +(for-each + (lambda (item) + (if (not (member item *b*)) + (set! *a-only* (cons item *a-only*)))) + *a*) + +;; @@PLEAC@@_4.8 +; The SRFI-1 'lset-xxx' routines are appropriate here + +(use-modules (srfi srfi-1)) + +(define *a* '(1 3 5 6 7 8)) +(define *b* '(2 3 5 7 9)) + +; Combined elements of *a* and *b* sans duplicates: 1 2 3 5 6 7 8 9 +(define *union* (lset-union = *a* *b*)) + +; Elements common to both *a* and *b*: 3 5 7 +(define *intersection* (lset-intersection = *a* *b*)) + +; Elements in *a* but not in *b*: 1 6 8 +(define *difference* (lset-difference = *a* *b*)) + +;; ---------------------------- + +;; Perl Cookbook-based example - illustrative only, *not* recommended approaches + +(use-modules (srfi srfi-1)) + +(define *a* '(1 3 5 6 7 8)) +(define *b* '(2 3 5 7 9)) + +(define *union* '()) +(define *isect* '()) +(define *diff* '()) + +;; ------------ + +; Union and intersection +(for-each + (lambda (item) (set! *union* (assoc-set! *union* item #t))) + *a*) + +(for-each + (lambda (item) + (if (assoc-ref *union* item) + (set! *isect* (assoc-set! *isect* item #t))) + (set! *union* (assoc-set! *union* item #t))) + *b*) + +; Difference *a* and *b* +(for-each + (lambda (item) + (if (not (assoc-ref *isect* item)) + (set! *diff* (assoc-set! *diff* item #t)))) + *a*) + +(set! *union* + (fold + (lambda (pair accum) (cons (car pair) accum)) + '() + *union*)) + +(set! *isect* + (fold + (lambda (pair accum) (cons (car pair) accum)) + '() + *isect*)) + +(set! *diff* + (fold + (lambda (pair accum) (cons (car pair) accum)) + '() + *diff*)) + +(print "Union count: " (length *union*)) +(print "Intersection count:" (length *isect*)) +(print "Difference count: " (length *diff*)) + +;; @@PLEAC@@_4.9 +; Arrays, specifically vectors in the current context, are fixed-size +; entities; joining several such together requires copying of their +; contents into a new, appropriately-sized, array. This task may be +; performed: + +; * Directly: loop through existing arrays copying elements into a +; newly-created array + +(define (vector-join vec . rest) + (let* ((all-vec (cons vec rest)) + (new-vec-len (reduce + 0 (map vector-length all-vec))) + (new-vec (make-vector new-vec-len)) + (new-vec-idx 0)) + (let loop ((all-vec all-vec)) + (cond + ((= new-vec-idx new-vec-len) new-vec) + (else + (array-for-each + (lambda (element) + (vector-set! new-vec new-vec-idx element) + (set! new-vec-idx (+ new-vec-idx 1))) + (car all-vec)) + (loop (cdr all-vec)) ))) )) + +;; ---- + +(define *array1* '#(1 2 3)) +(define *array2* '#(4 5 6)) + +(define *newarray* + (vector-join *array1* *array2*)) + +;; ---------------------------- + +; * Indirectly; convert arrays to lists, append the lists, convert +; resulting list back into an array + +(define *array1* '#(1 2 3)) +(define *array2* '#(4 5 6)) + +(define *newarray* + (list->vector (append (vector->list *array1*) (vector->list *array2*)) )) + +; Of course if random access is not required, it is probably best to simply +; use lists since a wealth of list manipulation routines are available + +;; ---------------------------- + +; While Perl offers an all-purpose 'splice' routine, a cleaner approach is +; to separate out such functionality; here three routines are implemented +; together offering an equivalent to 'splice'. The routines are: +; * vector-replace! [use with 'vector-copy' to avoid changing original] +; e.g. (vector-replace! vec ...) +; (set! new-vec (vector-replace! (vector-copy vec) ...)) +; * vector-delete +; * vector-insert + +(define (vector-replace! vec pos item . rest) + (let* ((all-items (cons item rest)) + (pos (if (< pos 0) (+ (vector-length vec) pos) pos)) + (in-bounds + (not (> (+ pos (length all-items)) (vector-length vec))))) + (if in-bounds + (let loop ((i pos) (all-items all-items)) + (cond + ((null? all-items) vec) + (else + (vector-set! vec i (car all-items)) + (loop (+ i 1) (cdr all-items))) )) + ;else + vec))) + +(define (vector-delete vec pos len) + (let* ((new-vec-len (- (vector-length vec) len)) + (new-vec #f) + (pos (if (< pos 0) (+ (vector-length vec) pos) pos))) + (cond + ((< new-vec-len 0) vec) + (else + (set! new-vec (make-vector new-vec-len)) + (let loop ((vec-idx 0) (new-vec-idx 0)) + (cond + ((= new-vec-idx new-vec-len) new-vec) + (else + (if (= vec-idx pos) (set! vec-idx (+ vec-idx len))) + (vector-set! new-vec new-vec-idx (vector-ref vec vec-idx)) + (loop (+ vec-idx 1) (+ new-vec-idx 1)) ))) )) )) + +; This routine would probably benefit from having 'cmd' implemented as a keyword +; argument. However, 'cmd' implemented as a positional to keep example simple +(define (vector-insert vec pos cmd item . rest) + (let* ((all-item-vec (list->array 1 (cons item rest))) + (all-item-vec-len (vector-length all-item-vec)) + (vec-len (vector-length vec)) + (new-vec (make-vector (+ vec-len all-item-vec-len))) + (pos (if (< pos 0) (+ (vector-length vec) pos) pos))) + (if (eq? cmd 'after) (set! pos (+ pos 1))) + (vector-move-left! vec 0 pos new-vec 0) + (vector-move-left! all-item-vec 0 all-item-vec-len new-vec pos) + (vector-move-left! vec pos vec-len new-vec (+ pos all-item-vec-len)) + new-vec)) + +;; ---- + +(define *members* '#("Time" "Flies")) +(define *initiates* '#("An" "Arrow")) + +(set! *members* (vector-join *members* *initiates*)) + +;; ------------ + +(set! *members* (vector-insert *members* 1 'after "Like" *initiates*)) +(print *members*) + +(set! *members* (vector-replace *members* 0 "Fruit")) +(set! *members* (vector-replace *members* -2 "A" "Banana")) +(print *members*) + +; was: '#("Time" "Flies" "An" "Arrow") +; now: '#("Fruit" "Flies" "Like" "A" "Banana") + +;; @@PLEAC@@_4.10 +; As for appending arrays, there is the choice of iterating through +; the array: +(define (vector-reverse! vec) + (let loop ((i 0) (j (- (vector-length vec) 1))) + (cond + ((>= i j) vec) + (else + (vector-ref-swap! vec i j) + (loop (+ i 1) (- j 1)))) )) + +;; ------------ + +(define *array* '#(1 2 3)) + +(vector-reverse! *array*) + +;; ------------ + +(define *array* '#(1 2 3)) + +(do ((i (- (vector-length *array*) 1) (- i 1))) + ((< i 0)) + ... do something with *array* ...) + +;; ---------------------------- + +; or of converting to / from a list, performing any manipulation using +; the list routines + +(define *array* '#(1 2 3)) + +(define *newarray* + (list->vector (reverse (sort (vector->list *array*) <)) )) + +;; @@PLEAC@@_4.11 +(define *array* '#(1 2 3 4 5 6 7 8)) + +;; ------------ + +; Remove first 3 elements +(define *front* (vector-delete *array* 0 3)) + +; Remove last 3 elements +(define *end* (vector-delete *array* -1 3)) + +;; ---------------------------- + +; Another helper routine +(define (vector-slice vec pos len) + (let* ((vec-len (vector-length vec)) + (pos (if (< pos 0) (+ vec-len pos) pos)) + (in-bounds + (not (> (+ pos len) vec-len)))) + (if in-bounds + (let ((new-vec (make-vector len))) + (let loop ((vec-idx pos) (new-vec-idx 0)) + (cond + ((= new-vec-idx len) new-vec) + (else + (vector-set! new-vec new-vec-idx (vector-ref vec vec-idx)) + (loop (+ vec-idx 1) (+ new-vec-idx 1))) ))) + ;else + vec))) + +; Both the following use, 'values', to return two values; this approach +; is quite contrived and is taken to mimic the Perl examples, not +; because it is a recommended one [returning a single list would probably +; be more sensible] +(define (shift2 vec) + (let ((vec (vector-slice vec 0 2))) + (values (vector-ref vec 0) (vector-ref vec 1)) )) + +(define (pop2 vec) + (let ((vec (vector-slice vec -1 2))) + (values (vector-ref vec 0) (vector-ref vec 1)) )) + +;; ------------ + +(define *friends* '#('Peter 'Paul 'Mary 'Jim 'Tim)) + +(let-values ( ((this that) (shift2 *friends*)) ) + (print this ":" that)) + +;; ------------ + +(define *beverages* '#('Dew 'Jolt 'Cola 'Sprite 'Fresca)) + +(let-values ( ((d1 d2) (pop2 *beverages*)) ) + (print d1 ":" d2)) + +;; @@PLEAC@@_4.12 +; SRFI-1 [list manipulation] routines are ideal for the types of task +; in this and the next section, in particular, 'for-each' and 'find', +; 'list-index', and many others for more specialist functions. The same +; applies to vectors with the SRFI-43 routines, 'vector-index' and +; 'vector-skip', though the approach taken in this chapter has been to +; implement functionally similar vector manipulation routines to more +; closely mimic the Perl examples + +; Return #f, or first index for which 'pred' returns true +(define (vector-first-idx pred vec) + (let ((vec-len (vector-length vec))) + (let loop ((idx 0)) + (cond + ((= idx vec-len) #f) + (else + (if (pred (vector-ref vec idx)) + idx + ;else + (loop (+ idx 1))) ))))) + +; Return #f, or first index for which 'pred' returns true +(define (list-first-idx pred list) + (let loop ((idx 0) (list list)) + (cond + ((null? list) #f) + (else + (if (pred (car list)) + idx + ;else + (loop (+ idx 1) (cdr list))) )))) + +;; ------------ + +(define *array* '#(1 2 3 4 5 6 7 8)) + +(print + (vector-first-idx + (lambda (x) (= x 9)) + *array*)) + +;; ---- + +(define *list* '(1 2 3 4 5 6 7 8)) + +(print + (list-first-idx + (lambda (x) (= x 4)) + *list*)) + +;; ---- + +(use-modules (srfi srfi-1)) + +(print + (list-index + (lambda (x) (= x 4)) + *list*)) + +;; ---------------------------- + +; The Perl 'highest paid engineer' example isn't really a 'first match' +; type of problem - the routines shown earlier really aren't suited to +; this. Better suited, instead, are the SRFI-1 routines like 'fold', +; 'fold-right' and 'reduce', even old standbys like 'filter' and 'for-each' + +(define +null-salary-rec+ + (list '() 0 '())) + +(define *salaries* + (list + (list 'engineer 43000 'Bob) + (list 'programmer 48000 'Andy) + (list 'engineer 35000 'Champ) + (list 'engineer 49000 'Bubbles) + (list 'programmer 47000 'Twig) + (list 'engineer 34000 'Axel) )) + +;; ---------------------------- + +(define *highest-paid-engineer* + (reduce + (lambda (salary-rec acc) + (if + (and + (eq? (car salary-rec) 'engineer) + (> (cadr salary-rec) (cadr acc))) + salary-rec + ;else + acc)) + +null-salary-rec+ + *salaries*)) + +(print *highest-paid-engineer*) + +;; ------------ + +(define *highest-paid-engineer* + (fold-right + (lambda (salary-rec acc) + (if (> (cadr salary-rec) (cadr acc)) + salary-rec + ;else + acc)) + +null-salary-rec+ + (filter + (lambda (salary-rec) + (eq? (car salary-rec) 'engineer)) + *salaries*)) ) + +(print *highest-paid-engineer*) + +;; ------------ + +(define *highest-paid-engineer* +null-salary-rec+) + +(for-each + (lambda (salary-rec) + (if + (and + (eq? (car salary-rec) 'engineer) + (> (cadr salary-rec) (cadr *highest-paid-engineer*))) + (set! *highest-paid-engineer* salary-rec))) + *salaries*) + +(print *highest-paid-engineer*) + +;; @@PLEAC@@_4.13 +; All tasks in this section consist of either generating a collection, +; or filtering a larger collection, of elements matching some criteria; +; obvious candidates are the 'filter' and 'array-filter' routines, though +; others like 'for-each' can also be applied + +(define *list-matching* (filter PRED LIST)) +(define *vector-matching* (array-filter PRED ARRAY)) + +;; ---------------------------- + +(define *nums* '(1e7 3e7 2e7 4e7 1e7 3e7 2e7 4e7)) + +(define *bigs* + (filter + (lambda (num) (> num 1000000)) + *nums*)) + +;; ------------ + +(define *users* + (list + '(u1 . 2e7) + '(u2 . 1e7) + '(u3 . 4e7) + '(u4 . 3e7) )) + +(define *pigs* + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + (filter + (lambda (pair) (> (cdr pair) 1e7)) + *users*))) + +(print *pigs*) + +;; ------------ + +(define *salaries* + (list + (list 'engineer 43000 'Bob) + (list 'programmer 48000 'Andy) + (list 'engineer 35000 'Champ) + (list 'engineer 49000 'Bubbles) + (list 'programmer 47000 'Twig) + (list 'engineer 34000 'Axel) )) + +(define *engineers* + (filter + (lambda (salary-rec) + (eq? (car salary-rec) 'engineer)) + *salaries*)) + +(print *engineers*) + +;; ------------ + +(define *applicants* + (list + (list 'a1 26000 'Bob) + (list 'a2 28000 'Andy) + (list 'a3 24000 'Candy) )) + +(define *secondary-assistance* + (filter + (lambda (salary-rec) + (and + (> (cadr salary-rec) 26000) + (< (cadr salary-rec) 30000))) + *applicants*)) + +(print *secondary-assistance*) + +;; @@PLEAC@@_4.14 +; Sorting numeric data in Scheme is very straightforward ... + +(define *unsorted* '(5 8 1 7 4 2 3 6)) + +;; ------------ + +; Ascending sort - use '<' as comparator +(define *sorted* + (sort + *unsorted* + <)) + +(print *sorted*) + +;; ------------ + +; Descending sort - use '>' as comparator +(define *sorted* + (sort + *unsorted* + >)) + +(print *sorted*) + +;; @@PLEAC@@_4.15 +; A customised lambda may be passed as comparator to 'sort', so +; sorting on one or more 'fields' is quite straightforward + +(define *unordered* '( ... )) + +; COMPARE is some comparator suited for the element type being +; sorted +(define *ordered* + (sort + *unordered* + (lambda (left right) + (COMPARE left right)))) + +;; ------------ + +(define *unordered* + (list + (cons 's 34) + (cons 'e 12) + (cons 'c 45) + (cons 'q 11) + (cons 'g 24) )) + +(define *pre-computed* + (map + ; Here element is returned unaltered, but it would normally be + ; transformed in som way + (lambda (element) element) + *unordered*)) + +(define *ordered-pre-computed* + (sort + *pre-computed* + ; Sort on the first field [assume it is the 'key'] + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))))) + +; Extract the second field [assume it is the 'value'] +(define *ordered* + (map + (lambda (element) (cdr element)) + *ordered-pre-computed*)) + +;; ---------------------------- + +(define *employees* + (list + (list 'Bob 43000 123 42) + (list 'Andy 48000 124 35) + (list 'Champ 35000 125 37) + (list 'Bubbles 49000 126 34) + (list 'Twig 47000 127 36) + (list 'Axel 34000 128 31) )) + +(define *ordered* + (sort + *employees* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))))) + +;; ------------ + +(for-each + (lambda (employee) + (print (car employee) "earns $" (cadr employee))) + (sort + *employees* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))))) + +;; ------------ + +(define *bonus* + (list + '(125 . 1000) + '(127 . 1500) )) + +(for-each + (lambda (employee) + (let ((bonus (assoc-ref *bonus* (caddr employee)))) + (if (not bonus) + '() + ;else + (print (car employee) "earned bonus" bonus) ))) + (sort + *employees* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))))) + +;; ---------------------------- + +(use-modules (srfi srfi-1) (ice-9 rdelim) (ice-9 regex)) + +(define *filename* "/etc/passwd") +(define *users* '()) + +(let ((port (open-input-file *filename*))) + (let loop ((line&terminator (read-line port 'split))) + (cond + ((eof-object? (cdr line&terminator)) '()) + (else + (set! *users* + (assoc-set! + *users* + (car (string-split (car line&terminator) #\:)) + #t)) + (loop (read-line port 'split)) ))) + (close-input-port port)) + +(for-each + (lambda (user) (print (car user))) + (sort + *users* + (lambda (left right) + (string<? + (car left) + (car right))))) + +;; @@PLEAC@@_4.16 +; Use SRFI-1's 'circular-list' routine to build a circular list +(use-modules (srfi srfi-1)) + +(define *processes* (circular-list 1 2 3 4 5)) + +(let loop ((processes *processes*)) + (print "Handling process" (car processes)) + (sleep 1) + (loop (cdr processes))) + +;; @@PLEAC@@_4.17 +(use-modules (srfi srfi-1)) + +; Implements Fischer-Yates shuffle algorithm +(define (vector-shuffle! vec) + (let ((vector-length (vector-length vec))) + (let loop ((i vector-length) (j (+ 1 (random vector-length)))) + (cond + ((= i 1) '()) + ((not (= i j)) + (vector-ref-swap! vec (- i 1) (- j 1)) + (loop (- i 1) (+ 1 (random (- i 1))))) + (else + (loop (- i 1) (+ 1 (random (- i 1))))) )))) + +(define (vector-ref-swap! vec idx1 idx2) + (let ((tmp (vector-ref vec idx1))) + (vector-set! vec idx1 (vector-ref vec idx2)) + (vector-set! vec idx2 tmp))) + +; Generate vector of values 1 .. 10 +(define *irange* (list->vector (iota 10 1 1))) + +; Shuffle array values +(vector-shuffle! *irange*) + +;; @@PLEAC@@_4.18 +;; @@INCOMPLETE@@ +;; @@INCOMPLETE@@ + +;; @@PLEAC@@_4.19 +;; @@INCOMPLETE@@ +;; @@INCOMPLETE@@ + +;; @@PLEAC@@_5.0 +;; --------------------------------------------------------------------- +;; Scheme offers two dictionary types: +;; +;; * Association list [list of pairs e.g. '((k1 . v1) (k2 . v2) ...)] +;; * Hash table [vector of pairs plus hash algorithm] +;; +;; Implementation differences aside, they are remarkably similar in that +;; the functions operating on them are similar named, and offer the same +;; interface. Examples: +;; +;; * Retrieve an item: (assoc-ref hash key) (hash-ref hash key) +;; * Update an item: (assoc-set! hash key value) (hash-set! hash key value) +;; +;; Hash tables would tend to be used where performance was critical e.g. +;; near constant-time lookups, or where entry updates are frequent, whilst +;; association lists would be used where table-level traversals and +;; manipulations require maximum flexibility +;; +;; Many of the sections include examples using both association lists and +;; hash tables. However, where only one of these is shown, implementing +;; the other is usually a trivial exercise. Finally, any helper functions +;; will be included in the Appendix +;; --------------------------------------------------------------------- + +; Association lists +(define *age* + (list + (cons 'Nat 24) + (cons 'Jules 25) + (cons 'Josh 17))) + +;; or, perhaps more compactly: +(define *age* + (list + '(Nat . 24) + '(Jules . 25) + '(Josh . 17))) + +;; ------------ + +; Guile built-in association list support +(define *age* (acons 'Nat 24 '())) +(set! *age* (acons 'Jules 25 *age*)) +(set! *age* (acons 'Josh 17 *age*)) + +;; ---- + +; SRFI-1 association list support +(use-modules (srfi srfi-1)) + +(define *age* (alist-cons 'Nat 24 '())) +(set! *age* (alist-cons 'Jules 25 *age*)) +(set! *age* (alist-cons 'Josh 17 *age*)) + +;; ------------ + +(define *food-colour* + (list + '(Apple . "red") + '(Banana . "yellow") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +;; ---------------------------- + +; Hash tables. Guile offers an implementation, and it is also +; possible to use SRFI-69 hash tables; only the former will be +; illustrated here + +(define *age* (make-hash-table 20)) +; or +(define *age* (make-vector 20 '())) + +(hash-set! *age* 'Nat 24) +(hash-set! *age* 'Jules 25) +(hash-set! *age* 'Josh 17) + +(hash-for-each + (lambda (key value) (print key)) + *age*) + +; or, if vector used as hash table, can also use: + +(array-for-each + (lambda (pair) + (if (not (null? pair)) (print (car pair)))) + *age*) + +;; ------------ + +(define *food-colour* (make-hash-table 20)) + +(hash-set! *food-colour* 'Apple "red") +(hash-set! *food-colour* 'Banana "yellow") +(hash-set! *food-colour* 'Lemon "yellow") +(hash-set! *food-colour* 'Carrot "orange") + +;; @@PLEAC@@_5.1 +(set! *hash* (acons key value *hash*)) + +;; ------------ + +(set! *food-colour* (acons 'Raspberry "pink" *food-colour*)) + +(print "Known foods:") +(for-each + (lambda (pair) (print (car pair))) + *food-colour*) + +;; ---------------------------- + +(hash-set! *hash* key value) + +;; ------------ + +(hash-set! *food-colour* 'Raspberry "pink") + +(print "Known foods:") +(hash-for-each + (lambda (key value) (print key)) + *food-colour*) + +;; @@PLEAC@@_5.2 +; 'assoc' returns the pair, (key . value) +(if (assoc key hash) + ... found ... +;else + ... not found ... + +; 'assoc-ref' returns the value only +(if (assoc-ref hash key) + ... found ... +;else + ... not found ... + +;; ------------ + +; *food-colour* association list from an earlier section + +(for-each + (lambda (name) + (let ((pair (assoc name *food-colour*))) + (if pair + (print (symbol->string (car pair)) "is a food") + ;else + (print (symbol->string name) "is a drink") ))) + (list 'Banana 'Martini)) + +;; ---------------------------- + +; 'hash-get-handle' returns the pair, (key . value) +(if (hash-get-handle hash key) + ... found ... +;else + ... not found ... + +; 'hash-ref' returns the value only +(if (hash-ref hash key) + ... found ... +;else + ... not found ... + +;; ------------ + +; *food-colour* hash table from an earlier section + +(for-each + (lambda (name) + (let ((value (hash-ref *food-colour* name))) + (if value + (print (symbol->string name) "is a food") + ;else + (print (symbol->string name) "is a drink") ))) + (list 'Banana 'Martini)) + +;; ---------------------------- + +(define *age* (make-hash-table 20)) + +(hash-set! *age* 'Toddler 3) +(hash-set! *age* 'Unborn 0) +(hash-set! *age* 'Phantasm '()) + +(for-each + (lambda (thing) + (let ((value (hash-ref *age* thing))) + (display thing) + (if value (display " Exists")) + (if (and value (not (string-null? value))) (display " Defined")) + ; Testing for non-zero as true is not applicable, so testing + ; for non-equality with zero + (if (and value (not (eq? value 0))) (display " True")) + (print "") )) + (list 'Toddler 'Unborn 'Phantasm 'Relic)) + +;; @@PLEAC@@_5.3 +(assoc-remove! hash key) + +;; ------------ + +(use-modules (srfi srfi-1)) + +; *food-colour* association list from an earlier section + +(define (print-foods) + (let ((foods + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + *food-colour*))) + (display "Keys: ") (print foods) + (print "Values:") + (for-each + (lambda (food) + (let ((colour (assoc-ref *food-colour* food))) + (cond + ((string-null? colour) (display "(undef) ")) + (else (display (string-append colour " "))) ))) + foods)) + (newline)) + +(print "Initially:") +(print-foods) + +(print "\nWith Banana undef") +(assoc-set! *food-colour* 'Banana "") +(print-foods) + +(print "\nWith Banana deleted") +(assoc-remove! *food-colour* 'Banana) +(print-foods) + +;; ---------------------------- + +(hash-remove! hash key) + +;; ------------ + +(use-modules (srfi srfi-1)) + +; *food-colour* hash table from an earlier section + +(define (print-foods) + (let ((foods + (hash-fold + (lambda (key value accum) (cons key accum)) + '() + *food-colour*))) + (display "Keys: ") (print (reverse foods)) + (print "Values:") + (for-each + (lambda (food) + (let ((colour (hash-ref *food-colour* food))) + (cond + ((string-null? colour) (display "(undef) ")) + (else (display (string-append colour " "))) ))) + foods)) + (newline)) + +(print "Initially:") +(print-foods) + +(print "\nWith Banana undef") +(hash-set! *food-colour* 'Banana "") +(print-foods) + +(print "\nWith Banana deleted") +(hash-remove! *food-colour* 'Banana) +(print-foods) + +;; @@PLEAC@@_5.4 +; Since an association list is nothing more than a list of pairs, it +; may be traversed using 'for-each' +(for-each + (lambda (pair) + (let ((key (car pair)) + (value (cdr pair))) + ... do something with key / value ...)) + hash) + +;; ---------------------------- + +; A 'for-each'-like function is available for hash table traversal +(hash-for-each + (lambda (key value) + ... do something with key / value ...) + hash) + +; If the hash table is directly implemented as a vector, then it is +; also possible to traverse it using, 'array-for-each', though a +; check for empty slots is needed +(array-for-each + (lambda (pair) + (if (not (null? pair)) ... do something with key / value ...)) + hash) + +;; ---------------------------- + +; *food-colour* association list from an earlier section + +(for-each + (lambda (pair) + (let ((food (car pair)) + (colour (cdr pair))) + (print (symbol->string food) "is" colour) )) + *food-colour*) + +;; ------------ + +; *food-colour* association list from an earlier section + +(for-each + (lambda (food) + (print (symbol->string food) "is" (assoc-ref *food-colour* food))) + (sort + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + *food-colour*) + (lambda (left right) + (string<? (symbol->string left) (symbol->string right))))) + +;; ---------------------------- + +(use-modules (srfi srfi-1) (ice-9 rdelim) (ice-9 regex)) + +(define *filename* "from.txt") +(define *from* '()) + +(let ((port (open-input-file *filename*))) + (let loop ((line&terminator (read-line port 'split))) + (cond + ((eof-object? (cdr line&terminator)) '()) + (else + (let* ((key (string->symbol + (match:substring + (string-match + "^From: (.*)" (car line&terminator)) + 1) )) + (value (assoc-ref *from* key))) + (if (not value) (set! value 0)) + (set! *from* (assoc-set! *from* key (+ 1 value)))) + (loop (read-line port 'split)) ))) + (close-input-port port)) + +(for-each + (lambda (person) + (print (symbol->string person) ":" (number->string (assoc-ref *from* person)))) + (sort + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + *from*) + (lambda (left right) + (string<? (symbol->string left) (symbol->string right))))) + +;; @@PLEAC@@_5.5 +; All approaches shown in the previous section apply here also, so +; there is little to be gained by repeating those examples [i.e. the +; use of 'for-each' and similar]. It is always possible, of course, +; to directly recurse over an association list: + +; *food-colour* association list from an earlier section + +(define *sorted-food-colour* + (sort + *food-colour* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))) )) + +(let loop ((hash *sorted-food-colour*)) + (cond + ((null? hash) '()) + (else + (print + (symbol->string (car (car hash))) "=>" (cdr (car hash)) ) + (loop (cdr hash))) )) + +;; @@PLEAC@@_5.6 +; AFAIK, Scheme doesn't offer a facility similar to Perl's 'Tie::IxHash'. +; Therefore, use an association list if retrieval [from a dictionary +; type container] in insertion order is required. + +(define *food-colour* (acons 'Banana "Yellow" '())) +(set! *food-colour* (acons 'Apple "Green" *food-colour*)) +(set! *food-colour* (acons 'Lemon "yellow" *food-colour*)) + +(print "In insertion order, the foods are:") +(for-each + (lambda (pair) + (let ((food (car pair)) + (colour (cdr pair))) + (print " " (symbol->string food)) )) + *food-colour*) + +(print "Still in insertion order, the food's colours are:") +(for-each + (lambda (pair) + (let ((food (car pair)) + (colour (cdr pair))) + (print (symbol->string food) "is coloured" colour) )) + *food-colour*) + +;; ---------------------------- + +; Of course, insertion order is lost if the association list is sorted, +; or elements removed, so if maintaining insertion order is vital, it +; might pay to associate data with a timestamp [e.g. create a timestamped +; record / structure], and manipulate those entities [no example given] + +;; @@PLEAC@@_5.7 +(define *ttys* '()) + +(for-each + (lambda (user-tty-pair) + (let* ((user-tty-pair (string-split user-tty-pair #\space)) + (user (string->symbol (car user-tty-pair))) + (newtty (cadr user-tty-pair)) + (current-ttys (assoc-ref *ttys* user))) + (set! *ttys* + (assoc-set! *ttys* user + (if (not current-ttys) + newtty + (string-append current-ttys " " newtty)) )))) + (string-split (qx "who|cut -d' ' -f1,2") #\newline)) + +(for-each + (lambda (user-ttys) + (print (symbol->string (car user-ttys)) ":" (cdr user-ttys))) + (sort + *ttys* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right))))) ) + +;; ---------------------------- + +(use-modules (ice-9 regex)) + +(define (multi-hash-delete hash key value) + (let ((value-found (assoc-ref hash key))) + (if value-found + (assoc-ref hash key + (regexp-substitute/global + #f (string-match value value-found) 'pre "" 'post ""))))) + +;; @@PLEAC@@_5.8 +; Alternate implementatons of a hash inversion function; both assume +; key is a symbol, value is a string + +(define (assoc-invert assoc) + (map + (lambda (pair) + (cons + (string->symbol (cdr pair)) + (symbol->string (car pair)))) + assoc)) + +;; ------------ + +(define (assoc-invert assoc) + (let loop ((assoc assoc) (new-assoc '())) + (cond + ((null? assoc) new-assoc) + (else + (loop (cdr assoc) + (acons + (string->symbol (cdar assoc)) + (symbol->string (caar assoc)) new-assoc)) )) )) + +;; ---------------------------- + +(define *surname* + (list + '(Mickey . "Mantle") + '(Babe . "Ruth"))) + +(define *first-name* (assoc-invert *surname*)) + +(print (assoc-ref *first-name* 'Mantle)) + +;; ---------------------------- + +; foodfind + +(define *given* (string->symbol (cadr (command-line)))) + +(define *colour* + (list + '(Apple . "red") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +(define *food* (assoc-invert *colour*)) + +(if (assoc-ref *colour* *given*) + (print + (symbol->string *given*) + "is a food with colour" + (assoc-ref *colour* *given*))) + +(if (assoc-ref *food* *given*) + (print + (assoc-ref *food* *given*) + "is a food with colour" + (symbol->string *given*))) + +;; @@PLEAC@@_5.9 +; *food-colour* association list from an earlier section + +; Use 'sort' to sort the entire hash, on key or on value, ascending or +; descending order +(define *sorted-on-key:food-colour* + (sort + *food-colour* + (lambda (left right) + (string<? + (symbol->string (car left)) + (symbol->string (car right)))) )) + +(define *sorted-on-value:food-colour* + (sort + *food-colour* + (lambda (left right) + (string<? + (cdr left) + (cdr right))) )) + +;; ------------ + +(for-each + (lambda (pair) + (let ((food (car pair)) + (colour (cdr pair))) + (print + (symbol->string food) + "is" + colour))) + *sorted-on-key:food-colour*) + +;; ---------------------------- + +; Alternatively, generate a list of keys or values, sort as required, +; and use list to guide the hash traversal + +(define *sorted-food-colour-keys* + (sort + (fold-right + (lambda (pair accum) (cons (car pair) accum)) + '() + *food-colour*) + (lambda (left right) + (string<? + (symbol->string left) + (symbol->string right))) )) + +(define *sorted-food-colour-values* + (sort + (fold-right + (lambda (pair accum) (cons (cdr pair) accum)) + '() + *food-colour*) + (lambda (left right) + (string<? left right)) )) + +;; ------------ + +(for-each + (lambda (food) + (print (symbol->string food) "is" (assoc-ref *food-colour* food))) + *sorted-food-colour-keys*) + +;; @@PLEAC@@_5.10 +; If merging is defined as the combining of the contents of two or more +; hashes, then it is simply a matter of copying the contents of each +; into a new hash + +; Association lists can simply be appended together +(define *food-colour* + (list + '(Apple . "red") + '(Banana . "yellow") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +(define *drink-colour* + (list + '(Galliano . "yellow") + '(Mai Tai . "blue"))) + +(define *ingested-colour* (append *food-colour* *drink-colour*)) + +;; ---------------------------- + +; Hash tables built from vectors can be copied element by element into +; a new vector, or spliced together using 'vector-join' [see Chapter 4] + +(define *food-colour* (make-vector 20 '()) +; ... +(define *drink-colour* (make-vector 20 '()) +; ... + +(define *ingested-colour* + (vector-join *food-colour* *drink-colour*)) + +;; @@PLEAC@@_5.11 +(define *common* '()) +(define *this-not-that* '()) + +;; ------------ + +(define *dict1* + (list + '(Apple . "red") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +(define *dict2* + (list + '(Apple . "red") + '(Carrot . "orange"))) + +;; ------------ + +; Find items common to '*dict1*' and '*dict2*' +(for-each + (lambda (pair) + (let ((key (car pair)) + (value (cdr pair))) + (if (assoc-ref *dict2* key) + (set! *common* (cons key *common*))) )) + *dict1*) + +;; ------------ + +; Find items in '*dict1*' but not '*dict2*' +(for-each + (lambda (pair) + (let ((key (car pair)) + (value (cdr pair))) + (if (not (assoc-ref *dict2* key)) + (set! *this-not-that* (cons key *this-not-that*))) )) + *dict1*) + +;; ---------------------------- + +(define *non-citrus* '()) + +(define *citrus-colour* + (list + '(Lemon . "yellow") + '(Orange . "orange") + '(Lime . "green"))) + +(define *food-colour* + (list + '(Apple . "red") + '(Banana . "yellow") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +(for-each + (lambda (pair) + (let ((key (car pair)) + (value (cdr pair))) + (if (not (assoc-ref *citrus-colour* key)) + (set! *non-citrus* (cons key *non-citrus*))) )) + *food-colour*) + +;; @@PLEAC@@_5.12 +; All objects [including functions] are first class entities, so there +; is no problem / special treatment needed to use any object, including +; those classed as 'references' [e.g. file handles or ports] as keys + +(use-modules (srfi srfi-1) (srfi srfi-13)) + +(define *ports* '()) + +(for-each + (lambda (filename) + (let ((port (open-input-file filename))) + (set! *ports* (assoc-set! *ports* port filename)) )) + '("/etc/termcap" "/vmlinux" "/bin/cat")) + +(print + (string-append "open files: " + (string-drop + (fold-right + (lambda (pair accum) (string-append ", " (cdr pair) accum)) + "" + *ports*) + 2))) + +(for-each + (lambda (pair) + (let ((port (car pair)) + (filename (cdr pair))) + (seek port 0 SEEK_END) + (print filename "is" (number->string (ftell port)) "bytes long.") + (close-input-port port) )) + *ports*) + +;; @@PLEAC@@_5.13 +; An association list takes on the size of the number of elements with +; which it is initialised, so presizing is implicit + +(define *hash* '()) ; zero elements + +;; ------------ + +(define *hash* ; three elements + (list + '(Apple . "red") + '(Lemon . "yellow") + '(Carrot . "orange"))) + +;; ---------------------------- + +; A size [i.e. number of entries] must be specified when a hash table +; is created, so presizing is implicit + +(define *hash* (make-hash-table 100)) + +;; ------------ + +(define *hash* (make-vector 100 '())) + +;; @@PLEAC@@_5.14 +(define *array* + (list 'a 'b 'c 'd 'd 'a 'a 'c 'd 'd 'e)) + +;; ---------------------------- + +(define *count* '()) + +(for-each + (lambda (element) + (let ((value (assoc-ref *count* element))) + (if (not value) (set! value 0)) + (set! *count* (assoc-set! *count* element (+ 1 value))))) + *array*) + +;; ---------------------------- + +(define *count* (make-hash-table 20)) + +(for-each + (lambda (element) + (let ((value (hash-ref *count* element))) + (if (not value) (set! value 0)) + (hash-set! *count* element (+ 1 value)))) + *array*) + +;; @@PLEAC@@_5.15 +(define *father* + (list + '(Cain . Adam) + '(Abel . Adam) + '(Seth . Adam) + '(Enoch . Cain) + '(Irad . Enoch) + '(Mehujael . Irad) + '(Methusael . Mehujael) + '(Lamech . Methusael) + '(Jabal . Lamech) + '(Jubal . Lamech) + '(Tubalcain . Lamech) + '(Enos . Seth))) + +;; ------------ + +(use-modules (srfi srfi-1) (ice-9 rdelim)) + +(let ((port (open-input-file *filename*))) + (let loop ((line&terminator (read-line port 'split))) + (cond + ((eof-object? (cdr line&terminator)) '()) + (else + (let ((person (string->symbol (car line&terminator)))) + (let loop ((father (assoc-ref *father* person))) + (if father + (begin + (print father) + (loop (assoc-ref *father* father)) ))) + (loop (read-line port 'split)) )))) + (close-input-port port)) + +;; ------------ + +(use-modules (srfi srfi-1) (ice-9 rdelim)) + +(define (assoc-invert-N:M assoc) + (let ((new-assoc '())) + (for-each + (lambda (pair) + (let* ((old-key (car pair)) + (new-key (cdr pair)) + (new-key-found (assoc-ref new-assoc new-key))) + (if (not new-key-found) + (set! new-assoc (acons new-key (list old-key) new-assoc)) + ;else + (set! new-assoc (assoc-set! new-assoc new-key (cons old-key new-key-found))) ))) + assoc) + new-assoc)) + +(define *children* (assoc-invert-N:M *father*)) + +(let ((port (open-input-file *filename*))) + (let loop ((line&terminator (read-line port 'split))) + (cond + ((eof-object? (cdr line&terminator)) '()) + (else + (let* ((person (string->symbol (car line&terminator))) + (children-found (assoc-ref *children* person))) + (print (symbol->string person) "begat:") + (if (not children-found) + (print "nobody") + ;else + (for-each + (lambda (child) (print (symbol->string child) ",")) + children-found)) + (loop (read-line port 'split)) )))) + (close-input-port port)) + +;; @@PLEAC@@_5.16 +;; @@INCOMPLETE@@ +;; @@INCOMPLETE@@ + +;; @@PLEAC@@_7.0 +;; use (open-input-file filename) or (open filename O_RDONLY) + +(define input (open-input-file "/usr/local/widgets/data")) +(let loop ((line (read-line input 'concat))) + (cond ((not (eof-object? line)) + (if (string-match "blue" line) + (display line)) + (loop (read-line input 'concat))))) +(close input) + +;; Many I/O functions default to the logical STDIN/OUT + +;; You can also explicitly get the standard ports with +;; [set-]current-{input,output,error}-port. + +;; format takes a port as the first argument. If #t is given, format +;; writes to stdout, if #f is given, format returns a string. + +(let loop ((line (read-line))) ; reads from stdin + (cond ((not (eof-object? line)) + (if (not (string-match "[0-9]" line)) + ;; writes to stderr + (display "No digit found.\n" (current-error-port)) + ;; writes to stdout + (format #t "Read: ~A\n" line)) + (loop (read-line))))) + +;; use open-output-file + +(define logfile (open-output-file "/tmp/log")) + +;; increasingly specific ways of closing ports (it's safe to close a +;; closed port) + +(close logfile) ; #t +(close-port logfile) ; #f (already closed) +(close-output-port logfile) ; unspecified + +;; you can rebind standard ports with set-current-<foo>-port: + +(let ((old-out (current-output-port))) + (set-current-output-port logfile) + (display "Countdown initiated ...\n") + (set-current-output-port old-out) + (display "You have 30 seconds to reach minimum safety distance.\n")) + +;; or + +(with-output-to-file logfile + (lambda () (display "Countdown initiated ...\n"))) +(display "You have 30 seconds to reach minimum safety distance.\n") + + +;; @@PLEAC@@_7.1 +(define source (open-input-file path)) +(define sink (open-output-file path)) + +(define source (open path O_RDONLY)) +(define sink (open path O_WRONLY)) + +;;----------------------------- +(define port (open-input-file path)) +(define port (open-file path "r")) +(define port (open path O_RDONLY)) +;;----------------------------- +(define port (open-output-file path)) +(define port (open-file path "w")) +(define port (open path (logior O_WRONLY O_TRUNC O_CREAT))) +;;----------------------------- +(define port (open path (logior O_WRONLY O_EXCL O_CREAT))) +;;----------------------------- +(define port (open-file path "a")) +(define port (open path (logior O_WRONLY O_APPEND O_CREAT))) +;;----------------------------- +(define port (open path (logior O_WRONLY O_APPEND))) +;;----------------------------- +(define port (open path O_RDWR)) +;;----------------------------- +(define port (open-file path "r+")) +(define port (open path (logior O_RDWR O_CREAT))) +;;----------------------------- +(define port (open path (logior O_RDWR O_EXCL O_CREAT))) +;;----------------------------- + +;; @@PLEAC@@_7.2 +;; Nothing different needs to be done with Guile + +;; @@PLEAC@@_7.3 +(define expand-user + (let ((rx (make-regexp "^\\~([^/]+)?"))) + (lambda (filename) + (let ((m (regexp-exec rx filename))) + (if m + (string-append + (if (match:substring m 1) + (passwd:dir (getpwnam (match:substring m 1))) + (or (getenv "HOME") (getenv "LOGDIR") + (passwd:dir (getpwuid (cuserid))) "")) + (substring filename (match:end m))) + filename))))) + +;; @@PLEAC@@_7.4 +(define port (open-file filename mode)) ; raise an exception on error + +;; use catch to trap errors +(catch 'system-error ; the type of error thrown + (lambda () (set! port (open-file filename mode))) ; thunk to try + (lambda (key . args) ; exception handler + (let ((fmt (cadr args)) + (msg&path (caddr args))) + (format (current-error-port) fmt (car msg&path) (cadr msg&path)) + (newline)))) + +;; @@PLEAC@@_7.5 +;; use the POSIX tmpnam +(let ((name (tmpnam))) + (call-with-output-file name + (lambda (port) + ;; ... output to port + ))) + +;; better to test and be sure you have exclusive access to the file +;; (temp file name will be available as (port-filename port)) +(define (open-temp-file) + (let loop ((name (tmpnam))) + (catch 'system-error + (lambda () (open name (logior O_RDWR O_CREAT O_EXCL))) + (lambda (key . args) (loop (tmpnam)))))) + +;; or let mkstemp! do the work for you: +(define port (mkstemp! template-string-ending-in-XXXXXX)) + +(let* ((tmpl "/tmp/programXXXXXX") + (port (mkstemp! tmpl))) + ;; tmpl now contains the name of the temp file, + ;; e.g. "/tmp/programhVoEzw" + (do ((i 0 (1+ i))) + ((= i 10)) + (format port "~A\n" i)) + (seek port 0 SEEK_SET) + (display "Tmp file has:\n") + (do ((line (read-line port 'concat) (read-line port 'concat))) + ((eof-object? line)) + (display line)) + (close port)) + +;; @@PLEAC@@_7.6 +;; string ports are ideal for this + +(define DATA " +your data goes here +") + +(call-with-input-string + DATA + (lambda (port) + ;; ... process input from port + )) + +;; or + +(with-input-from-string DATA + (lambda () + ;; ... stdin now comes from DATA + )) + +;; @@PLEAC@@_7.7 +;; to process lines of current-input-port: +(do ((line (read-line) (read-line))) + ((eof-object? line)) + ;; ... do something with line + ) + +;; a general filter template: + +(define (body) + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (display line) + (newline))) + +(let ((args (cdr (command-line)))) + ;; ... handle options here + (if (null? args) + (body) ; no args, just call body on stdin + (for-each ; otherwise, call body with stdin set to each arg in turn + (lambda (file) + (catch 'system-error + (lambda () + (with-input-from-file file + body)) + (lambda (key . args) + (format (current-error-port) (cadr args) (caaddr args) + (car (cdaddr args))) + (newline (current-error-port))))) + args))) + +;; example: count-chunks: +(use-modules (srfi srfi-1) (srfi srfi-13) (ice-9 format) (ice-9 regex)) + +;; also use directory-files from 9.5 and globbing functions from 9.6 + +;; can use (ice-9 getopt-long) described in chapter 15, or process +;; options by hand +(define opt-append 0) +(define opt-ignore-ints 0) +(define opt-nostdout 0) +(define opt-unbuffer 0) + +(define args (cdr (command-line))) + +(do ((opts args (cdr opts))) + ((or (null? opts) (not (eq? (string-ref (car opts) 0) #\-))) + (set! args opts)) + (let ((opt (car opts))) + (cond ((string=? opt "-a") (set! opt-append (1+ opt-append))) + ((string=? opt "-i") (set! opt-ignore-ints (1+ opt-ignore-ints))) + ((string=? opt "-n") (set! opt-nostdout (1+ opt-nostdout))) + ((string=? opt "-u") (set! opt-unbuffer (1+ opt-unbuffer))) + (else (throw 'usage-error "Unexpected argument: ~A" opt))))) + +;; default to all C source files +(if (null? args) (set! args (glob "*.[Cch]" "."))) + +(define (find-login) + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (cond ((string-match "login" line) + (display line) + (newline))))) + +(define (lowercase) + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (display (string-downcase line)) + (newline))) + +(define (count-chunks) + (do ((line (read-line) (read-line)) + (chunks 0)) + ((or (eof-object? line) + (string=? line "__DATA__") (string=? line "__END__")) + (format #t "Found ~A chunks\n" chunks)) + (let ((tokens + (string-tokenize (string-take line (or (string-index line #\#) + (string-length line)))))) + (set! chunks (+ chunks (length tokens)))))) + +(if (null? args) + (count-chunks) ; or find-login, lowercase, etc. + (for-each + (lambda (file) + (catch 'system-error + (lambda () + (with-input-from-file file + count-chunks)) + (lambda (key . args) + (format (current-error-port) (cadr args) (caaddr args) + (car (cdaddr args))) + (newline (current-error-port))))) + args)) + +;; @@PLEAC@@_7.8 +;; write changes to a temporary file then rename it +(with-input-from-file old + (lambda () + (with-output-to-file new + (lambda () + (do ((line (read-line) (read-line))) + ((eof-object? line)) + ;; change line, then... + (write-line line)))))) +(rename-file old (string-append old ".orig")) +(rename-file new old) + +;; @@PLEAC@@_7.9 +;; no -i switch + +;; @@PLEAC@@_7.10 +;; open the file in read/write mode, slurp up the contents, modify it, +;; then write it back out: +(let ((p (open-file file "r+")) + (lines '())) + ;; read in lines + (do ((line (read-line p) (read-line p))) + ((eof-object? line)) + (set! lines (cons line lines))) + ;; modify (reverse lines) + (seek p 0 SEEK_SET) + ;; write out lines + (for-each (lambda (x) (write-line x p)) lines) + ;; truncate the file + (truncate-file p) + (close p)) + +(let ((p (open-file "foo" "r+")) + (lines '()) + (date (date->string (current-date)))) + (do ((line (read-line p 'concat) (read-line p 'concat))) + ((eof-object? line)) + (set! lines (cons line lines))) + (seek p 0 SEEK_SET) + (for-each + (lambda (x) + (regexp-substitute/global p "DATE" x 'pre date 'post)) + (reverse lines)) + (truncate-file p) + (close p)) + +;; @@PLEAC@@_7.11 +(define p (open-file path "r+")) +(flock p LOCK_EX) +;; update the file, then... +(close p) + +;; to increment a number in a file +(define p (open "numfile" (logior O_RDWR O_CREAT))) +(flock p LOCK_EX) +;; Now we have acquired the lock, it's safe for I/O +(let* ((obj (read p)) + (num (if (eof-object? obj) 0 obj))) + (seek p 0 SEEK_SET) + (truncate-file p) + (write (1+ num) p) + (newline p)) +(close p) + +;; @@PLEAC@@_7.12 +;; use force-output +(force-output p) + +;; flush all open ports +(flush-all-ports) + +;; @@PLEAC@@_7.13 +;; use select +(select inputs outputs exceptions seconds) +(select (list p1 p2 p3) '() '()) + +(let* ((nfound (select (list inport) '() '())) + (inputs (car nfound))) + (if (not (null? inputs)) + (let ((line (read-line inport))) + (format #t "I read ~A\n" line)))) + +;; or use char-ready? if you only need a single character +(if (char-ready? p) + (format #t "I read ~A\n" (read-char p))) + +;; @@PLEAC@@_7.14 +;; use the O_NONBLOCK option with open +(define modem (open "/dev/cua0" (logior O_RDWR O_NONBLOCK))) + +;; or use fcntl if you already have a port +(let ((flags (fcntl p F_GETFD))) + (fcntl p F_SETFD (logior flags O_NONBLOCK))) + +;; @@PLEAC@@_7.15 +;; use stat +(let ((buf (make-string (stat:size (stat p))))) + (read-string!/partial buf input)) + +;; @@PLEAC@@_7.16 +;; not needed - ports are first class objects + +;; @@PLEAC@@_7.18 +;; use for-each on the list of ports: +(for-each (lambda (p) (display stuff-to-print p)) port-list) + +;; or, if you don't want to keep track of the port list and know you +;; want to print to all open output ports, you can use port-for-each: +(port-for-each (lambda (p) (if (output-port? p) (display stuff p)))) + +;; @@PLEAC@@_7.19 +;; use fdopen: +(define p (fdopen num mode)) +(define p (fdopen 3 "r")) + +(define p (fdopen (string->number (getenv "MHCONTEXTFD")) "r")) +;; after processing +(close p) + +;; @@PLEAC@@_7.20 +;; ports are first class objects and can be aliased and passed around +;; like any other non-immediate variables: +(define alias original) +(define old-in (current-input-port)) + +;; or you can open two separate ports on the same file: +(define p1 (open-input-file path)) +(define p2 (open-input-file path)) + +;; or use fdopen: +(define copy-of-p (fdopen (fileno p) mode)) + +(define old-out (current-output-port)) +(define old-err (current-error-port)) + +(define new-out (open-output-file "/tmp/program.out")) + +(set-current-output-port new-out) +(set-current-error-port new-out) + +(system joe-random-program) + +(close new-out) + +(set-current-output-port old-out) +(set-current-error-port old-out) + +;; @@PLEAC@@_8.0 +;; open the file and loop through the port with read-line: +(let ((p (open-input-file file))) + (do ((line (read-line p) (read-line p))) + ((eof-object? line)) + (format #t "~A\n" (string-length line))) + (close p)) + +;; you can use with-input-from-file to temporarily rebind stdin: +(with-input-from-file file + (lambda () + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (format #t "~A\n" (string-length line))))) + +;; or define a utility procedure to do this +(define (for-each-line proc file) + (with-input-from-file file + (lambda () + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (proc line))))) +(for-each-line (lambda (x) (format #t "~A\n" (string-length line))) file) + +;; read in the file as a list of lines +(define (read-lines file) + (let ((ls '())) + (with-input-from-file file + (lambda () + (do ((line (read-line) (read-line))) + ((eof-object? line)) + (set! ls (cons line ls))) + (reverse ls))))) + +;; read in the file as a single string +(define (file-contents file) + (call-with-input-file file + (lambda (p) + (let* ((size (stat:size (stat p))) + (buf (make-string size))) + (read-string!/partial buf p) + buf)))) + +;; use display to print human readable output +(display '("One" "two" "three") port) ; (One two three) +(display "Baa baa black sheep.\n") ; Sent to default output port + +;; use write to print machine readable output +(write '("One" "two" "three") port) ; ("One" "two" "three") + +;; use (ice-9 rw) to read/write fixed-length blocks of data: +(use-modules (ice-9 rw)) +(let ((buffer (make-string 4096))) + (read-string!/partial buffer port 4096)) + +;; truncate-file +(truncate-file port length) ; truncate to length +(truncate-file port) ; truncate to current pos + +;; ftell +(define pos (ftell port)) +(format #t "I'm ~A bytes from the start of DATAFILE.\n" pos) + +;; seek +(seek log-port 0 SEEK_END) ; seek to end +(seek data-port pos SEEK_SET) ; seek to pos +(seek out-port -20 SEEK_CUR) ; seek back 20 bytes + +;; block read/write +(use-modules (ice-9 rw)) +(write-string/partial mystring data-port (string-length mystring)) +(read-string!/partial block 256 5) + +;; @@PLEAC@@_8.1 +(let ((rx (make-regexp "(.*)\\\\$"))) ; or "(.*)\\\\\\s*$" + (with-input-from-file file + (lambda () + (let loop ((line (read-line))) + (if (not (eof-object? line)) + (let ((m (regexp-exec rx line)) + (next (read-line))) + (cond ((and m (not (eof-object? next))) + (loop (string-append (match:substring m 1) next))) + (else + ;; else process line here, then recurse + (loop next))))))))) + +;; @@PLEAC@@_8.2 +(do ((line (read-line p) (read-line p)) + (i 0 (1+ i))) + ((eof-object? line) i)) + +;; fastest way if your terminator is a single newline +(use-modules (ice-9 rw) (srfi srfi-13)) +(let ((buf (make-string (expt 2 16))) + (count 0)) + (do ((len (read-string!/partial buf p) (read-string!/partial buf p))) + ((not len) count) + (set! count (+ count (string-count buf #\newline 0 len))))) + +;; or use port-line +(let loop ((line (read-line p))) + (if (eof-object? line) (port-line p) (loop (read-line p)))) + +;; @@PLEAC@@_8.3 +;; default behaviour of string-tokenize is to split on whitespace: +(use-modules (srfi srfi-13)) +(let loop ((line (read-line p))) + (cond ((not eof-object? line) + (for-each some-function-of-word (string-tokenize line)) + (loop (read-line p))))) + +(let ((table (make-hash-table 31))) + (let loop ((line (read-line p))) + (cond ((not (eof-object? line)) + (for-each + (lambda (w) (hash-set! table w (1+ (hash-ref table w 0)))) + (string-tokenize line)) + (loop (read-line p))))) + (hash-fold (lambda (k v p) (format #t "~5D ~A\n" v k)) #f table)) + +;; @@PLEAC@@_8.4 +;; build up the list the reverse it or fold over it: +(define lines (read-lines file)) +(for-each (lambda (word) do-something-with-word) (reverse lines)) +(fold (lambda (word acc) do-something-with-word) #f lines) + +;; @@PLEAC@@_8.5 +;; save the current position and reseek to it +(define (tail file) + (call-with-input-file file + (lambda (p) + (let loop ((line (read-line p))) + (cond ((eof-object? line) + (sleep sometime) + (let ((pos (ftell p))) + (seek p 0 SEEK_SET) + (seek p pos SEEK_SET))) + (else + ;; process line + )) + (loop (read-line p)))))) + +;; @@PLEAC@@_8.6 +(let ((rand-line #f)) + (let loop ((line (read-line p))) + (cond ((not (eof-object? line)) + (if (= 0 (random (port-line p))) + (set! rand-line line)) + (loop (read-line p))))) + ;; rand-line is the random line + ) + +;; @@PLEAC@@_8.7 +(define (shuffle list) + (let ((v (list->vector list))) + (do ((i (1- (vector-length v)) (1- i))) + ((< i 0) (vector->list v)) + (let ((j (random (1+ i)))) + (cond ((not (= i j)) + (let ((temp (vector-ref v i))) + (vector-set! v i (vector-ref v j)) + (vector-set! v j temp)))))))) + +(define rand-lines (shuffle (read-lines file)) + +;; @@PLEAC@@_8.8 +;; looking for line number desired-line-number +(do ((line (read-line p) (read-line p))) + ((= ((port-line p) desired-line-number) line))) +;; or read into a list +(define lines (read-lines file)) +(list-ref lines desired-line-number) + +;; @@INCOMPLETE@@ +; (define (build-index data-file index-file) +; ) + +; (define (line-with-index data-file index-file line-number) +; ) + +;; @@PLEAC@@_8.9 +;; use string-tokenize with an appropriate character set +(use-modules (srfi srfi-13) (srfi srfi-14)) +(define fields (string-tokenize line (string->charset "+-"))) +(define fields (string-tokenize line (string->charset ":"))) +(define fields (string-tokenize line)) + +;; @@PLEAC@@_8.10 +(let ((p (open-file file "r+"))) + (let ((pos 0)) + (let loop ((line (read-line p))) + (cond ((eof-object? (peek-char p)) + (seek p 0 SEEK_SET) + (truncate-file p pos) + (close p)) + (else + (set! pos (ftell p)) + (loop (read-line p))))))) + +;; @@PLEAC@@_8.11 +;; no equivalent - don't know how Guile under windows handles this + +;; @@PLEAC@@_8.12 +(let* ((address (* recsize recno)) + (buf (make-string recsize))) + (seek p address SEEK_SET) + (read-string!/partial buf p) + buf) + +;; @@PLEAC@@_8.13 +(let* ((address (* recsize recno)) + (buf (make-string recsize))) + (seek p address SEEK_SET) + (read-string!/partial buf p) + ;; modify buf, then write back with + (seek p address SEEK_SET) + (write-string/partial buf p) + (close p)) + +;; @@INCOMPLETE@@ +;; weekearly + +;; @@PLEAC@@_8.14 +(seek p addr SEEK_SET) +(define str (read-delimited (make-string 1 #\nul) p)) + +#!/usr/local/bin/guile -s +!# +;; bgets -- get a string from an address in a binary file +(use-modules (ice-9 format)) + +(define args (cdr (command-line))) +(define file (car args)) +(define addrs (map string->number (cdr args))) +(define delims (make-string 1 #\nul)) + +(call-with-input-file file + (lambda (p) + (for-each + (lambda (addr) + (seek p addr SEEK_SET) + (format #t "~X ~O ~D ~S\n" addr addr addr + (read-delimited delims p))) + addrs))) + +;; @@INCOMPLETE@@ +;; strings + +;; @@PLEAC@@_9.0 +(define entry (stat "/usr/bin/vi")) +(define entry (stat "/usr/bin")) +(define entry (stat port)) + +(use-modules (ice-9 posix)) + +(define inode (stat "/usr/bin/vi")) +(define ctime (stat:ctime inode)) +(define size (stat:size inode)) + +(define F (open-input-file filename)) +;; no equivalent - what defines -T? +; unless (-s F && -T _) { +; die "$filename doesn't have text in it.\n"; +; } + +(define dir (opendir "/usr/bin")) +(do ((filename (readdir dir) (readdir dir))) + ((eof-object? filename)) + (format #t "Inside /usr/bin is something called ~A\n" filename)) +(closedir dir) + +;; @@PLEAC@@_9.1 +(define inode (stat filename)) +(define readtime (stat:atime inode)) +(define writetime (stat:mtime inode)) + +(utime newreadtime newwritetime filename) + +(define seconds-per-day (* 60 60 24)) +(define inode (stat file)) +(define atime (stat:atime inode)) +(define mtime (stat:mtime inode)) +(set! atime (- atime (* 7 seconds-per-day))) +(set! mtime (- mtime (* 7 seconds-per-day))) +(utime file atime mtime) + +;; mtime is optional +(utime file (current-time)) +(utime file (stat:atime (stat file)) (current-time)) + +#!/usr/local/bin/guile -s +!# +;; uvi - vi a file without changing its access times + +(define file (cadr (command-line))) +(define inode (stat file)) +(define atime (stat:atime inode)) +(define mtime (stat:mtime inode)) +(system (string-append (or (getenv "EDITOR") "vi") " " file)) +(utime file atime mtime) + +;; @@PLEAC@@_9.2 +(delete-file file) + +(let ((count 0)) + (for-each + (lambda (x) + (catch #t + (lambda () (delete-file x) (set! count (1+ count))) + (lambda (err . args) #f))) + file-list) + (if (not (= count (length file-list))) + (format (current-error-port) "could only delete ~A of ~A files" + count (length file-list)))) + +;; @@PLEAC@@_9.3 +;; use builtin copy-file +(copy-file oldfile newfile) +(rename-file oldfile newfile) + +;; or do it by hand (clumsy, error-prone) +(use-modules (ice-9 rw) (ice-9 posix)) +(with-input-from-file oldfile + (lambda () + (call-with-output-file newfile + (lambda (p) + (let* ((inode (stat oldfile)) + (blksize (if inode (stat:size inode) 16384)) + (buf (make-string blksize))) + (let loop ((len (read-string!/partial buf))) + (cond ((and len (> len 0)) + (write-string/partial buf p 0 len) + (loop (read-string!/partial buf)))))))))) + +;; or call out to the system (non-portable, insecure) +(system (string-append "cp " oldfile " " newfile)) ; unix +(system (string-append "copy " oldfile " " newfile)) ; dos, vms + +;; @@PLEAC@@_9.4 +;; use a hash lookup of inodes +(use-modules (ice-9 posix)) +(let ((seen (make-hash-table 31))) + (for-each + (lambda (file) + (let* ((stats (stat file)) + (key (cons (stat:dev stats) (stat:ino stats))) + (val (hash-ref seen key 0))) + (cond ((= val 0) + ;; do something with new file + )) + (hash-set! seen key (1+ val)))) + file-names)) + +(let ((seen (make-hash-table 31))) + (for-each + (lambda (file) + (let* ((stats (stat file)) + (key (cons (stat:dev stats) (stat:ino stats))) + (val (hash-ref seen key '()))) + (hash-set! seen key (cons file val)))) + file-names) + (hash-fold + (lambda (key value prior) + ;; process key == (dev . inode), value == list of filenames + ) + '() seen)) + +;; @@PLEAC@@_9.5 +;; use opendir, readdir, closedir +(let ((p (opendir dir))) + (let loop ((file (readdir p))) + (if (eof-object? file) + (close p) + ;; do something with file + ))) + +;; or define a utility function for this +(define (directory-files dir) + (if (not (access? dir R_OK)) + '() + (let ((p (opendir dir))) + (do ((file (readdir p) (readdir p)) + (ls '())) + ((eof-object? file) (closedir p) (reverse! ls)) + (set! ls (cons file ls)))))) + +;; to skip . and .. +(cddr (directory-files dir)) + +;; probably better to implement full Emacs style directory-files +(use-modules (ice-9 posix)) +(define plain-files + (let ((rx (make-regexp "^\\."))) + (lambda (dir) + (sort (filter (lambda (x) (eq? 'regular (stat:type (stat x)))) + (map (lambda (x) (string-append dir "/" x)) + (remove (lambda (x) (regexp-exec rx x)) + (cddr (directory-files dir))))) + string<)))) + +;; @@PLEAC@@_9.6 +(define (glob->regexp pat) + (let ((len (string-length pat)) + (ls '("^")) + (in-brace? #f)) + (do ((i 0 (1+ i))) + ((= i len)) + (let ((char (string-ref pat i))) + (case char + ((#\*) (set! ls (cons "[^.]*" ls))) + ((#\?) (set! ls (cons "[^.]" ls))) + ((#\[) (set! ls (cons "[" ls))) + ((#\]) (set! ls (cons "]" ls))) + ((#\\) + (set! i (1+ i)) + (set! ls (cons (make-string 1 (string-ref pat i)) ls)) + (set! ls (cons "\\" ls))) + (else + (set! ls (cons (regexp-quote (make-string 1 char)) ls)))))) + (string-concatenate (reverse (cons "$" ls))))) + +(define (glob pat dir) + (let ((rx (make-regexp (glob->regexp pat)))) + (filter (lambda (x) (regexp-exec rx x)) (directory-files dir)))) + +(define files (glob "*.c" ".")) +(define files (glob "*.[ch]" ".")) + +;; Not sure if the Schwartzian Transform would really be more +;; efficient here... perhaps with a much larger directory where very +;; few files matched. +(define dirs (filter + (lambda (x) (eq? 'directory (stat:type (stat x)))) + (map (lambda (x) (string-append dir "/" x)) + (sort (filter (lambda (x) (string-match "^[0-9]+$" x)) + (directory-files dir)) + (lambda (a b) + (< (string->number a) (string->number b))))))) + +;; @@PLEAC@@_9.7 +(define (find proc . dirs) + (cond ((pair? dirs) + (for-each proc (map (lambda (x) (string-append (car dirs) "/" x)) + (directory-files (car dirs)))) + (apply find proc (cdr dirs))))) + +(find (lambda (x) (format #t "~A~A\n" x + (if (equal? (stat:type (stat x)) 'directory) + "/" ""))) ".") + +(define saved-size -1) +(define saved-name "") +(define (biggest file) + (let ((stats (stat file))) + (if (eq? (stat:type stats) 'regular) + (let ((size (stat:size (stat file)))) + (cond ((> size saved-size) + (set! saved-size size) + (set! saved-name file))))))) +(apply find biggest (cdr (command-line))) +(format #t "Biggest file ~A in ~A is ~A bytes long.\n" + saved-name (cdr (command-line)) saved-size) + +#!/usr/local/bin/guile -s +!# +;; fdirs - find all directories +(define (print-dirs f) + (if (eq? (stat:type (stat f)) 'directory) + (write-line f))) +(apply find print-dirs (cdr (command-line))) + +;; @@PLEAC@@_9.8 +#!/usr/local/bin/guile -s +!# +;; rmtree - remove whole directory trees like rm -f +(define (finddepth proc . dirs) + (cond ((pair? dirs) + (apply finddepth proc (cdr dirs)) + (for-each proc (map (lambda (x) (string-append (car dirs) "/" x)) + (directory-files (car dirs))))))) +(define (zap f) + (let ((rm (if (eq? (stat:type (stat f)) 'directory) rmdir delete-file))) + (format #t "deleting ~A\n" f) + (catch #t + (lambda () (rm f)) + (lambda args (format #t "couldn't delete ~A\n" f))))) +(let ((args (cdr (command-line)))) + (if (null? args) + (error "usage: rmtree dir ..\n") + (apply finddepth zap args))) + +;; @@PLEAC@@_9.9 +(for-each + (lambda (file) + (let ((newname (function-of file))) + (catch #t + (lambda () (rename-file file newname)) + (lambda args (format (current-error-port) + "couldn't rename ~A to ~A\n" file newname))))) + names) + +#!/usr/local/bin/guile -s +!# +;; rename - Guile's filename fixer +(use-modules (ice-9 regex)) ; not needed, but often useful here +(define args (cdr (command-line))) +(if (null? args) (error "usage: rename expr [files]\n")) +(define proc (eval-string (car args))) +(for-each + (lambda (old) + (let ((new (proc old))) + (if (not (string=? old new)) + (catch #t + (lambda () (rename-file old new)) + (lambda args (format (current-error-port) + "couldn't rename ~A to ~A\n" old new)))))) + (cdr args)) + +;; command-line examples: +;; rename '(lambda (x) (regexp-substitute/global #f "\\.orig\$" x (quote pre)))' *.orig +;; rename string-downcase * +;; rename '(lambda (x) (if (string-match "^Make" x) x (string-downcase x)))' * +;; rename '(lambda (x) (string-append x ".bad"))' *.pl +;; rename '(lambda (x) (format #t "~a: ") (read-line))' * + +;; @@PLEAC@@_9.10 +(define base (basename path)) +(define base (dirname path ext)) +(define dir (dirname path)) + +(define path "/usr/lib/libc.a") +(define file (basename path)) +(define dir (dirname path)) + +(format #t "dir is ~A, file is ~A\n" dir file) + +(basename path ".a") ; libc + +(use-modules (ice-9 regex)) +(define (file-parse path . args) + (let* ((ext (if (null? args) "\\..*" (car args))) + (rx1 (string-append "^((.*)/)?(.*)?(" ext ")$")) + (rx2 (string-append "^((.*)/)?(.*)?()$"))) + (let ((m (or (string-match rx1 path) (string-match rx2 path)))) + (list (match:substring m 2) (match:substring m 3) + (match:substring m 4))))) + +(define (extension path . args) + (caddr (apply file-parse path args))) + +;; @@PLEAC@@_10.0 +; Note: Some of the examples will show code blocks in this style: +; +; (define +; ... code here ... +; ) +; +; This is not generally considered good style, and is not recommended; +; it is only used here to more clearly highlight block scope + +; By convention a 'global variable' i.e. a variable that is defined at +; the top-level, and as such, visible within any scope, is named with +; beginning and ending asterisks [and one to be used as a constant +; with beginning and ending plus signs] + +(define *greeted* 0) + +(define (hello) + (set! *greeted* (+ *greeted* 1)) + (print "hi there!, this procedure has been called" *greeted* "times")) + +(define (how-many-greetings) *greeted*) + +;; ------------ + +(hello) + +(define *greetings* (how-many-greetings)) + +(print "bye there!, there have been" *greetings* "greetings so far") + +;; @@PLEAC@@_10.1 +; Subroutine parameters are named [whether directly, or indirectly in +; the case of variable arguments - see next example]; this is the only +; means of access [This contrasts with languages like Perl and REXX which +; allow access to arguments via array subscripting, and function calls, +; respectively] +(define (hypotenuse side1 side2) + (sqrt (sum (* side1 side1) (* side2 side2)))) + +(define *diag* (hypotenuse 3 4)) + +;; ---- + +; 'other-sides' is the name of a list of containing any additional +; parameters. Note that a name is still used to access values +(define (hypotenuse side1 . other-sides) + (let ((all-sides (cons side1 other-sides))) + (for-each + (lambda (side) ...) + all-sides) + ...)) + +;; ---- + +(define *diag* (hypotenuse 3 4)) + +;; ---- + +; Possible to pack parameters into a single structure [e.g. list or +; array], and access values contained therein +(define (hypotenuse sides) + (let ((side1 (car sides)) (side2 (caar sides))) + (sqrt (sum (* side1 side1) (* side2 side2))))) + +;; ---- + +(define *args* '(3 4)) +(define *diag* (hypotenuse *args*)) + +;; ------------ + +; Parameters passed by reference, however, whether original object is +; modified depends on choice of functions used to manipulate them +; [most functions create copies and return these; mutating versions of +; same functions may also exist [see next example] +(define *nums* (vector 1.4 3.5 6.7)) + +(define (int-all vec) + (vector-map-in-order + (lambda (element) (inexact->exact (round element))) + vec)) + +; Copy created +(define *ints* (int-all *nums*)) + +(print *nums*) +(print *ints*) + +;; ---- + +(define *nums* (vector 1.4 3.5 6.7)) + +(define (trunc-all vec) + (array-map-in-order! + (lambda (element) (inexact->exact (round element))) + vec)) + +; Original modified +(trunc-all *nums*) + +;; @@PLEAC@@_10.2 +; Scheme is lexically-scoped; variables defined within a block are +; visible only within that block. Whilst nested / subordinate blocks +; have access to those variables, neither the caller, nor any called +; procedures have direct access to those same variables + +(define (some-func parm1 parm2 parm3) + ... paramaters visible here ... + + (let ((var1 ...) (var2 ...) (var3 ...) ...) + ... parameters also visible here, but variables, 'var1' etc + only visible within this block ... + ) + ... paramaters also visible here, but still within procedure body ... +) + +;; ------------ + +; Top-level definitions - accessable globally +(define *name* (caar (command-line))) +(define *age* (cadr (command-line))) + +(define *start* (fetch-time)) + +;; ---- + +; Lexical binding - accessable only within this block +(let ((name (caar (command-line))) + (age (cadr (command-line))) + (start (fetch-time))) + ... variables only visible here ... +) + +;; ------------ + +(define *pair* '(1 . 2)) + +; 'a' and 'b' need to be dereferenced and separately defined [Also, +; since globally defined, should really be named, '*a*', '*b*', etc] +(define a (car *pair*)) +(define b (cdr *pair*)) +(define c (fetch-time)) + +(define (run-check) + ... do something with 'a', 'b', and 'c' ... +) + +(define (check-x x y) + (if (run-check) + (print "got" x))) + +; Calling 'check-x'; 'run-check' has access to 'a', 'b', and 'c' +(check-x ...) + +;; ---- + +; If defined within a block, variables 'a', 'b', and 'c' are no longer +; accessable anywhere except that scope. Therefore, 'run-check' as +; defined above can no longer access these variables [in fact, the code +; will fail because variables 'a', 'b', and 'c' do not exist when +; 'run-check' is defined] +(let ((a (car *pair*)) + (b (cdr *pair*)) + (c (fetch-time))) + ... + (check-x ...) + ... +) + +;; ---- + +; The procedures, 'run-check' and 'check-x' are defined within the +; same block as variables, 'a', 'b', and 'c', so have direct access to +; them +(let* ((a (car *pair*)) + (b (cdr *pair*)) + (c (fetch-time)) + + (run-check + (lambda () ... do something with 'a', 'b', and 'c' ...)) + + (check-x + (lambda (x y) + (if (run-check) + (print "got" x)))) ) + ... + (check-x ...) + ... +) + +;; @@PLEAC@@_10.3 +; Ordinarily, a variable must be initialised when it is defined, +; whether at the top-level: +(define *variable* 1) + +; ... or within a 'let' binding +(let* ((variable 1) + (mysub + (lambda () ... accessing 'variable' ...))) + ... do stuff ... +) + +; However, since Scheme allows syntactic extensions via 'macros' [of +; which there are two varieties: hygenic and LISP-based], it is +; possible to create new forms which alter this behaviour. For example, +; in this tutorial: http://home.comcast.net/~prunesquallor/macro.txt +; there is a macro implementation equivalent to 'let, 'called, +; 'bind-values', which allows variables to be defined without initial +; values; an example follows: + +; Initialisation values for 'a' and 'b' not specified +(bind-values ((a) b (c (+ *global* 5))) + ... do stuff ... +) + +; In Scheme many things are possible, but not all those things are +; offered as standard features :) ! + +;; ------------ + +(let* ((counter 42) + (next-counter + (lambda () (set! counter (+ counter 1)) counter)) + (prev-counter + (lambda () (set! counter (- counter 1)) counter))) + + ... do stuff with 'next-counter' and 'prev-counter' ... +) + +;; ---- + +; A more complete, and practical, variation of the above code: + +; 'counter' constructor +(define (make-counter start) + (let* ((counter 42) + (next-counter + (lambda () (set! counter (+ counter 1)) counter)) + (prev-counter + (lambda () (set! counter (- counter 1)) counter))) + (lambda (op) + (cond + ((eq? op 'prev) prev-counter) + ((eq? op 'next) next-counter) + (else (lambda () (display "error:counter"))) )))) + +; Interface functions to 'counter' functionality +(define (prev-counter counter) (apply (counter 'prev) '())) +(define (next-counter counter) (apply (counter 'next) '())) + +; Create a 'counter' +(define *counter* (make-counter 42)) + +; Use the 'counter' ... +(print (prev-counter *counter*)) +(print (prev-counter *counter*)) +(print (next-counter *counter*)) + +;; @@PLEAC@@_10.4 +; Scheme interpreters generally provide a rich collection of procedure +; metadata, as well as easy access to a program's current 'execution +; state'. Put simply, provision of a powerful, highly customisable +; debugging / tracing facility is almost taken for granted. However, using +; it to perform as trivial a task as obtaining the current function name +; is less than trivial [at least it seems so in Guile] as it appears to +; require quite some setup work. Additionally, the documentation talks +; about facilities e.g. trap installation, that don't appear to be +; available [at least, I couldn't find them]. +; +; Example below uses in-built debugging facilities to dump a backtrace +; to a string port and extract the caller's name from the resulting +; string. Not exactly elegant ... + +; Execute using: guile --debug ... else no useful output seen +(use-modules (ice-9 debug)) + +(define (child num) + ; Create stack [i.e. activation record] object, discarding + ; irrelevant frames + (let ((s (make-stack #t 3 1)) + (trace-string-port (open-output-string)) + (parent-name "")) + + ; Dump backtrace to string port + (display-backtrace s trace-string-port) + + ; Extract caller's name from backtrace data + ; [shamefully crude - don't do this at home !] + (set! parent-name + (caddr (string-tokenize + (cadr (string-split + (get-output-string trace-string-port) + #\newline)) + char-set:graphic))) + + ; Who's your daddy ? + (print parent-name))) + +; Each invocation of 'child' should see 'parent' displayed as +; the caller +(define (parent) + (child 1) + (child 2) + (child 3)) + +(parent) + +;; @@PLEAC@@_10.5 +; Procedure parameters are references to entities, so there is no special +; treatment required. If an argument represents a mutable object such +; as an array, then care should be taken to not mutate the object within +; the procedure, or a copy of the object be made and used + +(array-diff *array1* *array2*) + +;; ------------ + +(define (add-vector-pair x y) + (let* ((vector-length (vector-length x)) + (new-vec (make-vector vector-length))) + (let loop ((i 0)) + (cond + ((= i vector-length) new-vec) + (else + (vector-set! new-vec i (+ (vector-ref x i) (vector-ref y i))) + (loop (+ i 1)) ))) )) + +;; ---- + +(define *a* '#(1 2)) +(define *b* '#(5 8)) + +(define *c* (add-vector-pair *a* *b*)) + +(print *c*) + +;; ---- + + ... + + (if (and (vector? a1) (vector? a2)) + (print (add-vector-pair a1 a2)) + ;else + (print "usage: add-vector-pair a1 a2")) + + ... + +;; @@PLEAC@@_10.6 +; AFAIK there is no Scheme equivalent to Perl's 'return context' where +; it is possible to use language primitives [e.g. 'wantarray'] to +; dynamically specify the return type of a procedure. It is, however, +; possible to: +; * Return one of several types from a procedure, whether based on +; processing results [e.g. 'false' on error, numeric on success], or +; perhaps specified via control argument +; * Check procedure return type and take appropriate action + +(define (my-sub) + (let* ((datatype (vector '() 7 '(1 2 3) "abc" 'sym))) + (vector-ref datatype (random (vector-length datatype))) )) + +;; ---- + +; '*result*' is bound to a randomly chosen datatype +(define *result* (my-sub)) + +(cond + ; It is common to return an empty list to represent 'void' + ((null? *result*) (print "void context")) + + ((list? *result*) (print "list context")) + ((number? *result*) (print "scalar context")) + ((string? *result*) (print "string context")) + ((symbol? *result*) (print "atom context")) + (else (print "Unknown type"))) + +;; @@PLEAC@@_10.7 +; Keyword parameters are fully supported. Note that pairs have +; replaced Perl strings in the examples since they are easier to +; manipulate + +(use-modules (ice-9 optargs)) + +(define* (the-func #:key (increment (cons 10 's)) + (finish (cons 0 'm)) + (start (cons 0 'm))) + (print increment) + (print finish) + (print start)) + +(the-func) +(the-func #:increment (cons 20 's) #:start (cons 5 'm) #:finish (cons 30 'm)) +(the-func #:start (cons 5 'm) #:finish (cons 30 'm)) +(the-func #:finish (cons 30 'm)) +(the-func #:start (cons 5 'm) #:increment (cons 20 's)) + +;; @@PLEAC@@_10.8 +;; @@INCOMPLETE@@ +;; @@INCOMPLETE@@ + +;; @@PLEAC@@_10.9 +; The return of multiple values, whether arrays or other items, may be +; achieved via: +; * Packaging return items as a single list, structure or array, an +; approach which is usable across many languages, though can be +; clunky because the procedure caller must manually extract all +; items +; * The 'values' procedure, a more Schemish idiom, is usually used in +; conjunction with the 'call-with-values' procedure [the former combines +; multiple values, the latter captures and cleanly extracts them]. It +; comes into its own, however, when used to create a 'macro' [an +; extension to the Scheme language] like 'let-values', a variation of +; the 'let' form that allows multiple return values to be placed directly +; into separate variables. Implementation shown here is from 'The +; Scheme Programming Language, 3rd Edition' by R. Kent Dybvig, though +; there exists a more standard implementation in SRFI-11. There is also +; the 'receive' functionality accessable via: (use-modules (ice-9 receive)) + +; [1] Implementation of 'somefunc' returning muliple values via packaging +; items within a list that is returned +(define (somefunc) + (let ((a (make-vector 5)) + (h (make-hash-table 5))) + (list a h) )) + +; Retrieving procedure values requires that the return list be captured +; and each contained item separately extracted ['let*' used in place of +; 'let' to ensure correct retrieval order] +(let* ((return-list (somefunc)) + (a (car return-list)) + (b (cadr return-list))) + + ... do something with 'a' and 'b' ...) + +;; ---------------------------- + +; [2] Implementation of 'somefunc' returning muliple values using the +; 'values' procedure + +(use-syntax (ice-9 syncase)) + +; 'let-values' from: http://www.scheme.com/tspl3/syntax.html#fullletvalues +(define-syntax let-values + (syntax-rules () + ((_ () f1 f2 ...) (let () f1 f2 ...)) + ((_ ((fmls1 expr1) (fmls2 expr2) ...) f1 f2 ...) + (lvhelp fmls1 () () expr1 ((fmls2 expr2) ...) (f1 f2 ...))))) + +(define-syntax lvhelp + (syntax-rules () + ((_ (x1 . fmls) (x ...) (t ...) e m b) + (lvhelp fmls (x ... x1) (t ... tmp) e m b)) + ((_ () (x ...) (t ...) e m b) + (call-with-values + (lambda () e) + (lambda (t ...) + (let-values m (let ((x t) ...) . b))))) + ((_ xr (x ...) (t ...) e m b) + (call-with-values + (lambda () e) + (lambda (t ... . tmpr) + (let-values m (let ((x t) ... (xr tmpr)) . b))))))) + +;; ------------ + +(define (somefunc) + (let ((a (make-vector 5)) + (h (make-hash-table 5))) + (values a h) )) + +; Multiple return items placed directly into separate variables +(let-values ( ((a h) (somefunc)) ) + (print (array? a)) + (print (hash-table? h))) + +;; @@PLEAC@@_10.10 +; Like most modern languages, Scheme supports exceptions for handling +; failure, something that will be illustrated in another section. However, +; conventions exist as to the choice of value used to indicate failure: +; * Empty list i.e. '() is often used for this task, as is it's string +; counterpart, "", the empty string +; * Return false i.e. #f to indicate failed / not found etc, and a valid +; value otherwise [e.g. testing set membership: if not a member, return +; #f, but if a member, return the item itself rather than #t] + +; Return empty list as indicating 'failure' +(define (sub-failed) '()) + +;; ------------ + +(define (look-for-something) + ... + (if (something-found) + ; Item found, return the item + something + ;else + ; Not found, indicate failure + #f + )) + +;; ---- + +(if (not (look-for-something)) + (print "Item could not be found ...") +;else + ; do something with item ... + ... + +;; ------------ + +; An interesting variation on returning #f as a failure indicator is +; in using the, 'false-if-exception' procedure whereby a procedure is +; executed, any exceptions it may throw caught, and handled by simply +; returning #f. See example in section on Exception Handling below. + +;; ------------ + +(define (ioctl) ... #f) + +(or (ioctl) (begin (print "can't ioctl") (exit 1))) + +;; @@PLEAC@@_10.11 +; Whether Scheme is seen to support prototyping depends on the definition +; of this term used: +; * Prototyping along the lines used in Ada, Modula X, and even C / C++, +; in which a procedure's interface is declared separately from its +; implementation, is *not* supported +; * Prototyping in which, as part of the procedure definition, parameter +; information must be supplied. This is a requirement in Scheme in that +; parameter number and names must be given, though there is no need to +; supply type information [optional and keyword parameters muddy the +; waters somewhat, but the general principle applies] + +(define (func-with-no-arg) ...) +(define (func-with-one-arg arg1) ...) +(define (func-with-two-arg arg1 arg2) ...) +(define (func-with-three-arg arg1 arg2 arg3) ...) + +;; @@PLEAC@@_10.12 +; Not exactly like the Perl example, but a way of immediately +; exiting from an application +(define (die msg . error-code) + (display (string-append msg "\n") (current-error-port)) + (exit (if (null? error-code) 1 (car error-code)))) + +;; ---- + +(die "some message") + +;; ------------ + +; An exception is thrown via 'throw'; argument must be a symbol +(throw 'some-exception) + +; Invalid attempts - these, themselves force a 'wrong-type-arg +; exception to be thrown +(throw #t) +(throw "my message") +(throw 1) + +;; ------------ + +; Example of a 'catch all' handler - 'proc' is executed, and any +; exception thrown is handled, in this case by simply returning false +(define (false-if-exception proc) + (catch #t + proc + (lambda (key . args) #f))) + +(define (func) + (print "Starting 'func' ...") + (throw 'myexception 1) + (print "Leaving 'func' ...")) + +;; ---- + +(if (not (false-if-exception main)) + (print "'func' raised an exception") + (print "'func' executed normally")) + +;; ------------ + +; More typical exception handling example in which: +; * 'func' is executed +; * 'catch' either: +; - returns return value of 'func' [if successful] +; - executes handler(s) + +(define (full-moon-exception-handler key . args) + (print "I'm executing after stack unwound !")) + +(define (full-moon-exception-prewind-handler key . args) + (print "I'm executing with the stack still intact !")) + +(define (func) + (print "Starting 'func' ...") + (throw 'full-moon-exception 1) + (print "Leaving 'func' ...")) + +(catch 'full-moon-exception + func + full-moon-exception-handler + full-moon-exception-prewind-handler) + +;; @@PLEAC@@_10.13 +; Scheme is lexically-scoped, so same-name, higher-level variables +; are merely shadowed in lower-level blocks. Upon exit from those +; blocks the higher-level values are again available. Therefore, the +; saving of global variables, as required by Perl, is not necessary + +; Global variable +(define age 18) + +; Procedure definition creates a closure - it captures the earlier +; version of, age', and will retain it +(define (func) + (print age)) + +(if (condition) + ; New 'local' variable created which acts to shadow the global + ; version + (let ((age 23)) + + ; Prints 23 because the global variable is shadowed within + ; this block + (print age) + + ; However, lexical-scoping ensures 'func' still accesses the + ; 'age' which was active when it was defined + (func) )) + +; The use of 'fluid-let' allows for similar behaviour to Perl's i.e. +; it mimics dynamic scope, but it does so cleanly in that once its +; scope ends any affected global variables are restored to previous +; values +(if (condition) + + ; This does not create a new 'local' variables but temporarily + ; sets the global variable, 'age' to 23 + (fluid-let ((age 23)) + + ; Prints 23 because it is accessing the global version of 'age' + (print age) + + ; Prints 23 because it is its lexically-scoped version of 'age' + ; that has its value altered, albeit temporarily + (func) )) + +;; @@PLEAC@@_10.14 +; Define two procedures, bind them to identifiers +(define (grow) (print "grow")) +(define (shrink) (print "shrink")) + +; Separate procedures executed +(grow) +(shrink) + +; Rebind identifier; now acts as alias for latter +(define grow shrink) + +; Same procedure executed in both cases +(grow) +(shrink) + +;; ------------ + +; As for previous except that rebinding is localised and +; ends once local scope exited +(let ((grow shrink)) + (grow) + (shrink)) + +;; ---------------------------- + +; Example of dynamically creating [from text data] and binding +; procedures. The example here is conceptually similar to the Perl +; example in that it makes use of an 'eval' type of facility to +; generate code from text. In Scheme such tasks are generally better +; dealt with using macros + +; List of procedure name / first argument pairs +(define *colours* + (list + '("red" . "baron") + '("blue" . "zephyr") + '("green" . "beret") + '("yellow" . "ribbon") + '("orange" . "county") + '("purple" . "haze") + '("violet" . "temper") )) + +; Build a series of procedures dynamically by traversing the +; *colours* list and obtaining: +; * Procedure name from first item of pair +; * Procedure argument from second item of pair +(for-each + (lambda (colour) + (let ((proc-string + (string-append + "(define " (car colour) " (lambda () " + "\"<FONT COLOR=" (car colour) ">" (cdr colour) + "</FONT>\"))" ))) + (eval-string proc-string))) + *colours*) + +; Apply each of the dynamically-built procedures +(for-each + (lambda (colour) + (print (apply (string->procedure (car colour)) '()))) + *colours*) + +;; @@PLEAC@@_10.15 +; AFAICT Guile doesn't implement an AUTOLOAD facility in which a +; 'replacement' function is available should another one fail to +; load [though there is an autoload feature available with modules +; which is a load-on-demand facility aimed at conserving memory and +; speeding up initial program load time]. +; +; One might think it would be feasable, however, to use exception +; handling to provide roughly similar functionality: + +; Catch all exceptions +(catch #t + ; Undefined procedure, 'x' + x + ; Exception handler could load missing code ? + (lambda (key . args) ... )) + +; However, an undefined function call is reported as: +; +; ERROR: Unbound variable: ... +; +; and this situation doesn't appear to be user-trappable. +; + +;; @@PLEAC@@_10.16 +; Both implementations below are correct, and exhibit identical +; behaviour + +(define (outer arg) + (let* ((x (+ arg 35)) + (inner (lambda () (* x 19)))) + (+ x (inner)))) + +;; ---------------------------- + +(define (outer arg) + (let ((x (+ arg 35))) + (define (inner) (* x 19)) + (+ x (inner)))) + +;; @@PLEAC@@_10.17 +;; @@INCOMPLETE@@ +;; @@INCOMPLETE@@ + +;; @@PLEAC@@_13.0 +;; Guile OOP is in the (oop goops) module (based on CLOS). All +;; following sections assume you have (oop goops loaded). +(use-modules (oop goops)) +(define-class <data-encoder> ()) +(define obj (make <data-encoder>)) + +(define obj #(3 5)) +(format #t "~A ~A\n" (class-of obj) (array-ref obj 1)) +(change-class v <human-cannibal>) ; has to be defined +(format #t "~A ~A\n" (slot-ref obj stomach) (slot-ref obj name)) + +(slot-ref obj 'stomach) +(slot-set! obj 'stomach "Empty") +(name obj) +(set! (name obj) "Thag") + +;; inheritance +(define-class <lawyer> (<human-cannibal>)) + +(define lector (make <human-cannibal>)) +(feed lector "Zak") +(move lector "New York") + +;; @@PLEAC@@_13.1 +(define-class <my-class> () + (start #:init-form (current-time)) + (age #:init-value 0)) + +;; classes must have predefined slots, but you could use one as a +;; dictionary: +(define-class <my-class> () + (start #:init-form (current-time)) + (age #:init-value 0) + (properties #:init-value '())) +(define (initialize (m <my-class>) initargs) + (and-let* ((extra (memq #:extra initargs))) + (slot-set! m 'properties (cdr extra)))) + +;; @@PLEAC@@_13.2 +;; For smobs (external C objects), you can specify a callback to be +;; performed when the object is garbage collected with the C API +;; function `scm_set_smob_free'. This solves the problem of cleaning up +;; after external objects and connections. Guile doesn't use reference +;; count garbage collection, so circular data structures aren't a +;; problem. + +;; @@PLEAC@@_13.3 +;; either use slot-ref/set! +(slot-ref obj 'name) +(slot-set! obj 'name value) + +;; or define the class with accessors +(define-class <my-class> () + (name #:accessor name)) +(name obj) +(set! (name obj) value) + +;; or use getters/setters to implement read/write-only slots +(define-class <my-class> () + (name #:getter name) + (age #:setter age)) +(name obj) +(set! (age obj) value) + +;; or implement getters/setters manually +(define-method ((setter name) (obj <my-class>) value) + (cond ((string-match "[^-\\w0-9']" value) + (warn "funny characters in name")) + ((string-match "[0-9]" value) + (warn "numbers in name")) + ((not (string-match "\\w+\\W+\\w+" value)) + (warn "prefer multiword names")) + ((not (string-match "\\w" value)) + (warn "name is blank"))) + (slot-set! obj 'name (string-downcase value))) + +;; @@PLEAC@@_13.4 +;; override the initialize method +(define body-count 0) + +(define-method (initialize (obj <person>) initargs) + (set! body-count (1+ body-count)) + (next-method)) + +(define people '()) +(do ((i 1 (1+ i))) + ((> i 10)) + (set! people (cons (make <person>) people))) + +(format #t "There are ~A people alive.\n" body-count) + +(define him (make <person>)) +(slot-set! him 'gender "male") + +(define her (make <person>)) +(slot-set! her 'gender "female") + +;; use the :class allocation method +(slot-set! (make <fixed-array>) 'max-bounds 100) ; set for whole class +(define alpha (make <fixed-array>)) +(format #t "Bound on alpha is ~D\n" (slot-ref alpha 'max-bounds)) +;; 100 + +(define beta (make <fixed-array>)) +(slot-set! beta 'max-bounds 50) ; still sets for whole class +(format #t "Bound on alpha is ~D\n" (slot-ref alpha 'max-bounds)) +;; 50 + +;; defined simply as +(define-class <fixed-array> () + (max-bounds #:init-value 7 #:allocation #:class)) + +;; @@PLEAC@@_13.5 +;; Guile classes are basically structs by definition. If you don't care +;; about OO programming at all, you can use records, which are portable +;; across most Schemes. This is, however, an OO chapter so I'll stick +;; to classes. +(define-class <person> () name age peers) + +(define p (make <person>)) +(slot-set! p 'name "Jason Smythe") +(slot-set! p 'age 13) +(slot-set! p 'peers '("Wilbur" "Ralph" "Fred")) +(format #t "At age ~D, ~A's first friend is ~A.\n" + (slot-ref p 'age) (slot-ref p 'name) (car (slot-ref p 'peers))) + +;; For type-checking and field validation, define the setters +;; accordingly. +(define-class <person> () + (name #:accessor name) + (age #:accessor age)) + +(define-method ((setter age) (p <person>) a) + (cond ((not (number? a)) + (warn "age" a "isn't numeric")) + ((> a 150) + (warn "age" a "is unreasonable"))) + (slot-set! p 'age a)) + +(define-class <family> () + (head #:init-form (make <person>) #:accessor head) + (address #:init-value "" #:accessor address) + (members #:init-value '() #:accessor members)) + +(define folks (make <family>)) + +(define dad (head folks)) +(set! (name dad) "John") +(set! (age dad) 34) + +(format #t "~A's age is ~D\n" (name dad) (age dad)) + +;; Macros are the usual way to add syntactic sugar + +;; For all fields of the same type, let's use _ to mean the slot name in +;; the options expansion. +(define-macro (define-uniform-class name supers slots . options) + `(define-class ,name ,supers + ,@(map (lambda (s) (cons s (map (lambda (o) (if (eq? o '_) s o)) options))) + slots))) + +(define-uniform-class <card> (name color cost type release text) + #:accessor _ #:init-value "") + +;; If you *really* wanted to enforce slot types you could use something +;; like the above with the custom setter. To illustrate reversing +;; normal slot definition args, we'll reverse an init-value: +(define-macro (define-default-class name supers . default&slots) + `(define-class ,name ,supers + ,@(map (lambda (d&s) (list (cadr d&s) + #:init-value (car d&s) + #:accessor (cadr d&s))) + default&slots))) + +(define-default-class hostent () + ("" name) + ('() aliases) + ("" addrtype) + (0 length) + ('() addr-list)) + +;; Nothing special needed for Aliases - all names are equal +(define type addrtype) +(define-method (addr (h <hostent>)) + (car (addr-list h))) + +;; @@PLEAC@@_13.6 +;; A little more clear than the Perl, but not very useful. +(define obj1 (make <some-class>)) +(define obj2 (make (class-of obj1))) + +;; Use the shallow-clone or deep-clone methods to initialize from +;; another instance. +(define obj1 (make <widget>)) +(define obj2 (deep-clone obj1)) + +;; @@PLEAC@@_13.7 +;; Use eval or a variant to convert from a symbol or string to the +;; actual method. As shown in 13.5 above, methods are first class and +;; you'd be more likely to store the actual method than the name in a +;; real Scheme program. +(define methname "flicker") +(apply-generic (eval-string methname) obj 10) + +(for-each (lambda (m) (apply-generic obj (eval-string m))) + '("start" "run" "stop")) + +;; really, don't do this... +(define methods '("name" "rank" "serno")) +(define his-info + (map (lambda (m) (cons m (apply-generic (eval-string m) obj))) + methods)) + +;; same as this: +(define his-info (list (cons "name" (name obj)) + (cons "rank" (rank obj)) + (cons "serno" (serno obj)))) + +;; a closure works +(define fnref (lambda args (method obj args))) +(fnref 10 "fred") +(method obj 10 fred) + +;; @@PLEAC@@_13.8 +;; use is-a? +(is-a? obj <http-message>) +(is-a? <http-response> <http-message>)
\ No newline at end of file diff --git a/test/scanners/scheme/strange.in.scm b/test/scanners/scheme/strange.in.scm new file mode 100644 index 0000000..4cb9c18 --- /dev/null +++ b/test/scanners/scheme/strange.in.scm @@ -0,0 +1,38 @@ + +("") +(string=? "K. Harper, M.D." ;; Taken from Section 6.3.3. (Symbols) of the R5RS + (symbol->string + (string->symbol "K. Harper, M.D."))) +;; BEGIN Factorial +(define factorial + (lambda (n) + (if (= n 1) + 1 + (* n (factorial (- n 1)))))) +;; END Factorial + + ;; BEGIN Square + (define square + (lambda (n) ;; My first lambda + (if (= n 0) + 0 + ;; BEGIN Recursive_Call + (+ (square (- n 1)) + (- (+ n n) 1))))) + ;; END Recursive_Call + ;; END Square + +;;LIST OF NUMBERS +(#b-1111 #xffa12 #o755 #o-755 +i -i +2i -2i 3+4i 1.6440287493492101i+2 1.344 3/4 #i23/70) + +;;a vector +#('(1 2 3) #\\a 3 #t #f) + +;;macros (USELESS AND INCORRECT, JUST TO CHECK THAT IDENTIFIERS ARE RECOGNIZED RIGHT) +(syntax-case () + ((_ name field ...) + (with-syntax + ((constructor (gen-id (syntax name) "make-" (syntax name))) + (predicate (gen-id (syntax name) (syntax name) "?")) + ((access ...) + (map (lambda (x) (gen-id x "set-" (syntax name) "-" x "!"))))))))
\ No newline at end of file |