home *** CD-ROM | disk | FTP | other *** search
/ ftp.robelle3000.ai 2014 / 2014.06.ftp.robelle3000.ai.tar / ftp.robelle3000.ai / papers / sd.pro < prev    next >
Text File  |  1995-08-31  |  70KB  |  2,097 lines

  1. .comment    File:     SD
  2. .comment
  3. .comment    Purpose:  Adopting Self-Describing Files
  4. .comment
  5. .comment  To print this document, do the following:
  6. .comment
  7. .comment    :run printdoc.pub.robelle;info="SD"
  8. .comment
  9. .com {ifout starts here}
  10. .comment  Choose the output device parameters for this document
  11. .if outfinal
  12. .  if outrecord
  13. .    out(las 30 c1 r+ s7).com Robelle bound version, attached
  14. .  else
  15. .    out(las 30 c1 r- s7).com Robelle bound version
  16. .  endif
  17. .elseif outhelpcomp
  18. .  out(lpt q+ w80).com helpcomp;parm=1
  19. .elseif outa4
  20. .  if outlpt
  21. .    if outrecord
  22. .      out(lpt s7 r+).com A4 paper, $Stdlist/LP/disc, attached
  23. .    else
  24. .      out(lpt s7 r-).com A4 paper, $Stdlist/LP/disc
  25. .    endif
  26. .  elseif outlaser
  27. .    if outrecord
  28. .      if outdouble
  29. .        out(las 30 s8 r+ d+).com A4 paper, LaserJet, attached, duplex
  30. .      else
  31. .        out(las 30 s8 r+).com A4 paper, LaserJet, attached
  32. .      endif
  33. .    else
  34. .      if outdouble
  35. .        out(las 30 s8 r- d+).com A4 paper, LaserJet, duplex
  36. .      else
  37. .        out(las 30 s8 r-).com A4 paper, LaserJet
  38. .      endif
  39. .    endif
  40. .  else.com No other outxxx jcws specified
  41. .    out(lpt s7 r-).com generic: A4 paper, $Stdlist/LP/disc
  42. .  endif
  43. .elseif outtext
  44. .  if outrecord
  45. .    out(lpt s0 u- r+)
  46. .  else
  47. .    out(lpt s0 u-)
  48. .  endif
  49. .else
  50. .  if outlpt
  51. .    if outrecord
  52. .      out(lpt s3 r+).com Letter, $Stdlist/LP/disc, attached
  53. .    else
  54. .      out(lpt s3 r-).com Letter, $Stdlist/LP/disc
  55. .    endif
  56. .  elseif outlaser
  57. .    if outrecord
  58. .      if outdouble
  59. .        out(las 30 s7 r+ d+).com Letter, LaserJet, attached, duplex
  60. .      else
  61. .        out(las 30 s7 r+).com Letter, LaserJet, attached
  62. .      endif
  63. .    else
  64. .      if outdouble
  65. .        out(las 30 s7 r- d+).com Letter, LaserJet, duplex
  66. .      else
  67. .        out(las 30 s7 r-).com Letter, LaserJet
  68. .      endif
  69. .    endif
  70. .  else.com No outxxx jcws specified
  71. .    out(lpt s3 r-).com Letter, generic: $Stdlist/LP/disc
  72. .  endif
  73. .endif
  74. .comment
  75. .comment  Choose the fonts for this document
  76. .if outfinal
  77. .  include final.qlibdata.green
  78. .else
  79. .  include f92286f.qlibdata.robelle
  80. .endif
  81. .comment
  82. .comment  Choose the margins for this document
  83. .comment
  84. .if outhelpcomp
  85. .  mar(r78)
  86. .elseif outa4
  87. .  mar(r62)
  88. .else
  89. .  mar(r65)
  90. .endif
  91. .comment
  92. .comment  Choose the forms for this document
  93. .comment
  94. .comment  Form 1 is for the body of the manual
  95. .comment  Form 2 is for unnumbered pages (end of section)
  96. .comment  Form 3 is for the table of contents (roman numerals)
  97. .comment
  98. .if outtext
  99. .   form(k1 [L8000] )
  100. .   form(k2 [L8000] )
  101. .   form(k3 [L8000] )
  102. .elseif outa4
  103. .   form(k1 [ T #23 S:40 // l58 / #33 "-" pn:1 "-" /]
  104. +           [ S #23 T:40 // l58 / #33 "-" pn:1 "-" /])
  105. .   form(k2 [ // l58 / #33 pr:3 /]).com Roman numerals
  106. .   form(k3 [ // l57 // ])
  107. .else
  108. .   form(k1 [ T #26 S:40 // l55 / #33 "-" pn:1 "-" /]
  109. +           [ S #26 T:40 // l55 / #33 "-" pn:1 "-" /])
  110. .   form(k2 [ // l55 / #33 pr:3 /]).com Roman numerals
  111. .   form(k3 [ // l54 // ])
  112. .endif
  113. .comment
  114. .comment  Option settings:
  115. .comment
  116. .comment   For text output we want a ragged right edge.
  117. .comment
  118. .if outtext
  119. .   opt(j4 p+ b+ r-)
  120. .else
  121. .   opt(j4 p+ b+).com     Okay to insert four blanks between words
  122. .comment                  when justifying, two spaces after period,
  123. .comment                  suppress blank lines at top of page
  124. .endif
  125. .comment
  126. .comment  Other formatting parameters:
  127. .par(f` p5 s1 u3)      .com  Automatic .skip 1.page 5.undent 3
  128. .inp(u~ b@ h\ e& t# f|).com   Underline,Blank,Hyphen,Escape,Tab,Font
  129.  
  130. .skip 5
  131. .opt(l- r- f-)
  132. |3Adopting Self-Describing Files|
  133.  
  134.  
  135. |1By David J.@Greer|
  136.  
  137.  
  138. |1Robelle Consulting Ltd.|
  139. Unit 201, 15399-102A Ave.
  140. Surrey, B.C.@Canada  V3R 7K1
  141. Phone:@@(604) 582-1700
  142. Fax:@@(604) 582-1799
  143. http://www.robelle.com
  144. .font 0.opt.skip 2
  145.  
  146.  
  147. .opt(l- r- f-)
  148. `|3Abstract|
  149. .opt.skip 1
  150.  
  151. Query can generate output to self-describing (SD) files, but no
  152. HP products read these files except the old DSG and Listkeeper
  153. products. A self-describing file is a data file which stores a
  154. standard description of its own record format in its user labels.
  155. Thus, it is like a little stand-alone database (a trendy
  156. developer might call this object-oriented). If two software tools
  157. understand SD files, it becomes trivial to transfer data between
  158. them. A user can archive some data in an SD file and when it is
  159. restored five years later the SD file can tell what the data
  160. means. The author describes the internal format of SD files,
  161. gives examples on how to read and write SD files, and describes
  162. problems integrating SD files into a software tool.
  163.  
  164.  
  165. .skip 5.opt(l- r- f-)
  166. Copyright Robelle Consulting Ltd.@1992
  167. .opt
  168.  
  169.  
  170. Permission is granted to reprint this document (but ~not~ for
  171. profit), provided that copyright notice is given.
  172. .com                         The next FORM is for Contents/Preface:
  173. .page.for([ // l55 / #33 pr:3 /]).com Roman numerals
  174. .com                         The next FORM is for the body of manual:
  175. .if outdouble
  176. .page
  177.  
  178. .endif
  179. .for([ T #26 S:40 // l55 / #33 "-" pn:1 "-" /]
  180. +    [ S #26 T:40 // l55 / #33 "-" pn:1 "-" /])
  181. .page.count 1          .com  Start page numbering over again.
  182. .contents(i+3)
  183. .tit
  184. .page.sub
  185.  
  186. .skip 3.opt(l- r- f-)
  187. `|3Introduction|
  188. .opt.skip 2
  189. .tit Adopting Self-Describing Files
  190. .ent `Introduction
  191.  
  192. For years, Query's Save Command has been able to create a file that is
  193. self-describing.  A self-describing file is one that contains the
  194. information about the fields in the file. Normal MPE and KSAM
  195. files are not self-describing. In general, we know nothing about
  196. the structure of the fields in each record.
  197.  
  198. Unfortunately, few software tools create or understand
  199. self-describing files.  While Query can produce self-describing
  200. files, it cannot use them as input. Our product Suprtool can both
  201. create and understand self-describing files (including KSAM ones).  In
  202. addition, Suprtool has a new self-describing format that removes
  203. some restrictions of the original self-describing structure.  In
  204. this article we will do the following:
  205. .mar(l+3)
  206. `o#4Describe the format of both the original self-describing file
  207. (this will be a summary of the information in Appendix E of the
  208. Query User Manual) and the new Robelle self-describing file.
  209. `o#4Show how to create a self-describing file.
  210. `o#4Give a programming example that can understand and provide
  211. a "form" listing of any self-describing file.
  212. `o#4Describe KSAM self-describing files.
  213. `o#4Speculate on what an "open system" self-describing file
  214. would look like.
  215. .mar
  216.  
  217. .sub |1Query versus Robelle|
  218. `|1Query Versus Robelle Self-Describing Files|
  219.  
  220. Throughout this article we will refer to one of two types of
  221. self-describing files.  The first kind are equivalent to the
  222. ones produced by Query.  They are identified by the version
  223. number "@A.00.00".  The second kind were designed to overcome
  224. limitations with the original self-describing format.  We identify
  225. the revised files by calling them Robelle self-\describing files.  They
  226. have a version number of "@B.00.00".
  227.  
  228. .sub |1Examples In SPL|
  229. `|1Examples In This Article|
  230.  
  231. Because we write code in SPL and SPLash!, we will give our
  232. examples in these programming languages.  The only word of
  233. caution is to remember that SPL uses zero-based addressing
  234. for all its arrays.
  235.  
  236. .sub |1MPE User Labels|
  237. `|1MPE User Labels|
  238.  
  239. User labels are an optional part of an MPE file. User labels are
  240. part of the file, but they are not part of the data (i.e., when
  241. reading the records in the file the user labels are ignored).
  242. User labels are a handy place to store extra information about a
  243. file. Unfortunately, MS-Dos and UNIX have no concept similar to
  244. MPE's user labels
  245. (see the section |2Future Self-Describing Formats| for ideas
  246. for UNIX and MS-Dos).
  247.  
  248. The number of user labels must be specified when the file is
  249. created. On most versions of MPE, the only way to create a file
  250. with user labels is via the FOPEN intrinsic. Newer versions of
  251. MPE/iX allow the ULABEL= keyword on the Build Command to specify
  252. the number of user labels. Each user label is 256 bytes long and
  253. user labels are numbered from zero. You access user labels by
  254. calling the FREADLABEL and FWRITELABEL intrinsics.
  255. .tit
  256. .page.sub
  257.  
  258. .skip 3.opt(l- r- f-)
  259. `|3Identifying Self-Describing Files|
  260. .opt.skip 2
  261. .tit Identifying
  262. .ent `Identifying Self-Describing Files
  263.  
  264. An MPE file that is self-describing has a filecode of 1084.  You
  265. will recognize these files by seeing "SD" next to the filecode of
  266. a :listf,2:
  267.  
  268. .font 5.opt(f-)
  269. FILENAME  CODE  ----------LOGICAL RECORD---------  ----SPACE----
  270.                   SIZE  TYP    EOF      LIMIT R/B  SECTORS #X MX
  271.  
  272. LOADFILE  SD      128W  FB      33      10000  35      256  1  *
  273. .font 0.opt
  274.  
  275. Recognizing self-describing KSAM files is more difficult.  KSAM
  276. sd-files do not have a special file code.  Instead, you must look
  277. for a KSAM file with extra file labels. On MPE/iX, this is done
  278. with a :listf ,3 (on MPE V/E use Listdir.Pub.Sys):
  279.  
  280. .sub |1Self-Describing Files|
  281. `|1What Is A Self-Describing File|
  282.  
  283. A self-describing file stores information in the MPE file
  284. labels about the fields in each record of the file.
  285. File labels are like a special file within a file.  An MPE file
  286. label is 256 bytes long and an MPE file is created with 0 to
  287. 256 file labels.  The file labels are accessed via the Freadlabel
  288. and Fwritelabel Intrinsic.
  289. User labels are numbered from zero.
  290.  
  291. Customarily, tools that create self-describing files leave
  292. the first ten file labels (numbered 0 to 9) for user
  293. applications.  The self-describing information is broken into
  294. two kinds of labels:  the header label and field labels.
  295.  
  296. .if outlaser then
  297. .page 20
  298. .skip 1
  299.      0-9 #27 Filler labels
  300. .skip 3
  301. #31|1.|
  302. .break
  303. #31|1.|
  304. .break
  305. #31|1.|
  306. .skip 2
  307.      n-3 #27 Second field label
  308. .skip 2
  309.      n-2 #27 First field label
  310. .skip 2
  311.      n-1 #27 Header label
  312. .skip 1
  313. .com Next 4 .pcl's:  PBOX 144.01 +525 -925 900 150 2 0 0 1 50
  314. .pcl~&f0S~*p+525x-925Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
  315. .pcl~*p-900x+150Y~*c902a2B~*c0P
  316. .pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
  317. .pcl~*c1a150B~*c0P~&f1S
  318. .com Next 4 .pcl's:  PBOX 144.11 +525 -775 900 300 2 0 0 1 50
  319. .pcl~&f0S~*p+525x-775Y~*c900a2B~*c0P~*c2a300B~*c0P~*p+900X~*c0P
  320. .pcl~*p-900x+300Y~*c902a2B~*c0P
  321. .pcl~*p+1X~*c900a1B~*c50g0P~*p-300y-1X~*p+1y+900X
  322. .pcl~*c1a300B~*c0P~&f1S
  323. .com Next 4 .pcl's:  PBOX 144.21 +525 -475 900 150 2 0 0 1 50
  324. .pcl~&f0S~*p+525x-475Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
  325. .pcl~*p-900x+150Y~*c902a2B~*c0P
  326. .pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
  327. .pcl~*c1a150B~*c0P~&f1S
  328. .com Next 4 .pcl's:  PBOX 144.31 +525 -325 900 150 2 0 0 1 50
  329. .pcl~&f0S~*p+525x-325Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
  330. .pcl~*p-900x+150Y~*c902a2B~*c0P
  331. .pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
  332. .pcl~*c1a150B~*c0P~&f1S
  333. .com Next 4 .pcl's:  PBOX 144.41 +525 -175 900 150 2 0 0 1 50
  334. .pcl~&f0S~*p+525x-175Y~*c900a2B~*c0P~*c2a150B~*c0P~*p+900X~*c0P
  335. .pcl~*p-900x+150Y~*c902a2B~*c0P
  336. .pcl~*p+1X~*c900a1B~*c50g0P~*p-150y-1X~*p+1y+900X
  337. .pcl~*c1a150B~*c0P~&f1S
  338. .endif.com if not outlaser
  339.  
  340. .sub |1Header Label|
  341. `|1Header Label|
  342.  
  343. If an MPE file has n file labels, they are numbered from 0 to
  344. n-1.  The self-describing labels are always added at the end of the
  345. any file labels needed by the user.  The last file label will be the
  346. sd-header label
  347. and the sd-field labels are arranged backwards from
  348. this label (n-2, n-3, ...).  The format of the header label is
  349. similar, but different for Query and Robelle self-describing
  350. files.
  351. .skip 1
  352. .sub |1Query Header Label|
  353. `|1Query Header Label|
  354.  
  355. The Query header label consists of the following fields:
  356. .mar(l+3)
  357. `|1version (X8).|@@Always equal to "@A.00.00" for Query
  358. self-\describing files.
  359. `|1length (J1).|@@The length of each record in the file in bytes.
  360. It appears to always
  361. be identical to the MPE record length of the file.
  362. `|1#fields (J1).|@@The number of fields in each file record.
  363. `|1#labels (J1).|@@Number of labels used for field descriptions
  364. plus one for the header label.
  365. This is different than the number of MPE labels for the file.
  366. `|1fields'per'label (J1).|@@Each field label contains one or more
  367. field descriptions. Do not assume a fixed number for this field
  368. -- you must check the value of this field.
  369. `|1size (J1).|@@Length of each field descriptor in 16-bit words.
  370. Because MPE file labels are always 128 words long, the
  371. |1fields'per'label| should always be 128 / |1size|.  Again, do not
  372. assume a fixed constant for the field descriptor size.
  373. .mar
  374.  
  375. .sub |1Robelle Header Label|
  376. `|1Robelle Header Label|
  377.  
  378. The Robelle header label contains all of the fields of Query's
  379. header label with one change (the version number is different)
  380. and three additions:
  381. .mar(l+3)
  382. `1.#4The version number is "@B.00.00" instead of "@A.00.00" (note
  383. the space at the beginning).
  384. `2.#4There are three new fields for handling sort keys.
  385. These fields are identical to the fields that you
  386. would pass to Sortinit (in compatibility-mode):
  387. .mar(l+3).par(p1)
  388. `|1sort'max'keys (J1)|.@@Maximum number of keys allowed in this
  389. sd-file.  The |1sort'keys| would be declared as:
  390. .skip 1
  391. |5       integer array sort'keys(0:sort'max'keys*3-1)|
  392. `|1sort'num'keys (J1)|.@@The actual number of keys in the table. This
  393. value must range from zero to |1sort'max'keys-1|.
  394. `|1sort'keys|.@@The sort keys themselves using the same
  395. conventions as Sort/3000.  The byte-offsets of each key start
  396. at one and not zero in the sort table.  The byte offsets in
  397. each field entry remain the same (i.e., zero-based instead of
  398. one-based offsets). The sort key types correspond to those for
  399. the Sortinit intrinsic and not the newer HPSortinit.
  400. .mar.par
  401. .mar
  402.  
  403. .sub |1Header Label|
  404. `|1SPL Layout of the Header Label|
  405.  
  406. Here is the layout in SPL notation of the Robelle header label.
  407. Note that we use exactly the same layout for accessing Query
  408. header labels (we just ignore all sd'sort'...@variables when
  409. accessing Query self-describing files).
  410. Each field descriptor is fifteen words long, but even the
  411. Robelle field descriptor only uses fourteen words.  We leave
  412. the last word unspecified (our code always sets the filler words
  413. with binary zeroes):
  414.  
  415. .font 5.opt(f-)
  416.     sdheader.srcinc:
  417.  
  418.     integer array sd'header(0:sd'label'len);   { 0 : 127 }
  419.     byte    array sd'version(*)        = sd'header;
  420.     integer array sd'reclength(*)      = sd'header(4);
  421.     integer array sd'numfields(*)      = sd'header(5);
  422.     integer array sd'numlabels(*)      = sd'header(6);
  423.     integer array sd'fieldsperlabel(*) = sd'header(7);
  424.     integer array sd'entrylen(*)       = sd'header(8);
  425.     integer array sd'sort'max'keys(*)  = sd'header(9);
  426.     integer array sd'sort'num'keys(*)  = sd'header(10);
  427.     integer array sd'sort'keys(*)      = sd'header(11);
  428. .font 0.opt
  429.  
  430. .sub |1Field Labels|
  431. `|1Field Labels|
  432.  
  433. Every self-describing file has one or more field labels of 256 bytes.
  434. Each field label has one or more field descriptors.
  435. The first fields in the file will be described in label N-2, the
  436. next set of fields in N-3, and so on.  This is opposite to what
  437. you might expect.
  438. Self-describing files that Query produces have eight field
  439. descriptors per user label.
  440.  
  441. .if outlaser then
  442. .page 25
  443. .opt(l- r- f-)
  444. Picture of one field label
  445. .opt
  446. .skip 4
  447. .font 1
  448. #9 1 #15 2 #21 3 #27 4 #33 5 #39 6 #45 7 #51 8
  449. .font 0
  450. .com Next 4 .pcl's:  PBOX 3 +180 -180 180 180 2 0 0 1 50
  451. .pcl~&f0S~*p+180x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  452. .pcl~*p-180x+180Y~*c182a2B~*c0P
  453. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  454. .pcl~*c1a180B~*c0P~&f1S
  455. .com Next 4 .pcl's:  PBOX 8 +360 -180 180 180 2 0 0 1 50
  456. .pcl~&f0S~*p+360x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  457. .pcl~*p-180x+180Y~*c182a2B~*c0P
  458. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  459. .pcl~*c1a180B~*c0P~&f1S
  460. .com Next 4 .pcl's:  PBOX 13 +540 -180 180 180 2 0 0 1 50
  461. .pcl~&f0S~*p+540x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  462. .pcl~*p-180x+180Y~*c182a2B~*c0P
  463. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  464. .pcl~*c1a180B~*c0P~&f1S
  465. .com Next 4 .pcl's:  PBOX 18 +720 -180 180 180 2 0 0 1 50
  466. .pcl~&f0S~*p+720x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  467. .pcl~*p-180x+180Y~*c182a2B~*c0P
  468. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  469. .pcl~*c1a180B~*c0P~&f1S
  470. .com Next 4 .pcl's:  PBOX 23 +900 -180 180 180 2 0 0 1 50
  471. .pcl~&f0S~*p+900x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  472. .pcl~*p-180x+180Y~*c182a2B~*c0P
  473. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  474. .pcl~*c1a180B~*c0P~&f1S
  475. .com Next 4 .pcl's:  PBOX 28 +1080 -180 180 180 2 0 0 1 50
  476. .pcl~&f0S~*p+1080x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  477. .pcl~*p-180x+180Y~*c182a2B~*c0P
  478. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  479. .pcl~*c1a180B~*c0P~&f1S
  480. .com Next 4 .pcl's:  PBOX 33 +1260 -180 180 180 2 0 0 1 50
  481. .pcl~&f0S~*p+1260x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  482. .pcl~*p-180x+180Y~*c182a2B~*c0P
  483. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  484. .pcl~*c1a180B~*c0P~&f1S
  485. .com Next 4 .pcl's:  PBOX 38 +1440 -180 180 180 2 0 0 1 50
  486. .pcl~&f0S~*p+1440x-180Y~*c180a2B~*c0P~*c2a180B~*c0P~*p+180X~*c0P
  487. .pcl~*p-180x+180Y~*c182a2B~*c0P
  488. .pcl~*p+1X~*c180a1B~*c50g0P~*p-180y-1X~*p+1y+180X
  489. .pcl~*c1a180B~*c0P~&f1S
  490. .com Next 5 .pcl's:  PBOX 43 +1620 -180 90 180 2 2 20 1 50
  491. .pcl~&f0S~*p+1620x-180Y~&f0S~*p+2a+2B~*c90a180b20g2P~&f1S
  492. .pcl~*c90a2B~*c0P~*c2a180B~*c0P~*p+90X~*c0P
  493. .pcl~*p-90x+180Y~*c92a2B~*c0P
  494. .pcl~*p+1X~*c90a1B~*c50g2P~*p-180y-1X~*p+1y+90X
  495. .pcl~*c1a180B~*c2P~&f1S
  496. .endif.com if outlaser
  497.  
  498. .sub |1Query Field Labels|
  499. `|1Query Field Labels|
  500.  
  501. Query always produces self-describing files with 15 words
  502. reserved for each field descriptor.
  503. Each field is described as
  504. follows:
  505. .mar(l+3)
  506. `|1field'name (X16).|@@The name of the field left-justified.
  507. Field names are in upper case.
  508. `|1field'type (J1).|@@The type of the field taken from the
  509. following list:
  510. .mar(l+3)
  511. `1.#7ASCII (type U and X).
  512. `2.#7free form ASCII numbers.
  513. `3.#7signed integer (type I).
  514. `4.#7floating point real (type R).
  515. `5.#7packed decimal (type P).
  516. `6.#7COBOL computational (type J).
  517. `7.#7unsigned integers (type K).
  518. `8.#7zoned decimal (type Z).
  519. `9.#7IEEE floating point (type E).  This is a Robelle extension
  520. that applies to either "@A.00.00" or "@B.00.00" self-describing
  521. files.
  522. `10.#7IMAGE compound field.
  523. .mar
  524. `|1field'offset (J1).|@@The offset of the field in bytes. The
  525. offset starts at zero.
  526. `|1field'length (J1).|@@The length of the field in bytes.
  527. `|1reserved'space (4J1).|@@Four words that are reserved for future
  528. use.
  529. .mar
  530.  
  531. .sub |1Robelle Field Labels|
  532. `|1Robelle Field Labels|
  533.  
  534. Many HP 3000 applications contain repeated fields.  Query
  535. self-describing files map all repeated fields into type "10", which
  536. is useless for applications that understand repeated fields.
  537. It would also be nice if additional
  538. user information, such as the number of decimal points or the
  539. format of a date were available.  The Robelle field descriptor
  540. provides for all of these, by using three of the four words of
  541. reserved space.
  542. All fields up to |1field'length| are the same as QUERY's
  543. (note especially
  544. that |1field'length| is the total length of the field and not
  545. the length of one sub-field).  These are the new fields:
  546. .mar(l+3)
  547. `|1field'repeat (J1)|.@@In IMAGE terms, this is known as the sub-count.
  548. For simple fields, |1field'repeat| is one (and not zero).
  549. `|1field'decplaces (J1)|.@@Logical number of decimal places in the
  550. field.  Zero means there are
  551. no decimal points.  This field must be zero if the |1field'type|
  552. is byte.
  553. `|1field'date'type (J1)|.@@Zero if the field is not a date.  Otherwise,
  554. contains a constant that describes the format of the date. These
  555. constants are described below.
  556. `|1reserved'space (J1).|@@One word that is reserved for future
  557. use.
  558. .mar
  559.  
  560. .sub |1Date Format|
  561. `|1Date Format|
  562.  
  563. The date format is mapped into the data type and byte-length
  564. of the field. Here are the constants for each
  565. date format:
  566.  
  567.      1    #8yymmdd
  568.      2    #8ddmmyy
  569.      3    #8mmddyy
  570.      4    #8yymm
  571.      5    #8calendar (MPE intrinsic format)
  572.      6    #8yyyymmdd
  573.      7    #8ddmmyyyy
  574.      8    #8mmddyyyy
  575.      9    #8phdate   (PowerHouse format)
  576.      10   #8ask      (ASK ManMan format)
  577.  
  578. .sub |1SPL Field Layout|
  579. `|1SPL Layout of the Field Descriptor|
  580.  
  581. Here is the layout in SPL notation of the Robelle field
  582. descriptor.
  583. Note that we use exactly the same layout for accessing Query
  584. field descriptors (we ignore the repeat, decplaces, and
  585. date'type fields for Query self-describing files):
  586.  
  587. .font 5.opt(f-)
  588.    sdfield.srcinc:
  589.  
  590.    integer array sd'field(0:sd'max'field'len);  { 0 : 14 }
  591.    byte    array sd'field'name(*)     = sd'field;
  592.    integer array sd'field'type(*)     = sd'field(8);
  593.    integer array sd'field'offset(*)   = sd'field(9);
  594.    integer array sd'field'bytelen(*)  = sd'field(10);
  595.    integer array sd'field'repeat(*)   = sd'field(11);
  596.    integer array sd'field'decplaces(*)= sd'field(12);
  597.    integer array sd'field'date'type(*)= sd'field(13);
  598. .font 0.opt
  599.  
  600. .sub |1Support Routines|
  601. `|1Support Routines|
  602.  
  603. To make our life easier, we have a standard include file with
  604. both variables and SPL/SPLash! subroutines that we use in many
  605. of our self-describing procedures:
  606.  
  607. .font 5
  608. sdsubr.src:
  609.  
  610. <<  Standard variables and subroutines needed to access fields in
  611.     a self-describing file.  This file must be included after all
  612.     variable declarations in a procedure.
  613. >>
  614.  
  615.    integer
  616.       file'userlabels
  617.      ,file'foptions
  618.      ,file'filecode
  619.      ,current'labelnum
  620.      ,sd'field'index
  621.      ;
  622.  
  623.    integer array current'label(0:sd'label'len);
  624.  
  625.    subroutine file'error(local'filenum);
  626.       value   local'filenum;
  627.       integer local'filenum;
  628.    begin
  629.       xfileinfo(local'filenum);
  630.       goto error'exit;
  631.    end'subr;   <<file'error>>
  632.  
  633.    subroutine read'label'error(local'filenum);
  634.       value   local'filenum;
  635.       integer local'filenum;
  636.    begin
  637.       p "Unable to read label from self-describing file" err;
  638.       file'error(local'filenum);
  639.    end'subr;   <<read'label'error>>
  640.  
  641.    subroutine read'label(local'filenum,labelnum);
  642.       value   local'filenum, labelnum;
  643.       integer local'filenum, labelnum;
  644.    begin
  645.       blank(current'label,sd'label'len);
  646.       freadlabel(local'filenum
  647.                 ,current'label
  648.                 ,sd'label'len
  649.                 ,labelnum
  650.                 );
  651.       if < then
  652.          read'label'error(local'filenum)
  653.       else
  654.       if > then
  655.          if labelnum > file'userlabels then
  656.          begin
  657.             b'blank(outbuf,bl'outbuf);
  658.             move outbuf := "Attempting to read label #";
  659.             ascii(labelnum,10,outbuf'(26));
  660.             say'errx(outbuf,35,bl'outbuf);
  661.             read'label'error(local'filenum);
  662.          end'if;
  663.    end'subr;   <<read'label>>
  664.  
  665.    subroutine file'info(local'filenum);
  666.       value   local'filenum;
  667.       integer local'filenum;
  668.    begin
  669.       fgetinfo(local'filenum<<filenum          iv>>
  670.         ,                   <<filename         ba>>
  671.         ,file'foptions      <<foptions         l >>
  672.         ,                   <<aoptions         l >>
  673.         ,                   <<recsize          i >>
  674.         ,                   <<devtype          i >>
  675.         ,                   <<ldnum            l >>
  676.         ,                   <<hdaddr           l >>
  677.         ,file'filecode      <<filecode         i >>
  678.         ,                   <<recptr           d >>
  679.         ,                   <<eof              d >>
  680.         ,                   <<flimit           d >>
  681.         ,                   <<logcount         d >>
  682.         ,                   <<physcount        d >>
  683.         ,                   <<blksize          i >>
  684.         ,                   <<extsize          l >>
  685.         ,                   <<numextents       i >>
  686.         ,file'userlabels    <<userlabels       i >>
  687.         );
  688.       if <> then
  689.       begin
  690.          p "Unable to fgetinfo on file" err;
  691.          file'error(local'filenum);
  692.       end'if;
  693.    end'subr;   <<file'info>>
  694.  
  695.    logical subroutine get'field(local'filenum,offset);
  696.       value   local'filenum, offset;
  697.       integer local'filenum, offset;
  698.    begin
  699.       get'field := false;
  700.       if sd'field'index < sd'numfields then
  701.       begin
  702.          if (sd'field'index mod sd'fieldsperlabel) = 0 then
  703.          begin
  704.             current'labelnum := current'labelnum - 1;
  705.             read'label(local'filenum,current'labelnum);
  706.          end'if;
  707.          offset := (sd'field'index mod sd'fieldsperlabel) *
  708.                    sd'entrylen;
  709.          move sd'field := current'label(offset),(sd'entrylen);
  710.          sd'field'index   := sd'field'index + 1;
  711.          get'field := true;
  712.       end'if;
  713.    end'subr;   <<get'field>>
  714. .font 0
  715.  
  716. .sub |1File'Error|
  717. `|1File'Error|
  718.  
  719. To make our life easier we will take a simple approach to file
  720. system errors. If any MPE file system intrinsic returns an error,
  721. we call the Robelle equivalent of the printfileinfo intrinsic and
  722. then we exit the Formselfdesc procedure. Yes, we use a goto in
  723. the |1file'error| subroutine.  This is a good example of where a
  724. goto enhances readability and reliability.
  725.  
  726. .sub |1File'Info|
  727. `|1File'Info|
  728.  
  729. We have developed a standard set of subroutines for working with
  730. self-describing files. The |1file'info| subroutine initializes
  731. the |1file'userlabels|, |1file'foptions|, and |1file'filecode|
  732. variables (declared as part of the |5sdsubr.src| file).
  733.  
  734. .sub |1Read'Label|
  735. `|1Read'Label|
  736.  
  737. It is important to understand the error checking in
  738. |1read'label|. MPE user labels may be allocated space, but
  739. they might not actually be written. For example, after first
  740. creating a file with user labels, none of the user labels have
  741. actually been written to the file. If we get an end-of-file
  742. condition from Freadlabel, we ignore the error unless a
  743. programming bug has caused us to attempt to read a label that is
  744. greater than the number of user labels in the file.
  745.  
  746. .sub |1Get'Field|
  747. `|1Get'Field|
  748.  
  749. We will describe how the |1get'field| subroutine works later in
  750. the section |2Understanding Self-Describing Information|.
  751.  
  752. .sub |1Am I Self-Describing|
  753. `|1Am I A Self-describing File|
  754.  
  755. We determine if a file is self-describing in two ways:
  756. .mar(l+3)
  757. `1.#4If the file has a filecode of 1084 and it has one or more
  758. MPE file labels.
  759. `2.#4The file is a KSAM file, has more than one label, we can
  760. read the last label, and the last label starts with either the
  761. string "@A.00.00" or "@B.00.00" (note the space at the
  762. beginning).
  763. .mar
  764.  
  765. Here is a procedure that returns True if the passed filenum is a
  766. self-describing file:
  767.  
  768. .font 5.opt(f-)
  769.      $page "sd'file"
  770.      <<  Return true if the passed file is self-describing.
  771.      >>
  772.  
  773.      logical procedure sd'file(filenum);
  774.         value   filenum;
  775.         integer filenum;
  776.         option check 3;
  777.      begin
  778.  
  779.      $include sdheader.srcinc
  780.      $include sdfield.srcinc
  781.  
  782.      $include sdsubr.src
  783.  
  784.      $page "sd'file/mainline"
  785.  
  786.         sd'file := false;
  787.  
  788.         file'info(filenum);
  789.  
  790.         if file'filecode = 1084 and file'userlabels <> 0 then
  791.            sd'file := true
  792.         else
  793.         if file'foptions.(2:3) = 1 or
  794.            file'foptions.(2:3) = 3 then
  795.            if file'userlabels > 1 then
  796.            begin   <<ksam with extra user labels>>
  797.               read'label(filenum,file'userlabels-1);
  798.               move sd'header := current'label,(sd'label'len);
  799.               if sd'version = " A.00.00" or
  800.                  sd'version = " B.00.00" then
  801.                  sd'file := true;
  802.            end'else;
  803.  
  804.      error'exit:
  805.  
  806.      end'proc;   <<sd'file>>
  807. .font 0.opt
  808. .tit
  809. .page.sub
  810.  
  811. .skip 3.opt(l- r- f-)
  812. `|3Creating A Self-Describing File|
  813. .opt.skip 2
  814. .tit Creating
  815. .ent `Creating A Self-Describing File
  816.  
  817. When we describe data structures we usually explain the input
  818. routine first and then the creation/output routine second.  For
  819. self-describing files, it is easier to do it in the opposite order.
  820. We will show the structure of a simple self-describing file and
  821. then we will show the code that produced the self-describing label
  822. information for the file.
  823.  
  824. HowMessy is a Robelle program that reports on database efficiency. For
  825. years, this program has produced a report. Unfortunately, reports
  826. must be read by humans.  It would make more sense for HowMessy to
  827. produce a self-describing MPE file with the efficiency
  828. information from one or more databases.
  829. You could then use a tool that understood self-describing files
  830. to report and act on the information from the file produced by
  831. HowMessy.
  832. We will show all of the
  833. routines in HowMessy's self-describing module, but first we need
  834. to know the structure of the self-describing file.
  835.  
  836. .sub |1HowMessy's Loadfile|
  837. `|1HowMessy's Loadfile|
  838.  
  839. HowMessy creates a self-describing file called Loadfile. This
  840. file has one record per database/dataset/search-field for one
  841. or more databases.  Here is a "form" listing of the Loadfile:
  842.  
  843. .font 5.opt(f-)
  844.     File: LOADFILE.GROUP.ACCT     (SD Version B.00.00)
  845.        Entry:                     Offset
  846.           DATABASE             X26     1
  847.           DATASET              X16    27
  848.           DATASETNUM           I1     43
  849.           DATASETTYPE          X4     45
  850.           CAPACITY             I2     49
  851.           ENTRIES              I2     53
  852.           LOADFACTOR           I2     57           << .2  >>
  853.           SECONDARIES          I2     61           << .2  >>
  854.           MAXBLOCKS            I2     65
  855.           HIGHWATER            I2     69
  856.           PATHSORT             X1     73
  857.           PATHPRIMARY          X1     74
  858.           BLOCKFACTOR          I1     75
  859.           SEARCHFIELD          X16    77
  860.           MAXCHAIN             I2     93
  861.           AVECHAIN             I2     97           << .2  >>
  862.           STDDEVIATION         I2    101           << .2  >>
  863.           EXPECTEDBLOCKS       I2    105           << .2  >>
  864.           AVERAGEBLOCKS        I2    109           << .2  >>
  865.           INEFFICIENTPTRS      I2    113           << .2  >>
  866.           ELONGATION           I2    117           << .2  >>
  867.           FUTUREFIELDS         X136  121
  868.     Limit: 10000  EOF: 33  Entry Length: 256  Blocking: 35
  869. .font 0.opt
  870.  
  871. .sub |1Global Equates|
  872. `|1Global Equates|
  873.  
  874. To simplify programming, we use a global constant
  875. "equates" that define specific attributes of Query and Robelle
  876. self-describing files.  When reading a self-describing file, we
  877. don't need most of these constants, since
  878. the necessary numbers are provided in the self-describing file
  879. header.  Here are the equates that we use when creating
  880. self-describing files:
  881.  
  882. .font 5.opt(f-)
  883.      sdequate.srcinc:
  884.  
  885.      equate
  886.         sd'max'field'len      = 15
  887.        ,sd'label'len          = 128
  888.        ,sd'max'fieldsperlabel = 8
  889.        ,sd'filler'labels      = 10
  890.        ;
  891.  
  892.      equate
  893.         sd'date'yymmdd   = 1
  894.        ,sd'date'ddmmyy   = 2
  895.        ,sd'date'mmddyy   = 3
  896.        ,sd'date'yymm     = 4
  897.        ,sd'date'calendar = 5
  898.        ,sd'date'yyyymmdd = 6
  899.        ,sd'date'ddmmyyyy = 7
  900.        ,sd'date'mmddyyyy = 8
  901.        ,sd'date'phdate   = 9
  902.        ,sd'date'askdate  = 10
  903.        ;
  904. .font 0.opt
  905.  
  906. .sub |1How Many Labels?|
  907. `|1Computing the Number of Labels|
  908.  
  909. Before opening the Loadfile, HowMessy must determine how many
  910. labels will be needed.  The following routine is used by
  911. Robelle products to compute the number of user labels for a
  912. self-describing file. Note that we continue the Query standard of
  913. reserving the first ten labels (numbered 0 to 9) for other uses:
  914.  
  915. .font 5.opt(f-)
  916. $page "sd'compute'labels"
  917. <<  Compute how many labels an SD file should have, based only
  918.     on the number of fields. Includes the mysterious filler
  919.     labels.
  920. >>
  921.  
  922. integer procedure sd'compute'labels (numfields);
  923.    value   numfields;
  924.    integer numfields;
  925.    option check 3;
  926. begin
  927.    sd'compute'labels :=
  928.                   (numfields-1+sd'max'fieldsperlabel) /
  929.                   sd'max'fieldsperlabel
  930.                   + 1 <<for the header label>>
  931.                   + sd'filler'labels;
  932.  
  933. end'proc;   <<sd'compute'labels>>
  934. .font 0.opt
  935.  
  936. .sub |1Opening the Loadfile|
  937. `|1Opening the Loadfile|
  938.  
  939. After computing the number of user labels, we can open a new MPE
  940. file called Loadfile. We designed the HowMessy Loadfile to have
  941. records 256 bytes long.  To make our life easier, we have a few
  942. global equates in the HowMessy self-describing module that we'll
  943. use throughout the rest of the examples:
  944.  
  945. .font 5.opt(f-)
  946. $page "global equates and defines for the selfdesc module"
  947.  
  948.    equate
  949.       wl'loadfile  = 128
  950.      ,bl'loadfile  = wl'loadfile * 2
  951.      ,bl'item'name = 16
  952.      ,max'field    = 22              ! #fields in Loadfile
  953.      ;
  954. .font 0.opt
  955.  
  956. Here is the actual code to create the Loadfile:
  957.  
  958. .font 5.opt(f-)
  959. $page "sd'open"
  960. <<  Open the Loadfile and initialize the self-describing
  961.     information.
  962. >>
  963.  
  964. logical procedure sd'open(outfile,loadfile'filenum);
  965.    integer loadfile'filenum;   ! Note by reference -- returned
  966.    integer array outfile;
  967.    option check 3;
  968. begin
  969. $include localvar.srcinc
  970.  
  971.    byte array
  972.       loadfile'filename(0:bl'local'filename)
  973.      ;
  974.  
  975.    move loadfile'filename := "loadfile ";
  976.  
  977.    loadfile'filenum :=
  978.        fopen(loadfile'filename
  979.             ,                       << foptions         lv >>
  980.             ,1   <<write>>          << aoptions         lv >>
  981.             ,wl'loadfile            << recsize          iv >>
  982.             ,                       << device           ba >>
  983.             ,                       << formmsg          ba >>
  984.             ,sd'compute'labels(max'field)
  985.             ,35                     << blockfactor      iv >>
  986.             ,                       << numbuffers       iv >>
  987.             ,10000d                 << filesize         dv >>
  988.             ,                       << numextents       iv >>
  989.             ,                       << initialloc       iv >>
  990.             ,1084                   << filecode         iv >>
  991.             );
  992.    if loadfile'filenum = 0 then
  993.    begin
  994.       error(outfile,10);
  995.       xfileinfo(loadfile'filenum);
  996.    end'if
  997.    else
  998.       sd'open := true;
  999.  
  1000. end'proc;   <<sd'open>>
  1001. .font 0.opt
  1002.  
  1003. Note that the filecode is 1084. For non-KSAM files, this is used
  1004. to indicate a self-describing file.  When you do a :Listf of such
  1005. a file, MPE translates the "1084" filecode into "SD".
  1006.  
  1007. .sub |1Writing the Labels|
  1008. `|1Writing the Self-Describing Labels|
  1009.  
  1010. Having successfully opened a new self-describing file,
  1011. it's time to write the self-describing information to the user
  1012. labels. Remember that the last user label (N-1) contains the
  1013. header information and the field labels are written in backward
  1014. order (N-2, N-3, ...).  Our routine to write the self-describing
  1015. information to the Loadfile writes the field information first
  1016. and then updates the header label as the last step:
  1017.  
  1018. .font 5.opt(f-)
  1019. $page "sd'write'labels"
  1020. <<  Write out the labels of a self-describing file with the
  1021.     Loadfile fields.
  1022. >>
  1023.  
  1024. logical procedure sd'write'labels(outfile,filenum);
  1025.    value   filenum;
  1026.    integer filenum;
  1027.    integer array outfile;
  1028.    option check 3;
  1029. begin
  1030. $include localvar.srcinc
  1031.    integer
  1032.       field'index
  1033.      ,field'offset
  1034.      ,labelnum
  1035.      ;
  1036.  
  1037. $include sdheader.srcinc
  1038. $include sdfield.srcinc
  1039.  
  1040.    integer array sd'label(0:sd'label'len);
  1041.  
  1042. $include sdsubr.src
  1043. $page "sd'write'labels/subroutines"
  1044.    subroutine write'label(labelnum);
  1045.       value   labelnum;
  1046.       integer labelnum;
  1047.    begin
  1048.       fwritelabel(filenum,sd'label,sd'label'len,labelnum);
  1049.       if <> then
  1050.       begin
  1051.          error(outfile,12);
  1052.          file'error(filenum);
  1053.       end'if;
  1054.    end'subr;   <<write'label>>
  1055.  
  1056.    subroutine init'header;
  1057.    begin
  1058.       zero'buf(sd'header,sd'label'len);
  1059.       b'blank(sd'version,8);
  1060.       move sd'version  := "@B.00.00";
  1061.       sd'numfields     := max'field;
  1062.       field'index      := 0;
  1063.       sd'numlabels     := sd'compute'labels(sd'numfields) -
  1064.                           sd'filler'labels;
  1065.       sd'fieldsperlabel:= sd'max'fieldsperlabel;
  1066.       sd'entrylen      := sd'max'field'len;
  1067.    end'subr;   <<init'header>>
  1068.  
  1069.    subroutine init'all'labels(curr'label,num'labels);
  1070.       value   curr'label, num'labels;
  1071.       integer curr'label, num'labels;
  1072.    begin
  1073.       while curr'label > num'labels do
  1074.       begin
  1075.          write'label(curr'label);
  1076.          curr'label := curr'label - 1;
  1077.       end'while;
  1078.    end'subr;     <<init'all'labels>>
  1079.  
  1080.    subroutine put'field(name,bytelen,decplaces,type);
  1081.       value   bytelen, type, decplaces;
  1082.       integer bytelen, decplaces, type;
  1083.       byte array name;
  1084.    begin
  1085.       zero'buf(sd'field,sd'max'field'len);
  1086.       move sd'field'name := name,(bl'item'name);
  1087.       sd'field'type      := type;
  1088.       sd'field'offset    := field'offset;
  1089.       sd'field'bytelen   := bytelen;
  1090.       sd'field'repeat    := 1;
  1091.       move sd'label(sd'entrylen*sd'field'index) := sd'field,
  1092.                                                    (sd'entrylen);
  1093.       field'offset := field'offset + sd'field'bytelen;
  1094.       sd'field'index := sd'field'index + 1;
  1095.       if sd'field'index >= sd'fieldsperlabel then
  1096.       begin
  1097.          write'label(labelnum);
  1098.          labelnum := labelnum - 1;
  1099.          sd'field'index := 0;
  1100.          zero'buf(sd'label,sd'label'len);
  1101.       end'if;
  1102.       b'blank(name,bl'item'name);
  1103.    end'subr;  <<put'field>>
  1104. $page "sd'write'labels/mainline"
  1105.  
  1106.    sd'write'labels := false;
  1107.  
  1108.    init'header;
  1109.  
  1110.    file'info(filenum);
  1111.  
  1112.    field'offset   := 0;
  1113.    sd'field'index := 0;
  1114.    zero'buf(sd'label,sd'label'len);
  1115.    init'all'labels(file'userlabels-1,sd'numlabels);
  1116.  
  1117.    labelnum       := file'userlabels - 2;
  1118.  
  1119.    sd'reclength   := bl'loadfile;
  1120.  
  1121.    b'blank(inbuf,bl'inbuf);
  1122.  
  1123.    move inbuf' := "DATABASE        "; put'field(inbuf, 26,0,1);
  1124.    move inbuf' := "DATASET         "; put'field(inbuf, 16,0,1);
  1125.    move inbuf' := "DATASETNUM      "; put'field(inbuf,  2,0,3);
  1126.    move inbuf' := "DATASETTYPE     "; put'field(inbuf,  4,0,1);
  1127.    move inbuf' := "CAPACITY        "; put'field(inbuf,  4,0,3);
  1128.    move inbuf' := "ENTRIES         "; put'field(inbuf,  4,0,3);
  1129.    move inbuf' := "LOADFACTOR      "; put'field(inbuf,  4,2,3);
  1130.    move inbuf' := "SECONDARIES     "; put'field(inbuf,  4,2,3);
  1131.    move inbuf' := "MAXBLOCKS       "; put'field(inbuf,  4,0,3);
  1132.    move inbuf' := "HIGHWATER       "; put'field(inbuf,  4,0,3);
  1133.    move inbuf' := "PATHSORT        "; put'field(inbuf,  1,0,1);
  1134.    move inbuf' := "PATHPRIMARY     "; put'field(inbuf,  1,0,1);
  1135.    move inbuf' := "BLOCKFACTOR     "; put'field(inbuf,  2,0,3);
  1136.    move inbuf' := "SEARCHFIELD     "; put'field(inbuf, 16,0,1);
  1137.    move inbuf' := "MAXCHAIN        "; put'field(inbuf,  4,0,3);
  1138.    move inbuf' := "AVECHAIN        "; put'field(inbuf,  4,2,3);
  1139.    move inbuf' := "STDDEVIATION    "; put'field(inbuf,  4,2,3);
  1140.    move inbuf' := "EXPECTEDBLOCKS  "; put'field(inbuf,  4,2,3);
  1141.    move inbuf' := "AVERAGEBLOCKS   "; put'field(inbuf,  4,2,3);
  1142.    move inbuf' := "INEFFICIENTPTRS "; put'field(inbuf,  4,2,3);
  1143.    move inbuf' := "ELONGATION      "; put'field(inbuf,  4,2,3);
  1144.    move inbuf' := "FUTUREFIELDS    "; put'field(inbuf,136,0,1);
  1145.  
  1146.    if sd'field'index <> 0 then
  1147.       write'label(labelnum);
  1148.  
  1149.    move sd'label := sd'header,(sd'label'len);
  1150.    write'label(file'userlabels - 1);
  1151.  
  1152.    sd'write'labels := true;
  1153.  
  1154. error'exit:
  1155.  
  1156. end'proc;   <<sd'write'labels>>
  1157. .font 0.opt
  1158.  
  1159. .sub |1Initializing the Labels|
  1160. `|1Init'Header|
  1161.  
  1162. We start our procedure by initializing most of the fields in the
  1163. header label.  We zero out the header label and then we
  1164. fill in the variables of the header label. This file has fields
  1165. with an implied decimal point, so we want to use the Robelle
  1166. format of self-describing files (version number "@B.00.00").
  1167. The number of fields is taken from our global equate.  The
  1168. number of self-describing labels is our computed number less the
  1169. ten overhead labels.  The number of fields in each label and the
  1170. length of each field description are taken from global equates
  1171. that match the values used by Query.  We also initialize the
  1172. |1field'index| variable which is used as an index into a single
  1173. label buffer (varies from 0 to sd'fieldsperlabel - 1).
  1174.  
  1175. .sub |1File'Info|
  1176. `|1File'Info|
  1177.  
  1178. To make our code more general-purpose, we
  1179. will not assume anything about the HowMessy Loadfile format (this
  1180. also makes it easier to change later).  Instead, we call Fgetinfo
  1181. to obtain the number of labels in our file, so that we know
  1182. exactly where the last label is.  The |1file'info| subroutine
  1183. initializes the |1file'userlabels| variable
  1184. (declared in the |5sdsubr.src| file) with the number of
  1185. user labels in our file.
  1186.  
  1187. .sub |1Init'All'Labels|
  1188. `|1Init'All'Labels|
  1189.  
  1190. To be on the safe side, we initialize all user labels in our file
  1191. with binary zeroes. Note that our |1write'label| subroutine uses
  1192. a procedure global array called |1label'buf| for writing. We
  1193. initialize this buffer to binary zeroes and then continually
  1194. write it out to all of the self-describing labels.  We don't
  1195. touch the initial ten labels reserved for other use.
  1196.  
  1197. .sub |1Put'Field|
  1198. `|1Put'Field|
  1199.  
  1200. This subroutine handles all of the details of adding a new field
  1201. to our self-describing Loadfile. It initializes a new field
  1202. record, moves this field record to the appropriate place in
  1203. |1label'buf|, and finally writes out labels as we overflow
  1204. |1sd'fieldsperlabel|. Each of our fields has a name (we move the
  1205. name to |1inbuf| and initialize |1inbuf| to blanks after adding
  1206. the field, a byte length, the implied number of decimal places,
  1207. and a type (either byte or integer for our file).  Note that the
  1208. |1put'field| subroutine looks after computing the byte offset of
  1209. each field by incrementing a counter.
  1210.  
  1211. We initialize each field record with binary zeroes (just to be
  1212. safe). We then fill in each portion of field information. We then
  1213. move our field record to the |1label'buf| at the correct offset.
  1214. This works well in SPL/SPLash!, but is more of a problem in C or
  1215. Pascal. In these languages, we would create a record/structure
  1216. that was an array of field records and index into the structure
  1217. using the current field index.  As we filled up the structure, we
  1218. would write out a new user label to our self-describing file.
  1219.  
  1220. .sub |1Finishing Up|
  1221. `|1Finishing Up|
  1222.  
  1223. After adding all the fields, we have to see if there is one label
  1224. record that has not been written to the Loadfile. If so, we write
  1225. it out.  Finally, the header record is written out.   We do this
  1226. last, since some of the variables used by |1put'field| were ones
  1227. from the header record.
  1228.  
  1229. .sub |1Closing|
  1230. `|1Closing the Self-Describing File|
  1231.  
  1232. Our next routine closes the self-describing file and handles any
  1233. errors from Fclose.  Pretty straight-forward MPE programming:
  1234.  
  1235. .font 5.opt(f-)
  1236. $page "sd'close"
  1237. <<  Close the loadfile and check for duplicate output files.
  1238. >>
  1239.  
  1240. logical procedure sd'close(outfile,loadfile'filenum);
  1241.    value   loadfile'filenum;
  1242.    integer loadfile'filenum;
  1243.    integer array outfile;
  1244.    option check 3;
  1245. begin
  1246. $include localvar.srcinc
  1247.  
  1248.    sd'close := false;
  1249.  
  1250.    fclose(loadfile'filenum,2,0);   ! Save temp
  1251.    if <> then
  1252.    begin
  1253.       error(outfile,11);
  1254.       xfileinfo(loadfile'filenum);
  1255.    end'if
  1256.    else
  1257.       sd'close := true;
  1258.  
  1259. end'proc;   <<sd'close>>
  1260. .font 0.opt
  1261.  
  1262. .sub |1Providing a Shell|
  1263. `|1Providing a Shell|
  1264.  
  1265. To make life easier in HowMessy, we provide one routine for the
  1266. main module to call.  This routine purges any exiting temporary
  1267. Loadfile, creates our new Loadfile, writes out the
  1268. self-\describing information, and saves the Loadfile.  The
  1269. controlling HowMessy routine then reopens Loadfile with
  1270. write-access (this may seem inefficient, but HowMessy is written
  1271. in both SPL/SPLash! and HP Pascal, so it was easier to organize
  1272. the code this way):
  1273.  
  1274. .font 5.opt(f-)
  1275. $page "sdcreate"
  1276. <<  Create Loadfile with all the self-describing information. We
  1277.     purge any existing file called Loadfile, create a temporary
  1278.     one, and then fill in the labels.
  1279. >>
  1280.  
  1281. integer procedure sdcreate(outfile);
  1282.    integer array outfile;
  1283.    option check 3;
  1284. begin
  1285. $include localvar.srcinc
  1286. $include mpecmd.srcinc
  1287.  
  1288.    integer
  1289.       loadfile'filenum
  1290.      ;
  1291.  
  1292.    logical subroutine purge'loadfile;
  1293.    begin
  1294.       purge'loadfile := false;
  1295.       say'str "purge loadfile,temp";
  1296.       say'add rtn;
  1297.       if mpecmd'execute(outbuf,mpe'print'buffer) then
  1298.          purge'loadfile := true
  1299.       else
  1300.          error(outfile,9);
  1301.    end'subr;   <<purge'loadfile>>
  1302. $page "sdcreate/mainline"
  1303.  
  1304.    sdcreate := 0;
  1305.  
  1306.    if purge'loadfile then
  1307.       if sd'open(outfile,loadfile'filenum) then
  1308.          if sd'write'labels(outfile,loadfile'filenum) then
  1309.             if sd'close(outfile,loadfile'filenum) then
  1310.                sdcreate := 1;
  1311.  
  1312. end'proc;   <<sdcreate>>
  1313. .font 0.opt
  1314. .tit
  1315. .page.sub
  1316.  
  1317. .opt(l- r- f-).skip 2
  1318. `|3Understanding Self-Describing Information|
  1319. .opt.skip 2
  1320. .tit Understanding
  1321. .ent `Understanding Self-Describing Information
  1322.  
  1323. Our HowMessy example showed the form of the Loadfile using a
  1324. format similar to the one Query uses, but the input was an MPE
  1325. self-describing file instead of an IMAGE dataset.  Here is our
  1326. example form again:
  1327.  
  1328. .font 5.opt(f-)
  1329.     File: LOADFILE.GROUP.ACCT     (SD Version B.00.00)
  1330.        Entry:                     Offset
  1331.           DATABASE             X26     1
  1332.           DATASET              X16    27
  1333.           DATASETNUM           I1     43
  1334.           DATASETTYPE          X4     45
  1335.           CAPACITY             I2     49
  1336.           ENTRIES              I2     53
  1337.           LOADFACTOR           I2     57           << .2  >>
  1338.           SECONDARIES          I2     61           << .2  >>
  1339.           MAXBLOCKS            I2     65
  1340.           HIGHWATER            I2     69
  1341.           PATHSORT             X1     73
  1342.           PATHPRIMARY          X1     74
  1343.           BLOCKFACTOR          I1     75
  1344.           SEARCHFIELD          X16    77
  1345.           MAXCHAIN             I2     93
  1346.           AVECHAIN             I2     97           << .2  >>
  1347.           STDDEVIATION         I2    101           << .2  >>
  1348.           EXPECTEDBLOCKS       I2    105           << .2  >>
  1349.           AVERAGEBLOCKS        I2    109           << .2  >>
  1350.           INEFFICIENTPTRS      I2    113           << .2  >>
  1351.           ELONGATION           I2    117           << .2  >>
  1352.           FUTUREFIELDS         X136  121
  1353.     Limit: 10000  EOF: 33  Entry Length: 256  Blocking: 35
  1354. .font 0.opt
  1355.  
  1356. .sub |1Formselfdesc|
  1357. `|1Formselfdesc Procedure|
  1358.  
  1359. We have developed a stand-alone procedure for producing this
  1360. output for a self-describing file.  The following is the source
  1361. code that we use:
  1362.  
  1363. .font 5.opt(f-)
  1364. $page "formselfdesc"
  1365. <<  If the passed filenum is a self-describing file, print a
  1366.     description of the fields in the file on $stdlist.
  1367. >>
  1368.  
  1369. logical procedure formselfdesc(sd'filenum);
  1370.    value   sd'filenum;
  1371.    integer sd'filenum;
  1372.    option check 3;
  1373. begin
  1374. $include localvar.srcinc
  1375. $include sdequate.srcinc
  1376.  
  1377.    integer
  1378.       file'code
  1379.      ,file'userlabels
  1380.      ,file'foptions
  1381.      ,current'labelnum
  1382.      ,field'index
  1383.      ,file'recsize
  1384.      ,file'blkfac
  1385.      ;
  1386.    double
  1387.       file'eof
  1388.      ,file'limit
  1389.      ;
  1390.    byte array
  1391.       filename(0:bl'local'filename)
  1392.      ;
  1393.  
  1394. $include sdheader.srcinc
  1395. $include sdfield.srcinc
  1396.  
  1397.    integer array current'label(0:sd'label'len);
  1398.  
  1399.    subroutine file'error;
  1400.    begin
  1401.       xfileinfo(sd'filenum);
  1402.       goto error'exit;
  1403.    end'subr;   <<file'error>>
  1404.  
  1405.    subroutine read'label(labelnum);
  1406.       value   labelnum;
  1407.       integer labelnum;
  1408.    begin
  1409.       freadlabel(sd'filenum,current'label,sd'label'len,labelnum);
  1410.       if <> then
  1411.       begin
  1412.          p "Unable to read label from self-describing file" err;
  1413.          file'error;
  1414.       end'if;
  1415.    end'subr;   <<read'label>>
  1416.  
  1417.    subroutine file'info(blksize);
  1418.       value   blksize;
  1419.       integer blksize;
  1420.    begin
  1421.       b'blank(filename,bl'local'filename);
  1422.       fgetinfo(sd'filenum      <<filenum          iv>>
  1423.         ,filename           <<filename         ba>>
  1424.         ,file'foptions      <<foptions         l >>
  1425.         ,                   <<aoptions         l >>
  1426.         ,file'recsize       <<recsize          i >>
  1427.         ,                   <<devtype          i >>
  1428.         ,                   <<ldnum            l >>
  1429.         ,                   <<hdaddr           l >>
  1430.         ,file'code          <<filecode         i >>
  1431.         ,                   <<recptr           d >>
  1432.         ,file'eof           <<eof              d >>
  1433.         ,file'limit         <<flimit           d >>
  1434.         ,                   <<logcount         d >>
  1435.         ,                   <<physcount        d >>
  1436.         ,blksize            <<blksize          i >>
  1437.         ,                   <<extsize          l >>
  1438.         ,                   <<numextents       i >>
  1439.         ,file'userlabels    <<userlabels       i >>
  1440.         );
  1441.       if <> then
  1442.       begin
  1443.          p "Unable to fgetinfo on file" err;
  1444.          file'error;
  1445.       end'if;
  1446.       if file'recsize <> 0 then
  1447.          file'blkfac := blksize / file'recsize
  1448.       else
  1449.          file'blkfac := 1;
  1450.       if file'recsize < 0 then
  1451.          file'recsize := \file'recsize\
  1452.       else
  1453.          file'recsize := file'recsize * 2;
  1454.    end'subr;   <<file'info>>
  1455.  
  1456.    logical subroutine file'is'sd;
  1457.    begin
  1458.       file'is'sd := false;
  1459.       file'info(0);
  1460.       if file'code = 1084 and file'userlabels <> 0 then
  1461.          file'is'sd := true
  1462.       else
  1463.       if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
  1464.          if file'userlabels > 1 then
  1465.          begin   <<ksam with extra user labels>>
  1466.             read'label(file'userlabels-1);
  1467.             move sd'header := current'label,(sd'label'len);
  1468.             if sd'version = "@A.00.00" or
  1469.                sd'version = "@B.00.00" then
  1470.                file'is'sd := true;
  1471.          end'else;
  1472.    end'subr;   <<file'is'sd>>
  1473.  
  1474.    logical subroutine get'field(offset);
  1475.       value   offset;
  1476.       integer offset;
  1477.    begin
  1478.       get'field := false;
  1479.       if field'index < sd'numfields then
  1480.       begin
  1481.          if (field'index mod sd'fieldsperlabel) = 0 then
  1482.          begin
  1483.             current'labelnum := current'labelnum - 1;
  1484.             read'label(current'labelnum);
  1485.          end'if;
  1486.          offset := (field'index mod sd'fieldsperlabel) *
  1487.                    sd'entrylen;
  1488.          move sd'field := current'label(offset),(sd'entrylen);
  1489.          field'index   := field'index + 1;
  1490.          get'field := true;
  1491.       end'if;
  1492.    end'subr;   <<get'field>>
  1493.  
  1494.    subroutine print'outbuf(len);
  1495.       value   len;
  1496.       integer len;
  1497.    begin
  1498.       len := bl'outbuf;
  1499.       while len > 0 and outbuf'(len-1) = " " do
  1500.          len := len - 1;
  1501.       print(outbuf,-len,0);
  1502.    end'subr;   <<print'outbuf>>
  1503.  
  1504.    subroutine print'header(len);
  1505.       value   len;
  1506.       integer len;
  1507.    begin
  1508.       b'blank(outbuf,bl'outbuf);
  1509.       move outbuf'(4) := "File: ";
  1510.       move outbuf'(10) := filename,(bl'local'filename);
  1511.       len := bl'outbuf;
  1512.       while len > 0 and outbuf'(len-1) = " " do
  1513.          len := len - 1;
  1514.       len := len + 5;
  1515.       len := len + move outbuf'(len) := "(SD Version";
  1516.       len := len + move outbuf'(len) := sd'version,(8);
  1517.       len := len + move outbuf'(len) := ")";
  1518.       print'outbuf(0);
  1519.       b'blank(outbuf,bl'outbuf);
  1520.       move outbuf'(7) := "Entry:";
  1521.       move outbuf'(34) := "Offset";
  1522.       print(outbuf,-50,0);
  1523.    end'subr;   <<print'header>>
  1524.  
  1525.    subroutine print'trailer(len);
  1526.       value   len;
  1527.       integer len;
  1528.    begin
  1529.       b'blank(outbuf,bl'outbuf);
  1530.       len := move outbuf' := "    ";
  1531.       len := len + move outbuf'(len) := "Limit: ";
  1532.       len := len + dascii(file'limit,10,outbuf'(len));
  1533.       len := len + move outbuf'(len) := "  EOF: ";
  1534.       len := len + dascii(file'eof,10,outbuf'(len));
  1535.       len := len + move outbuf'(len) := "  Entry Length: ";
  1536.       len := len + ascii(file'recsize,10,outbuf'(len));
  1537.       len := len + move outbuf'(len) := "  Blocking: ";
  1538.       len := len + ascii(file'blkfac,10,outbuf'(len));
  1539.       print(outbuf,-len,0);
  1540.    end'subr;  <<print'trailer>>
  1541.  
  1542.    subroutine format'field'type;
  1543.    begin
  1544.       if 0 <= sd'field'type <= 9 then
  1545.          case sd'field'type of begin
  1546.          <<0>> move outbuf'(31) := "?";
  1547.          <<1>> move outbuf'(31) := "X";
  1548.          <<2>> move outbuf'(31) := "?";
  1549.          <<3>> move outbuf'(31) := "I";
  1550.          <<4>> move outbuf'(31) := "R";
  1551.          <<5>> move outbuf'(31) := "P";
  1552.          <<6>> move outbuf'(31) := "J";
  1553.          <<7>> move outbuf'(31) := "K";
  1554.          <<8>> move outbuf'(31) := "Z";
  1555.          <<9>> move outbuf'(31) := "E";
  1556.          end'case
  1557.       else
  1558.          move outbuf'(31) := "?";
  1559.    end'subr;   <<format'field'type>>
  1560.  
  1561.    logical subroutine field'is'sorted(sort'index);
  1562.       value   sort'index;
  1563.       integer sort'index;
  1564.    begin
  1565.       field'is'sorted := false;
  1566.       if sd'field'offset + 1 = sd'sort'keys(sort'index*3) and
  1567.          sd'field'bytelen    = sd'sort'keys(sort'index*3+1) then
  1568.             field'is'sorted := true;
  1569.    end'subr;   <<field'is'sorted>>
  1570.  
  1571.    subroutine format'sort'key(sort'index);
  1572.       value   sort'index;
  1573.       integer sort'index;
  1574.    begin
  1575.       sort'index := 0;
  1576.       while sort'index < sd'sort'num'keys do
  1577.       begin
  1578.          if field'is'sorted(sort'index) then
  1579.          begin
  1580.             move outbuf'(42) := "<<Sort# ";
  1581.             ascii(sort'index+1,10,outbuf'(50));
  1582.             move outbuf'(52) := ">>";
  1583.          end'if;
  1584.          sort'index := sort'index + 1;
  1585.       end'while;
  1586.    end'subr;   <<format'sort'key>>
  1587.  
  1588.    subroutine format'date'type;
  1589.    begin
  1590.       if 1 <= sd'field'date'type <= 10 then
  1591.          case sd'field'date'type of begin
  1592.          <<0>> ;
  1593.          <<1>> move outbuf'(56) := "<<YYMMDD>>";
  1594.          <<2>> move outbuf'(56) := "<<DDMMYY>>";
  1595.          <<3>> move outbuf'(56) := "<<MMDDYY>>";
  1596.          <<4>> move outbuf'(56) := "<<YYMM>>";
  1597.          <<5>> move outbuf'(56) := "<<CALENDAR>>";
  1598.          <<6>> move outbuf'(56) := "<<YYYYMMDD>>";
  1599.          <<7>> move outbuf'(56) := "<<DDMMYYYY>>";
  1600.          <<8>> move outbuf'(56) := "<<MMDDYYYY>>";
  1601.          <<9>> move outbuf'(56) := "<<PHDATE>>";
  1602.          <<10>>move outbuf'(56) := "<<ASK>>";
  1603.          end'case;
  1604.    end'subr;   <<format'date'type>>
  1605.  
  1606.    subroutine format'decplaces;
  1607.    begin
  1608.       if sd'field'decplaces > 0 then
  1609.       begin
  1610.          move outbuf'(56) := "<< .";
  1611.          ascii(sd'field'decplaces,10,outbuf'(60));
  1612.          move outbuf'(63) := ">>";
  1613.       end'if;
  1614.    end'subr;   <<format'decplaces>>
  1615.  
  1616.    subroutine print'field'desc(field'repeat);
  1617.       value   field'repeat;
  1618.       integer field'repeat;
  1619.    begin
  1620.       b'blank(outbuf,bl'outbuf);
  1621.       move outbuf'(10) := sd'field'name,(16);
  1622.       if sd'version = "@B.00.00" then
  1623.       begin
  1624.          field'repeat     := sd'field'repeat;
  1625.          sd'field'bytelen := sd'field'bytelen / field'repeat;
  1626.       end'if
  1627.       else
  1628.          field'repeat := 1;
  1629.       if field'repeat <> 1 then
  1630.          ascii(field'repeat,-10,outbuf'(30));
  1631.       format'field'type;
  1632.       if sd'field'type = 3 or    <<integer>>
  1633.          sd'field'type = 4 or    <<real   >>
  1634.          sd'field'type = 7 then  <<logical>>
  1635.          ascii(sd'field'bytelen/2,10,outbuf'(32))
  1636.       else
  1637.       if sd'field'type = 5 then  <<packed>>
  1638.          ascii(sd'field'bytelen*2,10,outbuf'(32))
  1639.       else
  1640.          ascii(sd'field'bytelen,10,outbuf'(32));
  1641.       ascii(sd'field'offset+1,-10,outbuf'(39));
  1642.       if sd'version = "@B.00.00" then
  1643.       begin
  1644.          format'sort'key(0);
  1645.          format'date'type;
  1646.          format'decplaces;
  1647.       end'if;
  1648.       print'outbuf(0);
  1649.    end'subr;   <<print'field'desc>>
  1650. $page "formselfdesc/mainline"
  1651.  
  1652.    formselfdesc := false;
  1653.  
  1654.    if sd'filenum <> 0 then
  1655.    begin
  1656.       if file'is'sd then
  1657.       begin
  1658.          read'label(file'userlabels-1);
  1659.          move sd'header := current'label,(sd'label'len);
  1660.          current'labelnum := file'userlabels - 1;
  1661.          print'header(0);
  1662.          field'index      := 0;
  1663.          while get'field(0) do
  1664.             print'field'desc(0);
  1665.          print'trailer(0);
  1666.          formselfdesc := true;
  1667.       end'if;
  1668.    end'if;
  1669.  
  1670. error'exit:
  1671.  
  1672. end'proc;   <<formselfdesc>>
  1673. .font 0.opt
  1674.  
  1675. .sub |1A Different Structure|
  1676. `|1A Different Logical Structure|
  1677.  
  1678. Our HowMessy/Loadfile example had a number of separate
  1679. procedures. Our Formselfdesc procedure is self-contained,
  1680. but we will describe each subroutine in this procedure.
  1681.  
  1682. .sub |1Variables|
  1683. `|1Formselfdesc Variables|
  1684.  
  1685. We include our standard files for the global self-describing
  1686. equates, header layout, and field layout.  We also have a
  1687. number of local variables that are used for indexing through
  1688. the field labels and other variables needed to enhance the
  1689. output listing (e.g., the number of records in the
  1690. self-describing file).
  1691.  
  1692. .sub |1File'Is'SD|
  1693. `|1File'Is'Sd|
  1694.  
  1695. This is our standard |1sd'file| procedure, rewritten to work as a
  1696. stand-alone SPL subroutine.  |1File'Is'Sd| looks after calling the
  1697. |1file'info| subroutine which calls Fgetinfo.  We initialize a
  1698. number of variables during the |1file'info| call. Some of these
  1699. are used for obtaining self-describing information and some are
  1700. used to enhance the format of our form output (e.g., the filename
  1701. and the file limit).
  1702. .skip 1
  1703. .sub |1Read'Label|
  1704. `|1Read'Label|
  1705.  
  1706. The basic strategy we use in this routine is to read a specific
  1707. label into a buffer called |1current'label|.  We then move this
  1708. label to the appropriate self-describing header or field buffer.
  1709. |1Read'label| is careful to check for file
  1710. system errors and abort if it finds any.
  1711.  
  1712. .sub |1Get'Field|
  1713. `|1Get'Field|
  1714.  
  1715. This subroutine is the key to understanding self-describing
  1716. files. When |1get'field| is called the last label of the file has
  1717. been read into the |1sd'header| record. The variable
  1718. |1field'index| is initialized to zero is used as a counter of
  1719. self-describing files.  Each call to |1get'field| returns one
  1720. field description in the |1sd'field| record.
  1721.  
  1722. When first called, |1current'labelnum| contains the number of the
  1723. last label (minus one, since MPE numbers labels starting at
  1724. zero).  We check to see if we need to read in a new label with
  1725. the statement:
  1726.  
  1727. .font 5
  1728.      if (field'index mod sd'fieldsperlabel) = 0 then
  1729. .font 0
  1730.  
  1731. Note that we use |1sd'fieldsperlabel| as the divisor. This is the
  1732. value from our |1sd'header| record and not our equate that we use
  1733. when creating self-describing files. |1Get'Field| assumes that the
  1734. current label record is in the buffer |1current'label|.
  1735.  
  1736. Each user label contains one or more field descriptions (in most
  1737. cases there are eight per label). We compute an offset in the
  1738. label where the current field description is and then we move
  1739. the field description from |1current'label| to our |1sd'field|
  1740. record.
  1741.  
  1742. `|1Print'Field'Desc|
  1743.  
  1744. This routine looks after printing out the description of one
  1745. field.  We use the same routine whether we are dealing with
  1746. Query ("@A.00.00") or Robelle ("@B.00.00") self-describing
  1747. files.  We do have to adjust the byte length for "@B.00.00"
  1748. self-describing fields, so that the output looks similar to
  1749. what Query would produce for an IMAGE dataset.  Note how the
  1750. |1format'type| routine handles IEEE floating point for either
  1751. type of self-describing file.
  1752.  
  1753. For "@B.00.00" self-describing files, we can produce extra
  1754. information.  This is handled by the |1format'sort'key|,
  1755. |1format'date'type|, and |1format'decplaces| routines (which are
  1756. only called for "@B.00.00" self-describing files.
  1757.  
  1758. .sub |1Format'Sort'Key|
  1759. `|1Format'Sort'Key|
  1760.  
  1761. The sort information is stored in the |1sd'header| record as an
  1762. offset, a length, and a type. There is no direct way for us to
  1763. tell that a field is sorted. Instead, we index through all of the
  1764. sort keys checking if the sort key matches the current field
  1765. definition (there might not be a match).  We use the index into
  1766. the sort information as our key to print for the user.
  1767.  
  1768. .sub |1Field'Is'Sorted|
  1769. `|1Field'Is'Sorted|
  1770.  
  1771. To make our code clearer, we encapsulate the code for checking if
  1772. a specific sort key matches the current field in a subroutine. By
  1773. giving this subroutine a descriptive name, we make the intent of
  1774. the |1format'sort'key| routine clearer.  Our |1field'is'sorted|
  1775. routine checks that the offset (adjusted appropriately for
  1776. one-based and zero-based offsets) and the byte length of the
  1777. field and the sort key match.  We decided to ignore the data type
  1778. (the sd'type and the sort'type have different values).
  1779.  
  1780. `|1Summary|
  1781.  
  1782. It's harder to understand self-describing files than it is to
  1783. create them.  When creating self-describing files you often only
  1784. use a few of the self-describing features, but when understanding
  1785. them there are no features that you can leave out.
  1786. .tit
  1787. .page.sub
  1788.  
  1789. .skip 3.opt(l- r- f-)
  1790. `|3KSAM Self-Describing Files|
  1791. .opt.skip 2
  1792. .tit Adopting Self-Describing Files
  1793. .ent `KSAM Self-Describing Files
  1794.  
  1795. Self-describing KSAM files are a little trickier to deal with.
  1796. The 1084 filecode used for self-describing MPE files doesn't
  1797. work well for KSAM. It is more difficult
  1798. to create a new KSAM file, since all of the key information must
  1799. be passed to Fopen.  Here are a few hints for creating and
  1800. understanding self-describing KSAM files.
  1801.  
  1802. .sub |1SD (1084) Filecode|
  1803. `|1SD (1084) Filecode|
  1804.  
  1805. You can create a KSAM file with a filecode of 1084, but the
  1806. resulting :listf,2 gives no hint that the file is a KSAM
  1807. file.  Here's an example Build Command of a compatibility-mode
  1808. KSAM file with a filecode of 1084 and the resulting :listf,2.
  1809.  
  1810. .font 5.page 5.opt(f-)
  1811. :run ksamutil.pub.sys
  1812. >build file1;rec=-80,16,f,ascii;keyfile=file1key; &&
  1813.              key=i,6,2;code=1084
  1814. >exit
  1815. :listf file1&@,2
  1816.  
  1817. FILENAME  CODE  ----------LOGICAL RECORD---------  ----SPACE----
  1818.                   SIZE  TYP      EOF    LIMIT R/B  SECTORS #X MX
  1819.  
  1820. FILE1     SD       80B  FA         0     1023  16       48  1  *
  1821. FILE1KEY  KSAMK   128W  FB        98       98   1      112  1  8
  1822. .font 0.opt
  1823.  
  1824. Notice how there is no way to identify |5file1| as being a KSAM file.
  1825. For this reason, we don't use the 1084 filecode on self-describing
  1826. KSAM files.
  1827.  
  1828. .sub |1Creating KSAM|
  1829. `|1Creating KSAM Self-describing Files|
  1830.  
  1831. We use three steps to create self-describing KSAM
  1832. files:
  1833. .mar(l+3)
  1834. `1.#4Compute the number of labels (used in the KSAMUTIL
  1835. or MPE/iX build
  1836. command).
  1837. You could use our |1sd'compute'labels|
  1838. subroutine or you can compute the number of labels as the
  1839. truncated value of:
  1840.  
  1841.     #6|5labels = (&#fields + 7) / 8 + 11|
  1842. `2.#4Build your KSAM/V file with KSAMUTIL and
  1843. specify Labels=[the number computed above].
  1844. For KSAM/XL, use the Build Command with the userlabel keyword |5;ULABEL=|2x|
  1845. (where |2x|0 is the number computed above).
  1846. `3.#4Fopen the file as an old file with write access.
  1847. .mar
  1848.  
  1849. The most difficult part is computing the number of labels.
  1850. For example, if we have eight fields:
  1851.  
  1852. .beginkey example
  1853. .font 5
  1854.      Labels = (8 + 7) / 8 + 11
  1855.             = 12
  1856.    |0MPE V/E:|
  1857.  
  1858.      :run ksamutil.pub.sys
  1859.      >build file2;rec=-80,16,f,ascii;keyfile=file2k; &&
  1860.       key=i,6,2,,duplicate;labels=~12~
  1861.      >exit
  1862.  
  1863.    |0MPE/iX:|
  1864.  
  1865.      :build file2;rec=-80,16,f,ascii;key=(i,6,2,dup); &&
  1866.                   ksamxl;ulabel=~12~
  1867.  
  1868. .font 0
  1869.  
  1870. .sub |1Understanding KSAM|
  1871. `|1Understanding KSAM Self-Describing Files|
  1872.  
  1873. Our |1sd'file| routine returns true if a given file is
  1874. self-describing. If you examine the code in this routine
  1875. carefully, you'll see that for KSAM files we have the
  1876. statements:
  1877.  
  1878. .font 5
  1879.      if file'foptions.(2:3) = 1 or file'foptions.(2:3) = 3 then
  1880.         if file'userlabels > 1 then
  1881. .font 0
  1882.  
  1883. Note that we check for more than one user label.  Why don't we
  1884. check for more than zero user labels?  All self-describing files
  1885. must have at least two labels (one for the header information and
  1886. one or more for the field information). When we first implemented
  1887. our |1sd'file| routine we only checked for more than zero user
  1888. labels.  What we found was that many users had accidentally built
  1889. KSAM files with one user label (which was almost always empty).
  1890. We have no idea why this seemed to be so common, but by
  1891. checking for at least two labels we eliminated a lot of KSAM
  1892. files that were not self-describing.
  1893. .tit
  1894. .page.sub
  1895.  
  1896. .opt(l- r- f-)
  1897. `|3Future Self-Describing Formats|
  1898. .opt.skip 2
  1899. .tit Future Formats
  1900. .ent `Future Self-Describing Formats
  1901.  
  1902. We were motivated to create the new Robelle format
  1903. self-describing files in order to provide a better interface
  1904. between our product Suprtool and ASKPlus from ARES of France.
  1905. Pierre Senant of ARES is the R&&D Manager and the two of us
  1906. worked out the "@B.00.00" self-describing format (actually we
  1907. forced most of the format on poor Pierre).
  1908.  
  1909. ARES have been doing significant R&&D work on UNIX and a
  1910. portable version of ASKPlus.  As an example of how far we can
  1911. go with self-describing information, here is an extract of
  1912. Pierre's design for a UNIX implementation of self-describing
  1913. files.
  1914.  
  1915. .sub |1SDASK Files|
  1916. `|1SDASK Files|
  1917.  
  1918. A C/ISAM file is composed of two files, a data file and an
  1919. index file.  An SDASK file defines another file called a
  1920. 'label file' which contains the complete description of the data
  1921. file.  Like MPE self-describing files, an SDASK file is composed
  1922. of a header portion and a description of each field.
  1923. The data file and the label file must be located in the same
  1924. directory.
  1925.  
  1926. .sub |1Header Format|
  1927. `|1Header Format|
  1928.  
  1929. Pierre's header contains a lot more information than our MPE
  1930. header label.  Here are the parts of the header:
  1931. .mar(l+3)
  1932. `*#4Version number.
  1933. `*#4File code (1085).
  1934. `*#4Checksum (currently unused).
  1935. `*#4Number of fields per record.
  1936. `*#4Record length (in bytes).
  1937. `*#4Number of records.
  1938. `*#4Number of sort keys.
  1939. `*#4Password.
  1940. `*#4Total field area length (in the SDASK file).
  1941. `*#4File type (flat, C/ISAM, KSAM, Unibol, ...).
  1942. `*#4Data file name.
  1943. `*#4Unibol area (for migration from IBM/36 to UNIX)
  1944. `*#4Filter:  logical expression defining a condition that must
  1945. be True for the entries taken into account.  Originally
  1946. developed for Unibol files, but this feature can be used for
  1947. any other system.
  1948. .mar
  1949.  
  1950. .sub |1Field Description|
  1951. `|1Field Description|
  1952.  
  1953. Each field description is variable length:
  1954. .mar(l+3)
  1955. `*#4Field type (U, X, I, J, K, P, R). Additional information for
  1956. Ascii fields are: Roman-8, PC-8, ANSI-8, Mac-Apple, EBCIDC, and
  1957. ISO7-1@...@ISO7-13.  For Integer fields, there is additional
  1958. information for Intel versus HP.  For Real fields, there is
  1959. additional information for IEEE versus Classic.
  1960. `*#4Length (in bytes).
  1961. `*#4Offset.
  1962. `*#4Scale (number of decimal places).
  1963. `*#4Repeat factor.
  1964. `*#4Flags:
  1965. .mar(l+3)
  1966. `*#7Null value allowed.  If this flag is True, each entry in the
  1967. data file is preceded by a bitmap field. Each bit
  1968. indicates whether the corresponding field value is Null or not.
  1969. `*#7Hidden field.
  1970. `*#7Key.
  1971. `*#7Duplicate key allowed.
  1972. .mar
  1973. `*#4Field name length.
  1974. `*#4Field name.
  1975. `*#4Title length.
  1976. `*#4Title.
  1977. `*#4Edit mask length.
  1978. `*#4Edit mask.
  1979. `*#4Key file name length.
  1980. `*#4Key file (reserved for future implementation on MS-Dos).
  1981. .mar
  1982.  
  1983. .sub |1Sort Information|
  1984. `|1Sort Information|
  1985.  
  1986. Sort descriptors are also variable length:
  1987. .mar(l+3)
  1988. `*#4Expression length.
  1989. `*#4Sort expression (in ASKPlus syntax).  For example,
  1990. .font 5
  1991.      cust-name
  1992.  
  1993.      cust-zipcode cat cust-address
  1994. .font 0
  1995. `*#4Flag:  ascending/descending.
  1996. .mar
  1997. .tit
  1998. .page.sub
  1999.  
  2000. .opt(l- r- f-)
  2001. `|3Conclusion|
  2002. .opt.skip 2
  2003. .tit Adopting Self-Describing Files
  2004. .ent `Conclusion
  2005.  
  2006. Self-describing files are a great idea. As users, we almost
  2007. always create MPE and KSAM files with a fixed record structure
  2008. in mind.  By default, this record structure is lost when we build
  2009. a file.  With self-describing files, we can retain the structure
  2010. of our files.
  2011.  
  2012. .sub |1A Final Example|
  2013. `|1A Final Example|
  2014.  
  2015. The HP 3000 has a rich set of tools based on IMAGE. One reason
  2016. that so many good tools could be written for IMAGE was the DBINFO
  2017. intrinsic. This intrinsic let any program discover the structure
  2018. of an IMAGE database. Self-describing files provide the same
  2019. flexibility for MPE and KSAM files.
  2020.  
  2021. In this example, we show how two tools can be combined by using
  2022. self-describing files.  Our HowMessy program reports on database
  2023. efficiency.  While doing so it creates a self-describing file with
  2024. the statistics for a database.  Once you have this file, it's possible
  2025. to use Suprtool to check for certain boundary cases.  For example,
  2026.  
  2027. .font 5.page 5
  2028.      :run howmessy.pub.robelle          |0{create "loadfile"}|
  2029.  
  2030.      Enter database:  test.suprtest
  2031. .font 0
  2032.  
  2033. HowMessy creates the self-describing file called Loadfile (with the
  2034. structure that we've shown previously).  We now use Suprtool to
  2035. create a file that has all detail datasets that are more than 85% full
  2036. that also have a capacity greater than one:
  2037.  
  2038. .font 5.page 5
  2039.      :run suprtool.pub.robelle
  2040.      >input   loadfile
  2041.      >if      datasettype = "D"  and   &&   |0{detail dataset}|
  2042.               capacity    > 1    and   &&
  2043.               loadfactor  > 85.00          |0{more than 85% full}|
  2044.      >output  loaddetl,link                |0{create SD file}|
  2045.      >exit
  2046. .font 0
  2047.  
  2048. At Robelle, we would use our Xpress electronic mail system to mail
  2049. the |5loaddetl| file to the system manager.  Another alternative
  2050. would be to extract the database and dataset names and use them
  2051. to create a batch job to automatically increase the capacity of
  2052. detail datasets more than 85% full.  The possibilities are endless,
  2053. but only because HowMessy could provide information to Suprtool via
  2054. the self-describing file.
  2055.  
  2056. .sub |1Software Tools|
  2057. `|1Software Tools|
  2058.  
  2059. Few software tools are capable of creating or understanding
  2060. self-describing files.  This is a shame, since self-describing files
  2061. are a powerful data structure.  One reason that so few tools handle
  2062. self-describing files is that documentation on self-describing files
  2063. has been non-existent.
  2064. I hope that by publishing this description and the programming
  2065. examples in this paper that more vendors and users start creating
  2066. and accepting self-describing files.
  2067. .if not outtext
  2068. .tit
  2069. .sub
  2070. .for([// l50 //])
  2071. .page
  2072. .if outdouble
  2073. .align
  2074. .endif
  2075. .count 3
  2076. .contents.com reset
  2077. .com The FORM that follows is for Contents/Preface:
  2078. .for([ // l55 / #33 pr:3 /])
  2079.  
  2080. .skip 8
  2081. .opt(l- r- f-)
  2082. |3Adopting Self-Describing Files|
  2083.  
  2084. .skip 3
  2085. |3Contents|
  2086. .opt
  2087. .skip 2
  2088. #4|1Page   Topic|
  2089. .mar(l+4)
  2090. .tit |1Contents|
  2091. .sub |1Page   Topic|
  2092. .for([ 'Adopting Self-Describing Files' #26 T:40 // #4 S // l53 /#33 pr:1 /]
  2093. +    [ T #26 'Adopting Self-Describing Files':40 // #4 S // l53 /#33 pr:1 /])
  2094. .contents( l3 p+)
  2095. .mar
  2096. .endif.com if not outtext  {skip table of contents}
  2097.