HELP DLOCAL A.Sloman, John Gibson Mar 1990 Updated John Gibson Mar 1996 dlocal is a syntax word used within a procedure to specify dynamic local variables or expressions, that is expressions whose values are saved on entry and restored on exit -- whether by a normal or abnormal exit. For full details of the mechanisms involved see REF * VMCODE/sysLOCAL. Some tutorial examples, problems and solutions are presented below. CONTENTS - (Use g to access required sections) 1 Introduction 2 Example Formats 3 An Example Using a dlocal Expression 4 Multiplicity 5 Example of an Exit Action 6 vars vs dlocal 7 File-local Lexicals 8 dlocal_context: Controlling Entry and Exit Actions 9 Example using dlocal_context 10 Do Not use Initialised Local Variables in dlocal Expressions 11 Use of dlocal_context to Avoid an Extra Procedure Call 12 Exit Actions to Tidy Up Variables 13 Related Documentation ----------------------------------------------------------------------- 1 Introduction ----------------------------------------------------------------------- A dlocal declaration within a procedure may specify that the value of any of the following may be saved and restored: 1. an ordinary variable 2. an active variable (see * ACTIVE_VARIABLES) 3. an arbitrary expression, indicated between "% ... %". The declaration may also specify a value to be assigned to the expression when the procedure is run. NOTE that although a dlocal declaration may appear anywhere inside a procedure, the localisation of the variable or expression always applies to the procedure as a whole; only the assignment part (if supplied) follows the normal flow of control. Thus for example, in define foo(); if condition then dlocal X = value; endif; enddefine; there is no sense in which X will be localised only if condition is true, although the assignment will done only in that case. Thus the above is identical to define foo(); dlocal X; if condition then value -> X; endif; enddefine; A dlocal declaration may also be used outside of a procedure, to make the variable or expression local to the current compilation stream, e.g. the current file. (Used in this way, the expression actually becomes local to the current call of the Poplog VM procedure * sysCOMPILE). ----------------------------------------------------------------------- 2 Example Formats ----------------------------------------------------------------------- Individual dlocal declarations may be separated by commas, thus: dlocal var1, active_var1, ;;; ordinary or active identifiers var2 = expression, ;;; initialise var2 with nonactive active_var2, ;;; nonactive value of active identifier active_var3=(3,4,5), ;;; does: 3,4,5 -> active_var3 %expr%, ;;; expression to be saved and restored %expr1% = expr2, ;;; expr1 initialised (after saving) 3 %expr%, ;;; expression multiplicity 3 2 %expr% = (e1,e2), ;;; does: e1,e2 -> expr on entry 3 %expr1,expr2%, ;;; different expression run on exit 0 %, expr2%; ;;; only an exit action Note that when only one expression is given between % ..... % then the updater of its main procedure or operator is run when the procedure exits. So dlocal 3 %f(x,g(y))% = (a,b,c); Means 1. on entry save the three results of 'f(x,g(y))' 2. do: a,b,c, -> f(x,g(y)) 3. On exit do: -> f(x,g(y)) ----------------------------------------------------------------------- 3 An Example Using a dlocal Expression ----------------------------------------------------------------------- The procedure test below saves and restores the value of the expression 'hd(tl(list))', where list is a global identifier. vars list =[1 2 3]; define test(); dlocal %hd(tl(list))%; [original list ^list]=> 5 -> hd(tl(list)); [updated list ^list] => enddefine; test(); ** [original list [1 2 3]] ** [updated list [1 5 3]] But the value of hd(tl(list)) has been restored on exit: list => ** [1 2 3] It would also be restored if the procedure call terminated abnormally, e.g. as a result of a mishap or call of interrupt. ----------------------------------------------------------------------- 4 Multiplicity ----------------------------------------------------------------------- Dynamic local expressions have a multiplicity specifying how many values are to be saved on entry or restored on exit. In the case of an expression the multiplicity M may be specified explicitly dlocal M %expression%; (where M is 0 - 255), or defaults to 1 if not specified. For an identifier, the multiplicity is always known to the compiler (1 for a ordinary nonactive variable, or M for an active variable of multiplicity M). If the procedure to be run on exit is not the updater of that to be run on entry, then it may occur after a comma between % ... %. If nothing precedes the comma then there will be no values saved on entry, and only the exit action will be run on exit. ----------------------------------------------------------------------- 5 Example of an Exit Action ----------------------------------------------------------------------- define test(); dlocal 0 %,('leaving test'=>)%; 'in test' => interrupt(); 'still in test' => ;;; never executed enddefine; test(); ** in test ** leaving test ----------------------------------------------------------------------- 6 vars vs dlocal ----------------------------------------------------------------------- Prior to the introduction of dlocal, only permanent variables (i.e. those declared with vars) could be made dynamically local to a procedure, and this had to be done with a vars statement inside the procedure. However, this was unsatisfactory inasmuch as a vars statement, being committed to declaring a permanent variable with the identprops supplied (in case it is not already declared) may alter the identprops of a previous permanent declaration. (In other words, vars statements inside procedures have the appearance of making the identprops of variables local to the procedure, when in fact they do not.) dlocal however enables both permanent and lexical variables to be made dynamically local to a procedure (without any redeclaration), and thus means that the use of vars statements inside procedures can be avoided altogether; all permanent variables can be declared outside of procedures, and then made procedure-local with dlocal, e.g. vars v1, v2; define p(); dlocal v1, v2; ... enddefine; Note that this way of doing things makes permanent and lexical variables declared at top level in a file interchangeable, except insofar as the lexical variables can be accessed only in (a) the file in which they are declared, or (b) a file spliced into the current compilation stream using #_INCLUDE. (See REF * PROGLIST/#_INCLUDE). I.e Pop-11 supports what are sometimes called "file-local" lexical scoping. ----------------------------------------------------------------------- 7 File-local Lexicals ----------------------------------------------------------------------- So the above example could just as well have used 'lvars v1, v2;' instead of 'vars v1, v2;'. lvars v1, v2; define p(); dlocal v1, v2; ... enddefine; No procedure invoked by p would then be able to access v1 or v2 unless declared in the same file, or in a file spliced in using #_INCLUDE. See HELP * LEXICAL. ----------------------------------------------------------------------- 8 dlocal_context: Controlling Entry and Exit Actions ----------------------------------------------------------------------- Besides normal entry and exit, a procedure's environment may be left because of a call of * chain or another procedure that "chains" out of one or more procedures, e.g. * exitfrom, * exitto, * chainfrom, * chainto, * throw. A procedure can also be left because a process is suspended, as explained in HELP * PROCESSES. The environment can also be re-entered when a process is resume-ed. (See HELP * RESUME.) In order to distinguish all these cases, the active variable dlocal_context is provided. It has an integer value, the integer defining the context. Moreover the active variable dlocal_process points to the process undergoing suspension or resumption, if there is one. Here are the contexts that can be indicated by the value of dlocal_context value context applies to ----- ------- ---------- 1 normal entry access normal exit update 2 abnormal exit update 3 suspend access update 4 resume access update In contexts of type 1, i.e. normal procedure entry, the access code to save the value of a dlocal expression is planted BEFORE any instructions to initialise formal parameters from the stack. This can cause problems discussed at the end of this file. In the case of suspend and resume, the current process record is also available to the code from the active variable dlocal_process. The value this returns is defined only for contexts 3 and 4, and is UNDEFINED for the other two. So procedures specified by dlocal to be run on entry or exit from a procedure can use dlocal_context and dlocal_process to determine what actions to perform. ----------------------------------------------------------------------- 9 Example using dlocal_context ----------------------------------------------------------------------- define foo(...); define lconstant Inout_check(context, process); lvars context, process; if context == 1 then ... actions for normal entry .... elseif context == 3 then ... access actions for suspending a process ... elseif context == 4 then ... access actions for running or resuming a process ... endif enddefine; define updaterof Inout_check(context, process); lvars context, process; if context == 2 then ... update actions for abnormal exit (e.g. chain)... elseif context == 3 then .... update actions for resuming a process ... endif enddefine; dlocal 0 % Inout_check(dlocal_context, dlocal_process) % ; .... body of foo .... enddefine; Note that the identifiers dlocal_context and dlocal_process cannot be used except in the context of a dlocal expression. This is why their values have to be passed in as parameters to the procedures. The Pop-11 trace facility uses this kind of mechanism to give different trace printing in different contexts. For further details see REF * VMCODE/dlocal_context. The remainder of this file describes a complication that can occur when dlocal expressions are used. ----------------------------------------------------------------------- 10 Do Not use Initialised Local Variables in dlocal Expressions ----------------------------------------------------------------------- In order to understand this section, users must know how formal parameters in Pop-11 procedures get their values from the user stack, as explained in TEACH * STACK. Because of the sequence in which code corresponding to dlocal expressions and input variable initialisation is executed, it is possible to produce errors that can be hard to understand. The reason, as stated in REF * VMCODE, is that access code for dlocal expressions is run BEFORE popping procedure formal arguments from the stack (and update code is run AFTER pushing formal result variables). So the code that saves the current value of a dynamic local expression is run BEFORE any local variables are initialised, including any formal parameters. In consequence, the body of an dynamic local expression should not include any local variable initialised in the current procedure, including input variables. A failure to remember this is a common source of errors, as illustrated by the following procedure, which takes a list and some other item, and is supposed to save the contents of the head of the list on entry and restore it on exit. define test(list,item); lvars list, item; dlocal %hd(list)%; ;;; save hd(list) on entry, restore on exit item -> hd(list); list => enddefine; Unfortunately, the dlocal expression uses the local lexical variable "list", and this will not have been given a value at the time that the expression "hd(list)" is evaluated in order to save the value. As a result, there will be an error when the procedure is run. E.g. vars testlist = [a b c d]; test(testlist, "cat"); ;;; MISHAP - LIST NEEDED ;;; INVOLVING: 0 ;;; DOING : hd test compile I.e. the lexical variable "list" happens to have the value 0 when an attempt is made to apply hd to it. So an error results. It is possible to overcome this problem by using an extra level of procedure call, and embedding the dlocal declaration within the extra nested procedure, thus: define test(list,item); lvars list, item; define lconstant sub_test(); dlocal %hd(list)%; ;;; embedded dlocal declaration item -> hd(list); list => enddefine; sub_test(); enddefine; The embedded procedure is defined as "lconstant" for the reason explained in HELP * LEXICAL, namely to prevent unnecessary creation of temporary closures of the nested procedure, thereby avoiding unwanted garbage collections. The modified procedure can be tested thus: vars testlist = [a b c d]; testlist => ** [a b c d] test(testlist, "cat"); ** [cat b c d] ;;; Now check that the head of the list has been re-set testlist => ** [a b c d] The restriction on using the values of local variables does not apply to initialisation code. E.g. vars data = [a b c d]; define test2(item); lvars item; dlocal %hd(data)% = item; ;;; use item to change data data => enddefine; data => ** [a b c d] test2(99); ** [99 b c d] data => ** [a b c d] This works because the code for the initialisation instruction to set the value of hd(data), i.e. in effect the instruction: item -> hd(data) is run AFTER the procedure's input locals are popped off the stack. So the sequence is: 1. procedure entry 2. set up lexical locals with undefined values 3. save values of dlocal expressions (see note below) 4. get values for input parameters from stack 5. run dlocal initialisations, and user initialisations of local variables 6. run body of procedure 7. push values of output locals 8. restore saved values of dlocal expressions (see note below) NOTE: for use with processes the situation is more complex than this, as code has to be planted for saving and restoring dlocal values on suspending and resuming processes too. Full details are to be found in REF * VMCODE. The next section describes an alternative solution to the problem. ----------------------------------------------------------------------- 11 Use of dlocal_context to Avoid an Extra Procedure Call ----------------------------------------------------------------------- It is possible to use a dlocal expression to initalise the variables that would otherwise be used as input variables. However, this must be restricted to the case where dlocal_context == 1, and in access mode only. So the next procedure, instead of having "list" and "item" as formal parameters initialises them in a dlocal expression: define test3(/* list, item */) with_nargs 2; lvars list, item; dlocal 0 % if dlocal_context == 1 then -> (list, item) endif, % ; dlocal %hd(list)%; ;;; save hd(list) on entry, restore on exit item -> hd(list); list => enddefine; Notice the comma after "endif" implying that the conditional is run only in access mode, on entry. test3([1 2 3], 99); ** [99 2 3] Because the above looks rather clumsy it is possible to define a macro dlocal_args as follows, to conceal the complexity. define macro dlocal_args; ;;; Used to declare arguments that are initialised before other ;;; dlocal expressions are executed lvars list = [], item; ;;; read in the identifiers, and build a list until (readitem() ->> item) == ";" do unless item == "," then conspair(item,list) -> list endunless enduntil; ;;; construct the dlocal expression dl( #_< [dlocal 0 ^("%")if dlocal_context == 1 then] >_#); for w in list do "->", w, endfor; dl(#_< [endif, ^("%") ;] >_#); enddefine; Now test this new construct: define test4(/* list, item */) with_nargs 2; lvars list, item; dlocal_args list, item; dlocal %hd(list)%; ;;; save hd(list) on entry, restore on exit item -> hd(list); list => enddefine; test4([1 2 3], 999); ** [999 2 3] ----------------------------------------------------------------------- 12 Exit Actions to Tidy Up Variables ----------------------------------------------------------------------- A dlocal exit action may be used to 'tidy up' the value of a local variable that may or may not get set during the execution of a procedure. For example, suppose the procedure opens a file with sysopen, and you wish to ensure the file is closed whenever the procedure exits (either normally or abnormally). Assuming the device for the file is assigned to the variable dev, this can be done as follows: dlocal 0 % if dlocal_context == 1 then false -> dev endif, if dlocal_context <= 2 and dev then sysclose(dev) endif %; Here dev is initialised to false by the entry action on normal entry (i.e. dlocal_context == 1), and the exit action on normal or abnormal exit (i.e. dlocal_context <= 2) then uses sysclose only if dev has been set to a device. An essential element of the above is that the system guarantees that the exit action will not be run unless the entry action has been run (see REF * VMCODE). NOTE that you should always guard entry and exit actions like the above with tests for dlocal_context having the correct value. (That is, guard them against being performed inadvertently on process suspension and resumption. Although you may not be explicitly using processes in your program, the Ved editor makes heavy use of them, and your procedures may in fact be run as part of a process in some Ved contexts.) ----------------------------------------------------------------------- 13 Related Documentation ----------------------------------------------------------------------- HELP * VARS HELP * LVARS HELP * ACTIVE_VARIABLES (includes an example using dlocal) HELP * LEXICAL REF * dlocal_context REF * VMCODE/Dynamic REF * VMCODE/sysLOCAL HELP * TRACE REF * IDENT REF * POPSYNTAX --- C.all/help/dlocal --- Copyright University of Sussex 1990. All rights reserved.