These pmacros are processed differently in that parameters are
@emph{not} evaluated first. Instead it is up to the pmacro
to decide when, and if, the parameters are evaluated.
-The syntactic forms are: @code{.pmacro}, @code{.let}, @code{.if},
+The syntactic forms are: @code{.pmacro}, @code{.let}, @code{.let*}, @code{.if},
@code{.case}, @code{.cond}, @code{.begin}, @code{.andif}, and @code{.orif}.
If the result of the pmacro is another pmacro invocation,
@menu
* Immediate evaluation of a macro:: The @code{.eval} builtin
* Applying a macro to a list:: The @code{.apply} builtin
-* Defining a macro inline:: The @code{.pmacro} builtin
+* Defining a pmacro inline:: The @code{.pmacro} builtin
* Passing macros as arguments:: Passing a macro to another macro
-* Defining a block of locals:: The @code{.let} builtin
+* Defining a block of locals:: The @code{.let}, @code{.let*} builtins
* A block of statements:: The @code{.begin} builtin
@end menu
@samp{(0 1 2 3 4)} is passed as the first argument of @code{.str},
which is wrong.
-@node Defining a macro inline
-@subsection Defining a macro inline
+@node Defining a pmacro inline
+@subsection Defining a pmacro inline
@cindex .pmacro
Define a macro inline with @code{.pmacro}.
This is only supported when passing macros as arguments to other macros,
-and as values for local variables in @code{.let}.
+and as values for local variables in @code{.let} or @code{.let*}.
Example:
@node Defining a block of locals
@subsection Defining a block of locals
@cindex .let
+@cindex .let*
It is often handy to assign expressions to local variables,
if only to improve readability.
-This is accomplished with the @code{.let} builtin pmacro.
+This is accomplished with the @code{.let} and @code{.let*} builtin pmacros.
-Note that one can also assign pmacros to local variables.
+@code{.let} and @code{.let*} have the same syntax:
+
+ @samp{(.let local-list expr1 [expr2 ...])}
-Syntax: @samp{(.let local-list expr1 [expr2 ...])}
+ @samp{(.let* local-list expr1 [expr2 ...])}
where @samp{local-list} is a list of local variable assignments,
-with the syntax @samp{(name expr)}.
+with the syntax @samp{(name expr)}. All variable names must be distinct.
+
+The difference is that in @code{.let} all expressions of all locals
+are evaluated @emph{first} and @emph{then} assigned to the locals,
+whereas in @code{.let*} each local is evaluated and assigned in turn.
+This means that expressions in @code{.let} cannot refer to other locals
+in the @code{.let}. If they do, they will get the values the variables had
+before the @code{.let}. Also remember that symbols in pmacros are
+``self-quoting'', so if a symbol isn't bound to any value, its value is
+the symbol name.
Example:
(load-op uh OP2_11 HI zext-expr)
@end smallexample
+Note that one can also assign pmacros to local variables.
+
+Note that @code{.let*} is equivalent to a set of nested @code{.let}
+expressions:
+
+@smallexample
+(.let* ((x 1) (y x)) y)
+;; is equivalent to
+(.let ((x 1)) (.let ((y x)) y))
+@end smallexample
+
@node A block of statements
@subsection A block of statements
@cindex .begin
; (.apply pmacro-name arg)
; (.pmacro (arg-list) expansion) - akin go lambda in Scheme
; (.let (var-list) expr1 . expr-rest) - akin to let in Scheme
+; (.let* (var-list) expr1 . expr-rest) - akin to let* in Scheme
; (.if expr then [else])
; (.case expr ((case-list1) stmt) [case-expr-stmt-list] [(else stmt)])
; (.cond (expr stmt) [(cond-expr-stmt-list)] [(else stmt)])
; .sym and .str convert numbers to symbols/strings as necessary (base 10).
;
; .pmacro is for constructing pmacros on-the-fly, like lambda, and is currently
-; only valid as arguments to other pmacros or assigned to a local in a {.let}.
+; only valid as arguments to other pmacros or assigned to a local in a {.let}
+; or {.let*}.
+;
+; NOTE: While Scheme requires tail recursion to be implemented as a loop,
+; we do not. We might some day, but not today.
;
; ??? Methinks .foo isn't a valid R5RS symbol. May need to change
; to something else.
(define (-pmacro-builtin-let loc env locals expr1 . expr-rest)
(if (not (list? locals))
- (-pmacro-error ".let locals is not a list" locals))
+ (-pmacro-error "locals is not a list" locals))
(if (not (all-true? (map (lambda (l)
(and (list? l)
(= (length l) 2)
(-pmacro-expand-expr-list (cons expr1 expr-rest) new-env loc))
)
+; (.let* (var-list) expr1 . expr-rest)
+; NOTE: syntactic form
+
+(define (-pmacro-builtin-let* loc env locals expr1 . expr-rest)
+ (if (not (list? locals))
+ (-pmacro-error "locals is not a list" locals))
+ (if (not (all-true? (map (lambda (l)
+ (and (list? l)
+ (= (length l) 2)
+ (symbol? (car l))))
+ locals)))
+ (-pmacro-error "syntax error in locals list" locals))
+ (let loop ((locals locals) (new-env env))
+ (if (null? locals)
+ (-pmacro-expand-expr-list (cons expr1 expr-rest) new-env loc)
+ (loop (cdr locals) (acons (caar locals)
+ (-pmacro-expand (cadar locals) new-env loc)
+ new-env))))
+)
+
; (.if expr then [else])
; NOTE: syntactic form
(list '.eval '(expr) #f -pmacro-builtin-eval "process expr immediately")
(list '.apply '(pmacro arg-list) #f -pmacro-builtin-apply "apply a pmacro to a list of arguments")
(list '.pmacro '(params expansion) #t -pmacro-builtin-pmacro "create a pmacro on-the-fly")
- (list '.let '(locals expr1 . rest) #t -pmacro-builtin-let "create a binding context")
+ (list '.let '(locals expr1 . rest) #t -pmacro-builtin-let "create a binding context, let-style")
+ (list '.let* '(locals expr1 . rest) #t -pmacro-builtin-let* "create a binding context, let*-style")
(list '.if '(expr then . else) #t -pmacro-builtin-if "if expr is true, process then, else else")
(list '.case '(expr case1 . rest) #t -pmacro-builtin-case "process statement that matches expr")
(list '.cond '(expr1 . rest) #t -pmacro-builtin-cond "process first statement whose expr succeeds")