Skip to content

Commit

Permalink
Skipped input results can have children
Browse files Browse the repository at this point in the history
  • Loading branch information
scymtym committed May 30, 2024
1 parent 5c934b7 commit aa7a016
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 137 deletions.
50 changes: 50 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
Release 0.11 (not yet released)

* Major incompatible change

A children parameter has been added to the lambda list of the generic
function ECLECTOR.PARSE-RESULT:MAKE-SKIPPED-INPUT-RESULT so that results
which represent skipped material can have children. For example, before this
change, a ECLECTOR.PARSE-RESULT:READ call which encountered the expression
#+no-such-feature foo bar potentially constructed parse results for all
(recursive) READ calls, that is for the whole expression, for
no-such-feature, for foo and for bar, but the parse results for
no-such-feature and foo could not be attached to a parent parse result and
were thus lost. In other words the shape of the parse result tree was

skipped input result #+no-such-feature foo
expression result bar

With this change, the parse results in question can be attached to the parse
result which represents the whole #+no-such-feature foo expression so that
the entire parse result tree has the following shape

skipped input result #+no-such-feature foo
skipped input result no-such-feature
skipped input result foo
expression result bar

Since this is a major incompatible change, we offer the following workaround
for clients that must support Eclector versions with and without this change:

(eval-when (:compile-toplevel :load-toplevel :execute)
(let* ((generic-function #'eclector.parse-result:make-skipped-input-result)
(lambda-list (c2mop:generic-function-lambda-list
generic-function)))
(when (= (length lambda-list) 5)
(pushnew 'skipped-input-children *features*))))
(defmethod eclector.parse-result:make-skipped-input-result
((client client)
(stream t)
(reason t)
#+PACKAGE-THIS-CODE-IS-READ-IN::skipped-input-children (children t)
(source t))
...
#+PACKAGE-THIS-CODE-IS-READ-IN::skipped-input-children (use children)
...)

The above code pushes a symbol that is interned in a package under the
control of the respective client (as opposed to the KEYWORD package) onto
*FEATURES* before the second form is read and uses that feature to select
either the version with or the version without the children parameter of the
method definition. See Maintaining Portable Lisp Programs by Christophe
Rhodes for a detailed discussion of this technique.

Release 0.10 (2024-02-28)

* The deprecated generic functions ECLECTOR.PARSE-RESULT:SOURCE-POSITION and
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ input (due to e.g. comments and reader conditionals):
(list :result result :source source :children children))
(defmethod eclector.parse-result:make-skipped-input-result
((client my-client) (stream t) (reason t) (source t))
(list :reason reason :source source))
((client my-client) (stream t) (reason t) (children t) (source t))
(list :reason reason :source source :children children))
(with-input-from-string (stream "(1 #|comment|# \"string\")")
(eclector.parse-result:read (make-instance 'my-client) stream))
Expand Down
6 changes: 3 additions & 3 deletions code/parse-result/generic-functions.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
(defgeneric make-expression-result (client result children source)
(:argument-precedence-order result client children source))

(defgeneric make-skipped-input-result (client stream reason source)
(:method (client stream reason source)
(declare (ignore client stream reason source))
(defgeneric make-skipped-input-result (client stream reason children source)
(:method (client stream reason children source)
(declare (ignore client stream reason children source))
nil))

;;; The purpose of the following structure classes is to
Expand Down
3 changes: 2 additions & 1 deletion code/parse-result/read.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
(let* ((start *start*)
(end (eclector.base:source-position client input-stream))
(range (eclector.base:make-source-range client start end))
(children (reverse (first *stack*)))
(parse-result (make-skipped-input-result
client input-stream reason range)))
client input-stream reason children range)))
(when parse-result
(push parse-result (second *stack*)))))

Expand Down
60 changes: 59 additions & 1 deletion data/changes.sexp
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
(:changes
(:release "0.11" nil)
(:release "0.11" nil
(:item
(:paragraph
"Major" "incompatible" "change")
(:paragraph
"A" (:tt "children") "parameter" "has" "been" "added" "to" "the" "lambda"
"list" "of" "the" "generic" "function"
(:symbol "eclector.parse-result:make-skipped-input-result") "so" "that"
"results" "which" "represent" "skipped" "material" "can" "have" "children"
"." "For" "example" "," "before" "this" "change" "," "a"
(:symbol "eclector.parse-result:read") "call" "which" "encountered" "the"
"expression" (:tt "#+no-such-feature foo bar") "potentially" "constructed"
"parse" "results" "for" "all" "(" "recursive" ")" (:symbol "read") "calls"
"," "that" "is" "for" "the" "whole" "expression" "," "for"
(:tt "no-such-feature") "," "for" (:tt "foo") "and" "for" (:tt "bar") ","
"but" "the" "parse" "results" "for" (:tt "no-such-feature") "and"
(:tt "foo") "could" "not" "be" "attached" "to" "a" "parent" "parse"
"result" "and" "were" "thus" "lost" "." "In" "other" "words" "the" "shape"
"of" "the" "parse" "result" "tree" "was")
(:code nil "skipped input result #+no-such-feature foo
expression result bar")
(:paragraph
"With" "this" "change" "," "the" "parse" "results" "in" "question" "can"
"be" "attached" "to" "the" "parse" "result" "which" "represents" "the"
"whole" (:tt "#+no-such-feature foo") "expression" "so" "that" "the"
"entire" "parse" "result" "tree" "has" "the" "following" "shape")
(:code nil "skipped input result #+no-such-feature foo
skipped input result no-such-feature
skipped input result foo
expression result bar")
(:paragraph
"Since" "this" "is" "a" "major" "incompatible" "change" "," "we" "offer"
"the" "following" "workaround" "for" "clients" "that" "must" "support"
"Eclector" "versions" "with" "and" "without" "this" "change" ":")
(:code nil "(eval-when (:compile-toplevel :load-toplevel :execute)
(let* ((generic-function #'eclector.parse-result:make-skipped-input-result)
(lambda-list (c2mop:generic-function-lambda-list
generic-function)))
(when (= (length lambda-list) 5)
(pushnew 'skipped-input-children *features*))))
(defmethod eclector.parse-result:make-skipped-input-result
((client client)
(stream t)
(reason t)
#+PACKAGE-THIS-CODE-IS-READ-IN::skipped-input-children (children t)
(source t))
...
#+PACKAGE-THIS-CODE-IS-READ-IN::skipped-input-children (use children)
...)")
(:paragraph
"The" "above" "code" "pushes" "a" "symbol" "that" "is" "interned" "in" "a"
"package" "under" "the" "control" "of" "the" "respective" "client" "(" "as"
"opposed" "to" "the" (:tt "KEYWORD") "package" ")" "onto"
(:symbol "*features*") "before" "the" "second" "form" "is" "read" "and"
"uses" "that" "feature" "to" "select" "either" "the" "version" "with" "or"
"the" "version" "without" "the" (:tt "children") "parameter" "of" "the"
"method" "definition" "." "See" "Maintaining" "Portable" "Lisp" "Programs"
"by" "Christophe" "Rhodes" "for" "a" "detailed" "discussion" "of" "this"
"technique" ".")))

(:release "0.10" "2024-02-28"
(:item
Expand Down
17 changes: 10 additions & 7 deletions documentation/chap-external-protocols.texi
Original file line number Diff line number Diff line change
Expand Up @@ -1532,15 +1532,18 @@ Thus, a client must define a method on this generic function.
@end deffn

@defgena {make-skipped-input-result,eclector.parse-result} client@
stream reason source
stream reason children source

This generic function is called after the reader skipped over a range
of characters in @var{stream}. It returns either @t{nil} if
the skipped input should not be represented or a client-specific
representation of the skipped input. The value of the @var{source}
parameter designates the skipped range using a source range
representation obtained via @t{make-source-range} and
@t{source-position}.
of characters in @var{stream}. It returns either @t{nil} if the
skipped input should not be represented or a client-specific
representation of the skipped input. The value of the @var{children}
parameter is a list of already constructed parse result objects which
represent object read by recursive @t{read} calls (Such as the feature
expression and the ignored expression in @t{#+(and (or) some-feature)
skipped-expression}). The value of the @var{source} parameter
designates the skipped range using a source range representation
obtained via @t{make-source-range} and @t{source-position}.

Reasons for skipping input include comments, the @t{#+} and
@t{#-} reader macros and @t{*read-suppress*}. The
Expand Down
Binary file modified documentation/read-call-sequence-parse-result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit aa7a016

Please sign in to comment.