gentens.red 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. module gentens;
  2. % This module defines the characteristics of 'generic' tensors.
  3. % 'generic' means: any nimbers of indices, no transformation
  4. % properties under coordinate transformations assumed, any space
  5. % assignement allowed.
  6. % TENSOR calls make_tensor which applies on the list of IDP the
  7. % following properties:
  8. % Flags: tensor, full
  9. % Properties: indvarprt, xindvarprt_tens for printing indices.
  10. % : SIMPTENSOR for simplification.
  11. % : Presently used to construct a correct list of indices.
  12. % All arguments are NOT supposed to be tensor-indices. So
  13. % dependencies may be either IMPLICIT ir EXPLICIT.
  14. lisp remflag(list 'minus,'intfn);
  15. global '(dimex!* sgn!* signat!* spaces!* numindxl!* pair_id_num!*) ;
  16. lisp (pair_id_num!*:= '((!0 . 0) (!1 . 1) (!2 . 2) (!3 . 3) (!4 . 4)
  17. (!5 . 5) (!6 . 6) (!7 . 7) (!8 . 8) (!9 . 9)
  18. (!10 . 10) (!11 . 11) (!12 . 12) (!13 . 13)));
  19. fluid('(dummy_id!* g_dvnames epsilon!*));
  20. % g_dvnames is a vector.
  21. switch onespace;
  22. !*onespace:=t; % working inside a unique space is the default.
  23. rlistat('(tensor rem_tensor rem_value_tens));
  24. flag('(make_bloc_diagonal),'opfn);
  25. symbolic procedure make_bloc_diagonal te;
  26. % te is a generic tensor. Forces it to be bloc
  27. % diagonal when several spaces are involved.
  28. <<put(te,'bloc_diagonal,'symb_belong_several_spaces);t>>;
  29. symbolic procedure rem_value_tens u;
  30. % remove values of the components of tensors included in u
  31. << for each x in u do
  32. if atom x then remprop(x,'kvalue)
  33. else
  34. if listp x then
  35. begin scalar kval,tens,varl,ind;
  36. tens:=car x;
  37. kval:=get(tens,'kvalue);
  38. remprop(tens,'kvalue);
  39. varl:= splitlist!:(x,'list);
  40. ind:=if null varl then cdr x else setdiff(cdr x,varl);
  41. varl:=if varl then car varl;
  42. ind:= (lambda y;
  43. (mkindxlist for each z in y collect revalind z)) ind;
  44. kval:=delete(assoc(if varl then tens . varl . ind
  45. else tens . ind,kval),kval);
  46. put(tens,'kvalue,kval);
  47. end; t>>;
  48. symbolic procedure rem_tensor1 x;
  49. <<remflag(list x,'tensor); elim_names x;
  50. remprop(x,'kvalue);
  51. remprop(x,'klist);
  52. remprop(x,'simpfn);
  53. remprop(x,'prifn);
  54. remprop(x,'fancy!-pprifn);
  55. remprop(x,'partic_tens);
  56. remprop(x,'belong_to_space);
  57. remprop(x,'bloc_diagonal);
  58. remprop(x,'symtree);
  59. remflag(list x,'full);
  60. remflag(list x,'simp0fn);
  61. remflag(list x,'listargp);
  62. remflag(list x,'generic);
  63. remflag(list x, 'symmetric);
  64. remflag(list x,'antisymmetric);
  65. (if y then epsilon!*:=delete(y,epsilon!*))where y=assoc(x,epsilon!*);
  66. >>;
  67. symbolic procedure elim_names u;
  68. % u is the name of a particular tensor
  69. if get(u,'partic_tens)='simpdelt then remprop('delta,'name)
  70. else
  71. if get(u,'partic_tens)='simpdel then remprop('del,'name)
  72. else
  73. if get(u,'partic_tens)='simpeta then remprop('eta,'name)
  74. else
  75. if get(u,'partic_tens)='simpepsi then remprop('epsilon,'name)
  76. else
  77. if get(u,'partic_tens)='metric then remprop('metric,'name);
  78. symbolic procedure tensor u;
  79. % this is the basic constructor for the tensor object.
  80. begin;
  81. u:= for each x in u collect reval x; % correction
  82. for each x in u do
  83. if get(x,'avalue) or (flagp(x,'reserved) and null flagp(x,'tensor))
  84. or getrtype x or (gettype x eq 'procedure)
  85. or (x memq list('sin,'cos,'tan,'atan,'acos,'asin,'int,'df))
  86. then rerror(cantens,1,list(x,"may not be defined as tensor"))
  87. else make_tensor(x,t);
  88. return t
  89. end;
  90. symbolic procedure make_tensor(u,v);
  91. <<if v and flagp(u,'tensor) then
  92. lpri {"*** Warning: ",
  93. u,"redefined as generic tensor "};
  94. rem_tensor list u;
  95. flag(list u,'tensor);
  96. flag(list u,'listargp);
  97. put(u,'simpfn,'simptensor);
  98. flag(list u,'simp0fn);
  99. put(u,'prifn,'indvarprt);
  100. put(u,'fancy!-pprifn,'xindvarprt_tens);
  101. flag(list u,'full)>>;
  102. symbolic procedure rem_tensor u;
  103. % To erase tensor properties on the list of identifiers u.
  104. <<u:=for each x in u collect reval x;
  105. for each x in u do if flagp(x,'tensor) then
  106. rem_tensor1 x;
  107. t>>;
  108. symbolic procedure tensorp u;
  109. % Elementary function to detect tensors.
  110. not atom u and flagp(car u,'tensor);
  111. symbolic procedure tensorp!: u;
  112. % u is a list of kernel as it comes from the
  113. % function list_of_factors applied to a standard term.
  114. % returns the number of tensor kernel present.
  115. begin integer nt;
  116. <<while u do if tensorp car u then nt:=nt+1; u:=cdr u>>;
  117. return nt
  118. end;
  119. flag(list('make_tensor_belong_space),'opfn);
  120. symbolic procedure make_tensor_belong_space(te,sp);
  121. % te must be a tensor identifier
  122. % introduces the indicator 'belong_to_space
  123. % sp is a space name
  124. % First, if no space is defined, it is, by default, unique
  125. % and nothing should be done.
  126. if !*onespace then nil
  127. else
  128. if flagp(te,'tensor) then
  129. if get(te,'partic_tens) eq 'simpepsi then
  130. <<epsilon!* :=union(list(te . sp),
  131. delete(assoc(te,epsilon!*),epsilon!*));
  132. put(te,'belong_to_space,sp)
  133. >>
  134. else put(te,'belong_to_space,sp);
  135. rlistat '(make_tensor_belong_anyspace);
  136. symbolic procedure make_tensor_belong_anyspace u;
  137. % replace the list of tensors u in the ON ONESPACE
  138. % environment.
  139. <<for each x in u do
  140. <<remprop(x,'belong_to_space);
  141. (if y then
  142. epsilon!*:=delete(y,epsilon!*))where y=assoc(x,epsilon!*)
  143. >>;
  144. t>>;
  145. symbolic procedure simptensor u;
  146. % Basic simplification procedure for all tensors.
  147. begin scalar x,ind,func,varl,bool,lsym;
  148. varl:= splitlist!:(u,'list); % gives ((list ...)) or nil.
  149. if null varl then
  150. (if z then <<varl:=z; bool:=t;>>)where z=extract_vars cdr u;
  151. ind:=if null varl then cdr u else setdiff(cdr u,varl);
  152. varl:=if bool then 'list . varl
  153. else
  154. if varl then car varl;
  155. varl:= reval varl;
  156. x:= (lambda y;
  157. mkindxlist for each z in y collect revalind z) ind;
  158. x:=for each j in x collect reval j; % if substitutions are made.
  159. x:= (lambda y;
  160. mkindxlist for each z in y collect revalind z) x;
  161. x:=car u . x;
  162. % identify the possible 'dummy indices':
  163. ind:=split_cov_cont_ids cdr x;
  164. % Check numeric indices:
  165. num_ids_range(ind,car u);
  166. mk_dummy_ids ind;
  167. % verify if the set of dummy indices is consistent:
  168. verify_tens_ids ind;
  169. % if u is chosen bloc-diagonal then check the input
  170. % and, if symbols belong to different subspaces return 0
  171. if
  172. (if x then apply1(x,ind))where x=get(car u,'bloc_diagonal)
  173. then return nil ./ 1;
  174. % If u is a special tensor then apply the relevant simplification
  175. % function:
  176. return if func:=get(car x,'partic_tens) then
  177. if flagp(car u,'generic) then
  178. if func neq 'simpdelt then apply2(func,x,varl)
  179. else apply2(func,x,varl) ./ 1
  180. else apply1(func,x) ./ 1
  181. else
  182. if flagp(car x,'symmetric) then
  183. mksq(car x .
  184. if null varl then cont_before_cov ordn cdr x
  185. else varl . cont_before_cov ordn cdr x,1)
  186. else
  187. if flagp(car x,'antisymmetric) then
  188. if repeats
  189. (if null affinep u then
  190. (lambda y; append(car y,cadr y)
  191. )split_cov_cont_ids cdr x
  192. else cdr x)
  193. then nil ./ 1
  194. else
  195. (if not permp!:(z,cdr x) then
  196. negsq mksq(car x . if varl then varl . z
  197. else z,1)
  198. else mksq(car x . if varl then varl . z
  199. else z,1)
  200. )where z= cont_before_cov ordn cdr x
  201. else
  202. % cases of partial symmetry
  203. % when the tensor is 0 it is advantageous to detect it
  204. % BEFORE canonical acts:
  205. if lsym:=get(car u,'symtree) then
  206. if symtree_zerop(cdr x,lsym) then nil ./ 1
  207. else
  208. mksq(if varl then car x . varl . cdr x else x,1)
  209. else
  210. mksq(if varl then car x . varl . cdr x else x,1)
  211. end;
  212. %symbolic procedure current_princ_index_lst(u,v);
  213. % u is the tensor-kernel, v is its number of indices.
  214. % it returns a list of the form
  215. % ((id_tens1 (index1 . 1) (index2 . 2)...))
  216. % for instance:
  217. % ((tt (a . 1) ((minus b) . 2) (c . 3) (d . 4)))
  218. % for the currently handled tensors tt(a,-b,c,d).
  219. % From it one may extract all informations.
  220. % subla(v,'tt); ==>
  221. % ((a . 1) ((minus b) . 2) (c . 3) (d . 4))
  222. % it is also obtained from the macro 'extract_index_tens'.
  223. % begin integer n;
  224. % scalar x,id_tens;
  225. % n:=1;
  226. % id_tens:=car u;
  227. % u:=cdr u;
  228. % while n leq v do
  229. % <<x:=nconc(list(car u . n),x);u:=cdr u; n:=n+1>>;
  230. % return (id_tens . reverse x) . nil
  231. %end;
  232. %symbolic procedure get_n_index(n,u);
  233. % u is the ouput of the smacro extract_index_tens.
  234. % n is an integer which corresponds to the index position.
  235. % gives the corresponding index.
  236. % it is an atom if contravariant.
  237. % it is a list which begins by 'minus' if it is
  238. % covariant.
  239. % if n <= length u then car assoc2(n,u);
  240. %symbolic procedure index_list u;
  241. % u is the ouput of extract_index_tens.
  242. % gives the list of indices without their positions
  243. % order in the list corresponds to the order of indices
  244. % for instance:
  245. % (a (minus b) c d) for tt(a,-b,c,d)
  246. % when the tensor is given explicitly in prefix form,
  247. % it is better to take the cdr of this form.
  248. % begin scalar x;
  249. % for i:=1:length u do x:=get_n_index(i,u) . x;
  250. % return reversip x
  251. %end;
  252. symbolic procedure split_cov_cont_ids u;
  253. % output is the composite list ((cov_indices)(cont_indices))
  254. % INPUT u is the output of 'index_list' or is simply the cdr
  255. % of the prefix form.
  256. begin scalar xcov,xcont;
  257. while u do << (if careq_minus y then xcov:= (raiseind y) . xcov
  258. else xcont := y . xcont)where y=car u; u:=cdr u>>;
  259. return list(reversip xcov,reversip xcont)
  260. end;
  261. symbolic procedure verify_tens_ids u;
  262. % u is the output of split_cov_cont_ids
  263. begin scalar cov,cnt;
  264. cov:= car u;
  265. cnt:=cadr u;
  266. % eliminate the obviously misplaced dummy indices:
  267. % i.e. when a dummy index is at least TWICE in cov or cont
  268. if repeats extract_dummy_ids cov or
  269. repeats extract_dummy_ids cnt then
  270. rerror(cantens,2,
  271. list(list(car u, cadr u),
  272. "are inconsistent lists of indices"))
  273. else return t
  274. end;
  275. rlistat '(make_variables remove_variables);
  276. symbolic procedure make_variables u;
  277. % u is a list of idp's.
  278. % declare them as variables.
  279. % allow to distinghish them from indices.
  280. <<for each x in u do flag(list x,'variable);t>>;
  281. symbolic procedure remove_variables u;
  282. % u is a list of idp's.
  283. % declare them as variables.
  284. % allow to distinghish them from indices.
  285. <<for each x in u do remflag(list x,'variable);t>>;
  286. symbolic procedure extract_vars u;
  287. if null u then nil
  288. else
  289. if flagp(raiseind!: car u,'variable) then car u . extract_vars cdr u
  290. else extract_vars cdr u;
  291. symbolic procedure select_vars u;
  292. % used for SYMMETRIZE.
  293. % use extract_vars
  294. begin scalar varl,ind,bool;
  295. varl:= splitlist!:(u,'list); % gives ((list ...)) or nil.
  296. if null varl then
  297. (if z then <<varl:=z; bool:=t;>>)where z=extract_vars cdr u;
  298. ind:=if null varl then cdr u else setdiff(cdr u,varl);
  299. varl:=if bool then 'list . varl
  300. else
  301. if varl then car varl;
  302. return list(ind,varl)
  303. end;
  304. symbolic procedure symb_belong_several_spaces ind;
  305. % ind is the list which comes from split_cov_cont_ids
  306. if !*onespace then nil
  307. else
  308. begin scalar x,sp;
  309. x:=clean_numid flattens1 ind;
  310. while x and
  311. (null get(car x,'space) or get(car x,'space) eq 'wholespace)
  312. do x:= cdr x;
  313. if null x then return nil
  314. else
  315. while x and (null get(car x,'space) or
  316. get(car x,'space) eq 'wholespace) do x:=cdr x;
  317. sp:=get(car x,'space);
  318. while x and (null get(car x,'space) or
  319. get(car x,'space) eq 'wholespace or
  320. get(car x,'space) eq sp) do x:=cdr x;
  321. return
  322. if null x then nil else t
  323. end;
  324. symbolic procedure num_ids_range(ind,tens);
  325. % this procedure checks the validity of numeric indices in various
  326. % cases
  327. if !*onespace then
  328. if out_of_range(ind,dimex!*,nil) then
  329. rerror(cantens,3,"numeric indices out of range")
  330. else nil
  331. else % onespace is OFF.
  332. % verify if the tensor belong to a subspace:
  333. if null numindxl!* then
  334. if out_of_range(ind,get_dim_space get(tens,'belong_to_space),
  335. get_sign_space get(tens,'belong_to_space))
  336. then rerror(cantens,3,"numeric indices out of range")
  337. else nil
  338. else (if null lst_belong_interval(x,int) then
  339. rerror(cantens,3,"numeric indices do not belong to (sub)-space")
  340. )where x=extract_numid flattens1 ind,
  341. int=subla(numindxl!*,get(tens,'belong_to_space));
  342. symbolic procedure restore_tens_idx(u,v);
  343. % u is a dummy-compatible list,
  344. % v is the original list of indices given by
  345. % index_list extract_intex_tens <tensor> or cdr <prefix form>.
  346. % result is the new index_list
  347. % exemple:
  348. % u=(d (minus b) a a), v=(a (minus b) c (minus c))
  349. % restore_tesn_idx(u,v); ==> (d (minus b) a (minus (a)))
  350. if null u then nil
  351. else
  352. if null memq(car u,dummy_id!*) then car u . restore_tens_idx(cdr u,cdr v)
  353. else
  354. if atom car u and atom car v then car u . restore_tens_idx(cdr u,cdr v)
  355. else
  356. lowerind u . restore_tens_idx(cdr u,cdr v);
  357. symbolic procedure clean_numid u;
  358. % input is a list of indices.
  359. % output is a list of 'non-numeric' indices.
  360. % 11 is the biggest allowed integer
  361. if null u then nil
  362. else
  363. if !*id2num car u then clean_numid cdr u
  364. else car u . clean_numid cdr u;
  365. symbolic procedure extract_num_id u;
  366. % extract all pseudo-numeric indices from u.
  367. if null u then nil
  368. else
  369. if charnump!: car u then car u . extract_num_id cdr u
  370. else extract_num_id cdr u;
  371. symbolic procedure extract_numid u;
  372. % input is a list of indices.
  373. % output is a list of the corresponding 'numeric' indices.
  374. % 13 is the biggest allowed integer
  375. if null u then nil
  376. else
  377. (if x then x . extract_numid cdr u
  378. else extract_numid cdr u)where x=!*id2num car u;
  379. symbolic procedure mkindxlist u;
  380. % CONSTRUCTS THE COVARIANT and CONTRAVARIANT numeric INDICES.
  381. for each j in u collect
  382. if fixp j then !*num2id j else
  383. if pairp j and fixp cadr j then list('minus, !*num2id cadr j)
  384. else j;
  385. symbolic procedure !*num2id u;
  386. %CONVERTS A NUMERIC INDEX TO AN ID;
  387. %TAKEN FROM EXCALC.
  388. if u<12 then intern cdr assoc(u,
  389. '((0 . !0) (1 . !1) (2 . !2) (3 . !3) (4 . !4)
  390. (5 . !5) (6 . !6) (7 . !7) (8 . !8) (9 . !9)
  391. (10 . !10) (11 . !11) (12 . !12) (13 . !13)))
  392. else intern compress append(explode '!!,explode u);
  393. symbolic procedure !*id2num u;
  394. %CONVERTS AN INDEX TO A NUMBER OR nil IS RETURNED.
  395. begin scalar x ;
  396. if x:= assoc(u, pair_id_num!*) then
  397. return cdr x
  398. end;
  399. symbolic procedure num_indlistp u;
  400. % returns True if the list of indices
  401. % contains ONLY numeric indices.
  402. numlis for each y in u collect !*id2num y;
  403. symbolic procedure out_of_range(u,dim,sign);
  404. % dim represents the
  405. % actual space dimension of the space.
  406. % acts only when it is an integer.
  407. % dimsub represents the subspace signature
  408. % u is the list generated by split_cov_cont_ids
  409. if fixp dim then
  410. begin scalar lu,sign_space;
  411. lu:=extract_numid flattens1 u;
  412. sign_space:=if null sign then signat!* else sign;
  413. while lu and
  414. (if sign_space=1 then car lu < dim
  415. else
  416. if sign_space =0 then car lu <=dim)
  417. do lu:=cdr lu;
  418. return if lu then t else nil
  419. end;
  420. symbolic procedure revalind u;
  421. % Pour que -0 ne devienne pas +0:
  422. begin scalar x,y,alglist!*;
  423. x := subfg!*;
  424. subfg!* := nil;
  425. u := subst('!0,0,u);
  426. % The above line is used to avoid the simplification of -0 to 0.
  427. y := prepsq simp u;
  428. subfg!* := x;
  429. return y
  430. end;
  431. symbolic procedure revalindl u;
  432. for each ind in u collect revalind ind;
  433. symbolic procedure indvarprt u;
  434. % An extension of the corresponding function of EXCALC
  435. if null !*nat then <<prin2!* car u;
  436. prin2!* "(";
  437. if cddr u then inprint('!*comma!*,0,cdr u)
  438. else maprin cadr u;
  439. prin2!* ")" >>
  440. else begin scalar x,y,y2,args,spaceit; integer l,maxposn!*,oldy;
  441. l := flatsizec flatindxl u+length cdr u-1;
  442. if l>(linelength nil-spare!*)-posn!* then terpri!* t;
  443. %avoid breaking of an indexed variable over a line;
  444. y := ycoord!*;
  445. maxposn!*:=0;
  446. prin2!* car u;
  447. spaceit := if get(car u,'partic_tens) memq {'simpdelt,'simpdel}
  448. then << x := posn!*; nil>>
  449. else t;
  450. for each j on cdr u do
  451. <<oldy:=ycoord!*;
  452. ycoord!* := y + if (atom car j) or (careq_tilde car j) then 1 else -1;
  453. if null(spaceit) and (oldy neq ycoord!*) then
  454. << if posn!*>maxposn!* then maxposn!*:=posn!*;
  455. posn!*:=x;
  456. >>;
  457. if ycoord!*>ymax!* then ymax!* := ycoord!*;
  458. if ycoord!*<ymin!* then ymin!* := ycoord!*;
  459. if (atom car j) or (careq_tilde car j)
  460. then maprint (car j,0)
  461. else if careq_minus car j
  462. then maprint (cadar j,0)
  463. else args := car j;
  464. if cdr j then prin2!* " ">>;
  465. if null cdr u then
  466. <<ycoord!* := y + 1;
  467. if ycoord!*>ymax!* then ymax!* := ycoord!*;
  468. if ycoord!*<ymin!* then ymin!* := ycoord!*;
  469. maprint ('!(!),0)
  470. >>;
  471. ycoord!* := y;
  472. if (maxposn!*>0) and (posn!*<maxposn!*) then posn!*:=maxposn!*;
  473. if args then
  474. << prin2!* "(";
  475. obrkp!* := nil;
  476. y2 := orig!*;
  477. orig!* := if posn!*<18 then posn!* else orig!*+3;
  478. if cdr args then inprint('!*comma!*,0,cdr reval args );
  479. obrkp!* := t;
  480. orig!* := y2;
  481. prin2!* ")";
  482. >>;
  483. end;
  484. put('indvarprt,'expt,'inbrackets);
  485. symbolic procedure xindvarprt_tens(l,p);
  486. % An extension of the function XINDVARPRT of EXCALC.
  487. fancy!-level
  488. ( if not(get('expt,'infix)>p) then
  489. fancy!-in!-brackets({'xindvarprt_tens,mkquote l,0}, '!(,'!))
  490. else
  491. begin scalar w,x,s,args,spaceit;
  492. spaceit:=t;
  493. w:=(fancy!-prefix!-operator car l) where fancy_lower_digits = nil;
  494. if get(car l,'partic_tens) memq {'simpdelt,'simpdel}
  495. then spaceit:=nil;
  496. if w eq 'failed then return w;
  497. l := cdr l;
  498. if l then
  499. <<
  500. while l and (w neq 'failed) do
  501. << if (atom car l) or (careq_tilde car l) then
  502. (if s eq '!^ then
  503. x := car l . x
  504. else <<
  505. if s then
  506. <<if spaceit then fancy!-prin2!*("{}",0);
  507. w := fancy!-print!-indexlist1(reversip x,s,nil)>>;
  508. x := {car l};
  509. s := '!^>> )
  510. else (
  511. if careq_minus(car l) then
  512. ( if s eq '!_
  513. then x := cadar l . x
  514. else <<
  515. if s then
  516. <<if spaceit then fancy!-prin2!*("{}",0);
  517. w := fancy!-print!-indexlist1(reversip x,s,nil)>>;
  518. x := {cadar l};
  519. s := '!_>> )
  520. else
  521. args:=car l);
  522. l := cdr l>>;
  523. if x then
  524. << if spaceit then fancy!-prin2!*("{}",0);
  525. w := fancy!-print!-indexlist1(reversip x,s,nil);
  526. if w eq 'failed then return w >>;
  527. if args then w:=fancy!-print!-function!-arguments cdr args;
  528. >>
  529. else
  530. <<
  531. w := fancy!-print!-indexlist1(list('!(,'!)),'!^,nil)
  532. >>;
  533. return w;
  534. end);
  535. endmodule;
  536. end;