diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2015-05-25 22:14:18 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2015-05-25 22:14:18 -0700 |
commit | b0906871e8a6bbd2f5b4c25eca511ac1827a30d5 (patch) | |
tree | 05a4896092a797e0c3d5bb0ca6244fe1695fec44 | |
parent | 535999c69576b3e57e35f1936bac852c6aedc983 (diff) | |
download | txr-b0906871e8a6bbd2f5b4c25eca511ac1827a30d5.tar.gz txr-b0906871e8a6bbd2f5b4c25eca511ac1827a30d5.tar.bz2 txr-b0906871e8a6bbd2f5b4c25eca511ac1827a30d5.zip |
* txr.1: Document new syntactic places framework and place-mutating macros.
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | txr.1 | 2601 |
2 files changed, 2320 insertions, 285 deletions
@@ -1,5 +1,9 @@ 2015-05-25 Kaz Kylheku <kaz@kylheku.com> + * txr.1: Document new syntactic places framework and place-mutating macros. + +2015-05-25 Kaz Kylheku <kaz@kylheku.com> + Fix mismanaged dyn_env variable. * eval.c (bindings_helper): In the parallel binding case, only allocate @@ -9750,6 +9750,353 @@ form doesn't specify a value, and the variable doesn't exist, it is created with a value of .codn nil . +.SS* Syntactic Places and Accessors + +The \*(TL feature known as +.meta syntactic places +allows programs to use +the syntax of a form which is used to +.I access +a value from an environment or +object, as an expression which denotes a +.I place +where a value may be +.I stored. + +They are almost exactly the +same concept as "generalized references" in Common Lisp, and are related to +"lvalues" in languages in the C family, or "designators" in Pascal. + +.NP* Symbolic Places + +A symbol is a is a syntactic place if it names a variable. If +.code a +is a variable, then it may be assigned using the +.code set +operator: the form +.code (set a 42) +causes +.code a +to have the integer value 42. + +.NP* Compound Places + +A compound expression can be a syntactic place, if its leftmost constituent is +as symbol which is specially registered, and if the form has the correct syntax +for hat kind of place, and suitable semantics. Such an expression is a compound +place. + +An example of a compound place is a +.code car +form. If +.code c +is an expression denoting a +.code cons +cell, then +.code (car c) +is not only an expression which retrieves the value of the +.code car +field of the cell. It is also a syntactic place which denotes that field as +a storage location. Consequently, the expression +.cblk +(set (car c) "abc") +.cble +stores the character string +.str "abc" +in that location. Although the same effect can be obtained with +.cblk +(rplaca c "abc") +.cble +the syntactic place frees the programmer from having to remember +different update functions for different kinds of places. +There are +various other advantages. \*(TL provides a plethora of operators +for modifying a place in addition to +.codn set . +Subject to certain usage restrictions, these operators work uniformly on all +places. For instance, the expression +.code (rotate (car x) [str 3] y) +causes three different kinds of places to exchange contents, +while the expressions +.codn x , +.codn str , +and +.code y +are accessed only once. New kinds of place update macros like +.code rotate +are quite easily defined, as are new kinds of compound places. + +.NP* Accessor Functions + +When a function call form such as the above +.code (car x) +is a syntactic place, then the function is called an +.IR accessor . +This term is used throughout this document to denote functions +which have associated syntactic places. + +.NP* Macro Call Syntactic Places + +Syntactic places can be macros (global and lexical), including symbol macros. +So for instance in +.code (set x 42) +the +.code x +place can actually be a symbolic macro which expands to, say, +.codn (cdr y) . +And so then, the assignment is effectively +.codn (set (cdr y) 42) . + +.NP* User-Defined Syntactic Places and Place Operators + +Syntactic places, as well as operators upon syntactic places, +are both open-ended. Code can be written quite easily in \*(TL to introduce +new kinds of places, as well as new place-mutating operators. +New places can be introduced with the help of the +.code defplace +macro. New place update macros (place operators) are written using the +ordinary macro definer +.code defmacro +but with the help of special utility macros called +.codn with-update-expander , +.codn with-clobber-expander , +and +.codn with-delete-expander . +Simple update macros similar to +.code inc +and +.code push +can be written compactly using +.codn define-modify-macro . + +.NP* Deletable Places + +Unlike generalized references in Common Lisp, \*(TL syntactic +places support the concept of deletion. Some kinds of places (not all) +can be deleted, which is an action distinct from (but does not preclude) being +overwritten with a value. What exactly it means for a place to be deleted, +or whether that is even permitted, depends on the kind of place. +For instance a place which denotes a variable may not be deleted. +A place which denotes a hash table entry may be deleted, and results in the +entry being removed from the hash table. Deleting a place in a list +causes the trailing items, if any, or else the terminating atom, to +move in to close the gap. Users may, of course, define new kinds of places +which support deletion semantics. + +.NP* Evaluation of Places + +To bring about their effect, place operators must evaluate one or +more places. Moreover, some of them evaluate additional forms which are not +places. Which the arguments of a place operator form are places and which are +ordinary forms depends on its specific syntax. For all the built-in place +operators, the position of an argument in the syntax determines whether it is +treated as (and consequently required to be) a syntactic place, or whether it is +an ordinary form. + +All built-in place operators perform the evaluation of place and non-place +argument forms in strict left to right order. + +Place forms are evaluated not in order to compute a value, but in order to +determine the storage location. In addition to determining a storage location, +the evaluation of a place form may possibly give rise to side effects. +Once a place is fully evaluated, the storage location can then be accessed. +Access to the storage location is not considered part of the evaluation of a +place. To determine a storage location means to compute some hidden referential +object which provides subsequent access to that location without the need for a +re-evaluation of the original place form. (The subsequent access to the +place through this referential object may still require a multi-step traversal +of a data structure; minimizing such steps is a matter of optimization.) + +Place forms may themselves be compounds, which contain subexpressions that must +be evaluated. All such evaluation for the built-in places takes place in left +to right order. + +Certain place operators, such as +.code shift +and +.codn rotate , +exhibit an unspecified behavior with regard to the timing of the access +of the prior value of a place, relative to the evaluation of places +which occur later in the same place operator form. Access to the prior values +may be delayed until the entire form is evaluated, or it may be interleaved +into the evaluation of the form. For example, in the form +.codn (shift a b c 1) , +the prior value of +.code a +can be accessed and saved as soon as +.code a +is evaluated, prior to the evaluation of +.codn b . +Alternatively, +.code a +may be accessed and saved later, after the evaluation of +.code b +or after the evaluation of all the forms. This issue affects the behavior of +place-modifying forms whose subforms contain side effects. It is recommended +that such forms not be included in programs. + +.NP* Nested Places + +Certain place forms are required to have one or more arguments which +are themselves places. The prime example of this, and the only example from +among built-in syntactic places, are DWIM forms. A DWIM form has the syntax + +.cblk +.mets (dwim < obj-place < index <> [ alt ]) +.cble + +and of course the square-bracket-notation equivalent: + +.cblk +.mets >> [ obj-place < index <> [ alt ]] +.cble + +Note that not only is the entire form a place, denoting some element or element +range of +.metn obj-place , +but there is the added constraint that +.meta obj-place +must also itself be a syntactic place. + +This requirement is necessary, because it supports the behavior that +when the element or element range is updated, then +.meta obj-place +is also potentially updated. + +After the assignment +.cblk +(set [obj 0..3] '("forty" "two")) +.cble +not only is the range of places denoted by +.code [obj 0..3] +replaced by the list of strings +.cblk +("forty" "two") +.cble +but +.code obj +may also be overwritten with a new value. + +This behavior is necessary because the DWIM brackets notation maintains +the illusion of an encapsulated array-like container over several dis-similar +types, including Lisp lists. But Lisp lists do not behave as fully +encapsulated containers. Some mutations on Lisp lists return new objects, +which then have to stored (or otherwise accepted) in place of the original +objects in order to maintain the array-like illusion. + +Of course, this special double processing extends to an +arbitrary nesting depth. For instance, consider an assignment +such as +.codn (set [[obj 'a] 0] 42) . +Here, three places are affected by the operation: namely, +.codn obj , +.cblk +[obj 'a] +.cble +and +.codn [[obj 'a] 0] . + +.NP* Built-In Syntactic Places + +The following is a summary of the built-in place forms, in addition to symbolic +places denoting variables. Of course, new syntactic place forms can be +defined by \*(TX programs. + +.cblk +.mets (car << object ) +.mets (first << object ) +.mets (cdr << object ) +.mets (rest << object ) +.mets (vecref < vec << idx ) +.mets (chr-str < str << idx ) +.mets (gethash < hash < key <> [ alt ]) +.mets (dwim < obj-place < index <> [ alt ]) +.mets >> [ obj-place < index <> [ alt ]] ;; equivalent to dwim +.mets (symbol-value << symbol ) +.mets (symbol-function << symbol ) +.mets (fun << function-name ) +.mets (force << promise ) +.mets (errno) +.cble + +.NP* Built-In Place-Mutating Operators + +The following is a summery of the built-in place mutating macros. +They are described in detail in their own sections. + +.meIP (set >> { place << new-value }*) +Assigns the values of expressions to places, performing assignments in left to right order, +returning the value assigned to the rightmost place. + +.meIP (pset >> { place << new-value }*) +Assigns the values of expressions to places, performing the determination of +places and evaluation of the expressions left to right, but the assignment +in parallel. Returns the value assigned to the rightmost place. + +.meIP (zap < place <> [ new-value ]) +Assigns +.meta new-value +to place, defaulting to +.codn nil , +and returns the prior value. + +.meIP (flip << place ) +Logically toggles the boolean value of +.metn place , +and returns the new value. + +.meIP (inc < place <> [ delta ]) +Increments +.meta place +by +.metn delta , +which defaults to 1, and returns the new value. + +.meIP (dec < place <> [ delta ]) +Decrements +.meta place +by +.metn delta , +which defaults to 1, and returns the new value. + +.meIP (swap < left-place << right-place ) +Exchanges the values of +.meta left-place +and +.metn right-place . + +.meIP (push < item << place ) +Pushes +.meta item +into the list stored in +.code place +and returns +.codn item . + +.meIP (pop << place ) +Pop the list stored in +.meta place +and returns the popped value. + +.meIP (shift << place + << shift-in-value) +Treats one or more places as a "multi-place shift register". +Values are shifted to the left among the places. The +rightmost place receives +.metn shift-in-value , +and the value of the leftmost place emerges as the return value. + +.meIP (rotate << place *) +Treats zero or more places as a "multi-place rotate register". +The places exchange values among themselves, by a rotation +by one place to the left. The value of the leftmost place +goes to the rightmost place, and that value is returned. + +.meIP (del << place ) +Deletes a place which supports deletion, and returns +the value which existed in that place prior to deletion. + +.PP + .SH* TXR LISP OPERATOR AND FUNCTION LIBRARY A compound expression with a symbol as its first element, if intended to be evaluated, denotes either an operator invocation or a function @@ -10609,6 +10956,8 @@ and so the second pprint form is not evaluated. .synb .mets (dwim << argument *) .mets <> [ argument *] +.mets (set (dwim < obj-place < index <> [ alt ]) << new-value ) +.mets (set >> [ obj-place < index <> [ alt ]] << new-value ) .syne .desc The @@ -10705,7 +11054,7 @@ object to which the first argument expression evaluates. The possibilities are: .RS .meIP >> [ function << argument *] -Call the given function object to the given arguments. +Call the given function object with the given arguments. .meIP >> [ symbol << argument *] If the first expression evaluates to a symbol, that symbol @@ -10716,21 +11065,18 @@ given arguments. .meIP >> [ sequence << index ] Retrieve an element from .metn sequence , -given by the integer -.metn index . - -The following equivalences hold: - -.cblk - [seq index] <--> (ref seq index) +at the specified +.metn index , +which is a nonnegative integer. - (set [seq index] new) <--> (refset seq index new) -.cble +This form is also a place if the +.meta sequence +subform is a place. If a value is stored to this place, it replaces the +element. -This form accepts update operators like -.code inc -and -.codn flip . +The place may also be deleted, which has the effect of removing the element +from the sequence, shifting the elements at higher indices, if any, down one +element position, and shortening the sequence by one. .meIP >> [ sequence << from-index..to-below-index ] Retrieve the specified range of elements. @@ -10744,40 +11090,54 @@ for which the (dotdot) syntactic sugar is useful. See the section on Range Indexing below. -The following equivalences hold: - -.cblk - [seq from..to] <--> (sub seq from to) - - (set [seq from..to] new) <--> (refset seq new from to) -.cble - -This form does not accept update operators like -.code inc -and -.codn flip . +This form is also a syntactic place, if the +.meta sequence +subform is a place. Storing a value in this place +has the effect of replacing the subsequence with +a new subsequence. Deleting the place has the +effect of removing the specified subsequence +from +.metn sequence . +The +.meta new-value +argument in a range assignment can be a string, vector or list, +regardless of whether the target is a string, vector or list. +If the target is a string, the replacement sequence must be +a string, or a list or vector of characters. .meIP >> [ sequence << index-list ] Elements specified by -.meta index-list +.metn index-list , +which may be a list or vector, are extracted from .meta sequence and returned as a sequence of the same kind as .metn sequence . + This form is equivalent to .cblk .meti (select < sequence << where-index ) .cble except when the target of an assignment operation. +This form is a syntactic place if +.meta sequence +is one. If a sequence is assigned to this place, +then elements of the sequence are distributed to the +specified locations. + The following equivalences hold between index-list-based indexing and the .code select and .code replace -functions: +functions, except that +.code set +always returns the value assigned, whereas +.code replace +returns its first argument: .cblk [seq idx-list] <--> (select seq idx-list) @@ -10785,11 +11145,6 @@ functions: (set [seq idx-list] new) <--> (replace seq new idx-list) .cble -This form does not accept update operators like -.code inc -and -.codn flip . - Note that unlike the select function, this does not support .cblk .meti >> [ hash << index-list ] @@ -10799,46 +11154,28 @@ indistinguishable from a simple hash lookup where .meta index-list is the key. -.meIP >> [ hash < key << default-value ] +.meIP >> [ hash < key <> [ alt ]] Retrieve a value from the hash table corresponding to .metn key , -or -.meta default-value -if there is no such entry. - -The first argument may not be an operator such as -.codn let , -only a function. - -The places denoted by the -.code dwim -operator can be assigned. There are some -restrictions. List, string and vector ranges can only be replaced using -the set operator. The other operators like -.code push -do not apply. -Characters in a string can only be assigned with -.code set -or incremented with -.code inc -and dec. - -The source of a range assignment can be a string, vector or list, -regardless of whether the target is a string, vector or list. -If the target is a string, the replacement sequence must be -a string, or a list or vector of characters. +or else return +.meta alt +if there is no such entry. The expression +.meta alt +is always evaluated, whether or not its value is used. .RE .PP -.TP* "Range Indexing" -Vector and list range indexing is based from zero. The first element element -zero. Furthermore, the value +.TP* "Range Indexing:" +Vector and list range indexing is based from zero, meaning +that the first element is numbered zero, the second one +and so on. +zero. Negative values are allowed; the value .code -1 refers to the last element of the vector or list, and .code -2 -to the second last and so forth. So the range +to the second last and so forth. Thus the range .code 1 .. -2 means "everything except for the first element and the last two". @@ -10925,31 +11262,43 @@ and the .code dwim/[...] syntax does exactly this. -However, Lisp-2 also has its advantages, whereas Lisp-1 also has disadvantages. -Lisp-2 integrates with macros and operators more naturally than Lisp-1. Macros -come in two flavors: symbol macros and operator macros. This distinction -integrates better into a two namespace model, in which an operator macro is -recognized in the first position of a form, and a symbol macro in other -positions. Moreover, operators and operator macros are not functions; they -cannot work by reducing a symbol to a macro function under ordinary -evaluation rules. A Lisp-1 implementation must inspect the leftmost expression -to determine whether or not it is an operator, which breaks the principle -that all expressions are to be evaluated in the same way. Moreover, macros -and operators are never indirected upon. If -.code x -is an operator or macro, it never makes sense for -.code x -to occur other than in the leftmost position of a form. In Lisp-1 dialects, -the situation of using the name of an operator as a variable other than -in the first position, as in -.code (list let if) -has no meaning, indicating that the single namespace model serves no purpose -for macros and operators. - -\*(TL banishes operators and macros (except symbol macros) from Lisp-1 -evaluation, thereby achieving a more pure model of Lisp-1 evaluation than -actual Lisp-1 languages, while retaining all the advantages of Lisp-2. -In short, a "best of both worlds" situation is achieved in \*(TL. +\*(TL is a Lisp-2 because Lisp-2 also has advantages. Lisp-2 programs +which use macros naturally achieve hygiene because lexical variables do +not interfere with the function namespace. If a Lisp-2 program has +a local variable called +.codn list , +this does not interfere with the hidden use of the function +.code list +in a macro expansion in the same block of code. Lisp-1 dialects have to +provide hygienic macro systems to attack this problem. Furthermore, even when +not using macros, Lisp-1 programmers have to avoid using the names of functions +as lexical variable names, if the enclosing code might use them. + +The two namespaces of a Lisp-2 also naturally accommodate symbol macros and +operator macros. Whereas functions and variables can be represented in a +single namespace readily, because functions are data objects, this is not so +with symbol macros and operator macros, the latter of which are distinguished +syntactically by their position in a form. In a Lisp-1 dialect, given +.codn (foo bar) , +either of the two symbols could be a symbol macro, but only +.code foo +can possibly be an operator macro. Yet, having only a single namespace, a +Lisp-1 doesn't permit +.codn (foo foo) , +where +.code foo +is simultaneously a symbol macro and an operator macro, though the situation is +unambiguous by syntax even in Lisp-1. In other words, Lisp-1 dialects do not +entirely remove the special syntactic recognition given to the leftmost +position of a compound form, yet at the same time they prohibit +the user from taking full advantage of it by providing only one namespace. + +\*(TL provides +the "best of both worlds": the DWIM brackets notation provides a model of +Lisp-1 computation that is purer than Lisp-1 dialects (since the leftmost +argument is not given any special syntactic treatment at all) +while the Lisp-2 foundation provides a traditional Lisp environment with its +"natural hygiene". .coNP Function @ identity .synb @@ -11075,191 +11424,396 @@ In the future, will be able to recognize more constant forms, such as calls to certain functions whose arguments are constant forms. -.SS* Mutation - -.coNP Operators @, inc @, dec @, set @, push @, pop @, flip @, zap and @ del +.SS* Mutation of Syntactic Places +.coNP Macro @ set .synb -.mets (inc < place <> [ delta ]) -.mets (dec < place <> [ delta ]) -.mets (set < place << new-value ) -.mets (push < item << place ) -.mets (pop << place ) -.mets (flip << place ) -.mets (zap < place <> [ new-value ]) -.mets (del << place ) -.syne +.mets (set >> { place << new-value }*) .desc -These destructive operators update the value of a place. A place is a storage -location which is denoted by a form. Place forms are identical to value -accessing forms. That is to say, any form recognized as a place by these -operators can be evaluated by itself to retrieve the value of the storage -location. However, the converse is false: not all forms which access storage -location are recognized as places. - -With are exceptions noted below, it is an error if a place does not exist. -For instance, a variable being assigned must exist. +The +.code set operator stores the values of expressions in places. It must +be given an even number of arguments. -Literal objects which are directly specified in the source code are -considered part of the program body. Modifying parts of these objects -therefore gives rise to self-modifying code. The behavior of self-modifying -code is not specified. +If there are no arguments, then +.code set +does nothing and returns +.codn nil . -The -.code inc +If there are two arguments, +.meta place and -.code dec -update the place by adding or subtracting, respectively, a -displacement to or from that number. If the -.meta delta -expression is -specified, then it is evaluated and its value is used as the increment. -Otherwise, a default increment of -.code 1 -is used. The prior value of the place -and the delta must be suitable operands for the -.code + -and -.code - -functions. -.code (inc x) +.metn new-value , +then +.meta place is evaluated to determine its storage location, +then +.meta new-value +is evaluated to determine the value to be stored there, +and then the value is stored in that location. Finally, +the value is also returned as the result value. + +If there are more than two arguments, then +.code +set performs multiple assignments in left to right order. +Effectively, +.code (set v1 e1 v2 e2 ... vn en) +is precisely equivalent to +.codn (progn (set v1 e1) (set v2 e2) ... (set vn en)) . + +.coNP Macro @ pset +.synb +.mets (pset >> { place << new-value }*) +.syne +.desc +The syntax of +.code pset +is similar to that of +.codn set , +and the semantics is similar also in that zero or more places are +assigned zero or more values. In fact, if there are no arguments, or +if there is exactly one pair of arguments, +.code pset is equivalent to -.codn (set x (+ 1 x)) , -except that expression x -is evaluated only once to determine the storage location. The -.code inc -and -.code dec -operators return the new value that was stored. +.codn set . -The -.code set -operator overwrites the previous value of a place with a new value, -and also returns that value. +If there are two or more argument pairs, then all of the arguments +are evaluated first, in left-to-right order. No store takes place +until after every +.meta place +is determined, and every +.meta new-value +is calculated. During the calculation, the values to be stored +are retained in hidden, temporary locations. Finally, these values +are moved into the determined places. The rightmost value is returned +as the form's value. -The -.code push +The assignments thus appear to take place in parallel, and +.code pset +is capable of exchanging the values of a pair of places, or rotating +the values among three or more places. (However, there are more convenient +operators for this, namely +.code rotate and -.code pop -operators operate on a place which holds a list. The -.code push -operator updates the list by replacing it with a new list which has a new item -at the front, followed by the previous list. The item is returned. -The -.code pop -operator performs the reverse operation: it removes the first item -from the list and returns it. -.code (push y x) -is similar to +.codn swap ). +.TP* Example: .cblk - (let ((temp y)) (set x (cons temp x)) temp) + ;; exchange x and y + (pset x y y x) + + ;; exchange elements 0 and 1; and 2 and 3 of vector v: + (let ((v (vec 0 10 20 30)) + (i -1)) + (pset [vec (inc i)] [vec (inc i)] + [vec (inc i)] [vec (inc i)]) + vec) + -> #(10 0 30 20) .cble -except that -.code x -is evaluated only once to determine the storage place, and no -such temporary variable is visible to the program. Similarly, -.code (pop x) -is much like +.coNP Macro @ zap +.synb +.mets (zap < place <> [ new-value ]) +.syne +.desc +The +.code zap +macro assigns +.meta new-value +to +.meta place +and returns the previous value of +.metn place . -.cblk - (let ((temp (car x))) (set x (cdr x)) temp) -.cble +If +.meta new-value +is missing, then +.code nil +is used. + +In more detail, first +.code place +is evaluated to determine the storage location. +Then, the location is accessed to retrieve the +previous value. Then, the +.code new-value +expression is evaluated, and that value is +placed into the storage location. +Finally, the previously retrieved value is returned. -except that x is evaluated only once, and no such temporary variable -is visible to the program. +.coNP Macro @ flip +.synb +.mets (flip << place ) +.syne +.desc The .code flip -operator toggles a place between true and false. If the place -contains a value other than +macro toggles the boolean value stored in +.metn place . + +If +.meta place +previously held .codn nil , -then its value is replaced with -.codn nil . -If it contains +it is set to +.codn t , +and if it previously held a value other than .codn nil , -it is replaced with -.codn t . +it is set to +.codn nil . +.coNP Macros @ inc and @ dec +.synb +.mets (inc < place <> [ delta ]) +.mets (dec < place <> [ delta ]) +.syne +.desc The -.code zap -operator is similar to -.code set -except that -.meta new-value -is optional, defaulting to -.codn nil , -and the operator returns the previous value of the place. -This operator is useful in avoiding spurious retention of memory in the -face of garbage collection. The idiom -.code (function (zap variable)) -passes the value of -.code variable -to -.code function -while at the same time clearing it to -.codn nil . This means that the caller no longer has a reference to the -contents of the variable as far as the garbage collector is concerned. -This approach is essential in some uses of infinite, lazy objects. +.code inc +macro increments +.meta place +by adding +.meta delta +to its value. +If +.meta delta +is missing, the value used in its place the integer 1. + +First the +.meta place +argument is evaluated as a syntactic place to determine the location. +Then, the value currently stored in that location is retrieved. +Next, the +.meta delta +expression is evaluated. Its value is added to the previously retrieved +value as if by the +.code + +function. The resulting value is stored in the place, and returned. +The macro +.code dec +works exactly like +.code inc +except that addition is replaced by subtraction. The similarly defaulted +.meta delta +value is subtracted from the previous value of the place. + +.coNP Macro @ swap +.synb +.mets (swap < left-place << right-place ) +.syne +.desc The -.code del -operator does not modify the value of a place, but rather deletes the -place itself. Index values and ranges of lists denoted using the -.code dwim -operator -indexing notation can be subject to a deletion, as can hash table entries -denoted using -.code dwim -or -.codn gethash . -It is an error to try to delete other kinds of -places such as simple variables. The -.code del -operator returns the value of the -place that was deleted. Deleting from a sequence means removing the element or -elements. Deleting a hash place means removing the corresponding entry from the -hash table. +.code swap +macro exchanges the values of +.meta left-place +and +.meta right-place +and returns the value which is thereby transferred to +.metn right-place . -Currently, these forms are recognized as places: +First, +.meta left-place +and +.meta right-place +are evaluated, in that order, to determine their locations. +Then the prior values are retrieved, exchanged and stored back. +The value stored in +.meta right-place +is also returned. -.meIP < symbol -A .meta symbol place denotes a variable. If the variable does not exist, it is -an error. +.coNP Macro @ push +.synb +.mets (push < item << place ) +.syne +.desc +The +.code push +macro places +.meta item +at the head of the list stored in +.meta place +and returns the updated list which is stored back in +.metn place . + +First, the expression +.meta item +is evaluated to produce the push value. +Then, +.meta place +is evaluated to determine its storage location. +Next, the storage location is accessed to retrieve the +list value which is stored there. A new object is +produced as if by invoking +.code cons +function on the push value and list value. +This object is stored into the location, +and returned. -.meIP (car << cons ) -.meIP (cdr << cons ) -These places denote the corresponding slots -of a cons cell. The -.meta cons -form must be an expression which evaluates to a cons. +.coNP Macro @ pop +.synb +.mets (pop << place ) +.syne +The +.code pop +macro removes an element from the list stored in +.meta place +and returns it. -.meIP (gethash < hash < key << default-value ) +First, +.meta place +is evaluated to determine the place. The place is accessed to +retrieve the original value. Then a new value is calculated, +as if by applying the +.code cdr +function to the old value. This new value is stored. +Finally, a return value is calculated and returned, as if by applying the +.code car +function to the original value. + +.coNP Macro @ pushnew +.synb +.mets (pushnew < item < place >> [ testfun <> [ keyfun ]]) +.syne +.desc The -.code gethash -place denotes a value stored in a hash table. -The form -.meta hash -must evaluate to a hash table. If the place does not exist -in the hash table under the given key, then the destructive operation -will create it. In that case, the -.meta default-value -form is evaluated to -determine the initial value of the place. Otherwise it is ignored. +.code pushnew +macro inspects the list stored in +.metn place . +If the list already contains the item, then +it returns the list. Otherwise it creates a new list +with the item at the front and stores it back +into +.metn place , +and returns it. + +First, the expression +.meta item +is evaluated to produce the push value. +Then, +.meta place +is evaluated to determine its storage location. +Next, the storage location is accessed to retrieve the +list value which is stored there. The list is +inspected to check whether it already contains the push +value, as if using the +.code member +function. If that is the case, the list +is returned and the operation finishes. +Otherwise, a new object is +produced as if by invoking +.code cons +function on the push value and list value. +This object is stored into the location +and returned. -.meIP (vecref < vector << index ) +.coNP Macro @ shift +.synb +.mets (shift << place + << shift-in-value) +.syne +.desc The -.code vecref -place denotes a vector element, allowing vector elements -to be treated as assignment places. +.code shift +macro treats one or more places as a "multi-place shift register". +The values of the places are shifted one place to the left. +The first (leftmost) place receives the value of the second place, +the second receives that of the third, and so on. +The last (rightmost) place receives +.meta shift-in-value +(which is not treated as a place, even if it is a syntactic place form). +The previous value of the first place is returned. + +More precisely, all of the argument forms are evaluated left to right, in the +process of which the storage locations of the places are determined, +.meta shift-in-value +is reduced to its value. + +The values stored in the places are sampled and saved. + +Note that it is not specified whether the places are sampled in a separate +pass after the evaluation of the argument forms, or whether the +sampling is interleaved into the argument evaluation. This affects +the behavior in situations in which the evaluation of any of the +.meta place +forms, or of +.metn shift-in-value , +has the side effect of modifying later places. + +Next, the places are updated by storing the saved value of the second +place into the first place, the third place into the second and so forth, +and the value of +.meta shift-in-value +into the last place. + +Finally, the saved original value of the first place is returned. + +.coNP Macro @ rotate +.synb +.mets (rotate << place *) +.syne +.desc +Treats zero or more places as a "multi-place rotate register". +If there are no arguments, there is no effect and +.code nil +is returned. Otherwise, the last (rightmost) place receives +the value of the first (leftmost) place. The leftmost place +receives the value of the second place, and so on. +If there are two arguments, this equivalent to +.codn swap . +The prior value of the first place, which is the the value +rotated into the last place, is returned. + +More precisely, the +.meta place +arguments are evaluated left to right, +and the storage locations are thereby determined. The storage +locations are sampled, and then the sampled values are +stored back into the locations, but rotated by one place +as described above. The saved original value of the leftmost +.meta place +is returned. + +It is not specified whether the sampling of the original values +is a separate pass which takes place after the arguments +are evaluated, or whether this sampling it is interleaved into argument +evaluation. This affects +the behavior in situations in which the evaluation of any of the +.meta place +forms has the side effect of modifying the value stored in +a later +.meta place +form. -.meIP (dwim < obj ...) -.meIP >> [ obj ...] ;; equivalent +.coNP Macro @ del +.synb +.mets (del << place ) +.syne +.desc The -.code dwim/[] -place denotes a vector element, list element, string, or hash -table, depending on the type of -.metn obj . +.code del +macro requests the deletion of +.codn place . +If +.code place +doesn't support deletion, an exception is thrown. + +First +.code place +is evaluated, thereby determining its location. +Then the place is accessed to retrieve its value. +The place is then subject to deletion. Finally, the +previously retrieved value is returned. + +Precisely what deletion means depends on the kind of place. +The built-in places in \*(TL have deletion semantics which are +intended to be unsurprising to the programmer familiar with the +data structure which holds the place. + +Generally, if a place denotes the element of a sequence, then deletion of the +place implies deletion of the element, and deletion of the element implies that +the gap produced by the element is closed. The deleted element is effectively +replaced by its successor, that successor by its successor and so on. If a +place denotes a value stored in a dynamic data set such as a hash table, +then deletion of that place implies deletion of the entry which holds +that value. If the entry is identified by a key, that key is also removed. .SS* Binding and Iteration .coNP Operators @ defvar and @ defparm @@ -11858,10 +12412,12 @@ syntax .code (fun (lambda ...)) is invalid. -.coNP Functions @ symbol-function and @ symbol-value +.coNP Accessors @ symbol-function and @ symbol-value .synb .mets (symbol-function << symbol ) .mets (symbol-value << symbol ) +.mets (set (symbol-function << symbol ) << new-value ) +.mets (set (symbol-value << symbol ) << new-value ) .syne .desc @@ -11913,6 +12469,23 @@ are currently not assignable places. Only the .code defun operator defines functions. +A +.code symbol-function +or +.code symbol-value +form denotes a place. If +.meta symbol +has a binding in, respectively, the function or variable namespace +of the global environment, then a value may be stored in the place, +otherwise an error is thrown. Deleting a place doesn't require a binding; +it takes place as if using the +.code fmakunbound +or +.code makunbound +functions. If a nonexistent place is deleted, the prior value yielded +by the deletion is deemed to be +.codn nil . + .coNP Functions @ boundp and @ fboundp .synb .mets (boundp << symbol ) @@ -12591,10 +13164,12 @@ and the macro (consp '(3)) -> t .cble -.coNP Functions @ car and @ first +.coNP Accessors @ car and @ first .synb .mets (car << object ) .mets (first << object ) +.mets (set (car << object ) << new-value ) +.mets (set (first << object ) << new-value ) .syne .desc The functions @@ -12627,10 +13202,41 @@ string, then is returned. Otherwise the first character of the string or first element of the vector is returned. -.coNP Functions @ cdr and @ rest +A +.code car +form denotes a valid place when +.meta object +is accessible via +.codn car , +isn't the object +.codn nil , +and is modifiable. + +A +.code car +form supports deletion. The following equivalence +then applies: + +.cblk + (del (car place)) <--> (pop place) +.cble + +This implies that deletion requires the argument of the +.code car +form to be a place, rather than the whole form itself. +In this situation, the argument place may have a value +which is +.codn nil , +because +.code pop +is defined on an empty list. + +.coNP Accessors @ cdr and @ rest .synb .mets (cdr << object ) .mets (rest << object ) +.mets (set (cdr << object ) << new-value ) +.mets (set (rest << object ) << new-value ) .syne .desc The functions @@ -12640,7 +13246,7 @@ and are synonyms. If -.meta obejct +.meta object is a cons cell, these functions retrieve the .code cdr field of that cons cell. @@ -12675,6 +13281,33 @@ and both result in .codn nil . +The invocation syntax of a +.code cdr +or +.code rest +form is a syntactic place. The place is semantically valid when +.meta object +is accessible via +.codn cdr , +isn't the object +.codn nil , +and is modifiable. + +A +.code cdr +place supports deletion, according to the following equivalence: + +.cblk + (del (cdr obj)) <--> (zap (cdr obj)) +.cble + +Note that this is different from the delete semantics of +.code car +in that +.code obj +is not required to be a place, and must not be +.codn nil . + .TP* Example: @@ -12729,7 +13362,8 @@ and .code cdr fields of the cell .metn cons . -Note that + +Note that, except for the difference in return value, .code (rplaca x y) is the same as the more generic .codn (set (car x) y) , @@ -12753,10 +13387,38 @@ and .code rplacd functions return .metn cons . - Note: \*(TX versions 89 and earlier, these functions returned the new value. The behavior was undocumented. +The +.meta cons +argument does not have to be a cons cell. Both functions support meaningful +semantics for vectors and strings. If +.meta cons +is a string, it must be modifiable. + +The +.code rplaca +function replaces the first element of a vector or first character +of a string. The vector or string must be at least one element long. + +The +.code rplacd +function replaces the suffix of a vector or string after the first element +with a new suffix. The +.meta new-cdr-value +must be a sequence, and if the suffix of a string is being replaced, +it must be a sequence of characters. The suffix here refers to the portion of +the vector or string after the first element. + +It is permissible to use +.code rplacd +on an empty string or vector. In this case, +.meta new-cdr-value +specifies the contents of the entire string or vector, as if the operation +were done on a non-empty vector or string, followed by the deletion of the +first element. + .coNP Functions @, second @, third @, fourth @ fifth and @ sixth .synb .mets (first << list ) @@ -12956,7 +13618,7 @@ The function is like the replace function, except that the first argument must be a list. -For a description of the arguments and semantics, refer to the +For a description of the arguments, semantics and return value, refer to the .code replace function. @@ -15605,9 +16267,10 @@ only retrieve a cached value. *list* is (2 3) .cble -.coNP Function @ force +.coNP Accessor @ force .synb .mets (force << promise ) +.mets (set (force << promise ) << new-value ) .syne .desc The @@ -15638,6 +16301,30 @@ function call. If the function is invoked additional times on the same promise, the cached value is retrieved. +A +.code force +form is a syntactic place, denoting the value cache location within +.metn promise . + +Storing a value in a +.code force +place causes future accesses to the +.meta promise +to return that value. + +If the promise had not yet been forced, then +storing a value into it prevents that from ever happening. The +delayed +.meta expression +will never be evaluated. + +If, while a promise is being forced, the evaluation of +.meta expression +itself causes an assignment to the promise, it is not specified whether +the promise will take on the value of +.meta expression +or the assigned value. + .coNP Macro @ mlet .synb .mets (mlet >> ({ sym | >> ( sym << init-form )}*) << body-form *) @@ -16593,7 +17280,7 @@ function is like the function, except that the first argument must be a string. -For a description of the arguments and semantics, refer to the +For a description of the arguments, semantics and return value, refer to the .code replace function. @@ -17108,9 +17795,10 @@ to The argument is taken to be a Unicode code point value and the corresponding character object is returned. -.coNP Function @ chr-str +.coNP Accessor @ chr-str .synb .mets (chr-str < str << idx ) +.mets (set (chr-str < str << idx ) << new-value ) .syne .desc The @@ -17134,6 +17822,25 @@ An empty string cannot be indexed. A string of length one supports index 0 and index -1. A string of length two is indexed left to right by the values 0 and 1, and from right to left by -1 and -2. +If the element +.meta idx +of string +.meta str +exists, and the string is modifiable, then the +.code chr-str +form denotes a place. + +A +.code chr-str +place +supports deletion. When a deletion takes place, +then the character at +.meta idx +is removed from the string. Any characters +after that position move by one position +to close the gap, and the length of the string +decreases by one. + .TP* Notes: Direct use of @@ -17155,6 +17862,16 @@ this also holds: (chr-str s i) --> (ref s i) .cble +However, note the following difference. When the expression +.code [s i] +is used as a place, then the subexpression +.code s +must be a place. When +.code (chr-str s i) +is used as a place, +.code s +need not be a place. + .coNP Function @ chr-str-set .synb .mets (chr-str-set < str < idx << char ) @@ -17636,9 +18353,10 @@ argument must be nonnegative. The return value is .metn vec . -.coNP Function @ vecref +.coNP Accessor @ vecref .synb .mets (vecref < vec << idx ) +.mets (set (vecref < vec << idx ) << new-value ) .syne .desc The @@ -17654,6 +18372,32 @@ The value must range from 0 to one less than the length of the vector. The specified element is returned. +If the element +.meta idx +of vector +.meta vec +exists, then the +.code vecref +form denotes a place. + +A +.code vecref place +supports deletion. When a deletion takes place, +then if +.meta idx +denotes the last element in the vector, the +vector's length is decreased by one, so that +the vector no longer has that element. +Otherwise, if +.meta idx +isn't the last element, then each elements +values at a higher index than +.meta idx +shifts by one one element position to the +adjacent lower index. Then, the length of the +vector is decreased by one, so that the last +element position disappears. + .coNP Function @ vec-push .synb .mets (vec-push < vec << elem ) @@ -17768,7 +18512,7 @@ is like the function, except that the first argument must be a vector. -For a description of the arguments and semantics, refer to the +For a description of the arguments, semantics and return value, refer to the .code replace function. @@ -17966,6 +18710,46 @@ with the input sequence. .syne .desc The +.meta replace +function modifies +.meta sequence +in the ways described below. + +The operation is destructive: it may work "in place" by modifying +the original sequence. The caller should retain the return value +and stop relying on the original input sequence. + +The return value of +.code replace +is the modified +version of +.metn sequence . +This may be the same object as +.meta sequence +or it may be a newly allocated object. + +Note that the form: + +.cblk + (set seq (replace seq new fr to)) +.cble + +has the same effect on the variable +.code seq +as the form: + +.cblk + (set [seq fr..to] new) +.cble + +except that the former +.code set +form returns the entire modified sequence, whereas the latter +returns the value of the +.code new +argument. + +The .code replace function has two invocation styles, distinguished by the type of the third argument. If the third argument is a list or vector, then it @@ -18000,20 +18784,9 @@ and .code t respectively. -The following equivalence holds between assignment to a place denoted by -DWIM bracket syntax and first form of the replace function: - -.cblk - (set seq (replace seq new fr to)) <--> (set [seq fr..to] new) -.cble - The description of the dwim operator\(emin particular, the section on Range Indexing\(emexplains the semantics of the range specification. -This operation is destructive: it may work "in place" by modifying -the original sequence. The caller should retain the return value -and stop relying on the original input sequence. - The second form of the replace function replaces a subsequence of elements from .meta sequence @@ -18044,15 +18817,6 @@ is a list, then must be monotonically increasing. -The following equivalence holds between assignment to a place denoted by -DWIM bracket syntax and second form of the -.code replace -function: - -.cblk - (set seq (replace seq new ix-list)) <--> (set [seq ix-list] new) -.cble - .coNP Function @ search .synb .mets (search < haystack < needle >> [ testfun <> [ keyfun ]) @@ -20938,9 +21702,10 @@ the hash. Modifying the .code cdr field has the effect of updating the association with a new value. -.coNP Function @ gethash +.coNP Accessor @ gethash .synb .mets (gethash < hash < key <> [ alt ]) +.mets (set (gethash < hash < key <> [ alt ]) << new-value ) .syne .desc The @@ -20960,6 +21725,41 @@ was not specified, .code nil is returned. +A valid +.code gethash +form serves as a place. It denotes either an existing value in a hash +table or a value that would be created by the evaluation of the form. +The +.meta alt +argument is meaningful when +.code gethash +is used as a place, and, if present, is always evaluated whenever the place is +evaluated. +In place update operations, it provides the initial value, which defaults +to +.code nil +if the argument is not specified. For example +.code (inc (gethash h k d)) +will increment the value stored under key +.code k +in hash table +.code h +by one. If the key does not exist in the hash table, then +the value +.code (+ 1 d) +is inserted into the table under that key. +The expression +.code d +is always evaluated, whether or not its value is needed. + +If a +.code gethash +place is subject to a deletion, but doesn't exist, it is not an error. +The operation does nothing, and +.code nil +is considered the prior value of the place yielded +by the deletion. + .coNP Function @ sethash .synb .mets (sethash < hash < key << value ) @@ -20982,6 +21782,12 @@ pair is newly inserted into .metn hash . +The +.code sethash +function returns the +.metn value +argument. + .coNP Function @ pushhash .synb .mets (pushhash < hash < key << element ) @@ -21010,6 +21816,10 @@ value is added to the front of that list, and the extended list then becomes the new value under .metn key . +The return value is boolean. If true, indicates that the hash table entry was +newly created. If false, it indicates that the push took place on an existing +entry. + .coNP Function @ remhash .synb .mets (remhash < hash << key ) @@ -21024,6 +21834,11 @@ for a key similar to the If that key is found, then that key and its corresponding value are removed from the hash table. +If the key is found and removal takes place, then the associated value +is returned. Otherwise +.code nil +is returned. + .coNP Function @ hash-count .synb .mets (hash-count << hash ) @@ -24529,9 +25344,10 @@ if it exists. On some platforms, it instead sets the environment variable to the empty string. .SS* System Programming -.coNP Function @ errno +.coNP Accessor @ errno .synb .mets (errno <> [ new-errno ]) +.mets (set (errno) << new-value ) .syne .desc The @@ -24547,6 +25363,10 @@ specifies a value which is stored into .codn errno . The value returned is the prior value. +The place form of +.code errno +does not take an argument. + .coNP Function @ exit .synb .mets (exit << status ) @@ -27051,19 +27871,9 @@ The return value of the macro is the macro expansion. It is substituted in place of the entire macro call form. That form is then expanded again; it may itself be another macro call, or contain more macro calls. -.TP* Examples: +.TP* Example: .cblk - ;; A macro to swap two places, such as variables. - ;; (swap x y) will now exchange the contents - ;; of x and y. - - (defmacro swap (a b) - (let ((temp (gensym))) - ^(let ((,temp ,a)) - (set ,a ,b) - (set ,b ,temp)))) - ;; dolist macro similar to Common Lisp's: ;; ;; The following will print 1, 2 and 3 @@ -27667,6 +28477,1227 @@ should be understood to be a globally unique symbol: clause1 clause2 ...)) .cble +.SS* User-Defined Places and Place Operators + +\*(TL provides a number of place-modifying operators such as +.codn set , +.codn push , +and +.codn inc . +It also provides a variety of kinds of syntactic places +which may be used with these operators. + +Both of these categories are open-ended: \*(TL programs may extend +the set of place-modifying operators, as well as the vocabulary of +forms which are recognized as syntactic places. + +Regarding place operators, it might seem obvious that new place operators can +be developed, since they are macros, and macros can expand to uses +of existing place operators. As an example, it may seem that +.code inc +operator could be written as a macro which uses +.codn set : + +.cblk + (defmacro new-inc (place : (delta 1)) + ^(set ,place (+ ,place ,delta))) +.cble + +However, the above +.code new-inc +macro has a problem: the +.code place +argument form is inserted into two places in the expansion, which +leads to two evaluations. This is visibly incorrect if the place +form contains any side effects. It is also potentially inefficient. + +\*(TL provides a framework for writing place update macros which +evaluate their argument forms once, even if they have to access +and update the same places. + +The framework also supports the development of new kinds of place forms +as capsules of code which introduce the right kind of material into +the lexical environment of the body of an update macro, to enable +this special evaluation. + +.NP* Place-Expander Functions + +The central design concept in \*(TL syntactic places are place-expander +function. Each compound place is defined by up to three +.IR "place-expander functions" , +which are associated with the place via the leftmost operator +symbol of the place form. One place-expander, the +.IR "update expander" , +is mandatory. Optionally, a place may also provide a +.I "clobber expander" +as well as a +.IR "delete expander" . +An update expander provides the expertise for evaluating a place form once +in its proper run-time context to determine its actual run-time storage +location, and to access and modify the storage location. +A clobber expander provides an optimized mechanism for uses that perform +a one-time store to a place without requiring its prior value. +If a place definition does not supply a clobber expander, then the syntactic +places framework uses the update expander to achieve the functionality. +A delete expander provides the expertise for determining the actual run-time +storage location corresponding to a place, and obliterating it, +returning its prior value. If a place does not supply a delete expander, then +the place does not support deletion. Operators which require deletion, such as +.code del +will raise an error when applied to that place. + +The expanders operate independently, and it is expected that place-modifying +operators choose one of the three, and use only that expander. For example, +accessing a place with an update expander and then overwriting its value +with a clobber expander may result in incorrect code which contains multiple +evaluations of the place form. + +The programmer who implements a new place does not write expanders directly, +but rather defines them via the +.code defplace +macro. + +The programmer who implements a new place update macro likewise does not +call the expanders directly. Usually, they are invoked via the macros +.codn with-update-expander , +.codn with-clobber-expander +and +.codn with-delete-expander . +These are sufficient for most kind of macros. +In certain complicated cases, expanders may be invoked using the wrapper +functions +.codn call-update-expander , +.codn call-clobber-expander +and +.codn call-delete-expander . +These convenience macros and functions perform certain common chores, like +macro-expanding the place in the correct environment, and choosing the +appropriate function. + +The expanders are described in the following sections. + +.NP* The Update Expander +.synb +.mets (lambda >> ( getter-sym < setter-sym < place-form +.mets \ \ \ \ \ \ \ \ << body-form ) ...) +.syne +.desc +The update expander is a code-writer. It takes a +.meta body-form +argument, representing code, and returns a larger form which surrounds +this code with additional code. + +This larger form returned by the update expander can be regarded as having two +abstract actions, when it is substituted and evaluated in the context where +.meta place-form +occurs. The first abstract action is to evaluate +.meta place-form +exactly one time, in order to determine the actual run-time location to which +that form refers. +The second abstract action is to evaluate the caller's +.metn body-form -s, +in a lexical environment in which bindings exist for some lexical +functions or (more usually) lexical macros. These lexical macros +are explicitly referenced by the +.metn body-form ; +the update expander just provides their definition, under the names +it is given via the +.meta getter-sym +and +.meta setter-sym +arguments. + +The update expander writes local functions or macros under these names: a +getter function and a setter function. Usually, update expanders write +macros rather than functions, possibly in combination with some lexical +anonymous variables which hold temporary objects. Therefore the getter +and setter are henceforth referred to as macros. + +The code being generated is with regard to some concrete instance of +.metn place-form . +This argument is the actual form which occurs in a program. For +instance, the update expander for the +.code car +place might be called with an arbitrary variant of the +.meta place-form +might look like +.codn (car (inc (third some-list))) . + +In the abstract semantics, upfront code wrapped around the +.meta body-form +by the update expander provides the logic to evaluate this place to +a location, which is retained in some hidden local context. + +The getter local macro named by +.meta getter-sym +must provide the logic for retrieving the value of this place. +The getter macro takes no arguments. +The +.meta body-form +makes free use of the getter function; they may call it multiple times, +which must not trigger multiple evaluations of the original place +form. + +The setter local macro named by +.meta setter-sym +must generate the logic for storing a new value into the once-evaluated +version of +.metn place-form . +The setter function takes exactly one argument, whose +value specifies the value to be stored into the place. +It is the caller's responsibility to ensure that the +argument form which produces the value to be stored via the setter is evaluated +only once, and in the correct order. The setter does not concern itself with +this form. Multiple calls to the setter can be expected to result in multiple +evaluations of its argument. Thus, if necessary, the caller must supply the code +to evaluate the new value form to a temporary variable, and then pass the +temporary variable to the setter. This code can be embedded in +the +.meta body-form +or can be added to the code returned by a call to the update expander. + +The setter local macro or function must return the new value which is stored. +That is to say, when +.meta body-form +invokes this local macro or function, it may rely on it yielding the +new value which was stored, as part of achieving its own semantics. + +The update expander does not macro-expand +.codn place-form . +It is assumed that the expander is invoked in such a way that the +place has been expanded in the correct environment. In other words, the +form matches the type of place which the expander handles. +If the expander had to macro-expand the place form, it would sometimes have +to come to the conclusion that the place form must be handled by a different +expander. No such consideration is the case: when an expander is called on +a form, that is final; it is certain that it is the correct expander, which +matches the symbol in the +.code car +position of the form, which is not a macro in the context where it occurs. + +An update expander is free to assume that any place which is stored +(the setter local macro is invoked on it) is accessed at least once by +an invocation of the getter. A place update macro which relies on an update +expander, but uses only the store macro, might not work properly. +An example of an update expander which relies on this assumption is the +expander for the +.cblk +.meti (force << promise ) +.cble +place type. If +.meta promise +has not yet been forced, and only the setter is used, then +.meta promise +might remain unforced as its internal value location is updated. +A subsequent access to the place will incorrectly trigger a force, +which will overwrite the value. The expected behavior is that storing +a value in an unforced +.code force +place changes the place to forced state, preempting the evaluation of +the delayed form. Afterward, the promise exhibits the value which was +thus assigned. + +The update expander is not responsible for all issues of evaluation order. A +place update macro may consist of numerous places, as well as numerous +value-producing forms which are not places. Each of the places can provide its +registered update expander which provides code for evaluating just that place, +and a means of accessing and storing the values. The place update macro must +call the place expanders in the correct order, and generate any additional code +in the correct order, so that the macro achieves its required documented +evaluation order. + +.TP* "Example Update Expander Call:" + +.cblk + ;; First, capture the update expander + ;; function for (car ...) places + ;; in a variable, for clarity. + + (defvar car-update-expander [*place-update-expander* 'car]) + + ;; Next, call it for the place (car [a 0]). + ;; The body form specifies logic for + ;; incrementing the place by one and + ;; returning the new value. + + (call car-update-expander 'getit 'setit '(car [a 0]) + '(setit (+ (getit) 1))) + + --> ;; Resulting code: + + (rlet ((#:g0032 [a 0])) + (macrolet ((getit nil + (append (list 'car) (list '#:g0032))) + (setit (val) + (append (list 'sys:rplaca) + (list '#:g0032) (list val)))) + (setit (+ (getit) 1)))) + + ;; Same expander call as above, with a sys:expand to show the + ;; fully expanded version of the returned code, in which the + ;; setit and getit calls have disappeared, replaced by their + ;; macro-expansions. + + (sys:expand + (call car-update-expander 'getit 'setit '(car [a 0]) + '(setit (+ (getit) 1)))) + + --> + + (let ((#:g0032 [a 0])) + (sys:rplaca #:g0032 (+ (car #:g0032) 1))) + +.cble +The main noteworthy points about the generated code are: +.RS +.IP - +the +.code (car [a 0]) +place is evaluated by evaluating the embedded form +.code [a 0] +and storing storing the resulting object into a hidden local variable. +That's as close a reference as we can make to the +.code car +field. +.IP - +the getter macro expands to code which simply calls the +.code car +function on the cell. +.IP - +the setter uses a system function called +.codn sys:rplaca , +which differs from +.code rplaca +in that it returns the stored value, rather than the cell. +.RE + +.NP* The Clobber Expander +.synb +.mets (lambda >> ( simple-setter-sym < place-form +.mets \ \ \ \ \ \ \ \ << body-form ) ...) +.syne +.desc +The clobber expander is a code-writer similar to the update expander. +It takes a +.meta body-form +argument, and returns a larger form which surrounds this form +with additional program code. + +The returned block of code has one main abstract action. +It must arrange for the evaluation of +.meta body-form +in a lexical environment in which a lexical macro or lexical function +exists which has the name requested by the +.meta simple-setter-sym +argument. + +The simple setter local macro written by the clobber expander is similar to the +local setter written by the update expander. It has exactly the +same interface, performs the same action of storing a value into +the place, and returns the new value. + +The difference is that its logic may be considerably simplified by the +assumption that the place is being subject to exactly one store, +and no access. + +A place update macro which uses a clobber expander, and calls it more than +once, break the assumption; doing so may result in multiple evaluations +of the +.metn place-form . + +.NP* The Delete Expander +.synb +.mets (lambda >> ( deleter-sym < place-form +.mets \ \ \ \ \ \ \ \ << body-form ) ...) +.syne +.desc +The delete expander is a code-writer similar to clobber expander. +It takes a +.meta body-form +arguments, and returns a larger form which surrounds this form +with additional program code. + +The returned block of code has one main abstract action. +It must arrange for the evaluation of +.meta body-form +in a lexical environment in which a lexical macro or lexical function +exists which has the name requested by the +.meta deleter-sym +argument. + +The deleter macro written by the clobber expander takes no arguments. +It may be called at most once. It returns the previous value of the +place, and arranges for its obliteration, whatever that means for +that particular kind of place. + +.coNP Macro @ with-update-expander +.synb +.mets (with-update-expander >> ( getter << setter ) < place < env +.mets \ << body-form ) +.syne +.desc +The +.code with-update-expander +macro evaluates the +.meta body-form +argument, whose result is expected to be a Lisp form. +The macro adds additional code around this code, and the result is returned. +This additional code is called the +.IR "place-access code" . + +The +.meta getter +and +.meta setter +arguments must be symbols. Over the evaluation of the +.metn body-form , +these symbols are bound to the names of local functions which +are provided in the place-access code. + +The +.meta place +argument is a form which evaluates to a syntactic place. The generated +place-access code is based on this place. + +The +.meta env +argument is a form which evaluates to a macro-expansion-time environment. +The +.code with-update-expander +macro uses this environment to perform macro-expansion on the value of the +.meta place +form, to obtain the correct update expander function for the fully +macro-expanded place. + +The place-access code is generated by calling the update expander +for the expanded version of +.codn place . + +.TP* "Example:" + +The following is an implementation of the +.code swap +macro, which exchanges the contents of two places. + +Two places are involved, and, correspondingly, the +.code with-update-expander +macro is used twice, to add two instances of place-update code +to the macro's body. + +.cblk + (defmacro swap (place-0 place-1 :env env) + (with-gensyms (tmp) + (with-update-expander (getter-0 setter-0) place-0 env + (with-update-expander (getter-1 setter-1) place-1 env + ^(let ((,tmp (,getter-0))) + (,setter-0 (,getter-1)) + (,setter-1 ,tmp)))))) +.cble + +The basic logic for swapping two places is contained in the code template: + +.cblk + ^(let ((,tmp (,getter-0))) + (,setter-0 (,getter-1)) + (,setter-1 ,tmp)) +.cble + +The temporary variable named by the +.code gensym +symbol +.code tmp +is initialized by calling the getter function for +.metn place-0 . +Then the setter function of +.meta place-0 +is called in order to store the value of +.meta place-1 +into +.metn place-0 . +Finally, the setter for +.meta place-1 +is invoked to store the previously saved temporary value into +that place. + +The name for the temporary variable is provided by the +.code with-gensyms +macro, but establishing the variable is the caller's responsibility; +this is seen as an explicit +.code let +binding in the code template. + +The names of the getter and setter functions are similarly provided +by the +.code with-update-expander +macros. However, binding those functions is the responsibility of that +macro. To achieve this, it adds the place-access code to the code generated by +the +.code ^(let ...) +backquote template. In the following example macro-expansion, the additional +code added around the template is seen. It takes the form of two +.code macrolet +binding blocks, each added by an invocation of +.codn with-update-expander : + +.cblk + (macroexpand '(swap a b)) + + --> + + (macrolet ((#:g0036 () 'a) ;; getter macro for a + (#:g0037 (val-expr) ;; setter macro for a + (append (list 'sys:setq) (list 'a) + (list val-expr)))) + (macrolet ((#:g0038 () 'b) ;; getter macro for b + (#:g0039 (val-expr) ;; setter macro for b + (append (list 'sys:setq) (list 'b) + (list val-expr)))) + (let ((#:g0035 (#:g0036))) ;; temp <- a + (#:g0037 (#:g0038)) ;; a <- b + (#:g0039 #:g0035)))) ;; b <- temp +.cble + +In this expansion, for example +.code #:g0036 +is the generated symbol which forms the value of the +.code getter-0 +variable in the +.code swap +macro. The getter is a macro which simply expands to a +.codn a : +straightforward access to the variable a. +Of course, +.code #:g0035 +is nothing but the value of the +.code tmp +variable. Thus the swap macro's +.cblk +^(let ((,tmp (,getter-0))) ...) +.cble +has turned into +.cblk +^(let ((#:g0035 (#:g0036))) ...) +.cble + +A full expansion, with the +.code macrolet +local macros expanded out: + +.cblk + (sys:expand '(swap a b))" + + --> + + (let ((#:g0035 a)) + (sys:setq a b) + (sys:setq b #:g0035)) +.cble + +In other words, the original syntax +.cblk +(,getter-0) +.cble +became +.cblk +(#:g0036) +.cble +and finally just +.codn a . + +Similarly, +.cblk +(,setter-0 (,getter-1)) +.cble +became the +.code macrolet +invocations +.cblk +(#:g0037 (#:g0038)) +.cble +which finally turned into: +.codn "(sys:setq a b)" . + +.coNP Macro @ with-clobber-expander +.synb +.mets (with-clobber-expander <> ( simple-setter ) < place < env +.mets \ << body-form ) +.syne +.desc +The +.code with-clobber-expander +macro evaluates +.metn body-form , +whose result is expected to be a Lisp form. The macro adds additional code +around this form, and the result is returned. This additional code is called +the +.IR "place-access code" . + +The +.meta simple-setter +argument must be a symbol. Over the evaluation of the +.metn body-form , +this symbol is bound to the name of a functions which +are provided in the place-access code. + +The +.meta place +argument is a form which evaluates to a syntactic place. The generated +place-access code is based on this place. + +The +.meta env +argument is a form which evaluates to a macro-expansion-time environment. +The +.code with-clobber-expander +macro uses this environment to perform macro-expansion on the value of the +.meta place +form, to obtain the correct update expander function for the fully +macro-expanded place. + +The place-access code is generated by calling the update expander +for the expanded version of +.codn place . + +.TP* "Example:" + +The following implements a simple assignment statement, similar to +.code set +except that it only handles exactly two arguments: + +.cblk + (defmacro assign (place new-value :env env) + (with-clobber-expander (setter) place env + ^(,setter ,new-value))) +.cble + +Note that the correct evaluation order of +.code place +and +.code new-value +is taken care of, because +.code with-clobber-expander +generates the code which performs all the necessary evaluations of +.codn place . +This evaluation occurs before the code which is generated by +.cblk +^(,setter ,new-value) +.cble +part is evaluated, and that code is what evaluates +.codn new-value . + +Suppose that a macro were desired which allows assignment to be notated in a right to left +style, as in: + +.cblk + (assign 42 a) ;; store 42 in variable a +.cble + +Now, the new value must be evaluated prior to the place, if left to right +evaluation order is to be maintained. The standard +.code push +macro has this property: the push value is on the left, and the place +is on the right. + +Now, the code has to explicitly take care of the order, like this: + +.cblk + ;; WRONG! We can't just swap the parameters; + ;; place is still evaluated first, then new-value: + + (defmacro assign (new-value place :env env) + (with-clobber-expander (setter) place env + ^(,setter ,new-value))) + + ;; Correct: arrange for evaluation of new-value first, + ;; then place: + + (defmacro assign (new-value place :env env) + (with-gensym (tmp) + ^(let ((,tmp ,new-value)) + ,(with-clobber-expander (setter) place env + ^(,setter ,tmp))))) +.cble + +.coNP Macro @ with-delete-expander +.synb +.mets (with-delete-expander <> ( deleter ) < place < env +.mets \ << body-form ) +.syne +.desc +The +.code with-delete-expander +macro evaluates +.metn body-form , +whose result is expected to be a Lisp form. +The macro adds additional code +around this code, and the resulting code is returned. This additional code is +called the +.IR "place-access code" . + +The +.meta deleter +argument must be a symbol. Over the evaluation of the +.metn body-form , +this symbol is bound to the name of a functions which +are provided in the place-access code. + +The +.meta place +argument is a form which evaluates to a syntactic place. The generated +place-access code is based on this place. + +The +.meta env +argument is a form which evaluates to a macro-expansion-time environment. +The +.code with-delete-expander +macro uses this environment to perform macro-expansion on the value of the +.meta place +form, to obtain the correct update expander function for the fully +macro-expanded place. + +The place-access code is generated by calling the update expander +for the expanded version of +.codn place . + +.TP* "Example:" + +The following implements the +.code del +macro: + +.cblk + (defmacro del (place :env env) + (with-delete-expander (deleter) place env + ^(,deleter))) +.cble + +.coNP Function @ call-update-expander +.synb +.mets (call-update-expander < getter < setter < place < env << body-form ) +.syne +.desc +The +.code call-update-expander +function provides an alternative interface for making use of an update +expander, complementary to +.codn with-update-expander . + +Arguments +.meta getter +and +.meta setter +are symbols, provided by the caller. These are passed to the update +expander function, and are used for naming local functions in the +generated code which the update expander adds to +.metn body-form . + +The +.meta place +argument is a place which has not been subject to macro-expansion. +The +.code call-update-expander +function takes on the responsibility for macro-expanding the place. + +The +.meta env +parameter is the macro-expansion environment object required to +correctly expand +.code place +in its original environment. + +The +.meta body-form +argument represents the source code of a place update operation. +This code makes references to the local functions whose names +are given by +.meta getter +and +.metn setter . +Those arguments allow the update expander to write these functions +with the matching names expected by +.metn body-form . + +The return value is an object representing source code which incorporates +the +.metn body-form , +augmenting it with additional code which evaluates +.code place +to determine its location, and provides place accessor local functions +expected by the +.metn body-form . + +.TP* "Example:" + +The following shows how to implement a +.code with-update-expander +macro using +.codn call-update-expander : + +.cblk + (defmacro with-update-expander ((getter setter) + unex-place env body) + ^(with-gensyms (,getter ,setter) + (call-update-expander ,getter ,setter + ,unex-place ,env ,body))) +.cble + +Essentially, all that +.code with-update-expander +does is to choose the names for the local functions, and bind them +to the local variable names it is given as arguments. Then it +calls +.codn call-update-expander . + +.TP* "Example:" + +Implement the swap macro using +.codn call-update-expander : + +.cblk + (defmacro swap (place-0 place-1 :env env) + (with-gensyms (tmp getter-0 setter-0 getter-1 setter-1) + (call-update-expander getter-0 setter-0 place-0 env + (call-update-expander getter-1 setter-1 place-1 env + ^(let ((,tmp (,getter-0))) + (,setter-0 (,getter-1)) + (,setter-1 ,tmp)))))) +.cble + +.coNP Function @ call-clobber-expander +.synb +.mets (call-clobber-expander < simple-setter < place < env << body-form ) +.syne +.desc +The +.code call-clobber-expander +function provides an alternative interface for making use of a clobber +expander, complementary to +.codn with-clobber-expander . + +Argument +.meta simple-setter +is a symbol, provided by the caller. It is passed to the clobber +expander function, and is used for naming a local function in the +generated code which the update expander adds to +.metn body-form . + +The +.meta place +argument is a place which has not been subject to macro-expansion. +The +.code call-clobber-expander +function takes on the responsibility for macro-expanding the place. + +The +.meta env +parameter is the macro-expansion environment object required to +correctly expand +.code place +in its original environment. + +The +.metn body-form +argument represents the source code of a place update operation. +This code makes references to the local function whose name +is given by +.metn simple-setter . +That argument allows the update expander to write this function +with the matching name expected by +.metn body-form . + +The return value is an object representing source code which incorporates +the +.metn body-form , +augmenting it with additional code which evaluates +.code place +to determine its location, and provides the clobber local function +to the +.metn body-form . + +.coNP Function @ call-delete-expander +.synb +.mets (call-delete-expander < deleter < place < env << body-form ) +.syne +.desc +The +.code call-delete-expander +function provides an alternative interface for making use of a delete +expander, complementary to +.codn with-delete-expander . + +Argument +.meta deleter +is a symbol, provided by the caller. It is passed to the delete +expander function, and is used for naming a local function in the +generated code which the update expander adds to +.metn body-form . + +The +.meta place +argument is a place which has not been subject to macro-expansion. +The +.code call-delete-expander +function takes on the responsibility for macro-expanding the place. + +The +.meta env +parameter is the macro-expansion environment object required to +correctly expand +.code place +in its original environment. + +The +.meta body-form +argument represents the source code of a place delete operation. +This code makes references to the local function whose name +is given by +.metn deleter . +That argument allows the update expander to write this function +with the matching name expected by +.metn body-form . + +The return value is an object representing source code which incorporates +the +.metn body-form , +augmenting it with additional code which evaluates +.code place +to determine its location, and provides the delete local function +to the +.metn body-form . + +.coNP Macro @ define-modify-macro +.synb +.mets (define-modify-macro < name < parameter-list << function-name ) +.syne +.desc +The +.code define-modify-macro +macro provides a simplified way to write certain kinds of place update +macros. Specifically, it provides a way to write place update macros +which modify a place by retrieving the previous value, pass it through +a function (perhaps together with some additional arguments), and then store +the resulting value back into the place and return it. + +The +.meta name +parameter specifies the name for the place update macro to be written. + +The +.meta function-name +parameter must specify a symbol: the name of the update function. + +The update macro and update function both take at least one parameter: +the place to be updated, and its value, respectively. + +The +.meta parameter-list +specifies the additional parameters for update function, which will also +become additional parameters of the macro. Because it is a +function parameter list, it cannot use the special destructuring features of +macro parameter lists, or the +.code :env +or +.code :whole +special parameters. It can use optional parameters. Of course, it may be empty. + +The +.code define-modify-macro +macro writes a macro called +.metn name . +The leftmost parameter of this macro is a place, followed by the additional arguments +specified by +.metn parameter-list . +The macro will arrange for the evaluation of the place argument to determine +the place location. It will then retrieve and save the prior value of the +place, and evaluate the remaining arguments. The prior value of the +place, and the values of the additional arguments, are all passed to +.meta function +and the resulting value is then stored back into the location previously +determined for +.metn place . + +.TP* "Example:" + +Some standard place update macros are implementable using +.codn define-modify-macro , +such as +.codn inc . + +The +.code inc +macro reads the old value of the place, then passes it through the +.code + +(plus) function, along with an extra argument: the delta value, which +defaults to one. The +.code inc +macro could be written using +.code define-modify-macro +as follows: + +.cblk + (define-modify-macro inc (: (delta 1)) +) +.cble + +Note that the argument list +.code (: (delta 1)) +doesn't specify the place, because the place is the implicit leftmost +argument of the macro which isn't given a name. With the above definition +in place, when +.code (inc (car a)) +is invoked, then +.code (car a) +is first reduced to a location, and that location's value is retrieved and +saved. Then the +.code delta +parameter s evaluated to its value, which has defaulted to 1, since +the argument was omitted. +Then these two values are passed to the +.code + +function, and so 1 is added to the value previously retrieved from +.codn (car a) . +The resulting sum is then stored back +.code (car a) +without, of course, evaluating +.code (car a) +again. + +.coNP Macro @ defplace +.synb +.mets (defplace < place-destructuring-args < body-sym +.mets \ \ \ \ \ \ \ \ \ >> ( getter-sym < setter-sym << update-body ) +.mets \ \ \ \ \ \ \ \ \ >> [( ssetter-sym << clobber-body ) +.mets \ \ \ \ \ \ \ \ \ \ >> [( deleter-sym << delete-body )]]) +.syne +.desc +The +.code defplace +macro is used to introduce a new kind of syntactic place. +It writes the update expander, and optionally clobber and delete +expander functions, from a simpler, more compact specification, +and automatically registers the resulting functions. The compact specification +of a +.code defplace +call contains only code fragments for the expander functions. + +The name and syntax of the place is determined by the +.meta place-destructuring-args +argument, which is macro-style parameter list whose structure +mimics that of the the place. In particular, its leftmost symbol +gives the name under which the place is registered. +The +.code defplace +macro provides automatic destructuring of the syntactic place, +so that the expander code fragments can refer to the components +of a place by name. + +The +.meta body-sym +parameter must be be a symbol. This symbol will capture the +.meta body-forms +parameter which is passed to the update expander, clobber +expander or delete expander. The code fragments then have +access to the the body forms via this name. + +The +.metn getter-sym , +.metn setter-sym , +and +.metn update-body +parenthesized triplet specify the update expander fragment. +The +.code defplace +macro will bind +.meta getter-sym +and +.meta setter-sym +to symbols. The +.meta update-body +must then specify a template of code which evaluates the syntactic place to +determine its storage location, and provides a pair of local functions, using +these two symbols as their name. The template must also insert the +.meta body-sym +forms into the scope of these local functions, and the place determining code. + +The +.meta setter-sym +and +.meta clobber-body +arguments similarly specify an optional clobber expander fragment, +as a single optional argument. If specified, the +.meta clobber-body +must generate a local function named using +.meta setter-sym +wrapped around +.meta body-sym +forms. + +The +.meta deleter-sym +and +.meta deleter-body +likewise specify a delete expander fragment. If this is omitted, +then the place shall not support deletion. + +.TP* "Example:" + +Implementation of the place denoting the +.code car +field of +.code cons +cells: + +.cblk + (defplace (car cell) body + + ;; the update expander fragment + (getter setter + (with-gensyms (cell-sym) ;; temporary symbol for cell + ^(let ((,cell-sym ,cell)) ;; evaluate place to cell + ;; getter and setter access cell via temp var + (macrolet ((,getter () + ^(car ,',cell-sym)) + (,setter (val) + ^(sys:rplaca ,',cell-sym ,val))) + + ;; insert body form from place update macro + ,body)))) + + ;; clobber expander fragment: simpler: no need + ;; to evaluate cell to temporary variable. + (ssetter + ^(macrolet ((,ssetter (val) + ^(sys:rplaca ,',cell ,val))) + ,body)) + + ;; deleter: delegate to pop semantics: + ;; (del (car a)) == (pop a). + (deleter + ^(macrolet ((,deleter () ^(pop ,',cell))) + ,body))) +.cble + +.coNP Macro @ rlet +.synb +.mets (rlet >> ({( sym << init-form )}*) << body-form *) +.syne +.desc +The macro +.code rlet +is similar to the +.code let +operator. It establishes bindings for one or more +.metn sym -s, +which are initialized using the values of +.metn init-form -s. + +Note that the simplified syntax for a variable which initializes to +.code nil +by default is not supported by +.codn rlet ; +that is to say, the syntax +.meta sym +cannot be used in place of the +.meti >> ( sym << init-form ) +syntax when +.meta sym +is to be initialized to +.codn nil . + +The +.code rlet +macro differs from +.code let +in that +.code rlet +assumes that those +.metn sym -s +which have constant +.metn init-form -s +(according to the +.code constantp +function) may be safely implemented as a symbol macro rather than a lexical +variable. + +Therefore +.code rlet +is suitable in situations in which simpler code is desired from the output +of certain kinds of machine-generated code, which binds local symbols: +code with fewer temporary variables. + +On the other hand, +.code rlet +is not suitable in situations when true variables are required, which +are assignable, and provide temporary storage. + +.TP* "Example:" + +.cblk + ;; WRONG! Exchange two variables, a and b: + (rlet ((temp a)) + (set a b) + (set b temp)) + + ;; Demonstration of constant-propagation + (let ((a 42)) + (rlet ((x 1) + (y a)) + (+ x y))) --> 43 + + (sys:expand + '(let ((a 42)) + (rlet ((x 1) + (y a)) + (+ x y)))) --> (let ((a 42)) + (let ((y a)) + (+ 1 y))) +.cble + +The last example shows that the +.code x +variable has disappeared in the expansion. The +.code rlet +macro turned it into into a +.code symacrolet +denoting the constant 1, which then propagated to the use site, +turning the expression +.code (+ x y) +into +.codn (+ 1 y) . + +.coNP Macro @ with-gensyms +.synb +.mets (with-gensyms <> ( sym *) << body-form *) +.syne +.desc +The +.code with-gensyms +evaluates the +.metn body-form -s +in an environment in which each variable name symbol +.meta sym +is bound to a new uninterned symbol ("gensym"). + +.TP* "Example:" + +The code: + +.cblk + (let ((x (gensym)) + (y (gensym)) + (z (gensym))) + ^(,x ,y ,z)) +.cble + +may be expressed more conveniently using the +.code with-gensyms +shorthand: + +.cblk + (with-gensyms (x y z) + ^(,x ,y ,z)) +.cble + .SS* Debugging Functions .coNP Functions source-loc and source-loc-str .synb |