OSDN Git Service

Add .let* pmacro builtin.
authordevans <devans>
Thu, 13 Aug 2009 17:57:51 +0000 (17:57 +0000)
committerdevans <devans>
Thu, 13 Aug 2009 17:57:51 +0000 (17:57 +0000)
* pmacros.scm (-pmacro-builtin-let*): New function.
(pmacros-init!): Add .let*.
* doc/pmacros.texi: Document .let*.
* testsuite/pmacros-1.test: Test .let*.

cgen/doc/pmacros.texi
cgen/pmacros.scm
cgen/testsuite/pmacros-1.test

index b171a57..079e3d1 100644 (file)
@@ -146,7 +146,7 @@ Some function pmacros are called @samp{syntactic forms}.
 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,
@@ -798,9 +798,9 @@ Macros for working with pmacros.
 @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
 
@@ -847,13 +847,13 @@ Note that @code{(.str (.iota 5))} is an error.  Here the list
 @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:
 
@@ -918,17 +918,29 @@ 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:
 
@@ -957,6 +969,17 @@ 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
index de8292c..506da15 100644 (file)
@@ -67,6 +67,7 @@
 ; (.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")
index 7b34f90..b31eaa5 100644 (file)
@@ -103,6 +103,16 @@ cat > ${cpu_file} <<EOF
 (test-name ".let")
 (print-match "xyzzy")
 (print-expr (.let ((x xyzzy)) x))
+;; FIXME: This is the currently defined behaviour, but it's somewhat
+;; unintuitive.
+;; pmacro expansion re-evaluates the result if it's also a pmacro,
+;; so x -> y -> x and y -> x -> y.
+(print-match "(x y)")
+(print-expr (.let ((x y) (y x)) (.list x y)))
+
+(test-name ".let*")
+(print-match "(1 2)")
+(print-expr (.let* ((x 1) (y (.add x 1))) (.list x y)))
 
 (test-name ".if")
 (print-match "then")