Fix pprint dispatch and circularity-aware pretty printing#745
Open
blakemcbride wants to merge 1 commit into
Open
Fix pprint dispatch and circularity-aware pretty printing#745blakemcbride wants to merge 1 commit into
blakemcbride wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix pprint dispatch and circularity-aware pretty printing
Summary
Repairs four ANSI pretty-printer failures rooted in two separate
issues: the generic pretty-printer entry points (
pprint-fill,pprint-linear,pprint-tabular) routed non-list atoms through apath that bypassed
print-object, andpprint-logical-blockdidnot cooperate with
*print-circle*when it was entered recursivelyfrom a dispatched pretty printer. Four previously-failing ANSI
conformance tests now pass, and no previously-passing test
regresses.
PPRINT-FILL.2PPRINT-LINEAR.2PPRINT-TABULAR.2PPRINT-LOGICAL-BLOCK.17pprint-logical-block+ circleRoot causes
A. Functions printed via
%write-to-stringon xp streamsWhen
pprint-fill/pprint-linear/pprint-tabularreceived anon-list atom, they ultimately called
output-ugly-objectwith anxp-structure stream. In that function, the
xp::xp-structure-p streambranch matched before thefunctionp objectbranch, sofunctions were serialized via
sys::%write-to-string— which goesthrough
Function.writeToString/Function.princToStringandproduces only the inner
#<FOO {id}>form.write-to-stringofthe same function, used as the oracle by
PPRINT-{FILL,LINEAR,TABULAR}.2, takes the CLOSprint-objectpath and produces the wrapped
#<FUNCTION #<FOO {id}> {id}>form.The two disagreed on every compiled/closure value in
*mini-universe*.B. Nested
pprint-logical-blockignored circularitymaybe-initiate-xp-printing— the common entry for every xp-levelpretty printing operation, including
pprint-logical-block— had ashort-circuit for xp-structure streams that simply called the body
thunk without consulting
*circularity-hash-table*. So the nestedpprint-logical-blockin test.17:emitted
"((8) (8))"instead of the required"(#1=(8) #1#)".The outer
%check-objectpopulated the circularity tablecorrectly, but the inner
pprint-logical-blocknever asked.C. Naively adding the circularity check regressed five tests
A straight-forward fix — calling
check-for-circularity+handle-circularityat the top ofmaybe-initiate-xp-printingon xp streams — broke five tests thatpreviously passed:
PPRINT-FILL.14,PPRINT-LINEAR.14,PPRINT-TABULAR.13PPRINT-POP.7,PPRINT-POP.8They produced
"(#1=#1# #1#)"instead of"(#1=(A) #1#)". Thecause: when the dispatched pretty printer for a shared cons is
invoked, the outer
output-objecthas already runcheck-for-circularitywithassign=tand already emitted#1=. The hash-table entry is now a positive integer. A secondcheck-for-circularitycall for the same object during pretty-printingreturned the negation (
-n), andhandle-circularitydutifullyemitted
#1#instead of descending into the cons body.So the fix has to distinguish nested user-initiated
pprint-logical-block(should detect circularity, no outer owner)from dispatcher-initiated pretty printing (circularity already
handled by the outer
%check-object, should skip the check).Changes
1.
print.lisp: route functions throughprint-objectbefore the xp-stream shortcutThe
(functionp object)branch inoutput-ugly-objectwas movedabove the
(xp::xp-structure-p stream)branch, so functions alwaystake the CLOS
print-objectpath regardless of stream type:This brings
pprint-fill/pprint-linear/pprint-tabularoutput for a function value into agreement with
write-to-string(which the tests compare against).
2.
print.lisp: introduce*circularity-handled-object*A new dynamic variable marks the object whose circularity has
just been resolved by
%check-object:%check-objectbinds it around both%print-objectcall sites,so any pretty printer dispatched for
objectsees it as the"already handled" sentinel until control returns.
3.
pprint.lisp: circularity-awaremaybe-initiate-xp-printingThe xp-structure branch now consults the circularity hash table,
but skips the work when the outer
%check-objectalready did it:Key points:
object is uniquely identified by its print form (numbers,
characters, interned symbols), no check is performed.
*circularity-handled-object*, theouter
%check-objectalready emitted#n=or#n#asappropriate. Clear the sentinel before recursing so any
deeper nested call behaves correctly, then call the body.
pprint-logical-blockcase — run the full circularityprotocol.
Files changed
src/org/armedbear/lisp/print.lispsrc/org/armedbear/lisp/pprint.lispTest plan
ant abclbuilds cleanly.PPRINT-*ANSI tests pass:=== FAILED TESTS: 0 ===.ant test.ansi.compiled): the four targettests (
PPRINT-FILL.2,PPRINT-LINEAR.2,PPRINT-TABULAR.2,PPRINT-LOGICAL-BLOCK.17) no longerappear in the unexpected-failure list. Remaining unexpected
failures match the pre-existing baseline from other
unmerged branches (numeric FORMAT, pathname printing, CLOS
corrections, etc.).
-
(let ((v1 '(8)))(let ((*print-circle* t))(with-output-to-string (s)(pprint-logical-block (s (list v1 v1) :prefix "(" :suffix ")")(pprint-exit-if-list-exhausted)(loop (pprint-logical-block (s (pprint-pop))(princ (car v1) s))(pprint-exit-if-list-exhausted)(write-char #\space s))))))→
"(#1=(8) #1#)"-
(pprint-fill s #'car)on a fresh stream matches(write-to-string #'car)— both produce"#<FUNCTION #<CAR {id}> {id}>".-
(let ((x (list 'a))) (let ((*print-circle* t))(write-to-string (list x x))))still produces"(#1=(A) #1#)"(regression guard for the five tests thata naive fix would have broken).
Compatibility
No public API change.
pprint-fill/pprint-linear/pprint-tabularnow matchwrite-to-string(CLOSprint-objectwrapping). Callers that relied on theunwrapped
#<FOO {id}>form will see the fuller#<FUNCTION #<FOO {id}> {id}>form.pprint-logical-blocknow honours*print-circle*.Shared substructure that previously printed as duplicated
literals will now print with
#n=/#n#labels, as CLHSrequires.