Function: elisp-scope-analyze-form
elisp-scope-analyze-form is an autoloaded and byte-compiled function
defined in elisp-scope.el.gz.
Signature
(elisp-scope-analyze-form CALLBACK &optional STREAM)
Documentation
Read and analyze code from STREAM, reporting findings via CALLBACK.
Call CALLBACK for each analyzed symbol SYM with arguments ROLE, POS,
SYM, ID and DEF, where ROLE is a symbol that specifies the semantics of
SYM; POS is the position of SYM in STREAM; ID is an object that uniquely
identifies the local reference of SYM in the current defun, so different
occurrences of SYM get the same ID (up to equal) if and only if they
refer to the same object; and lastly, DEF is the position in which SYM
is locally defined, or nil. For the occurrence of SYM at the position
where it is locally defined (a.k.a. "bound"), the values of POS and
DEF are equal. If SYM is not lexically bound, then DEF is nil and so
is ID.
CALLBACK should use ID by checking if it is nil or equal to other ID
values produced in the same call to this function. The specific value
of a given ID is otherwise meaningless.
As an example, when this function analyzes the following form
(lambda (mode) (let ((mode (or mode major-mode))) (symbol-name mode)))
the CALLBACK function is invoked four times with SYM mode:
- Once for the mode in the lambda arguments list, with ROLE
binding-variable, some non-nil ID value MODE-ID1, and with POS and
DEF both being the same position POS1 where this mode occurs.
- Another time for the binder in the let form, with ROLE
binding-variable some non-nil ID value MODE-ID2 that is not equal
to MODE-ID1, and with POS and DEF both being the same position POS2.
- Another for the first argument of or, with ROLE bound-variable and
ID of MODE-ID1, since this occurrence of mode is bound by the
lambda argument mode. Similarly, DEF is POS1, and POS is now a
different position, POS3.
- Finally, CALLBACK is also invoked for the mode that appears in the
body of let as the argument of symbol-name, with ROLE set to
bound-variable, ID set to MODE-ID2, and DEF set to POS3.
In the above example, CALLBACK is also invoked for lambda, let,
or, major-mode and symbol-name. Since those symbols do not have
local references (they refer to global functions/macros/variables),
CALLBACK gets nil ID and nil DEF.
Note that if SYM is locally-bound, but has no specific binding position, then DEF is nil while ID is non-nil. This is the case when SYM is bound by a binder that is only introduced during macro expansion and does not appear literally in the analyzed code.
If STREAM is nil, it defaults to the current buffer. When reading from the current buffer, this function leaves point at the end of the form.
This function recursively analyzes Lisp forms (HEAD . TAIL), usually starting with a top-level form, by inspecting HEAD at each level:
- If HEAD is a symbol with a non-nil elisp-scope-analyzer symbol
property, then the value of that property specifies a bespoke analyzer
function, AF, that is called as (AF HEAD . TAIL) to analyze the form.
See more details about writing analyzer functions below.
- If HEAD satisfies functionp, which means it is a function in the
running Emacs session, analyze the form as a function call.
- If HEAD is a safe macro (see elisp-scope-safe-macro-p), expand it
and analyze the resulting form.
- If HEAD is unknown, then the arguments in TAIL are ignored, unless
elisp-scope-assume-func is non-nil, in which case they are analyzed
as evaluated forms (i.e. HEAD is assumed to be a function).
An analyzer (function specified via the elisp-scope-analyzer property)
can use the functions elisp-scope-report-s, elisp-scope-1 and
elisp-scope-n to analyze its arguments, and it can consult the
variable elisp-scope-output-spec to obtain the expected output spec of
the analyzed form. For example, the following is a suitable analyzer
for the identity function:
(lambda (fsym arg)
(elisp-scope-report-s fsym 'function)
(elisp-scope-1 arg elisp-scope-output-spec))
Source Code
;; Defined in /usr/src/emacs/lisp/emacs-lisp/elisp-scope.el.gz
;;;###autoload
(defun elisp-scope-analyze-form (callback &optional stream)
"Read and analyze code from STREAM, reporting findings via CALLBACK.
Call CALLBACK for each analyzed symbol SYM with arguments ROLE, POS,
SYM, ID and DEF, where ROLE is a symbol that specifies the semantics of
SYM; POS is the position of SYM in STREAM; ID is an object that uniquely
identifies the local reference of SYM in the current defun, so different
occurrences of SYM get the same ID (up to `equal') if and only if they
refer to the same object; and lastly, DEF is the position in which SYM
is locally defined, or nil. For the occurrence of SYM at the position
where it is locally defined (a.k.a. \"bound\"), the values of POS and
DEF are equal. If SYM is not lexically bound, then DEF is nil and so
is ID.
CALLBACK should use ID by checking if it is nil or `equal' to other ID
values produced in the same call to this function. The specific value
of a given ID is otherwise meaningless.
As an example, when this function analyzes the following form
(lambda (mode) (let ((mode (or mode major-mode))) (symbol-name mode)))
the CALLBACK function is invoked four times with SYM `mode':
- Once for the `mode' in the `lambda' arguments list, with ROLE
`binding-variable', some non-nil ID value MODE-ID1, and with POS and
DEF both being the same position POS1 where this `mode' occurs.
- Another time for the binder in the let form, with ROLE
`binding-variable' some non-nil ID value MODE-ID2 that is not `equal'
to MODE-ID1, and with POS and DEF both being the same position POS2.
- Another for the first argument of `or', with ROLE `bound-variable' and
ID of MODE-ID1, since this occurrence of `mode' is bound by the
`lambda' argument `mode'. Similarly, DEF is POS1, and POS is now a
different position, POS3.
- Finally, CALLBACK is also invoked for the `mode' that appears in the
body of `let' as the argument of `symbol-name', with ROLE set to
`bound-variable', ID set to MODE-ID2, and DEF set to POS3.
In the above example, CALLBACK is also invoked for `lambda', `let',
`or', `major-mode' and `symbol-name'. Since those symbols do not have
local references (they refer to global functions/macros/variables),
CALLBACK gets nil ID and nil DEF.
Note that if SYM is locally-bound, but has no specific binding position,
then DEF is nil while ID is non-nil. This is the case when SYM is bound
by a binder that is only introduced during macro expansion and does not
appear literally in the analyzed code.
If STREAM is nil, it defaults to the current buffer. When reading from
the current buffer, this function leaves point at the end of the form.
This function recursively analyzes Lisp forms (HEAD . TAIL), usually
starting with a top-level form, by inspecting HEAD at each level:
- If HEAD is a symbol with a non-nil `elisp-scope-analyzer' symbol
property, then the value of that property specifies a bespoke analyzer
function, AF, that is called as (AF HEAD . TAIL) to analyze the form.
See more details about writing analyzer functions below.
- If HEAD satisfies `functionp', which means it is a function in the
running Emacs session, analyze the form as a function call.
- If HEAD is a safe macro (see `elisp-scope-safe-macro-p'), expand it
and analyze the resulting form.
- If HEAD is unknown, then the arguments in TAIL are ignored, unless
`elisp-scope-assume-func' is non-nil, in which case they are analyzed
as evaluated forms (i.e. HEAD is assumed to be a function).
An analyzer (function specified via the `elisp-scope-analyzer' property)
can use the functions `elisp-scope-report-s', `elisp-scope-1' and
`elisp-scope-n' to analyze its arguments, and it can consult the
variable `elisp-scope-output-spec' to obtain the expected output spec of
the analyzed form. For example, the following is a suitable analyzer
for the `identity' function:
(lambda (fsym arg)
(elisp-scope-report-s fsym \\='function)
(elisp-scope-1 arg elisp-scope-output-spec))"
(let* ((stream (or stream (current-buffer)))
(form (read-positioning-symbols stream))
(elisp-scope--counter 0)
(elisp-scope--callback callback)
(max-lisp-eval-depth 32768))
(if (eq stream (current-buffer))
;; `save-excursion' so CALLBACK can change point freely.
(save-excursion (elisp-scope-1 form))
(elisp-scope-1 form))))