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))))