A tour to Lisp
The Scheme dialect.
At first, it seems that the only difference between lisp and other programing languages is the writing style: in Julia, it is
+(1, 2)
and in Scheme, a dialect of of Lisp,
(+ 1 2)
Other than the placement of brackets, there do not seem much difference. …Until, an exercise in sicp came up:
(define (a-plus-abs-b a b)
((if (> b 0) + -) a b))
Here, the operator +
or -
is determined by some condition.
This clearly shows the benefit of the arrangement of brackets in Lisp:
easily manipulate functions just as any data.
Dive into julia-parser.scm
After the aperitif, let’s move to our real target: understand julia-parser.scm
and modify it.
The do
-related function is,
(define (parse-do s)
(with-bindings
((expect-end-current-line (input-port-line (ts:port s))))
(without-whitespace-newline
(let ((doargs (if (memv (peek-token s) '(#\newline #\;))
'()
(parse-comma-separated s parse-range))))
`(-> (tuple ,@doargs)
,(begin0 (parse-block s)
(expect-end s 'do)))))))
Some functions are used here:
with-bindings
This function comes from the femtoLisp standard library.
(define-macro (with-bindings binds . body)
(let ((vars (map car binds))
(vals (map cadr binds))
(olds (map (lambda (x) (gensym)) binds)))
`(let ,(map list olds vars)
,@(map (lambda (v val) `(set! ,v ,val)) vars vals)
(unwind-protect
(begin ,@body)
(begin ,@(map (lambda (v old) `(set! ,v ,old)) vars olds))))))
To understand it, we have to figure out what
define-macro
- the
`
notation in`(blabla)
- the
,
notation in,(blabla)
and,v
- the
,@
notation in,@(blabla)
are talking about. From documentation
the forms 'datum `datum ,datum, and ,@datum denote two-element lists whose first elements are the symbols quote, quasiquote, unquote, and unquote-splicing, respectively. The second element in each case is datum.
expect-end-current-line
This is a little bit strange. A number?
(define expect-end-current-line 0)
input-port-line
It is a built-in function which receives a port and returns the line number.
Note that port
is the I/O stream for scheme.
ts:port
Note: aref
is the common-lisp name for vector-ref
.
i.e., get the $i$-th element of a vector.
(define-macro (ts:port s) `(aref ,s 1))
without-whitespace-newline
; treat newline like ordinary whitespace instead of as a potential separator
(define whitespace-newline #f)
(define-macro (with-whitespace-newline . body)
`(with-bindings ((whitespace-newline #t))
,@body))
(define-macro (without-whitespace-newline . body)
`(with-bindings ((whitespace-newline #f))
,@body))
memv
memv object list
returns the first pair of list
whose car
is object
.
Returns #f
if not found.
#\newline
Characters are written using the notation #\character
or #\character-name
.
For example: #\newline
is the newline character.
parse-comma-separated
(define (parse-comma-separated s what)
(let loop ((exprs '()))
(let ((r (what s)))
(case (peek-token s)
((#\,)
(take-token s)
(loop (cons r exprs)))
(else (reverse! (cons r exprs)))))))
parse-range
;; parse ranges and postfix ...
;; colon is strange; 3 arguments with 2 colons yields one call:
;; 1:2 => (call : 1 2)
;; 1:2:3 => (call : 1 2 3)
(define (parse-range s)
(let loop ((ex (parse-expr s))
(first? #t))
(let* ((t (peek-token s))
(spc (ts:space? s)))
(cond ((and first? (eq? t '|..|))
(take-token s)
`(call ,t ,ex ,(parse-expr s)))
((and range-colon-enabled (eq? t ':))
(take-token s)
(if (and space-sensitive spc
(or (peek-token s) #t) (not (ts:space? s)))
;; "a :b" in space sensitive mode
(begin (ts:put-back! s ': spc)
ex)
(let ((argument
(cond ((closing-token? (peek-token s))
(error (string "missing last argument in \""
(deparse ex) ":\" range expression ")))
((newline? (peek-token s))
(error "line break in \":\" expression"))
(else
(parse-expr s)))))
(if (and (not (ts:space? s))
(or (eq? argument '<) (eq? argument '>)))
(error (string "\":" argument "\" found instead of \""
argument ":\"")))
(if first?
(loop (list 'call t ex argument) #f)
(loop (append ex (list argument)) #t)))))
((eq? t '...)
(take-token s)
(list '... ex))
(else ex)))))