NDXSORT.MOD 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. IMPLEMENTATION MODULE NdxSort;
  2. (*
  3. * REPERTOIRE
  4. * Release 1.6
  5. * By Charles Bradford and Cole Brecheen
  6. * (c) Copyright 1985-1992 PMI
  7. * Green Bay, Wisconsin
  8. * All rights reserved
  9. * (414) 468-6040
  10. *
  11. * $Header: D:/logfiles/mods/ndxsort.mov 1.8 10 Mar 1991 15:30:36 coleb $
  12. *
  13. *
  14. * Written and contributed by Torbjorn Sund of
  15. * Tromsoe, Norway.
  16. *
  17. *)
  18. (*EntryDiag:
  19. IMPORT Diagnostics;
  20. :EntryDiag*)
  21. IMPORT ErrorManager;
  22. IMPORT GenLists;
  23. IMPORT LowLevel;
  24. IMPORT M2Strings;
  25. IMPORT NdxFiles;
  26. IMPORT NdxTypes;
  27. IMPORT PosUtils;
  28. IMPORT StrConv;
  29. IMPORT StrEdit;
  30. IMPORT SYSTEM;
  31. VAR
  32. Initialized : BOOLEAN;
  33. PROCEDURE Init();
  34. BEGIN
  35. IF Initialized THEN
  36. RETURN;
  37. ELSE
  38. Initialized := TRUE;
  39. END;
  40. (*EntryDiag:
  41. Diagnostics.Init();
  42. :EntryDiag*)
  43. ErrorManager.Init();
  44. LowLevel.Init();
  45. StrEdit.Init();
  46. NdxTypes.Init();
  47. NdxFiles.Init();
  48. GenLists.Init();
  49. M2Strings.Init();
  50. PosUtils.Init();
  51. StrConv.Init();
  52. (*EntryDiag:
  53. Diagnostics.diagS( 'Entering NdxSort', '' );
  54. :EntryDiag*)
  55. DefMaxPos := InitDefMaxPos;
  56. (*EntryDiag:
  57. Diagnostics.diagS( 'Exiting NdxSort', '' );
  58. :EntryDiag*)
  59. END Init;
  60. CONST
  61. AFTERLAST = 65535;
  62. (* clarifies list-append operation *)
  63. TYPE
  64. String = ARRAY [0..127] OF CHAR;
  65. (* below taken from Repertoire book *)
  66. PROCEDURE TypeOf(TheList : GenLists.GenList; TheElmt : CARDINAL) : CARDINAL;
  67. VAR
  68. TypeCode, DummySize : CARDINAL;
  69. DummyAddr : POINTER TO CARDINAL;
  70. BEGIN
  71. GenLists.GetElmtAdr(TheList, TheElmt, DummyAddr, DummySize, TypeCode);
  72. RETURN TypeCode;
  73. END TypeOf;
  74. (* PMI. Get1Parameter below is something I seem to end up implementing
  75. variations upon again and again. You might use your procedure to
  76. parse the structure string - or at closer look it seems to be convoluted
  77. enough already! You might consider implementing a general, useful,
  78. small, fast etc parsing routine ?
  79. *)
  80. PROCEDURE Get1Parameter(TheStr : ARRAY OF CHAR; TheSep : CHAR;
  81. TheNum : CARDINAL; VAR ThePar : ARRAY OF CHAR) : BOOLEAN;
  82. VAR
  83. AtPos, Postpos, AtNo, len : CARDINAL;
  84. BEGIN
  85. len := M2Strings.Length(TheStr);
  86. (* search for separator number TheNum-1 *)
  87. AtNo := 1;
  88. AtPos := 0;
  89. WHILE (AtPos<len) AND (AtNo<TheNum) DO
  90. INC(AtNo);
  91. AtPos := PosUtils.Positn(TheSep,TheStr,AtPos)+1;
  92. END;
  93. IF (AtNo<TheNum) OR (AtPos>=len) THEN
  94. (* didn't find it *)
  95. RETURN FALSE;
  96. ELSE
  97. (* found it, so get the parameter *)
  98. IF AtPos>=len THEN
  99. StrEdit.SetLength(ThePar, 0);
  100. ELSE
  101. Postpos := PosUtils.Positn(TheSep,TheStr,AtPos);
  102. IF Postpos<len THEN
  103. M2Strings.Copy(TheStr, AtPos, Postpos-AtPos, ThePar);
  104. ELSE
  105. (* Postpos may be greater than len, so to be on the safe side: *)
  106. M2Strings.Copy(TheStr, AtPos, len-AtPos, ThePar);
  107. END;
  108. END;
  109. RETURN TRUE;
  110. END;
  111. (* shouldn't get here *)
  112. HALT();
  113. END Get1Parameter;
  114. PROCEDURE CompareCriteria(e1 : SYSTEM.ADDRESS; l1 : CARDINAL; e2 : SYSTEM.ADDRESS;
  115. l2 : CARDINAL) : INTEGER;
  116. (* Compare routine for SortList. It is assumed here that
  117. (1) the elements to be sorted are themselves lists of equal length, and
  118. (2) there is at least one spot in the sublists with unequal elements of
  119. same type.
  120. These conditions are guaranteed by InitCriteriaList(...) below.
  121. *)
  122. VAR
  123. count, spot : CARDINAL;
  124. maxcount : CARDINAL;
  125. RdAd1, RdAd2 : SYSTEM.ADDRESS;
  126. GL1, GL2 : GenLists.GenList;
  127. GLP1, GLP2 : POINTER TO GenLists.GenList;
  128. Typ1, Typ2 : CARDINAL;
  129. Siz1, Siz2 : CARDINAL;
  130. Reslt : INTEGER;
  131. Str1, Str2 : String;
  132. BEGIN
  133. (* Note that it is not necessary to EXIT from the outer LOOP,
  134. the inner LOOP shall always RETURN (see conditions above).
  135. *)
  136. spot := 0;
  137. LOOP
  138. INC(spot);
  139. (* would have been nice to dereference directly through a construct
  140. like GLPType(e1)^, but Logitech won't take that one. Even more
  141. elegant of course would have been to declare e1 and e2 NOT as
  142. ADDRESS, but as POINTER TO GenList directly. Why couldn't a variable
  143. of type ADDRESS be compatible with any pointer type?
  144. Well, programming probably never was meant to be easy. Sigh ..
  145. *)
  146. GLP1 := e1;
  147. GL1 := GLP1^;
  148. Typ1 := TypeOf(GL1,spot);
  149. GLP2 := e2;
  150. GL2 := GLP2^;
  151. Typ2 := TypeOf(GL2,spot);
  152. IF Typ1=Typ2 THEN
  153. (* should I compare apples and oranges ? *)
  154. IF Typ1=GenLists.StrCode THEN
  155. GenLists.GetElmt(GL1, spot, Str1, Typ1);
  156. GenLists.GetElmt(GL2, spot, Str2, Typ2);
  157. Reslt := M2Strings.CompareStr(Str1,Str2);
  158. IF Reslt#0 THEN
  159. RETURN Reslt;
  160. END;
  161. ELSE
  162. (* PMI! I never sort on anything else than strings,
  163. so this part of the code has probably never been
  164. executed.
  165. *)
  166. GenLists.GetElmtAdr(GL1, spot, RdAd1, Siz1, Typ1);
  167. GenLists.GetElmtAdr(GL2, spot, RdAd2, Siz2, Typ2);
  168. (* block compare could come in handy here, but the one found in
  169. Logitech's block ops only returns true/false. Silly *)
  170. count := 0;
  171. maxcount := Siz1;
  172. IF Siz2>Siz1 THEN
  173. maxcount := Siz2;
  174. END;
  175. (* this is probably a good place to turn off run-time checks *)
  176. LOOP
  177. INC(count);
  178. IF count>maxcount THEN
  179. EXIT;
  180. END;
  181. IF CARDINAL(RdAd1^) > CARDINAL(RdAd2^) THEN
  182. RETURN -1;
  183. ELSIF CARDINAL(RdAd1^) < CARDINAL(RdAd2^) THEN
  184. RETURN 1;
  185. END;
  186. LowLevel.IncAddr( RdAd1, 1 );
  187. LowLevel.IncAddr( RdAd2, 1 );
  188. END;
  189. END;
  190. (* EXITed, elements equal so far. test lengths. *)
  191. IF Siz1>Siz2 THEN
  192. RETURN -1;
  193. ELSIF Siz1<Siz2 THEN
  194. RETURN 1;
  195. END;
  196. END;
  197. (* IF types equal *)
  198. (* proceed to next element in the two lists *)
  199. END;
  200. (* LOOP *)
  201. (* impossible to get here *)
  202. HALT();
  203. END CompareCriteria;
  204. (* routine to decode the string with sort fields and optionally lengths *)
  205. PROCEDURE DecodeFieldStr(TheFile : NdxTypes.NdxFileType; FieldStr : ARRAY OF
  206. CHAR; VAR FieldList : GenLists.GenList);
  207. (* Give this routine an NdxFile and a string containing names of
  208. fields in this file, the routine will exit with FieldList containing
  209. a list of the position of each field in the genlist that holds the
  210. file's data record. Fields that are not in the file are ignored.
  211. The names in the FieldStr are separated by blank(s). Each name may
  212. optionally be followed by parameters, separated from the name and from
  213. each other by comma. The first parameter signifies the maximum
  214. number of bytes used for keeping the copy of that field in the criteria
  215. list, default is DefMaxSize. Subsequent parameters are (currently)
  216. ignored, but could easily be stored in the FieldStr to be used
  217. while building the criteria list.
  218. Execution speed has not been a concern for the implementation.
  219. *)
  220. CONST
  221. FieldSep = ' ';
  222. ParSep = ',';
  223. VAR
  224. AFieldName, AString : String;
  225. FieldPart, NumbrPart : String;
  226. MaxPos, p, dummy, AType : CARDINAL;
  227. found : BOOLEAN;
  228. BEGIN
  229. StrEdit.CrunchBlanks(FieldStr);
  230. StrEdit.CAPstr(FieldStr);
  231. p := 0;
  232. LOOP
  233. (* on all fields in the input string *)
  234. INC(p);
  235. IF NOT Get1Parameter(FieldStr,FieldSep,p,AString) THEN
  236. EXIT;
  237. END;
  238. (* look into the substructure of this parameter *)
  239. IF Get1Parameter(AString,ParSep,1,FieldPart) AND (M2Strings.Length(
  240. FieldPart)>0) THEN
  241. IF Get1Parameter(AString,ParSep,2,NumbrPart) AND
  242. StrConv.StrToCardinal(NumbrPart,0,MaxPos) AND (MaxPos>0) THEN
  243. IF MaxPos>255 THEN
  244. MaxPos := 255;
  245. END;
  246. ELSE
  247. MaxPos := DefMaxPos;
  248. END;
  249. (* is this a field of the file ? Could use Get1Parameter on the
  250. structure string instead of relying on internal details of the
  251. NdxFile-record.
  252. *)
  253. GenLists.GetElmt(TheFile^.StructLst, 1, AFieldName, AType);
  254. LOOP
  255. IF M2Strings.CompareStr(FieldPart,AFieldName)=0 THEN
  256. found := TRUE;
  257. EXIT;
  258. ELSIF GenLists.ElmtNow(TheFile^.StructLst)>=GenLists.ListLength(TheFile^.
  259. StructLst) THEN
  260. found := FALSE;
  261. EXIT;
  262. END;
  263. GenLists.NextElmt(TheFile^.StructLst, 1, AFieldName, AType);
  264. END;
  265. (* LOOP on structure list *)
  266. IF found THEN
  267. (* append the position and the max size *)
  268. dummy := GenLists.ElmtNow(TheFile^.StructLst);
  269. GenLists.ListInsert(dummy, 0, FieldList, AFTERLAST);
  270. GenLists.ListInsert(MaxPos, 0, FieldList, AFTERLAST);
  271. END;
  272. END;
  273. (* IF FieldPart not empty *)
  274. END;
  275. (* LOOP on the input string *)
  276. END DecodeFieldStr;
  277. PROCEDURE InitCriteriaList(TheNdxFile : NdxTypes.NdxFileType; TheIndexes :
  278. GenLists.GenList; FldNums : GenLists.GenList; VAR into : GenLists.GenList);
  279. (* Initialize the "into" criteria list for those records in TheNdxFile
  280. that are selected by TheIndexes. For each record, use the fields
  281. that are indicated by the FldNums list.
  282. NB. To keep NewList and DisposeList on the same program level (easier to
  283. verify correctness), it is left to the calling program to do a
  284. NewList(into) before calling InitCriteriaList.
  285. The "into" GenList is structured as a list of lists, where each sublist
  286. corresponds to a record in TheNdxFile. The elements of each sublist contain
  287. values fetched from fields in each data record, and FldNums specifies which
  288. data fields shall enter as criterion.
  289. In addition, the next-to-last element of each sublist contains the original
  290. sequence number, this guarantees that the sort is stable, and makes the
  291. compare routine easier (will always find two elements that are unequal).
  292. The last element of the sublist contains a copy of the original record
  293. key. It is not used during the sort but is copied back into the output
  294. (sorted) list after the sort is finished..
  295. *)
  296. VAR
  297. AType : CARDINAL;
  298. SubList : GenLists.GenList;
  299. AListPtr : POINTER TO GenLists.GenList;
  300. TheRec : GenLists.GenList;
  301. ReadAddr : SYSTEM.ADDRESS;
  302. TheSize, MaxSiz : CARDINAL;
  303. TheNum : CARDINAL;
  304. IndexNo : CARDINAL;
  305. AnIndex : NdxTypes.RecNameStr;
  306. AnNdxElmt : NdxTypes.NdxElement;
  307. TypeOK : BOOLEAN;
  308. BEGIN
  309. IF GenLists.ListLength(FldNums)=0 THEN
  310. RETURN;
  311. END;
  312. IndexNo := 0;
  313. (* Loop on the selected records in TheNdxFile *)
  314. WHILE IndexNo < GenLists.ListLength(TheIndexes) DO
  315. INC(IndexNo);
  316. TypeOK := TRUE;
  317. CASE TypeOf(TheIndexes,IndexNo) OF
  318. NdxTypes.NdxTypeCode :
  319. GenLists.GetElmt(TheIndexes, IndexNo, AnNdxElmt, AType);
  320. StrEdit.AssignStr(AnNdxElmt.RecName, AnIndex);
  321. | GenLists.StrCode :
  322. GenLists.GetElmt(TheIndexes, IndexNo, AnIndex, AType);
  323. ELSE
  324. TypeOK := FALSE;
  325. END;
  326. IF NOT TypeOK THEN
  327. ErrorManager.WARN('Improper type in TheIndexes in NdxSort.InitCriteriaList');
  328. ELSIF NOT NdxFiles.RecordExists(TheNdxFile,AnIndex) THEN
  329. (* that's ok, no such record, not much to be done then *)
  330. ELSIF NOT NdxFiles.GetField(TheNdxFile,AnIndex,'RECORD',AType,TheRec) THEN
  331. (* ? *)
  332. ErrorManager.WARN('Corrupted RECORD in NdxSort.InitCriteriaList');
  333. ELSIF AType#GenLists.ListCode THEN
  334. ErrorManager.WARN('Corrupted GetField in NdxSort.InitCriteriaList');
  335. ELSE
  336. (* Move each sort field into the sublist. *)
  337. GenLists.NewList(SubList);
  338. (* note structure of FldNums: REPEAT(fieldno maxfieldsize) *)
  339. GenLists.GetElmt(FldNums, 1, TheNum, AType);
  340. GenLists.NextElmt(FldNums, 1, MaxSiz, AType);
  341. (* could put this into loop *)
  342. LOOP
  343. (* on each selected field *)
  344. GenLists.GetElmtAdr(TheRec, TheNum, ReadAddr, TheSize, AType);
  345. (* It doesn't make much sense to compare lists as addresses.
  346. So if the field is a list, use instead the first element of the
  347. list, and if that element is a list, use the first element, etc,
  348. etc, ... ad nauseatum
  349. *)
  350. LOOP
  351. IF AType#GenLists.ListCode THEN
  352. EXIT;
  353. END;
  354. AListPtr := ReadAddr;
  355. IF GenLists.ListLength(AListPtr^)=0 THEN
  356. EXIT;
  357. END;
  358. (* retry with the first elmt of the list *)
  359. GenLists.GetElmtAdr(AListPtr^, 1, ReadAddr, TheSize, AType);
  360. END;
  361. GenLists.ListInsertAdr(ReadAddr, TheSize, AType, SubList, AFTERLAST);
  362. IF GenLists.ElmtNow(FldNums)>=GenLists.ListLength(FldNums) THEN
  363. EXIT;
  364. END;
  365. GenLists.NextElmt(FldNums, 1, TheNum, AType);
  366. GenLists.NextElmt(FldNums, 1, MaxSiz, AType);
  367. END;
  368. (* append to SubList the original position number in the main list *)
  369. GenLists.ListInsert(IndexNo, 0, SubList, AFTERLAST);
  370. (* append the record index to the sublist *)
  371. GenLists.ListInsert(AnIndex, GenLists.StrCode, SubList, AFTERLAST);
  372. (* finally append the sublist to the main list *)
  373. (* note that in 1.4c, the sublist is absorbed into the main list,
  374. so the sublist should not be disposed afterwards
  375. *)
  376. GenLists.ListInsert(SubList, GenLists.ListCode, into, AFTERLAST);
  377. END;
  378. (* IF index is proper type AND record found AND has proper type *)
  379. END;
  380. (* LOOP on all selected records *)
  381. END InitCriteriaList;
  382. PROCEDURE RefillSortedList(frm : GenLists.GenList; VAR into : GenLists.GenList);
  383. VAR
  384. AType : CARDINAL;
  385. AList : GenLists.GenList;
  386. AString : String;
  387. BEGIN
  388. (* dispose of the original key sequence found in SortedList *)
  389. GenLists.DisposeList(into);
  390. GenLists.NewList(into);
  391. (* initialize loop with first element of list *)
  392. IF GenLists.ListLength(frm)=0 THEN
  393. RETURN;
  394. END;
  395. GenLists.GetElmt(frm, 1, AList, AType);
  396. LOOP
  397. IF AType#GenLists.ListCode THEN
  398. ErrorManager.WARN('Corrupted frm in NdxSort.RefillSortedList');
  399. ELSE
  400. (* index is in the last element of each (list) element of the frm list*)
  401. GenLists.GetElmt(AList, GenLists.ListLength(AList), AString, AType);
  402. IF AType#GenLists.StrCode THEN
  403. ErrorManager.WARN('Corrupted frm.sublist in NdxSort.RefillSortedList');
  404. ELSE
  405. GenLists.ListInsert(AString, AType, into, AFTERLAST);
  406. END;
  407. END;
  408. IF GenLists.ElmtNow(frm)>=GenLists.ListLength(frm) THEN
  409. EXIT;
  410. END;
  411. GenLists.NextElmt(frm, 1, AList, AType);
  412. END;
  413. (* LOOP *)
  414. END RefillSortedList;
  415. PROCEDURE AltNdxSort(TheFile : NdxTypes.NdxFileType; TheFields : ARRAY OF CHAR;
  416. VAR TheRecordList : GenLists.GenList);
  417. (* Finally, the main driver routine. *)
  418. VAR
  419. FieldNumbers, CriteriaList : GenLists.GenList;
  420. BEGIN
  421. (* NdxSort *)
  422. IF NOT GenLists.Initialized(TheRecordList) THEN
  423. ErrorManager.WARN(' Unitialized "TheRecordList" in call to NdxSort');
  424. GenLists.NewList(TheRecordList);
  425. END;
  426. (* Decode the string with the list of fields to sort on *)
  427. GenLists.NewList(FieldNumbers);
  428. DecodeFieldStr(TheFile, TheFields, FieldNumbers);
  429. (* Initialize the list of sort criteria *)
  430. GenLists.NewList(CriteriaList);
  431. IF GenLists.ListLength(TheRecordList)>0 THEN
  432. (* TheRecordList contains the list of ptrs to selected records *)
  433. InitCriteriaList(TheFile, TheRecordList, FieldNumbers,
  434. CriteriaList);
  435. ELSE
  436. (* otherwise, sort the whole file *)
  437. InitCriteriaList(TheFile, TheFile^.Ndx, FieldNumbers,
  438. CriteriaList);
  439. END;
  440. (* Sort the criteria list *)
  441. GenLists.SortList(CriteriaList, CompareCriteria);
  442. (* dispose and refill the original list of record keys *)
  443. RefillSortedList(CriteriaList, TheRecordList);
  444. (* finally do away with all temporary lists *)
  445. GenLists.DisposeList(CriteriaList);
  446. GenLists.DisposeList(FieldNumbers);
  447. END AltNdxSort;
  448. BEGIN
  449. Initialized := FALSE;
  450. Init();
  451. END NdxSort.