merging in the content from the README

This commit is contained in:
Danny Yoo 2011-06-15 16:26:26 -04:00
parent 11135be94d
commit 6b8a3f30ea
5 changed files with 1133 additions and 0 deletions

View File

@ -2,6 +2,9 @@
# raco make -v --disable-inline test-analyzer.rkt
# racket test-analyzer.rkt
launcher: whalesong
racket make-launcher.rkt
whalesong:
raco make -v --disable-inline whalesong.rkt
@ -32,3 +35,8 @@ test-conform:
raco make -v --disable-inline tests/test-conform.rkt
racket tests/test-conform.rkt
doc:
scribble ++xref-in setup/xref load-collections-xref --redirect-main http://docs.racket-lang.org/ --dest generated-docs --dest-name index.html scribblings/manual.scrbl

11
README
View File

@ -7,6 +7,17 @@ Danny Yoo (dyoo@cs.wpi.edu)
======================================================================
See: http://hashcollision.org/whalesong/index.html for documentation.
The rest of the content in this document will migrate there shortly.
======================================================================
Prerequisite: Racket 5.1.1. The majority of the project is written
Typed Racket, and I highly recommend you use a version of Racket
that's at least 5.1.1; otherwise, compilation may take an unusual

5
info.rkt Normal file
View File

@ -0,0 +1,5 @@
#lang setup/infotab
(define name "Whalesong")
(define racket-launcher-libraries '("whalesong.rkt"))
(define racket-launcher-names '("whalesong"))

723
make-launcher.rkt Normal file
View File

@ -0,0 +1,723 @@
#lang racket/base
;; copy of launcher/launcher since it has a bug. I'll send a bug patch
;; upstream.
(require scheme/path
scheme/file
scheme/list
scheme/string
compiler/embed
setup/dirs
setup/variant
compiler/private/winutf16)
(define current-launcher-variant
(make-parameter (system-type 'gc)
(lambda (v)
(unless (memq v '(3m script-3m cgc script-cgc))
(raise-type-error
'current-launcher-variant
"variant symbol"
v))
v)))
(define (variant-available? kind cased-kind-name variant)
(cond
[(or (eq? 'unix (system-type))
(and (eq? 'macosx (system-type))
(eq? kind 'mzscheme)))
(let ([bin-dir (find-console-bin-dir)])
(and bin-dir
(file-exists?
(build-path bin-dir
(format "~a~a"
(case kind
[(mzscheme) 'racket]
[(mred) 'gracket])
(variant-suffix variant #f))))))]
[(eq? 'macosx (system-type))
;; kind must be mred, because mzscheme case is caught above
(directory-exists? (build-path (find-gui-bin-dir)
(format "~a~a.app"
cased-kind-name
(variant-suffix variant #f))))]
[(eq? 'windows (system-type))
(file-exists?
(build-path
(if (eq? kind 'mzscheme) (find-console-bin-dir) (find-gui-bin-dir))
(format "~a~a.exe" cased-kind-name (variant-suffix variant #t))))]
[else (error "unknown system type")]))
(define (available-variants kind)
(let* ([cased-kind-name (if (eq? kind 'mzscheme)
"Racket"
"GRacket")]
[normal-kind (system-type 'gc)]
[alt-kind (if (eq? '3m normal-kind)
'cgc
'3m)]
[normal (if (variant-available? kind cased-kind-name normal-kind)
(list normal-kind)
null)]
[alt (if (variant-available? kind cased-kind-name alt-kind)
(list alt-kind)
null)]
[script (if (and (eq? 'macosx (system-type))
(eq? kind 'mred)
(pair? normal))
(if (eq? normal-kind '3m)
'(script-3m)
'(script-cgc))
null)]
[script-alt (if (and (memq alt-kind alt)
(pair? script))
(if (eq? alt-kind '3m)
'(script-3m)
'(script-cgc))
null)])
(append normal alt script script-alt)))
(define (available-gracket-variants)
(available-variants 'mred))
(define (available-mred-variants)
(available-variants 'mred))
(define (available-racket-variants)
(available-variants 'mzscheme))
(define (available-mzscheme-variants)
(available-variants 'mzscheme))
(define (install-template dest kind mz mr)
(define src (build-path (collection-path "launcher")
(if (eq? kind 'mzscheme) mz mr)))
(when (or (file-exists? dest)
(directory-exists? dest)
(link-exists? dest))
(delete-directory/files dest))
(copy-file src dest)
;; Make sure we can write.
(file-or-directory-permissions dest
(bitwise-ior
(file-or-directory-permissions dest 'bits)
user-write-bit)))
(define (script-variant? v)
(memq v '(script-3m script-cgc)))
(define (add-file-suffix path variant mred?)
(let ([s (variant-suffix
variant
(case (system-type)
[(unix) #f]
[(windows) #t]
[(macosx) (and mred? (not (script-variant? variant)))]))])
(if (string=? "" s)
path
(path-replace-suffix
path
(string->bytes/utf-8
(if (and (eq? 'windows (system-type))
(regexp-match #rx#"[.]exe$" (path->bytes path)))
(format "~a.exe" s)
s))))))
(define (string-append/spaces f flags)
(string-append* (append-map (lambda (x) (list (f x) " ")) flags)))
(define (str-list->sh-str flags)
(string-append/spaces
(lambda (s)
(string-append "'" (regexp-replace* #rx"'" s "'\"'\"'") "'"))
flags))
(define (str-list->dos-str flags)
(define (trans s)
(if (not (regexp-match? #rx"[ \n\t\r\v\"\\]" s))
s
(list->string
(let loop ([l (string->list s)] [slashes '()])
(cond [(null? l) '()]
[(char-whitespace? (car l))
`(,@slashes #\" ,(car l) #\" ,@(loop (cdr l) '()))]
[(eq? #\\ (car l))
`(#\\ ,@(loop (cdr l) (cons #\\ slashes)))]
[(eq? #\" (car l))
`(,@slashes #\" #\\ #\" #\" ,@(loop (cdr l) '()))]
[else `(,(car l) ,@(loop (cdr l) '()))])))))
(string-append/spaces trans flags))
(define one-arg-x-flags '((xa "-display")
(xb "-geometry")
(xc "-bg" "-background")
(xd "-fg" "-foregound")
(xe "-font")
(xf "-name")
(xg "-selectionTimeout")
(xh "-title")
(xi "-xnllanguage")
(xj "-xrm")))
(define no-arg-x-flags '((xk "-iconic")
(xl "-rv" "-reverse")
(xm "+rv")
(xn "-synchronous")
(xo "-singleInstance")))
(define (skip-x-flags flags)
(let ([xfmem (lambda (flag) (lambda (xf) (member flag (cdr xf))))])
(let loop ([f flags])
(cond [(null? f) null]
[(ormap (xfmem (car f)) one-arg-x-flags)
(if (null? (cdr f)) null (loop (cddr f)))]
[(ormap (xfmem (car f)) no-arg-x-flags) (loop (cdr f))]
[else f]))))
(define (output-x-arg-getter exec args)
(let ([or-flags (lambda (l) (string-append* (add-between l " | ")))])
(string-append*
(append
(list "# Find X flags and shift them to the front\n"
"findxend() {\n"
" oneargflag=''\n"
" case \"$1\" in\n")
(map
(lambda (f)
(format (string-append
" ~a)\n"
" oneargflag=\"$1\"\n"
" ~a=\"$2\"\n"
" ;;\n")
(or-flags (cdr f))
(car f)))
one-arg-x-flags)
(map
(lambda (f)
(format " ~a)\n ~a=yes\n ;;\n" (or-flags (cdr f)) (car f)))
no-arg-x-flags)
(list
(format (string-append
" *)\n ~a~a ~a ;;\n"
" esac\n"
" shift\n"
" if [ \"$oneargflag\" != '' ] ; then\n"
" if [ \"${1+n}\" != 'n' ] ; then echo $0: missing argument for standard X flag $oneargflag ; exit 1 ; fi\n"
" shift\n"
" fi\n"
" findxend ${1+\"$@\"}\n"
"}\nfindxend ${1+\"$@\"}\n")
exec
(string-append*
(append
(map (lambda (f)
(format " ${~a+\"~a\"} ${~a+\"$~a\"}"
(car f) (cadr f) (car f) (car f)))
one-arg-x-flags)
(map (lambda (f)
(format " ${~a+\"~a\"}" (car f) (cadr f)))
no-arg-x-flags)))
args))))))
(define (protect-shell-string s)
(regexp-replace*
#rx"[\"`'$\\]" (if (path? s) (path->string s) s) "\\\\&"))
(define (normalize+explode-path p)
(explode-path (normal-case-path (simple-form-path p))))
(define (relativize bindir-explode dest-explode)
(let loop ([b bindir-explode] [d dest-explode])
(if (and (pair? b) (equal? (car b) (car d)))
(loop (cdr b) (cdr d))
(let ([p (append (map (lambda (x) 'up) (cdr d)) b)])
(if (null? p) #f (apply build-path p))))))
(define (make-relative-path-header dest bindir)
;; rely only on binaries in /usr/bin:/bin
(define (has-exe? exe)
(or (file-exists? (build-path "/usr/bin" exe))
(file-exists? (build-path "/bin" exe))))
(let* ([has-readlink? (and (not (eq? 'macosx (system-type)))
(has-exe? "readlink"))]
[dest-explode (normalize+explode-path dest)]
[bindir-explode (normalize+explode-path bindir)])
(if (and (has-exe? "dirname") (has-exe? "basename")
(or has-readlink? (and (has-exe? "ls") (has-exe? "sed")))
(equal? (car dest-explode) (car bindir-explode)))
(string-append
"# Make this PATH-independent\n"
"saveP=\"$PATH\"\n"
"PATH=\"/usr/bin:/bin\"\n"
"\n"
(if has-readlink? ""
(string-append
"# imitate possibly-missing readlink\n"
"readlink() {\n"
" ls -l -- \"$1\" | sed -e \"s/^.* -> //\"\n"
"}\n"
"\n"))
"# Remember current directory\n"
"saveD=`pwd`\n"
"\n"
"# Find absolute path to this script,\n"
"# resolving symbolic references to the end\n"
"# (changes the current directory):\n"
"D=`dirname \"$0\"`\n"
"F=`basename \"$0\"`\n"
"cd \"$D\"\n"
"while test "
;; On solaris, Edward Chrzanowski from Waterloo says that the man
;; page says that -L is not supported, but -h is; on other systems
;; (eg, freebsd) -h is listed as a compatibility feature
(if (regexp-match #rx"solaris" (path->string
(system-library-subpath)))
"-h" "-L")
" \"$F\"; do\n"
" P=`readlink \"$F\"`\n"
" D=`dirname \"$P\"`\n"
" F=`basename \"$P\"`\n"
" cd \"$D\"\n"
"done\n"
"D=`pwd`\n"
"\n"
"# Restore current directory\n"
"cd \"$saveD\"\n"
"\n"
"bindir=\"$D"
(let ([s (relativize bindir-explode dest-explode)])
(if s (string-append "/" (protect-shell-string s)) ""))
"\"\n"
"PATH=\"$saveP\"\n")
;; fallback to absolute path header
(make-absolute-path-header bindir))))
(define (make-absolute-path-header bindir)
(string-append "bindir=\""(protect-shell-string bindir)"\"\n"))
(define (make-unix-launcher kind variant flags dest aux)
(install-template dest kind "sh" "sh") ; just for something that's executable
(let* ([alt-exe (let ([m (and (eq? kind 'mred)
(script-variant? variant)
(assq 'exe-name aux))])
(and m
(format "~a~a.app/Contents/MacOS/~a~a"
(cdr m) (variant-suffix variant #t)
(cdr m) (variant-suffix variant #t))))]
[x-flags? (and (eq? kind 'mred)
(eq? (system-type) 'unix)
(not (script-variant? variant)))]
[post-flags (cond
[x-flags? (skip-x-flags flags)]
[alt-exe null]
[else flags])]
[pre-flags (cond
[(not x-flags?) null]
[else
(let loop ([f flags])
(if (eq? f post-flags)
null
(cons (car f) (loop (cdr f)))))])]
[pre-str (str-list->sh-str pre-flags)]
[post-str (str-list->sh-str post-flags)]
[header (string-append
"#!/bin/sh\n"
"# This script was created by make-"
(symbol->string kind)"-launcher\n")]
[dir-finder
(let ([bindir (if alt-exe
(find-gui-bin-dir)
(find-console-bin-dir))])
(if (let ([a (assq 'relative? aux)])
(and a (cdr a)))
(make-relative-path-header dest bindir)
(make-absolute-path-header bindir)))]
[exec (format
"exec \"${bindir}/~a~a\" ~a"
(or alt-exe (case kind
[(mred) "gracket"]
[(mzscheme) "racket"]))
(if alt-exe "" (variant-suffix variant #f))
pre-str)]
[args (format
"~a~a ${1+\"$@\"}\n"
(if alt-exe "" "-N \"$0\" ")
post-str)]
[assemble-exec (if (and (eq? kind 'mred)
(not (script-variant? variant))
(not (null? post-flags)))
output-x-arg-getter
string-append)])
(unless (find-console-bin-dir)
(error 'make-unix-launcher "unable to locate bin directory"))
(with-output-to-file dest
#:exists 'truncate
(lambda ()
(display header)
(newline)
;; comments needed to rehack launchers when paths change
;; (see setup/unixstyle-install.ss)
(display "# {{{ bindir\n")
(display dir-finder)
(display "# }}} bindir\n")
(newline)
(display (assemble-exec exec args))))))
(define (utf-16-regexp b)
(byte-regexp (bytes-append (bytes->utf-16-bytes b)
#"[^>]*"
(bytes->utf-16-bytes #">"))))
(define (make-windows-launcher kind variant flags dest aux)
(if (not (and (let ([m (assq 'independent? aux)])
(and m (cdr m)))))
;; Normal launcher:
(make-embedding-executable
dest (eq? kind 'mred) #f null null null flags aux #t variant)
;; Independent launcher (needed for Setup PLT):
(begin
(install-template dest kind "mzstart.exe" "mrstart.exe")
(let ([bstr (bytes->utf-16-bytes
(string->bytes/utf-8 (str-list->dos-str flags)))]
[p (open-input-file dest)]
[m (utf-16-regexp #"<Command Line: Replace This")]
[x (utf-16-regexp #"<Executable Directory: Replace This")]
[v (utf-16-regexp #"<Executable Variant: Replace This")])
(let* ([exedir (bytes->utf-16-bytes
(bytes-append
(path->bytes (let ([bin-dir (if (eq? kind 'mred)
(find-gui-bin-dir)
(find-console-bin-dir))])
(if (let ([m (assq 'relative? aux)])
(and m (cdr m)))
(or (relativize (normalize+explode-path bin-dir)
(normalize+explode-path dest))
(build-path 'same))
bin-dir)))
;; null wchar marks end of executable directory
#"\0\0"))]
[find-it ; Find the magic start
(lambda (magic s)
(file-position p 0)
(let ([m (regexp-match-positions magic p)])
(if m
(car m)
(begin
(close-input-port p)
(when (file-exists? dest) (delete-file dest))
(error 'make-windows-launcher
"Couldn't find ~a position in template" s)))))]
[exedir-poslen (find-it x "executable path")]
[command-poslen (find-it m "command-line")]
[variant-poslen (find-it v "variant")]
[pos-exedir (car exedir-poslen)]
[len-exedir (- (cdr exedir-poslen) (car exedir-poslen))]
[pos-command (car command-poslen)]
[len-command (- (cdr command-poslen) (car command-poslen))]
[pos-variant (car variant-poslen)]
[space (char->integer #\space)]
[write-magic
(lambda (p s pos len)
(file-position p pos)
(display s p)
(display (make-bytes (- len (bytes-length s)) space) p))]
[check-len
(lambda (len s es)
(when (> (bytes-length s) len)
(when (file-exists? dest) (delete-file dest))
(error
(format
"~a exceeds limit of ~a characters with ~a characters: ~a"
es len (string-length s) s))))])
(close-input-port p)
(check-len len-exedir exedir "executable home directory")
(check-len len-command bstr "collection/file name")
(let ([p (open-output-file dest #:exists 'update)])
(write-magic p exedir pos-exedir len-exedir)
(write-magic p (bytes-append bstr #"\0\0") pos-command len-command)
(let* ([suffix (variant-suffix (current-launcher-variant) #t)]
[suffix-bytes
(bytes-append
(list->bytes
(append-map (lambda (c) (list c 0))
(bytes->list (string->bytes/latin-1 suffix))))
#"\0\0")])
(write-magic p suffix-bytes pos-variant (bytes-length suffix-bytes)))
(close-output-port p)))))))
;; OS X launcher code:
;; make-macosx-launcher : symbol (listof str) pathname ->
(define (make-macosx-launcher kind variant flags dest aux)
(if (or (eq? kind 'mzscheme) (script-variant? variant))
;; Racket or script launcher is the same as for Unix
(make-unix-launcher kind variant flags dest aux)
;; Gracket "launcher" is a stand-alone executable
(make-embedding-executable dest (eq? kind 'mred) #f
null null null
flags
aux
#t
variant)))
(define (make-macos-launcher kind variant flags dest aux)
(install-template dest kind "GoMr" "GoMr")
(let* ([p (open-input-file dest)]
[m (regexp-match-positions #rx#"<Insert offset here>" p)])
;; fast-forward to the end:
(let ([s (make-bytes 4096)])
(let loop ()
(if (eof-object? (read-bytes! s p)) (file-position p) (loop))))
(let ([data-fork-size (file-position p)])
(close-input-port p)
(let ([p (open-output-file dest #:exists 'update)]
[str (str-list->sh-str
(append (if (eq? kind 'mred) null '("-Z")) flags))])
(file-position p (caar m))
(display (integer->integer-bytes (string-length str) 4 #t #t) p)
(display (integer->integer-bytes data-fork-size 4 #t #t) p)
(file-position p data-fork-size)
(display str p)
(close-output-port p)))))
(define (get-maker)
(case (system-type)
[(unix) make-unix-launcher]
[(windows) make-windows-launcher]
[(macos) make-macos-launcher]
[(macosx) make-macosx-launcher]))
(define (make-gracket-launcher flags dest [aux null])
((get-maker) 'mred (current-launcher-variant) flags dest aux))
(define (make-mred-launcher flags dest [aux null])
((get-maker) 'mred (current-launcher-variant) (list* "-I" "scheme/gui/init" flags) dest aux))
(define (make-racket-launcher flags dest [aux null])
((get-maker) 'mzscheme (current-launcher-variant) flags dest aux))
(define (make-mzscheme-launcher flags dest [aux null])
((get-maker) 'mzscheme (current-launcher-variant) (list* "-I" "scheme/init" flags) dest aux))
(define (strip-suffix s)
(path-replace-suffix s #""))
(define (build-aux-from-path aux-root)
(let ([aux-root (if (string? aux-root) (string->path aux-root) aux-root)])
(define (try key suffix)
(let ([p (path-replace-suffix aux-root suffix)])
(if (file-exists? p) (list (cons key p)) null)))
(append
(try 'icns #".icns")
(try 'ico #".ico")
(try 'independent? #".lch")
(let ([l (try 'creator #".creator")])
(if (null? l)
l
(with-handlers ([exn:fail:filesystem? (lambda (x) null)])
(with-input-from-file (cdar l)
(lambda ()
(let ([s (read-string 4)])
(if s (list (cons (caar l) s)) null)))))))
(let ([l (try 'file-types #".filetypes")])
(if (null? l)
l
(with-handlers ([exn:fail:filesystem? (lambda (x) null)])
(with-input-from-file (cdar l)
(lambda ()
(let*-values ([(d) (read)]
[(local-dir base dir?) (split-path aux-root)]
[(icon-files)
(append-map
(lambda (spec)
(let ([m (assoc "CFBundleTypeIconFile" spec)])
(if m
(list (build-path
(path->complete-path local-dir)
(format "~a.icns" (cadr m))))
null)))
d)])
(list (cons 'file-types d)
(cons 'resource-files
(remove-duplicates icon-files)))))))))
(let ([l (try 'file-types #".utiexports")])
(if (null? l)
l
(with-handlers ([exn:fail:filesystem? (lambda (x) null)])
(with-input-from-file (cdar l)
(lambda ()
(let ([d (read)])
(list (cons 'uti-exports d)))))))))))
(define (make-gracket-program-launcher file collection dest)
(make-mred-launcher (list "-l-" (string-append collection "/" file))
dest
(build-aux-from-path
(build-path (collection-path collection)
(strip-suffix file)))))
(define (make-mred-program-launcher file collection dest)
(make-gracket-program-launcher file collection dest))
(define (make-racket-program-launcher file collection dest)
(make-mzscheme-launcher (list "-l-" (string-append collection "/" file))
dest
(build-aux-from-path
(build-path (collection-path collection)
(strip-suffix file)))))
(define (make-mzscheme-program-launcher file collection dest)
(make-racket-program-launcher file collection dest))
(define (unix-sfx file mred?)
(string-downcase (regexp-replace* #px"\\s" file "-")))
(define (sfx file mred?)
(case (system-type)
[(unix) (unix-sfx file mred?)]
[(windows)
(string-append (if mred? file (unix-sfx file mred?)) ".exe")]
[else file]))
(define (program-launcher-path name mred?)
(let* ([variant (current-launcher-variant)]
[mac-script? (and (eq? (system-type) 'macosx)
(script-variant? variant))])
(let ([p (add-file-suffix
(build-path
(if (or mac-script? (not mred?))
(find-console-bin-dir)
(find-gui-bin-dir))
((if mac-script? unix-sfx sfx) name mred?))
variant
mred?)])
(if (and (eq? (system-type) 'macosx)
(not (script-variant? variant)))
(path-replace-suffix p #".app")
p))))
(define (gracket-program-launcher-path name)
(program-launcher-path name #t))
(define (mred-program-launcher-path name)
(gracket-program-launcher-path name))
(define (racket-program-launcher-path name)
(case (system-type)
[(macosx)
(add-file-suffix (build-path (find-console-bin-dir) (unix-sfx name #f))
(current-launcher-variant)
#f)]
[else (program-launcher-path name #f)]))
(define (mzscheme-program-launcher-path name)
(racket-program-launcher-path name))
(define (gracket-launcher-is-directory?)
#f)
(define (racket-launcher-is-directory?)
#f)
(define (mred-launcher-is-directory?)
#f)
(define (mzscheme-launcher-is-directory?)
#f)
(define (gracket-launcher-is-actually-directory?)
(and (eq? 'macosx (system-type))
(not (script-variant? (current-launcher-variant)))))
(define (mred-launcher-is-actually-directory?)
(gracket-launcher-is-actually-directory?))
(define (racket-launcher-is-actually-directory?)
#f)
(define (mzscheme-launcher-is-actually-directory?)
#f)
;; Helper:
(define (put-file-extension+style+filters type)
(case type
[(windows) (values "exe" null '(("Executable" "*.exe")))]
[(macosx) (values "app" '(packages) '(("App" "*.app")))]
[else (values #f null null)]))
(define (gracket-launcher-add-suffix path)
(embedding-executable-add-suffix path #t))
(define (mred-launcher-add-suffix path)
(gracket-launcher-add-suffix path))
(define (racket-launcher-add-suffix path)
(embedding-executable-add-suffix path #f))
(define (mzscheme-launcher-add-suffix path)
(racket-launcher-add-suffix path))
(define (gracket-launcher-put-file-extension+style+filters)
(put-file-extension+style+filters
(if (and (eq? 'macosx (system-type))
(script-variant? (current-launcher-variant)))
'unix
(system-type))))
(define (mred-launcher-put-file-extension+style+filters)
(gracket-launcher-put-file-extension+style+filters))
(define (racket-launcher-put-file-extension+style+filters)
(put-file-extension+style+filters
(if (eq? 'macosx (system-type)) 'unix (system-type))))
(define (mzscheme-launcher-put-file-extension+style+filters)
(racket-launcher-put-file-extension+style+filters))
(define (gracket-launcher-up-to-date? dest [aux null])
(racket-launcher-up-to-date? dest aux))
(define (mred-launcher-up-to-date? dest [aux null])
(racket-launcher-up-to-date? dest aux))
(define (mzscheme-launcher-up-to-date? dest [aux null])
(racket-launcher-up-to-date? dest aux))
(define (racket-launcher-up-to-date? dest [aux null])
(cond
;; When running Setup PLT under Windows, the
;; launcher process stays running until Racket
;; completes, which means that it cannot be
;; overwritten at that time. So we assume
;; that a Setup-PLT-style independent launcher
;; is always up-to-date.
[(eq? 'windows (system-type))
(and (let ([m (assq 'independent? aux)]) (and m (cdr m)))
(file-exists? dest))]
;; For any other setting, we could implement
;; a fancy check, but for now always re-create
;; launchers.
[else #f]))
(define (install-gracket-program-launcher file collection name)
(make-gracket-program-launcher file collection
(gracket-program-launcher-path name)))
(define (install-racket-program-launcher file collection name)
(make-racket-program-launcher file collection
(racket-program-launcher-path name)))
(define (install-mred-program-launcher file collection name)
(make-mred-program-launcher file collection
(mred-program-launcher-path name)))
(define (install-mzscheme-program-launcher file collection name)
(make-mzscheme-program-launcher file collection
(mzscheme-program-launcher-path name)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(require racket/runtime-path)
(define-runtime-path whalesong-path "whalesong.rkt")
(make-racket-launcher (list (path->string whalesong-path))
"whalesong"
'())

386
scribblings/manual.scrbl Normal file
View File

@ -0,0 +1,386 @@
#lang scribble/manual
@(require planet/scribble
planet/version
planet/resolver
scribble/eval
racket/sandbox
(for-label racket/base))
@;; I may need an evaluator for some small examples.
@(define my-evaluator
(call-with-trusted-sandbox-configuration
(lambda ()
(parameterize ([sandbox-output 'string]
[sandbox-error-output 'string])
(make-evaluator 'racket)))))
@title{Whalesong: a Racket to JavaScript compiler}
@author+email["Danny Yoo" "dyoo@cs.wpi.edu"]
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@; Warning Will Robinson, Warning!
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@centered{@larger{@bold{@italic{Warning: this is work in progress!}}}}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Introduction}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Whalesong is a compiler from Racket to JavaScript; it takes Racket
programs and translates them so that they can run stand-alone on a
user's web browser. It should allow Racket programs to run with
little modification, and provide access through the foreign-function
interface to native JavaScript APIs. The included runtime library
also includes a framework to programming the web in functional
event-driven style.
The GitHub source repository to Whalesong can be found at
@url{https://github.com/dyoo/whalesong}.
Prerequisites: at least @link["http://racket-lang.org/"]{Racket
5.1.1}, and a @link["http://www.java.com"]{Java 1.6} SDK.
@; (This might be superfluous information, so commented out
@; for the moment...)
@;The majority of the project is written
@;@link["http://docs.racket-lang.org/ts-guide/index.html"]{Typed
@;Racket}, and Racket 5.1.1 and above provides the support necessary to
@;compile Whalesong; otherwise, compilation may take an unusual amount
@;of time.
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Getting started}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@subsection{Installing Whalesong}
At the time of this writing, Whalesong hasn't been deployed to
@link["http://planet.racket-lang.org"]{PLaneT} yet, so getting it
requires doing a little bit of manual work. The steps are:
@itemlist[
@item{Check Whalesong out of Github.}
@item{Set up the PLaneT development link to your local Whalesong instance.}
@item{Run @tt{raco setup} over Whalesong to finish the installation}]
We can check it out of the source repository in
@link["https://github.com/"]{GitHub}; the repository can be checked out by
using @tt{git clone}. At the command-line, clone the tree
with: @verbatim|{ $ git clone git://github.com/dyoo/whalesong.git }|
This should check the repository in the current directory.
Next, let's set up a @link["http://docs.racket-lang.org/planet/Developing_Packages_for_PLaneT.html#(part._devlinks)"]{PLaneT development link}. Make sure you are in the
parent directory that contains the @filepath{whalesong} repository, and
then run this on your command line:
@verbatim|{
$ planet link dyoo whalesong.plt 1 0 whalesong
}|
Finally, we need to set up Whalesong with @tt{raco setup}.
Here's how to do this at the command
line:
@verbatim|{
$ raco setup -P dyoo whalesong.plt 1 0
}|
This should compile Whalesong, as well as set up the @filepath{whalesong} executable.
Any time the source code in @filepath{whalesong} changes, we should repeat
this @tt{raco setup} step again.
At this point, you should be able to rung @filepath{whalesong} from the command line.
@verbatim|{
$ ./whalesong
Expected one of the following: [build, get-runtime, get-javascript].
}|
and if this does appear, then Whalesong should be installed successfully.
@subsection{Running Whalesong}
Let's try making a simple, standalone executable. At the moment, the
program must be written in the base language of @racket[(planet
dyoo/whalesong)]. This restriction currently prevents arbitrary
racket/base programs from compiling, and the developers will be working
to remove this restriction.
Write a @filepath{hello.rkt} with the following content
@filebox["hello.rkt"]{
@codeblock{
#lang planet dyoo/whalesong
(display "hello world")
(newline)
}}
This program can be executed in Racket,
@verbatim|{
$ racket hello.rkt
hello world
$
}|
and it can also be packaged with @filepath{whalesong}.
@verbatim|{
$ whalesong build hello.rkt
$ ls -l hello.xhtml
-rw-rw-r-- 1 dyoo nogroup 692213 Jun 7 18:00 hello.xhtml
}|
Running @tt{whalesong build} on a Racket program will produce a self-contained
@filepath{.xhtml} file. If you open this file in your favorite web browser,
you should see a triumphant message show on screen.
We can do something slightly more interesting. Let's write a Whalesong program
that accesses the JavaScript DOM. Call this file @filepath{dom-play.rkt}.
@filebox["dom-play.rkt"]{
@codeblock|{
#lang planet dyoo/whalesong
;; Uses the JavaScript FFI, which provides bindings for:
;; $ and call
(require (planet dyoo/whalesong/js))
;; insert-break: -> void
(define (insert-break)
(call ($ "<br/>") "appendTo" body)
(void))
;; write-message: any -> void
(define (write-message msg)
(void (call (call (call ($ "<span/>") "text" msg)
"css" "white-space" "pre")
"appendTo"
body)))
;; Set the background green, and show some content
;; on the browser.
(void (call body "css" "background-color" "lightgreen"))
(void (call ($ "<h1>Hello World</h1>") "appendTo" body))
(write-message "Hello, this is a test!")
(insert-break)
(let loop ([i 0])
(cond
[(= i 10)
(void)]
[else
(write-message "iteration ") (write-message i)
(insert-break)
(loop (add1 i))]))
}|}
This program uses the @link["http:/jquery.com"]{JQuery} API provided by @racketmodname[(planet dyoo/whalesong/js)],
as well as the native JavaScript FFI to produce output on the browser.
If w run Whalesong on this program, and view the resulting @filepath{dom-play.xhtml} in your
web browser, we should see a pale, green page with some output.
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Extended example}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Reference}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(This section should describe the whalesong language.)
@subsection{The @filepath{whalesong} command-line}
(This section should describe the whalesong launcher and the options
we can use.)
(We want to add JavaScript compression here as an option.)
(We also need an example that shows how to use the get-javascript and get-runtime
commands to do something interesting...)
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Internals}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Skip this section if you're a regular user: this is really notes internal
to Whalesong development.
(This section should describe the internal details of the runtime,
including the type map from Racket values to JavaScript values. It
should also describe how to write FFI bindings.)
@subsection{Architecture}
The basic idea is to reuse most of the Racket compiler infrastructure.
We use the underlying Racket compiler to produce bytecode from Racket
source; it also performs macro expansion and module-level
optimizations for us. We parse that bytecode using the
@racketmodname[compiler/zo-parse] collection to get an AST,
compile that to an
intermediate language, and finally assemble JavaScript.
@verbatim|{
AST IL JS
parse-bytecode.rkt ----------> compiler.rkt --------> assembler.rkt ------->
(todo)
}|
The IL is intended to be translated straightforwardly. We currently
have an assembler to JavaScript, as well as a simulator
in @filepath{simulator.rkt}. The simulator allows us to test the compiler in a
controlled environment.
@subsection{parser/parse-bytecode.rkt}
(We try to insulate against changes in the bytecode structure by
using the version-case library to choose a bytecode parser based on
the Racket version number. Add more content here as necessary...)
@subsection{compiler/compiler.rkt}
This translates the AST to the intermediate language. The compiler has its
origins in the register compiler in @link[ "http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-35.html#%_sec_5.5"
]{Structure and Interpretation of
Computer Programs} with some significant modifications.
Since this is a stack machine,
we don't need any of the register-saving infrastructure in the
original compiler. We also need to support slightly different linkage
structures, since we want to support multiple value contexts. We're
trying to generate code that works effectively on a machine like the
one described in \url{http://plt.eecs.northwestern.edu/racket-machine/}.
The intermediate language is defined in @filepath{il-structs.rkt}, and a
simulator for the IL in @filepath{simulator/simulator.rkt}. See
@filepath{tests/test-simulator.rkt} to see the simulator in action, and
@filepath{tests/test-compiler.rkt} to see how the output of the compiler can be fed
into the simulator.
The assumed machine is a stack machine with the following atomic
registers:
@itemlist[
@item{val: value}
@item{proc: procedure}
@item{argcount: number of arguments}
]
and two stack registers:
@itemlist[
@item{env: environment stack}
@item{control: control stack}
]
@subsection{js-assembler/assemble.rkt}
The intent is to potentially support different back end generators
for the IL. @filepath{js-assembler/assemble.rkt} provides a backend
for JavaScript.
The JavaScript assembler plays a few tricks to make things like tail
calls work:
@itemlist[
@item{Each basic block is translated to a function taking a MACHINE
argument.}
@item{ Every GOTO becomes a function call.}
@item{ The head of each basic-blocked function checks to see if we
should trampoline
(http://en.wikipedia.org/wiki/Trampoline_(computers))}
@item{We support a limited form of computed jump by assigning an
attribute to the function corresponding to a return point. See
the code related to the LinkedLabel structure for details.}
]
Otherwise, the assembler is fairly straightforward. It depends on
library functions defined in @filepath{runtime-src/runtime.js}. As soon as the compiler
stabilizes, we will be pulling in the runtime library in Moby Scheme
into this project. We are right in the middle of doing this, so expect
a lot of flux here.
The assembled output distinguishes between Primitives and Closures.
Primitives are only allowed to return single values back, and are not
allowed to do any higher-order procedure calls. Closures, on the
other hand, have full access to the machine, but they are responsible
for calling the continuation and popping off their arguments when
they're finished.
@subsection{Tests}
The test suite in @filepath{tests/test-all.rkt} runs the test suite.
You'll need to
run this on a system with a web browser, as the suite will evaluate
JavaScript and make sure it is producing values. A bridge module
in @filepath{tests/browser-evaluate.rkt} brings up a temporary web server
that allows us
to pass values between Racket and the JavaScript evaluator on the
browser for testing output.
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Incomplete features}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(This section should describe what needs to get done next.)
(I should catalog the bug list in GitHub, as well as the feature list,
so I have a better idea of what's needed to complete the project.)
(We also need a list of the primitives missing that prevent us from
running @racketmodname[racket/base]; it's actually a short list that
I'll be attacking once things stabilize.)
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@section{Acknowledgements}
@;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@;; shriram, kathi, emmanuel, everyone who helped with moby and wescheme
@;;
@;; also need to list out all the external libraries we're using
@;; and the license.
Whalesong uses code and utilities from the following external projects:
@itemlist[
@item{ jshashtable (@url{http://www.timdown.co.uk/jshashtable/})}
@item{ js-numbers (@url{http://github.com/dyoo/js-numbers/})}
@item{ JSON (@url{http://www.json.org/js.html})}
@item{ jquery (@url{http://jquery.com/})}
@item{ Google Closure Compiler (@url{http://code.google.com/p/closure-compiler/})}
]