extras.red 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. % extras.red Copyright Codemist Ltd 2002
  2. %
  3. % Additional useful functions to have in a Lisp environment.
  4. %
  5. %
  6. % This code may be used and modified, and redistributed in binary
  7. % or source form, subject to the "CCL Public License", which should
  8. % accompany it. This license is a variant on the BSD license, and thus
  9. % permits use of code derived from this in either open and commercial
  10. % projects: but it does require that updates to this code be made
  11. % available back to the originators of the package.
  12. % Before merging other code in with this or linking this code
  13. % with other packages or libraries please check that the license terms
  14. % of the other material are compatible with those of this.
  15. %
  16. % The following small function is just used for testing the CSL OEM
  17. % interface code...
  18. symbolic procedure oem!-supervisor();
  19. print eval read();
  20. %
  21. % If you go (setq !*break!-loop!* 'break!-loop) then errors will get this
  22. % function called - and it is rather desirable that it does not itself fail.
  23. % The argument is what was passed to (ERROR ...) if the Lisp-level error
  24. % function was called. When this function exits the system will unwind back
  25. % to the next enclosing ERRORSET. (enable!-backtrace <fg>) can be used to
  26. % switch backtrace display on or off.
  27. %
  28. symbolic procedure break!-loop a;
  29. begin
  30. scalar prompt, ifile, ofile, u, v;
  31. % I use wrs/rds so I am compatible between Standard and Common Lisp here
  32. ifile := rds !*debug!-io!*;
  33. ofile := wrs !*debug!-io!*;
  34. prompt := setpchar "Break loop (:X exits)> ";
  35. top:u := read();
  36. if u = '!:x then go to exit
  37. else if u = '!:q then <<
  38. enable!-backtrace nil;
  39. princ "Backtrace now disabled";
  40. terpri() >>
  41. else if u = '!:v then <<
  42. enable!-backtrace t;
  43. princ "Backtrace now enabled";
  44. terpri() >>
  45. else <<
  46. if null u then v := nil
  47. else v := errorset(u, nil, nil);
  48. if atom v then <<
  49. princ ":Q quietens backtrace"; terpri();
  50. princ ":V enables backtrace"; terpri();
  51. princ ":X exits from break loop"; terpri();
  52. princ "else form for evaluation"; terpri();
  53. >>
  54. else <<
  55. prin "=> ";
  56. prinl car v;
  57. terpri() >> >>;
  58. go to top;
  59. exit:
  60. rds ifile;
  61. wrs ofile;
  62. setpchar prompt;
  63. return nil
  64. end;
  65. % dated!-name manufactures a symbol that is expected to be unique - but
  66. % there will in fact be no strict guarantee of that. The name is made up out
  67. % of a base part provided by the caller, then a chunk that encodes the
  68. % date and time of day that the function was called (accurate to around
  69. % a second, typically). Finally a serial number that starts off as 1 when
  70. % the "extras" module is loaded into a copy of Lisp. Two copies of Lisp
  71. % running at the same time could lead to clashes here. But names of this
  72. % sort seem to be needed for inclusion in files and other places where
  73. % re-readability is vital.
  74. global '(s!:gensym!-serial);
  75. s!:gensym!-serial := 0;
  76. symbolic procedure s!:stamp n;
  77. % Converts an integer (which will in fact be a timestamp, an about
  78. % 2^29 or 2^30 in value) into a sequence of letters and digits by
  79. % converting to base 36 (with the digits ending up in the "wrong"
  80. % order). Used only when generating probably-unique-identifiers to
  81. % use as names for internally generated functions.
  82. if n < 0 then append(s!:stamp(-n), '(!-))
  83. else if n = 0 then nil
  84. else schar("0123456789abcdefghijklmnopqrstuvwxyz", remainder(n, 36)) .
  85. s!:stamp truncate(n ,36);
  86. symbolic procedure dated!-name base;
  87. intern list!-to!-string
  88. append(explodec base,
  89. '!_ . append(reverse s!:stamp datestamp(),
  90. '!_ . explodec(s!:gensym!-serial := s!:gensym!-serial + 1)));
  91. % hashtagged!-name(base, value) manufactures a name based on the
  92. % base together with a hash-value computed from the "value". This
  93. % is expected to be a reliable signature (but clashes are of course
  94. % possible). Eg base may be the name of a function and value its
  95. % definition, then this will invent a name suitable for a parallel
  96. % version of the function where the new name ought not to conflict with
  97. % ones used later if this function gets defined with a different
  98. % definition.
  99. symbolic procedure hashtagged!-name(base, value);
  100. intern list!-to!-string
  101. append(explodec base, '!_ . s!:stamp md60 value);
  102. %
  103. % Sorting
  104. %
  105. remflag('(sort sortip), 'lose);
  106. symbolic procedure sort(l, pred);
  107. % Sort the list l according to the given predicate. If l is a list
  108. % of numbers then the predicate "lessp" will sort the list into
  109. % ascending order. The predicate should be a strict inequality, i.e.
  110. % it should return NIL if the two items compared are equal.
  111. % As implemented here SORT just calls STABLE-SORT, but as a matter of
  112. % style any use where the ordering of incomparable items in the output
  113. % matters ought to use STABLE!-SORT directly, thereby allowing the
  114. % replacement of this code with a faster non-stable method.
  115. % (Note: the previous REDUCE sort function also happened to be stable, so
  116. % this code should give exactly the same results for all calls where
  117. % the predicate is self-consistent and never has both pred(a,b) and
  118. % pred(b,a) true. A previous CSL sort was not stable, but was perhaps
  119. % very slightly faster than this)
  120. stable!-sortip(append(l, nil), pred);
  121. symbolic procedure stable!-sort(l, pred);
  122. % Sorts a list, as SORT, but if two items x and y in the input list
  123. % satisfy neither pred(x,y) nor pred(y,x) [i.e. they are equal so far
  124. % as the given ordering predicate is concerned] this function guarantees
  125. % that they will appear in the output list in the same order that they
  126. % were in the input.
  127. stable!-sortip(append(l, nil), pred);
  128. symbolic procedure sortip(l, pred);
  129. stable!-sortip(l, pred);
  130. symbolic procedure stable!-sortip(l, pred);
  131. % As stable!-sort, but over-writes the input list to make the output.
  132. % It is not intended that people should call this function directly: it
  133. % is present just as the implementation of the main sort procedures defined
  134. % above.
  135. begin
  136. scalar l1, l2, w;
  137. if null l then return l; % Input list of length 0
  138. l1 := l;
  139. l2 := cdr l;
  140. if null l2 then return l; % Input list of length 1
  141. % Now I have dealt with the essential special cases of lists of length 0
  142. % and 1 (which do not need sorting at all). Since it possibly speeds things
  143. % up just a little I will now have some fairly ugly code that makes special
  144. % cases of lists of length 2. I could easily have special code for length
  145. % 3 lists here (and include it, but commented out), but at present my
  146. % measurements suggest that the speed improvement that it gives is minimal
  147. % and the increase in code bulk is large enough to give some pain.
  148. l := cdr l2;
  149. if null l then << % Input list of length 2
  150. if apply2(pred, car l2, car l1) then <<
  151. l := car l1;
  152. rplaca(l1, car l2);
  153. rplaca(l2, l) >>;
  154. return l1 >>;
  155. % Now I will check to see if the list is in fact in order already
  156. % Doing so will have a cost - but sometimes that cost will be repaid
  157. % when I am able to exit especially early. The result of all this
  158. % is that I will have a best case behaviour with linear cost growth for
  159. % inputs that are initially in the correct order, while my average and
  160. % worst-case costs will increase by a constant factor.
  161. l := l1;
  162. while l2 and not apply2(pred, car l2, car l) do <<
  163. % In the input list is NOT already in order then I expect that this
  164. % loop will exit fairly early, and so will not contribute much to the
  165. % total cost. If it exits very late then probably in the next recursion
  166. % down the first half of the list will be found to be already sorted, and
  167. % again I have a chance to win.
  168. l := l2; l2 := cdr l2 >>;
  169. if null l2 then return l1;
  170. l2 := l1;
  171. l := cddr l2;
  172. while l and cdr l do << l2 := cdr l2; l := cddr l >>;
  173. l := l2;
  174. l2 := cdr l2;
  175. rplacd(l, nil);
  176. % The two sub-lists are then sorted.
  177. l1 := stable!-sortip(l1, pred);
  178. l2 := stable!-sortip(l2, pred);
  179. % Now I merge the sorted fragments, giving priority to item from the
  180. % earlier part of the original list.
  181. l := w := list nil;
  182. while l1 and l2 do <<
  183. if apply2(pred, car l2, car l1) then <<
  184. rplacd(w, l2); w := l2; l2 := cdr l2 >>
  185. else << rplacd(w, l1); w := l1; l1 := cdr l1 >> >>;
  186. if l1 then l2 := l1;
  187. rplacd(w, l2);
  188. return cdr l
  189. end;
  190. %
  191. % Code to print potentially re-entrant lists
  192. %
  193. fluid '(!*prinl!-visited!-nodes!* !*prinl!-index!*
  194. !*prinl!-fn!* !*loop!-print!* !*print!-array!*
  195. !*print!-length!* !*print!-level!*);
  196. !*print!-length!* := !*print!-level!* := nil;
  197. !*prinl!-visited!-nodes!* := mkhash(10, 0, 1.5)$
  198. symbolic procedure s!:prinl0(x,!*prinl!-fn!*);
  199. % print x even if it has loops in it
  200. begin
  201. scalar !*prinl!-index!*;
  202. !*prinl!-index!*:=0;
  203. % Clear the hash table AFTER use, so that the junk that goes into it does
  204. % not gobble memory between calls to prinl. This relies on unwind!-protect
  205. % to make sure that it is indeed always cleared. Errors (eg ^C) during the
  206. % clean-up operation could lead to curious displays in the next use of
  207. % prinl. Also of course bugs in the implementation of unwind!-protect...
  208. % clrhash !*prinl!-visited!-nodes!*;
  209. unwind!-protect(<< s!:prinl1(x, 0); s!:prinl2(x, 0) >>,
  210. clrhash !*prinl!-visited!-nodes!*);
  211. return x
  212. end;
  213. symbolic procedure s!:prinl1(x, depth);
  214. % Find all the nodes in x and record them in the hash table.
  215. % The first time a node is met it is inserted with associated value 0.
  216. % If a node is met a second time then it is assigned an unique positive
  217. % integer code that will later be used in its label.
  218. begin
  219. scalar w, length;
  220. if fixp !*print!-level!* and depth > !*print!-level!* then return nil;
  221. length := 0;
  222. top:
  223. if atom x and not simple!-vector!-p x and not gensymp x then return nil
  224. else if w := gethash(x,!*prinl!-visited!-nodes!*) then <<
  225. if w = 0 then <<
  226. !*prinl!-index!* := !*prinl!-index!* + 1;
  227. puthash(x,!*prinl!-visited!-nodes!*, !*prinl!-index!*) >>;
  228. return nil >>
  229. else <<
  230. puthash(x, !*prinl!-visited!-nodes!*, 0);
  231. if simple!-vector!-p x then <<
  232. if !*print!-array!* then <<
  233. length := upbv x;
  234. if fixp !*print!-length!* and !*print!-length!* < length then
  235. length := !*print!-length!*;
  236. for i:=0:length do s!:prinl1(getv(x,i), depth+1) >> >>
  237. else if not atom x then <<
  238. s!:prinl1(car x, depth+1);
  239. if fixp !*print!-length!* and
  240. (length := length+1) > !*print!-length!* then return nil;
  241. x := cdr x;
  242. go to top >> >>
  243. end;
  244. symbolic procedure s!:prinl2(x, depth);
  245. % Scan a structure that was previously processed by s!:prinl1. Thus all
  246. % nodes in x are already in the hash table. Those with value zero
  247. % are only present once in x, while those with strictly positive values
  248. % occur at least twice. After printing a label for such value this resets the
  249. % value negative so that the printing can tell when the visit is for
  250. % a second rather than first time. The output format is intended to
  251. % bear some resemblance to the expectations of Common Lisp.
  252. if fixp !*print!-level!* and depth > !*print!-level!* then
  253. princ "#"
  254. else if atom x and not simple!-vector!-p x and not gensymp x then <<
  255. !#if common!-lisp!-mode
  256. if complex!-arrayp x and not !*print!-array!* then princ "[Array]"
  257. else if structp x and not !*print!-array!* then princ "[Struct]"
  258. else
  259. !#endif
  260. funcall(!*prinl!-fn!*, x) >>
  261. else begin scalar w, length;
  262. w := gethash(x,!*prinl!-visited!-nodes!*);
  263. % w has better be a number here, following s!:prinl1
  264. if not zerop w then <<
  265. if w < 0 then <<
  266. princ "#";
  267. princ (-w);
  268. princ "#";
  269. return nil >>
  270. else <<
  271. puthash(x,!*prinl!-visited!-nodes!*, -w);
  272. princ "#";
  273. princ w;
  274. princ "=" >> >>;
  275. if simple!-vector!-p x then <<
  276. princ "%(";
  277. if !*print!-array!* then <<
  278. length := upbv x;
  279. if fixp !*print!-length!* and !*print!-length!* < length then
  280. length := !*print!-length!*;
  281. for i:=0:length do << s!:prinl2(getv(x,i), depth+1);
  282. if not i=upbv x then princ " " >> >>
  283. else princ "...";
  284. princ ")";
  285. return nil >>
  286. else if atom x then return funcall(!*prinl!-fn!*, x);
  287. princ "(";
  288. length := 0;
  289. loop:
  290. s!:prinl2(car x, depth+1);
  291. x:=cdr x;
  292. if atom x then <<
  293. if simple!-vector!-p x then <<
  294. princ " . %(";
  295. if !*print!-array!* then <<
  296. length := upbv x;
  297. if fixp !*print!-length!* and !*print!-length!* < length then
  298. length := !*print!-length!*;
  299. for i:=0:length do <<s!:prinl2(getv(x,i), depth+1);
  300. if not i=upbv x then princ " ">> >>
  301. else princ "...";
  302. princ ")" >>
  303. else if x then <<
  304. princ " . ";
  305. funcall(!*prinl!-fn!*, x) >>;
  306. return princ ")" >>;
  307. if fixp !*print!-length!* and
  308. (length := length + 1) > !*print!-length!* then
  309. return princ " ...)";
  310. w := gethash(x, !*prinl!-visited!-nodes!*);
  311. if not (w = 0) then if w < 0 then <<
  312. princ " . #";
  313. princ (-w);
  314. return princ "#)" >>
  315. else <<
  316. princ " . ";
  317. s!:prinl2(x, depth+1); % This will set the label
  318. return princ ")" >>
  319. else princ " ";
  320. go to loop
  321. end;
  322. symbolic procedure printl x;
  323. << prinl x;
  324. terpri();
  325. x >>;
  326. symbolic procedure printcl x;
  327. << princl x;
  328. terpri();
  329. x >>;
  330. symbolic procedure princl x;
  331. s!:prinl0(x,function princ);
  332. symbolic procedure prinl x;
  333. s!:prinl0(x,function prin);
  334. %
  335. % A small subset of the facilities of the unreasonably baroque Common
  336. % Lisp FORMAT function may be useful.
  337. %
  338. !#if (not common!-lisp!-mode)
  339. % If I am in COMMON Lisp mode then a more complete version of this
  340. % will be installed from elsewhere.
  341. symbolic procedure s!:format(dest, fmt, args);
  342. begin
  343. scalar len, c, a, res, o;
  344. if not null dest then <<
  345. if dest = 't then o := wrs nil
  346. else o := wrs dest >>;
  347. len := upbv fmt;
  348. for i := 0:len do <<
  349. c := schar(fmt, i);
  350. if c = '!~ then <<
  351. i := i + 1;
  352. c := char!-downcase schar(fmt, i);
  353. if c = '!% then
  354. if null dest then res := !$eol!$ . res
  355. else terpri()
  356. else if c = '!~ then
  357. if null dest then res := '!~ . res
  358. else princ '!~
  359. else <<
  360. if null args then a := nil
  361. else <<
  362. a := car args;
  363. args := cdr args >>;
  364. if c = '!a then
  365. if null dest then for each k in explode2 a do res := k . res
  366. else princ a
  367. else if c = '!s then
  368. if null dest then for each k in explode a do res := k . res
  369. else prin a
  370. else
  371. if null dest then for each k in explode a do res := k . res
  372. else prin list('!?!?!?, c, a) >> >>
  373. else <<
  374. if null dest then res := c . res
  375. else princ c >> >>;
  376. if null dest then return list!-to!-string reversip res
  377. else << wrs o; return nil >>
  378. end;
  379. symbolic macro procedure format(u, !&optional, env);
  380. list('s!:format, cadr u, caddr u, 'list . cdddr u);
  381. !#endif
  382. fluid '(bn
  383. bufferi
  384. buffero
  385. indblanks
  386. indentlevel
  387. initialblanks
  388. lmar
  389. pendingrpars
  390. rmar
  391. rparcount
  392. stack);
  393. global '(!*quotes !*pretty!-symmetric thin!*);
  394. !*pretty!-symmetric := t;
  395. !*quotes := t;
  396. thin!* := 5;
  397. % This package prints list structures in an indented format that
  398. % is intended to make them legible. There are a number of special
  399. % cases recognized, but in general the intent of the algorithm
  400. % is that given a list (R1 R2 R3 ...), SUPERPRINT checks if
  401. % the list will fit directly on the current line and if so
  402. % prints it as:
  403. % (R1 R2 R3 ...)
  404. % if not it prints it as:
  405. % (R1
  406. % R2
  407. % R3
  408. % ... )
  409. % where each sublist is similarly treated.
  410. %
  411. % Functions:
  412. % SUPERPRINTM(X,M) print expression X with left margin M
  413. % PRETTYPRINT(X) = <<superprintm(x,posn()); terpri(); terpri()>>;
  414. %
  415. % Flag:
  416. % !*SYMMETRIC If TRUE, print with escape characters,
  417. % otherwise do not (as PRIN1/PRIN2
  418. % distinction). defaults to TRUE;
  419. % !*QUOTES If TRUE, (QUOTE x) gets displayed as 'x.
  420. % default is TRUE;
  421. %
  422. % Variable:
  423. % THIN!* if THIN!* expressions can be fitted onto
  424. % a single line they will be printed that way.
  425. % this is a parameter used to control the
  426. % formatting of long thin lists. default
  427. % value is 5;
  428. symbolic procedure prettyprint x;
  429. << superprinm(x,posn()); % What REDUCE seems to want. Looks a bit odd to me!
  430. terpri();
  431. nil>>;
  432. symbolic procedure superprintm(x,lmar);
  433. << superprinm(x,lmar); terpri(); x >>;
  434. % From here down the functions are not intended for direct use.
  435. symbolic procedure superprinm(x,lmar);
  436. begin
  437. scalar stack,bufferi,buffero,bn,initialblanks,rmar,
  438. pendingrpars,indentlevel,indblanks,rparcount,w;
  439. bufferi:=buffero:=list nil; %fifo buffer.
  440. initialblanks:=0;
  441. rparcount:=0;
  442. indblanks:=0;
  443. rmar:=linelength(nil); % right margin.
  444. linelength 500; % To try to be extra cautious
  445. if rmar<25 then error(0,list(rmar,
  446. "Linelength too short for superprinting"));
  447. bn:=0; %characters in buffer.
  448. indentlevel:=0; %no indentation needed, yet.
  449. if lmar+20>=rmar then lmar:=rmar - 21; %no room for specified margin
  450. w:=posn();
  451. if w>lmar then << terpri(); w:=0 >>;
  452. if w<lmar then initialblanks:=lmar - w;
  453. s!:prindent(x,lmar+3); %main recursive print routine.
  454. % traverse routine finished - now tidy up buffers.
  455. s!:overflow 'none; %flush out the buffer.
  456. linelength rmar;
  457. return x
  458. end;
  459. % Access functions for a stack entry.
  460. symbolic macro procedure s!:top(u,!&optional,v);
  461. '(car stack);
  462. symbolic macro procedure s!:depth(u,!&optional,v);
  463. list('car, cadr u);
  464. symbolic macro procedure s!:indenting(u,!&optional,v);
  465. list('cadr, cadr u);
  466. symbolic macro procedure s!:blankcount(u,!&optional,v);
  467. list('caddr, cadr u);
  468. symbolic macro procedure s!:blanklist(u,!&optional,v);
  469. list('cdddr, cadr u);
  470. symbolic macro procedure s!:setindenting(u,!&optional,v);
  471. list('rplaca, list('cdr, cadr u), caddr u);
  472. symbolic macro procedure s!:setblankcount(u,!&optional,v);
  473. list('rplaca, list('cddr, cadr u), caddr u);
  474. symbolic macro procedure s!:setblanklist(u,!&optional,v);
  475. list('rplacd, list('cddr, cadr u), caddr u);
  476. symbolic macro procedure s!:newframe(u,!&optional,v);
  477. list('list, cadr u, nil, 0);
  478. symbolic macro procedure s!:blankp(u,!&optional,v);
  479. list('numberp, list('car, cadr u));
  480. symbolic procedure s!:prindent(x,n);
  481. % Print list x with indentation level n.
  482. if atom x then if simple!-vector!-p x then s!:prvector(x,n)
  483. else for each c in
  484. (if !*pretty!-symmetric
  485. then if stringp x then s!:explodes x else explode x
  486. else explode2 x) do s!:putch c
  487. else if s!:quotep x then <<
  488. s!:putch '!';
  489. s!:prindent(cadr x,n+1) >>
  490. else begin
  491. scalar cx;
  492. if 4*n>3*rmar then << %list is too deep for sanity.
  493. s!:overflow 'all;
  494. n:=truncate(n, 8);
  495. if initialblanks>n then <<
  496. lmar:=lmar - initialblanks+n;
  497. initialblanks:=n >> >>;
  498. stack := (s!:newframe n) . stack;
  499. s!:putch ('lpar . s!:top());
  500. cx:=car x;
  501. s!:prindent(cx,n+1);
  502. if idp cx and not atom cdr x then
  503. cx:=get(cx,'s!:ppformat) else cx:=nil;
  504. if cx=2 and atom cddr x then cx:=nil;
  505. if cx='prog then <<
  506. s!:putch '! ;
  507. s!:prindent(car (x:=cdr x),n+3) >>;
  508. % CX now controls the formatting of what follows:
  509. % nil default action
  510. % <number> first few blanks are non-indenting
  511. % prog display atoms as labels.
  512. x:=cdr x;
  513. scan: if atom x then go to outt;
  514. s!:finishpending(); %about to print a blank.
  515. if cx='prog then <<
  516. s!:putblank();
  517. s!:overflow bufferi; %force format for prog.
  518. if atom car x then << % a label.
  519. lmar:=initialblanks:=max(lmar - 6,0);
  520. s!:prindent(car x,n - 3); % print the label.
  521. x:=cdr x;
  522. if not atom x and atom car x then go to scan;
  523. if lmar+bn>n then s!:putblank()
  524. else for i:=lmar+bn:n - 1 do s!:putch '! ;
  525. if atom x then go to outt>> >>
  526. else if numberp cx then <<
  527. cx:=cx - 1;
  528. if cx=0 then cx:=nil;
  529. s!:putch '! >>
  530. else s!:putblank();
  531. s!:prindent(car x,n+3);
  532. x:=cdr x;
  533. go to scan;
  534. outt: if not null x then <<
  535. s!:finishpending();
  536. s!:putblank();
  537. s!:putch '!.;
  538. s!:putch '! ;
  539. s!:prindent(x,n+5) >>;
  540. s!:putch ('rpar . (n - 3));
  541. if s!:indenting s!:top()='indent and not null s!:blanklist s!:top() then
  542. s!:overflow car s!:blanklist s!:top()
  543. else s!:endlist s!:top();
  544. stack:=cdr stack
  545. end;
  546. symbolic procedure s!:explodes x;
  547. %dummy function just in case another format is needed.
  548. explode x;
  549. symbolic procedure s!:prvector(x,n);
  550. begin
  551. scalar bound;
  552. bound:=upbv x; % length of the vector.
  553. stack:=(s!:newframe n) . stack;
  554. s!:putch ('lsquare . s!:top());
  555. s!:prindent(getv(x,0),n+3);
  556. for i:=1:bound do <<
  557. s!:putch '!,;
  558. s!:putblank();
  559. s!:prindent(getv(x,i),n+3) >>;
  560. s!:putch('rsquare . (n - 3));
  561. s!:endlist s!:top();
  562. stack:=cdr stack
  563. end;
  564. symbolic procedure s!:putblank();
  565. begin
  566. s!:putch s!:top(); %represents a blank character.
  567. s!:setblankcount(s!:top(),s!:blankcount s!:top()+1);
  568. s!:setblanklist(s!:top(),bufferi . s!:blanklist s!:top());
  569. %remember where I was.
  570. indblanks:=indblanks+1
  571. end;
  572. symbolic procedure s!:endlist l;
  573. %Fix up the blanks in a complete list so that they
  574. %will not be turned into indentations.
  575. pendingrpars:=l . pendingrpars;
  576. % When I have printed a ')' I want to mark all of the blanks
  577. % within the parentheses as being unindented, ordinary blank
  578. % characters. It is however possible that I may get a buffer
  579. % overflow while printing a string of )))))))))), and so this
  580. % marking should be delayed until I get round to printing
  581. % a further blank (which will be a candidate for a place to
  582. % split lines). This delay is dealt with by the list
  583. % pendingrpars which holds a list of levels that, when
  584. % convenient, can be tidied up and closed out.
  585. symbolic procedure s!:finishpending();
  586. << for each stackframe in pendingrpars do <<
  587. if s!:indenting stackframe neq 'indent then
  588. for each b in s!:blanklist stackframe do
  589. << rplaca(b,'! ); indblanks:=indblanks - 1>>;
  590. % s!:blanklist of stackframe must be non-nil so that overflow
  591. % will not treat the '(' specially.
  592. s!:setblanklist(stackframe,t) >>;
  593. pendingrpars:=nil >>;
  594. symbolic procedure s!:quotep x;
  595. !*quotes and
  596. not atom x and
  597. car x='quote and
  598. not atom cdr x and
  599. null cddr x;
  600. % property s!:ppformat drives the prettyprinter -
  601. % prog : special for prog only
  602. % 1 : (fn a1
  603. % a2
  604. % ... )
  605. % 2 : (fn a1 a2
  606. % a3
  607. % ... ) ;
  608. put('prog,'s!:ppformat,'prog);
  609. put('lambda,'s!:ppformat,1);
  610. put('lambdaq,'s!:ppformat,1);
  611. put('setq,'s!:ppformat,1);
  612. put('set,'s!:ppformat,1);
  613. put('while,'s!:ppformat,1);
  614. put('t,'s!:ppformat,1);
  615. put('de,'s!:ppformat,2);
  616. put('df,'s!:ppformat,2);
  617. put('dm,'s!:ppformat,2);
  618. put('defun,'s!:ppformat,2);
  619. put('defmacro,'s!:ppformat,2);
  620. put('foreach,'s!:ppformat,4); % (foreach x in y do ...) etc.
  621. % Now for the routines that buffer things on a character by character
  622. % basis, and deal with buffer overflow.
  623. symbolic procedure s!:putch c;
  624. begin
  625. if atom c then rparcount:=0
  626. else if s!:blankp c then << rparcount:=0; go to nocheck >>
  627. else if car c='rpar then <<
  628. rparcount:=rparcount+1;
  629. % format for a long string of rpars is:
  630. % )))) ))) ))) ))) ))) ;
  631. if rparcount>4 then << s!:putch '! ; rparcount:=2 >> >>
  632. else rparcount:=0;
  633. while lmar+bn>=rmar do s!:overflow 'more;
  634. nocheck:
  635. bufferi:=cdr rplacd(bufferi,list c);
  636. bn:=bn+1
  637. end;
  638. symbolic procedure s!:overflow flg;
  639. begin
  640. scalar c,blankstoskip;
  641. % The current buffer holds so much information that it will
  642. % not all fit on a line. try to do something about it.
  643. % flg is one of:
  644. % 'none do not force more indentation
  645. % 'more force one level more indentation
  646. % <a pointer into the buffer>
  647. % prints up to and including that character, which
  648. % should be a blank.
  649. if indblanks=0 and initialblanks>3 and flg='more then <<
  650. initialblanks:=initialblanks - 3;
  651. lmar:=lmar - 3;
  652. return 'moved!-left >>;
  653. fblank:
  654. if bn=0 then <<
  655. % No blank found - can do no more for now.
  656. % If flg='more I am in trouble and so have to print
  657. % a continuation mark. in the other cases I can just exit.
  658. if not(flg = 'more) then return 'empty;
  659. if atom car buffero then
  660. % continuation mark not needed if last char printed was
  661. % special (e.g. lpar or rpar).
  662. prin2 "%+"; %continuation marker.
  663. terpri();
  664. lmar:=0;
  665. return 'continued >>
  666. else <<
  667. spaces initialblanks;
  668. initialblanks:=0 >>;
  669. buffero:=cdr buffero;
  670. bn:=bn - 1;
  671. lmar:=lmar+1;
  672. c:=car buffero;
  673. if atom c then <<
  674. prin2 c;
  675. go to fblank >>
  676. else if s!:blankp c then if not atom blankstoskip then <<
  677. prin2 '! ;
  678. indblanks:=indblanks - 1;
  679. % blankstoskip = (stack-frame . skip-count).
  680. if c eq car blankstoskip then <<
  681. rplacd(blankstoskip,cdr blankstoskip - 1);
  682. if cdr blankstoskip=0 then blankstoskip:=t >>;
  683. go to fblank >>
  684. else go to blankfound
  685. else if car c='lpar or car c='lsquare then <<
  686. prin2 get(car c,'s!:ppchar);
  687. if flg='none then go to fblank;
  688. % now I want to flag this level for indentation.
  689. c:=cdr c; %the stack frame.
  690. if not null s!:blanklist c then go to fblank;
  691. if s!:depth c>indentlevel then << %new indentation.
  692. % this level has not emitted any blanks yet.
  693. indentlevel:=s!:depth c;
  694. s!:setindenting(c,'indent) >>;
  695. go to fblank >>
  696. else if car c='rpar or car c='rsquare then <<
  697. if cdr c<indentlevel then indentlevel:=cdr c;
  698. prin2 get(car c,'s!:ppchar);
  699. go to fblank >>
  700. else error(0,list(c,"UNKNOWN TAG IN OVERFLOW"));
  701. blankfound:
  702. if eqcar(s!:blanklist c,buffero) then
  703. s!:setblanklist(c,nil);
  704. % at least one entry on blanklist ought to be valid, so if I
  705. % print the last blank I must kill blanklist totally.
  706. indblanks:=indblanks - 1;
  707. % check if next level represents new indentation.
  708. if s!:depth c>indentlevel then <<
  709. if flg='none then << %just print an ordinary blank.
  710. prin2 '! ;
  711. go to fblank >>;
  712. % here I increase the indentation level by one.
  713. if blankstoskip then blankstoskip:=nil
  714. else <<
  715. indentlevel:=s!:depth c;
  716. s!:setindenting(c,'indent) >> >>;
  717. %otherwise I was indenting at that level anyway.
  718. if s!:blankcount c>(thin!* - 1) then << %long thin list fix-up here.
  719. blankstoskip:=c . ((s!:blankcount c) - 2);
  720. s!:setindenting(c,'thin);
  721. s!:setblankcount(c,1);
  722. indentlevel:=(s!:depth c) - 1;
  723. prin2 '! ;
  724. go to fblank >>;
  725. s!:setblankcount(c,(s!:blankcount c) - 1);
  726. terpri();
  727. lmar:=initialblanks:=s!:depth c;
  728. if buffero eq flg then return 'to!-flg;
  729. if blankstoskip or not (flg='more) then go to fblank;
  730. % keep going unless call was of type 'more'.
  731. return 'more; %try some more.
  732. end;
  733. put('lpar,'s!:ppchar,'!();
  734. put('lsquare,'s!:ppchar,'![);
  735. put('rpar,'s!:ppchar,'!));
  736. put('rsquare,'s!:ppchar,'!]);
  737. % Now some (experimental) support for network access
  738. symbolic procedure fetch!-url(url, !&optional, dest);
  739. begin
  740. scalar a, b, c, d, e, w;
  741. a := open!-url url;
  742. if null a then return nil;
  743. if dest then <<
  744. d := open(dest, 'output);
  745. if null d then <<
  746. close a;
  747. return error(0, "unable to open destination file") >>;
  748. d := wrs d >>;
  749. b := rds a;
  750. w := linelength 500;
  751. while not ((c := readch()) = !$eof!$) do princ c;
  752. linelength e;
  753. rds b;
  754. close a;
  755. if dest then close wrs d
  756. end;
  757. end;
  758. % end of extras.red