From unknown Wed Jun 18 22:56:33 2025 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Mailer: MIME-tools 5.509 (Entity 5.509) Content-Type: text/plain; charset=utf-8 From: bug#24923 <24923@debbugs.gnu.org> To: bug#24923 <24923@debbugs.gnu.org> Subject: Status: Lisp watchpoints Reply-To: bug#24923 <24923@debbugs.gnu.org> Date: Thu, 19 Jun 2025 05:56:33 +0000 retitle 24923 Lisp watchpoints reassign 24923 emacs submitter 24923 npostavs@users.sourceforge.net severity 24923 wishlist tag 24923 patch fixed thanks From debbugs-submit-bounces@debbugs.gnu.org Thu Nov 10 22:10:30 2016 Received: (at submit) by debbugs.gnu.org; 11 Nov 2016 03:10:30 +0000 Received: from localhost ([127.0.0.1]:52903 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52EV-0000KJ-VM for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:10:30 -0500 Received: from eggs.gnu.org ([208.118.235.92]:53379) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52ER-0000K5-JA for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:10:25 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c52EF-0003hi-44 for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:10:18 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=0.8 required=5.0 tests=BAYES_50,FREEMAIL_FROM, T_DKIM_INVALID autolearn=disabled version=3.3.2 Received: from lists.gnu.org ([2001:4830:134:3::11]:50250) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1c52EE-0003hS-UQ for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:10:11 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:44253) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c52E6-00063V-Up for bug-gnu-emacs@gnu.org; Thu, 10 Nov 2016 22:10:10 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c52Dz-0003bF-T2 for bug-gnu-emacs@gnu.org; Thu, 10 Nov 2016 22:10:02 -0500 Received: from mail-it0-x235.google.com ([2607:f8b0:4001:c0b::235]:38797) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1c52Dz-0003av-5U for bug-gnu-emacs@gnu.org; Thu, 10 Nov 2016 22:09:55 -0500 Received: by mail-it0-x235.google.com with SMTP id q124so89571993itd.1 for ; Thu, 10 Nov 2016 19:09:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:subject:date:message-id:mime-version; bh=TG+cbIbBe0iJKluhGPR8D47h4PqLBtP/vTVCWCSSqkA=; b=ZYITA2HrG81XZ2DcjMohC8E9a9FDuWT4+qZ6it+2KRrg/ii+9cAhMIZ7a52EQFgHog lfakSIsTF+3ids4E1MB05wBxn97vAusINCbWX8CFnN9gLVJdx+4PPHZ/8MS2bj17KBHr 83aUupp+pcH00P5Ty0KILQvXYOgXiUzPM5wmwf2aKoCi6LvPZ4hggIOr5xwH61tbYdJy A682eLLU8vGZurqZlB37OEbhI31C9mGHn7QF6lL8UslKf0xfaK8rW2vItviL2OrnDWVq HzFY3N4OprRFVi0/qjl7rhug+F9RPSOt8exnTIcBB9pKWmbvohk584ryT+ew/Nv7jYOY /Nzw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:subject:date:message-id :mime-version; bh=TG+cbIbBe0iJKluhGPR8D47h4PqLBtP/vTVCWCSSqkA=; b=UzAoGZ66KAgoTrvrz+oCj811443rSUNw/8BTP8+FForORdejbrr97acvoA4u6xfO03 QL6YsB+Ab2UxYddMX0EgecZ/7oxe4AcoQZLTCU5nSbngTGjE6f3zO1wjW+1vXjd9mkjh +RzJEHyzjz/TBxeIPjrnH7OAV7JcDtuXkx8XEhMQ72nWaNwYL8WDqPczcsNnqrJzp5Oj yTbmP+OXXasPeNSlQ6RapV0ON/rxd6fqEmEvs/p3QTx3dxMfF+UoeBdxoJYQ2zGdg9wb efVqD6OMuM+c5qXOz+JpZ7glDPh+ErX8Me8G9AzyucvM/AJEld+WVxLbEmfoWy26Vqlc n1Og== X-Gm-Message-State: ABUngvc4n2JB4m4AKVE76Ij6a7o7Nb+Aa60lR91F5Ds9l6f9h7Br/dW8tvmbPbYqrudyTA== X-Received: by 10.36.6.5 with SMTP id 5mr19468080itv.116.1478833793365; Thu, 10 Nov 2016 19:09:53 -0800 (PST) Received: from zony ([45.2.7.130]) by smtp.googlemail.com with ESMTPSA id p20sm8467562itc.8.2016.11.10.19.09.51 for (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 10 Nov 2016 19:09:52 -0800 (PST) From: npostavs@users.sourceforge.net To: bug-gnu-emacs@gnu.org Subject: 25.1; Lisp watchpoints Date: Thu, 10 Nov 2016 22:10:38 -0500 Message-ID: <87vavun235.fsf@users.sourceforge.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-Received-From: 2001:4830:134:3::11 X-Spam-Score: -4.0 (----) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -4.0 (----) --=-=-= Content-Type: text/plain severity: wishlist tags: patch Continuing from https://lists.nongnu.org/archive/html/emacs-devel/2015-11/msg01437.html, main code difference since then is that I use a subr instead of indexing into a table of C functions (the reason for using an index was to avoid GC on Ffuncall; instead I did that by checking if the function is SUBRP and doing funcall_subr on it (a new function extracted from Ffuncall)). I've also added tests, which turned up some interesting corner cases regarding aliases, and also that I had completely missed kill-local-variables. I still need to write something up in the manual. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0001-Add-lisp-watchpoints.patch >From 0507854b885681c2e57bb1c6cb97f898417bd99c Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 19 Nov 2015 19:50:06 -0500 Subject: [PATCH v4 1/5] Add lisp watchpoints This allows to call a function whenever a symbol-value is changed. * src/lisp.h (lisp_h_SYMBOL_TRAPPED_WRITE_P): Rename from lisp_h_SYMBOL_CONSTANT_P. (SYMBOL_TRAPPED_WRITE_P): Rename from SYMBOL_CONSTANT_P. (enum symbol_trapped_write): New enumeration. (struct Lisp_Symbol): Rename field constant to trapped_write. (make_symbol_constant): New function. * src/data.c (Fadd_variable_watcher, Fremove_variable_watcher): (set_symbol_trapped_write, reset_symbol_trapped_write): (harmonize_variable_watchers, notify_variable_watchers): New functions. * src/data.c (Fset_default): Call `notify_variable_watchers' for trapped symbols. (set_internal): Change bool argument BIND to 3-value enum and call `notify_variable_watchers' for trapped symbols. * src/data.c (syms_of_data): * src/data.c (syms_of_data): * src/font.c (syms_of_font): * src/lread.c (intern_sym, init_obarray): * src/buffer.c (syms_of_buffer): Use make_symbol_constant. * src/alloc.c (init_symbol): * src/bytecode.c (exec_byte_code): Use SYMBOL_TRAPPED_WRITE_P. * src/data.c (Fmake_variable_buffer_local, Fmake_local_variable): (Fmake_variable_frame_local): * src/eval.c (Fdefvaralias, specbind): Refer to Lisp_Symbol's trapped_write instead of constant. (Ffuncall): Move subr calling code into separate function. (funcall_subr): New function. --- src/alloc.c | 2 +- src/buffer.c | 82 +++++++++++++---------- src/bytecode.c | 4 +- src/data.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++------- src/eval.c | 202 ++++++++++++++++++++++++++++++--------------------------- src/font.c | 6 +- src/lisp.h | 47 ++++++++++---- src/lread.c | 6 +- 8 files changed, 365 insertions(+), 176 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index a58dc13..f373f6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3562,7 +3562,7 @@ init_symbol (Lisp_Object val, Lisp_Object name) set_symbol_next (val, NULL); p->gcmarkbit = false; p->interned = SYMBOL_UNINTERNED; - p->constant = 0; + p->trapped_write = SYMBOL_UNTRAPPED_WRITE; p->declared_special = false; p->pinned = false; } diff --git a/src/buffer.c b/src/buffer.c index 3d205bb..cc75cdb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -984,40 +984,54 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, Qnil); else { - Lisp_Object tmp, prop, last = Qnil; + Lisp_Object tmp, last = Qnil; for (tmp = BVAR (b, local_var_alist); CONSP (tmp); tmp = XCDR (tmp)) - if (!NILP (prop = Fget (XCAR (XCAR (tmp)), Qpermanent_local))) - { - /* If permanent-local, keep it. */ - last = tmp; - if (EQ (prop, Qpermanent_local_hook)) - { - /* This is a partially permanent hook variable. - Preserve only the elements that want to be preserved. */ - Lisp_Object list, newlist; - list = XCDR (XCAR (tmp)); - if (!CONSP (list)) - newlist = list; - else - for (newlist = Qnil; CONSP (list); list = XCDR (list)) - { - Lisp_Object elt = XCAR (list); - /* Preserve element ELT if it's t, - if it is a function with a `permanent-local-hook' property, - or if it's not a symbol. */ - if (! SYMBOLP (elt) - || EQ (elt, Qt) - || !NILP (Fget (elt, Qpermanent_local_hook))) - newlist = Fcons (elt, newlist); - } - XSETCDR (XCAR (tmp), Fnreverse (newlist)); - } - } - /* Delete this local variable. */ - else if (NILP (last)) - bset_local_var_alist (b, XCDR (tmp)); - else - XSETCDR (last, XCDR (tmp)); + { + Lisp_Object local_var = XCAR (XCAR (tmp)); + Lisp_Object prop = Fget (local_var, Qpermanent_local); + + if (!NILP (prop)) + { + /* If permanent-local, keep it. */ + last = tmp; + if (EQ (prop, Qpermanent_local_hook)) + { + /* This is a partially permanent hook variable. + Preserve only the elements that want to be preserved. */ + Lisp_Object list, newlist; + list = XCDR (XCAR (tmp)); + if (!CONSP (list)) + newlist = list; + else + for (newlist = Qnil; CONSP (list); list = XCDR (list)) + { + Lisp_Object elt = XCAR (list); + /* Preserve element ELT if it's t, + if it is a function with a `permanent-local-hook' property, + or if it's not a symbol. */ + if (! SYMBOLP (elt) + || EQ (elt, Qt) + || !NILP (Fget (elt, Qpermanent_local_hook))) + newlist = Fcons (elt, newlist); + } + newlist = Fnreverse (newlist); + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, newlist, + Qmakunbound, Fcurrent_buffer ()); + XSETCDR (XCAR (tmp), newlist); + continue; /* Don't do variable write trapping twice. */ + } + } + /* Delete this local variable. */ + else if (NILP (last)) + bset_local_var_alist (b, XCDR (tmp)); + else + XSETCDR (last, XCDR (tmp)); + + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, Qnil, + Qmakunbound, Fcurrent_buffer ()); + } } for (i = 0; i < last_per_buffer_idx; ++i) @@ -5682,7 +5696,7 @@ syms_of_buffer (void) This variable is buffer-local but you cannot set it directly; use the function `set-buffer-multibyte' to change a buffer's representation. See also Info node `(elisp)Text Representations'. */); - XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1; + make_symbol_constant (intern_c_string ("enable-multibyte-characters")); DEFVAR_PER_BUFFER ("buffer-file-coding-system", &BVAR (current_buffer, buffer_file_coding_system), Qnil, diff --git a/src/bytecode.c b/src/bytecode.c index e2d8ab7..edfc9c5 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -569,10 +569,10 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (SYMBOLP (sym) && !EQ (val, Qunbound) && !XSYMBOL (sym)->redirect - && !SYMBOL_CONSTANT_P (sym)) + && !SYMBOL_TRAPPED_WRITE_P (sym)) SET_SYMBOL_VAL (XSYMBOL (sym), val); else - set_internal (sym, val, Qnil, false); + set_internal (sym, val, Qnil, SET_INTERNAL_SET); } NEXT; diff --git a/src/data.c b/src/data.c index d221db4..44a08e6 100644 --- a/src/data.c +++ b/src/data.c @@ -649,9 +649,10 @@ DEFUN ("makunbound", Fmakunbound, Smakunbound, 1, 1, 0, (register Lisp_Object symbol) { CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + if (XSYMBOL (symbol)->trapped_write == SYMBOL_NOWRITE) xsignal1 (Qsetting_constant, symbol); - Fset (symbol, Qunbound); + else + Fset (symbol, Qunbound); return symbol; } @@ -1225,7 +1226,7 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */) (register Lisp_Object symbol, Lisp_Object newval) { - set_internal (symbol, newval, Qnil, 0); + set_internal (symbol, newval, Qnil, SET_INTERNAL_SET); return newval; } @@ -1233,13 +1234,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, If buffer/frame-locality is an issue, WHERE specifies which context to use. (nil stands for the current buffer/frame). - If BINDFLAG is false, then if this symbol is supposed to become - local in every buffer where it is set, then we make it local. - If BINDFLAG is true, we don't do that. */ + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to + become local in every buffer where it is set, then we make it + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we + don't do that. */ void set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, - bool bindflag) + enum Set_Internal_Bind bindflag) { bool voide = EQ (newval, Qunbound); struct Lisp_Symbol *sym; @@ -1250,18 +1252,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; */ CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (newval, Fsymbol_value (symbol))) - xsignal1 (Qsetting_constant, symbol); + || !EQ (newval, Fsymbol_value (symbol))) + xsignal1 (Qsetting_constant, symbol); else - /* Allow setting keywords to their own value. */ - return; + /* Allow setting keywords to their own value. */ + return; + + case SYMBOL_TRAPPED_WRITE: + notify_variable_watchers (symbol, voide? Qnil : newval, + (bindflag == SET_INTERNAL_BIND? Qlet : + bindflag == SET_INTERNAL_UNBIND? Qunlet : + voide? Qmakunbound : Qset), + where); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } maybe_set_redisplay (symbol); - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1385,6 +1400,107 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, } return; } + +static void +set_symbol_trapped_write (Lisp_Object symbol, enum symbol_trapped_write trap) +{ + struct Lisp_Symbol* sym = XSYMBOL (symbol); + if (sym->trapped_write == SYMBOL_NOWRITE) + xsignal1 (Qtrapping_constant, symbol); + else if (sym->redirect == SYMBOL_LOCALIZED && + SYMBOL_BLV (sym)->frame_local) + xsignal1 (Qtrapping_frame_local, symbol); + sym->trapped_write = trap; +} + +static void +reset_symbol_trapped_write (Lisp_Object symbol) +{ + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); +} + +static void +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) +{ + if (!EQ (base_variable, alias) && + EQ (base_variable, Findirect_variable (alias))) + set_symbol_trapped_write + (alias, XSYMBOL (base_variable)->trapped_write); +} + +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, + 2, 2, 0, + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + + Lisp_Object watchers = Fget (symbol, Qwatchers); + Lisp_Object member = Fmember (watch_function, watchers); + if (NILP (member)) + Fput (symbol, Qwatchers, Fcons (watch_function, watchers)); + return Qnil; +} + +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, + 2, 2, 0, + doc: /* Undo the effect of `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + Lisp_Object watchers = Fget (symbol, Qwatchers); + watchers = Fdelete (watch_function, watchers); + if (NILP (watchers)) + { + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + } + Fput (symbol, Qwatchers, watchers); + return Qnil; +} + +void +notify_variable_watchers (Lisp_Object symbol, + Lisp_Object newval, + Lisp_Object operation, + Lisp_Object where) +{ + symbol = Findirect_variable (symbol); + /* Avoid recursion. */ + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + ptrdiff_t count = SPECPDL_INDEX (); + record_unwind_protect (reset_symbol_trapped_write, symbol); + + if (NILP (where) && + !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) && + !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) + { + XSETBUFFER (where, current_buffer); + } + + if (EQ (operation, Qset_default)) + operation = Qset; + + for (Lisp_Object watchers = Fget (symbol, Qwatchers); + CONSP (watchers); + watchers = XCDR (watchers)) + { + Lisp_Object watcher = XCAR (watchers); + /* Call subr directly to avoid gc. */ + if (SUBRP (watcher)) + { + Lisp_Object args[] = { symbol, newval, operation, where }; + funcall_subr (XSUBR (watcher), ARRAYELTS (args), args); + } + else + CALLN (Ffuncall, watcher, symbol, newval, operation, where); + } + + unbind_to (count, Qnil); +} + /* Access or set a buffer-local symbol's default value. */ @@ -1471,16 +1587,27 @@ DEFUN ("set-default", Fset_default, Sset_default, 2, 2, 0, struct Lisp_Symbol *sym; CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (value, Fdefault_value (symbol))) - xsignal1 (Qsetting_constant, symbol); + || !EQ (value, Fsymbol_value (symbol))) + xsignal1 (Qsetting_constant, symbol); else - /* Allow setting keywords to their own value. */ - return value; + /* Allow setting keywords to their own value. */ + return value; + + case SYMBOL_TRAPPED_WRITE: + /* Don't notify here if we're going to call Fset anyway. */ + if (sym->redirect != SYMBOL_PLAINVAL) + notify_variable_watchers (symbol, value, Qset_default, Qnil); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1651,7 +1778,7 @@ DEFUN ("make-variable-buffer-local", Fmake_variable_buffer_local, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); if (!blv) @@ -1726,7 +1853,7 @@ DEFUN ("make-local-variable", Fmake_local_variable, Smake_local_variable, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); @@ -1838,6 +1965,9 @@ DEFUN ("kill-local-variable", Fkill_local_variable, Skill_local_variable, default: emacs_abort (); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (variable, Qnil, Qmakunbound, Fcurrent_buffer ()); + /* Get rid of this buffer's alist element, if any. */ XSETSYMBOL (variable, sym); /* Propagate variable indirection. */ tem = Fassq (variable, BVAR (current_buffer, local_var_alist)); @@ -1920,7 +2050,7 @@ DEFUN ("make-variable-frame-local", Fmake_variable_frame_local, Smake_variable_f default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_TRAPPED_WRITE_P (variable)) error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable))); blv = make_blv (sym, forwarded, valcontents); @@ -3471,6 +3601,8 @@ syms_of_data (void) DEFSYM (Qcyclic_variable_indirection, "cyclic-variable-indirection"); DEFSYM (Qvoid_variable, "void-variable"); DEFSYM (Qsetting_constant, "setting-constant"); + DEFSYM (Qtrapping_constant, "trapping-constant"); + DEFSYM (Qtrapping_frame_local, "trapping-frame-local"); DEFSYM (Qinvalid_read_syntax, "invalid-read-syntax"); DEFSYM (Qinvalid_function, "invalid-function"); @@ -3549,6 +3681,10 @@ syms_of_data (void) PUT_ERROR (Qvoid_variable, error_tail, "Symbol's value as variable is void"); PUT_ERROR (Qsetting_constant, error_tail, "Attempt to set a constant symbol"); + PUT_ERROR (Qtrapping_constant, error_tail, + "Attempt to trap writes to a constant symbol"); + PUT_ERROR (Qtrapping_frame_local, error_tail, + "Attempt to trap writes to a frame local variable"); PUT_ERROR (Qinvalid_read_syntax, error_tail, "Invalid read syntax"); PUT_ERROR (Qinvalid_function, error_tail, "Invalid function"); PUT_ERROR (Qwrong_number_of_arguments, error_tail, @@ -3727,10 +3863,18 @@ syms_of_data (void) DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum, doc: /* The largest value that is representable in a Lisp integer. */); Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-positive-fixnum")); DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum, doc: /* The smallest value that is representable in a Lisp integer. */); Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-negative-fixnum")); + + DEFSYM (Qwatchers, "watchers"); + DEFSYM (Qmakunbound, "makunbound"); + DEFSYM (Qunlet, "unlet"); + DEFSYM (Qset, "set"); + DEFSYM (Qset_default, "set-default"); + defsubr (&Sadd_variable_watcher); + defsubr (&Sremove_variable_watcher); } diff --git a/src/eval.c b/src/eval.c index a9bad24..7c66a46 100644 --- a/src/eval.c +++ b/src/eval.c @@ -595,8 +595,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, sym = XSYMBOL (new_alias); - if (sym->constant) - /* Not sure why, but why not? */ + if (sym->trapped_write == SYMBOL_NOWRITE) + /* Making it an alias effectively changes its value. */ error ("Cannot make a constant an alias"); switch (sym->redirect) @@ -617,8 +617,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, so that old-code that affects n_a before the aliasing is setup still works. */ if (NILP (Fboundp (base_variable))) - set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1); - + set_internal (base_variable, find_symbol_value (new_alias), + Qnil, SET_INTERNAL_BIND); { union specbinding *p; @@ -628,11 +628,14 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, error ("Don't know how to make a let-bound variable an alias"); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (new_alias, base_variable, Qdefvaralias, Qnil); + sym->declared_special = 1; XSYMBOL (base_variable)->declared_special = 1; sym->redirect = SYMBOL_VARALIAS; SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable)); - sym->constant = SYMBOL_CONSTANT_P (base_variable); + sym->trapped_write = XSYMBOL (base_variable)->trapped_write; LOADHIST_ATTACH (new_alias); /* Even if docstring is nil: remove old docstring. */ Fput (new_alias, Qvariable_documentation, docstring); @@ -2644,9 +2647,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, Lisp_Object fun, original_fun; Lisp_Object funcar; ptrdiff_t numargs = nargs - 1; - Lisp_Object lisp_numargs; Lisp_Object val; - Lisp_Object *internal_args; ptrdiff_t count; QUIT; @@ -2679,86 +2680,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, fun = indirect_function (fun); if (SUBRP (fun)) - { - if (numargs < XSUBR (fun)->min_args - || (XSUBR (fun)->max_args >= 0 && XSUBR (fun)->max_args < numargs)) - { - XSETFASTINT (lisp_numargs, numargs); - xsignal2 (Qwrong_number_of_arguments, original_fun, lisp_numargs); - } - - else if (XSUBR (fun)->max_args == UNEVALLED) - xsignal1 (Qinvalid_function, original_fun); - - else if (XSUBR (fun)->max_args == MANY) - val = (XSUBR (fun)->function.aMANY) (numargs, args + 1); - else - { - Lisp_Object internal_argbuf[8]; - if (XSUBR (fun)->max_args > numargs) - { - eassert (XSUBR (fun)->max_args <= ARRAYELTS (internal_argbuf)); - internal_args = internal_argbuf; - memcpy (internal_args, args + 1, numargs * word_size); - memclear (internal_args + numargs, - (XSUBR (fun)->max_args - numargs) * word_size); - } - else - internal_args = args + 1; - switch (XSUBR (fun)->max_args) - { - case 0: - val = (XSUBR (fun)->function.a0 ()); - break; - case 1: - val = (XSUBR (fun)->function.a1 (internal_args[0])); - break; - case 2: - val = (XSUBR (fun)->function.a2 - (internal_args[0], internal_args[1])); - break; - case 3: - val = (XSUBR (fun)->function.a3 - (internal_args[0], internal_args[1], internal_args[2])); - break; - case 4: - val = (XSUBR (fun)->function.a4 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3])); - break; - case 5: - val = (XSUBR (fun)->function.a5 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4])); - break; - case 6: - val = (XSUBR (fun)->function.a6 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5])); - break; - case 7: - val = (XSUBR (fun)->function.a7 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5], - internal_args[6])); - break; - - case 8: - val = (XSUBR (fun)->function.a8 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5], - internal_args[6], internal_args[7])); - break; - - default: - - /* If a subr takes more than 8 arguments without using MANY - or UNEVALLED, we need to extend this function to support it. - Until this is done, there is no way to call the function. */ - emacs_abort (); - } - } - } + val = funcall_subr (XSUBR (fun), numargs, args + 1); else if (COMPILEDP (fun)) val = funcall_lambda (fun, numargs, args + 1); else @@ -2790,6 +2712,89 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, return val; } + +/* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR + and return the result of evaluation. */ + +Lisp_Object +funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *args) +{ + if (numargs < subr->min_args + || (subr->max_args >= 0 && subr->max_args < numargs)) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal2 (Qwrong_number_of_arguments, fun, make_number (numargs)); + } + + else if (subr->max_args == UNEVALLED) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal1 (Qinvalid_function, fun); + } + + else if (subr->max_args == MANY) + return (subr->function.aMANY) (numargs, args); + else + { + Lisp_Object internal_argbuf[8]; + Lisp_Object *internal_args; + if (subr->max_args > numargs) + { + eassert (subr->max_args <= ARRAYELTS (internal_argbuf)); + internal_args = internal_argbuf; + memcpy (internal_args, args, numargs * word_size); + memclear (internal_args + numargs, + (subr->max_args - numargs) * word_size); + } + else + internal_args = args; + switch (subr->max_args) + { + case 0: + return (subr->function.a0 ()); + case 1: + return (subr->function.a1 (internal_args[0])); + case 2: + return (subr->function.a2 + (internal_args[0], internal_args[1])); + case 3: + return (subr->function.a3 + (internal_args[0], internal_args[1], internal_args[2])); + case 4: + return (subr->function.a4 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3])); + case 5: + return (subr->function.a5 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4])); + case 6: + return (subr->function.a6 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5])); + case 7: + return (subr->function.a7 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5], + internal_args[6])); + case 8: + return (subr->function.a8 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5], + internal_args[6], internal_args[7])); + + default: + + /* If a subr takes more than 8 arguments without using MANY + or UNEVALLED, we need to extend this function to support it. + Until this is done, there is no way to call the function. */ + emacs_abort (); + } + } +} + static Lisp_Object apply_lambda (Lisp_Object fun, Lisp_Object args, ptrdiff_t count) { @@ -3158,10 +3163,10 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.symbol = symbol; specpdl_ptr->let.old_value = SYMBOL_VAL (sym); grow_specpdl (); - if (!sym->constant) + if (!sym->trapped_write) SET_SYMBOL_VAL (sym, value); else - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; case SYMBOL_LOCALIZED: if (SYMBOL_BLV (sym)->frame_local) @@ -3201,7 +3206,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.kind = SPECPDL_LET; grow_specpdl (); - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; } default: emacs_abort (); @@ -3328,14 +3333,16 @@ unbind_to (ptrdiff_t count, Lisp_Object value) case SPECPDL_BACKTRACE: break; case SPECPDL_LET: - { /* If variable has a trivial value (no forwarding), we can - just set it. No need to check for constant symbols here, - since that was already done by specbind. */ + { /* If variable has a trivial value (no forwarding), and + isn't trapped, we can just set it. */ Lisp_Object sym = specpdl_symbol (specpdl_ptr); if (SYMBOLP (sym) && XSYMBOL (sym)->redirect == SYMBOL_PLAINVAL) { - SET_SYMBOL_VAL (XSYMBOL (sym), - specpdl_old_value (specpdl_ptr)); + if (XSYMBOL (sym)->trapped_write == SYMBOL_UNTRAPPED_WRITE) + SET_SYMBOL_VAL (XSYMBOL (sym), specpdl_old_value (specpdl_ptr)); + else + set_internal (sym, specpdl_old_value (specpdl_ptr), + Qnil, SET_INTERNAL_UNBIND); break; } else @@ -3358,7 +3365,7 @@ unbind_to (ptrdiff_t count, Lisp_Object value) /* If this was a local binding, reset the value in the appropriate buffer, but only if that buffer's binding still exists. */ if (!NILP (Flocal_variable_p (symbol, where))) - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } break; } @@ -3583,7 +3590,7 @@ backtrace_eval_unrewind (int distance) { set_specpdl_old_value (tmp, Fbuffer_local_value (symbol, where)); - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } } break; @@ -3927,6 +3934,7 @@ syms_of_eval (void) defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); + DEFSYM (Qdefvaralias, "defvaralias"); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); diff --git a/src/font.c b/src/font.c index ce63233..3b821a4 100644 --- a/src/font.c +++ b/src/font.c @@ -5417,19 +5417,19 @@ syms_of_font (void) [NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...] NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */); Vfont_weight_table = BUILD_STYLE_TABLE (weight_table); - XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-weight-table")); DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table, doc: /* Vector of font slant symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_slant_table = BUILD_STYLE_TABLE (slant_table); - XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-slant-table")); DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table, doc: /* Alist of font width symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_width_table = BUILD_STYLE_TABLE (width_table); - XSYMBOL (intern_c_string ("font-width-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-width-table")); staticpro (&font_style_table); font_style_table = make_uninit_vector (3); diff --git a/src/lisp.h b/src/lisp.h index 2e46592..c1483fa 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -320,7 +320,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) #define lisp_h_NILP(x) EQ (x, Qnil) #define lisp_h_SET_SYMBOL_VAL(sym, v) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v)) -#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant) +#define lisp_h_SYMBOL_TRAPPED_WRITE_P(sym) (XSYMBOL (sym)->trapped_write) #define lisp_h_SYMBOL_VAL(sym) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value) #define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol) @@ -374,7 +374,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) # define MISCP(x) lisp_h_MISCP (x) # define NILP(x) lisp_h_NILP (x) # define SET_SYMBOL_VAL(sym, v) lisp_h_SET_SYMBOL_VAL (sym, v) -# define SYMBOL_CONSTANT_P(sym) lisp_h_SYMBOL_CONSTANT_P (sym) +# define SYMBOL_TRAPPED_WRITE_P(sym) lisp_h_SYMBOL_TRAPPED_WRITE_P (sym) # define SYMBOL_VAL(sym) lisp_h_SYMBOL_VAL (sym) # define SYMBOLP(x) lisp_h_SYMBOLP (x) # define VECTORLIKEP(x) lisp_h_VECTORLIKEP (x) @@ -602,6 +602,9 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); /* Defined in data.c. */ extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object); extern _Noreturn void wrong_choice (Lisp_Object, Lisp_Object); +extern void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, + Lisp_Object operation, Lisp_Object where); + /* Defined in emacs.c. */ #ifdef DOUG_LEA_MALLOC @@ -632,6 +635,13 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); SYMBOL_FORWARDED = 3 }; +enum symbol_trapped_write +{ + SYMBOL_UNTRAPPED_WRITE = 0, + SYMBOL_NOWRITE = 1, + SYMBOL_TRAPPED_WRITE = 2 +}; + struct Lisp_Symbol { bool_bf gcmarkbit : 1; @@ -643,10 +653,10 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); 3 : it's a forwarding variable, the value is in `forward'. */ ENUM_BF (symbol_redirect) redirect : 3; - /* Non-zero means symbol is constant, i.e. changing its value - should signal an error. If the value is 3, then the var - can be changed, but only by `defconst'. */ - unsigned constant : 2; + /* 0 : normal case, just set the value + 1 : constant, cannot set, e.g. nil, t, :keywords. + 2 : trap the write, call watcher functions. */ + ENUM_BF (symbol_trapped_write) trapped_write : 2; /* Interned state of the symbol. This is an enumerator from enum symbol_interned. */ @@ -1850,14 +1860,14 @@ SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (Lisp_Object sym) return XSYMBOL (sym)->interned == SYMBOL_INTERNED_IN_INITIAL_OBARRAY; } -/* Value is non-zero if symbol is considered a constant, i.e. its - value cannot be changed (there is an exception for keyword symbols, - whose value can be set to the keyword symbol itself). */ +/* Value is non-zero if symbol cannot be changed through a simple set, + i.e. it's a constant (e.g. nil, t, :keywords), or it has some + watching functions. */ INLINE int -(SYMBOL_CONSTANT_P) (Lisp_Object sym) +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) { - return lisp_h_SYMBOL_CONSTANT_P (sym); + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); } /* Placeholder for make-docfile to process. The actual symbol @@ -3289,6 +3299,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) XSYMBOL (sym)->next = next; } +INLINE void +make_symbol_constant (Lisp_Object sym) +{ + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; +} + /* Buffer-local (also frame-local) variable access functions. */ INLINE int @@ -3397,7 +3413,13 @@ set_sub_char_table_contents (Lisp_Object table, ptrdiff_t idx, Lisp_Object val) extern _Noreturn void args_out_of_range_3 (Lisp_Object, Lisp_Object, Lisp_Object); extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *); -extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool); +enum Set_Internal_Bind { + SET_INTERNAL_SET, + SET_INTERNAL_BIND, + SET_INTERNAL_UNBIND +}; +extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, + enum Set_Internal_Bind); extern void syms_of_data (void); extern void swap_in_global_binding (struct Lisp_Symbol *); @@ -3880,6 +3902,7 @@ xsignal (Lisp_Object error_symbol, Lisp_Object data) extern _Noreturn void xsignal3 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object); extern _Noreturn void signal_error (const char *, Lisp_Object); +extern Lisp_Object funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *arg_vector); extern Lisp_Object eval_sub (Lisp_Object form); extern Lisp_Object apply1 (Lisp_Object, Lisp_Object); extern Lisp_Object call0 (Lisp_Object); diff --git a/src/lread.c b/src/lread.c index 58d518c..7e74703 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3833,7 +3833,7 @@ intern_sym (Lisp_Object sym, Lisp_Object obarray, Lisp_Object index) if (SREF (SYMBOL_NAME (sym), 0) == ':' && EQ (obarray, initial_obarray)) { - XSYMBOL (sym)->constant = 1; + make_symbol_constant (sym); XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL; SET_SYMBOL_VAL (XSYMBOL (sym), sym); } @@ -4120,12 +4120,12 @@ init_obarray (void) DEFSYM (Qnil, "nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); - XSYMBOL (Qnil)->constant = 1; + make_symbol_constant (Qnil); XSYMBOL (Qnil)->declared_special = true; DEFSYM (Qt, "t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); - XSYMBOL (Qt)->constant = 1; + make_symbol_constant (Qt); XSYMBOL (Qt)->declared_special = true; /* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */ -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0001-Add-lisp-watchpoints.patch >From 0507854b885681c2e57bb1c6cb97f898417bd99c Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 19 Nov 2015 19:50:06 -0500 Subject: [PATCH v4 1/5] Add lisp watchpoints This allows to call a function whenever a symbol-value is changed. * src/lisp.h (lisp_h_SYMBOL_TRAPPED_WRITE_P): Rename from lisp_h_SYMBOL_CONSTANT_P. (SYMBOL_TRAPPED_WRITE_P): Rename from SYMBOL_CONSTANT_P. (enum symbol_trapped_write): New enumeration. (struct Lisp_Symbol): Rename field constant to trapped_write. (make_symbol_constant): New function. * src/data.c (Fadd_variable_watcher, Fremove_variable_watcher): (set_symbol_trapped_write, reset_symbol_trapped_write): (harmonize_variable_watchers, notify_variable_watchers): New functions. * src/data.c (Fset_default): Call `notify_variable_watchers' for trapped symbols. (set_internal): Change bool argument BIND to 3-value enum and call `notify_variable_watchers' for trapped symbols. * src/data.c (syms_of_data): * src/data.c (syms_of_data): * src/font.c (syms_of_font): * src/lread.c (intern_sym, init_obarray): * src/buffer.c (syms_of_buffer): Use make_symbol_constant. * src/alloc.c (init_symbol): * src/bytecode.c (exec_byte_code): Use SYMBOL_TRAPPED_WRITE_P. * src/data.c (Fmake_variable_buffer_local, Fmake_local_variable): (Fmake_variable_frame_local): * src/eval.c (Fdefvaralias, specbind): Refer to Lisp_Symbol's trapped_write instead of constant. (Ffuncall): Move subr calling code into separate function. (funcall_subr): New function. --- src/alloc.c | 2 +- src/buffer.c | 82 +++++++++++++---------- src/bytecode.c | 4 +- src/data.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++------- src/eval.c | 202 ++++++++++++++++++++++++++++++--------------------------- src/font.c | 6 +- src/lisp.h | 47 ++++++++++---- src/lread.c | 6 +- 8 files changed, 365 insertions(+), 176 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index a58dc13..f373f6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3562,7 +3562,7 @@ init_symbol (Lisp_Object val, Lisp_Object name) set_symbol_next (val, NULL); p->gcmarkbit = false; p->interned = SYMBOL_UNINTERNED; - p->constant = 0; + p->trapped_write = SYMBOL_UNTRAPPED_WRITE; p->declared_special = false; p->pinned = false; } diff --git a/src/buffer.c b/src/buffer.c index 3d205bb..cc75cdb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -984,40 +984,54 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, Qnil); else { - Lisp_Object tmp, prop, last = Qnil; + Lisp_Object tmp, last = Qnil; for (tmp = BVAR (b, local_var_alist); CONSP (tmp); tmp = XCDR (tmp)) - if (!NILP (prop = Fget (XCAR (XCAR (tmp)), Qpermanent_local))) - { - /* If permanent-local, keep it. */ - last = tmp; - if (EQ (prop, Qpermanent_local_hook)) - { - /* This is a partially permanent hook variable. - Preserve only the elements that want to be preserved. */ - Lisp_Object list, newlist; - list = XCDR (XCAR (tmp)); - if (!CONSP (list)) - newlist = list; - else - for (newlist = Qnil; CONSP (list); list = XCDR (list)) - { - Lisp_Object elt = XCAR (list); - /* Preserve element ELT if it's t, - if it is a function with a `permanent-local-hook' property, - or if it's not a symbol. */ - if (! SYMBOLP (elt) - || EQ (elt, Qt) - || !NILP (Fget (elt, Qpermanent_local_hook))) - newlist = Fcons (elt, newlist); - } - XSETCDR (XCAR (tmp), Fnreverse (newlist)); - } - } - /* Delete this local variable. */ - else if (NILP (last)) - bset_local_var_alist (b, XCDR (tmp)); - else - XSETCDR (last, XCDR (tmp)); + { + Lisp_Object local_var = XCAR (XCAR (tmp)); + Lisp_Object prop = Fget (local_var, Qpermanent_local); + + if (!NILP (prop)) + { + /* If permanent-local, keep it. */ + last = tmp; + if (EQ (prop, Qpermanent_local_hook)) + { + /* This is a partially permanent hook variable. + Preserve only the elements that want to be preserved. */ + Lisp_Object list, newlist; + list = XCDR (XCAR (tmp)); + if (!CONSP (list)) + newlist = list; + else + for (newlist = Qnil; CONSP (list); list = XCDR (list)) + { + Lisp_Object elt = XCAR (list); + /* Preserve element ELT if it's t, + if it is a function with a `permanent-local-hook' property, + or if it's not a symbol. */ + if (! SYMBOLP (elt) + || EQ (elt, Qt) + || !NILP (Fget (elt, Qpermanent_local_hook))) + newlist = Fcons (elt, newlist); + } + newlist = Fnreverse (newlist); + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, newlist, + Qmakunbound, Fcurrent_buffer ()); + XSETCDR (XCAR (tmp), newlist); + continue; /* Don't do variable write trapping twice. */ + } + } + /* Delete this local variable. */ + else if (NILP (last)) + bset_local_var_alist (b, XCDR (tmp)); + else + XSETCDR (last, XCDR (tmp)); + + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, Qnil, + Qmakunbound, Fcurrent_buffer ()); + } } for (i = 0; i < last_per_buffer_idx; ++i) @@ -5682,7 +5696,7 @@ syms_of_buffer (void) This variable is buffer-local but you cannot set it directly; use the function `set-buffer-multibyte' to change a buffer's representation. See also Info node `(elisp)Text Representations'. */); - XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1; + make_symbol_constant (intern_c_string ("enable-multibyte-characters")); DEFVAR_PER_BUFFER ("buffer-file-coding-system", &BVAR (current_buffer, buffer_file_coding_system), Qnil, diff --git a/src/bytecode.c b/src/bytecode.c index e2d8ab7..edfc9c5 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -569,10 +569,10 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (SYMBOLP (sym) && !EQ (val, Qunbound) && !XSYMBOL (sym)->redirect - && !SYMBOL_CONSTANT_P (sym)) + && !SYMBOL_TRAPPED_WRITE_P (sym)) SET_SYMBOL_VAL (XSYMBOL (sym), val); else - set_internal (sym, val, Qnil, false); + set_internal (sym, val, Qnil, SET_INTERNAL_SET); } NEXT; diff --git a/src/data.c b/src/data.c index d221db4..44a08e6 100644 --- a/src/data.c +++ b/src/data.c @@ -649,9 +649,10 @@ DEFUN ("makunbound", Fmakunbound, Smakunbound, 1, 1, 0, (register Lisp_Object symbol) { CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + if (XSYMBOL (symbol)->trapped_write == SYMBOL_NOWRITE) xsignal1 (Qsetting_constant, symbol); - Fset (symbol, Qunbound); + else + Fset (symbol, Qunbound); return symbol; } @@ -1225,7 +1226,7 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */) (register Lisp_Object symbol, Lisp_Object newval) { - set_internal (symbol, newval, Qnil, 0); + set_internal (symbol, newval, Qnil, SET_INTERNAL_SET); return newval; } @@ -1233,13 +1234,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, If buffer/frame-locality is an issue, WHERE specifies which context to use. (nil stands for the current buffer/frame). - If BINDFLAG is false, then if this symbol is supposed to become - local in every buffer where it is set, then we make it local. - If BINDFLAG is true, we don't do that. */ + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to + become local in every buffer where it is set, then we make it + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we + don't do that. */ void set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, - bool bindflag) + enum Set_Internal_Bind bindflag) { bool voide = EQ (newval, Qunbound); struct Lisp_Symbol *sym; @@ -1250,18 +1252,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; */ CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (newval, Fsymbol_value (symbol))) - xsignal1 (Qsetting_constant, symbol); + || !EQ (newval, Fsymbol_value (symbol))) + xsignal1 (Qsetting_constant, symbol); else - /* Allow setting keywords to their own value. */ - return; + /* Allow setting keywords to their own value. */ + return; + + case SYMBOL_TRAPPED_WRITE: + notify_variable_watchers (symbol, voide? Qnil : newval, + (bindflag == SET_INTERNAL_BIND? Qlet : + bindflag == SET_INTERNAL_UNBIND? Qunlet : + voide? Qmakunbound : Qset), + where); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } maybe_set_redisplay (symbol); - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1385,6 +1400,107 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, } return; } + +static void +set_symbol_trapped_write (Lisp_Object symbol, enum symbol_trapped_write trap) +{ + struct Lisp_Symbol* sym = XSYMBOL (symbol); + if (sym->trapped_write == SYMBOL_NOWRITE) + xsignal1 (Qtrapping_constant, symbol); + else if (sym->redirect == SYMBOL_LOCALIZED && + SYMBOL_BLV (sym)->frame_local) + xsignal1 (Qtrapping_frame_local, symbol); + sym->trapped_write = trap; +} + +static void +reset_symbol_trapped_write (Lisp_Object symbol) +{ + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); +} + +static void +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) +{ + if (!EQ (base_variable, alias) && + EQ (base_variable, Findirect_variable (alias))) + set_symbol_trapped_write + (alias, XSYMBOL (base_variable)->trapped_write); +} + +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, + 2, 2, 0, + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + + Lisp_Object watchers = Fget (symbol, Qwatchers); + Lisp_Object member = Fmember (watch_function, watchers); + if (NILP (member)) + Fput (symbol, Qwatchers, Fcons (watch_function, watchers)); + return Qnil; +} + +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, + 2, 2, 0, + doc: /* Undo the effect of `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + Lisp_Object watchers = Fget (symbol, Qwatchers); + watchers = Fdelete (watch_function, watchers); + if (NILP (watchers)) + { + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + } + Fput (symbol, Qwatchers, watchers); + return Qnil; +} + +void +notify_variable_watchers (Lisp_Object symbol, + Lisp_Object newval, + Lisp_Object operation, + Lisp_Object where) +{ + symbol = Findirect_variable (symbol); + /* Avoid recursion. */ + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + ptrdiff_t count = SPECPDL_INDEX (); + record_unwind_protect (reset_symbol_trapped_write, symbol); + + if (NILP (where) && + !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) && + !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) + { + XSETBUFFER (where, current_buffer); + } + + if (EQ (operation, Qset_default)) + operation = Qset; + + for (Lisp_Object watchers = Fget (symbol, Qwatchers); + CONSP (watchers); + watchers = XCDR (watchers)) + { + Lisp_Object watcher = XCAR (watchers); + /* Call subr directly to avoid gc. */ + if (SUBRP (watcher)) + { + Lisp_Object args[] = { symbol, newval, operation, where }; + funcall_subr (XSUBR (watcher), ARRAYELTS (args), args); + } + else + CALLN (Ffuncall, watcher, symbol, newval, operation, where); + } + + unbind_to (count, Qnil); +} + /* Access or set a buffer-local symbol's default value. */ @@ -1471,16 +1587,27 @@ DEFUN ("set-default", Fset_default, Sset_default, 2, 2, 0, struct Lisp_Symbol *sym; CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (value, Fdefault_value (symbol))) - xsignal1 (Qsetting_constant, symbol); + || !EQ (value, Fsymbol_value (symbol))) + xsignal1 (Qsetting_constant, symbol); else - /* Allow setting keywords to their own value. */ - return value; + /* Allow setting keywords to their own value. */ + return value; + + case SYMBOL_TRAPPED_WRITE: + /* Don't notify here if we're going to call Fset anyway. */ + if (sym->redirect != SYMBOL_PLAINVAL) + notify_variable_watchers (symbol, value, Qset_default, Qnil); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1651,7 +1778,7 @@ DEFUN ("make-variable-buffer-local", Fmake_variable_buffer_local, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); if (!blv) @@ -1726,7 +1853,7 @@ DEFUN ("make-local-variable", Fmake_local_variable, Smake_local_variable, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); @@ -1838,6 +1965,9 @@ DEFUN ("kill-local-variable", Fkill_local_variable, Skill_local_variable, default: emacs_abort (); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (variable, Qnil, Qmakunbound, Fcurrent_buffer ()); + /* Get rid of this buffer's alist element, if any. */ XSETSYMBOL (variable, sym); /* Propagate variable indirection. */ tem = Fassq (variable, BVAR (current_buffer, local_var_alist)); @@ -1920,7 +2050,7 @@ DEFUN ("make-variable-frame-local", Fmake_variable_frame_local, Smake_variable_f default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_TRAPPED_WRITE_P (variable)) error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable))); blv = make_blv (sym, forwarded, valcontents); @@ -3471,6 +3601,8 @@ syms_of_data (void) DEFSYM (Qcyclic_variable_indirection, "cyclic-variable-indirection"); DEFSYM (Qvoid_variable, "void-variable"); DEFSYM (Qsetting_constant, "setting-constant"); + DEFSYM (Qtrapping_constant, "trapping-constant"); + DEFSYM (Qtrapping_frame_local, "trapping-frame-local"); DEFSYM (Qinvalid_read_syntax, "invalid-read-syntax"); DEFSYM (Qinvalid_function, "invalid-function"); @@ -3549,6 +3681,10 @@ syms_of_data (void) PUT_ERROR (Qvoid_variable, error_tail, "Symbol's value as variable is void"); PUT_ERROR (Qsetting_constant, error_tail, "Attempt to set a constant symbol"); + PUT_ERROR (Qtrapping_constant, error_tail, + "Attempt to trap writes to a constant symbol"); + PUT_ERROR (Qtrapping_frame_local, error_tail, + "Attempt to trap writes to a frame local variable"); PUT_ERROR (Qinvalid_read_syntax, error_tail, "Invalid read syntax"); PUT_ERROR (Qinvalid_function, error_tail, "Invalid function"); PUT_ERROR (Qwrong_number_of_arguments, error_tail, @@ -3727,10 +3863,18 @@ syms_of_data (void) DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum, doc: /* The largest value that is representable in a Lisp integer. */); Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-positive-fixnum")); DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum, doc: /* The smallest value that is representable in a Lisp integer. */); Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-negative-fixnum")); + + DEFSYM (Qwatchers, "watchers"); + DEFSYM (Qmakunbound, "makunbound"); + DEFSYM (Qunlet, "unlet"); + DEFSYM (Qset, "set"); + DEFSYM (Qset_default, "set-default"); + defsubr (&Sadd_variable_watcher); + defsubr (&Sremove_variable_watcher); } diff --git a/src/eval.c b/src/eval.c index a9bad24..7c66a46 100644 --- a/src/eval.c +++ b/src/eval.c @@ -595,8 +595,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, sym = XSYMBOL (new_alias); - if (sym->constant) - /* Not sure why, but why not? */ + if (sym->trapped_write == SYMBOL_NOWRITE) + /* Making it an alias effectively changes its value. */ error ("Cannot make a constant an alias"); switch (sym->redirect) @@ -617,8 +617,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, so that old-code that affects n_a before the aliasing is setup still works. */ if (NILP (Fboundp (base_variable))) - set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1); - + set_internal (base_variable, find_symbol_value (new_alias), + Qnil, SET_INTERNAL_BIND); { union specbinding *p; @@ -628,11 +628,14 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, error ("Don't know how to make a let-bound variable an alias"); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (new_alias, base_variable, Qdefvaralias, Qnil); + sym->declared_special = 1; XSYMBOL (base_variable)->declared_special = 1; sym->redirect = SYMBOL_VARALIAS; SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable)); - sym->constant = SYMBOL_CONSTANT_P (base_variable); + sym->trapped_write = XSYMBOL (base_variable)->trapped_write; LOADHIST_ATTACH (new_alias); /* Even if docstring is nil: remove old docstring. */ Fput (new_alias, Qvariable_documentation, docstring); @@ -2644,9 +2647,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, Lisp_Object fun, original_fun; Lisp_Object funcar; ptrdiff_t numargs = nargs - 1; - Lisp_Object lisp_numargs; Lisp_Object val; - Lisp_Object *internal_args; ptrdiff_t count; QUIT; @@ -2679,86 +2680,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, fun = indirect_function (fun); if (SUBRP (fun)) - { - if (numargs < XSUBR (fun)->min_args - || (XSUBR (fun)->max_args >= 0 && XSUBR (fun)->max_args < numargs)) - { - XSETFASTINT (lisp_numargs, numargs); - xsignal2 (Qwrong_number_of_arguments, original_fun, lisp_numargs); - } - - else if (XSUBR (fun)->max_args == UNEVALLED) - xsignal1 (Qinvalid_function, original_fun); - - else if (XSUBR (fun)->max_args == MANY) - val = (XSUBR (fun)->function.aMANY) (numargs, args + 1); - else - { - Lisp_Object internal_argbuf[8]; - if (XSUBR (fun)->max_args > numargs) - { - eassert (XSUBR (fun)->max_args <= ARRAYELTS (internal_argbuf)); - internal_args = internal_argbuf; - memcpy (internal_args, args + 1, numargs * word_size); - memclear (internal_args + numargs, - (XSUBR (fun)->max_args - numargs) * word_size); - } - else - internal_args = args + 1; - switch (XSUBR (fun)->max_args) - { - case 0: - val = (XSUBR (fun)->function.a0 ()); - break; - case 1: - val = (XSUBR (fun)->function.a1 (internal_args[0])); - break; - case 2: - val = (XSUBR (fun)->function.a2 - (internal_args[0], internal_args[1])); - break; - case 3: - val = (XSUBR (fun)->function.a3 - (internal_args[0], internal_args[1], internal_args[2])); - break; - case 4: - val = (XSUBR (fun)->function.a4 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3])); - break; - case 5: - val = (XSUBR (fun)->function.a5 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4])); - break; - case 6: - val = (XSUBR (fun)->function.a6 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5])); - break; - case 7: - val = (XSUBR (fun)->function.a7 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5], - internal_args[6])); - break; - - case 8: - val = (XSUBR (fun)->function.a8 - (internal_args[0], internal_args[1], internal_args[2], - internal_args[3], internal_args[4], internal_args[5], - internal_args[6], internal_args[7])); - break; - - default: - - /* If a subr takes more than 8 arguments without using MANY - or UNEVALLED, we need to extend this function to support it. - Until this is done, there is no way to call the function. */ - emacs_abort (); - } - } - } + val = funcall_subr (XSUBR (fun), numargs, args + 1); else if (COMPILEDP (fun)) val = funcall_lambda (fun, numargs, args + 1); else @@ -2790,6 +2712,89 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, return val; } + +/* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR + and return the result of evaluation. */ + +Lisp_Object +funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *args) +{ + if (numargs < subr->min_args + || (subr->max_args >= 0 && subr->max_args < numargs)) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal2 (Qwrong_number_of_arguments, fun, make_number (numargs)); + } + + else if (subr->max_args == UNEVALLED) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal1 (Qinvalid_function, fun); + } + + else if (subr->max_args == MANY) + return (subr->function.aMANY) (numargs, args); + else + { + Lisp_Object internal_argbuf[8]; + Lisp_Object *internal_args; + if (subr->max_args > numargs) + { + eassert (subr->max_args <= ARRAYELTS (internal_argbuf)); + internal_args = internal_argbuf; + memcpy (internal_args, args, numargs * word_size); + memclear (internal_args + numargs, + (subr->max_args - numargs) * word_size); + } + else + internal_args = args; + switch (subr->max_args) + { + case 0: + return (subr->function.a0 ()); + case 1: + return (subr->function.a1 (internal_args[0])); + case 2: + return (subr->function.a2 + (internal_args[0], internal_args[1])); + case 3: + return (subr->function.a3 + (internal_args[0], internal_args[1], internal_args[2])); + case 4: + return (subr->function.a4 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3])); + case 5: + return (subr->function.a5 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4])); + case 6: + return (subr->function.a6 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5])); + case 7: + return (subr->function.a7 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5], + internal_args[6])); + case 8: + return (subr->function.a8 + (internal_args[0], internal_args[1], internal_args[2], + internal_args[3], internal_args[4], internal_args[5], + internal_args[6], internal_args[7])); + + default: + + /* If a subr takes more than 8 arguments without using MANY + or UNEVALLED, we need to extend this function to support it. + Until this is done, there is no way to call the function. */ + emacs_abort (); + } + } +} + static Lisp_Object apply_lambda (Lisp_Object fun, Lisp_Object args, ptrdiff_t count) { @@ -3158,10 +3163,10 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.symbol = symbol; specpdl_ptr->let.old_value = SYMBOL_VAL (sym); grow_specpdl (); - if (!sym->constant) + if (!sym->trapped_write) SET_SYMBOL_VAL (sym, value); else - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; case SYMBOL_LOCALIZED: if (SYMBOL_BLV (sym)->frame_local) @@ -3201,7 +3206,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.kind = SPECPDL_LET; grow_specpdl (); - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; } default: emacs_abort (); @@ -3328,14 +3333,16 @@ unbind_to (ptrdiff_t count, Lisp_Object value) case SPECPDL_BACKTRACE: break; case SPECPDL_LET: - { /* If variable has a trivial value (no forwarding), we can - just set it. No need to check for constant symbols here, - since that was already done by specbind. */ + { /* If variable has a trivial value (no forwarding), and + isn't trapped, we can just set it. */ Lisp_Object sym = specpdl_symbol (specpdl_ptr); if (SYMBOLP (sym) && XSYMBOL (sym)->redirect == SYMBOL_PLAINVAL) { - SET_SYMBOL_VAL (XSYMBOL (sym), - specpdl_old_value (specpdl_ptr)); + if (XSYMBOL (sym)->trapped_write == SYMBOL_UNTRAPPED_WRITE) + SET_SYMBOL_VAL (XSYMBOL (sym), specpdl_old_value (specpdl_ptr)); + else + set_internal (sym, specpdl_old_value (specpdl_ptr), + Qnil, SET_INTERNAL_UNBIND); break; } else @@ -3358,7 +3365,7 @@ unbind_to (ptrdiff_t count, Lisp_Object value) /* If this was a local binding, reset the value in the appropriate buffer, but only if that buffer's binding still exists. */ if (!NILP (Flocal_variable_p (symbol, where))) - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } break; } @@ -3583,7 +3590,7 @@ backtrace_eval_unrewind (int distance) { set_specpdl_old_value (tmp, Fbuffer_local_value (symbol, where)); - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } } break; @@ -3927,6 +3934,7 @@ syms_of_eval (void) defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); + DEFSYM (Qdefvaralias, "defvaralias"); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); diff --git a/src/font.c b/src/font.c index ce63233..3b821a4 100644 --- a/src/font.c +++ b/src/font.c @@ -5417,19 +5417,19 @@ syms_of_font (void) [NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...] NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */); Vfont_weight_table = BUILD_STYLE_TABLE (weight_table); - XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-weight-table")); DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table, doc: /* Vector of font slant symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_slant_table = BUILD_STYLE_TABLE (slant_table); - XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-slant-table")); DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table, doc: /* Alist of font width symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_width_table = BUILD_STYLE_TABLE (width_table); - XSYMBOL (intern_c_string ("font-width-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-width-table")); staticpro (&font_style_table); font_style_table = make_uninit_vector (3); diff --git a/src/lisp.h b/src/lisp.h index 2e46592..c1483fa 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -320,7 +320,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) #define lisp_h_NILP(x) EQ (x, Qnil) #define lisp_h_SET_SYMBOL_VAL(sym, v) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v)) -#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant) +#define lisp_h_SYMBOL_TRAPPED_WRITE_P(sym) (XSYMBOL (sym)->trapped_write) #define lisp_h_SYMBOL_VAL(sym) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value) #define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol) @@ -374,7 +374,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) # define MISCP(x) lisp_h_MISCP (x) # define NILP(x) lisp_h_NILP (x) # define SET_SYMBOL_VAL(sym, v) lisp_h_SET_SYMBOL_VAL (sym, v) -# define SYMBOL_CONSTANT_P(sym) lisp_h_SYMBOL_CONSTANT_P (sym) +# define SYMBOL_TRAPPED_WRITE_P(sym) lisp_h_SYMBOL_TRAPPED_WRITE_P (sym) # define SYMBOL_VAL(sym) lisp_h_SYMBOL_VAL (sym) # define SYMBOLP(x) lisp_h_SYMBOLP (x) # define VECTORLIKEP(x) lisp_h_VECTORLIKEP (x) @@ -602,6 +602,9 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); /* Defined in data.c. */ extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object); extern _Noreturn void wrong_choice (Lisp_Object, Lisp_Object); +extern void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, + Lisp_Object operation, Lisp_Object where); + /* Defined in emacs.c. */ #ifdef DOUG_LEA_MALLOC @@ -632,6 +635,13 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); SYMBOL_FORWARDED = 3 }; +enum symbol_trapped_write +{ + SYMBOL_UNTRAPPED_WRITE = 0, + SYMBOL_NOWRITE = 1, + SYMBOL_TRAPPED_WRITE = 2 +}; + struct Lisp_Symbol { bool_bf gcmarkbit : 1; @@ -643,10 +653,10 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); 3 : it's a forwarding variable, the value is in `forward'. */ ENUM_BF (symbol_redirect) redirect : 3; - /* Non-zero means symbol is constant, i.e. changing its value - should signal an error. If the value is 3, then the var - can be changed, but only by `defconst'. */ - unsigned constant : 2; + /* 0 : normal case, just set the value + 1 : constant, cannot set, e.g. nil, t, :keywords. + 2 : trap the write, call watcher functions. */ + ENUM_BF (symbol_trapped_write) trapped_write : 2; /* Interned state of the symbol. This is an enumerator from enum symbol_interned. */ @@ -1850,14 +1860,14 @@ SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (Lisp_Object sym) return XSYMBOL (sym)->interned == SYMBOL_INTERNED_IN_INITIAL_OBARRAY; } -/* Value is non-zero if symbol is considered a constant, i.e. its - value cannot be changed (there is an exception for keyword symbols, - whose value can be set to the keyword symbol itself). */ +/* Value is non-zero if symbol cannot be changed through a simple set, + i.e. it's a constant (e.g. nil, t, :keywords), or it has some + watching functions. */ INLINE int -(SYMBOL_CONSTANT_P) (Lisp_Object sym) +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) { - return lisp_h_SYMBOL_CONSTANT_P (sym); + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); } /* Placeholder for make-docfile to process. The actual symbol @@ -3289,6 +3299,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) XSYMBOL (sym)->next = next; } +INLINE void +make_symbol_constant (Lisp_Object sym) +{ + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; +} + /* Buffer-local (also frame-local) variable access functions. */ INLINE int @@ -3397,7 +3413,13 @@ set_sub_char_table_contents (Lisp_Object table, ptrdiff_t idx, Lisp_Object val) extern _Noreturn void args_out_of_range_3 (Lisp_Object, Lisp_Object, Lisp_Object); extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *); -extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool); +enum Set_Internal_Bind { + SET_INTERNAL_SET, + SET_INTERNAL_BIND, + SET_INTERNAL_UNBIND +}; +extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, + enum Set_Internal_Bind); extern void syms_of_data (void); extern void swap_in_global_binding (struct Lisp_Symbol *); @@ -3880,6 +3902,7 @@ xsignal (Lisp_Object error_symbol, Lisp_Object data) extern _Noreturn void xsignal3 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object); extern _Noreturn void signal_error (const char *, Lisp_Object); +extern Lisp_Object funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *arg_vector); extern Lisp_Object eval_sub (Lisp_Object form); extern Lisp_Object apply1 (Lisp_Object, Lisp_Object); extern Lisp_Object call0 (Lisp_Object); diff --git a/src/lread.c b/src/lread.c index 58d518c..7e74703 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3833,7 +3833,7 @@ intern_sym (Lisp_Object sym, Lisp_Object obarray, Lisp_Object index) if (SREF (SYMBOL_NAME (sym), 0) == ':' && EQ (obarray, initial_obarray)) { - XSYMBOL (sym)->constant = 1; + make_symbol_constant (sym); XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL; SET_SYMBOL_VAL (XSYMBOL (sym), sym); } @@ -4120,12 +4120,12 @@ init_obarray (void) DEFSYM (Qnil, "nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); - XSYMBOL (Qnil)->constant = 1; + make_symbol_constant (Qnil); XSYMBOL (Qnil)->declared_special = true; DEFSYM (Qt, "t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); - XSYMBOL (Qt)->constant = 1; + make_symbol_constant (Qt); XSYMBOL (Qt)->declared_special = true; /* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */ -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0002-Add-function-to-trigger-debugger-on-variable-writ.patch >From 08b87d9912c190dca57fb6c07dc97d294c6984dc Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 16:03:06 -0500 Subject: [PATCH v4 2/5] Add function to trigger debugger on variable write * lisp/emacs-lisp/debug.el (debug-watchpoint): (debug--variable-list): (cancel-debug-watchpoint): New functions. (debugger-setup-buffer): Add watchpoint clause. --- lisp/emacs-lisp/debug.el | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 7d27380..48b3543 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -306,6 +306,21 @@ debugger-setup-buffer (delete-char 1) (insert ? ) (beginning-of-line)) + ;; Watchpoint triggered. + ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) + (insert + "--" + (pcase details + (`(makunbound ,_) (format "Making %s void" symbol)) + (`(let ,_) (format "let-binding %s to %S" symbol newval)) + (`(unlet ,_) (format "ending let-binding of %s" symbol)) + (`(set nil) (format "setting %s to %S" symbol newval)) + (`(set ,buffer) (format "setting %s in %s to %S" + symbol buffer newval)) + (_ (format "watchpoint triggered %S" (cdr args)))) + ": ") + (setq pos (point)) + (insert ?\n)) ;; Debugger entered for an error. (`error (insert "--Lisp error: ") @@ -850,6 +865,53 @@ debugger-list-functions (princ "Note: if you have redefined a function, then it may no longer\n") (princ "be set to debug on entry, even if it is in the list.")))))) +(defun debug--implement-debug-watch (symbol newval op where) + "Conditionally call the debugger. +This function is called when SYMBOL's value is modified." + (if (or inhibit-debug-on-entry debugger-jumping-flag) + nil + (let ((inhibit-debug-on-entry t)) + (funcall debugger 'watchpoint symbol newval op where)))) + +;;;###autoload +(defun debug-watch (variable) + (interactive + (let* ((var-at-point (variable-at-point)) + (var (and (symbolp var-at-point) var-at-point)) + (val (completing-read + (concat "Debug when setting variable" + (if var (format " (default %s): " var) ": ")) + obarray #'boundp + t nil nil (and var (symbol-name var))))) + (list (if (equal val "") var (intern val))))) + (add-variable-watcher variable #'debug--implement-debug-watch)) + + +(defun debug--variable-list () + "List of variables currently set for debug on set." + (let ((vars '())) + (mapatoms + (lambda (s) + (when (memq #'debug--implement-debug-watch + (get s 'watchers)) + (push s vars)))) + vars)) + +;;;###autoload +(defun cancel-debug-watch (&optional variable) + (interactive + (list (let ((name + (completing-read + "Cancel debug on set for variable (default all variables): " + (mapcar #'symbol-name (debug--variable-list)) nil t))) + (when name + (unless (string= name "") + (intern name)))))) + (if variable + (remove-variable-watcher variable #'debug--implement-debug-watch) + (message "Canceling debug-watch for all variables") + (mapc #'cancel-debug-watch (debug--variable-list)))) + (provide 'debug) ;;; debug.el ends here -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0003-Ensure-redisplay-using-variable-watcher.patch >From 908d6b2bd1597ce69e99db404202c51e2292a40a Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 17:02:42 -0500 Subject: [PATCH v4 3/5] Ensure redisplay using variable watcher Instead of looking up the variable name in redisplay--variables when setting. * lisp/frame.el: Replace redisplay--variables with add-variable-watcher calls. * src/xdisp.c (Fset_buffer_redisplay): Rename from maybe_set_redisplay, set the redisplay flag unconditionally. (Vredisplay__variables): Remove it. * src/data.c (set_internal): Remove maybe_set_redisplay call. --- lisp/frame.el | 3 +-- src/data.c | 2 -- src/window.h | 1 - src/xdisp.c | 23 ++++++++++------------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lisp/frame.el b/lisp/frame.el index a584567..1dffc6c 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2249,9 +2249,8 @@ 'automatic-hscrolling 'window-system-version "it does not give useful information." "24.3") ;; Variables which should trigger redisplay of the current buffer. -(setq redisplay--variables (make-hash-table :test 'eq :size 10)) (mapc (lambda (var) - (puthash var 1 redisplay--variables)) + (add-variable-watcher var (symbol-function 'set-buffer-redisplay))) '(line-spacing overline-margin line-prefix diff --git a/src/data.c b/src/data.c index 44a08e6..27aac09 100644 --- a/src/data.c +++ b/src/data.c @@ -1276,8 +1276,6 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, default: emacs_abort (); } - maybe_set_redisplay (symbol); - start: switch (sym->redirect) { diff --git a/src/window.h b/src/window.h index a124b33..4a102f2 100644 --- a/src/window.h +++ b/src/window.h @@ -1063,7 +1063,6 @@ void set_window_buffer (Lisp_Object window, Lisp_Object buffer, extern void fset_redisplay (struct frame *f); extern void bset_redisplay (struct buffer *b); extern void bset_update_mode_line (struct buffer *b); -extern void maybe_set_redisplay (Lisp_Object); /* Call this to tell redisplay to look for other windows than selected-window that need to be redisplayed. Calling one of the *set_redisplay functions above already does it, so it's only needed in unusual cases. */ diff --git a/src/xdisp.c b/src/xdisp.c index 6e8af8a..9d36ab6 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -622,15 +622,15 @@ bset_update_mode_line (struct buffer *b) b->text->redisplay = true; } -void -maybe_set_redisplay (Lisp_Object symbol) -{ - if (HASH_TABLE_P (Vredisplay__variables) - && hash_lookup (XHASH_TABLE (Vredisplay__variables), symbol, NULL) >= 0) - { - bset_update_mode_line (current_buffer); - current_buffer->prevent_redisplay_optimizations_p = true; - } +DEFUN ("set-buffer-redisplay", Fset_buffer_redisplay, + Sset_buffer_redisplay, 4, 4, 0, + doc: /* Mark the current buffer for redisplay. +This function may be passed to `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object newval, Lisp_Object op, Lisp_Object where) +{ + bset_update_mode_line (current_buffer); + current_buffer->prevent_redisplay_optimizations_p = true; + return Qnil; } #ifdef GLYPH_DEBUG @@ -31319,6 +31319,7 @@ syms_of_xdisp (void) message_dolog_marker3 = Fmake_marker (); staticpro (&message_dolog_marker3); + defsubr (&Sset_buffer_redisplay); #ifdef GLYPH_DEBUG defsubr (&Sdump_frame_glyph_matrix); defsubr (&Sdump_glyph_matrix); @@ -31988,10 +31989,6 @@ or t (meaning all windows). */); doc: /* */); Vredisplay__mode_lines_cause = Fmake_hash_table (0, NULL); - DEFVAR_LISP ("redisplay--variables", Vredisplay__variables, - doc: /* A hash-table of variables changing which triggers a thorough redisplay. */); - Vredisplay__variables = Qnil; - DEFVAR_BOOL ("redisplay--inhibit-bidi", redisplay__inhibit_bidi, doc: /* Non-nil means it is not safe to attempt bidi reordering for display. */); /* Initialize to t, since we need to disable reordering until -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0004-Add-tests-for-watchpoints.patch >From bf239f14dbff006ba7a86d2c657cdf94122ba5d0 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 12 Dec 2015 23:10:15 -0500 Subject: [PATCH v4 4/5] Add tests for watchpoints * test/src/data-tests.el (data-tests-variable-watchers): (data-tests-local-variable-watchers): New tests. --- test/src/data-tests.el | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/src/data-tests.el b/test/src/data-tests.el index 0a29233..4c2ea54 100644 --- a/test/src/data-tests.el +++ b/test/src/data-tests.el @@ -255,3 +255,118 @@ test-bool-vector-binop (v2 (test-bool-vector-bv-from-hex-string "0000C")) (v3 (bool-vector-not v1))) (should (equal v2 v3)))) + +(ert-deftest data-tests-variable-watchers () + (defvar data-tests-var 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 1) + (should-have-watch-data '(data-tests-var 1 set nil)) + (let ((data-tests-var 2)) + (should-have-watch-data '(data-tests-var 2 let nil)) + (setq data-tests-var 3) + (should-have-watch-data '(data-tests-var 3 set nil))) + (should-have-watch-data '(data-tests-var 1 unlet nil)) + ;; `setq-default' on non-local variable is same as `setq'. + (setq-default data-tests-var 4) + (should-have-watch-data '(data-tests-var 4 set nil)) + (makunbound 'data-tests-var) + (should-have-watch-data '(data-tests-var nil makunbound nil)) + (setq data-tests-var 5) + (should-have-watch-data '(data-tests-var 5 set nil)) + (remove-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 6) + (should (null watch-data))))) + +(ert-deftest data-tests-varalias-watchers () + (defvar data-tests-var0 0) + (defvar data-tests-var1 0) + (defvar data-tests-var2 0) + (defvar data-tests-var3 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + ;; Watch var0, then alias it. + (add-variable-watcher 'data-tests-var0 collect-watch-data) + (defvaralias 'data-tests-var0-alias 'data-tests-var0) + (setq data-tests-var0 1) + (should-have-watch-data '(data-tests-var0 1 set nil)) + (setq data-tests-var0-alias 2) + (should-have-watch-data '(data-tests-var0 2 set nil)) + ;; Alias var1, then watch var1-alias. + (defvaralias 'data-tests-var1-alias 'data-tests-var1) + (add-variable-watcher 'data-tests-var1-alias collect-watch-data) + (setq data-tests-var1 1) + (should-have-watch-data '(data-tests-var1 1 set nil)) + (setq data-tests-var1-alias 2) + (should-have-watch-data '(data-tests-var1 2 set nil)) + ;; Alias var2, then watch it. + (defvaralias 'data-tests-var2-alias 'data-tests-var2) + (add-variable-watcher 'data-tests-var2 collect-watch-data) + (setq data-tests-var2 1) + (should-have-watch-data '(data-tests-var2 1 set nil)) + (setq data-tests-var2-alias 2) + (should-have-watch-data '(data-tests-var2 2 set nil)) + ;; Watch var3-alias, then make it alias var3 (this removes the + ;; watcher flag). + (defvar data-tests-var3-alias 0) + (add-variable-watcher 'data-tests-var3-alias collect-watch-data) + (defvaralias 'data-tests-var3-alias 'data-tests-var3) + (should-have-watch-data '(data-tests-var3-alias + data-tests-var3 defvaralias nil)) + (setq data-tests-var3 1) + (setq data-tests-var3-alias 2) + (should (null watch-data))))) + +(ert-deftest data-tests-local-variable-watchers () + (defvar-local data-tests-lvar 0) + (let* ((buf1 (current-buffer)) + (buf2 nil) + (watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf1)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf1)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf1))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf1)) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (with-temp-buffer + (setq buf2 (current-buffer)) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf2)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf2)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf2))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf2)) + (kill-local-variable 'data-tests-lvar) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2)) + (setq data-tests-lvar 3.5) + (should-have-watch-data `(data-tests-lvar 3.5 set ,buf2)) + (kill-all-local-variables) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2))) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (makunbound 'data-tests-lvar) + (should-have-watch-data '(data-tests-lvar nil makunbound nil)) + (setq data-tests-lvar 5) + (should-have-watch-data `(data-tests-lvar 5 set ,buf1)) + (remove-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 6) + (should (null watch-data))))) -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v4-0005-etc-NEWS-Add-entry-for-watchpoints.patch >From bc3dd88273a987c4b9d54b1b6161a3eb399a93eb Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 13 Dec 2015 14:47:58 -0500 Subject: [PATCH v4 5/5] * etc/NEWS: Add entry for watchpoints --- etc/NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index e29dfe2..7c5a592 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -626,6 +626,10 @@ two objects are 'eq' ('eql'), then the result of 'sxhash-eq' consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. +** New function `add-variable-watcher' can be used to execute code +when a symbol's value is changed. This is used to implement the new +debugger command `debug-watch'. + +++ ** Time conversion functions that accept a time zone rule argument now allow it to be OFFSET or a list (OFFSET ABBR), where the integer -- 2.9.3 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Thu Nov 10 22:13:22 2016 Received: (at control) by debbugs.gnu.org; 11 Nov 2016 03:13:22 +0000 Received: from localhost ([127.0.0.1]:52915 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52HK-0000Pa-EH for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:13:22 -0500 Received: from mail-it0-f46.google.com ([209.85.214.46]:38010) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52HI-0000PI-RD for control@debbugs.gnu.org; Thu, 10 Nov 2016 22:13:21 -0500 Received: by mail-it0-f46.google.com with SMTP id q124so89741177itd.1 for ; Thu, 10 Nov 2016 19:13:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:subject:date:message-id:mime-version; bh=HkBgFA9t7PnIrgMEsnS7CFGImKtYoqMk6yK9LOwNgMw=; b=K1wHx2BtBRpXYM1bp39w3lqed4Ify4grfTtxhTzGfa31Ae8H+5daAGIBfRwSvQcAcy 009VxvP5BIkKRFukUGmnLh94FDW2t4ruQAbXWEIlLEAhuT0ezjT4sxKiKTuvoe3fr6VL 7k0pHcjimw+rgNvLextcxxkPWDsr58LEGOEfvp5ty/X7ZD96mGLzpOY/PXnwosGBkIl5 2DJ+qPKVsVXgRlxP1LusDnzyrzd7od/WmtmVxP8gHS4Pn/p6o2lZsv7Y9CowmDH+wHL7 kUXgg+sqEeWaiDCrQzRGb6kfOdEjyvUH9W6g0g9pnBOEB3cLGdhi94aKypKr1l8wOcgb UtzQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:subject:date:message-id :mime-version; bh=HkBgFA9t7PnIrgMEsnS7CFGImKtYoqMk6yK9LOwNgMw=; b=XlTVgxrQ7kN3UPQ79i0dwdiU3InAAOCEhg3ZLsIEegEQI9nqGrqWwCKvCOg2P3yr0G wsBp+io0X8e328OaC14qWK870JzGvjjDEtzG6uFqhEpbe2jgHbVTc87op+awGCoBdJsb nZvClIJSjdgS3JdSXKZGUn6o9aRa463ch8UY2+0KFl59Vaiwc22uOjnzhgMNrqWV7kII FsF0MYKfnKoRPMhqT3zpj5EqkGp6ixNe7hWZfD/7lZM1lc6nv67eYv7EIB2P1Vf3vtpn fXKz0xCsdQoMxN7Uwvag6Ji6O24OAhUranFYprPvQ6w22VbCdhFMbSqV69hPJ+zGPxXJ OTAg== X-Gm-Message-State: ABUngvfprH6hWAmoqoNUzbX7FkZgde9LnVfPuSfX6Go5vDzdUxpkneyXyEfdcHU4S1clhA== X-Received: by 10.107.150.10 with SMTP id y10mr3952467iod.27.1478833995165; Thu, 10 Nov 2016 19:13:15 -0800 (PST) Received: from zony ([45.2.7.130]) by smtp.googlemail.com with ESMTPSA id k198sm11179387itb.18.2016.11.10.19.13.14 for (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 10 Nov 2016 19:13:14 -0800 (PST) From: npostavs@users.sourceforge.net To: control@debbugs.gnu.org Subject: control message for bug #24923 Date: Thu, 10 Nov 2016 22:14:01 -0500 Message-ID: <87twben1xi.fsf@users.sourceforge.net> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -0.2 (/) X-Debbugs-Envelope-To: control X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.2 (/) # Didn't mean to make this bug for 25.1, # it would go into 26.1 actually. retitle 24923 Lisp watchpoints notfound 25.1 quit From debbugs-submit-bounces@debbugs.gnu.org Thu Nov 10 22:16:10 2016 Received: (at control) by debbugs.gnu.org; 11 Nov 2016 03:16:10 +0000 Received: from localhost ([127.0.0.1]:52921 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52K1-0001Xj-QE for submit@debbugs.gnu.org; Thu, 10 Nov 2016 22:16:09 -0500 Received: from mail-it0-f53.google.com ([209.85.214.53]:35609) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c52Jz-0001RC-VB for control@debbugs.gnu.org; Thu, 10 Nov 2016 22:16:08 -0500 Received: by mail-it0-f53.google.com with SMTP id e187so303047729itc.0 for ; Thu, 10 Nov 2016 19:16:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:subject:date:message-id:mime-version; bh=PrRpCOxpB7nNDwe4DrN1TAcgQ4yIyzziBSM0Oj0Bd1w=; b=IYHtGIPY6Ugz6S/t1C1UthW/clzPFuT+viU3Ht3Nhqo3M4AO3HDNfzNnkuTnAPXR3x gGgqNuO3uqaAkjIKCwPl3VejF8WBwNaiUOjG8DUbJsUeeZI+sR02aJbehYNa2n7y6Dpe Glih/jESOwlkToExVKgReas1D5TQPa9RSKEgHFGO2mIxqL4HJX1a7w1QPu9YCIVn6XQP o23WiLs7i259ATc4Z2BFM3JdzdFCv13N+nmqjmmAL+z2/YVnUiYSEen3kHZnQO67KC4U qGvqkwPN40czdP4E7bpTo3gnT0R9JHcerVWNLVRXClfB+v6evkkJaIFioTFKZTW/NeTx aSwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:subject:date:message-id :mime-version; bh=PrRpCOxpB7nNDwe4DrN1TAcgQ4yIyzziBSM0Oj0Bd1w=; b=iEWuKASPdQsMi9xP5eHPlIJfCoKFRmb9aJM37moriVmQFj+0t5EGxrevJ73mfXpNhV liDiEYhkiGKEBWYXk+O9ZnUAenQ9QgcD5fC4gm/hnvabH5tg6NIzkNAgR0kYI3Y3HEOm 0c5YQX8aiHniiGbNgTOK6TOM7EEtS7GH35qCT4IXk0qw2c1TNSUYdFR1LCteH6mTE5C6 vONnoEa7+jaeJoI2x/9FK0YYR1JAgDhtPABjVYYj5Yx1KiN7osNC8ML8Pg4Lkn2mJZgB PePJ4Z0xFMTREnEFo3N6/gLg0heAsZuuYWmfCkK8GzDWpTIkJp2eyUB7GnxmtjLLoJuL ZVpg== X-Gm-Message-State: ABUngveL19QfiM5T1I0ekrd0gx8l2PBLaIJRpSjH+wpL0sRmiUeJUIZTX2Jg+z94rFPSlg== X-Received: by 10.107.138.142 with SMTP id c14mr11188169ioj.213.1478834162259; Thu, 10 Nov 2016 19:16:02 -0800 (PST) Received: from zony ([45.2.7.130]) by smtp.googlemail.com with ESMTPSA id v197sm12782968ita.0.2016.11.10.19.16.01 for (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 10 Nov 2016 19:16:01 -0800 (PST) From: npostavs@users.sourceforge.net To: control@debbugs.gnu.org Subject: control message for bug #24923 Date: Thu, 10 Nov 2016 22:16:48 -0500 Message-ID: <87shqyn1sv.fsf@users.sourceforge.net> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -0.2 (/) X-Debbugs-Envelope-To: control X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.2 (/) notfound 24923 25.1 quit From debbugs-submit-bounces@debbugs.gnu.org Fri Nov 11 05:02:51 2016 Received: (at 24923) by debbugs.gnu.org; 11 Nov 2016 10:02:51 +0000 Received: from localhost ([127.0.0.1]:53002 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c58fa-0006NJ-Te for submit@debbugs.gnu.org; Fri, 11 Nov 2016 05:02:51 -0500 Received: from eggs.gnu.org ([208.118.235.92]:35129) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c58fY-0006N6-Mn for 24923@debbugs.gnu.org; Fri, 11 Nov 2016 05:02:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c58fQ-0002XC-9p for 24923@debbugs.gnu.org; Fri, 11 Nov 2016 05:02:43 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:58228) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c58fQ-0002X6-60; Fri, 11 Nov 2016 05:02:40 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1416 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c58fO-0003RL-Tm; Fri, 11 Nov 2016 05:02:39 -0500 Date: Fri, 11 Nov 2016 12:02:42 +0200 Message-Id: <83eg2ie3lp.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87vavun235.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Date: Thu, 10 Nov 2016 22:10:38 -0500 > > Continuing from > https://lists.nongnu.org/archive/html/emacs-devel/2015-11/msg01437.html, > main code difference since then is that I use a subr instead of indexing > into a table of C functions (the reason for using an index was to avoid > GC on Ffuncall; instead I did that by checking if the function is SUBRP > and doing funcall_subr on it (a new function extracted from Ffuncall)). Thanks. A few comments below. One general comment is that these patches are harder to review due to whitespace changes TAB->spaces. How about using some non-default options to "git diff" when generating the patch? > >From 0507854b885681c2e57bb1c6cb97f898417bd99c Mon Sep 17 00:00:00 2001 This changeset was attached twice, for some reason. > --- a/src/data.c > +++ b/src/data.c > @@ -649,9 +649,10 @@ DEFUN ("makunbound", Fmakunbound, Smakunbound, 1, 1, 0, > (register Lisp_Object symbol) > { > CHECK_SYMBOL (symbol); > - if (SYMBOL_CONSTANT_P (symbol)) > + if (XSYMBOL (symbol)->trapped_write == SYMBOL_NOWRITE) > xsignal1 (Qsetting_constant, symbol); > - Fset (symbol, Qunbound); > + else > + Fset (symbol, Qunbound); > return symbol; Why was this needed? Doesn't xsignal1 never return anymore? > +static void > +reset_symbol_trapped_write (Lisp_Object symbol) > +{ > + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); > +} Suggest to find a better name for this function, like restore_symbol_trapped_write, for example. Calling an action that sets an attribute "reset" makes it harder to read the code. > +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, > + 2, 2, 0, > + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. */) I think the doc string needs to mention that the function also affects all of SYMBOL's aliases. > +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, > + 2, 2, 0, > + doc: /* Undo the effect of `add-variable-watcher'. */) The doc string should mention SYMBOL. Also, preferably have the doc string be self-contained, since that's easy in this case. > + (Lisp_Object symbol, Lisp_Object watch_function) > +{ > + symbol = Findirect_variable (symbol); > + Lisp_Object watchers = Fget (symbol, Qwatchers); > + watchers = Fdelete (watch_function, watchers); > + if (NILP (watchers)) > + { > + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); > + map_obarray (Vobarray, harmonize_variable_watchers, symbol); > + } > + Fput (symbol, Qwatchers, watchers); > + return Qnil; Would it be more useful for this and add-variable-watcher to return the list of watchers instead? > + /* Avoid recursion. */ > + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); > + ptrdiff_t count = SPECPDL_INDEX (); > + record_unwind_protect (reset_symbol_trapped_write, symbol); I think record_unwind_protect should be called before setting the attribute, for this code to be safer and more future-proof. > +/* Value is non-zero if symbol cannot be changed through a simple set, > + i.e. it's a constant (e.g. nil, t, :keywords), or it has some > + watching functions. */ > > INLINE int > -(SYMBOL_CONSTANT_P) (Lisp_Object sym) > +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) > { > - return lisp_h_SYMBOL_CONSTANT_P (sym); > + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); > } > > /* Placeholder for make-docfile to process. The actual symbol > @@ -3289,6 +3299,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) > XSYMBOL (sym)->next = next; > } > > +INLINE void > +make_symbol_constant (Lisp_Object sym) > +{ > + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; > +} > + So we will have make_symbol_constant, but no SYMBOL_CONSTANT_P? Isn't that confusing? Some C code may wish to know whether a symbol is constant, not just either constant or trap-on-write; why not give them what they want? > + ;; Watchpoint triggered. > + ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) > + (insert > + "--" > + (pcase details > + (`(makunbound ,_) (format "Making %s void" symbol)) > + (`(let ,_) (format "let-binding %s to %S" symbol newval)) > + (`(unlet ,_) (format "ending let-binding of %s" symbol)) > + (`(set nil) (format "setting %s to %S" symbol newval)) > + (`(set ,buffer) (format "setting %s in %s to %S" ^^^^^^^^^^^^^^^^^^^^^^^^ IMO, this should include the word "buffer", otherwise it can be confusing. > + (_ (format "watchpoint triggered %S" (cdr args)))) Can you give a couple of examples of this, with %S shown explicitly? I'm not sure whether the result will be self-explanatory. > +;;;###autoload > +(defun debug-watch (variable) > + (interactive > + (let* ((var-at-point (variable-at-point)) > + (var (and (symbolp var-at-point) var-at-point)) > + (val (completing-read > + (concat "Debug when setting variable" > + (if var (format " (default %s): " var) ": ")) I think all the other commands/variables similar to this one are named debug-on-SOMETHING, so perhaps this one should also have such a name. Like debug-on-setting-variable, perhaps? > +** New function `add-variable-watcher' can be used to execute code ^^^^^^^^^^^^^^^ It is better to say "to call a function" here. Thanks again for working on this. From debbugs-submit-bounces@debbugs.gnu.org Fri Nov 11 23:33:54 2016 Received: (at 24923) by debbugs.gnu.org; 12 Nov 2016 04:33:54 +0000 Received: from localhost ([127.0.0.1]:54398 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5Q0n-0004Mv-Lh for submit@debbugs.gnu.org; Fri, 11 Nov 2016 23:33:53 -0500 Received: from mail-it0-f45.google.com ([209.85.214.45]:38485) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5Q0m-0004Mj-El for 24923@debbugs.gnu.org; Fri, 11 Nov 2016 23:33:52 -0500 Received: by mail-it0-f45.google.com with SMTP id q124so16018209itd.1 for <24923@debbugs.gnu.org>; Fri, 11 Nov 2016 20:33:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=CL/PF/xbMk/wOxpP9YEaU/VGmMqJYii/+ghLt3WZWcs=; b=vIPI+3mO6KUEwi6k6u7ynGudZrmfG2CXL8cPuHLsxWJxrXhp4QsHrufsdkDqJZXFTz 3Q/YFE1GYy3X/szsiiLl/XY/EJGHligB4FwAnNGj1aVU7YlGqA4c1mfok1T68hzl7fhx u2pzIGtlkVZ6U97avMPC5pgaoCunCHaUrqE5NLhIWRzUVjXMGWEjk+pp5KCr61AXQqV2 Mecd3HqDa5DzD1M+K6DSihUsdM+l7AMx63YfQk4zyZa75OOgdqy0xmPGCV7x3BCTmRFR Bozn6k5b9tO1Naw1742eRtrAZOkZN706vMB5HVbylSYpYpY1EUfX8CDc3NIogDvuEuj5 CQog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=CL/PF/xbMk/wOxpP9YEaU/VGmMqJYii/+ghLt3WZWcs=; b=D53GDWv73mR/M/uI5ZALc50BFDIJc2lqrKkNTrn+KyziNuQXdMNOKgO2WzXViivTVU U77N9nSRVTZ8Z5TjbdxbNiTslcVo5YTKmbXOw2QBhYcQovVVBJyhaPYW4tu033cu0fJ9 0QftGWkPAg+MoQkAEqZAYUy30fDxPi4d535/7rCkOmCgskbYUsNB35Jr5Ri6Cavswkmm S0shKmJbBbhVf5PytH4jv/n29mLmFCortqf9/JxvkizVb9IMlMnmYpkLnE3Pvy8oHgtU hxoOtgmKxqVrEayRbGD8CB2Q6FF78H1QJ5/wmErBbqpxrWb4Jw/IeTvndDQ5/76PGtj/ fAHw== X-Gm-Message-State: ABUngvf75Agl8v3AMvZWM49iK9YciVvT58nkWRZoZSgfB5V4FwG31B7x/ZyvEKjLKXDoqA== X-Received: by 10.107.164.2 with SMTP id n2mr11957238ioe.202.1478925226827; Fri, 11 Nov 2016 20:33:46 -0800 (PST) Received: from zony ([45.2.7.130]) by smtp.googlemail.com with ESMTPSA id o71sm5152752ita.6.2016.11.11.20.33.45 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 11 Nov 2016 20:33:46 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> Date: Fri, 11 Nov 2016 23:34:33 -0500 In-Reply-To: <83eg2ie3lp.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 11 Nov 2016 12:02:42 +0200") Message-ID: <87pom1mi3q.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -0.2 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.2 (/) Eli Zaretskii writes: > One general comment is that these patches are harder to review due to > whitespace changes TAB->spaces. How about using some non-default > options to "git diff" when generating the patch? Will do for the next one. > >> >From 0507854b885681c2e57bb1c6cb97f898417bd99c Mon Sep 17 00:00:00 2001 > > This changeset was attached twice, for some reason. Oops, I was trying to insert all the attachments at once in gnus via some text manipulation and I forgot to delete the first one I copied from. > >> --- a/src/data.c >> +++ b/src/data.c >> @@ -649,9 +649,10 @@ DEFUN ("makunbound", Fmakunbound, Smakunbound, 1, 1, 0, >> (register Lisp_Object symbol) >> { >> CHECK_SYMBOL (symbol); >> - if (SYMBOL_CONSTANT_P (symbol)) >> + if (XSYMBOL (symbol)->trapped_write == SYMBOL_NOWRITE) >> xsignal1 (Qsetting_constant, symbol); >> - Fset (symbol, Qunbound); >> + else >> + Fset (symbol, Qunbound); >> return symbol; > > Why was this needed? Doesn't xsignal1 never return anymore? Hmm, I'm not sure why I did this. I think this whole hunk can be dropped (as long as SYMBOL_CONSTANT_P is preserved as suggested below). > >> + (Lisp_Object symbol, Lisp_Object watch_function) >> +{ >> + symbol = Findirect_variable (symbol); >> + Lisp_Object watchers = Fget (symbol, Qwatchers); >> + watchers = Fdelete (watch_function, watchers); >> + if (NILP (watchers)) >> + { >> + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); >> + map_obarray (Vobarray, harmonize_variable_watchers, symbol); >> + } >> + Fput (symbol, Qwatchers, watchers); >> + return Qnil; > > Would it be more useful for this and add-variable-watcher to return > the list of watchers instead? I don't think it would be especially useful, but it's easy to do so. > >> +/* Value is non-zero if symbol cannot be changed through a simple set, >> + i.e. it's a constant (e.g. nil, t, :keywords), or it has some >> + watching functions. */ >> >> INLINE int >> -(SYMBOL_CONSTANT_P) (Lisp_Object sym) >> +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) >> { >> - return lisp_h_SYMBOL_CONSTANT_P (sym); >> + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); >> } >> >> /* Placeholder for make-docfile to process. The actual symbol >> @@ -3289,6 +3299,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) >> XSYMBOL (sym)->next = next; >> } >> >> +INLINE void >> +make_symbol_constant (Lisp_Object sym) >> +{ >> + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; >> +} >> + > > So we will have make_symbol_constant, but no SYMBOL_CONSTANT_P? Isn't > that confusing? Some C code may wish to know whether a symbol is > constant, not just either constant or trap-on-write; why not give them > what they want? I suppose I took Stefan's advice to replace SYMBOL_CONSTANT_P with SYMBOL_TRAPPED_WRITE_P too literally. Although it's almost always the case that C code checking if a symbol is constant wants to write to the symbol, in which case it should be doing a 3-way switch (writable, trapped write, or constant). But there is one exception in Fmakunbound (mentioned above), so I guess we may as well keep it. > >> + (_ (format "watchpoint triggered %S" (cdr args)))) > > Can you give a couple of examples of this, with %S shown explicitly? > I'm not sure whether the result will be self-explanatory. You mean examples of this this clause being used? It was meant more as a catchall in case some watch types were missed by the previous clauses. It shouldn't really ever happen unless the debugger and watchpoint code get out of sync. Do you think it would be better to just signal an error? (although would signalling an error while the debugger is invoked cause trouble?) > >> +;;;###autoload >> +(defun debug-watch (variable) >> + (interactive >> + (let* ((var-at-point (variable-at-point)) >> + (var (and (symbolp var-at-point) var-at-point)) >> + (val (completing-read >> + (concat "Debug when setting variable" >> + (if var (format " (default %s): " var) ": ")) > > I think all the other commands/variables similar to this one are named > debug-on-SOMETHING, so perhaps this one should also have such a name. > Like debug-on-setting-variable, perhaps? Hah, I initially called this debug-on-set (https://lists.nongnu.org/archive/html/emacs-devel/2015-11/txtyjJDztIULG.txt), and then you suggested debug-watch instead (https://lists.nongnu.org/archive/html/emacs-devel/2015-11/msg02017.html). An alias probably makes sense, how about debug-on-variable-change? (since it catches some changes other than `set'.) From debbugs-submit-bounces@debbugs.gnu.org Sat Nov 12 02:19:25 2016 Received: (at 24923) by debbugs.gnu.org; 12 Nov 2016 07:19:25 +0000 Received: from localhost ([127.0.0.1]:54407 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5Saz-0008WU-Dx for submit@debbugs.gnu.org; Sat, 12 Nov 2016 02:19:25 -0500 Received: from eggs.gnu.org ([208.118.235.92]:45873) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5Sax-0008WD-IV for 24923@debbugs.gnu.org; Sat, 12 Nov 2016 02:19:23 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c5Sap-0004wb-5C for 24923@debbugs.gnu.org; Sat, 12 Nov 2016 02:19:18 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:59345) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c5Sap-0004wW-1E; Sat, 12 Nov 2016 02:19:15 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3021 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c5Sao-0004j2-BB; Sat, 12 Nov 2016 02:19:14 -0500 Date: Sat, 12 Nov 2016 09:19:21 +0200 Message-Id: <83r36hcghy.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87pom1mi3q.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org > Date: Fri, 11 Nov 2016 23:34:33 -0500 > > >> + (_ (format "watchpoint triggered %S" (cdr args)))) > > > > Can you give a couple of examples of this, with %S shown explicitly? > > I'm not sure whether the result will be self-explanatory. > > You mean examples of this this clause being used? It was meant more as > a catchall in case some watch types were missed by the previous clauses. > It shouldn't really ever happen unless the debugger and watchpoint code > get out of sync. Do you think it would be better to just signal an > error? (although would signalling an error while the debugger is > invoked cause trouble?) Either signal an error, or include something like "(please submit a bug report)" in the text. > >> +;;;###autoload > >> +(defun debug-watch (variable) > >> + (interactive > >> + (let* ((var-at-point (variable-at-point)) > >> + (var (and (symbolp var-at-point) var-at-point)) > >> + (val (completing-read > >> + (concat "Debug when setting variable" > >> + (if var (format " (default %s): " var) ": ")) > > > > I think all the other commands/variables similar to this one are named > > debug-on-SOMETHING, so perhaps this one should also have such a name. > > Like debug-on-setting-variable, perhaps? > > Hah, I initially called this debug-on-set > (https://lists.nongnu.org/archive/html/emacs-devel/2015-11/txtyjJDztIULG.txt), > and then you suggested debug-watch instead > (https://lists.nongnu.org/archive/html/emacs-devel/2015-11/msg02017.html). > > An alias probably makes sense, how about debug-on-variable-change? > (since it catches some changes other than `set'.) Fine with me, thanks. From debbugs-submit-bounces@debbugs.gnu.org Sat Nov 12 19:53:24 2016 Received: (at 24923) by debbugs.gnu.org; 13 Nov 2016 00:53:24 +0000 Received: from localhost ([127.0.0.1]:55265 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5j2w-00039E-Ft for submit@debbugs.gnu.org; Sat, 12 Nov 2016 19:53:24 -0500 Received: from mail-it0-f49.google.com ([209.85.214.49]:37355) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5j2u-000390-6b for 24923@debbugs.gnu.org; Sat, 12 Nov 2016 19:53:21 -0500 Received: by mail-it0-f49.google.com with SMTP id u205so45881907itc.0 for <24923@debbugs.gnu.org>; Sat, 12 Nov 2016 16:53:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=qUI5nAXZpxf9iEmocC7bAddcPT7kzJ4a+jlehAI2Dtw=; b=zbk9iJFMyhrYSQHv9fL/95I3gZyY9GtUDcJIkFR1bZ2yhjqOQDoc4PH7oQXgEu5K5Y vc1OznRv+/oe/SeMnKgMHRmzf+up6MciH2JxrveTm5JOs6VHdkafEaV8gZJkMSFzTo00 YzqaNgP1vandES7FlRJlkNQlLGW03zDCSYo7HsmeoPGMml4be52YZSkhDBuN5sgRkGkv pUaUil3xB2HQ+8uvj6u7fXpwWWlCTklkBhXzbC9RKJrzCV65LL7nXQdN12YpcaClo9vW XGyzuj6O6LDYJN/2UiGiz7ywXEZWbgeHStDoBlhsGw45Zwd1/0asbp6CUbJLCKN29ZFa DLXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=qUI5nAXZpxf9iEmocC7bAddcPT7kzJ4a+jlehAI2Dtw=; b=XSsLoKwna+WhaQTYP30vr6tPr5U9ZjgIaeokkPOUcZRtX8edtgW/up00OAFVIDe/OE rn/zUZnC9NB71COjxpLC8YmXU1yUENr3qEYZIbSXmPCV3ZfJk8ey3UKHYznHNdoH5VZK vHeJx/2oHF/UN670UL4/xs0KRhryYOeNnEiS3D9x1dZEM+y+HUiFqs+Bajh9YW+slP9f eK1GqDfrSGSzrwuRnmuctyZUGk7BXk6RrWj5B+cpvFzKIvg3u2CE2AdEdRbG9LlJvZmu W43jIV7YbYuj6PFc3ysRvwePG0GG57TQam2MBRCt2aHtTJ1QDxF/cY+4au59blau9Ddo Gu/A== X-Gm-Message-State: ABUngvel9nRY1sbIQZ9M/6sdzQ6GLsjeH1w06XQxs/1OxfOdyHA0Q1WN0N9qeN6APc+dnQ== X-Received: by 10.107.1.138 with SMTP id 132mr17985728iob.72.1478998394378; Sat, 12 Nov 2016 16:53:14 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id p20sm12766875itc.2.2016.11.12.16.53.12 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sat, 12 Nov 2016 16:53:13 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> Date: Sat, 12 Nov 2016 19:54:01 -0500 In-Reply-To: <83r36hcghy.fsf@gnu.org> (Eli Zaretskii's message of "Sat, 12 Nov 2016 09:19:21 +0200") Message-ID: <87fumwmc7q.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: -0.2 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.2 (/) --=-=-= Content-Type: text/plain Eli Zaretskii writes: >> From: npostavs@users.sourceforge.net >> Cc: 24923@debbugs.gnu.org >> Date: Fri, 11 Nov 2016 23:34:33 -0500 >> >> >> + (_ (format "watchpoint triggered %S" (cdr args)))) >> > >> > Can you give a couple of examples of this, with %S shown explicitly? >> > I'm not sure whether the result will be self-explanatory. >> >> You mean examples of this this clause being used? It was meant more as >> a catchall in case some watch types were missed by the previous clauses. >> It shouldn't really ever happen unless the debugger and watchpoint code >> get out of sync. Do you think it would be better to just signal an >> error? (although would signalling an error while the debugger is >> invoked cause trouble?) > > Either signal an error, or include something like "(please submit a > bug report)" in the text. Here is the updated patch, created with -b. I went with a call to `error'. And actually, I had missed a couple of watchpoint types. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v5-b-0001-Add-lisp-watchpoints.patch Content-Description: patch >From dc2e844f824e558711befebd97fb6535e8f47bc2 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 19 Nov 2015 19:50:06 -0500 Subject: [PATCH v5 1/5] Add lisp watchpoints This allows to call a function whenever a symbol-value is changed. * src/lisp.h (lisp_h_SYMBOL_TRAPPED_WRITE_P): (SYMBOL_TRAPPED_WRITE_P): New function/macro. (lisp_h_SYMBOL_CONSTANT_P): Check for SYMBOL_NOWRITE specifically. (enum symbol_trapped_write): New enumeration. (struct Lisp_Symbol): Rename field constant to trapped_write. (make_symbol_constant): New function. * src/data.c (Fadd_variable_watcher, Fremove_variable_watcher): (set_symbol_trapped_write, restore_symbol_trapped_write): (harmonize_variable_watchers, notify_variable_watchers): New functions. * src/data.c (Fset_default): Call `notify_variable_watchers' for trapped symbols. (set_internal): Change bool argument BIND to 3-value enum and call `notify_variable_watchers' for trapped symbols. * src/data.c (syms_of_data): * src/data.c (syms_of_data): * src/font.c (syms_of_font): * src/lread.c (intern_sym, init_obarray): * src/buffer.c (syms_of_buffer): Use make_symbol_constant. * src/alloc.c (init_symbol): * src/bytecode.c (exec_byte_code): Use SYMBOL_TRAPPED_WRITE_P. * src/data.c (Fmake_variable_buffer_local, Fmake_local_variable): (Fmake_variable_frame_local): * src/eval.c (Fdefvaralias, specbind): Refer to Lisp_Symbol's trapped_write instead of constant. (Ffuncall): Move subr calling code into separate function. (funcall_subr): New function. --- src/alloc.c | 2 +- src/buffer.c | 22 +++++-- src/bytecode.c | 4 +- src/data.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/eval.c | 168 ++++++++++++++++++++++++++++-------------------------- src/font.c | 6 +- src/lisp.h | 54 +++++++++++++++--- src/lread.c | 6 +- 8 files changed, 322 insertions(+), 117 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index a58dc13..f373f6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3562,7 +3562,7 @@ init_symbol (Lisp_Object val, Lisp_Object name) set_symbol_next (val, NULL); p->gcmarkbit = false; p->interned = SYMBOL_UNINTERNED; - p->constant = 0; + p->trapped_write = SYMBOL_UNTRAPPED_WRITE; p->declared_special = false; p->pinned = false; } diff --git a/src/buffer.c b/src/buffer.c index 3d205bb..cc75cdb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -984,9 +984,13 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, Qnil); else { - Lisp_Object tmp, prop, last = Qnil; + Lisp_Object tmp, last = Qnil; for (tmp = BVAR (b, local_var_alist); CONSP (tmp); tmp = XCDR (tmp)) - if (!NILP (prop = Fget (XCAR (XCAR (tmp)), Qpermanent_local))) + { + Lisp_Object local_var = XCAR (XCAR (tmp)); + Lisp_Object prop = Fget (local_var, Qpermanent_local); + + if (!NILP (prop)) { /* If permanent-local, keep it. */ last = tmp; @@ -1010,7 +1014,12 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) || !NILP (Fget (elt, Qpermanent_local_hook))) newlist = Fcons (elt, newlist); } - XSETCDR (XCAR (tmp), Fnreverse (newlist)); + newlist = Fnreverse (newlist); + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, newlist, + Qmakunbound, Fcurrent_buffer ()); + XSETCDR (XCAR (tmp), newlist); + continue; /* Don't do variable write trapping twice. */ } } /* Delete this local variable. */ @@ -1018,6 +1027,11 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, XCDR (tmp)); else XSETCDR (last, XCDR (tmp)); + + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, Qnil, + Qmakunbound, Fcurrent_buffer ()); + } } for (i = 0; i < last_per_buffer_idx; ++i) @@ -5682,7 +5696,7 @@ syms_of_buffer (void) This variable is buffer-local but you cannot set it directly; use the function `set-buffer-multibyte' to change a buffer's representation. See also Info node `(elisp)Text Representations'. */); - XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1; + make_symbol_constant (intern_c_string ("enable-multibyte-characters")); DEFVAR_PER_BUFFER ("buffer-file-coding-system", &BVAR (current_buffer, buffer_file_coding_system), Qnil, diff --git a/src/bytecode.c b/src/bytecode.c index e2d8ab7..18eaf9f 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -569,10 +569,10 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (SYMBOLP (sym) && !EQ (val, Qunbound) && !XSYMBOL (sym)->redirect - && !SYMBOL_CONSTANT_P (sym)) + && !SYMBOL_TRAPPED_WRITE_P (sym)) SET_SYMBOL_VAL (XSYMBOL (sym), val); else - set_internal (sym, val, Qnil, false); + set_internal (sym, val, Qnil, SET_INTERNAL_SET); } NEXT; diff --git a/src/data.c b/src/data.c index d221db4..a071261 100644 --- a/src/data.c +++ b/src/data.c @@ -1225,7 +1225,7 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */) (register Lisp_Object symbol, Lisp_Object newval) { - set_internal (symbol, newval, Qnil, 0); + set_internal (symbol, newval, Qnil, SET_INTERNAL_SET); return newval; } @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, If buffer/frame-locality is an issue, WHERE specifies which context to use. (nil stands for the current buffer/frame). - If BINDFLAG is false, then if this symbol is supposed to become - local in every buffer where it is set, then we make it local. - If BINDFLAG is true, we don't do that. */ + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to + become local in every buffer where it is set, then we make it + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we + don't do that. */ void set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, - bool bindflag) + enum Set_Internal_Bind bindflag) { bool voide = EQ (newval, Qunbound); struct Lisp_Symbol *sym; @@ -1250,18 +1251,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; */ CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) || !EQ (newval, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return; + + case SYMBOL_TRAPPED_WRITE: + notify_variable_watchers (symbol, voide? Qnil : newval, + (bindflag == SET_INTERNAL_BIND? Qlet : + bindflag == SET_INTERNAL_UNBIND? Qunlet : + voide? Qmakunbound : Qset), + where); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } maybe_set_redisplay (symbol); - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1386,6 +1400,111 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; } +static void +set_symbol_trapped_write (Lisp_Object symbol, enum symbol_trapped_write trap) +{ + struct Lisp_Symbol* sym = XSYMBOL (symbol); + if (sym->trapped_write == SYMBOL_NOWRITE) + xsignal1 (Qtrapping_constant, symbol); + else if (sym->redirect == SYMBOL_LOCALIZED && + SYMBOL_BLV (sym)->frame_local) + xsignal1 (Qtrapping_frame_local, symbol); + sym->trapped_write = trap; +} + +static void +restore_symbol_trapped_write (Lisp_Object symbol) +{ + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); +} + +static void +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) +{ + if (!EQ (base_variable, alias) && + EQ (base_variable, Findirect_variable (alias))) + set_symbol_trapped_write + (alias, XSYMBOL (base_variable)->trapped_write); +} + +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, + 2, 2, 0, + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. +All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + + Lisp_Object watchers = Fget (symbol, Qwatchers); + Lisp_Object member = Fmember (watch_function, watchers); + if (NILP (member)) + Fput (symbol, Qwatchers, Fcons (watch_function, watchers)); + return Qnil; +} + +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, + 2, 2, 0, + doc: /* Undo the effect of `add-variable-watcher'. Remove +Rmove WATCH-FUNCTION from the list of functions to be called when +SYMBOL (or its aliases) are set. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + Lisp_Object watchers = Fget (symbol, Qwatchers); + watchers = Fdelete (watch_function, watchers); + if (NILP (watchers)) + { + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + } + Fput (symbol, Qwatchers, watchers); + return Qnil; +} + +void +notify_variable_watchers (Lisp_Object symbol, + Lisp_Object newval, + Lisp_Object operation, + Lisp_Object where) +{ + symbol = Findirect_variable (symbol); + + ptrdiff_t count = SPECPDL_INDEX (); + record_unwind_protect (restore_symbol_trapped_write, symbol); + /* Avoid recursion. */ + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + + if (NILP (where) && + !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) && + !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) + { + XSETBUFFER (where, current_buffer); + } + + if (EQ (operation, Qset_default)) + operation = Qset; + + for (Lisp_Object watchers = Fget (symbol, Qwatchers); + CONSP (watchers); + watchers = XCDR (watchers)) + { + Lisp_Object watcher = XCAR (watchers); + /* Call subr directly to avoid gc. */ + if (SUBRP (watcher)) + { + Lisp_Object args[] = { symbol, newval, operation, where }; + funcall_subr (XSUBR (watcher), ARRAYELTS (args), args); + } + else + CALLN (Ffuncall, watcher, symbol, newval, operation, where); + } + + unbind_to (count, Qnil); +} + + /* Access or set a buffer-local symbol's default value. */ /* Return the default value of SYMBOL, but don't check for voidness. @@ -1471,16 +1590,27 @@ DEFUN ("set-default", Fset_default, Sset_default, 2, 2, 0, struct Lisp_Symbol *sym; CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (value, Fdefault_value (symbol))) + || !EQ (value, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return value; + + case SYMBOL_TRAPPED_WRITE: + /* Don't notify here if we're going to call Fset anyway. */ + if (sym->redirect != SYMBOL_PLAINVAL) + notify_variable_watchers (symbol, value, Qset_default, Qnil); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1651,7 +1781,7 @@ DEFUN ("make-variable-buffer-local", Fmake_variable_buffer_local, default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_CONSTANT_P (variable)) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); if (!blv) @@ -1726,7 +1856,7 @@ DEFUN ("make-local-variable", Fmake_local_variable, Smake_local_variable, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); @@ -1838,6 +1968,9 @@ DEFUN ("kill-local-variable", Fkill_local_variable, Skill_local_variable, default: emacs_abort (); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (variable, Qnil, Qmakunbound, Fcurrent_buffer ()); + /* Get rid of this buffer's alist element, if any. */ XSETSYMBOL (variable, sym); /* Propagate variable indirection. */ tem = Fassq (variable, BVAR (current_buffer, local_var_alist)); @@ -1920,7 +2053,7 @@ DEFUN ("make-variable-frame-local", Fmake_variable_frame_local, Smake_variable_f default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_TRAPPED_WRITE_P (variable)) error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable))); blv = make_blv (sym, forwarded, valcontents); @@ -3471,6 +3604,8 @@ syms_of_data (void) DEFSYM (Qcyclic_variable_indirection, "cyclic-variable-indirection"); DEFSYM (Qvoid_variable, "void-variable"); DEFSYM (Qsetting_constant, "setting-constant"); + DEFSYM (Qtrapping_constant, "trapping-constant"); + DEFSYM (Qtrapping_frame_local, "trapping-frame-local"); DEFSYM (Qinvalid_read_syntax, "invalid-read-syntax"); DEFSYM (Qinvalid_function, "invalid-function"); @@ -3549,6 +3684,10 @@ syms_of_data (void) PUT_ERROR (Qvoid_variable, error_tail, "Symbol's value as variable is void"); PUT_ERROR (Qsetting_constant, error_tail, "Attempt to set a constant symbol"); + PUT_ERROR (Qtrapping_constant, error_tail, + "Attempt to trap writes to a constant symbol"); + PUT_ERROR (Qtrapping_frame_local, error_tail, + "Attempt to trap writes to a frame local variable"); PUT_ERROR (Qinvalid_read_syntax, error_tail, "Invalid read syntax"); PUT_ERROR (Qinvalid_function, error_tail, "Invalid function"); PUT_ERROR (Qwrong_number_of_arguments, error_tail, @@ -3727,10 +3866,18 @@ syms_of_data (void) DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum, doc: /* The largest value that is representable in a Lisp integer. */); Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-positive-fixnum")); DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum, doc: /* The smallest value that is representable in a Lisp integer. */); Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-negative-fixnum")); + + DEFSYM (Qwatchers, "watchers"); + DEFSYM (Qmakunbound, "makunbound"); + DEFSYM (Qunlet, "unlet"); + DEFSYM (Qset, "set"); + DEFSYM (Qset_default, "set-default"); + defsubr (&Sadd_variable_watcher); + defsubr (&Sremove_variable_watcher); } diff --git a/src/eval.c b/src/eval.c index a9bad24..93a320f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -595,8 +595,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, sym = XSYMBOL (new_alias); - if (sym->constant) - /* Not sure why, but why not? */ + if (sym->trapped_write == SYMBOL_NOWRITE) + /* Making it an alias effectively changes its value. */ error ("Cannot make a constant an alias"); switch (sym->redirect) @@ -617,8 +617,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, so that old-code that affects n_a before the aliasing is setup still works. */ if (NILP (Fboundp (base_variable))) - set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1); - + set_internal (base_variable, find_symbol_value (new_alias), + Qnil, SET_INTERNAL_BIND); { union specbinding *p; @@ -628,11 +628,14 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, error ("Don't know how to make a let-bound variable an alias"); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (new_alias, base_variable, Qdefvaralias, Qnil); + sym->declared_special = 1; XSYMBOL (base_variable)->declared_special = 1; sym->redirect = SYMBOL_VARALIAS; SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable)); - sym->constant = SYMBOL_CONSTANT_P (base_variable); + sym->trapped_write = XSYMBOL (base_variable)->trapped_write; LOADHIST_ATTACH (new_alias); /* Even if docstring is nil: remove old docstring. */ Fput (new_alias, Qvariable_documentation, docstring); @@ -2644,9 +2647,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, Lisp_Object fun, original_fun; Lisp_Object funcar; ptrdiff_t numargs = nargs - 1; - Lisp_Object lisp_numargs; Lisp_Object val; - Lisp_Object *internal_args; ptrdiff_t count; QUIT; @@ -2679,76 +2680,110 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, fun = indirect_function (fun); if (SUBRP (fun)) + val = funcall_subr (XSUBR (fun), numargs, args + 1); + else if (COMPILEDP (fun)) + val = funcall_lambda (fun, numargs, args + 1); + else { - if (numargs < XSUBR (fun)->min_args - || (XSUBR (fun)->max_args >= 0 && XSUBR (fun)->max_args < numargs)) + if (NILP (fun)) + xsignal1 (Qvoid_function, original_fun); + if (!CONSP (fun)) + xsignal1 (Qinvalid_function, original_fun); + funcar = XCAR (fun); + if (!SYMBOLP (funcar)) + xsignal1 (Qinvalid_function, original_fun); + if (EQ (funcar, Qlambda) + || EQ (funcar, Qclosure)) + val = funcall_lambda (fun, numargs, args + 1); + else if (EQ (funcar, Qautoload)) { - XSETFASTINT (lisp_numargs, numargs); - xsignal2 (Qwrong_number_of_arguments, original_fun, lisp_numargs); + Fautoload_do_load (fun, original_fun, Qnil); + check_cons_list (); + goto retry; } - - else if (XSUBR (fun)->max_args == UNEVALLED) + else xsignal1 (Qinvalid_function, original_fun); + } + check_cons_list (); + lisp_eval_depth--; + if (backtrace_debug_on_exit (specpdl + count)) + val = call_debugger (list2 (Qexit, val)); + specpdl_ptr--; + return val; +} - else if (XSUBR (fun)->max_args == MANY) - val = (XSUBR (fun)->function.aMANY) (numargs, args + 1); + +/* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR + and return the result of evaluation. */ + +Lisp_Object +funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *args) +{ + if (numargs < subr->min_args + || (subr->max_args >= 0 && subr->max_args < numargs)) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal2 (Qwrong_number_of_arguments, fun, make_number (numargs)); + } + + else if (subr->max_args == UNEVALLED) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal1 (Qinvalid_function, fun); + } + + else if (subr->max_args == MANY) + return (subr->function.aMANY) (numargs, args); else { Lisp_Object internal_argbuf[8]; - if (XSUBR (fun)->max_args > numargs) + Lisp_Object *internal_args; + if (subr->max_args > numargs) { - eassert (XSUBR (fun)->max_args <= ARRAYELTS (internal_argbuf)); + eassert (subr->max_args <= ARRAYELTS (internal_argbuf)); internal_args = internal_argbuf; - memcpy (internal_args, args + 1, numargs * word_size); + memcpy (internal_args, args, numargs * word_size); memclear (internal_args + numargs, - (XSUBR (fun)->max_args - numargs) * word_size); + (subr->max_args - numargs) * word_size); } else - internal_args = args + 1; - switch (XSUBR (fun)->max_args) + internal_args = args; + switch (subr->max_args) { case 0: - val = (XSUBR (fun)->function.a0 ()); - break; + return (subr->function.a0 ()); case 1: - val = (XSUBR (fun)->function.a1 (internal_args[0])); - break; + return (subr->function.a1 (internal_args[0])); case 2: - val = (XSUBR (fun)->function.a2 + return (subr->function.a2 (internal_args[0], internal_args[1])); - break; case 3: - val = (XSUBR (fun)->function.a3 + return (subr->function.a3 (internal_args[0], internal_args[1], internal_args[2])); - break; case 4: - val = (XSUBR (fun)->function.a4 + return (subr->function.a4 (internal_args[0], internal_args[1], internal_args[2], internal_args[3])); - break; case 5: - val = (XSUBR (fun)->function.a5 + return (subr->function.a5 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4])); - break; case 6: - val = (XSUBR (fun)->function.a6 + return (subr->function.a6 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5])); - break; case 7: - val = (XSUBR (fun)->function.a7 + return (subr->function.a7 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6])); - break; - case 8: - val = (XSUBR (fun)->function.a8 + return (subr->function.a8 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6], internal_args[7])); - break; default: @@ -2758,36 +2793,6 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, emacs_abort (); } } - } - else if (COMPILEDP (fun)) - val = funcall_lambda (fun, numargs, args + 1); - else - { - if (NILP (fun)) - xsignal1 (Qvoid_function, original_fun); - if (!CONSP (fun)) - xsignal1 (Qinvalid_function, original_fun); - funcar = XCAR (fun); - if (!SYMBOLP (funcar)) - xsignal1 (Qinvalid_function, original_fun); - if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) - val = funcall_lambda (fun, numargs, args + 1); - else if (EQ (funcar, Qautoload)) - { - Fautoload_do_load (fun, original_fun, Qnil); - check_cons_list (); - goto retry; - } - else - xsignal1 (Qinvalid_function, original_fun); - } - check_cons_list (); - lisp_eval_depth--; - if (backtrace_debug_on_exit (specpdl + count)) - val = call_debugger (list2 (Qexit, val)); - specpdl_ptr--; - return val; } static Lisp_Object @@ -3158,10 +3163,10 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.symbol = symbol; specpdl_ptr->let.old_value = SYMBOL_VAL (sym); grow_specpdl (); - if (!sym->constant) + if (!sym->trapped_write) SET_SYMBOL_VAL (sym, value); else - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; case SYMBOL_LOCALIZED: if (SYMBOL_BLV (sym)->frame_local) @@ -3201,7 +3206,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.kind = SPECPDL_LET; grow_specpdl (); - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; } default: emacs_abort (); @@ -3328,14 +3333,16 @@ unbind_to (ptrdiff_t count, Lisp_Object value) case SPECPDL_BACKTRACE: break; case SPECPDL_LET: - { /* If variable has a trivial value (no forwarding), we can - just set it. No need to check for constant symbols here, - since that was already done by specbind. */ + { /* If variable has a trivial value (no forwarding), and + isn't trapped, we can just set it. */ Lisp_Object sym = specpdl_symbol (specpdl_ptr); if (SYMBOLP (sym) && XSYMBOL (sym)->redirect == SYMBOL_PLAINVAL) { - SET_SYMBOL_VAL (XSYMBOL (sym), - specpdl_old_value (specpdl_ptr)); + if (XSYMBOL (sym)->trapped_write == SYMBOL_UNTRAPPED_WRITE) + SET_SYMBOL_VAL (XSYMBOL (sym), specpdl_old_value (specpdl_ptr)); + else + set_internal (sym, specpdl_old_value (specpdl_ptr), + Qnil, SET_INTERNAL_UNBIND); break; } else @@ -3358,7 +3365,7 @@ unbind_to (ptrdiff_t count, Lisp_Object value) /* If this was a local binding, reset the value in the appropriate buffer, but only if that buffer's binding still exists. */ if (!NILP (Flocal_variable_p (symbol, where))) - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } break; } @@ -3583,7 +3590,7 @@ backtrace_eval_unrewind (int distance) { set_specpdl_old_value (tmp, Fbuffer_local_value (symbol, where)); - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } } break; @@ -3927,6 +3934,7 @@ syms_of_eval (void) defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); + DEFSYM (Qdefvaralias, "defvaralias"); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); diff --git a/src/font.c b/src/font.c index ce63233..3b821a4 100644 --- a/src/font.c +++ b/src/font.c @@ -5417,19 +5417,19 @@ syms_of_font (void) [NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...] NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */); Vfont_weight_table = BUILD_STYLE_TABLE (weight_table); - XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-weight-table")); DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table, doc: /* Vector of font slant symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_slant_table = BUILD_STYLE_TABLE (slant_table); - XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-slant-table")); DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table, doc: /* Alist of font width symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_width_table = BUILD_STYLE_TABLE (width_table); - XSYMBOL (intern_c_string ("font-width-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-width-table")); staticpro (&font_style_table); font_style_table = make_uninit_vector (3); diff --git a/src/lisp.h b/src/lisp.h index 2e46592..701ee9c 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -320,7 +320,8 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) #define lisp_h_NILP(x) EQ (x, Qnil) #define lisp_h_SET_SYMBOL_VAL(sym, v) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v)) -#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant) +#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->trapped_write == SYMBOL_NOWRITE) +#define lisp_h_SYMBOL_TRAPPED_WRITE_P(sym) (XSYMBOL (sym)->trapped_write) #define lisp_h_SYMBOL_VAL(sym) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value) #define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol) @@ -375,6 +376,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) # define NILP(x) lisp_h_NILP (x) # define SET_SYMBOL_VAL(sym, v) lisp_h_SET_SYMBOL_VAL (sym, v) # define SYMBOL_CONSTANT_P(sym) lisp_h_SYMBOL_CONSTANT_P (sym) +# define SYMBOL_TRAPPED_WRITE_P(sym) lisp_h_SYMBOL_TRAPPED_WRITE_P (sym) # define SYMBOL_VAL(sym) lisp_h_SYMBOL_VAL (sym) # define SYMBOLP(x) lisp_h_SYMBOLP (x) # define VECTORLIKEP(x) lisp_h_VECTORLIKEP (x) @@ -602,6 +604,9 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); /* Defined in data.c. */ extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object); extern _Noreturn void wrong_choice (Lisp_Object, Lisp_Object); +extern void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, + Lisp_Object operation, Lisp_Object where); + /* Defined in emacs.c. */ #ifdef DOUG_LEA_MALLOC @@ -632,6 +637,13 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); SYMBOL_FORWARDED = 3 }; +enum symbol_trapped_write +{ + SYMBOL_UNTRAPPED_WRITE = 0, + SYMBOL_NOWRITE = 1, + SYMBOL_TRAPPED_WRITE = 2 +}; + struct Lisp_Symbol { bool_bf gcmarkbit : 1; @@ -643,10 +655,10 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); 3 : it's a forwarding variable, the value is in `forward'. */ ENUM_BF (symbol_redirect) redirect : 3; - /* Non-zero means symbol is constant, i.e. changing its value - should signal an error. If the value is 3, then the var - can be changed, but only by `defconst'. */ - unsigned constant : 2; + /* 0 : normal case, just set the value + 1 : constant, cannot set, e.g. nil, t, :keywords. + 2 : trap the write, call watcher functions. */ + ENUM_BF (symbol_trapped_write) trapped_write : 2; /* Interned state of the symbol. This is an enumerator from enum symbol_interned. */ @@ -1850,9 +1862,20 @@ SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (Lisp_Object sym) return XSYMBOL (sym)->interned == SYMBOL_INTERNED_IN_INITIAL_OBARRAY; } -/* Value is non-zero if symbol is considered a constant, i.e. its - value cannot be changed (there is an exception for keyword symbols, - whose value can be set to the keyword symbol itself). */ +/* Value is non-zero if symbol cannot be changed through a simple set, + i.e. it's a constant (e.g. nil, t, :keywords), or it has some + watching functions. */ + +INLINE int +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) +{ + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); +} + +/* Value is non-zero if symbol cannot be changed at all, i.e. it's a + constant (e.g. nil, t, :keywords). Code that actually wants to + write to SYM, should also check whether there are any watching + functions. */ INLINE int (SYMBOL_CONSTANT_P) (Lisp_Object sym) @@ -3289,6 +3312,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) XSYMBOL (sym)->next = next; } +INLINE void +make_symbol_constant (Lisp_Object sym) +{ + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; +} + /* Buffer-local (also frame-local) variable access functions. */ INLINE int @@ -3397,7 +3426,13 @@ set_sub_char_table_contents (Lisp_Object table, ptrdiff_t idx, Lisp_Object val) extern _Noreturn void args_out_of_range_3 (Lisp_Object, Lisp_Object, Lisp_Object); extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *); -extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool); +enum Set_Internal_Bind { + SET_INTERNAL_SET, + SET_INTERNAL_BIND, + SET_INTERNAL_UNBIND +}; +extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, + enum Set_Internal_Bind); extern void syms_of_data (void); extern void swap_in_global_binding (struct Lisp_Symbol *); @@ -3880,6 +3915,7 @@ xsignal (Lisp_Object error_symbol, Lisp_Object data) extern _Noreturn void xsignal3 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object); extern _Noreturn void signal_error (const char *, Lisp_Object); +extern Lisp_Object funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *arg_vector); extern Lisp_Object eval_sub (Lisp_Object form); extern Lisp_Object apply1 (Lisp_Object, Lisp_Object); extern Lisp_Object call0 (Lisp_Object); diff --git a/src/lread.c b/src/lread.c index 58d518c..7e74703 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3833,7 +3833,7 @@ intern_sym (Lisp_Object sym, Lisp_Object obarray, Lisp_Object index) if (SREF (SYMBOL_NAME (sym), 0) == ':' && EQ (obarray, initial_obarray)) { - XSYMBOL (sym)->constant = 1; + make_symbol_constant (sym); XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL; SET_SYMBOL_VAL (XSYMBOL (sym), sym); } @@ -4120,12 +4120,12 @@ init_obarray (void) DEFSYM (Qnil, "nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); - XSYMBOL (Qnil)->constant = 1; + make_symbol_constant (Qnil); XSYMBOL (Qnil)->declared_special = true; DEFSYM (Qt, "t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); - XSYMBOL (Qt)->constant = 1; + make_symbol_constant (Qt); XSYMBOL (Qt)->declared_special = true; /* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */ -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v5-b-0002-Add-function-to-trigger-debugger-on-variable-writ.patch Content-Description: patch >From 76719cfbdeed48028c008c423caa81dd18dabb2e Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 16:03:06 -0500 Subject: [PATCH v5 2/5] Add function to trigger debugger on variable write * lisp/emacs-lisp/debug.el (debug-on-variable-change): (debug--variable-list): (cancel-debug-on-variable-change): New functions. (debugger-setup-buffer): Add watchpoint clause. --- lisp/emacs-lisp/debug.el | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 7d27380..0fefc54 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -306,6 +306,24 @@ debugger-setup-buffer (delete-char 1) (insert ? ) (beginning-of-line)) + ;; Watchpoint triggered. + ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) + (insert + "--" + (pcase details + (`(makunbound nil) (format "making %s void" symbol)) + (`(makunbound ,buffer) (format "killing local value of %s in buffer %s" + symbol buffer)) + (`(defvaralias ,_) (format "aliasing %s to %s" symbol newval)) + (`(let ,_) (format "let-binding %s to %S" symbol newval)) + (`(unlet ,_) (format "ending let-binding of %s" symbol)) + (`(set nil) (format "setting %s to %S" symbol newval)) + (`(set ,buffer) (format "setting %s in buffer %s to %S" + symbol buffer newval)) + (_ (error "unrecognized watchpoint triggered %S" (cdr args)))) + ": ") + (setq pos (point)) + (insert ?\n)) ;; Debugger entered for an error. (`error (insert "--Lisp error: ") @@ -850,6 +868,79 @@ debugger-list-functions (princ "Note: if you have redefined a function, then it may no longer\n") (princ "be set to debug on entry, even if it is in the list.")))))) +(defun debug--implement-debug-watch (symbol newval op where) + "Conditionally call the debugger. +This function is called when SYMBOL's value is modified." + (if (or inhibit-debug-on-entry debugger-jumping-flag) + nil + (let ((inhibit-debug-on-entry t)) + (funcall debugger 'watchpoint symbol newval op where)))) + +;;;###autoload +(defun debug-on-variable-change (variable) + "Trigger a debugger invocation when VARIABLE is changed. + +When called interactively, prompt for VARIABLE in the minibuffer. + +This works by calling `add-variable-watch' on VARIABLE. If you +quit from the debugger, this will abort the change (unless the +change is caused by the termination of a let-binding). + +The watchpoint may be circumvented by C code that changes the +variable directly (i.e., not via `set'). Changing the value of +the variable (e.g., `setcar' on a list variable) will not trigger +watchpoint. + +Use \\[cancel-debug-on-variable-change] to cancel the effect of +this command. Uninterning VARIABLE or making it an alias of +another symbol also cancels it." + (interactive + (let* ((var-at-point (variable-at-point)) + (var (and (symbolp var-at-point) var-at-point)) + (val (completing-read + (concat "Debug when setting variable" + (if var (format " (default %s): " var) ": ")) + obarray #'boundp + t nil nil (and var (symbol-name var))))) + (list (if (equal val "") var (intern val))))) + (add-variable-watcher variable #'debug--implement-debug-watch)) + +;;;###autoload +(defalias 'debug-watch #'debug-on-variable-change) + + +(defun debug--variable-list () + "List of variables currently set for debug on set." + (let ((vars '())) + (mapatoms + (lambda (s) + (when (memq #'debug--implement-debug-watch + (get s 'watchers)) + (push s vars)))) + vars)) + +;;;###autoload +(defun cancel-debug-on-variable-change (&optional variable) + "Undo effect of \\[debug-on-entry] on VARIABLE. +If VARIABLE is nil, cancel debug-on-variable-change for all variables. +When called interactively, prompt for VARIABLE in the minibuffer. +To specify a nil argument interactively, exit with an empty minibuffer." + (interactive + (list (let ((name + (completing-read + "Cancel debug on set for variable (default all variables): " + (mapcar #'symbol-name (debug--variable-list)) nil t))) + (when name + (unless (string= name "") + (intern name)))))) + (if variable + (remove-variable-watcher variable #'debug--implement-debug-watch) + (message "Canceling debug-watch for all variables") + (mapc #'cancel-debug-watch (debug--variable-list)))) + +;;;###autoload +(defalias 'cancel-debug-watch #'cancel-debug-on-variable-change) + (provide 'debug) ;;; debug.el ends here -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v5-b-0003-Ensure-redisplay-using-variable-watcher.patch Content-Description: patch >From 0e4dd8a5bce95e88e924effa683f085499e9bf31 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 17:02:42 -0500 Subject: [PATCH v5 3/5] Ensure redisplay using variable watcher Instead of looking up the variable name in redisplay--variables when setting. * lisp/frame.el: Replace redisplay--variables with add-variable-watcher calls. * src/xdisp.c (Fset_buffer_redisplay): Rename from maybe_set_redisplay, set the redisplay flag unconditionally. (Vredisplay__variables): Remove it. * src/data.c (set_internal): Remove maybe_set_redisplay call. --- lisp/frame.el | 3 +-- src/data.c | 2 -- src/window.h | 1 - src/xdisp.c | 17 +++++++---------- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lisp/frame.el b/lisp/frame.el index a584567..1dffc6c 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2249,9 +2249,8 @@ 'automatic-hscrolling 'window-system-version "it does not give useful information." "24.3") ;; Variables which should trigger redisplay of the current buffer. -(setq redisplay--variables (make-hash-table :test 'eq :size 10)) (mapc (lambda (var) - (puthash var 1 redisplay--variables)) + (add-variable-watcher var (symbol-function 'set-buffer-redisplay))) '(line-spacing overline-margin line-prefix diff --git a/src/data.c b/src/data.c index a071261..527c770 100644 --- a/src/data.c +++ b/src/data.c @@ -1275,8 +1275,6 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, default: emacs_abort (); } - maybe_set_redisplay (symbol); - start: switch (sym->redirect) { diff --git a/src/window.h b/src/window.h index a124b33..4a102f2 100644 --- a/src/window.h +++ b/src/window.h @@ -1063,7 +1063,6 @@ void set_window_buffer (Lisp_Object window, Lisp_Object buffer, extern void fset_redisplay (struct frame *f); extern void bset_redisplay (struct buffer *b); extern void bset_update_mode_line (struct buffer *b); -extern void maybe_set_redisplay (Lisp_Object); /* Call this to tell redisplay to look for other windows than selected-window that need to be redisplayed. Calling one of the *set_redisplay functions above already does it, so it's only needed in unusual cases. */ diff --git a/src/xdisp.c b/src/xdisp.c index 6e8af8a..9d36ab6 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -622,15 +622,15 @@ bset_update_mode_line (struct buffer *b) b->text->redisplay = true; } -void -maybe_set_redisplay (Lisp_Object symbol) +DEFUN ("set-buffer-redisplay", Fset_buffer_redisplay, + Sset_buffer_redisplay, 4, 4, 0, + doc: /* Mark the current buffer for redisplay. +This function may be passed to `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object newval, Lisp_Object op, Lisp_Object where) { - if (HASH_TABLE_P (Vredisplay__variables) - && hash_lookup (XHASH_TABLE (Vredisplay__variables), symbol, NULL) >= 0) - { bset_update_mode_line (current_buffer); current_buffer->prevent_redisplay_optimizations_p = true; - } + return Qnil; } #ifdef GLYPH_DEBUG @@ -31319,6 +31319,7 @@ syms_of_xdisp (void) message_dolog_marker3 = Fmake_marker (); staticpro (&message_dolog_marker3); + defsubr (&Sset_buffer_redisplay); #ifdef GLYPH_DEBUG defsubr (&Sdump_frame_glyph_matrix); defsubr (&Sdump_glyph_matrix); @@ -31988,10 +31989,6 @@ or t (meaning all windows). */); doc: /* */); Vredisplay__mode_lines_cause = Fmake_hash_table (0, NULL); - DEFVAR_LISP ("redisplay--variables", Vredisplay__variables, - doc: /* A hash-table of variables changing which triggers a thorough redisplay. */); - Vredisplay__variables = Qnil; - DEFVAR_BOOL ("redisplay--inhibit-bidi", redisplay__inhibit_bidi, doc: /* Non-nil means it is not safe to attempt bidi reordering for display. */); /* Initialize to t, since we need to disable reordering until -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v5-b-0004-Add-tests-for-watchpoints.patch Content-Description: patch >From e2b702ac03022b01f2f174522f03c89687c97909 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 12 Dec 2015 23:10:15 -0500 Subject: [PATCH v5 4/5] Add tests for watchpoints * test/src/data-tests.el (data-tests-variable-watchers): (data-tests-local-variable-watchers): New tests. --- test/src/data-tests.el | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/src/data-tests.el b/test/src/data-tests.el index 0a29233..4c2ea54 100644 --- a/test/src/data-tests.el +++ b/test/src/data-tests.el @@ -255,3 +255,118 @@ test-bool-vector-binop (v2 (test-bool-vector-bv-from-hex-string "0000C")) (v3 (bool-vector-not v1))) (should (equal v2 v3)))) + +(ert-deftest data-tests-variable-watchers () + (defvar data-tests-var 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 1) + (should-have-watch-data '(data-tests-var 1 set nil)) + (let ((data-tests-var 2)) + (should-have-watch-data '(data-tests-var 2 let nil)) + (setq data-tests-var 3) + (should-have-watch-data '(data-tests-var 3 set nil))) + (should-have-watch-data '(data-tests-var 1 unlet nil)) + ;; `setq-default' on non-local variable is same as `setq'. + (setq-default data-tests-var 4) + (should-have-watch-data '(data-tests-var 4 set nil)) + (makunbound 'data-tests-var) + (should-have-watch-data '(data-tests-var nil makunbound nil)) + (setq data-tests-var 5) + (should-have-watch-data '(data-tests-var 5 set nil)) + (remove-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 6) + (should (null watch-data))))) + +(ert-deftest data-tests-varalias-watchers () + (defvar data-tests-var0 0) + (defvar data-tests-var1 0) + (defvar data-tests-var2 0) + (defvar data-tests-var3 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + ;; Watch var0, then alias it. + (add-variable-watcher 'data-tests-var0 collect-watch-data) + (defvaralias 'data-tests-var0-alias 'data-tests-var0) + (setq data-tests-var0 1) + (should-have-watch-data '(data-tests-var0 1 set nil)) + (setq data-tests-var0-alias 2) + (should-have-watch-data '(data-tests-var0 2 set nil)) + ;; Alias var1, then watch var1-alias. + (defvaralias 'data-tests-var1-alias 'data-tests-var1) + (add-variable-watcher 'data-tests-var1-alias collect-watch-data) + (setq data-tests-var1 1) + (should-have-watch-data '(data-tests-var1 1 set nil)) + (setq data-tests-var1-alias 2) + (should-have-watch-data '(data-tests-var1 2 set nil)) + ;; Alias var2, then watch it. + (defvaralias 'data-tests-var2-alias 'data-tests-var2) + (add-variable-watcher 'data-tests-var2 collect-watch-data) + (setq data-tests-var2 1) + (should-have-watch-data '(data-tests-var2 1 set nil)) + (setq data-tests-var2-alias 2) + (should-have-watch-data '(data-tests-var2 2 set nil)) + ;; Watch var3-alias, then make it alias var3 (this removes the + ;; watcher flag). + (defvar data-tests-var3-alias 0) + (add-variable-watcher 'data-tests-var3-alias collect-watch-data) + (defvaralias 'data-tests-var3-alias 'data-tests-var3) + (should-have-watch-data '(data-tests-var3-alias + data-tests-var3 defvaralias nil)) + (setq data-tests-var3 1) + (setq data-tests-var3-alias 2) + (should (null watch-data))))) + +(ert-deftest data-tests-local-variable-watchers () + (defvar-local data-tests-lvar 0) + (let* ((buf1 (current-buffer)) + (buf2 nil) + (watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf1)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf1)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf1))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf1)) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (with-temp-buffer + (setq buf2 (current-buffer)) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf2)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf2)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf2))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf2)) + (kill-local-variable 'data-tests-lvar) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2)) + (setq data-tests-lvar 3.5) + (should-have-watch-data `(data-tests-lvar 3.5 set ,buf2)) + (kill-all-local-variables) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2))) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (makunbound 'data-tests-lvar) + (should-have-watch-data '(data-tests-lvar nil makunbound nil)) + (setq data-tests-lvar 5) + (should-have-watch-data `(data-tests-lvar 5 set ,buf1)) + (remove-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 6) + (should (null watch-data))))) -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v5-b-0005-etc-NEWS-Add-entry-for-watchpoints.patch Content-Description: patch >From 2b8818979d097cf0d6d975f5b5cdd2b0e6673f8a Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 13 Dec 2015 14:47:58 -0500 Subject: [PATCH v5 5/5] * etc/NEWS: Add entry for watchpoints --- etc/NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index e29dfe2..bc3b284 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -626,6 +626,10 @@ two objects are 'eq' ('eql'), then the result of 'sxhash-eq' consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. +** New function `add-variable-watcher' can be used to call a function +when a symbol's value is changed. This is used to implement the new +debugger command `debug-watch'. + +++ ** Time conversion functions that accept a time zone rule argument now allow it to be OFFSET or a list (OFFSET ABBR), where the integer -- 2.9.3 --=-=-= Content-Type: text/plain For the manual, do you think I should document just the debugging commands, or should there additionally be a section in the "Variables" chapter about the watchpoint mechanism? --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 13 10:29:55 2016 Received: (at 24923) by debbugs.gnu.org; 13 Nov 2016 15:29:55 +0000 Received: from localhost ([127.0.0.1]:55989 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5wjD-0005Af-3l for submit@debbugs.gnu.org; Sun, 13 Nov 2016 10:29:55 -0500 Received: from eggs.gnu.org ([208.118.235.92]:41552) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c5wjB-0005AN-Bf for 24923@debbugs.gnu.org; Sun, 13 Nov 2016 10:29:53 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c5wj3-0005fj-2j for 24923@debbugs.gnu.org; Sun, 13 Nov 2016 10:29:48 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:49319) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c5wj2-0005ff-Vc; Sun, 13 Nov 2016 10:29:45 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4834 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c5wj2-0001BH-AG; Sun, 13 Nov 2016 10:29:44 -0500 Date: Sun, 13 Nov 2016 17:29:54 +0200 Message-Id: <83fumvcs99.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87fumwmc7q.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.8 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.8 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org > Date: Sat, 12 Nov 2016 19:54:01 -0500 > > Here is the updated patch, created with -b. I went with a call to > `error'. And actually, I had missed a couple of watchpoint types. This LGTM, just one comment for when you actually push: > + else if (sym->redirect == SYMBOL_LOCALIZED && > + SYMBOL_BLV (sym)->frame_local) Our coding conventions put the logical operators at the beginning of a line, not at EOL. > +static void > +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) > +{ > + if (!EQ (base_variable, alias) && > + EQ (base_variable, Findirect_variable (alias))) Same here. > + if (NILP (where) && > + !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) && > + !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) And here. > --- a/etc/NEWS > +++ b/etc/NEWS > @@ -626,6 +626,10 @@ two objects are 'eq' ('eql'), then the result of 'sxhash-eq' > consistency with the new functions. For compatibility, 'sxhash' > remains as an alias to 'sxhash-equal'. > > +** New function `add-variable-watcher' can be used to call a function > +when a symbol's value is changed. This is used to implement the new > +debugger command `debug-watch'. ^^^^^^^^^^^ This should follow the renaming. (Hopefully, this will be followed by a suitable Edebug binding.) > For the manual, do you think I should document just the debugging > commands, or should there additionally be a section in the "Variables" > chapter about the watchpoint mechanism? Both, I think. Thanks, I think this is a very important new feature. From debbugs-submit-bounces@debbugs.gnu.org Sat Nov 19 21:11:33 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 02:11:33 +0000 Received: from localhost ([127.0.0.1]:35450 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8HbP-0005dQ-Oo for submit@debbugs.gnu.org; Sat, 19 Nov 2016 21:11:33 -0500 Received: from mail-it0-f48.google.com ([209.85.214.48]:35605) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8HbN-0005dA-1w for 24923@debbugs.gnu.org; Sat, 19 Nov 2016 21:11:30 -0500 Received: by mail-it0-f48.google.com with SMTP id c20so65577911itb.0 for <24923@debbugs.gnu.org>; Sat, 19 Nov 2016 18:11:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=rCCppzO7tJZaRZB8R+PNPO5HomnPwS/Y5F5UT6QGO8s=; b=N45o9eQk0/4ur+gYygZg4U/tBPfRToLyrBGLFEnjDawqmmm+ZV+6+4e930gsueRbgi jIi0AJlIUtsHAhFlRycXxOzI7aus50kmYcnv+j5QV5iiWsNcAVWRZtUb51HUrpJCKyBe v+G95ocufp6gQ+F7AGzJYXYkhBbaR+kewCVyvXAmi/m/clbbTZUGeH5TxjMS7oR8vk80 iC5EY73NWsmUi3HBV9uulkpAYEyqwF2pGZK7p7gW+mV2/ZK0BwZ4RKDUGF2zN5xjxgg3 Id5JJCPk0eIKJAhPZ2DaqUDrDoSstGwU0Uy/kZzfaFkHud8w4L3Y2XdmmjUCK1wThfHI 81LA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=rCCppzO7tJZaRZB8R+PNPO5HomnPwS/Y5F5UT6QGO8s=; b=NgpgIzL+HABFB/vhFXCoFa1IVvzvf6scDjQ37VwIQ84rRm+TL9qBA5EbtAsXivJf92 DfxtiRPW6J0tdXvXcLtTcZHo/kJ6oJw/oAAnDmdmWwuJ3+qi2M6SXrw3P8mlHu8XnefU jt4RuGbol0AqQCY41+GyR8wCJQFnpykKpBCJTZD4G5NBMFWwfA19btLOV8j/Ypvm3Jq0 Soyp6Gx3jEwBbC2jw1envJInazv68h5U7NAaH5RE/NEAvhAfuI7x7JN1wgr0WsdSB2QT CL8TPmD6nTmS6Q9DCQUnWQ/JtpQqq623R6zXTi0nbu9kJpcgZAFhwgnway2hhxZL0dzw zspw== X-Gm-Message-State: AKaTC02L9QEKI0+YpRyG1amUluxqo5NwIOpK851GZ9G7VMzHsGTJOiSlkMTZJCXWdhH+ZA== X-Received: by 10.36.139.197 with SMTP id g188mr4821998ite.68.1479607883439; Sat, 19 Nov 2016 18:11:23 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id 2sm5699402ioz.30.2016.11.19.18.11.21 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sat, 19 Nov 2016 18:11:22 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> Date: Sat, 19 Nov 2016 21:12:13 -0500 In-Reply-To: <83fumvcs99.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 13 Nov 2016 17:29:54 +0200") Message-ID: <874m32lx1e.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) --=-=-= Content-Type: text/plain Eli Zaretskii writes: > > Our coding conventions put the logical operators at the beginning of a > line, not at EOL. Fixed this, and added documentation. Also, watcher functions are now listed in describe-variable output. Does it make sense to mention the use of the `watchers' symbol property in the manual? Since I've added a `get-variable-watchers' it's now possible to ignore the symbol property as an implementation detail. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0001-Add-lisp-watchpoints.patch Content-Description: patch >From c318be2dc401d2f3b958ceb3b48e466a3019091e Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 19 Nov 2015 19:50:06 -0500 Subject: [PATCH v6 1/6] Add lisp watchpoints This allows to call a function whenever a symbol-value is changed. * src/lisp.h (lisp_h_SYMBOL_TRAPPED_WRITE_P): (SYMBOL_TRAPPED_WRITE_P): New function/macro. (lisp_h_SYMBOL_CONSTANT_P): Check for SYMBOL_NOWRITE specifically. (enum symbol_trapped_write): New enumeration. (struct Lisp_Symbol): Rename field constant to trapped_write. (make_symbol_constant): New function. * src/data.c (Fadd_variable_watcher, Fremove_variable_watcher): (set_symbol_trapped_write, restore_symbol_trapped_write): (harmonize_variable_watchers, notify_variable_watchers): New functions. * src/data.c (Fset_default): Call `notify_variable_watchers' for trapped symbols. (set_internal): Change bool argument BIND to 3-value enum and call `notify_variable_watchers' for trapped symbols. * src/data.c (syms_of_data): * src/data.c (syms_of_data): * src/font.c (syms_of_font): * src/lread.c (intern_sym, init_obarray): * src/buffer.c (syms_of_buffer): Use make_symbol_constant. * src/alloc.c (init_symbol): * src/bytecode.c (exec_byte_code): Use SYMBOL_TRAPPED_WRITE_P. * src/data.c (Fmake_variable_buffer_local, Fmake_local_variable): (Fmake_variable_frame_local): * src/eval.c (Fdefvaralias, specbind): Refer to Lisp_Symbol's trapped_write instead of constant. (Ffuncall): Move subr calling code into separate function. (funcall_subr): New function. --- src/alloc.c | 2 +- src/buffer.c | 22 +++++-- src/bytecode.c | 4 +- src/data.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/eval.c | 172 +++++++++++++++++++++++++++++-------------------------- src/font.c | 6 +- src/lisp.h | 54 +++++++++++++++--- src/lread.c | 6 +- 8 files changed, 324 insertions(+), 119 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index a58dc13..f373f6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3562,7 +3562,7 @@ init_symbol (Lisp_Object val, Lisp_Object name) set_symbol_next (val, NULL); p->gcmarkbit = false; p->interned = SYMBOL_UNINTERNED; - p->constant = 0; + p->trapped_write = SYMBOL_UNTRAPPED_WRITE; p->declared_special = false; p->pinned = false; } diff --git a/src/buffer.c b/src/buffer.c index 3d205bb..cc75cdb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -984,9 +984,13 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, Qnil); else { - Lisp_Object tmp, prop, last = Qnil; + Lisp_Object tmp, last = Qnil; for (tmp = BVAR (b, local_var_alist); CONSP (tmp); tmp = XCDR (tmp)) - if (!NILP (prop = Fget (XCAR (XCAR (tmp)), Qpermanent_local))) + { + Lisp_Object local_var = XCAR (XCAR (tmp)); + Lisp_Object prop = Fget (local_var, Qpermanent_local); + + if (!NILP (prop)) { /* If permanent-local, keep it. */ last = tmp; @@ -1010,7 +1014,12 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) || !NILP (Fget (elt, Qpermanent_local_hook))) newlist = Fcons (elt, newlist); } - XSETCDR (XCAR (tmp), Fnreverse (newlist)); + newlist = Fnreverse (newlist); + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, newlist, + Qmakunbound, Fcurrent_buffer ()); + XSETCDR (XCAR (tmp), newlist); + continue; /* Don't do variable write trapping twice. */ } } /* Delete this local variable. */ @@ -1018,6 +1027,11 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, XCDR (tmp)); else XSETCDR (last, XCDR (tmp)); + + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, Qnil, + Qmakunbound, Fcurrent_buffer ()); + } } for (i = 0; i < last_per_buffer_idx; ++i) @@ -5682,7 +5696,7 @@ syms_of_buffer (void) This variable is buffer-local but you cannot set it directly; use the function `set-buffer-multibyte' to change a buffer's representation. See also Info node `(elisp)Text Representations'. */); - XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1; + make_symbol_constant (intern_c_string ("enable-multibyte-characters")); DEFVAR_PER_BUFFER ("buffer-file-coding-system", &BVAR (current_buffer, buffer_file_coding_system), Qnil, diff --git a/src/bytecode.c b/src/bytecode.c index e2d8ab7..18eaf9f 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -569,10 +569,10 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (SYMBOLP (sym) && !EQ (val, Qunbound) && !XSYMBOL (sym)->redirect - && !SYMBOL_CONSTANT_P (sym)) + && !SYMBOL_TRAPPED_WRITE_P (sym)) SET_SYMBOL_VAL (XSYMBOL (sym), val); else - set_internal (sym, val, Qnil, false); + set_internal (sym, val, Qnil, SET_INTERNAL_SET); } NEXT; diff --git a/src/data.c b/src/data.c index d221db4..8954b42 100644 --- a/src/data.c +++ b/src/data.c @@ -1225,7 +1225,7 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */) (register Lisp_Object symbol, Lisp_Object newval) { - set_internal (symbol, newval, Qnil, 0); + set_internal (symbol, newval, Qnil, SET_INTERNAL_SET); return newval; } @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, If buffer/frame-locality is an issue, WHERE specifies which context to use. (nil stands for the current buffer/frame). - If BINDFLAG is false, then if this symbol is supposed to become - local in every buffer where it is set, then we make it local. - If BINDFLAG is true, we don't do that. */ + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to + become local in every buffer where it is set, then we make it + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we + don't do that. */ void set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, - bool bindflag) + enum Set_Internal_Bind bindflag) { bool voide = EQ (newval, Qunbound); struct Lisp_Symbol *sym; @@ -1250,18 +1251,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; */ CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) || !EQ (newval, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return; + + case SYMBOL_TRAPPED_WRITE: + notify_variable_watchers (symbol, voide? Qnil : newval, + (bindflag == SET_INTERNAL_BIND? Qlet : + bindflag == SET_INTERNAL_UNBIND? Qunlet : + voide? Qmakunbound : Qset), + where); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } maybe_set_redisplay (symbol); - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1386,6 +1400,111 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; } +static void +set_symbol_trapped_write (Lisp_Object symbol, enum symbol_trapped_write trap) +{ + struct Lisp_Symbol* sym = XSYMBOL (symbol); + if (sym->trapped_write == SYMBOL_NOWRITE) + xsignal1 (Qtrapping_constant, symbol); + else if (sym->redirect == SYMBOL_LOCALIZED + && SYMBOL_BLV (sym)->frame_local) + xsignal1 (Qtrapping_frame_local, symbol); + sym->trapped_write = trap; +} + +static void +restore_symbol_trapped_write (Lisp_Object symbol) +{ + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); +} + +static void +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) +{ + if (!EQ (base_variable, alias) + && EQ (base_variable, Findirect_variable (alias))) + set_symbol_trapped_write + (alias, XSYMBOL (base_variable)->trapped_write); +} + +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, + 2, 2, 0, + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. +All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + + Lisp_Object watchers = Fget (symbol, Qwatchers); + Lisp_Object member = Fmember (watch_function, watchers); + if (NILP (member)) + Fput (symbol, Qwatchers, Fcons (watch_function, watchers)); + return Qnil; +} + +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, + 2, 2, 0, + doc: /* Undo the effect of `add-variable-watcher'. +Remove WATCH-FUNCTION from the list of functions to be called when +SYMBOL (or its aliases) are set. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + Lisp_Object watchers = Fget (symbol, Qwatchers); + watchers = Fdelete (watch_function, watchers); + if (NILP (watchers)) + { + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + } + Fput (symbol, Qwatchers, watchers); + return Qnil; +} + +void +notify_variable_watchers (Lisp_Object symbol, + Lisp_Object newval, + Lisp_Object operation, + Lisp_Object where) +{ + symbol = Findirect_variable (symbol); + + ptrdiff_t count = SPECPDL_INDEX (); + record_unwind_protect (restore_symbol_trapped_write, symbol); + /* Avoid recursion. */ + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + + if (NILP (where) + && !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) + && !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) + { + XSETBUFFER (where, current_buffer); + } + + if (EQ (operation, Qset_default)) + operation = Qset; + + for (Lisp_Object watchers = Fget (symbol, Qwatchers); + CONSP (watchers); + watchers = XCDR (watchers)) + { + Lisp_Object watcher = XCAR (watchers); + /* Call subr directly to avoid gc. */ + if (SUBRP (watcher)) + { + Lisp_Object args[] = { symbol, newval, operation, where }; + funcall_subr (XSUBR (watcher), ARRAYELTS (args), args); + } + else + CALLN (Ffuncall, watcher, symbol, newval, operation, where); + } + + unbind_to (count, Qnil); +} + + /* Access or set a buffer-local symbol's default value. */ /* Return the default value of SYMBOL, but don't check for voidness. @@ -1471,16 +1590,27 @@ DEFUN ("set-default", Fset_default, Sset_default, 2, 2, 0, struct Lisp_Symbol *sym; CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (value, Fdefault_value (symbol))) + || !EQ (value, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return value; + + case SYMBOL_TRAPPED_WRITE: + /* Don't notify here if we're going to call Fset anyway. */ + if (sym->redirect != SYMBOL_PLAINVAL) + notify_variable_watchers (symbol, value, Qset_default, Qnil); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1651,7 +1781,7 @@ DEFUN ("make-variable-buffer-local", Fmake_variable_buffer_local, default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_CONSTANT_P (variable)) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); if (!blv) @@ -1726,7 +1856,7 @@ DEFUN ("make-local-variable", Fmake_local_variable, Smake_local_variable, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); @@ -1838,6 +1968,9 @@ DEFUN ("kill-local-variable", Fkill_local_variable, Skill_local_variable, default: emacs_abort (); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (variable, Qnil, Qmakunbound, Fcurrent_buffer ()); + /* Get rid of this buffer's alist element, if any. */ XSETSYMBOL (variable, sym); /* Propagate variable indirection. */ tem = Fassq (variable, BVAR (current_buffer, local_var_alist)); @@ -1920,7 +2053,7 @@ DEFUN ("make-variable-frame-local", Fmake_variable_frame_local, Smake_variable_f default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_TRAPPED_WRITE_P (variable)) error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable))); blv = make_blv (sym, forwarded, valcontents); @@ -3471,6 +3604,8 @@ syms_of_data (void) DEFSYM (Qcyclic_variable_indirection, "cyclic-variable-indirection"); DEFSYM (Qvoid_variable, "void-variable"); DEFSYM (Qsetting_constant, "setting-constant"); + DEFSYM (Qtrapping_constant, "trapping-constant"); + DEFSYM (Qtrapping_frame_local, "trapping-frame-local"); DEFSYM (Qinvalid_read_syntax, "invalid-read-syntax"); DEFSYM (Qinvalid_function, "invalid-function"); @@ -3549,6 +3684,10 @@ syms_of_data (void) PUT_ERROR (Qvoid_variable, error_tail, "Symbol's value as variable is void"); PUT_ERROR (Qsetting_constant, error_tail, "Attempt to set a constant symbol"); + PUT_ERROR (Qtrapping_constant, error_tail, + "Attempt to trap writes to a constant symbol"); + PUT_ERROR (Qtrapping_frame_local, error_tail, + "Attempt to trap writes to a frame local variable"); PUT_ERROR (Qinvalid_read_syntax, error_tail, "Invalid read syntax"); PUT_ERROR (Qinvalid_function, error_tail, "Invalid function"); PUT_ERROR (Qwrong_number_of_arguments, error_tail, @@ -3727,10 +3866,18 @@ syms_of_data (void) DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum, doc: /* The largest value that is representable in a Lisp integer. */); Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-positive-fixnum")); DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum, doc: /* The smallest value that is representable in a Lisp integer. */); Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-negative-fixnum")); + + DEFSYM (Qwatchers, "watchers"); + DEFSYM (Qmakunbound, "makunbound"); + DEFSYM (Qunlet, "unlet"); + DEFSYM (Qset, "set"); + DEFSYM (Qset_default, "set-default"); + defsubr (&Sadd_variable_watcher); + defsubr (&Sremove_variable_watcher); } diff --git a/src/eval.c b/src/eval.c index a9bad24..c5c6fa9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -593,12 +593,12 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, CHECK_SYMBOL (new_alias); CHECK_SYMBOL (base_variable); - sym = XSYMBOL (new_alias); - - if (sym->constant) - /* Not sure why, but why not? */ + if (SYMBOL_CONSTANT_P (new_alias)) + /* Making it an alias effectively changes its value. */ error ("Cannot make a constant an alias"); + sym = XSYMBOL (new_alias); + switch (sym->redirect) { case SYMBOL_FORWARDED: @@ -617,8 +617,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, so that old-code that affects n_a before the aliasing is setup still works. */ if (NILP (Fboundp (base_variable))) - set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1); - + set_internal (base_variable, find_symbol_value (new_alias), + Qnil, SET_INTERNAL_BIND); { union specbinding *p; @@ -628,11 +628,14 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, error ("Don't know how to make a let-bound variable an alias"); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (new_alias, base_variable, Qdefvaralias, Qnil); + sym->declared_special = 1; XSYMBOL (base_variable)->declared_special = 1; sym->redirect = SYMBOL_VARALIAS; SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable)); - sym->constant = SYMBOL_CONSTANT_P (base_variable); + sym->trapped_write = XSYMBOL (base_variable)->trapped_write; LOADHIST_ATTACH (new_alias); /* Even if docstring is nil: remove old docstring. */ Fput (new_alias, Qvariable_documentation, docstring); @@ -2644,9 +2647,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, Lisp_Object fun, original_fun; Lisp_Object funcar; ptrdiff_t numargs = nargs - 1; - Lisp_Object lisp_numargs; Lisp_Object val; - Lisp_Object *internal_args; ptrdiff_t count; QUIT; @@ -2679,76 +2680,110 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, fun = indirect_function (fun); if (SUBRP (fun)) + val = funcall_subr (XSUBR (fun), numargs, args + 1); + else if (COMPILEDP (fun)) + val = funcall_lambda (fun, numargs, args + 1); + else { - if (numargs < XSUBR (fun)->min_args - || (XSUBR (fun)->max_args >= 0 && XSUBR (fun)->max_args < numargs)) + if (NILP (fun)) + xsignal1 (Qvoid_function, original_fun); + if (!CONSP (fun)) + xsignal1 (Qinvalid_function, original_fun); + funcar = XCAR (fun); + if (!SYMBOLP (funcar)) + xsignal1 (Qinvalid_function, original_fun); + if (EQ (funcar, Qlambda) + || EQ (funcar, Qclosure)) + val = funcall_lambda (fun, numargs, args + 1); + else if (EQ (funcar, Qautoload)) { - XSETFASTINT (lisp_numargs, numargs); - xsignal2 (Qwrong_number_of_arguments, original_fun, lisp_numargs); + Fautoload_do_load (fun, original_fun, Qnil); + check_cons_list (); + goto retry; } - - else if (XSUBR (fun)->max_args == UNEVALLED) + else xsignal1 (Qinvalid_function, original_fun); + } + check_cons_list (); + lisp_eval_depth--; + if (backtrace_debug_on_exit (specpdl + count)) + val = call_debugger (list2 (Qexit, val)); + specpdl_ptr--; + return val; +} - else if (XSUBR (fun)->max_args == MANY) - val = (XSUBR (fun)->function.aMANY) (numargs, args + 1); + +/* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR + and return the result of evaluation. */ + +Lisp_Object +funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *args) +{ + if (numargs < subr->min_args + || (subr->max_args >= 0 && subr->max_args < numargs)) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal2 (Qwrong_number_of_arguments, fun, make_number (numargs)); + } + + else if (subr->max_args == UNEVALLED) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal1 (Qinvalid_function, fun); + } + + else if (subr->max_args == MANY) + return (subr->function.aMANY) (numargs, args); else { Lisp_Object internal_argbuf[8]; - if (XSUBR (fun)->max_args > numargs) + Lisp_Object *internal_args; + if (subr->max_args > numargs) { - eassert (XSUBR (fun)->max_args <= ARRAYELTS (internal_argbuf)); + eassert (subr->max_args <= ARRAYELTS (internal_argbuf)); internal_args = internal_argbuf; - memcpy (internal_args, args + 1, numargs * word_size); + memcpy (internal_args, args, numargs * word_size); memclear (internal_args + numargs, - (XSUBR (fun)->max_args - numargs) * word_size); + (subr->max_args - numargs) * word_size); } else - internal_args = args + 1; - switch (XSUBR (fun)->max_args) + internal_args = args; + switch (subr->max_args) { case 0: - val = (XSUBR (fun)->function.a0 ()); - break; + return (subr->function.a0 ()); case 1: - val = (XSUBR (fun)->function.a1 (internal_args[0])); - break; + return (subr->function.a1 (internal_args[0])); case 2: - val = (XSUBR (fun)->function.a2 + return (subr->function.a2 (internal_args[0], internal_args[1])); - break; case 3: - val = (XSUBR (fun)->function.a3 + return (subr->function.a3 (internal_args[0], internal_args[1], internal_args[2])); - break; case 4: - val = (XSUBR (fun)->function.a4 + return (subr->function.a4 (internal_args[0], internal_args[1], internal_args[2], internal_args[3])); - break; case 5: - val = (XSUBR (fun)->function.a5 + return (subr->function.a5 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4])); - break; case 6: - val = (XSUBR (fun)->function.a6 + return (subr->function.a6 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5])); - break; case 7: - val = (XSUBR (fun)->function.a7 + return (subr->function.a7 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6])); - break; - case 8: - val = (XSUBR (fun)->function.a8 + return (subr->function.a8 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6], internal_args[7])); - break; default: @@ -2759,36 +2794,6 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, } } } - else if (COMPILEDP (fun)) - val = funcall_lambda (fun, numargs, args + 1); - else - { - if (NILP (fun)) - xsignal1 (Qvoid_function, original_fun); - if (!CONSP (fun)) - xsignal1 (Qinvalid_function, original_fun); - funcar = XCAR (fun); - if (!SYMBOLP (funcar)) - xsignal1 (Qinvalid_function, original_fun); - if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) - val = funcall_lambda (fun, numargs, args + 1); - else if (EQ (funcar, Qautoload)) - { - Fautoload_do_load (fun, original_fun, Qnil); - check_cons_list (); - goto retry; - } - else - xsignal1 (Qinvalid_function, original_fun); - } - check_cons_list (); - lisp_eval_depth--; - if (backtrace_debug_on_exit (specpdl + count)) - val = call_debugger (list2 (Qexit, val)); - specpdl_ptr--; - return val; -} static Lisp_Object apply_lambda (Lisp_Object fun, Lisp_Object args, ptrdiff_t count) @@ -3158,10 +3163,10 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.symbol = symbol; specpdl_ptr->let.old_value = SYMBOL_VAL (sym); grow_specpdl (); - if (!sym->constant) + if (!sym->trapped_write) SET_SYMBOL_VAL (sym, value); else - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; case SYMBOL_LOCALIZED: if (SYMBOL_BLV (sym)->frame_local) @@ -3201,7 +3206,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.kind = SPECPDL_LET; grow_specpdl (); - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; } default: emacs_abort (); @@ -3328,14 +3333,16 @@ unbind_to (ptrdiff_t count, Lisp_Object value) case SPECPDL_BACKTRACE: break; case SPECPDL_LET: - { /* If variable has a trivial value (no forwarding), we can - just set it. No need to check for constant symbols here, - since that was already done by specbind. */ + { /* If variable has a trivial value (no forwarding), and + isn't trapped, we can just set it. */ Lisp_Object sym = specpdl_symbol (specpdl_ptr); if (SYMBOLP (sym) && XSYMBOL (sym)->redirect == SYMBOL_PLAINVAL) { - SET_SYMBOL_VAL (XSYMBOL (sym), - specpdl_old_value (specpdl_ptr)); + if (XSYMBOL (sym)->trapped_write == SYMBOL_UNTRAPPED_WRITE) + SET_SYMBOL_VAL (XSYMBOL (sym), specpdl_old_value (specpdl_ptr)); + else + set_internal (sym, specpdl_old_value (specpdl_ptr), + Qnil, SET_INTERNAL_UNBIND); break; } else @@ -3358,7 +3365,7 @@ unbind_to (ptrdiff_t count, Lisp_Object value) /* If this was a local binding, reset the value in the appropriate buffer, but only if that buffer's binding still exists. */ if (!NILP (Flocal_variable_p (symbol, where))) - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } break; } @@ -3583,7 +3590,7 @@ backtrace_eval_unrewind (int distance) { set_specpdl_old_value (tmp, Fbuffer_local_value (symbol, where)); - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } } break; @@ -3927,6 +3934,7 @@ syms_of_eval (void) defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); + DEFSYM (Qdefvaralias, "defvaralias"); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); diff --git a/src/font.c b/src/font.c index ce63233..3b821a4 100644 --- a/src/font.c +++ b/src/font.c @@ -5417,19 +5417,19 @@ syms_of_font (void) [NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...] NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */); Vfont_weight_table = BUILD_STYLE_TABLE (weight_table); - XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-weight-table")); DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table, doc: /* Vector of font slant symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_slant_table = BUILD_STYLE_TABLE (slant_table); - XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-slant-table")); DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table, doc: /* Alist of font width symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_width_table = BUILD_STYLE_TABLE (width_table); - XSYMBOL (intern_c_string ("font-width-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-width-table")); staticpro (&font_style_table); font_style_table = make_uninit_vector (3); diff --git a/src/lisp.h b/src/lisp.h index 2e46592..701ee9c 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -320,7 +320,8 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) #define lisp_h_NILP(x) EQ (x, Qnil) #define lisp_h_SET_SYMBOL_VAL(sym, v) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v)) -#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant) +#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->trapped_write == SYMBOL_NOWRITE) +#define lisp_h_SYMBOL_TRAPPED_WRITE_P(sym) (XSYMBOL (sym)->trapped_write) #define lisp_h_SYMBOL_VAL(sym) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value) #define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol) @@ -375,6 +376,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) # define NILP(x) lisp_h_NILP (x) # define SET_SYMBOL_VAL(sym, v) lisp_h_SET_SYMBOL_VAL (sym, v) # define SYMBOL_CONSTANT_P(sym) lisp_h_SYMBOL_CONSTANT_P (sym) +# define SYMBOL_TRAPPED_WRITE_P(sym) lisp_h_SYMBOL_TRAPPED_WRITE_P (sym) # define SYMBOL_VAL(sym) lisp_h_SYMBOL_VAL (sym) # define SYMBOLP(x) lisp_h_SYMBOLP (x) # define VECTORLIKEP(x) lisp_h_VECTORLIKEP (x) @@ -602,6 +604,9 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); /* Defined in data.c. */ extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object); extern _Noreturn void wrong_choice (Lisp_Object, Lisp_Object); +extern void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, + Lisp_Object operation, Lisp_Object where); + /* Defined in emacs.c. */ #ifdef DOUG_LEA_MALLOC @@ -632,6 +637,13 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); SYMBOL_FORWARDED = 3 }; +enum symbol_trapped_write +{ + SYMBOL_UNTRAPPED_WRITE = 0, + SYMBOL_NOWRITE = 1, + SYMBOL_TRAPPED_WRITE = 2 +}; + struct Lisp_Symbol { bool_bf gcmarkbit : 1; @@ -643,10 +655,10 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); 3 : it's a forwarding variable, the value is in `forward'. */ ENUM_BF (symbol_redirect) redirect : 3; - /* Non-zero means symbol is constant, i.e. changing its value - should signal an error. If the value is 3, then the var - can be changed, but only by `defconst'. */ - unsigned constant : 2; + /* 0 : normal case, just set the value + 1 : constant, cannot set, e.g. nil, t, :keywords. + 2 : trap the write, call watcher functions. */ + ENUM_BF (symbol_trapped_write) trapped_write : 2; /* Interned state of the symbol. This is an enumerator from enum symbol_interned. */ @@ -1850,9 +1862,20 @@ SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (Lisp_Object sym) return XSYMBOL (sym)->interned == SYMBOL_INTERNED_IN_INITIAL_OBARRAY; } -/* Value is non-zero if symbol is considered a constant, i.e. its - value cannot be changed (there is an exception for keyword symbols, - whose value can be set to the keyword symbol itself). */ +/* Value is non-zero if symbol cannot be changed through a simple set, + i.e. it's a constant (e.g. nil, t, :keywords), or it has some + watching functions. */ + +INLINE int +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) +{ + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); +} + +/* Value is non-zero if symbol cannot be changed at all, i.e. it's a + constant (e.g. nil, t, :keywords). Code that actually wants to + write to SYM, should also check whether there are any watching + functions. */ INLINE int (SYMBOL_CONSTANT_P) (Lisp_Object sym) @@ -3289,6 +3312,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) XSYMBOL (sym)->next = next; } +INLINE void +make_symbol_constant (Lisp_Object sym) +{ + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; +} + /* Buffer-local (also frame-local) variable access functions. */ INLINE int @@ -3397,7 +3426,13 @@ set_sub_char_table_contents (Lisp_Object table, ptrdiff_t idx, Lisp_Object val) extern _Noreturn void args_out_of_range_3 (Lisp_Object, Lisp_Object, Lisp_Object); extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *); -extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool); +enum Set_Internal_Bind { + SET_INTERNAL_SET, + SET_INTERNAL_BIND, + SET_INTERNAL_UNBIND +}; +extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, + enum Set_Internal_Bind); extern void syms_of_data (void); extern void swap_in_global_binding (struct Lisp_Symbol *); @@ -3880,6 +3915,7 @@ xsignal (Lisp_Object error_symbol, Lisp_Object data) extern _Noreturn void xsignal3 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object); extern _Noreturn void signal_error (const char *, Lisp_Object); +extern Lisp_Object funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *arg_vector); extern Lisp_Object eval_sub (Lisp_Object form); extern Lisp_Object apply1 (Lisp_Object, Lisp_Object); extern Lisp_Object call0 (Lisp_Object); diff --git a/src/lread.c b/src/lread.c index 58d518c..7e74703 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3833,7 +3833,7 @@ intern_sym (Lisp_Object sym, Lisp_Object obarray, Lisp_Object index) if (SREF (SYMBOL_NAME (sym), 0) == ':' && EQ (obarray, initial_obarray)) { - XSYMBOL (sym)->constant = 1; + make_symbol_constant (sym); XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL; SET_SYMBOL_VAL (XSYMBOL (sym), sym); } @@ -4120,12 +4120,12 @@ init_obarray (void) DEFSYM (Qnil, "nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); - XSYMBOL (Qnil)->constant = 1; + make_symbol_constant (Qnil); XSYMBOL (Qnil)->declared_special = true; DEFSYM (Qt, "t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); - XSYMBOL (Qt)->constant = 1; + make_symbol_constant (Qt); XSYMBOL (Qt)->declared_special = true; /* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */ -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0002-Show-watchpoints-when-describing-variables.patch Content-Description: patch >From dda5dc2280e77c664e7f479eb9bd84e2db2024f0 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 19 Nov 2016 16:50:34 -0500 Subject: [PATCH v6 2/6] Show watchpoints when describing variables * src/data.c (Fget_variable_watchers): New function. * lisp/help-fns.el (describe-variable): Use it to detect watching functions. --- lisp/help-fns.el | 7 +++++++ src/data.c | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 87e7d8f..23dec89 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -918,6 +918,7 @@ describe-variable (indirect-variable variable) (error variable))) (obsolete (get variable 'byte-obsolete-variable)) + (watchpoints (get-variable-watchers variable)) (use (car obsolete)) (safe-var (get variable 'safe-local-variable)) (doc (or (documentation-property @@ -967,6 +968,12 @@ describe-variable (t "."))) (terpri)) + (when watchpoints + (setq extra-line t) + (princ " Calls these functions when changed: ") + (princ watchpoints) + (terpri)) + (when (member (cons variable val) (with-current-buffer buffer file-local-variables-alist)) diff --git a/src/data.c b/src/data.c index 8954b42..911789f 100644 --- a/src/data.c +++ b/src/data.c @@ -1463,6 +1463,16 @@ SYMBOL (or its aliases) are set. */) return Qnil; } +DEFUN ("get-variable-watchers", Fget_variable_watchers, Sget_variable_watchers, + 1, 1, 0, + doc: /* Return a list of SYMBOL's active watchers. */) + (Lisp_Object symbol) +{ + return (SYMBOL_TRAPPED_WRITE_P (symbol) == SYMBOL_TRAPPED_WRITE) + ? Fget (Findirect_variable (symbol), Qwatchers) + : Qnil; +} + void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, @@ -3880,4 +3890,5 @@ syms_of_data (void) DEFSYM (Qset_default, "set-default"); defsubr (&Sadd_variable_watcher); defsubr (&Sremove_variable_watcher); + defsubr (&Sget_variable_watchers); } -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0003-Add-function-to-trigger-debugger-on-variable-writ.patch Content-Description: patch >From 68b3861097e1b6ea8e353e89aa339a59645d6a37 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 16:03:06 -0500 Subject: [PATCH v6 3/6] Add function to trigger debugger on variable write * lisp/emacs-lisp/debug.el (debug-on-variable-change): (debug--variable-list): (cancel-debug-on-variable-change): New functions. (debugger-setup-buffer): Add watchpoint clause. --- lisp/emacs-lisp/debug.el | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 7d27380..0fefc54 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -306,6 +306,24 @@ debugger-setup-buffer (delete-char 1) (insert ? ) (beginning-of-line)) + ;; Watchpoint triggered. + ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) + (insert + "--" + (pcase details + (`(makunbound nil) (format "making %s void" symbol)) + (`(makunbound ,buffer) (format "killing local value of %s in buffer %s" + symbol buffer)) + (`(defvaralias ,_) (format "aliasing %s to %s" symbol newval)) + (`(let ,_) (format "let-binding %s to %S" symbol newval)) + (`(unlet ,_) (format "ending let-binding of %s" symbol)) + (`(set nil) (format "setting %s to %S" symbol newval)) + (`(set ,buffer) (format "setting %s in buffer %s to %S" + symbol buffer newval)) + (_ (error "unrecognized watchpoint triggered %S" (cdr args)))) + ": ") + (setq pos (point)) + (insert ?\n)) ;; Debugger entered for an error. (`error (insert "--Lisp error: ") @@ -850,6 +868,79 @@ debugger-list-functions (princ "Note: if you have redefined a function, then it may no longer\n") (princ "be set to debug on entry, even if it is in the list.")))))) +(defun debug--implement-debug-watch (symbol newval op where) + "Conditionally call the debugger. +This function is called when SYMBOL's value is modified." + (if (or inhibit-debug-on-entry debugger-jumping-flag) + nil + (let ((inhibit-debug-on-entry t)) + (funcall debugger 'watchpoint symbol newval op where)))) + +;;;###autoload +(defun debug-on-variable-change (variable) + "Trigger a debugger invocation when VARIABLE is changed. + +When called interactively, prompt for VARIABLE in the minibuffer. + +This works by calling `add-variable-watch' on VARIABLE. If you +quit from the debugger, this will abort the change (unless the +change is caused by the termination of a let-binding). + +The watchpoint may be circumvented by C code that changes the +variable directly (i.e., not via `set'). Changing the value of +the variable (e.g., `setcar' on a list variable) will not trigger +watchpoint. + +Use \\[cancel-debug-on-variable-change] to cancel the effect of +this command. Uninterning VARIABLE or making it an alias of +another symbol also cancels it." + (interactive + (let* ((var-at-point (variable-at-point)) + (var (and (symbolp var-at-point) var-at-point)) + (val (completing-read + (concat "Debug when setting variable" + (if var (format " (default %s): " var) ": ")) + obarray #'boundp + t nil nil (and var (symbol-name var))))) + (list (if (equal val "") var (intern val))))) + (add-variable-watcher variable #'debug--implement-debug-watch)) + +;;;###autoload +(defalias 'debug-watch #'debug-on-variable-change) + + +(defun debug--variable-list () + "List of variables currently set for debug on set." + (let ((vars '())) + (mapatoms + (lambda (s) + (when (memq #'debug--implement-debug-watch + (get s 'watchers)) + (push s vars)))) + vars)) + +;;;###autoload +(defun cancel-debug-on-variable-change (&optional variable) + "Undo effect of \\[debug-on-entry] on VARIABLE. +If VARIABLE is nil, cancel debug-on-variable-change for all variables. +When called interactively, prompt for VARIABLE in the minibuffer. +To specify a nil argument interactively, exit with an empty minibuffer." + (interactive + (list (let ((name + (completing-read + "Cancel debug on set for variable (default all variables): " + (mapcar #'symbol-name (debug--variable-list)) nil t))) + (when name + (unless (string= name "") + (intern name)))))) + (if variable + (remove-variable-watcher variable #'debug--implement-debug-watch) + (message "Canceling debug-watch for all variables") + (mapc #'cancel-debug-watch (debug--variable-list)))) + +;;;###autoload +(defalias 'cancel-debug-watch #'cancel-debug-on-variable-change) + (provide 'debug) ;;; debug.el ends here -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0004-Ensure-redisplay-using-variable-watcher.patch Content-Description: patch >From 5d87668684319bb165ed0f31f637c44cda5716a6 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 17:02:42 -0500 Subject: [PATCH v6 4/6] Ensure redisplay using variable watcher Instead of looking up the variable name in redisplay--variables when setting. * lisp/frame.el: Replace redisplay--variables with add-variable-watcher calls. * src/xdisp.c (Fset_buffer_redisplay): Rename from maybe_set_redisplay, set the redisplay flag unconditionally. (Vredisplay__variables): Remove it. * src/data.c (set_internal): Remove maybe_set_redisplay call. --- lisp/frame.el | 3 +-- src/data.c | 2 -- src/window.h | 1 - src/xdisp.c | 17 +++++++---------- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lisp/frame.el b/lisp/frame.el index a584567..1dffc6c 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2249,9 +2249,8 @@ 'automatic-hscrolling 'window-system-version "it does not give useful information." "24.3") ;; Variables which should trigger redisplay of the current buffer. -(setq redisplay--variables (make-hash-table :test 'eq :size 10)) (mapc (lambda (var) - (puthash var 1 redisplay--variables)) + (add-variable-watcher var (symbol-function 'set-buffer-redisplay))) '(line-spacing overline-margin line-prefix diff --git a/src/data.c b/src/data.c index 911789f..ff35315 100644 --- a/src/data.c +++ b/src/data.c @@ -1275,8 +1275,6 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, default: emacs_abort (); } - maybe_set_redisplay (symbol); - start: switch (sym->redirect) { diff --git a/src/window.h b/src/window.h index a124b33..4a102f2 100644 --- a/src/window.h +++ b/src/window.h @@ -1063,7 +1063,6 @@ void set_window_buffer (Lisp_Object window, Lisp_Object buffer, extern void fset_redisplay (struct frame *f); extern void bset_redisplay (struct buffer *b); extern void bset_update_mode_line (struct buffer *b); -extern void maybe_set_redisplay (Lisp_Object); /* Call this to tell redisplay to look for other windows than selected-window that need to be redisplayed. Calling one of the *set_redisplay functions above already does it, so it's only needed in unusual cases. */ diff --git a/src/xdisp.c b/src/xdisp.c index 6e8af8a..9d36ab6 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -622,15 +622,15 @@ bset_update_mode_line (struct buffer *b) b->text->redisplay = true; } -void -maybe_set_redisplay (Lisp_Object symbol) -{ - if (HASH_TABLE_P (Vredisplay__variables) - && hash_lookup (XHASH_TABLE (Vredisplay__variables), symbol, NULL) >= 0) +DEFUN ("set-buffer-redisplay", Fset_buffer_redisplay, + Sset_buffer_redisplay, 4, 4, 0, + doc: /* Mark the current buffer for redisplay. +This function may be passed to `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object newval, Lisp_Object op, Lisp_Object where) { bset_update_mode_line (current_buffer); current_buffer->prevent_redisplay_optimizations_p = true; - } + return Qnil; } #ifdef GLYPH_DEBUG @@ -31319,6 +31319,7 @@ syms_of_xdisp (void) message_dolog_marker3 = Fmake_marker (); staticpro (&message_dolog_marker3); + defsubr (&Sset_buffer_redisplay); #ifdef GLYPH_DEBUG defsubr (&Sdump_frame_glyph_matrix); defsubr (&Sdump_glyph_matrix); @@ -31988,10 +31989,6 @@ or t (meaning all windows). */); doc: /* */); Vredisplay__mode_lines_cause = Fmake_hash_table (0, NULL); - DEFVAR_LISP ("redisplay--variables", Vredisplay__variables, - doc: /* A hash-table of variables changing which triggers a thorough redisplay. */); - Vredisplay__variables = Qnil; - DEFVAR_BOOL ("redisplay--inhibit-bidi", redisplay__inhibit_bidi, doc: /* Non-nil means it is not safe to attempt bidi reordering for display. */); /* Initialize to t, since we need to disable reordering until -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0005-Add-tests-for-watchpoints.patch Content-Description: patch >From 10d324a797981a3f89e890ed3806a9f470f3b7bf Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 12 Dec 2015 23:10:15 -0500 Subject: [PATCH v6 5/6] Add tests for watchpoints * test/src/data-tests.el (data-tests-variable-watchers): (data-tests-local-variable-watchers): New tests. --- test/src/data-tests.el | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/src/data-tests.el b/test/src/data-tests.el index 0a29233..4c2ea54 100644 --- a/test/src/data-tests.el +++ b/test/src/data-tests.el @@ -255,3 +255,118 @@ test-bool-vector-binop (v2 (test-bool-vector-bv-from-hex-string "0000C")) (v3 (bool-vector-not v1))) (should (equal v2 v3)))) + +(ert-deftest data-tests-variable-watchers () + (defvar data-tests-var 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 1) + (should-have-watch-data '(data-tests-var 1 set nil)) + (let ((data-tests-var 2)) + (should-have-watch-data '(data-tests-var 2 let nil)) + (setq data-tests-var 3) + (should-have-watch-data '(data-tests-var 3 set nil))) + (should-have-watch-data '(data-tests-var 1 unlet nil)) + ;; `setq-default' on non-local variable is same as `setq'. + (setq-default data-tests-var 4) + (should-have-watch-data '(data-tests-var 4 set nil)) + (makunbound 'data-tests-var) + (should-have-watch-data '(data-tests-var nil makunbound nil)) + (setq data-tests-var 5) + (should-have-watch-data '(data-tests-var 5 set nil)) + (remove-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 6) + (should (null watch-data))))) + +(ert-deftest data-tests-varalias-watchers () + (defvar data-tests-var0 0) + (defvar data-tests-var1 0) + (defvar data-tests-var2 0) + (defvar data-tests-var3 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + ;; Watch var0, then alias it. + (add-variable-watcher 'data-tests-var0 collect-watch-data) + (defvaralias 'data-tests-var0-alias 'data-tests-var0) + (setq data-tests-var0 1) + (should-have-watch-data '(data-tests-var0 1 set nil)) + (setq data-tests-var0-alias 2) + (should-have-watch-data '(data-tests-var0 2 set nil)) + ;; Alias var1, then watch var1-alias. + (defvaralias 'data-tests-var1-alias 'data-tests-var1) + (add-variable-watcher 'data-tests-var1-alias collect-watch-data) + (setq data-tests-var1 1) + (should-have-watch-data '(data-tests-var1 1 set nil)) + (setq data-tests-var1-alias 2) + (should-have-watch-data '(data-tests-var1 2 set nil)) + ;; Alias var2, then watch it. + (defvaralias 'data-tests-var2-alias 'data-tests-var2) + (add-variable-watcher 'data-tests-var2 collect-watch-data) + (setq data-tests-var2 1) + (should-have-watch-data '(data-tests-var2 1 set nil)) + (setq data-tests-var2-alias 2) + (should-have-watch-data '(data-tests-var2 2 set nil)) + ;; Watch var3-alias, then make it alias var3 (this removes the + ;; watcher flag). + (defvar data-tests-var3-alias 0) + (add-variable-watcher 'data-tests-var3-alias collect-watch-data) + (defvaralias 'data-tests-var3-alias 'data-tests-var3) + (should-have-watch-data '(data-tests-var3-alias + data-tests-var3 defvaralias nil)) + (setq data-tests-var3 1) + (setq data-tests-var3-alias 2) + (should (null watch-data))))) + +(ert-deftest data-tests-local-variable-watchers () + (defvar-local data-tests-lvar 0) + (let* ((buf1 (current-buffer)) + (buf2 nil) + (watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf1)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf1)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf1))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf1)) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (with-temp-buffer + (setq buf2 (current-buffer)) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf2)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf2)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf2))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf2)) + (kill-local-variable 'data-tests-lvar) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2)) + (setq data-tests-lvar 3.5) + (should-have-watch-data `(data-tests-lvar 3.5 set ,buf2)) + (kill-all-local-variables) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2))) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (makunbound 'data-tests-lvar) + (should-have-watch-data '(data-tests-lvar nil makunbound nil)) + (setq data-tests-lvar 5) + (should-have-watch-data `(data-tests-lvar 5 set ,buf1)) + (remove-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 6) + (should (null watch-data))))) -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6-0006-Document-watchpoints.patch Content-Description: patch >From 984109b9b204c82ce2e6482210425a70b7b7e867 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 13 Dec 2015 14:47:58 -0500 Subject: [PATCH v6 6/6] Document watchpoints * doc/lispref/debugging.texi (Variable Debugging): * doc/lispref/variables.texi (Watching Variables): New section. * etc/NEWS: Add entry for watchpoints --- doc/lispref/debugging.texi | 32 +++++++++++++++++++++++ doc/lispref/variables.texi | 63 ++++++++++++++++++++++++++++++++++++++++++++++ etc/NEWS | 5 ++++ src/data.c | 9 +++++++ 4 files changed, 109 insertions(+) diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 6c0908a..8cae203 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi @@ -69,6 +69,7 @@ Debugger * Error Debugging:: Entering the debugger when an error happens. * Infinite Loops:: Stopping and debugging a program that doesn't exit. * Function Debugging:: Entering it when a certain function is called. +* Variable Debugging:: Entering it when a variable is modified. * Explicit Debug:: Entering it at a certain point in the program. * Using Debugger:: What the debugger does; what you see while in it. * Debugger Commands:: Commands used while in the debugger. @@ -290,6 +291,37 @@ Function Debugging not currently set up to break on entry. @end deffn +@node Variable Debugging +@subsection Entering the debugger when a variable is modified +@cindex variable write debugging +@cindex debugging changes to variables + +Sometimes a problem with a function is due to a wrong setting of a +variable. Setting up the debugger to trigger whenever the variable is +changed is quick way to find the origin of the setting. + +@deffn Command debug-on-variable-change variable +This function arranges causes the debugger to be called whenever +@var{variable} is modified. + +It is implemented using the watchpoint mechanism, so it inherits the +same characteristics and limitations: all aliases of @var{variable} +will be watched together, only dynamic variables can be watched, and +changes to the objects referenced by variables are not detected. For +details, see @xref{Watching Variables}. + +@end deffn + +@deffn Command cancel-debug-on-variable-change &optional variable +This function undoes the effect of @code{debug-on-variable-change} on +@var{variable}. When called interactively, it prompts for +@var{variable} in the minibuffer. If @var{variable} is omitted or +@code{nil}, it cancels break-on-change for all variables. Calling +@code{cancel-debug-on-variable-change} does nothing to a variable +which is not currently set up to break on change. +@end deffn + + @node Explicit Debug @subsection Explicit Entry to the Debugger @cindex debugger, explicit entry diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 418a416..07f787c 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -34,6 +34,7 @@ Variables * Accessing Variables:: Examining values of variables whose names are known only at run time. * Setting Variables:: Storing new values in variables. +* Watching Variables:: Running a function when a variable is changed. * Variable Scoping:: How Lisp chooses among local and global values. * Buffer-Local Variables:: Variable values in effect only in one buffer. * File Local Variables:: Handling local variable lists in files. @@ -765,6 +766,68 @@ Setting Variables @end example @end defun +@node Watching Variables +@section Running a function when a variable is changed. +@cindex variable watchpoints + +It is sometimes useful to take some action when a variable changes its +value. The watchpoint facility provides the means to do so. Some +possible uses for this feature include keeping display in sync with +variable settings, and invoking the debugger to track down unexpected +changes to variables @pxref{Variable Debugging}. + +Each variable has a list of watch functions stored in its +@code{watchers} symbol property, @xref{Symbol Properties}. However, +for efficiency reasons, the list is only consulted if symbol is marked +as watched. Therefore, the watch function list should only be +manipulated by the following functions, which take care of the +symbol's watched status in addition to the property value. + +@defun add-variable-watcher symbol watch-function +This function arranges for @var{watch-function} to be called whenever +@var{symbol} (or any of its aliases @pxref{Variable Aliases}) are +modified. + +It will be called with 4 arguments: (@var{symbol} @var{newval} +@var{operation} @var{where}). + +@var{symbol} is the variable being changed. +@var{newval} is the value it will be changed to. +@var{operation} is a symbol representing the kind of change, one of: +`set', `let', `unlet', `makunbound', and `defvaralias'. +@var{where} is a buffer if the buffer-local value of the variable +being changed, nil otherwise. +@end defun + +@defun remove-variable-watch symbol watch-function +This function removes @var{watch-function} from @var{symbol}'s list of +watchers. +@end defun + +@defun get-variable-watchers symbol +This function returns the list of active watcher functions. +@end defun + +@subsection Limitations + +There are a couple of ways in which a variable could be modifed (or at +least appear to be modified) without triggering a watchpoint. + +Since the watchpoint are attached to symbols, modification to the +objects contained within variables (e.g., by a list modification +function @pxref{Modifying Lists}) is not caught by this mechanism. + +Additionally, C code can modify the value of variables directly, +bypassing the watchpoint mechanism. + +A minor limitation of this feature, again because it targets symbols, +is that only variables of dynamic scope may be watched. This poses +little difficulty, since modifications to lexical variables can be +discovered easily by inspecting the code within the scope of the +variable (unlike dynamic variables which can be modified by any code +at all, @pxref{Variable Scoping}). + + @node Variable Scoping @section Scoping Rules for Variable Bindings @cindex scoping rule diff --git a/etc/NEWS b/etc/NEWS index e29dfe2..fcbbb44 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -627,6 +627,11 @@ consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. +++ +** New function `add-variable-watcher' can be used to call a function +when a symbol's value is changed. This is used to implement the new +debugger command `debug-on-variable-change'. + ++++ ** Time conversion functions that accept a time zone rule argument now allow it to be OFFSET or a list (OFFSET ABBR), where the integer OFFSET is a count of seconds east of Universal Time, and the string diff --git a/src/data.c b/src/data.c index ff35315..ef6b48b 100644 --- a/src/data.c +++ b/src/data.c @@ -1428,6 +1428,15 @@ harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, 2, 2, 0, doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. + +It will be called with 4 arguments: (SYMBOL NEWVAL OPERATION WHERE). +SYMBOL is the variable being changed. +NEWVAL is the value it will be changed to. +OPERATION is a symbol representing the kind of change, one of: `set', +`let', `unlet', `makunbound', and `defvaralias'. +WHERE is a buffer if the buffer-local value of the variable being +changed, nil otherwise. + All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) (Lisp_Object symbol, Lisp_Object watch_function) { -- 2.9.3 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 05:50:14 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 10:50:14 +0000 Received: from localhost ([127.0.0.1]:35639 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8PhN-00037P-RR for submit@debbugs.gnu.org; Sun, 20 Nov 2016 05:50:14 -0500 Received: from mout.gmx.net ([212.227.15.18]:60630) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8PhL-000377-2v for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 05:50:11 -0500 Received: from rosalinde ([89.245.65.169]) by mail.gmx.com (mrgmx001 [212.227.17.190]) with ESMTPSA (Nemesis) id 0LaooK-1cb6c21c0O-00kO7e; Sun, 20 Nov 2016 11:49:50 +0100 From: Stephen Berman To: npostavs@users.sourceforge.net Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> Date: Sun, 20 Nov 2016 11:49:49 +0100 In-Reply-To: <874m32lx1e.fsf@users.sourceforge.net> (npostavs's message of "Sat, 19 Nov 2016 21:12:13 -0500") Message-ID: <87k2bya0j6.fsf@gmx.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Provags-ID: V03:K0:8jr+fw4n+QYpuHRtt8KwiyfobDbBay1Jct0FOe7h70jk2gJcJ+0 6Rzb6GAXd4e82NkETpatlhAYCAyktllYFyHe2mhTy4xkw2QVoB4GjPMmpJLxuLDhsCuzrPF 1fsjktwqa5GYxmFBp1ffVVPMFUscTZ5OSkHDH8epNNfPNI4s6dxEEKoa9MjUlz4yhoQmbhd ZvxDUveh/0nQe9Xr42P4A== X-UI-Out-Filterresults: notjunk:1;V01:K0:bqKzFmi6jB8=:ZX9mlTH3tV0F36LaPU7g8x Ln4SEHHd4fu1ArCUsYmIuSksfnW1aOXGewhJOaxA31VMkgrTo37TwCl+SLL64vfQ0OvsVclvj g2+XcvN0VdNs72a0MjAQ8+2zcEeDv/Fc/GZpLcB+a7My79S+PRhGTK5HBEbpkfMuIUQQYT6Q3 uPDUn3TuGTOI2QTYBhnzfRvopGhCBFNqNNLQn9eQPKO1ozKQTtm6sI/jyrgsCYbmxQg8MzP3g 4EaVMIMVLFsW3KclqHsycvxufbF7lEXpOE/w6kitw6z8D5fgKcJ9fDfC5klkW2s8sBQVOZtaw u35Vx9ndyNPMEkWYdbTOYtSDxxk+B/O2TWrBcfJ5k03cQeHpHm1nmv/z0RKA9DY4f9wUoumh/ U28TfS/tgdxUJAB8KP5gBnvZZtdqZCEiwTkPMF0UFZk2H+fUeGusupWLG9n1wlg3k2CGp0XK0 /Q6x4qTTbWOEqaQYLdc6s+w5i6MXNML31u6auggkrH0QMt9IZheMEXUbpvLpGeYbdfQZZfx1h XsH7zX3Fy1ZVsAHwTqZDNA/Vha6YkUk3R1ywtkHf/tE0Wu1Mz586YwMDXslAL/Dd+YDUZK2F0 qaLblcuRhqV54Rpr9WCOWZWt6/wUuYb8JlLG42BqprBZ3J5AL0m5ERHjH5gPLEW0Ws4HrU/kv bsM9jLOrCp+Pu7gxrPXo3aQq4TRtEm+a5QOrumI6g3OOeX+dTPeyAT3SW9I4YT8KaNchTcjnu jpvkt82qhAwcpLhSr1u9y5aqMaPLJxdtRJjKIeZDveTbKDwj5RZiBM9WK1uFbBBsiXen44ql5 Ige2txv X-Spam-Score: -3.6 (---) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, Eli Zaretskii X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.6 (---) There are a few typos in the documentation: On Sat, 19 Nov 2016 21:12:13 -0500 npostavs@users.sourceforge.net wrote: > From c318be2dc401d2f3b958ceb3b48e466a3019091e Mon Sep 17 00:00:00 2001 > From: Noam Postavsky > Date: Thu, 19 Nov 2015 19:50:06 -0500 > Subject: [PATCH v6 1/6] Add lisp watchpoints > > This allows to call a function whenever a symbol-value is changed. ^^^^^^^ calling > From 5d87668684319bb165ed0f31f637c44cda5716a6 Mon Sep 17 00:00:00 2001 > From: Noam Postavsky > Date: Sat, 21 Nov 2015 17:02:42 -0500 > Subject: [PATCH v6 4/6] Ensure redisplay using variable watcher > > Instead of looking up the variable name in redisplay--variables when > setting. Incomplete sentence? Or if it's the continuation of the Subject: line, shouldn't it be lowercase? > From 984109b9b204c82ce2e6482210425a70b7b7e867 Mon Sep 17 00:00:00 2001 > From: Noam Postavsky > Date: Sun, 13 Dec 2015 14:47:58 -0500 > Subject: [PATCH v6 6/6] Document watchpoints > > * doc/lispref/debugging.texi (Variable Debugging): > * doc/lispref/variables.texi (Watching Variables): New section. > * etc/NEWS: Add entry for watchpoints > --- > doc/lispref/debugging.texi | 32 +++++++++++++++++++++++ > doc/lispref/variables.texi | 63 ++++++++++++++++++++++++++++++++++++++++++++++ > etc/NEWS | 5 ++++ > src/data.c | 9 +++++++ > 4 files changed, 109 insertions(+) > > diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi > index 6c0908a..8cae203 100644 > --- a/doc/lispref/debugging.texi > +++ b/doc/lispref/debugging.texi [...] > @@ -290,6 +291,37 @@ Function Debugging > not currently set up to break on entry. > @end deffn > > +@node Variable Debugging > +@subsection Entering the debugger when a variable is modified > +@cindex variable write debugging > +@cindex debugging changes to variables > + > +Sometimes a problem with a function is due to a wrong setting of a > +variable. Setting up the debugger to trigger whenever the variable is > +changed is quick way to find the origin of the setting. ^ a > + > +@deffn Command debug-on-variable-change variable > +This function arranges causes the debugger to be called whenever ^^^^^^^^^^^^^^^ either "arranges for" or "causes" > diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi > index 418a416..07f787c 100644 > --- a/doc/lispref/variables.texi > +++ b/doc/lispref/variables.texi [...] > @@ -765,6 +766,68 @@ Setting Variables > @end example > @end defun > > +@node Watching Variables > +@section Running a function when a variable is changed. > +@cindex variable watchpoints > + > +It is sometimes useful to take some action when a variable changes its > +value. The watchpoint facility provides the means to do so. Some > +possible uses for this feature include keeping display in sync with > +variable settings, and invoking the debugger to track down unexpected > +changes to variables @pxref{Variable Debugging}. > + > +Each variable has a list of watch functions stored in its > +@code{watchers} symbol property, @xref{Symbol Properties}. However, > +for efficiency reasons, the list is only consulted if symbol is marked ^ the > +as watched. Therefore, the watch function list should only be > +manipulated by the following functions, which take care of the > +symbol's watched status in addition to the property value. > + > +@defun add-variable-watcher symbol watch-function > +This function arranges for @var{watch-function} to be called whenever > +@var{symbol} (or any of its aliases @pxref{Variable Aliases}) are > +modified. > + > +It will be called with 4 arguments: (@var{symbol} @var{newval} > +@var{operation} @var{where}). > + > +@var{symbol} is the variable being changed. > +@var{newval} is the value it will be changed to. > +@var{operation} is a symbol representing the kind of change, one of: > +`set', `let', `unlet', `makunbound', and `defvaralias'. > +@var{where} is a buffer if the buffer-local value of the variable ^ is > +being changed, nil otherwise. > +@end defun > + > +@defun remove-variable-watch symbol watch-function > +This function removes @var{watch-function} from @var{symbol}'s list of > +watchers. > +@end defun > + > +@defun get-variable-watchers symbol > +This function returns the list of active watcher functions. > +@end defun > + > +@subsection Limitations > + > +There are a couple of ways in which a variable could be modifed (or at > +least appear to be modified) without triggering a watchpoint. > + > +Since the watchpoint are attached to symbols, modification to the ^^^^^^^^^^^^^^ watchpoints > +objects contained within variables (e.g., by a list modification > +function @pxref{Modifying Lists}) is not caught by this mechanism. > + > +Additionally, C code can modify the value of variables directly, > +bypassing the watchpoint mechanism. > + > +A minor limitation of this feature, again because it targets symbols, > +is that only variables of dynamic scope may be watched. This poses > +little difficulty, since modifications to lexical variables can be > +discovered easily by inspecting the code within the scope of the > +variable (unlike dynamic variables which can be modified by any code ^ , [comma] > +at all, @pxref{Variable Scoping}). > + > + > @node Variable Scoping > @section Scoping Rules for Variable Bindings > @cindex scoping rule [...] > diff --git a/src/data.c b/src/data.c > index ff35315..ef6b48b 100644 > --- a/src/data.c > +++ b/src/data.c > @@ -1428,6 +1428,15 @@ harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) > DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, > 2, 2, 0, > doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. > + > +It will be called with 4 arguments: (SYMBOL NEWVAL OPERATION WHERE). > +SYMBOL is the variable being changed. > +NEWVAL is the value it will be changed to. > +OPERATION is a symbol representing the kind of change, one of: `set', > +`let', `unlet', `makunbound', and `defvaralias'. > +WHERE is a buffer if the buffer-local value of the variable being ^ is > +changed, nil otherwise. > + > All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) > (Lisp_Object symbol, Lisp_Object watch_function) > { Steve Berman From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 09:13:18 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 14:13:18 +0000 Received: from localhost ([127.0.0.1]:35732 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Sru-0001n1-Bp for submit@debbugs.gnu.org; Sun, 20 Nov 2016 09:13:18 -0500 Received: from mail-io0-f182.google.com ([209.85.223.182]:34300) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Srt-0001mp-Ah for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 09:13:17 -0500 Received: by mail-io0-f182.google.com with SMTP id c21so20957307ioj.1 for <24923@debbugs.gnu.org>; Sun, 20 Nov 2016 06:13:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=ozRylucf2FKV6X5dZcR9k+35ASX2SUUHfeI38uVNAMY=; b=HJp5vcFVgdeUWiT3gzk/+ily3lV0BC752y1xxSF1v2l90sfAVcWyXrH/z6syU2EjeY Ei3BBqGABnHSdcCawRdX2pS8cwnfTMqbLcFMvFARuZCwo9X9r17tAO/iJXTVhbhg8Mxl nQpiTgHADfhF9VVVDjqeDQ6xd//gTmVGfr8QriAU09+/7ERxANMtxrVcn+ttyFUMBDGS 4uQTYqUPlA0EUVPRxZ1UgvVRmEtxyQSPHU6/zyOJsoe8uFa2pC9xe9oH7F9qIj58HsLr BzqC4rqEo7jFExqYQMY0YO4KP+VE1DVC6jlzhp3wFjSE8DZ/kSPV9K+KSZx1BRFo41Bc ClQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=ozRylucf2FKV6X5dZcR9k+35ASX2SUUHfeI38uVNAMY=; b=LwABDv4US8wM9G+/0Dki0glcOBj7BpHwvWnxDycWNJUph62hzzSgaDfEu7GJYirx0M dwp42nhDZBr5Cs3QAJjXFRjM1zTtEGRkGqswg4f4tHNz2mh1iwKZdbqXxHXKgPTZgApW 1zJuYAe34tRCmRjiIyEc4CFamFUOWMd/88nEtN72SkFHrw/NtXY/vkhloEh8FSmo5U8k sNSy2NMtsQpmcxsSQ2/sI4YmO/oD+ShtyApm69Am8OnEh78/ZqrFsW+UXCrGOfFXZQ+O B/yFigTtLNRoscGgA8FQ4UN2IpTck/2T4NIDcZH8UsPSyytWvdT5eY1EbRaK9wPGnVhU Af1g== X-Gm-Message-State: AKaTC02LcErA6aVqIoGKdDd0ijvjzJnCIjnMkkQYEiYlspd4USo6AMt55WFUa1E+kRoKiA== X-Received: by 10.107.48.134 with SMTP id w128mr9179781iow.226.1479651191493; Sun, 20 Nov 2016 06:13:11 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id l14sm6415359ioi.18.2016.11.20.06.13.10 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 20 Nov 2016 06:13:10 -0800 (PST) From: npostavs@users.sourceforge.net To: Stephen Berman Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> Date: Sun, 20 Nov 2016 09:14:02 -0500 In-Reply-To: <87k2bya0j6.fsf@gmx.net> (Stephen Berman's message of "Sun, 20 Nov 2016 11:49:49 +0100") Message-ID: <87wpfyjl1x.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, Eli Zaretskii X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) --=-=-= Content-Type: text/plain Stephen Berman writes: > There are a few typos in the documentation: Thanks, reattaching just the fixed documentation commit. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v6.1-0006-Document-watchpoints.patch Content-Description: patch >From b2aff88d97942c32fb0e2695e47ad2cb3433c71f Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 13 Dec 2015 14:47:58 -0500 Subject: [PATCH v6.1 6/6] Document watchpoints * doc/lispref/debugging.texi (Variable Debugging): * doc/lispref/variables.texi (Watching Variables): New section. * etc/NEWS: Add entry for watchpoints --- doc/lispref/debugging.texi | 32 +++++++++++++++++++++++ doc/lispref/variables.texi | 63 ++++++++++++++++++++++++++++++++++++++++++++++ etc/NEWS | 5 ++++ src/data.c | 9 +++++++ 4 files changed, 109 insertions(+) diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 6c0908a..c047d45 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi @@ -69,6 +69,7 @@ Debugger * Error Debugging:: Entering the debugger when an error happens. * Infinite Loops:: Stopping and debugging a program that doesn't exit. * Function Debugging:: Entering it when a certain function is called. +* Variable Debugging:: Entering it when a variable is modified. * Explicit Debug:: Entering it at a certain point in the program. * Using Debugger:: What the debugger does; what you see while in it. * Debugger Commands:: Commands used while in the debugger. @@ -290,6 +291,37 @@ Function Debugging not currently set up to break on entry. @end deffn +@node Variable Debugging +@subsection Entering the debugger when a variable is modified +@cindex variable write debugging +@cindex debugging changes to variables + +Sometimes a problem with a function is due to a wrong setting of a +variable. Setting up the debugger to trigger whenever the variable is +changed is a quick way to find the origin of the setting. + +@deffn Command debug-on-variable-change variable +This function arranges for the debugger to be called whenever +@var{variable} is modified. + +It is implemented using the watchpoint mechanism, so it inherits the +same characteristics and limitations: all aliases of @var{variable} +will be watched together, only dynamic variables can be watched, and +changes to the objects referenced by variables are not detected. For +details, see @xref{Watching Variables}. + +@end deffn + +@deffn Command cancel-debug-on-variable-change &optional variable +This function undoes the effect of @code{debug-on-variable-change} on +@var{variable}. When called interactively, it prompts for +@var{variable} in the minibuffer. If @var{variable} is omitted or +@code{nil}, it cancels break-on-change for all variables. Calling +@code{cancel-debug-on-variable-change} does nothing to a variable +which is not currently set up to break on change. +@end deffn + + @node Explicit Debug @subsection Explicit Entry to the Debugger @cindex debugger, explicit entry diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 418a416..1e0b098 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -34,6 +34,7 @@ Variables * Accessing Variables:: Examining values of variables whose names are known only at run time. * Setting Variables:: Storing new values in variables. +* Watching Variables:: Running a function when a variable is changed. * Variable Scoping:: How Lisp chooses among local and global values. * Buffer-Local Variables:: Variable values in effect only in one buffer. * File Local Variables:: Handling local variable lists in files. @@ -765,6 +766,68 @@ Setting Variables @end example @end defun +@node Watching Variables +@section Running a function when a variable is changed. +@cindex variable watchpoints + +It is sometimes useful to take some action when a variable changes its +value. The watchpoint facility provides the means to do so. Some +possible uses for this feature include keeping display in sync with +variable settings, and invoking the debugger to track down unexpected +changes to variables @pxref{Variable Debugging}. + +Each variable has a list of watch functions stored in its +@code{watchers} symbol property, @xref{Symbol Properties}. However, +for efficiency reasons, the list is only consulted if the symbol is +marked as watched. Therefore, the watch function list should only be +manipulated by the following functions, which take care of the +symbol's watched status in addition to the property value. + +@defun add-variable-watcher symbol watch-function +This function arranges for @var{watch-function} to be called whenever +@var{symbol} (or any of its aliases @pxref{Variable Aliases}) are +modified. + +It will be called with 4 arguments: (@var{symbol} @var{newval} +@var{operation} @var{where}). + +@var{symbol} is the variable being changed. +@var{newval} is the value it will be changed to. +@var{operation} is a symbol representing the kind of change, one of: +`set', `let', `unlet', `makunbound', and `defvaralias'. +@var{where} is a buffer if the buffer-local value of the variable is +being changed, nil otherwise. +@end defun + +@defun remove-variable-watch symbol watch-function +This function removes @var{watch-function} from @var{symbol}'s list of +watchers. +@end defun + +@defun get-variable-watchers symbol +This function returns the list of active watcher functions. +@end defun + +@subsection Limitations + +There are a couple of ways in which a variable could be modifed (or at +least appear to be modified) without triggering a watchpoint. + +Since watchpoints are attached to symbols, modification to the +objects contained within variables (e.g., by a list modification +function @pxref{Modifying Lists}) is not caught by this mechanism. + +Additionally, C code can modify the value of variables directly, +bypassing the watchpoint mechanism. + +A minor limitation of this feature, again because it targets symbols, +is that only variables of dynamic scope may be watched. This poses +little difficulty, since modifications to lexical variables can be +discovered easily by inspecting the code within the scope of the +variable (unlike dynamic variables, which can be modified by any code +at all, @pxref{Variable Scoping}). + + @node Variable Scoping @section Scoping Rules for Variable Bindings @cindex scoping rule diff --git a/etc/NEWS b/etc/NEWS index e29dfe2..fcbbb44 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -627,6 +627,11 @@ consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. +++ +** New function `add-variable-watcher' can be used to call a function +when a symbol's value is changed. This is used to implement the new +debugger command `debug-on-variable-change'. + ++++ ** Time conversion functions that accept a time zone rule argument now allow it to be OFFSET or a list (OFFSET ABBR), where the integer OFFSET is a count of seconds east of Universal Time, and the string diff --git a/src/data.c b/src/data.c index ff35315..ef6b48b 100644 --- a/src/data.c +++ b/src/data.c @@ -1428,6 +1428,15 @@ harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, 2, 2, 0, doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. + +It will be called with 4 arguments: (SYMBOL NEWVAL OPERATION WHERE). +SYMBOL is the variable being changed. +NEWVAL is the value it will be changed to. +OPERATION is a symbol representing the kind of change, one of: `set', +`let', `unlet', `makunbound', and `defvaralias'. +WHERE is a buffer if the buffer-local value of the variable being +changed, nil otherwise. + All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) (Lisp_Object symbol, Lisp_Object watch_function) { -- 2.9.3 --=-=-= Content-Type: text/plain > >> From 5d87668684319bb165ed0f31f637c44cda5716a6 Mon Sep 17 00:00:00 2001 >> From: Noam Postavsky >> Date: Sat, 21 Nov 2015 17:02:42 -0500 >> Subject: [PATCH v6 4/6] Ensure redisplay using variable watcher >> >> Instead of looking up the variable name in redisplay--variables when >> setting. > > Incomplete sentence? Or if it's the continuation of the Subject: line, > shouldn't it be lowercase? I sort of about it as a continuation, but it looks a bit weird to make it an official continuation when there's a blank line in between. I rephrased: This replaces checking for the variable name in redisplay--variables when setting it. --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 10:58:20 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 15:58:20 +0000 Received: from localhost ([127.0.0.1]:36264 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8UVY-0006Qy-CA for submit@debbugs.gnu.org; Sun, 20 Nov 2016 10:58:20 -0500 Received: from eggs.gnu.org ([208.118.235.92]:50074) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8UVW-0006Qf-4Y for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 10:58:18 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c8UVM-0007og-R4 for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 10:58:12 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.2 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:60914) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c8UVM-0007oc-O7; Sun, 20 Nov 2016 10:58:08 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1664 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c8UVL-00063d-E2; Sun, 20 Nov 2016 10:58:08 -0500 Date: Sun, 20 Nov 2016 17:58:17 +0200 Message-Id: <83mvgu9m92.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <874m32lx1e.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org > Date: Sat, 19 Nov 2016 21:12:13 -0500 > > Eli Zaretskii writes: > > > > Our coding conventions put the logical operators at the beginning of a > > line, not at EOL. > > Fixed this, and added documentation. Also, watcher functions are now > listed in describe-variable output. Thanks. > Does it make sense to mention the use of the `watchers' symbol property > in the manual? Since I've added a `get-variable-watchers' it's now > possible to ignore the symbol property as an implementation detail. I think the property can indeed be left undocumented. > @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, > If buffer/frame-locality is an issue, WHERE specifies which context to use. > (nil stands for the current buffer/frame). > > - If BINDFLAG is false, then if this symbol is supposed to become > - local in every buffer where it is set, then we make it local. > - If BINDFLAG is true, we don't do that. */ > + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to > + become local in every buffer where it is set, then we make it > + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we > + don't do that. */ What are those SET_INTERNAL_* values? They are numbers, right? Then they should be described as such in the doc string. > +(defun cancel-debug-on-variable-change (&optional variable) > + "Undo effect of \\[debug-on-entry] on VARIABLE. ^^^^^^^^^^^^^^^^^^ Copy/paste error. I will comment on the documentation in a separate message. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 11:11:44 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 16:11:44 +0000 Received: from localhost ([127.0.0.1]:36269 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8UiW-0006oh-IY for submit@debbugs.gnu.org; Sun, 20 Nov 2016 11:11:44 -0500 Received: from eggs.gnu.org ([208.118.235.92]:51701) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8UiU-0006oP-Ok for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 11:11:43 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c8UiM-0002Yx-AM for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 11:11:37 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.2 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:32792) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c8UiM-0002Yt-6y; Sun, 20 Nov 2016 11:11:34 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1673 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c8UiL-0000s1-FY; Sun, 20 Nov 2016 11:11:33 -0500 Date: Sun, 20 Nov 2016 18:11:43 +0200 Message-Id: <83lgwe9lmo.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87wpfyjl1x.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org, Eli Zaretskii > Date: Sun, 20 Nov 2016 09:14:02 -0500 > > > There are a few typos in the documentation: > > Thanks, reattaching just the fixed documentation commit. > diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi > index 6c0908a..c047d45 100644 > --- a/doc/lispref/debugging.texi > +++ b/doc/lispref/debugging.texi > @@ -69,6 +69,7 @@ Debugger > * Error Debugging:: Entering the debugger when an error happens. > * Infinite Loops:: Stopping and debugging a program that doesn't exit. > * Function Debugging:: Entering it when a certain function is called. > +* Variable Debugging:: Entering it when a variable is modified. This additional menu item should be also added to the master menu in elisp.texi. > +@deffn Command debug-on-variable-change variable > +This function arranges for the debugger to be called whenever > +@var{variable} is modified. > + > +It is implemented using the watchpoint mechanism, so it inherits the > +same characteristics and limitations: all aliases of @var{variable} > +will be watched together, only dynamic variables can be watched, and > +changes to the objects referenced by variables are not detected. For > +details, see @xref{Watching Variables}. ^^^^^ @xref already generates "See", capitalized, so you want @ref here. > diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi > index 418a416..1e0b098 100644 > --- a/doc/lispref/variables.texi > +++ b/doc/lispref/variables.texi > @@ -34,6 +34,7 @@ Variables > * Accessing Variables:: Examining values of variables whose names > are known only at run time. > * Setting Variables:: Storing new values in variables. > +* Watching Variables:: Running a function when a variable is changed. Likewise, this should be added to the master menu. > +@node Watching Variables > +@section Running a function when a variable is changed. > +@cindex variable watchpoints I'd add here an index entry that begins with "watchpoint", like this: @cindex watchpoints for Lisp variables That's for those who like using completion in Info-index command. > +variable settings, and invoking the debugger to track down unexpected > +changes to variables @pxref{Variable Debugging}. I believe you meant to put @pxref in parentheses. > +Each variable has a list of watch functions stored in its > +@code{watchers} symbol property, @xref{Symbol Properties}. ^^^^^ Either "see @ref" or "@pxref", because @xref generates a capitalized "See", and so is only appropriate at the beginning of a sentence. > +@defun add-variable-watcher symbol watch-function > +This function arranges for @var{watch-function} to be called whenever > +@var{symbol} (or any of its aliases @pxref{Variable Aliases}) are ^ > +modified. A comma is missing here. Also, I believe "is modified" reads better here. Or maybe replace the parentheses with commas, then plural should be okay, I think. > +@defun get-variable-watchers symbol > +This function returns the list of active watcher functions. Please mention SYMBOL here. Thanks. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 12:00:09 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 17:00:10 +0000 Received: from localhost ([127.0.0.1]:36310 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8VTN-0008AB-Oj for submit@debbugs.gnu.org; Sun, 20 Nov 2016 12:00:09 -0500 Received: from mail-io0-f177.google.com ([209.85.223.177]:34033) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8VTL-00088o-4P for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 12:00:07 -0500 Received: by mail-io0-f177.google.com with SMTP id c21so22800493ioj.1 for <24923@debbugs.gnu.org>; Sun, 20 Nov 2016 09:00:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=nrK8kbegXbnG2c8+ujbOBSP01QrybDN0LnlEcmVn0ww=; b=EColidJSEBixEKYoY8I+4FDX5f3phIbBX0xU0uWiAVKCKhEGXPNnwmVYdQRt5tDzpc fo+ExQSALFqiREGpmfbDcPZq4aNqgkQU03MWuekFKXISMyZjNFqzF2bpgokYIvIeVBxd F+D9PBlUjtJkbEdhZtYtgAnA6KrpBJmAgg91ptnZgzseSShGt6BjUg9TGrWfu3eu96n1 ITr8qsrfmDEcfpmwl36d5IVYp/daV4/OZNgOoSo5APZUiglsxfcuGa+9BDlZYKlCDGxw LhdoiybV7lBFrR9JbgvBShUYEk6NeDnfj4xXKjbr4cVzpwHK3cwkwuPTSg3R/uyS6IP4 KS/g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=nrK8kbegXbnG2c8+ujbOBSP01QrybDN0LnlEcmVn0ww=; b=gpLeThCFJLIG/3z1Ksijd6FiONJKobu5bycIt9m3f+UXTlvkQhiNr0hZPDX8bDQcBs 9gEWLkU3tZo4UsNC65EgtRvkM22X+1NqDVaC0yMAoxUg79BGlnXZpKHu7HwpnZF7mILr QGQggzR6djA9hfMZFKL+b5T0/chjnrcq2eh2ZzCU/1oFbtsFgoCvgsjJJBrDmcLKOFij kq4CgPTz1vv2SajPly1KIOlek7sWZyrOSEnjAerXYrbKHfNfRFkNGXx2bhZX0yhp54me ZEqOHh71P6CMDjhGS7acyU58nYr0EU2Ag2eF47VhXFO4ftsHz4kYnWp6SpAWitUBysPt 0maA== X-Gm-Message-State: AKaTC0356Cb5AhXNO+5XEOjjBylfvG85h/GkQAmvCMaW5+md7K2zNxaO5xII4pMRjvaX+g== X-Received: by 10.107.131.150 with SMTP id n22mr7386395ioi.71.1479661201358; Sun, 20 Nov 2016 09:00:01 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id o144sm6617421iod.40.2016.11.20.09.00.00 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 20 Nov 2016 09:00:00 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <83mvgu9m92.fsf@gnu.org> Date: Sun, 20 Nov 2016 12:00:52 -0500 In-Reply-To: <83mvgu9m92.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 20 Nov 2016 17:58:17 +0200") Message-ID: <87polqjdbv.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) Eli Zaretskii writes: >> @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, >> If buffer/frame-locality is an issue, WHERE specifies which context to use. >> (nil stands for the current buffer/frame). >> >> - If BINDFLAG is false, then if this symbol is supposed to become >> - local in every buffer where it is set, then we make it local. >> - If BINDFLAG is true, we don't do that. */ >> + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to >> + become local in every buffer where it is set, then we make it >> + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we >> + don't do that. */ > > What are those SET_INTERNAL_* values? They are numbers, right? Then > they should be described as such in the doc string. They're enum values. Perhaps you were confused by the hunk header? That's not a doc string, it's the comment on set_internal, a C function. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 12:25:05 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 17:25:05 +0000 Received: from localhost ([127.0.0.1]:36323 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8VrV-0000Rk-E4 for submit@debbugs.gnu.org; Sun, 20 Nov 2016 12:25:05 -0500 Received: from eggs.gnu.org ([208.118.235.92]:35981) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8VrU-0000RB-Aj for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 12:25:04 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c8VrM-00078Z-6G for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 12:24:59 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-3.0 required=5.0 tests=BAYES_20,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:33668) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c8VrM-00078U-2f; Sun, 20 Nov 2016 12:24:56 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1765 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c8VrL-0000S1-Ch; Sun, 20 Nov 2016 12:24:55 -0500 Date: Sun, 20 Nov 2016 19:25:06 +0200 Message-Id: <83bmxa9i8d.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87polqjdbv.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <83mvgu9m92.fsf@gnu.org> <87polqjdbv.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org > Date: Sun, 20 Nov 2016 12:00:52 -0500 > > Eli Zaretskii writes: > >> @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, > >> If buffer/frame-locality is an issue, WHERE specifies which context to use. > >> (nil stands for the current buffer/frame). > >> > >> - If BINDFLAG is false, then if this symbol is supposed to become > >> - local in every buffer where it is set, then we make it local. > >> - If BINDFLAG is true, we don't do that. */ > >> + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to > >> + become local in every buffer where it is set, then we make it > >> + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we > >> + don't do that. */ > > > > What are those SET_INTERNAL_* values? They are numbers, right? Then > > they should be described as such in the doc string. > > They're enum values. Perhaps you were confused by the hunk header? > That's not a doc string, it's the comment on set_internal, a C function. Sorry, I thought it was a doc string. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 14:25:34 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 19:25:34 +0000 Received: from localhost ([127.0.0.1]:36418 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Xk6-0003nx-6Z for submit@debbugs.gnu.org; Sun, 20 Nov 2016 14:25:34 -0500 Received: from mail-it0-f45.google.com ([209.85.214.45]:36929) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Xk1-0003nd-Io for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 14:25:32 -0500 Received: by mail-it0-f45.google.com with SMTP id y23so88328570itc.0 for <24923@debbugs.gnu.org>; Sun, 20 Nov 2016 11:25:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=TVPBpVSYEaH3Ga2jdq5qz3+E3EjUQw0KW11ekhJg66Y=; b=JHUVlNyf+90KiXBxwrsCWTmffYv51N0tHhKfU9a3tzx7qSVi2r3kHGNgl0iHHeEqwj I5PNsnaJ2B/hAeHp5kc3s5RdcdKWkGyzJKVOkT6bjw8jwXbH2U0mKkgxP7LPYtMDacVK JE3StZrA2LT32pn8dIl5AHl8px3Uq98hUpZhKu5234WciD3heyYURKlRZxvqJkS6wMte Fx0lY3e/jrfbRxNCNQ0nW6QN11M7lY8qgaYuXJ7RBEKa/co+ojtrMJX3kzoh7MjtpCYM Knwf5QLSLuQVpR7FNGFMtDCFl0InaTn6EIssuO2i5Z4s9jhDGUYvETOC9qq+zfU90qcg pAzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=TVPBpVSYEaH3Ga2jdq5qz3+E3EjUQw0KW11ekhJg66Y=; b=Lp4LzRKvIXpmHZ5V5mYIpFJu7vp5pwseIkZZSZSJ+JcYeCovT7CRFYClNbwthLcozD aCDSj52T+WK9RsGOMk/e1GSfnxevcVlpUgkRdxn9A06qtXnCAXmBUN6200ZvpRy6BVe6 nvFxRcP94N6Q1VhW2aGytQyxqqeRN9olhJV9Pfbb7qlyJ63RNdAlrCgcOqnWZzelUNnw DLVEWSfdeWcnbRjXo2TA6FIcLjaHllZlf9vnvNCqjeJpcl6G2bZCML2DLnj944kIrD1B 9wW8wOus6oVI0lsLDrQCXUe3hEDIX2FPVR+QIN/ZJ0+PTI3Hd1T3+i/ylAQq+hLQTJb0 XjCQ== X-Gm-Message-State: AKaTC02OW4kAohXBxK+JKVl0MiJiBXhCTHAtTzOWJHs/leC2DASLTV4KiGQ8AoxcQKfNwQ== X-Received: by 10.36.193.197 with SMTP id e188mr6136615itg.99.1479669923907; Sun, 20 Nov 2016 11:25:23 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id l73sm4544398ioe.37.2016.11.20.11.25.23 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 20 Nov 2016 11:25:23 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> Date: Sun, 20 Nov 2016 14:26:15 -0500 In-Reply-To: <83lgwe9lmo.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 20 Nov 2016 18:11:43 +0200") Message-ID: <87mvguj6lk.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.7 (/) Eli Zaretskii writes: >> +changes to the objects referenced by variables are not detected. For >> +details, see @xref{Watching Variables}. > ^^^^^ > @xref already generates "See", capitalized, so you want @ref here. > >> +Each variable has a list of watch functions stored in its >> +@code{watchers} symbol property, @xref{Symbol Properties}. > ^^^^^ > Either "see @ref" or "@pxref", because @xref generates a capitalized > "See", and so is only appropriate at the beginning of a sentence. @xref seems to be generating lowercase "see" for me, perhaps because I'm using makeinfo 4.13? I'll change to @ref anyway. > >> +@defun add-variable-watcher symbol watch-function >> +This function arranges for @var{watch-function} to be called whenever >> +@var{symbol} (or any of its aliases @pxref{Variable Aliases}) are > ^ >> +modified. > > A comma is missing here. Also, I believe "is modified" reads better > here. Or maybe replace the parentheses with commas, then plural > should be okay, I think. Hmm, maybe rephrasing like this: @defun add-variable-watcher symbol watch-function This function arranges for @var{watch-function} to be called whenever @var{symbol} is modified. Modifications through aliases (@pxref{Variable Aliases}) will have the same effect. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 14:36:58 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 19:36:58 +0000 Received: from localhost ([127.0.0.1]:36423 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Xv8-00047n-8h for submit@debbugs.gnu.org; Sun, 20 Nov 2016 14:36:58 -0500 Received: from eggs.gnu.org ([208.118.235.92]:53941) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8Xv6-00047X-U8 for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 14:36:57 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c8Xv0-000151-Ou for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 14:36:51 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.2 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:34677) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c8Xuv-00010v-8W; Sun, 20 Nov 2016 14:36:45 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2211 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c8Xus-0006BR-Cc; Sun, 20 Nov 2016 14:36:44 -0500 Date: Sun, 20 Nov 2016 21:36:40 +0200 Message-Id: <83wpfy7xkn.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87mvguj6lk.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -7.9 (-------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -7.9 (-------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net > Date: Sun, 20 Nov 2016 14:26:15 -0500 > > Eli Zaretskii writes: > > >> +changes to the objects referenced by variables are not detected. For > >> +details, see @xref{Watching Variables}. > > ^^^^^ > > @xref already generates "See", capitalized, so you want @ref here. > > > >> +Each variable has a list of watch functions stored in its > >> +@code{watchers} symbol property, @xref{Symbol Properties}. > > ^^^^^ > > Either "see @ref" or "@pxref", because @xref generates a capitalized > > "See", and so is only appropriate at the beginning of a sentence. > > @xref seems to be generating lowercase "see" for me, perhaps because I'm > using makeinfo 4.13? Unlikely. Are you looking at the file in Info, or as plain text? The former has its own ideas about how to display cross-references; I meant what is actually in the file. > @defun add-variable-watcher symbol watch-function > This function arranges for @var{watch-function} to be called whenever > @var{symbol} is modified. Modifications through aliases > (@pxref{Variable Aliases}) will have the same effect. Fine with me, thanks. From debbugs-submit-bounces@debbugs.gnu.org Sun Nov 20 15:16:10 2016 Received: (at 24923) by debbugs.gnu.org; 20 Nov 2016 20:16:10 +0000 Received: from localhost ([127.0.0.1]:36433 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8YX2-0005DL-RX for submit@debbugs.gnu.org; Sun, 20 Nov 2016 15:16:10 -0500 Received: from mail-io0-f171.google.com ([209.85.223.171]:32817) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8YWz-0005CZ-NI for 24923@debbugs.gnu.org; Sun, 20 Nov 2016 15:16:07 -0500 Received: by mail-io0-f171.google.com with SMTP id j65so24957960iof.0 for <24923@debbugs.gnu.org>; Sun, 20 Nov 2016 12:16:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=89TFijDTTyT4NkfsibqDwIKOjr8vmv7IwxTOKHz2Rdg=; b=pkEJeQo7RdIssQzdGOlvu/OUisdHx/AnSP+EhNtprs9KUM/CdBIhwXcie4bM1a7zt/ YeWl5sKkQdBEZozAdjXpJ8tYRQ+cYZd86XSDGoXNZmvVNlz0ShnRvEfYlegdrKwjnMmF 4Hhs4K6YIOmn/Y+We9c1RPcmwm++uj2DcNCGNc7h3wYZWcZWhM1F8gqiuDjHQPwQm+De K0G9T2LV3CVD5KV34CeGjBL7oTuJ9HeB/gWZq3f8It7I0STjEcdngd1xO/hqqtRwpMbh Tu54TIld9HKs7Si9AMAoo0lw0pvvbxWW3dHh3kzQArpQb6E6b4AzGH/oDu2Cv0EHrsa3 1Uiw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=89TFijDTTyT4NkfsibqDwIKOjr8vmv7IwxTOKHz2Rdg=; b=QLKTNP6A1bvaiZv3B8AaSyTmUY0/AkI6DpXASUhMvGIpwwaUpdUn4+jUodb8gL64ao PJB1e+vBChSTO3Bjs+lkvnjEH00HWlpWOWoQHUbSAYhgPn6qqaIAbdb/o51yq9N9/lxp /S3QLrsBarwGcn0b14qBtwJNaaKX+ca9FpvoVh98AQNGnhz1mauVGkCp2EkW6hhKdyKh Q9KloOzgzQnRRBeN0hpS/3ogcs4MnR+kUPZA9q5ETOKliFU4let3XClbhY/keo+hq0JD 5RGVEgrWIP1SpeOvbSijwtGrkf8gN5jJSNeQXPwe1UAAjIDBNz1AMZCR+ph28CzRYSWX anvg== X-Gm-Message-State: AKaTC01kIGh+iQ8Uemg9SQdfsG85Dvw/oy3WIquXVefhcxnL/251SXG4y3pvRefV2ZCxGQ== X-Received: by 10.107.135.219 with SMTP id r88mr8091734ioi.224.1479672960047; Sun, 20 Nov 2016 12:16:00 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id p20sm5320768itc.2.2016.11.20.12.15.57 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 20 Nov 2016 12:15:59 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> Date: Sun, 20 Nov 2016 15:16:49 -0500 In-Reply-To: <83wpfy7xkn.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 20 Nov 2016 21:36:40 +0200") Message-ID: <87k2bxkitq.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) --=-=-= Content-Type: text/plain Eli Zaretskii writes: >> >> @xref seems to be generating lowercase "see" for me, perhaps because I'm >> using makeinfo 4.13? > > Unlikely. Are you looking at the file in Info, or as plain text? The > former has its own ideas about how to display cross-references; I > meant what is actually in the file. I was looking at the file in Info-mode. Looking in fundamental-mode, it seems that @xref generates an uppercase "*Note", where @ref generates "*note". Anyway, here is the final(?) patchset: --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0001-Add-lisp-watchpoints.patch Content-Description: patch >From e62c29c23590af50801278e49ffab8aa0a975248 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 19 Nov 2015 19:50:06 -0500 Subject: [PATCH v7 1/6] Add lisp watchpoints This allows calling a function whenever a symbol-value is changed. * src/lisp.h (lisp_h_SYMBOL_TRAPPED_WRITE_P): (SYMBOL_TRAPPED_WRITE_P): New function/macro. (lisp_h_SYMBOL_CONSTANT_P): Check for SYMBOL_NOWRITE specifically. (enum symbol_trapped_write): New enumeration. (struct Lisp_Symbol): Rename field constant to trapped_write. (make_symbol_constant): New function. * src/data.c (Fadd_variable_watcher, Fremove_variable_watcher): (set_symbol_trapped_write, restore_symbol_trapped_write): (harmonize_variable_watchers, notify_variable_watchers): New functions. * src/data.c (Fset_default): Call `notify_variable_watchers' for trapped symbols. (set_internal): Change bool argument BIND to 3-value enum and call `notify_variable_watchers' for trapped symbols. * src/data.c (syms_of_data): * src/data.c (syms_of_data): * src/font.c (syms_of_font): * src/lread.c (intern_sym, init_obarray): * src/buffer.c (syms_of_buffer): Use make_symbol_constant. * src/alloc.c (init_symbol): * src/bytecode.c (exec_byte_code): Use SYMBOL_TRAPPED_WRITE_P. * src/data.c (Fmake_variable_buffer_local, Fmake_local_variable): (Fmake_variable_frame_local): * src/eval.c (Fdefvaralias, specbind): Refer to Lisp_Symbol's trapped_write instead of constant. (Ffuncall): Move subr calling code into separate function. (funcall_subr): New function. --- src/alloc.c | 2 +- src/buffer.c | 22 +++++-- src/bytecode.c | 4 +- src/data.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/eval.c | 172 +++++++++++++++++++++++++++++-------------------------- src/font.c | 6 +- src/lisp.h | 54 +++++++++++++++--- src/lread.c | 6 +- 8 files changed, 324 insertions(+), 119 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index a58dc13..f373f6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3562,7 +3562,7 @@ init_symbol (Lisp_Object val, Lisp_Object name) set_symbol_next (val, NULL); p->gcmarkbit = false; p->interned = SYMBOL_UNINTERNED; - p->constant = 0; + p->trapped_write = SYMBOL_UNTRAPPED_WRITE; p->declared_special = false; p->pinned = false; } diff --git a/src/buffer.c b/src/buffer.c index 3d205bb..cc75cdb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -984,9 +984,13 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, Qnil); else { - Lisp_Object tmp, prop, last = Qnil; + Lisp_Object tmp, last = Qnil; for (tmp = BVAR (b, local_var_alist); CONSP (tmp); tmp = XCDR (tmp)) - if (!NILP (prop = Fget (XCAR (XCAR (tmp)), Qpermanent_local))) + { + Lisp_Object local_var = XCAR (XCAR (tmp)); + Lisp_Object prop = Fget (local_var, Qpermanent_local); + + if (!NILP (prop)) { /* If permanent-local, keep it. */ last = tmp; @@ -1010,7 +1014,12 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) || !NILP (Fget (elt, Qpermanent_local_hook))) newlist = Fcons (elt, newlist); } - XSETCDR (XCAR (tmp), Fnreverse (newlist)); + newlist = Fnreverse (newlist); + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, newlist, + Qmakunbound, Fcurrent_buffer ()); + XSETCDR (XCAR (tmp), newlist); + continue; /* Don't do variable write trapping twice. */ } } /* Delete this local variable. */ @@ -1018,6 +1027,11 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too) bset_local_var_alist (b, XCDR (tmp)); else XSETCDR (last, XCDR (tmp)); + + if (XSYMBOL (local_var)->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (local_var, Qnil, + Qmakunbound, Fcurrent_buffer ()); + } } for (i = 0; i < last_per_buffer_idx; ++i) @@ -5682,7 +5696,7 @@ syms_of_buffer (void) This variable is buffer-local but you cannot set it directly; use the function `set-buffer-multibyte' to change a buffer's representation. See also Info node `(elisp)Text Representations'. */); - XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1; + make_symbol_constant (intern_c_string ("enable-multibyte-characters")); DEFVAR_PER_BUFFER ("buffer-file-coding-system", &BVAR (current_buffer, buffer_file_coding_system), Qnil, diff --git a/src/bytecode.c b/src/bytecode.c index e2d8ab7..18eaf9f 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -569,10 +569,10 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (SYMBOLP (sym) && !EQ (val, Qunbound) && !XSYMBOL (sym)->redirect - && !SYMBOL_CONSTANT_P (sym)) + && !SYMBOL_TRAPPED_WRITE_P (sym)) SET_SYMBOL_VAL (XSYMBOL (sym), val); else - set_internal (sym, val, Qnil, false); + set_internal (sym, val, Qnil, SET_INTERNAL_SET); } NEXT; diff --git a/src/data.c b/src/data.c index d221db4..8954b42 100644 --- a/src/data.c +++ b/src/data.c @@ -1225,7 +1225,7 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */) (register Lisp_Object symbol, Lisp_Object newval) { - set_internal (symbol, newval, Qnil, 0); + set_internal (symbol, newval, Qnil, SET_INTERNAL_SET); return newval; } @@ -1233,13 +1233,14 @@ DEFUN ("set", Fset, Sset, 2, 2, 0, If buffer/frame-locality is an issue, WHERE specifies which context to use. (nil stands for the current buffer/frame). - If BINDFLAG is false, then if this symbol is supposed to become - local in every buffer where it is set, then we make it local. - If BINDFLAG is true, we don't do that. */ + If BINDFLAG is SET_INTERNAL_SET, then if this symbol is supposed to + become local in every buffer where it is set, then we make it + local. If BINDFLAG is SET_INTERNAL_BIND or SET_INTERNAL_UNBIND, we + don't do that. */ void set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, - bool bindflag) + enum Set_Internal_Bind bindflag) { bool voide = EQ (newval, Qunbound); struct Lisp_Symbol *sym; @@ -1250,18 +1251,31 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; */ CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) || !EQ (newval, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return; + + case SYMBOL_TRAPPED_WRITE: + notify_variable_watchers (symbol, voide? Qnil : newval, + (bindflag == SET_INTERNAL_BIND? Qlet : + bindflag == SET_INTERNAL_UNBIND? Qunlet : + voide? Qmakunbound : Qset), + where); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } maybe_set_redisplay (symbol); - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1386,6 +1400,111 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, return; } +static void +set_symbol_trapped_write (Lisp_Object symbol, enum symbol_trapped_write trap) +{ + struct Lisp_Symbol* sym = XSYMBOL (symbol); + if (sym->trapped_write == SYMBOL_NOWRITE) + xsignal1 (Qtrapping_constant, symbol); + else if (sym->redirect == SYMBOL_LOCALIZED + && SYMBOL_BLV (sym)->frame_local) + xsignal1 (Qtrapping_frame_local, symbol); + sym->trapped_write = trap; +} + +static void +restore_symbol_trapped_write (Lisp_Object symbol) +{ + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); +} + +static void +harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) +{ + if (!EQ (base_variable, alias) + && EQ (base_variable, Findirect_variable (alias))) + set_symbol_trapped_write + (alias, XSYMBOL (base_variable)->trapped_write); +} + +DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, + 2, 2, 0, + doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. +All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + set_symbol_trapped_write (symbol, SYMBOL_TRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + + Lisp_Object watchers = Fget (symbol, Qwatchers); + Lisp_Object member = Fmember (watch_function, watchers); + if (NILP (member)) + Fput (symbol, Qwatchers, Fcons (watch_function, watchers)); + return Qnil; +} + +DEFUN ("remove-variable-watcher", Fremove_variable_watcher, Sremove_variable_watcher, + 2, 2, 0, + doc: /* Undo the effect of `add-variable-watcher'. +Remove WATCH-FUNCTION from the list of functions to be called when +SYMBOL (or its aliases) are set. */) + (Lisp_Object symbol, Lisp_Object watch_function) +{ + symbol = Findirect_variable (symbol); + Lisp_Object watchers = Fget (symbol, Qwatchers); + watchers = Fdelete (watch_function, watchers); + if (NILP (watchers)) + { + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + map_obarray (Vobarray, harmonize_variable_watchers, symbol); + } + Fput (symbol, Qwatchers, watchers); + return Qnil; +} + +void +notify_variable_watchers (Lisp_Object symbol, + Lisp_Object newval, + Lisp_Object operation, + Lisp_Object where) +{ + symbol = Findirect_variable (symbol); + + ptrdiff_t count = SPECPDL_INDEX (); + record_unwind_protect (restore_symbol_trapped_write, symbol); + /* Avoid recursion. */ + set_symbol_trapped_write (symbol, SYMBOL_UNTRAPPED_WRITE); + + if (NILP (where) + && !EQ (operation, Qset_default) && !EQ (operation, Qmakunbound) + && !NILP (Flocal_variable_if_set_p (symbol, Fcurrent_buffer ()))) + { + XSETBUFFER (where, current_buffer); + } + + if (EQ (operation, Qset_default)) + operation = Qset; + + for (Lisp_Object watchers = Fget (symbol, Qwatchers); + CONSP (watchers); + watchers = XCDR (watchers)) + { + Lisp_Object watcher = XCAR (watchers); + /* Call subr directly to avoid gc. */ + if (SUBRP (watcher)) + { + Lisp_Object args[] = { symbol, newval, operation, where }; + funcall_subr (XSUBR (watcher), ARRAYELTS (args), args); + } + else + CALLN (Ffuncall, watcher, symbol, newval, operation, where); + } + + unbind_to (count, Qnil); +} + + /* Access or set a buffer-local symbol's default value. */ /* Return the default value of SYMBOL, but don't check for voidness. @@ -1471,16 +1590,27 @@ DEFUN ("set-default", Fset_default, Sset_default, 2, 2, 0, struct Lisp_Symbol *sym; CHECK_SYMBOL (symbol); - if (SYMBOL_CONSTANT_P (symbol)) + sym = XSYMBOL (symbol); + switch (sym->trapped_write) { + case SYMBOL_NOWRITE: if (NILP (Fkeywordp (symbol)) - || !EQ (value, Fdefault_value (symbol))) + || !EQ (value, Fsymbol_value (symbol))) xsignal1 (Qsetting_constant, symbol); else /* Allow setting keywords to their own value. */ return value; + + case SYMBOL_TRAPPED_WRITE: + /* Don't notify here if we're going to call Fset anyway. */ + if (sym->redirect != SYMBOL_PLAINVAL) + notify_variable_watchers (symbol, value, Qset_default, Qnil); + /* FALLTHROUGH! */ + case SYMBOL_UNTRAPPED_WRITE: + break; + + default: emacs_abort (); } - sym = XSYMBOL (symbol); start: switch (sym->redirect) @@ -1651,7 +1781,7 @@ DEFUN ("make-variable-buffer-local", Fmake_variable_buffer_local, default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_CONSTANT_P (variable)) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); if (!blv) @@ -1726,7 +1856,7 @@ DEFUN ("make-local-variable", Fmake_local_variable, Smake_local_variable, default: emacs_abort (); } - if (sym->constant) + if (sym->trapped_write == SYMBOL_NOWRITE) error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable))); @@ -1838,6 +1968,9 @@ DEFUN ("kill-local-variable", Fkill_local_variable, Skill_local_variable, default: emacs_abort (); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (variable, Qnil, Qmakunbound, Fcurrent_buffer ()); + /* Get rid of this buffer's alist element, if any. */ XSETSYMBOL (variable, sym); /* Propagate variable indirection. */ tem = Fassq (variable, BVAR (current_buffer, local_var_alist)); @@ -1920,7 +2053,7 @@ DEFUN ("make-variable-frame-local", Fmake_variable_frame_local, Smake_variable_f default: emacs_abort (); } - if (sym->constant) + if (SYMBOL_TRAPPED_WRITE_P (variable)) error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable))); blv = make_blv (sym, forwarded, valcontents); @@ -3471,6 +3604,8 @@ syms_of_data (void) DEFSYM (Qcyclic_variable_indirection, "cyclic-variable-indirection"); DEFSYM (Qvoid_variable, "void-variable"); DEFSYM (Qsetting_constant, "setting-constant"); + DEFSYM (Qtrapping_constant, "trapping-constant"); + DEFSYM (Qtrapping_frame_local, "trapping-frame-local"); DEFSYM (Qinvalid_read_syntax, "invalid-read-syntax"); DEFSYM (Qinvalid_function, "invalid-function"); @@ -3549,6 +3684,10 @@ syms_of_data (void) PUT_ERROR (Qvoid_variable, error_tail, "Symbol's value as variable is void"); PUT_ERROR (Qsetting_constant, error_tail, "Attempt to set a constant symbol"); + PUT_ERROR (Qtrapping_constant, error_tail, + "Attempt to trap writes to a constant symbol"); + PUT_ERROR (Qtrapping_frame_local, error_tail, + "Attempt to trap writes to a frame local variable"); PUT_ERROR (Qinvalid_read_syntax, error_tail, "Invalid read syntax"); PUT_ERROR (Qinvalid_function, error_tail, "Invalid function"); PUT_ERROR (Qwrong_number_of_arguments, error_tail, @@ -3727,10 +3866,18 @@ syms_of_data (void) DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum, doc: /* The largest value that is representable in a Lisp integer. */); Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-positive-fixnum")); DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum, doc: /* The smallest value that is representable in a Lisp integer. */); Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM); - XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1; + make_symbol_constant (intern_c_string ("most-negative-fixnum")); + + DEFSYM (Qwatchers, "watchers"); + DEFSYM (Qmakunbound, "makunbound"); + DEFSYM (Qunlet, "unlet"); + DEFSYM (Qset, "set"); + DEFSYM (Qset_default, "set-default"); + defsubr (&Sadd_variable_watcher); + defsubr (&Sremove_variable_watcher); } diff --git a/src/eval.c b/src/eval.c index a9bad24..c5c6fa9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -593,12 +593,12 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, CHECK_SYMBOL (new_alias); CHECK_SYMBOL (base_variable); - sym = XSYMBOL (new_alias); - - if (sym->constant) - /* Not sure why, but why not? */ + if (SYMBOL_CONSTANT_P (new_alias)) + /* Making it an alias effectively changes its value. */ error ("Cannot make a constant an alias"); + sym = XSYMBOL (new_alias); + switch (sym->redirect) { case SYMBOL_FORWARDED: @@ -617,8 +617,8 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, so that old-code that affects n_a before the aliasing is setup still works. */ if (NILP (Fboundp (base_variable))) - set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1); - + set_internal (base_variable, find_symbol_value (new_alias), + Qnil, SET_INTERNAL_BIND); { union specbinding *p; @@ -628,11 +628,14 @@ DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0, error ("Don't know how to make a let-bound variable an alias"); } + if (sym->trapped_write == SYMBOL_TRAPPED_WRITE) + notify_variable_watchers (new_alias, base_variable, Qdefvaralias, Qnil); + sym->declared_special = 1; XSYMBOL (base_variable)->declared_special = 1; sym->redirect = SYMBOL_VARALIAS; SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable)); - sym->constant = SYMBOL_CONSTANT_P (base_variable); + sym->trapped_write = XSYMBOL (base_variable)->trapped_write; LOADHIST_ATTACH (new_alias); /* Even if docstring is nil: remove old docstring. */ Fput (new_alias, Qvariable_documentation, docstring); @@ -2644,9 +2647,7 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, Lisp_Object fun, original_fun; Lisp_Object funcar; ptrdiff_t numargs = nargs - 1; - Lisp_Object lisp_numargs; Lisp_Object val; - Lisp_Object *internal_args; ptrdiff_t count; QUIT; @@ -2679,76 +2680,110 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, fun = indirect_function (fun); if (SUBRP (fun)) + val = funcall_subr (XSUBR (fun), numargs, args + 1); + else if (COMPILEDP (fun)) + val = funcall_lambda (fun, numargs, args + 1); + else { - if (numargs < XSUBR (fun)->min_args - || (XSUBR (fun)->max_args >= 0 && XSUBR (fun)->max_args < numargs)) + if (NILP (fun)) + xsignal1 (Qvoid_function, original_fun); + if (!CONSP (fun)) + xsignal1 (Qinvalid_function, original_fun); + funcar = XCAR (fun); + if (!SYMBOLP (funcar)) + xsignal1 (Qinvalid_function, original_fun); + if (EQ (funcar, Qlambda) + || EQ (funcar, Qclosure)) + val = funcall_lambda (fun, numargs, args + 1); + else if (EQ (funcar, Qautoload)) { - XSETFASTINT (lisp_numargs, numargs); - xsignal2 (Qwrong_number_of_arguments, original_fun, lisp_numargs); + Fautoload_do_load (fun, original_fun, Qnil); + check_cons_list (); + goto retry; } - - else if (XSUBR (fun)->max_args == UNEVALLED) + else xsignal1 (Qinvalid_function, original_fun); + } + check_cons_list (); + lisp_eval_depth--; + if (backtrace_debug_on_exit (specpdl + count)) + val = call_debugger (list2 (Qexit, val)); + specpdl_ptr--; + return val; +} - else if (XSUBR (fun)->max_args == MANY) - val = (XSUBR (fun)->function.aMANY) (numargs, args + 1); + +/* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR + and return the result of evaluation. */ + +Lisp_Object +funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *args) +{ + if (numargs < subr->min_args + || (subr->max_args >= 0 && subr->max_args < numargs)) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal2 (Qwrong_number_of_arguments, fun, make_number (numargs)); + } + + else if (subr->max_args == UNEVALLED) + { + Lisp_Object fun; + XSETSUBR (fun, subr); + xsignal1 (Qinvalid_function, fun); + } + + else if (subr->max_args == MANY) + return (subr->function.aMANY) (numargs, args); else { Lisp_Object internal_argbuf[8]; - if (XSUBR (fun)->max_args > numargs) + Lisp_Object *internal_args; + if (subr->max_args > numargs) { - eassert (XSUBR (fun)->max_args <= ARRAYELTS (internal_argbuf)); + eassert (subr->max_args <= ARRAYELTS (internal_argbuf)); internal_args = internal_argbuf; - memcpy (internal_args, args + 1, numargs * word_size); + memcpy (internal_args, args, numargs * word_size); memclear (internal_args + numargs, - (XSUBR (fun)->max_args - numargs) * word_size); + (subr->max_args - numargs) * word_size); } else - internal_args = args + 1; - switch (XSUBR (fun)->max_args) + internal_args = args; + switch (subr->max_args) { case 0: - val = (XSUBR (fun)->function.a0 ()); - break; + return (subr->function.a0 ()); case 1: - val = (XSUBR (fun)->function.a1 (internal_args[0])); - break; + return (subr->function.a1 (internal_args[0])); case 2: - val = (XSUBR (fun)->function.a2 + return (subr->function.a2 (internal_args[0], internal_args[1])); - break; case 3: - val = (XSUBR (fun)->function.a3 + return (subr->function.a3 (internal_args[0], internal_args[1], internal_args[2])); - break; case 4: - val = (XSUBR (fun)->function.a4 + return (subr->function.a4 (internal_args[0], internal_args[1], internal_args[2], internal_args[3])); - break; case 5: - val = (XSUBR (fun)->function.a5 + return (subr->function.a5 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4])); - break; case 6: - val = (XSUBR (fun)->function.a6 + return (subr->function.a6 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5])); - break; case 7: - val = (XSUBR (fun)->function.a7 + return (subr->function.a7 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6])); - break; - case 8: - val = (XSUBR (fun)->function.a8 + return (subr->function.a8 (internal_args[0], internal_args[1], internal_args[2], internal_args[3], internal_args[4], internal_args[5], internal_args[6], internal_args[7])); - break; default: @@ -2759,36 +2794,6 @@ DEFUN ("funcall", Ffuncall, Sfuncall, 1, MANY, 0, } } } - else if (COMPILEDP (fun)) - val = funcall_lambda (fun, numargs, args + 1); - else - { - if (NILP (fun)) - xsignal1 (Qvoid_function, original_fun); - if (!CONSP (fun)) - xsignal1 (Qinvalid_function, original_fun); - funcar = XCAR (fun); - if (!SYMBOLP (funcar)) - xsignal1 (Qinvalid_function, original_fun); - if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) - val = funcall_lambda (fun, numargs, args + 1); - else if (EQ (funcar, Qautoload)) - { - Fautoload_do_load (fun, original_fun, Qnil); - check_cons_list (); - goto retry; - } - else - xsignal1 (Qinvalid_function, original_fun); - } - check_cons_list (); - lisp_eval_depth--; - if (backtrace_debug_on_exit (specpdl + count)) - val = call_debugger (list2 (Qexit, val)); - specpdl_ptr--; - return val; -} static Lisp_Object apply_lambda (Lisp_Object fun, Lisp_Object args, ptrdiff_t count) @@ -3158,10 +3163,10 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.symbol = symbol; specpdl_ptr->let.old_value = SYMBOL_VAL (sym); grow_specpdl (); - if (!sym->constant) + if (!sym->trapped_write) SET_SYMBOL_VAL (sym, value); else - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; case SYMBOL_LOCALIZED: if (SYMBOL_BLV (sym)->frame_local) @@ -3201,7 +3206,7 @@ specbind (Lisp_Object symbol, Lisp_Object value) specpdl_ptr->let.kind = SPECPDL_LET; grow_specpdl (); - set_internal (symbol, value, Qnil, 1); + set_internal (symbol, value, Qnil, SET_INTERNAL_BIND); break; } default: emacs_abort (); @@ -3328,14 +3333,16 @@ unbind_to (ptrdiff_t count, Lisp_Object value) case SPECPDL_BACKTRACE: break; case SPECPDL_LET: - { /* If variable has a trivial value (no forwarding), we can - just set it. No need to check for constant symbols here, - since that was already done by specbind. */ + { /* If variable has a trivial value (no forwarding), and + isn't trapped, we can just set it. */ Lisp_Object sym = specpdl_symbol (specpdl_ptr); if (SYMBOLP (sym) && XSYMBOL (sym)->redirect == SYMBOL_PLAINVAL) { - SET_SYMBOL_VAL (XSYMBOL (sym), - specpdl_old_value (specpdl_ptr)); + if (XSYMBOL (sym)->trapped_write == SYMBOL_UNTRAPPED_WRITE) + SET_SYMBOL_VAL (XSYMBOL (sym), specpdl_old_value (specpdl_ptr)); + else + set_internal (sym, specpdl_old_value (specpdl_ptr), + Qnil, SET_INTERNAL_UNBIND); break; } else @@ -3358,7 +3365,7 @@ unbind_to (ptrdiff_t count, Lisp_Object value) /* If this was a local binding, reset the value in the appropriate buffer, but only if that buffer's binding still exists. */ if (!NILP (Flocal_variable_p (symbol, where))) - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } break; } @@ -3583,7 +3590,7 @@ backtrace_eval_unrewind (int distance) { set_specpdl_old_value (tmp, Fbuffer_local_value (symbol, where)); - set_internal (symbol, old_value, where, 1); + set_internal (symbol, old_value, where, SET_INTERNAL_UNBIND); } } break; @@ -3927,6 +3934,7 @@ syms_of_eval (void) defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); defsubr (&Sdefvaralias); + DEFSYM (Qdefvaralias, "defvaralias"); defsubr (&Sdefconst); defsubr (&Smake_var_non_special); defsubr (&Slet); diff --git a/src/font.c b/src/font.c index ce63233..3b821a4 100644 --- a/src/font.c +++ b/src/font.c @@ -5417,19 +5417,19 @@ syms_of_font (void) [NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...] NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */); Vfont_weight_table = BUILD_STYLE_TABLE (weight_table); - XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-weight-table")); DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table, doc: /* Vector of font slant symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_slant_table = BUILD_STYLE_TABLE (slant_table); - XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-slant-table")); DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table, doc: /* Alist of font width symbols vs the corresponding numeric values. See `font-weight-table' for the format of the vector. */); Vfont_width_table = BUILD_STYLE_TABLE (width_table); - XSYMBOL (intern_c_string ("font-width-table"))->constant = 1; + make_symbol_constant (intern_c_string ("font-width-table")); staticpro (&font_style_table); font_style_table = make_uninit_vector (3); diff --git a/src/lisp.h b/src/lisp.h index 2e46592..701ee9c 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -320,7 +320,8 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) #define lisp_h_NILP(x) EQ (x, Qnil) #define lisp_h_SET_SYMBOL_VAL(sym, v) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v)) -#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant) +#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->trapped_write == SYMBOL_NOWRITE) +#define lisp_h_SYMBOL_TRAPPED_WRITE_P(sym) (XSYMBOL (sym)->trapped_write) #define lisp_h_SYMBOL_VAL(sym) \ (eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value) #define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol) @@ -375,6 +376,7 @@ DEFINE_GDB_SYMBOL_END (USE_LSB_TAG) # define NILP(x) lisp_h_NILP (x) # define SET_SYMBOL_VAL(sym, v) lisp_h_SET_SYMBOL_VAL (sym, v) # define SYMBOL_CONSTANT_P(sym) lisp_h_SYMBOL_CONSTANT_P (sym) +# define SYMBOL_TRAPPED_WRITE_P(sym) lisp_h_SYMBOL_TRAPPED_WRITE_P (sym) # define SYMBOL_VAL(sym) lisp_h_SYMBOL_VAL (sym) # define SYMBOLP(x) lisp_h_SYMBOLP (x) # define VECTORLIKEP(x) lisp_h_VECTORLIKEP (x) @@ -602,6 +604,9 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); /* Defined in data.c. */ extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object); extern _Noreturn void wrong_choice (Lisp_Object, Lisp_Object); +extern void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, + Lisp_Object operation, Lisp_Object where); + /* Defined in emacs.c. */ #ifdef DOUG_LEA_MALLOC @@ -632,6 +637,13 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); SYMBOL_FORWARDED = 3 }; +enum symbol_trapped_write +{ + SYMBOL_UNTRAPPED_WRITE = 0, + SYMBOL_NOWRITE = 1, + SYMBOL_TRAPPED_WRITE = 2 +}; + struct Lisp_Symbol { bool_bf gcmarkbit : 1; @@ -643,10 +655,10 @@ INLINE bool (VECTORLIKEP) (Lisp_Object); 3 : it's a forwarding variable, the value is in `forward'. */ ENUM_BF (symbol_redirect) redirect : 3; - /* Non-zero means symbol is constant, i.e. changing its value - should signal an error. If the value is 3, then the var - can be changed, but only by `defconst'. */ - unsigned constant : 2; + /* 0 : normal case, just set the value + 1 : constant, cannot set, e.g. nil, t, :keywords. + 2 : trap the write, call watcher functions. */ + ENUM_BF (symbol_trapped_write) trapped_write : 2; /* Interned state of the symbol. This is an enumerator from enum symbol_interned. */ @@ -1850,9 +1862,20 @@ SYMBOL_INTERNED_IN_INITIAL_OBARRAY_P (Lisp_Object sym) return XSYMBOL (sym)->interned == SYMBOL_INTERNED_IN_INITIAL_OBARRAY; } -/* Value is non-zero if symbol is considered a constant, i.e. its - value cannot be changed (there is an exception for keyword symbols, - whose value can be set to the keyword symbol itself). */ +/* Value is non-zero if symbol cannot be changed through a simple set, + i.e. it's a constant (e.g. nil, t, :keywords), or it has some + watching functions. */ + +INLINE int +(SYMBOL_TRAPPED_WRITE_P) (Lisp_Object sym) +{ + return lisp_h_SYMBOL_TRAPPED_WRITE_P (sym); +} + +/* Value is non-zero if symbol cannot be changed at all, i.e. it's a + constant (e.g. nil, t, :keywords). Code that actually wants to + write to SYM, should also check whether there are any watching + functions. */ INLINE int (SYMBOL_CONSTANT_P) (Lisp_Object sym) @@ -3289,6 +3312,12 @@ set_symbol_next (Lisp_Object sym, struct Lisp_Symbol *next) XSYMBOL (sym)->next = next; } +INLINE void +make_symbol_constant (Lisp_Object sym) +{ + XSYMBOL (sym)->trapped_write = SYMBOL_NOWRITE; +} + /* Buffer-local (also frame-local) variable access functions. */ INLINE int @@ -3397,7 +3426,13 @@ set_sub_char_table_contents (Lisp_Object table, ptrdiff_t idx, Lisp_Object val) extern _Noreturn void args_out_of_range_3 (Lisp_Object, Lisp_Object, Lisp_Object); extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *); -extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool); +enum Set_Internal_Bind { + SET_INTERNAL_SET, + SET_INTERNAL_BIND, + SET_INTERNAL_UNBIND +}; +extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, + enum Set_Internal_Bind); extern void syms_of_data (void); extern void swap_in_global_binding (struct Lisp_Symbol *); @@ -3880,6 +3915,7 @@ xsignal (Lisp_Object error_symbol, Lisp_Object data) extern _Noreturn void xsignal3 (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object); extern _Noreturn void signal_error (const char *, Lisp_Object); +extern Lisp_Object funcall_subr (struct Lisp_Subr *subr, ptrdiff_t numargs, Lisp_Object *arg_vector); extern Lisp_Object eval_sub (Lisp_Object form); extern Lisp_Object apply1 (Lisp_Object, Lisp_Object); extern Lisp_Object call0 (Lisp_Object); diff --git a/src/lread.c b/src/lread.c index 58d518c..7e74703 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3833,7 +3833,7 @@ intern_sym (Lisp_Object sym, Lisp_Object obarray, Lisp_Object index) if (SREF (SYMBOL_NAME (sym), 0) == ':' && EQ (obarray, initial_obarray)) { - XSYMBOL (sym)->constant = 1; + make_symbol_constant (sym); XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL; SET_SYMBOL_VAL (XSYMBOL (sym), sym); } @@ -4120,12 +4120,12 @@ init_obarray (void) DEFSYM (Qnil, "nil"); SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil); - XSYMBOL (Qnil)->constant = 1; + make_symbol_constant (Qnil); XSYMBOL (Qnil)->declared_special = true; DEFSYM (Qt, "t"); SET_SYMBOL_VAL (XSYMBOL (Qt), Qt); - XSYMBOL (Qt)->constant = 1; + make_symbol_constant (Qt); XSYMBOL (Qt)->declared_special = true; /* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */ -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0002-Show-watchpoints-when-describing-variables.patch Content-Description: patch >From e4df90f33424df9599c84cabdae5d3d9a4f3219a Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 19 Nov 2016 16:50:34 -0500 Subject: [PATCH v7 2/6] Show watchpoints when describing variables * src/data.c (Fget_variable_watchers): New function. * lisp/help-fns.el (describe-variable): Use it to detect watching functions. --- lisp/help-fns.el | 7 +++++++ src/data.c | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 87e7d8f..23dec89 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -918,6 +918,7 @@ describe-variable (indirect-variable variable) (error variable))) (obsolete (get variable 'byte-obsolete-variable)) + (watchpoints (get-variable-watchers variable)) (use (car obsolete)) (safe-var (get variable 'safe-local-variable)) (doc (or (documentation-property @@ -967,6 +968,12 @@ describe-variable (t "."))) (terpri)) + (when watchpoints + (setq extra-line t) + (princ " Calls these functions when changed: ") + (princ watchpoints) + (terpri)) + (when (member (cons variable val) (with-current-buffer buffer file-local-variables-alist)) diff --git a/src/data.c b/src/data.c index 8954b42..911789f 100644 --- a/src/data.c +++ b/src/data.c @@ -1463,6 +1463,16 @@ SYMBOL (or its aliases) are set. */) return Qnil; } +DEFUN ("get-variable-watchers", Fget_variable_watchers, Sget_variable_watchers, + 1, 1, 0, + doc: /* Return a list of SYMBOL's active watchers. */) + (Lisp_Object symbol) +{ + return (SYMBOL_TRAPPED_WRITE_P (symbol) == SYMBOL_TRAPPED_WRITE) + ? Fget (Findirect_variable (symbol), Qwatchers) + : Qnil; +} + void notify_variable_watchers (Lisp_Object symbol, Lisp_Object newval, @@ -3880,4 +3890,5 @@ syms_of_data (void) DEFSYM (Qset_default, "set-default"); defsubr (&Sadd_variable_watcher); defsubr (&Sremove_variable_watcher); + defsubr (&Sget_variable_watchers); } -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0003-Add-function-to-trigger-debugger-on-variable-writ.patch Content-Description: patch >From 1372db86c55c6d8a7cc4023b408f542ee681f442 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 16:03:06 -0500 Subject: [PATCH v7 3/6] Add function to trigger debugger on variable write * lisp/emacs-lisp/debug.el (debug-on-variable-change): (debug--variable-list): (cancel-debug-on-variable-change): New functions. (debugger-setup-buffer): Add watchpoint clause. --- lisp/emacs-lisp/debug.el | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el index 7d27380..5430b72 100644 --- a/lisp/emacs-lisp/debug.el +++ b/lisp/emacs-lisp/debug.el @@ -306,6 +306,24 @@ debugger-setup-buffer (delete-char 1) (insert ? ) (beginning-of-line)) + ;; Watchpoint triggered. + ((and `watchpoint (let `(,symbol ,newval . ,details) (cdr args))) + (insert + "--" + (pcase details + (`(makunbound nil) (format "making %s void" symbol)) + (`(makunbound ,buffer) (format "killing local value of %s in buffer %s" + symbol buffer)) + (`(defvaralias ,_) (format "aliasing %s to %s" symbol newval)) + (`(let ,_) (format "let-binding %s to %S" symbol newval)) + (`(unlet ,_) (format "ending let-binding of %s" symbol)) + (`(set nil) (format "setting %s to %S" symbol newval)) + (`(set ,buffer) (format "setting %s in buffer %s to %S" + symbol buffer newval)) + (_ (error "unrecognized watchpoint triggered %S" (cdr args)))) + ": ") + (setq pos (point)) + (insert ?\n)) ;; Debugger entered for an error. (`error (insert "--Lisp error: ") @@ -850,6 +868,79 @@ debugger-list-functions (princ "Note: if you have redefined a function, then it may no longer\n") (princ "be set to debug on entry, even if it is in the list.")))))) +(defun debug--implement-debug-watch (symbol newval op where) + "Conditionally call the debugger. +This function is called when SYMBOL's value is modified." + (if (or inhibit-debug-on-entry debugger-jumping-flag) + nil + (let ((inhibit-debug-on-entry t)) + (funcall debugger 'watchpoint symbol newval op where)))) + +;;;###autoload +(defun debug-on-variable-change (variable) + "Trigger a debugger invocation when VARIABLE is changed. + +When called interactively, prompt for VARIABLE in the minibuffer. + +This works by calling `add-variable-watch' on VARIABLE. If you +quit from the debugger, this will abort the change (unless the +change is caused by the termination of a let-binding). + +The watchpoint may be circumvented by C code that changes the +variable directly (i.e., not via `set'). Changing the value of +the variable (e.g., `setcar' on a list variable) will not trigger +watchpoint. + +Use \\[cancel-debug-on-variable-change] to cancel the effect of +this command. Uninterning VARIABLE or making it an alias of +another symbol also cancels it." + (interactive + (let* ((var-at-point (variable-at-point)) + (var (and (symbolp var-at-point) var-at-point)) + (val (completing-read + (concat "Debug when setting variable" + (if var (format " (default %s): " var) ": ")) + obarray #'boundp + t nil nil (and var (symbol-name var))))) + (list (if (equal val "") var (intern val))))) + (add-variable-watcher variable #'debug--implement-debug-watch)) + +;;;###autoload +(defalias 'debug-watch #'debug-on-variable-change) + + +(defun debug--variable-list () + "List of variables currently set for debug on set." + (let ((vars '())) + (mapatoms + (lambda (s) + (when (memq #'debug--implement-debug-watch + (get s 'watchers)) + (push s vars)))) + vars)) + +;;;###autoload +(defun cancel-debug-on-variable-change (&optional variable) + "Undo effect of \\[debug-on-variable-change] on VARIABLE. +If VARIABLE is nil, cancel debug-on-variable-change for all variables. +When called interactively, prompt for VARIABLE in the minibuffer. +To specify a nil argument interactively, exit with an empty minibuffer." + (interactive + (list (let ((name + (completing-read + "Cancel debug on set for variable (default all variables): " + (mapcar #'symbol-name (debug--variable-list)) nil t))) + (when name + (unless (string= name "") + (intern name)))))) + (if variable + (remove-variable-watcher variable #'debug--implement-debug-watch) + (message "Canceling debug-watch for all variables") + (mapc #'cancel-debug-watch (debug--variable-list)))) + +;;;###autoload +(defalias 'cancel-debug-watch #'cancel-debug-on-variable-change) + (provide 'debug) ;;; debug.el ends here -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0004-Ensure-redisplay-using-variable-watcher.patch Content-Description: patch >From d7e1393e5d68fa4c6e279a8e7bc584be9bc506d3 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 21 Nov 2015 17:02:42 -0500 Subject: [PATCH v7 4/6] Ensure redisplay using variable watcher This replaces looking up the variable name in redisplay--variables when setting it. * lisp/frame.el: Replace redisplay--variables with add-variable-watcher calls. * src/xdisp.c (Fset_buffer_redisplay): Rename from maybe_set_redisplay, set the redisplay flag unconditionally. (Vredisplay__variables): Remove it. * src/data.c (set_internal): Remove maybe_set_redisplay call. --- lisp/frame.el | 3 +-- src/data.c | 2 -- src/window.h | 1 - src/xdisp.c | 17 +++++++---------- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lisp/frame.el b/lisp/frame.el index a584567..1dffc6c 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2249,9 +2249,8 @@ 'automatic-hscrolling 'window-system-version "it does not give useful information." "24.3") ;; Variables which should trigger redisplay of the current buffer. -(setq redisplay--variables (make-hash-table :test 'eq :size 10)) (mapc (lambda (var) - (puthash var 1 redisplay--variables)) + (add-variable-watcher var (symbol-function 'set-buffer-redisplay))) '(line-spacing overline-margin line-prefix diff --git a/src/data.c b/src/data.c index 911789f..ff35315 100644 --- a/src/data.c +++ b/src/data.c @@ -1275,8 +1275,6 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where, default: emacs_abort (); } - maybe_set_redisplay (symbol); - start: switch (sym->redirect) { diff --git a/src/window.h b/src/window.h index a124b33..4a102f2 100644 --- a/src/window.h +++ b/src/window.h @@ -1063,7 +1063,6 @@ void set_window_buffer (Lisp_Object window, Lisp_Object buffer, extern void fset_redisplay (struct frame *f); extern void bset_redisplay (struct buffer *b); extern void bset_update_mode_line (struct buffer *b); -extern void maybe_set_redisplay (Lisp_Object); /* Call this to tell redisplay to look for other windows than selected-window that need to be redisplayed. Calling one of the *set_redisplay functions above already does it, so it's only needed in unusual cases. */ diff --git a/src/xdisp.c b/src/xdisp.c index 6e8af8a..9d36ab6 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -622,15 +622,15 @@ bset_update_mode_line (struct buffer *b) b->text->redisplay = true; } -void -maybe_set_redisplay (Lisp_Object symbol) -{ - if (HASH_TABLE_P (Vredisplay__variables) - && hash_lookup (XHASH_TABLE (Vredisplay__variables), symbol, NULL) >= 0) +DEFUN ("set-buffer-redisplay", Fset_buffer_redisplay, + Sset_buffer_redisplay, 4, 4, 0, + doc: /* Mark the current buffer for redisplay. +This function may be passed to `add-variable-watcher'. */) + (Lisp_Object symbol, Lisp_Object newval, Lisp_Object op, Lisp_Object where) { bset_update_mode_line (current_buffer); current_buffer->prevent_redisplay_optimizations_p = true; - } + return Qnil; } #ifdef GLYPH_DEBUG @@ -31319,6 +31319,7 @@ syms_of_xdisp (void) message_dolog_marker3 = Fmake_marker (); staticpro (&message_dolog_marker3); + defsubr (&Sset_buffer_redisplay); #ifdef GLYPH_DEBUG defsubr (&Sdump_frame_glyph_matrix); defsubr (&Sdump_glyph_matrix); @@ -31988,10 +31989,6 @@ or t (meaning all windows). */); doc: /* */); Vredisplay__mode_lines_cause = Fmake_hash_table (0, NULL); - DEFVAR_LISP ("redisplay--variables", Vredisplay__variables, - doc: /* A hash-table of variables changing which triggers a thorough redisplay. */); - Vredisplay__variables = Qnil; - DEFVAR_BOOL ("redisplay--inhibit-bidi", redisplay__inhibit_bidi, doc: /* Non-nil means it is not safe to attempt bidi reordering for display. */); /* Initialize to t, since we need to disable reordering until -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0005-Add-tests-for-watchpoints.patch Content-Description: patch >From d3f07419258505df7d335d1c80e20bf2758e57cd Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 12 Dec 2015 23:10:15 -0500 Subject: [PATCH v7 5/6] Add tests for watchpoints * test/src/data-tests.el (data-tests-variable-watchers): (data-tests-local-variable-watchers): New tests. --- test/src/data-tests.el | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/src/data-tests.el b/test/src/data-tests.el index 0a29233..4c2ea54 100644 --- a/test/src/data-tests.el +++ b/test/src/data-tests.el @@ -255,3 +255,118 @@ test-bool-vector-binop (v2 (test-bool-vector-bv-from-hex-string "0000C")) (v3 (bool-vector-not v1))) (should (equal v2 v3)))) + +(ert-deftest data-tests-variable-watchers () + (defvar data-tests-var 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 1) + (should-have-watch-data '(data-tests-var 1 set nil)) + (let ((data-tests-var 2)) + (should-have-watch-data '(data-tests-var 2 let nil)) + (setq data-tests-var 3) + (should-have-watch-data '(data-tests-var 3 set nil))) + (should-have-watch-data '(data-tests-var 1 unlet nil)) + ;; `setq-default' on non-local variable is same as `setq'. + (setq-default data-tests-var 4) + (should-have-watch-data '(data-tests-var 4 set nil)) + (makunbound 'data-tests-var) + (should-have-watch-data '(data-tests-var nil makunbound nil)) + (setq data-tests-var 5) + (should-have-watch-data '(data-tests-var 5 set nil)) + (remove-variable-watcher 'data-tests-var collect-watch-data) + (setq data-tests-var 6) + (should (null watch-data))))) + +(ert-deftest data-tests-varalias-watchers () + (defvar data-tests-var0 0) + (defvar data-tests-var1 0) + (defvar data-tests-var2 0) + (defvar data-tests-var3 0) + (let* ((watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + ;; Watch var0, then alias it. + (add-variable-watcher 'data-tests-var0 collect-watch-data) + (defvaralias 'data-tests-var0-alias 'data-tests-var0) + (setq data-tests-var0 1) + (should-have-watch-data '(data-tests-var0 1 set nil)) + (setq data-tests-var0-alias 2) + (should-have-watch-data '(data-tests-var0 2 set nil)) + ;; Alias var1, then watch var1-alias. + (defvaralias 'data-tests-var1-alias 'data-tests-var1) + (add-variable-watcher 'data-tests-var1-alias collect-watch-data) + (setq data-tests-var1 1) + (should-have-watch-data '(data-tests-var1 1 set nil)) + (setq data-tests-var1-alias 2) + (should-have-watch-data '(data-tests-var1 2 set nil)) + ;; Alias var2, then watch it. + (defvaralias 'data-tests-var2-alias 'data-tests-var2) + (add-variable-watcher 'data-tests-var2 collect-watch-data) + (setq data-tests-var2 1) + (should-have-watch-data '(data-tests-var2 1 set nil)) + (setq data-tests-var2-alias 2) + (should-have-watch-data '(data-tests-var2 2 set nil)) + ;; Watch var3-alias, then make it alias var3 (this removes the + ;; watcher flag). + (defvar data-tests-var3-alias 0) + (add-variable-watcher 'data-tests-var3-alias collect-watch-data) + (defvaralias 'data-tests-var3-alias 'data-tests-var3) + (should-have-watch-data '(data-tests-var3-alias + data-tests-var3 defvaralias nil)) + (setq data-tests-var3 1) + (setq data-tests-var3-alias 2) + (should (null watch-data))))) + +(ert-deftest data-tests-local-variable-watchers () + (defvar-local data-tests-lvar 0) + (let* ((buf1 (current-buffer)) + (buf2 nil) + (watch-data nil) + (collect-watch-data + (lambda (&rest args) (push args watch-data)))) + (cl-flet ((should-have-watch-data (data) + (should (equal (pop watch-data) data)) + (should (null watch-data)))) + (add-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf1)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf1)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf1))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf1)) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (with-temp-buffer + (setq buf2 (current-buffer)) + (setq data-tests-lvar 1) + (should-have-watch-data `(data-tests-lvar 1 set ,buf2)) + (let ((data-tests-lvar 2)) + (should-have-watch-data `(data-tests-lvar 2 let ,buf2)) + (setq data-tests-lvar 3) + (should-have-watch-data `(data-tests-lvar 3 set ,buf2))) + (should-have-watch-data `(data-tests-lvar 1 unlet ,buf2)) + (kill-local-variable 'data-tests-lvar) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2)) + (setq data-tests-lvar 3.5) + (should-have-watch-data `(data-tests-lvar 3.5 set ,buf2)) + (kill-all-local-variables) + (should-have-watch-data `(data-tests-lvar nil makunbound ,buf2))) + (setq-default data-tests-lvar 4) + (should-have-watch-data `(data-tests-lvar 4 set nil)) + (makunbound 'data-tests-lvar) + (should-have-watch-data '(data-tests-lvar nil makunbound nil)) + (setq data-tests-lvar 5) + (should-have-watch-data `(data-tests-lvar 5 set ,buf1)) + (remove-variable-watcher 'data-tests-lvar collect-watch-data) + (setq data-tests-lvar 6) + (should (null watch-data))))) -- 2.9.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v7-0006-Document-watchpoints.patch Content-Description: patch >From d6e8c7aaa5eecf76e2ecfcf2fcd3484dc7b7fd8a Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 13 Dec 2015 14:47:58 -0500 Subject: [PATCH v7 6/6] Document watchpoints * doc/lispref/debugging.texi (Variable Debugging): * doc/lispref/variables.texi (Watching Variables): New section. * etc/NEWS: Add entry for watchpoints --- doc/lispref/debugging.texi | 31 +++++++++++++++++++++++ doc/lispref/elisp.texi | 2 ++ doc/lispref/variables.texi | 61 ++++++++++++++++++++++++++++++++++++++++++++++ etc/NEWS | 5 ++++ src/data.c | 9 +++++++ 5 files changed, 108 insertions(+) diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 6c0908a..c80b0f9 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi @@ -69,6 +69,7 @@ Debugger * Error Debugging:: Entering the debugger when an error happens. * Infinite Loops:: Stopping and debugging a program that doesn't exit. * Function Debugging:: Entering it when a certain function is called. +* Variable Debugging:: Entering it when a variable is modified. * Explicit Debug:: Entering it at a certain point in the program. * Using Debugger:: What the debugger does; what you see while in it. * Debugger Commands:: Commands used while in the debugger. @@ -290,6 +291,36 @@ Function Debugging not currently set up to break on entry. @end deffn +@node Variable Debugging +@subsection Entering the debugger when a variable is modified +@cindex variable write debugging +@cindex debugging changes to variables + +Sometimes a problem with a function is due to a wrong setting of a +variable. Setting up the debugger to trigger whenever the variable is +changed is a quick way to find the origin of the setting. + +@deffn Command debug-on-variable-change variable +This function arranges for the debugger to be called whenever +@var{variable} is modified. + +It is implemented using the watchpoint mechanism, so it inherits the +same characteristics and limitations: all aliases of @var{variable} +will be watched together, only dynamic variables can be watched, and +changes to the objects referenced by variables are not detected. For +details, see @ref{Watching Variables}. +@end deffn + +@deffn Command cancel-debug-on-variable-change &optional variable +This function undoes the effect of @code{debug-on-variable-change} on +@var{variable}. When called interactively, it prompts for +@var{variable} in the minibuffer. If @var{variable} is omitted or +@code{nil}, it cancels break-on-change for all variables. Calling +@code{cancel-debug-on-variable-change} does nothing to a variable +which is not currently set up to break on change. +@end deffn + + @node Explicit Debug @subsection Explicit Entry to the Debugger @cindex debugger, explicit entry diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index 4a1e528..c308c79 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -498,6 +498,7 @@ Top * Accessing Variables:: Examining values of variables whose names are known only at run time. * Setting Variables:: Storing new values in variables. +* Watching Variables:: Running a function when a variable is changed. * Variable Scoping:: How Lisp chooses among local and global values. * Buffer-Local Variables:: Variable values in effect only in one buffer. * File Local Variables:: Handling local variable lists in files. @@ -641,6 +642,7 @@ Top * Error Debugging:: Entering the debugger when an error happens. * Infinite Loops:: Stopping and debugging a program that doesn't exit. * Function Debugging:: Entering it when a certain function is called. +* Variable Debugging:: Entering it when a variable is modified. * Explicit Debug:: Entering it at a certain point in the program. * Using Debugger:: What the debugger does; what you see while in it. * Debugger Commands:: Commands used while in the debugger. diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 418a416..393d253 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -34,6 +34,7 @@ Variables * Accessing Variables:: Examining values of variables whose names are known only at run time. * Setting Variables:: Storing new values in variables. +* Watching Variables:: Running a function when a variable is changed. * Variable Scoping:: How Lisp chooses among local and global values. * Buffer-Local Variables:: Variable values in effect only in one buffer. * File Local Variables:: Handling local variable lists in files. @@ -765,6 +766,66 @@ Setting Variables @end example @end defun +@node Watching Variables +@section Running a function when a variable is changed. +@cindex variable watchpoints +@cindex watchpoints for Lisp variables + +It is sometimes useful to take some action when a variable changes its +value. The watchpoint facility provides the means to do so. Some +possible uses for this feature include keeping display in sync with +variable settings, and invoking the debugger to track down unexpected +changes to variables (@pxref{Variable Debugging}). + +The following functions may be used to manipulate and query the watch +functions for a variable. + +@defun add-variable-watcher symbol watch-function +This function arranges for @var{watch-function} to be called whenever +@var{symbol} is modified. Modifications through aliases +(@pxref{Variable Aliases}) will have the same effect. + +@var{watch-function} will be called with 4 arguments: (@var{symbol} +@var{newval} @var{operation} @var{where}). + +@var{symbol} is the variable being changed. +@var{newval} is the value it will be changed to. +@var{operation} is a symbol representing the kind of change, one of: +`set', `let', `unlet', `makunbound', and `defvaralias'. +@var{where} is a buffer if the buffer-local value of the variable is +being changed, nil otherwise. +@end defun + +@defun remove-variable-watch symbol watch-function +This function removes @var{watch-function} from @var{symbol}'s list of +watchers. +@end defun + +@defun get-variable-watchers symbol +This function returns the list of @var{symbol}'s active watcher +functions. +@end defun + +@subsection Limitations + +There are a couple of ways in which a variable could be modifed (or at +least appear to be modified) without triggering a watchpoint. + +Since watchpoints are attached to symbols, modification to the +objects contained within variables (e.g., by a list modification +function @pxref{Modifying Lists}) is not caught by this mechanism. + +Additionally, C code can modify the value of variables directly, +bypassing the watchpoint mechanism. + +A minor limitation of this feature, again because it targets symbols, +is that only variables of dynamic scope may be watched. This poses +little difficulty, since modifications to lexical variables can be +discovered easily by inspecting the code within the scope of the +variable (unlike dynamic variables, which can be modified by any code +at all, @pxref{Variable Scoping}). + + @node Variable Scoping @section Scoping Rules for Variable Bindings @cindex scoping rule diff --git a/etc/NEWS b/etc/NEWS index e29dfe2..fcbbb44 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -627,6 +627,11 @@ consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. +++ +** New function `add-variable-watcher' can be used to call a function +when a symbol's value is changed. This is used to implement the new +debugger command `debug-on-variable-change'. + ++++ ** Time conversion functions that accept a time zone rule argument now allow it to be OFFSET or a list (OFFSET ABBR), where the integer OFFSET is a count of seconds east of Universal Time, and the string diff --git a/src/data.c b/src/data.c index ff35315..ef6b48b 100644 --- a/src/data.c +++ b/src/data.c @@ -1428,6 +1428,15 @@ harmonize_variable_watchers (Lisp_Object alias, Lisp_Object base_variable) DEFUN ("add-variable-watcher", Fadd_variable_watcher, Sadd_variable_watcher, 2, 2, 0, doc: /* Cause WATCH-FUNCTION to be called when SYMBOL is set. + +It will be called with 4 arguments: (SYMBOL NEWVAL OPERATION WHERE). +SYMBOL is the variable being changed. +NEWVAL is the value it will be changed to. +OPERATION is a symbol representing the kind of change, one of: `set', +`let', `unlet', `makunbound', and `defvaralias'. +WHERE is a buffer if the buffer-local value of the variable being +changed, nil otherwise. + All writes to aliases of SYMBOL will call WATCH-FUNCTION too. */) (Lisp_Object symbol, Lisp_Object watch_function) { -- 2.9.3 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Mon Nov 21 12:31:05 2016 Received: (at 24923) by debbugs.gnu.org; 21 Nov 2016 17:31:05 +0000 Received: from localhost ([127.0.0.1]:37314 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8sQr-00013q-EJ for submit@debbugs.gnu.org; Mon, 21 Nov 2016 12:31:05 -0500 Received: from eggs.gnu.org ([208.118.235.92]:41132) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c8sQq-00013M-3P for 24923@debbugs.gnu.org; Mon, 21 Nov 2016 12:31:04 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c8sQk-0007ys-2P for 24923@debbugs.gnu.org; Mon, 21 Nov 2016 12:30:58 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.2 required=5.0 tests=BAYES_50,RP_MATCHES_RCVD autolearn=disabled version=3.3.2 Received: from fencepost.gnu.org ([2001:4830:134:3::e]:49120) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c8sQe-0007wI-Er; Mon, 21 Nov 2016 12:30:52 -0500 Received: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3183 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1c8sQd-0004cR-Ot; Mon, 21 Nov 2016 12:30:52 -0500 Date: Mon, 21 Nov 2016 19:31:03 +0200 Message-Id: <83a8cs91uw.fsf@gnu.org> From: Eli Zaretskii To: npostavs@users.sourceforge.net In-reply-to: <87k2bxkitq.fsf@users.sourceforge.net> (npostavs@users.sourceforge.net) Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-Spam-Score: -8.0 (--------) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Eli Zaretskii Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -8.0 (--------) > From: npostavs@users.sourceforge.net > Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net > Date: Sun, 20 Nov 2016 15:16:49 -0500 > > Eli Zaretskii writes: > >> > >> @xref seems to be generating lowercase "see" for me, perhaps because I'm > >> using makeinfo 4.13? > > > > Unlikely. Are you looking at the file in Info, or as plain text? The > > former has its own ideas about how to display cross-references; I > > meant what is actually in the file. > > I was looking at the file in Info-mode. Looking in fundamental-mode, it > seems that @xref generates an uppercase "*Note", where @ref generates > "*note". Yes. @xref produces "See" in the printed output (PDF etc.). > Anyway, here is the final(?) patchset: LGTM, thanks. From debbugs-submit-bounces@debbugs.gnu.org Fri Dec 02 20:46:48 2016 Received: (at 24923) by debbugs.gnu.org; 3 Dec 2016 01:46:48 +0000 Received: from localhost ([127.0.0.1]:50773 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cCzPc-000661-Bn for submit@debbugs.gnu.org; Fri, 02 Dec 2016 20:46:48 -0500 Received: from mail-io0-f194.google.com ([209.85.223.194]:33949) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cCzPZ-00065j-OU; Fri, 02 Dec 2016 20:46:46 -0500 Received: by mail-io0-f194.google.com with SMTP id r94so11293458ioe.1; Fri, 02 Dec 2016 17:46:45 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=1rKCfT+V6Chtvz7OCTyX5LWrVXsBKFdsEOopJaY4CJM=; b=GL9oQZfmfKti1Fjiaey9cZdDUtX8dHHb9lLr+Rqn87cVYuALJrcqkC1BHAHzwCl3T7 Gv1oZWleS5LIIzN0C3eXovxVAHwrtL3qte+Sq5BbVV19Rv4sGca4Y+NPrrq0BWX6ubU2 3tCK9yQ1I7KlqEE1j3zDswZHDW7yXe7ipqTTQvlxNZqZItM/PjSiwhJVbppzmlS6cdwl DwDiaUYJ1/jDT/KYzrchIqhU90VJmyLkYLLKCyPJiDVqe0qQlIxC5uRdqCr1df7brzOt eC27ifBDinVmJMhEpineZv88utaeJoHcL0HLG361vpU2uAQ+/avrv772hjeQFrzZhv3U v8Sw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version; bh=1rKCfT+V6Chtvz7OCTyX5LWrVXsBKFdsEOopJaY4CJM=; b=DNwUzG89CS/jOEs40DmuoOVAIljLB6Lw2ROqlBaW+ZaOf/A0AJpyMRzWL/m7cZGb3P TA0QuyeKXL2ROepsyN4l1CwmeIDK4U5EFeODHV8zEwk22B5Y+qM3U5Plq2YEnT/n9Y/E huqDws9m8iYrZv08y/s50fBcrIspKs2gA6NrY1G/EL5+mEsm0LUr7J/hATEvELlHLja5 Jt+As/rSyZYuIbcLakxTYePt6s2eGpKzKh8XTkHwPFGvPM5hDfnOzOrOm7SkGfaptnFk TjYXEGq9yLTQehqSEJzlwkTUb2WrbSEHuqoeRuPnms72jqMRe7y6yCdrQAfc+dhVM97W yHVQ== X-Gm-Message-State: AKaTC01Aij2zEGFbRpbc7n/zKdj25VS1uLEDKknYDjxHdbhrVsYlS3xyplwMKFJfdUhGXg== X-Received: by 10.36.156.132 with SMTP id b126mr70564ite.91.1480729600155; Fri, 02 Dec 2016 17:46:40 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id x190sm2080375ite.14.2016.12.02.17.46.38 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 02 Dec 2016 17:46:39 -0800 (PST) From: npostavs@users.sourceforge.net To: Eli Zaretskii Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> Date: Fri, 02 Dec 2016 20:47:37 -0500 In-Reply-To: <83a8cs91uw.fsf@gnu.org> (Eli Zaretskii's message of "Mon, 21 Nov 2016 19:31:03 +0200") Message-ID: <878trxhjgm.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, stephen.berman@gmx.net X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) tags 24923 fixed close 24923 quit Eli Zaretskii writes: > >> Anyway, here is the final(?) patchset: > > LGTM, thanks. Pushed as 88fefc3 (56c8178, e7cd98b, d3faef9, cfd2b9e, 459a234, 2272131). From debbugs-submit-bounces@debbugs.gnu.org Fri Dec 02 22:49:45 2016 Received: (at submit) by debbugs.gnu.org; 3 Dec 2016 03:49:45 +0000 Received: from localhost ([127.0.0.1]:50806 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD1Kb-0002E0-6B for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:49:45 -0500 Received: from eggs.gnu.org ([208.118.235.92]:34374) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD1KZ-0002Do-NB for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:49:44 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cD1KT-0003vH-K5 for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:49:38 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=0.8 required=5.0 tests=BAYES_50,FREEMAIL_FROM autolearn=disabled version=3.3.2 Received: from lists.gnu.org ([2001:4830:134:3::11]:39329) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cD1KT-0003vB-Hw for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:49:37 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:53575) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cD1KS-0003xR-GX for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:49:37 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cD1KN-0003ub-KY for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:49:36 -0500 Received: from mout.kundenserver.de ([217.72.192.75]:57956) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cD1KN-0003uM-B3 for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:49:31 -0500 Received: from [18.189.106.208] ([18.189.106.208]) by mrelayeu.kundenserver.de (mreue103 [212.227.15.184]) with ESMTPSA (Nemesis) id 0LqnUA-1cqDxy2ejS-00eI1W for ; Sat, 03 Dec 2016 04:49:29 +0100 Subject: Re: bug#24923: 25.1; Lisp watchpoints To: bug-gnu-emacs@gnu.org References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> From: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= Message-ID: Date: Fri, 2 Dec 2016 22:49:21 -0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 In-Reply-To: <878trxhjgm.fsf@users.sourceforge.net> Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="t7sxRJv56frq906fVVOOgdTOouwklTj9g" X-Provags-ID: V03:K0:9kp6y60E8agap0gubSHilpWaW9/UlGb+ibd3rtBU2Rqlxg86oTG VP8jKBkCkJkMNjQAsqtp/Gfs3HbZL3j0ElfzS/mUK2ISnzIpTa3xzS2DSIZbmurKYmIGKgf XUNtqSg5zZ6eAXeMyI3QVROzWI26KIaLgiWyMF4J5Pwt2PxKAh6N1wPMNjB0AH7Dwa30PhO v4vTwqDm7tKr+/vC5EqbQ== X-UI-Out-Filterresults: notjunk:1;V01:K0:xQSTS/FuxpY=:PCz4a9gJP4cBfqc4h4Czh8 kxNPXcl0ht8dSii1izlcGTe3r46TNKKdzQvDijl42GIyAu1h0rZgzZpU8lRnnbO6XmRyvIezf wqH6+xJv5eySSTl/OyNB0TDmrn1fChOtcLRVlT7xQj708JoH+oOSs7iXUDFn5BSeqwD1R+plc eCUKcRKvZSm/1cf+BxrXj7ztfPnVzl3v9LMlHNAOwEE63b9sG/v+z+0BL8g/lXxnhhngHgrH3 VqXrRr901xpU4m2ABShXAdB5DJzvbe1CsXoXAo9aV57TrcST9AhT74G1uiYLpgrUOGkAc2+wi KaDSLGZWkQNQICq4uAMXJ1qEy9SafKZxc/JBnKyqT9KwGcAQH2y6GRYe7N/26xHk8qzoDPNqX ZRUeSfQHii4QhboZcwYbOd10w/LK+0IM8nEqCQBD5nDIIah4XkxJltFrP0re7WlvfeSpjwUsp xyZhdJnOSekSw5QNFEd8wUpdGK6vhJwRwUVngnCjAT4uDqiY4VLQHKbnroz6gcar7wPyczARp +9m9fixxOdhe24/99MC/JhXlGrm6axIxyzFsbCxVRwQFCDCHvaOL9scLZD9TTS3zW4IwYZdcb cm90iN+9Sapmv0Tcjz0A44a8SyL6/aB3MjKyEOgslJQK2LYE6DYi7wKud9fTzuolQdTd4WBhg YFdP9XZ0PTttbojj3sTl6Kuy3TiRz9HWNpoqWIhpt8ac8QVajd55Bkx+/WI52nVJCM/M= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-Received-From: 2001:4830:134:3::11 X-Spam-Score: -4.0 (----) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -4.0 (----) This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --t7sxRJv56frq906fVVOOgdTOouwklTj9g Content-Type: multipart/mixed; boundary="JQPRiOLIPpRBd2QVkAWxkmhA7wBJJi2G9"; protected-headers="v1" From: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= To: bug-gnu-emacs@gnu.org Message-ID: Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> In-Reply-To: <878trxhjgm.fsf@users.sourceforge.net> --JQPRiOLIPpRBd2QVkAWxkmhA7wBJJi2G9 Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: quoted-printable On 2016-12-02 20:47, npostavs@users.sourceforge.net wrote: > tags 24923 fixed > close 24923=20 > quit >=20 > Eli Zaretskii writes: >> >>> Anyway, here is the final(?) patchset: >> >> LGTM, thanks. >=20 > Pushed as 88fefc3 (56c8178, e7cd98b, d3faef9, cfd2b9e, 459a234, 2272131= ). This is incredibly cool! Thanks so much for working on this :) --JQPRiOLIPpRBd2QVkAWxkmhA7wBJJi2G9-- --t7sxRJv56frq906fVVOOgdTOouwklTj9g Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAEBCAAGBQJYQkDBAAoJEPqg+cTm90wjrt8P/Ahzwrvo7DDjxs9woHKn6Drr Zp0zUylNsv1hOzLMwN5zKrCazmyNcrirGTOp8qIzW905J/0CQLTMTYKcFjbxMpnI 3soFYaDUrnII0yCyBWPAGtR8amSquv1jv+qQpOkibE6KwFWv7Wzr7iad4MfFu3h4 6hhfxA3wdPYRavgSBR5Zndjc5iQFGfaPMgDOffRWP09VXmcobeqZQQUigWsi+ao+ HX2zXkIAjSJrpE4/R4J1I2TE30XMMxmkRDj73TkQuvAHWKqe05CgItxkz5Pbge8m 2zA1b5ciy+Mby3E4S8QjXtvaLXwuYM3GChRFoppdA0f/3apq5+QlAIJ1bRkcTgTJ 86GI31oBr1SA8ZVRdgxw8QyDVMbyBeGjRkXmMqRt5x1Asy0CcuQFCa425BhhDfr3 9nztw9Qz313dtOneGUEvDel1u7uhJYcgxZO7eXDoN9nnKx6zCFGQLw5bcbye4CjL ze5w83g1fjZ7BtzAQg3qBHZhTC2ObU146kj03rDPhMPCG67A6N6/IStEaTNGuAui kyzi5Rhk2TUNqjybhufArC8D4VBUGQMt0C1EZhjhhsomLLdILjEaXW5n6+BUq6Qm SOJ2KY5ZdWne6aqqmFeNdW6jRAFjS+dGlHK2RJ4m9MvVTDJIjFldxRPXDgxeuLv/ GAzaiq4v28SihxAH8C3E =SxqS -----END PGP SIGNATURE----- --t7sxRJv56frq906fVVOOgdTOouwklTj9g-- From debbugs-submit-bounces@debbugs.gnu.org Fri Dec 02 22:50:56 2016 Received: (at submit) by debbugs.gnu.org; 3 Dec 2016 03:50:56 +0000 Received: from localhost ([127.0.0.1]:50810 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD1Lk-0002Fv-Fb for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:50:56 -0500 Received: from eggs.gnu.org ([208.118.235.92]:34645) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD1Lj-0002Fi-G7 for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:50:55 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cD1Ld-0004TF-Kf for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:50:50 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: * X-Spam-Status: No, score=1.3 required=5.0 tests=BAYES_50,FREEMAIL_FROM, RCVD_IN_SORBS_SPAM autolearn=disabled version=3.3.2 Received: from lists.gnu.org ([2001:4830:134:3::11]:48719) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cD1Ld-0004TA-HG for submit@debbugs.gnu.org; Fri, 02 Dec 2016 22:50:49 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:53848) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cD1Lc-0004B2-JU for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:50:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cD1LX-0004SL-OE for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:50:48 -0500 Received: from mout.kundenserver.de ([217.72.192.73]:57402) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cD1LX-0004SG-ES for bug-gnu-emacs@gnu.org; Fri, 02 Dec 2016 22:50:43 -0500 Received: from [18.189.106.208] ([18.189.106.208]) by mrelayeu.kundenserver.de (mreue103 [212.227.15.184]) with ESMTPSA (Nemesis) id 0Lm6Wf-1cmd963ZbN-00Zdej for ; Sat, 03 Dec 2016 04:50:42 +0100 Subject: Re: bug#24923: 25.1; Lisp watchpoints To: bug-gnu-emacs@gnu.org References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> From: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= Message-ID: <8df1d08a-531c-c2a5-b4f3-32523068ce03@gmail.com> Date: Fri, 2 Dec 2016 22:50:40 -0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 In-Reply-To: <878trxhjgm.fsf@users.sourceforge.net> Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="cj37phd4rnshh0SuUW3bhUgNMtRtAQwkp" X-Provags-ID: V03:K0:qHEw7v/Sw2ZW31x4nw/B6ZnVpU+UG3v/6fRSAxB7sU4EM/hLcTS DNXnR81RpQmScBdr5rK01Xjzyw1kjKnNe3XFPE2eVT1QtnuR5eDzdwKsfQWV0g97iZ+dcct fTcN40sn1C4JgCkcQjRL2ZF39yB2h1uRqq63CMZa/S+r9SlhtAxvcNSFesCdE8IqHvUhKrP 5+FKTfs04ZKsWlyaY7nkg== X-UI-Out-Filterresults: notjunk:1;V01:K0:I0pqX6q2tuU=:Yc1wYj2bnyyAQ5M56XVQYS CguMCPcgmUvZgsVfdZzJhUU7qriikWTfuEz2WiB2H8H1B7Gu6Bjf6g3/+m3YwICShNEZlThm/ +kbRtvKUdykanh9+PQABcHMQdcjPBnOKQi3d3xA995RBvnNbCz1l9jHWoDvcSEnWYFYvb15wG 04z4XOdf/IDqYGm6bv5OYpJpIcX2ZsV5YtGvGSEPXN5bPDXBINqgk6A2ZmdebxQyziNcOuJQB 5ZG09wSycQEzpeGHbkjRIH4C+PyWBrDK3I8WH4IKs6KplVbPscTSeoXB/NmzzVd66WtriJ9e7 F4qsovVhPykhTFnniXdTgJs13XgzILjgDDqpPcCPjuiKSczBwbn8PptDw4kgRejGg2chenYjx pP1mQ2JW6YtXOyagAgAezQ8KwVn+lk90M57jRC4s/fPlwYEFHGDiT7L2iKxiz6qBvECX+8RyF WqHr6aBwZjr+syiPu4Frdl2YC+hs9s6A5LooTx9ASRSaEWeeD4r4J/8bGQSYUOVr6DD2yU3K2 +KrMiy6xJLd9JmZs7UVdXu0ileQQlnVHo++2vWCu8nFTn99WI69txUCHomGnUEsG5i9Smn6e/ QC66BTrpn82VH+EpyWPoCksq06+H0bjES0o6HB6nkYn2xLX1d121g+Yje8IFk/dYOvx1s3AN1 j5+TQQUZ8e8fEG+cjHnM+fFC1+UHFM8W2QEgjlf1bkOgSOg/X8r6bkPUkSFy0lLmBH3E= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-Received-From: 2001:4830:134:3::11 X-Spam-Score: -3.5 (---) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.5 (---) This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --cj37phd4rnshh0SuUW3bhUgNMtRtAQwkp Content-Type: multipart/mixed; boundary="62uSmNiILMfIIsr2v3f61ETDIGrjMDhUx"; protected-headers="v1" From: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= To: bug-gnu-emacs@gnu.org Message-ID: <8df1d08a-531c-c2a5-b4f3-32523068ce03@gmail.com> Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> In-Reply-To: <878trxhjgm.fsf@users.sourceforge.net> --62uSmNiILMfIIsr2v3f61ETDIGrjMDhUx Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: quoted-printable On 2016-12-02 20:47, npostavs@users.sourceforge.net wrote: > Pushed as 88fefc3 (56c8178, e7cd98b, d3faef9, cfd2b9e, 459a234, 2272131= ) I wonder: would it make sense to extend defcustom to use this, too? Would= n't it be great if I could tag my defcustom variables in some way, and th= en setq would automatically invoke the corresponding defcustom setter? --62uSmNiILMfIIsr2v3f61ETDIGrjMDhUx-- --cj37phd4rnshh0SuUW3bhUgNMtRtAQwkp Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAEBCAAGBQJYQkEQAAoJEPqg+cTm90wjExwP/jPUaj9uVZls+1iVq7TChoGY 28404uOSpbodKjuex8sWGYMf5zH1pCmXG5NsA55ajTAlJMRtMEDW1AyuN2PkawqQ nlNG2rMbV1oVrrf5AvOZ8gb6CnMdb9tptvrqjw7kEaKA2toVTAegPUr3byahy9Qu 0rRDRn+O80rWx6lqHKKFGxvzRZ1bM2BXVDgBq/whVY7PKxBWZX6ElHGQA4DKPsfo uBTGPqpZKtChP74KGLESz3AYWnL4LPzTMnTgIzgvFFSVUKSADHpYWcVJKcr9Azxo 6EEKBqtHiX4mNAs3HsIkiIrIBOGXkSC1h9T+mVvziInHDigG3rN6DHuvyiRMmb5/ i2XM8jL4NDFcDIjtM2n/olagxPumOBZc8hBlAoLD3FrimbhcAletp5xNhGitPdR+ 4JQUcXO4CeFbUYuqnl1+UMOVqNxYK5tLtd1c+w71QxETmwvjTFNu+XXIfM1z2TK9 s8Q4zzUYxXTovk8TQY2A07ts32lS/se6lC4uFraHLsLrzQO4m997lUCoHwf3GSCR kOzPBhMadtGmOH0ifycgFfgYk00N/ZD/qjuCEwyXaQNMKFDZZAg8GcpivLTdJ8SO HWDmSf+RVVd5TAu2pMvY8ieP6PDmJBXyfB+X9tnJ4JMr5t2AmOnEutRy9xboG/n8 jDx34Y2qF11NfepzMLY0 =eWwD -----END PGP SIGNATURE----- --cj37phd4rnshh0SuUW3bhUgNMtRtAQwkp-- From debbugs-submit-bounces@debbugs.gnu.org Sat Dec 03 00:01:17 2016 Received: (at 24923) by debbugs.gnu.org; 3 Dec 2016 05:01:17 +0000 Received: from localhost ([127.0.0.1]:50832 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD2Rp-0003sw-CJ for submit@debbugs.gnu.org; Sat, 03 Dec 2016 00:01:17 -0500 Received: from dancol.org ([96.126.100.184]:35892) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cD2Rn-0003so-AV for 24923@debbugs.gnu.org; Sat, 03 Dec 2016 00:01:15 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=dancol.org; s=x; h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-ID:In-Reply-To:Date:References:Subject:Cc:To:From; bh=eLQaZv78bdEGtNvrg9e09zQeT+nm5L3MVOVmcGv4sck=; b=GKVQVZkXHTbARcItKkyi5i76B+EFO5BW8V2mKrsOSbmnlokzwB+Mdrfq4tJDLpOjhLFjvYJKnooKy7W7YXVxfd1EzrxHs+rYVx30VWHWz9jT08pCeWxILknSWYNZGIpMSKMOsete4JpZKmzvRVL+S9QF9/kUQRi0p4mlt9Ztixd77DU/T/xbzRze0JG+apnAyxmgjcBNhQwc6On+BARPproZtLMoeps1fBe/p/7PxmsglOqpIeQXsnNooJvZpC0Jw2Syl/FHQNPvOs3jZ4a0STHsYNN5IAeu4VaMS0ZLp3wZxbl7KufbOkZP+ufwbgJPJGtUpZVpigq6Ksd4fzV+SA==; Received: from [2601:602:9802:d9d1:bc8e:4940:ab86:ab74] (helo=dancol-glaptop0) by dancol.org with esmtpsa (TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2) (envelope-from ) id 1cD2Rl-0007A4-Sq; Fri, 02 Dec 2016 21:01:13 -0800 From: Daniel Colascione To: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> <8df1d08a-531c-c2a5-b4f3-32523068ce03@gmail.com> Date: Fri, 02 Dec 2016 21:01:06 -0800 In-Reply-To: <8df1d08a-531c-c2a5-b4f3-32523068ce03@gmail.com> (=?utf-8?Q?=22Cl=C3=A9ment?= Pit--Claudel"'s message of "Fri, 2 Dec 2016 22:50:40 -0500") Message-ID: User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.9 (--) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.9 (--) On Fri, Dec 02 2016, Cl=C3=A9ment Pit--Claudel wrote: > On 2016-12-02 20:47, npostavs@users.sourceforge.net wrote: >> Pushed as 88fefc3 (56c8178, e7cd98b, d3faef9, cfd2b9e, 459a234, 2272131) > > I wonder: would it make sense to extend defcustom to use this, too? > Wouldn't it be great if I could tag my defcustom variables in some > way, and then setq would automatically invoke the corresponding > defcustom setter? setf maybe. setq should not be magical. From debbugs-submit-bounces@debbugs.gnu.org Sat Dec 03 09:10:21 2016 Received: (at 24923) by debbugs.gnu.org; 3 Dec 2016 14:10:21 +0000 Received: from localhost ([127.0.0.1]:50943 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cDB1B-0001UA-A4 for submit@debbugs.gnu.org; Sat, 03 Dec 2016 09:10:21 -0500 Received: from mail-io0-f180.google.com ([209.85.223.180]:35081) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cDB18-0001Tw-G7 for 24923@debbugs.gnu.org; Sat, 03 Dec 2016 09:10:20 -0500 Received: by mail-io0-f180.google.com with SMTP id a124so528524855ioe.2 for <24923@debbugs.gnu.org>; Sat, 03 Dec 2016 06:10:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version:content-transfer-encoding; bh=EHG0d8IZOHwl0uIBj01mXENI2a0qGplev0l3D+aB334=; b=qw6uECkTvwIB2I6tFF3ySdK5xCN0axHZ+51iQqgQUED/RUmByFdy93fTRrRPaE4OAc 5SWjVHhwCsnHAEwmBsXeDrraWXayqlqSeXQsqAMpy7rfYmDwWR421JACF72mGVghOnJA NrAaNHWd5Q/L1UK6qCtMZ9XDqA8+PNFzqUAhm0yGQh9SJCrJb8BACVbJEMsRJNpIwGZu 2smf8oAQARfQh8FPxfa9y7qA2corqPUG212BEJaP5E9Xc9+y2ZdcifcHk38qUF9LW15C mWSCHdBpyXkP2QOVfXNlJpoZiW+SsqcvtUAu8J5iyTDJk6DbhknADKFis2W+Qx4cHwN+ BfHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:references:date :in-reply-to:message-id:user-agent:mime-version :content-transfer-encoding; bh=EHG0d8IZOHwl0uIBj01mXENI2a0qGplev0l3D+aB334=; b=RbpiB1v7Fjoovb14c8UrkPGTUFcII2twpuLVR6BldVJRgUWHKPv/JBwf+Dft7JCTdu yOZ4HQ6UZRhG8DcrzkVQAx9V/slYAJnwIfCUFG4scSNXShocrf/yjECn0Ul72EqiB07l q/IXwiOK+qn7Vfn6ObYJRMNkAOba7TysBR6tLsoyCJQ8ZiYxhapNsdXnmYe1aQ0Uauup nZR0bvkit3AckJ3H1qjc43hn1Y6XMKC7gGyPpue0s5xbs4Urfh4za/NhXWAg+UVOkVVF JxlliFMNZiewgtUlD5/cm+BjVb1B66l3biXitIsR/IyO0p7vg7NbGupc7FsmFvWgSaXL wokw== X-Gm-Message-State: AKaTC010vA6UCkhv7Ii+G1Xkf+tXR+uS2rrA1ft/IUUal6D12ZW1cF6nxvtUv3KiIQizXw== X-Received: by 10.36.122.136 with SMTP id a130mr1744134itc.69.1480774212752; Sat, 03 Dec 2016 06:10:12 -0800 (PST) Received: from zony ([45.2.7.65]) by smtp.googlemail.com with ESMTPSA id n206sm3166097itg.1.2016.12.03.06.10.11 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sat, 03 Dec 2016 06:10:11 -0800 (PST) From: npostavs@users.sourceforge.net To: Daniel Colascione Subject: Re: bug#24923: 25.1; Lisp watchpoints References: <87vavun235.fsf@users.sourceforge.net> <83eg2ie3lp.fsf@gnu.org> <87pom1mi3q.fsf@users.sourceforge.net> <83r36hcghy.fsf@gnu.org> <87fumwmc7q.fsf@users.sourceforge.net> <83fumvcs99.fsf@gnu.org> <874m32lx1e.fsf@users.sourceforge.net> <87k2bya0j6.fsf@gmx.net> <87wpfyjl1x.fsf@users.sourceforge.net> <83lgwe9lmo.fsf@gnu.org> <87mvguj6lk.fsf@users.sourceforge.net> <83wpfy7xkn.fsf@gnu.org> <87k2bxkitq.fsf@users.sourceforge.net> <83a8cs91uw.fsf@gnu.org> <878trxhjgm.fsf@users.sourceforge.net> <8df1d08a-531c-c2a5-b4f3-32523068ce03@gmail.com> Date: Sat, 03 Dec 2016 09:11:10 -0500 In-Reply-To: (Daniel Colascione's message of "Fri, 02 Dec 2016 21:01:06 -0800") Message-ID: <871sxpgl1d.fsf@users.sourceforge.net> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.5 (/) X-Debbugs-Envelope-To: 24923 Cc: 24923@debbugs.gnu.org, =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.5 (/) Daniel Colascione writes: > On Fri, Dec 02 2016, Cl=C3=A9ment Pit--Claudel wrote: >> On 2016-12-02 20:47, npostavs@users.sourceforge.net wrote: >>> Pushed as 88fefc3 (56c8178, e7cd98b, d3faef9, cfd2b9e, 459a234, 2272131) >> >> I wonder: would it make sense to extend defcustom to use this, too? >> Wouldn't it be great if I could tag my defcustom variables in some >> way, and then setq would automatically invoke the corresponding >> defcustom setter? > > setf maybe. setq should not be magical. There were indeed some concerns about that kind usage when this feature was first proposed, e.g.: - http://lists.gnu.org/archive/html/emacs-devel/2015-01/msg01064.html I like the idea of such hooks (which I've always thought of as "watchers" rather than hooks), actually, but only for purposes such as debugging. If we want to use such hooks for purposes such as "automatically recompute values of dependent vars", then I think the right way is to introduce a new layer which checks&runs these hooks, using the "raw" `setq' underneath. From unknown Wed Jun 18 22:56:33 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Sun, 01 Jan 2017 12:24:10 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator