From 438152be2bda0288e5d5e8c5b013077ac9512d86 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Fri, 21 Feb 2014 21:47:39 -0800 Subject: Implementing special variables with local overriding. All predefined globals become special. * eval.c (special, with_saved_vars_s): New global variables. (mark_special, special_p): New functions. (bindings_helper): Takes new argument, include_specials. Now processes the special colon syntax for denoting special variables, setting up their values, taking care to observe whether the binding is parallel or sequential. (op_let, op_for): Pass new argument to bindings_helper. (op_each): Pass new argument to bindings_helper with a value of it, and deal with the colon annotations that emerge in the bindings. (op_defvar, regvar): Mark symbol as special. (op_with_saved_vars): New static function. (expand_vars): Takes new argument, returns a cons. Detects special variables among the vars and produces the colon syntax. (expand_catch_clause): Bugfix: this was using expand_vars on a parameter list. Now properly uses expand_params. (expand_save_specials): New static function. (expand): For the operators that are binding constructs, handle the new form of expand_vars which returns information about special variables. If specials occur, then generate the with-saved-vars form around the expansion which will save and restore their values. The expansion of vars done by expand_vars, together with the run-time actions of bindings_helper, do the rest. Speaking of which, the new with-saved-vars operator form is now expanded here too. (eval_init): Protect new variables special and with_saved_vars_s. Initialize special with new hash table. Store new interned symbol in with_saved_vars_s. Register op_with_save_vars in op_table. * txr.1: Documented specials. --- ChangeLog | 35 +++++++++++ eval.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++------------- txr.1 | 75 +++++++++++++++++++---- 3 files changed, 259 insertions(+), 56 deletions(-) diff --git a/ChangeLog b/ChangeLog index 23d7836c..84b68cfc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,38 @@ +2014-02-21 Kaz Kylheku + + Implementing special variables with local overriding. + All predefined globals become special. + + * eval.c (special, with_saved_vars_s): New global variables. + (mark_special, special_p): New functions. + (bindings_helper): Takes new argument, include_specials. + Now processes the special colon syntax for denoting special variables, + setting up their values, taking care to observe whether the + binding is parallel or sequential. + (op_let, op_for): Pass new argument to bindings_helper. + (op_each): Pass new argument to bindings_helper with a value of it, + and deal with the colon annotations that emerge in the bindings. + (op_defvar, regvar): Mark symbol as special. + (op_with_saved_vars): New static function. + (expand_vars): Takes new argument, returns a cons. Detects special + variables among the vars and produces the colon syntax. + (expand_catch_clause): Bugfix: this was using expand_vars on + a parameter list. Now properly uses expand_params. + (expand_save_specials): New static function. + (expand): For the operators that are binding constructs, handle + the new form of expand_vars which returns information about + special variables. If specials occur, then generate the + with-saved-vars form around the expansion which will save and restore + their values. The expansion of vars done by expand_vars, together + with the run-time actions of bindings_helper, do the rest. + Speaking of which, the new with-saved-vars operator form is now + expanded here too. + (eval_init): Protect new variables special and with_saved_vars_s. + Initialize special with new hash table. Store new interned + symbol in with_saved_vars_s. Register op_with_save_vars in op_table. + + * txr.1: Documented specials. + 2014-02-21 Kaz Kylheku * eval.c (subst_vars): Change throwing of query_error_s diff --git a/eval.c b/eval.c index 7abf8911..d293feab 100644 --- a/eval.c +++ b/eval.c @@ -71,7 +71,7 @@ struct c_var { val bind; }; -val top_vb, top_fb, top_mb; +val top_vb, top_fb, top_mb, special; val op_table; val eval_error_s; @@ -88,6 +88,7 @@ val delay_s, promise_s, op_s; val hash_lit_s, hash_construct_s; val vector_lit_s, vector_list_s; val macro_time_s; +val with_saved_vars_s; val whole_k, env_k; @@ -224,6 +225,16 @@ static val lookup_sym_lisp1(val env, val sym) } } +static void mark_special(val sym) +{ + sethash(special, sym, t); +} + +static val special_p(val sym) +{ + return gethash(special, sym); +} + static val bind_args(val env, val params, val args, val ctx_form) { val new_env = make_env(nil, nil, env); @@ -835,37 +846,55 @@ static val op_unquote_error(val form, val env) } -static val bindings_helper(val vars, val env, val sequential, val ctx_form) +static val bindings_helper(val vars, val env, val sequential, + val include_specials, val ctx_form) { val iter; list_collect_decl (new_bindings, ptail); val nenv = if3(sequential, make_env(nil, nil, env), env); + val spec_val[32], *spec_loc[32]; + int speci = 0; for (iter = vars; iter; iter = cdr(iter)) { val item = car(iter); - val var, val = nil; + val var, value = nil; if (consp(item)) { - if (!consp(cdr(item))) - eval_error(ctx_form, lit("~s: invalid syntax: ~s"), - car(ctx_form), item, nao); - var = first(item); - val = eval(second(item), nenv, ctx_form); + var = pop(&item); + value = eval(pop(&item), nenv, ctx_form); } else { var = item; } - if (symbolp(var)) { - if (!bindable(var)) + if (!bindable(var)) { + val special = car(item); + val *loc = lookup_var_l(nil, special); + if (var != colon_k) eval_error(ctx_form, lit("~s: ~s is not a bindable symbol"), car(ctx_form), var, nao); - } - - ptail = list_collect (ptail, cons(var, val)); + if (!loc) + eval_error(ctx_form, lit("~s: cannot rebind variable ~s: not found"), + car(ctx_form), special, nao); + if (sequential) { + *loc = value; + } else if (speci < 32) { + spec_val[speci] = value; + spec_loc[speci++] = loc; + } else { + eval_error(ctx_form, lit("~s: too many special variables rebound"), + car(ctx_form), nao); + } + if (include_specials) + ptail = list_collect (ptail, cons(colon_k, var)); + } else { + ptail = list_collect (ptail, cons(var, value)); - if (sequential) - env_replace_vbind(nenv, new_bindings); + if (sequential) + env_replace_vbind(nenv, new_bindings); + } } + while (speci-- > 0) + *spec_loc[speci] = spec_val[speci]; return new_bindings; } @@ -885,7 +914,7 @@ static val op_let(val form, val env) val args = rest(form); val vars = first(args); val body = rest(args); - val new_bindings = bindings_helper(vars, env, eq(let, let_star_s), form); + val new_bindings = bindings_helper(vars, env, eq(let, let_star_s), nil, form); return eval_progn(body, make_env(new_bindings, nil, env), form); } @@ -901,7 +930,7 @@ static val op_each(val form, val env) eq(each, append_each_star_s)); val collect = or2(eq(each, collect_each_s), eq(each, collect_each_star_s)); val append = or2(eq(each, append_each_s), eq(each, append_each_star_s)); - val new_bindings = bindings_helper(vars, env, star, form); + val new_bindings = bindings_helper(vars, env, star, t, form); val lists = mapcar(cdr_f, new_bindings); list_collect_decl (collection, ptail); @@ -915,9 +944,18 @@ static val op_each(val form, val env) { val binding = car(biter); val list = car(liter); + val sym = car(binding); if (!list) goto out; - rplacd(binding, car(list)); + if (sym == colon_k) { + val *loc = lookup_var_l(nil, cdr(binding)); + if (!loc) + eval_error(form, lit("~s: nonexistent special var ~a"), + car(form), sym); + *loc = car(list); + } else { + rplacd(binding, car(list)); + } rplaca(liter, cdr(list)); } @@ -1024,6 +1062,7 @@ static val op_defvar(val form, val env) val value = eval(second(args), env, form); sethash(top_vb, sym, cons(sym, value)); } + mark_special(sym); } return sym; @@ -1431,7 +1470,8 @@ static val op_for(val form, val env) val cond = third(form); val incs = fourth(form); val forms = rest(rest(rest(rest(form)))); - val new_bindings = bindings_helper(vars, env, eq(forsym, for_star_s), form); + val new_bindings = bindings_helper(vars, env, eq(forsym, for_star_s), + nil, form); val new_env = make_env(new_bindings, nil, env); uw_block_begin (nil, result); @@ -1652,6 +1692,39 @@ static val op_quasi_lit(val form, val env) return cat_str(subst_vars(rest(form), env), nil); } +static val op_with_saved_vars(val form, val env) +{ + val vars = (pop(&form), pop(&form)); + val prot_form = pop(&form); + val result = nil; + val var_save[32], *var_loc[32]; + int n; + + uw_simple_catch_begin; + + for (n = 0; n < 32 && vars; n++, vars = cdr(vars)) { + val sym = car(vars); + val *loc = lookup_var_l(nil, sym); + if (!loc) { + eval_error(form, lit("~s: cannot save value of " + "nonexistent var ~a"), car(form), sym, nao); + } + var_loc[n] = loc; + var_save[n] = *loc; + } + + result = eval(prot_form, env, prot_form); + + uw_unwind { + while (n-- > 0) + *var_loc[n] = var_save[n]; + } + + uw_catch_end; + + return result; +} + val expand_forms(val form) { if (atom(form)) { @@ -1791,26 +1864,42 @@ static val expand_qquote(val qquoted_form) abort(); } -static val expand_vars(val vars) +static val expand_vars(val vars, val specials) { + val sym; + if (atom(vars)) { return vars; - } else if (symbolp(car(vars))) { + } else if (special_p(sym = car(vars))) { + val rest_vars = rest(vars); + cons_bind (rest_vars_ex, new_specials, + rlcp(expand_vars(rest_vars, specials), rest_vars)); + val ret_specials = cons(sym, new_specials); + val var_ex = cons(colon_k, cons(nil, cons(sym, nil))); + return cons(rlcp(cons(var_ex, rest_vars_ex), vars), ret_specials); + } else if (symbolp(sym)) { val rest_vars = rest(vars); - val rest_vars_ex = expand_vars(rest_vars); + cons_bind (rest_vars_ex, new_specials, expand_vars(rest_vars, specials)); if (rest_vars == rest_vars_ex) - return vars; - return rlcp(cons(car(vars), rest_vars_ex), vars); + return cons(vars, new_specials); + return cons(rlcp(cons(sym, rest_vars_ex), vars), new_specials); } else { - cons_bind (var, init, car(vars)); + cons_bind (var, init, sym); val rest_vars = rest(vars); val init_ex = rlcp(expand_forms(init), init); - val rest_vars_ex = rlcp(expand_vars(rest_vars), rest_vars); + cons_bind (rest_vars_ex, new_specials, + rlcp(expand_vars(rest_vars, specials), rest_vars)); - if (init == init_ex && rest_vars == rest_vars_ex) - return vars; - - return rlcp(cons(cons(var, init_ex), rest_vars_ex), vars); + if (special_p(var)) { + val ret_specials = cons(var, new_specials); + val var_ex = cons(colon_k, cons(car(init_ex), cons(var, nil))); + return cons(rlcp(cons(var_ex, rest_vars_ex), vars), ret_specials); + } else { + if (init == init_ex && rest_vars == rest_vars_ex) + return cons(vars, new_specials); + return cons(rlcp(cons(cons(var, init_ex), rest_vars_ex), vars), + new_specials); + } } } @@ -1992,13 +2081,13 @@ static val expand_op(val sym, val body) static val expand_catch_clause(val form) { val sym = first(form); - val vars = second(form); + val params = second(form); val body = rest(rest(form)); - val vars_ex = expand_vars(vars); + val params_ex = expand_params(params); val body_ex = expand_forms(body); - if (body == body_ex && vars == vars_ex) + if (body == body_ex && params == params_ex) return form; - return rlcp(cons(sym, cons(vars_ex, body_ex)), form); + return rlcp(cons(sym, cons(params_ex, body_ex)), form); } static val expand_catch(val body) @@ -2017,6 +2106,13 @@ static val expand_catch(val body) return rlcp(expanded, body); } +static val expand_save_specials(val form, val specials) +{ + if (!specials) + return form; + return rlcp(cons(with_saved_vars_s, cons(specials, cons(form, nil))), form); +} + val expand(val form) { val macro = nil; @@ -2035,10 +2131,13 @@ tail: val body = rest(rest(form)); val vars = second(form); val body_ex = expand_forms(body); - val vars_ex = expand_vars(vars); - if (body == body_ex && vars == vars_ex) + cons_bind (vars_ex, specials, expand_vars(vars, nil)); + if (body == body_ex && vars == vars_ex && !specials) { return form; - return rlcp(cons(sym, cons(vars_ex, body_ex)), form); + } else { + val basic_form = rlcp(cons(sym, cons(vars_ex, body_ex)), form); + return expand_save_specials(basic_form, specials); + } } else if (sym == block_s || sym == return_from_s) { val name = second(form); val body = rest(rest(form)); @@ -2134,17 +2233,21 @@ tail: val cond = third(form); val incs = fourth(form); val forms = rest(rest(rest(rest(form)))); - val vars_ex = expand_vars(vars); + cons_bind (vars_ex, specials, expand_vars(vars, nil)); val cond_ex = expand_forms(cond); val incs_ex = expand_forms(incs); val forms_ex = expand_forms(forms); if (vars == vars_ex && cond == cond_ex && - incs == incs_ex && forms == forms_ex) + incs == incs_ex && forms == forms_ex && !specials) { return form; - return rlcp(cons(sym, - cons(vars_ex, - cons(cond_ex, cons(incs_ex, forms_ex)))), form); + } else { + val basic_form = rlcp(cons(sym, + cons(vars_ex, + cons(cond_ex, + cons(incs_ex, forms_ex)))), form); + return expand_save_specials(basic_form, specials); + } } else if (sym == dohash_s) { val spec = second(form); val keysym = first(spec); @@ -2186,6 +2289,17 @@ tail: val args_ex = expand_forms(args); val result = eval_progn(args_ex, make_env(nil, nil, nil), args); return cons(quote_s, cons(result, nil)); + } else if (sym == with_saved_vars_s) { + /* We should never have to expand a machine-generated with-saved-vars + * produced by the expander itself. This is for the sake of someone + * testing with-saved-vars in isolation. + */ + val vars = first(form); + val expr = second(form); + val expr_ex = expand(expr); + if (expr == expr_ex) + return form; + return cons(vars, cons(expr_ex, nil)); } else if ((macro = gethash(top_mb, sym))) { val mac_expand = expand_macro(form, macro, make_env(nil, nil, nil)); if (mac_expand == form) @@ -2624,6 +2738,7 @@ static void reg_var(val sym, val *loc) cv->loc = loc; cv->bind = cons(sym, *loc); sethash(top_vb, sym, cobj((mem_t *) cv, cptr_s, &c_var_ops)); + mark_special(sym); } static val if_fun(val cond, val then, val alt) @@ -2656,10 +2771,12 @@ static val and_fun(val vals) void eval_init(void) { - protect(&top_vb, &top_fb, &top_mb, &op_table, &last_form_evaled, (val *) 0); + protect(&top_vb, &top_fb, &top_mb, &special, + &op_table, &last_form_evaled, (val *) 0); top_fb = make_hash(t, nil, nil); top_vb = make_hash(t, nil, nil); top_mb = make_hash(t, nil, nil); + special = make_hash(t, nil, nil); op_table = make_hash(nil, nil, nil); dwim_s = intern(lit("dwim"), user_package); @@ -2712,6 +2829,7 @@ void eval_init(void) vector_lit_s = intern(lit("vector-lit"), system_package); vector_list_s = intern(lit("vector-list"), user_package); macro_time_s = intern(lit("macro-time"), user_package); + with_saved_vars_s = intern(lit("with-saved-vars"), system_package); whole_k = intern(lit("whole"), keyword_package); sethash(op_table, quote_s, cptr((mem_t *) op_quote)); @@ -2757,6 +2875,7 @@ void eval_init(void) sethash(op_table, dwim_s, cptr((mem_t *) op_dwim)); sethash(op_table, quasi_s, cptr((mem_t *) op_quasi_lit)); sethash(op_table, catch_s, cptr((mem_t *) op_catch)); + sethash(op_table, with_saved_vars_s, cptr((mem_t *) op_with_saved_vars)); reg_fun(cons_s, func_n2(cons)); reg_fun(intern(lit("make-lazy-cons"), user_package), func_n1(make_lazy_cons)); diff --git a/txr.1 b/txr.1 index e5a4ed99..f5685248 100644 --- a/txr.1 +++ b/txr.1 @@ -5154,6 +5154,50 @@ Here, the shorthand 1 .. 3 denotes (cons 1 3). This is treated just like (call '(1 2 3 4) 1 3), which performs range extraction: taking a slice of the list starting at index 1, up to and not including index 3. +.SS Special Variables + +Similarly to Common Lisp, TXR Lisp is lexically scoped by default, but +also has dynamically scoped (a.k.a "special") variables. + +When a variable is defined with defvar, it is introduced as a top-level +(global) binding, regardless of where in the scope the defvar form occurs. + +Furthermore, at the time the defvar form is evaluated, the symbol which +names the variable is tagged as special. + +When a symbol is tagged as special, it behaves differently when it is used +in a lexical binding construct like let, and all other such constructs +such as function parameter lists. Such a binding is not the usual lexical +binding, but a "rebinding" of the global variable. Over the dynamic scope +of the form, the global variable takes on the value given to it by the +rebinding. When the form terminates, the prior value of the variable +is restored. (This is true no matter how the form terminates; even if by +an exception.) + +Because of this "pervasive special" behavior of a symbol that has been +used as the name of a global variable, a good practice is to make global +variables have visually distinct names via the "earmuffs" convention: +beginning and ending the name with an asterisk. + +Certain variables in TXR's library break this convention; however, they at +least have distinct prefixes, examples being example s-ifmt, log-emerg and +sig-hup. + +.TP +Example: + + (defvar *x* 42) ;; *x* has a value of 42 + + (defun print-x () + (format t "~a\en" *x*)) + + (let ((*x* "abc")) ;; this overrides *x* + (print-x)) ;; *x* is now "abc" and so that is printed + + (print-x) ;; *x* is 42 again and so "42" is printed + + + .SH CONTROL FLOW AND SEQUENCING When the first element of a compound expression is an operator symbol, @@ -5807,6 +5851,11 @@ in which the defvar form occurs, not necessarily in the top-level environment. The symbols t and nil may not be used as variables, and neither can be keyword symbols: symbols denoted by a leading colon. +In addition to creating a binding, the defvar operator also marks +as the name of a special variable. This changes what it means to bind +that symbol in a lexical binding construct such as the let operator, +or a function parameter list. See the section "Special Variables" far above. + .SS Operators let and let* .TP @@ -10918,7 +10967,7 @@ In general, I/O errors are usually turned into exceptions. When the description of error reporting is omitted from the description of a function, it can be assumed that it throws an error. -.SS Variables *stdout*, *stddebug*, *stdin*, *stderr* and *stdnull* +.SS Special variables *stdout*, *stddebug*, *stdin*, *stderr* and *stdnull* These variables hold predefined stream objects. The *stdin*, *stdout* and *stderr* streams closely correspond to the underlying operating system streams. @@ -11558,7 +11607,7 @@ These properties correspond to the similarly-named entires of the struct stat structure in POSIX. For instance, the :dev property has the same value as the st_dev field. -.SS The variables s-ifmt s-iflnk s-ifreg s-ifblk ... s-ixoth +.SS Special variables s-ifmt s-iflnk s-ifreg s-ifblk ... s-ixoth The following variables exist, having integer values. These are bitmasks which can be applied against the value given by the :mode property @@ -11722,7 +11771,7 @@ name. The find-package function performs this lookup. A package may be deleted from the list with the delete-package function, but it continues to exist until the program loses the last reference to that package. -.SS Variables *user-package*, *keyword-package*, *system-package* +.SS Special variables *user-package*, *keyword-package*, *system-package* These variables hold predefined packages. The *user-package* is the one in which symbols are read when a TXR program is being scanned. @@ -11769,7 +11818,7 @@ Note: the variation in name is not the basis of the uniqueness of gensym; the basis of its uniqueness is that it is a freshly instantiated object. make-sym also returns unique symbols even if repeatedly called with the same string. -.SS Variable *gensym-counter* +.SS Special variable *gensym-counter* This variable is initialized to 0. Each time the gensym function is called, it is incremented. The incremented value forms the basis of the numeric @@ -11919,7 +11968,7 @@ returns nil. .SH PSEUDO-RANDOM NUMBERS -.SS Variable *random-state* +.SS Special variable *random-state* The *random-state* variable holds an object which encapsulates the state of a pseudo-random number generator. This variable is the default argument for @@ -12133,7 +12182,7 @@ savings time). .SH ENVIRONMENT VARIABLES AND COMMAND LINE -.SS Variables *args* and *args-full* +.SS Special variables *args* and *args-full* The *args* variable holds a list of strings representing the remaining arguments which follow any options processed by the txr executable, and the @@ -12243,7 +12292,7 @@ Additionally, the sig-check function can be used to dispatch and clear deferred signals. These handlers are then safely called if they were subroutines of sig-check, and not asynchronous interrupts. -.SS Variables sig-hup, sig-int, sig-quit, sig-ill, sig-trap, sig-abrt, sig-bus, sig-fpe, sig-kill, sig-usr1, sig-segv, sig-usr2, sig-pipe, sig-alrm, sig-term, sig-chld, sig-cont, sig-stop, sig-tstp, sig-ttin, sig-ttou, sig-urg, sig-xcpu, sig-xfsz, sig-vtalrm, sig-prof, sig-poll, sig-sys, sig-winch, sig-iot, sig-stkflt, sig-io, sig-lost and sig-pwr +.SS Special variables sig-hup, sig-int, sig-quit, sig-ill, sig-trap, sig-abrt, sig-bus, sig-fpe, sig-kill, sig-usr1, sig-segv, sig-usr2, sig-pipe, sig-alrm, sig-term, sig-chld, sig-cont, sig-stop, sig-tstp, sig-ttin, sig-ttou, sig-urg, sig-xcpu, sig-xfsz, sig-vtalrm, sig-prof, sig-poll, sig-sys, sig-winch, sig-iot, sig-stkflt, sig-io, sig-lost and sig-pwr .TP Description: @@ -12349,7 +12398,7 @@ interface. TXR programs can configure logging via the openlog function, control the loging mask via setlogmask and generate logs vis syslog, or using special syslog streams. -.SS Variables log-pid, log-cons, log-ndelay, log-odelay, log-nowait and log-perror +.SS Special variables log-pid, log-cons, log-ndelay, log-odelay, log-nowait and log-perror These variables take on the values of the corresponding C preprocessor constants from the header: LOG_PID, LOG_CONS, etc. These @@ -12359,7 +12408,7 @@ openlog function. Note: LOG_PERROR is not in POSIX, and so log-perror might not be available. See notes about LOG_AUTHPRIV in the next section. -.SS Variables log-user, log-daemon, log-auth and log-authpriv +.SS Special variables log-user, log-daemon, log-auth and log-authpriv These variables take on the values of the corresponding C preprocessor constants from the header: LOG_USER, LOG_DAEMON, LOG_AUTH @@ -12371,14 +12420,14 @@ For portability use code like (of (symbol-value 'log-authpriv) 0) to evaluate to 0 if log-authpriv doesn't exist, or else check for its existence using (boundp 'log-authpriv). -.SS Variables log-emerg, log-alert, log-crit, log-err, log-warning, log-notice, log-info and log-debug +.SS Special variables log-emerg, log-alert, log-crit, log-err, log-warning, log-notice, log-info and log-debug These variables take on the values of the corresponding C preprocessor constants from the header: LOG_EMERG, LOG_ALERT, etc. These are the integer priority codes specified in the syslog call. -.SS The *stdlog* variable holds a special kind of stream: a syslog stream. -Each newline-terminated line of text sent to this stream becomes a log +.SS The *stdlog* special variable holds a special kind of stream: a syslog +stream. Each newline-terminated line of text sent to this stream becomes a log message. The stream internally maintains a priority value that is applied @@ -12891,7 +12940,7 @@ to the original untransformed source code. .SH MODULARIZATION -.SS Variable *self-path* +.SS Special variable *self-path* This variable holds the invocation path name of the TXR program. -- cgit v1.2.3