home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / advrexx.zip / ADVREXX.TXT
Text File  |  1995-10-24  |  85KB  |  1,750 lines

  1. Advanced REXX Programming Topics
  2.  
  3. The entire contents of this paper are Copyright (c) 1994-5 by
  4. Charles Daney - All Rights Reserved. Permission is granted to disseminate
  5. this paper electronically as long as no changes are made. The paper may
  6. not be republished in hard copy form without explicit permission from
  7. the author, whose email address is 75300.2450@compuserve.com.
  8.  
  9. This paper, future updates (if any), and much additional information
  10. on the REXX language are available from Quercus Systems Web pages -
  11. http://www.quercus-sys.com.
  12.  
  13. Introduction
  14. ------------
  15.  
  16. REXX is one of the great under-appreciated treasures of OS/2. Although
  17. REXX as a programming language is now 15 years old and REXX has been
  18. implemented on just about every major computer platform, many OS/2 users
  19. will be encountering it for the first time. And when it is encountered
  20. for the first time in OS/2, users are often introduced to REXX as just a
  21. modern replacement for the primitive Microsoft-designed "batch" language.
  22.  
  23. In fact, REXX is a very good "batch" or "procedure" language, in that it
  24. can be used to automate repetitive sequences of operating system
  25. commands that need to be used together in a "batch". But REXX brings all
  26. of the features of a full-fledged programming language to this task -
  27. variables, arithmetic, input/output, and control structures. These
  28. features make it possible to do things easily in REXX which could be
  29. done in the old batch language (if they could be done at all) only with
  30. very arcane and convoluted techniques. Many computer columnists and book
  31. authors at one time wrote endless streams of words describing yet another
  32. clever trick to make the batch language perform tasks it simply wasn't
  33. designed to do - tasks that are almost trivial in REXX.
  34.  
  35. Many OS/2 users are introduced to REXX for the first time when they come
  36. across a simple REXX procedure to manipulate features of the Workplace
  37. Shell - such as creating folders, adding background bitmaps, modifying
  38. OS/2 configuration parameters, or changing system fonts. Other users
  39. encounter REXX for the first time as a tool for writing installation
  40. programs for various applications. These are all excellent examples of
  41. REXX used as a "batch" language.
  42.  
  43. Unfortunately, this usage of REXX may have obscured the fact that, since
  44. REXX is a complete programming language, it can actually do much more.
  45. Recently, several tools for visual programming have become very popular
  46. in OS/2 - VX-REXX from Watcom, VisPro/REXX from Hockware, and GpfRexx
  47. from Gpf Systems. As the product names imply, each of these products uses
  48. REXX as its underlying development language. So OS/2 users who first
  49. encountered REXX as a better batch language also discovered that it is
  50. the foundation of powerful tools for visual programming.
  51.  
  52. It is possible, using tools like these, for people with little detailed
  53. knowledge of GUI programming (or perhaps of any kind of programming) to
  54. quickly learn to develop professional-looking applications that use the
  55. full toolbox of OS/2 Presentation Manager elements, such as dialogs,
  56. push buttons, list boxes, notebooks, and containers. The user interface
  57. builders do most of the work of automatically generating REXX code to
  58. create the desired interface. It is necessary for the developer to write
  59. only a relatively small amount of REXX code to create a customized
  60. application. This makes it natural to use REXX for personal programming
  61. or application prototyping in a GUI environment.
  62.  
  63. But there are even more surprises for OS/2 users as far as REXX is
  64. concerned. In fact, OS/2 users are just now discovering that many OS/2
  65. applications have adopted REXX as their primary or alternate "scripting"
  66. language. The list of such "REXX-enabled" applications now includes:
  67.  
  68.     Text editors
  69.     ---- -------
  70.     KEDIT (Mansfield Software)
  71.     SPF/2 (Command Technology)
  72.     Tritus SPF (Tritus)
  73.     EPM (IBM)
  74.     SourceLink (One Up Corporation)
  75.  
  76.     Communication software
  77.     ------------- --------
  78.     REXXTERM (Quercus Systems)
  79.     PMCOMM (Multinet)
  80.     TE/2 (Oberon Software)
  81.     Extra! (Attachmate)
  82.     Communications Manager (IBM)
  83.     TCP/IP (IBM)
  84.  
  85.     Word processors
  86.     ---- ----------
  87.     Ami Pro (Lotus)
  88.     DeScribe (DeScribe)
  89.  
  90.     Database tools
  91.     -------- -----
  92.     DB2/2 (IBM)
  93.     dbfREXX (dSoft Development)
  94.     REXXBASE (American Coders)
  95.     QELIB (Q+E Software)
  96.     XDB-QMT (XDB Systems)
  97.  
  98.     Other
  99.     -----
  100.     1-2-3 (Lotus)
  101.     MMPM/2 (IBM)
  102.     Deskman/2 (Development Technologies)
  103.     Fax/PM (Microformatic)
  104.     Chron (Hilbert Computing)
  105.  
  106. Clearly something is going on here - REXX is being adopted as a
  107. universal macro language by sophisticated OS/2 applications. The most
  108. obvious benefit of this is that users no longer need to learn a new
  109. language to control each application - one common language can do it
  110. all. In the past, the very term "macro language" has been daunting to
  111. many users because such languages have tended to be obscure and
  112. difficult to use since they were designed with only a limited purpose in
  113. mind. And this is on top of the fact that they were different for each
  114. application. REXX makes a big improvement here. While it is not true
  115. that there is an insignificant learning curve to REXX, the language is
  116. still fairly natural and intuitive. But most importantly, once it has
  117. been mastered, one doesn't have to learn it all over again for a new
  118. application.
  119.  
  120. There is a second major advantage to having REXX as a universal macro
  121. language. This is the fact that it puts REXX in the position of being
  122. able to communicate with any REXX-enabled application. And hence, every
  123. such application can communicate with any other one through REXX. So
  124. REXX becomes a sophisticated, programmable, interprocess communication
  125. tool.
  126.  
  127. The bottom line of all this is that REXX is not just a better batch
  128. language, or a tool for visual programming and rapid application
  129. development, or a universal macro language. It is all of these things at
  130. the same time, so perhaps the best way to think of REXX is as a gateway
  131. to most of the facilities of OS/2 and among all REXX-enabled
  132. applications. REXX gives programmers access to most OS/2 services, like
  133. the Workplace Shell, multimedia, interprocess communication, and the
  134. file system. But because REXX is also accessible from applications, it
  135. makes all these services available to the applications, and it makes
  136. the services of each application available to others. A REXX-aware
  137. application does not (necessarily) need to provide its own support
  138. for multimedia, serial communications, or database services, because
  139. it can utilize REXX scripts to get at these capabilities. REXX can
  140. be thought of as "application glue".
  141.  
  142. With all this as prologue, it is not difficult to understand why so many
  143. OS/2 users have already taken the time to become at least passingly
  144. familiar with REXX (and many more will in the near future). Now, REXX
  145. was designed to be easy to use, but there's no point in pretending it
  146. is effortless, since it isn't. Prospective users of REXX should be
  147. prepared for a learning curve and should allow some time to get
  148. reasonably proficient with it - anywhere from a few days to a few weeks
  149. depending on the individual and his or her background. The good news is
  150. that, once proficiency is gained, the investment can be reused again and
  151. again because REXX is so versatile.
  152.  
  153. In the rest of this paper, we will assume that the reader has already
  154. attained some level of comfort in understanding and using REXX, because
  155. the purpose here is to provide an introduction to some advanced topics
  156. in REXX. Just as with the initial learning of the language, one will be
  157. repaid over and over by the mastery of some of these advanced
  158. techniques, because they are (potentially) equally useful in a complex
  159. VX-REXX based application, an Ami Pro macro, or a personal utility
  160. batch program.
  161.  
  162. Just in case you don't feel you already have a good grounding in the
  163. fundamentals of REXX, we'll mention a few resources you can turn to for
  164. review. (See the Bibliography for full details.) First, online
  165. documentation for REXX comes with OS/2. But hard copy, including a
  166. tutorial that isn't online, can be purchased from IBM. _The REXX
  167. Language_ by Mike Cowlishaw (who invented the language) is a very good
  168. language definition and complete reference on the rules. (A lot of IBM's
  169. own documentation is straight out of this book.) The present author's
  170. book, _Programming in REXX_, is recommended for readers who want a more
  171. detailed explanation of REXX concepts and general programming
  172. techniques. These two books deal with REXX in general and do not refer
  173. to OS/2 specifically.
  174.  
  175. There are already several books that do cover REXX explicitly from an
  176. OS/2 perspective. Two of the best are the _OS/2 2.1 REXX Handbook_ by
  177. Hal German and _Application Development Using OS/2 REXX_ by Tony Rudd.
  178. Finally, every even halfway serious REXX programmer should have a copy
  179. of the _REXX Reference Summary Handbook_ by Dick Goran. This last is not
  180. a textbook but rather a handy reference summary filled with the
  181. essential information about REXX, several REXX function libraries, and
  182. details of parameters used to control the Workplace Shell.
  183.  
  184. Data Structure, Program Structure
  185. ---- ---------- ------- ---------
  186.  
  187. It's possible to become proficient with REXX simply by learning the
  188. relatively simple syntax rules of the language, studying a few good
  189. sample programs, and proceeding to actually use REXX in creating
  190. prototypes or full applications that are useful to yourself personally
  191. or to your company. Any one of the visual REXX programming tools
  192. mentioned before can be highly recommended to assist in the learning
  193. process. They do a lot of the work for you and teach you many of the
  194. standard REXX rules and techniques as you go. They also include
  195. their own debuggers, which can show you exactly how REXX programs
  196. operate.
  197.  
  198. However, because REXX is a full and complete programming language,
  199. becoming really good at it requires that you learn a few key concepts as
  200. well. Some of these concepts are shared by other programming languages,
  201. but there are others that are more or less unique to REXX itself.
  202.  
  203. The concepts we want to focus on have to do with data structure and
  204. program structure. In both cases, there are significant differences in
  205. how these structures are used in REXX as compared to other languages.
  206.  
  207. Data structures have to do with how program data is organized. Like
  208. other languages, REXX has variables. But REXX is rather different in how
  209. it organizes collections of data. Unlike most languages, REXX does not
  210. have arrays in the usual sense, but it does have a much more powerful
  211. structure called "compound" variables. Although such compound variables
  212. can be a little more awkward to work with than ordinary arrays, they can
  213. also do a lot more. A compound variable is something like an array that
  214. may have non-numeric subscripts. Sometimes this is called an
  215. "associative" array, because data items can be retrieved by their
  216. "association" with other data items. Another way to think of this is
  217. that it's like looking up a record in a database on the basis of some
  218. key value, e. g. a person's name or a book's title.
  219.  
  220. Another thing that is definitely lacking in REXX is the notion of a
  221. "structure" in the narrow sense of a C structure or Cobol record. This
  222. can be a major stumbling block for would-be advanced users of REXX.
  223. C structures are often used for one of two things. They may
  224. represent "records", which are collections of related data, such as
  225. information related to a particular employee. There are relatively
  226. simple (though somewhat clumsy) ways to simulate records in REXX using
  227. compound variables.
  228.  
  229. C structures can also be employed to build up more complex data objects
  230. using techniques involving linked lists, trees, and so forth. Such
  231. intermediate level data structures can in turn be used to represent
  232. fairly high-level data abstractions like sets, collections, tables, and
  233. the like. It becomes rather awkward to use REXX compound variables to
  234. simulate linked lists directly, and even harder to use these to
  235. implement the high-level abstractions.
  236.  
  237. But fortunately, it is often possible to succeed by rethinking the whole
  238. problem in terms of native REXX facilities. Many high-level abstractions
  239. can be handled in REXX without working with lower-level representations
  240. at all. A lot of the discussion here will explain how to do this.
  241.  
  242. When we come to discuss program structure here we will not say very much
  243. about traditional concepts of "structured" programming - loops, subroutines
  244. and functions, handling of alternative cases. The REXX facilities in this
  245. respect are largely similar to what exists in other languages like C or
  246. PL/I. Instead, we will look at how REXX applications are organized in
  247. one or more files and how these semi-independent pieces can communicate
  248. and share data. This is an issue that often does not even arise with other
  249. languages, where programs are traditionally all linked into a single
  250. executable file.
  251.  
  252. REXX applications, in contrast, often consist of separate program units
  253. that are never explicitly linked together at all. Sometimes the relation
  254. between such units is the simple one of caller and callee, in much the
  255. same way that subroutines and functions can be used within a single
  256. source file. But sometimes the relation can be more complicated,
  257. particularly in OS/2 with its sophisticated multithreading abilities.
  258. The relationship of different program parts can become even more complex
  259. when visual REXX programming tools are used, since there may be one or
  260. more pieces of REXX code associated with each event that can occur for a
  261. specific interface element - such as a button press.
  262.  
  263. Keeping persistent data associated with a single REXX program, and
  264. sharing data between independent REXX programs, can become a very tricky
  265. problem, but a solvable one. And this is the other issue we plan to
  266. address here.
  267.  
  268. There is, moreover, a significant interaction between data structure and
  269. program structure. Specifically, you have to consider how to represent
  270. data in REXX not only in light of how it will be used internally, but
  271. also in terms of how it may be shared between independent REXX programs.
  272.  
  273. Compound variables
  274. -------- ---------
  275.  
  276. Arrays are the most commonly used data structure in programming.
  277. Although REXX does not have arrays as such, compound variables can
  278. usually be used like an array, although with some occasional
  279. syntactical awkwardness.
  280.  
  281. To review, a compound variable is one whose name is derived from a
  282. compound symbol, that is a symbol which begins with a legal symbol
  283. character other than a number or a period, contains at least one period,
  284. and at least one character following the last period. The following are
  285. all legal compound symbols:
  286.  
  287.     array.i
  288.     restaurant..address
  289.     a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z
  290.  
  291. The part of a compound symbol up to and including the first period is
  292. called the "stem". It is always used literally. All other simple symbols
  293. occurring in a compound symbol, i. e. the symbols delimited by the
  294. periods, are replaced with their current values in forming the name of a
  295. compound variable. In this respect they are analogous to the subscripts
  296. of arrays in other languages. Indeed, it is possible to think of a
  297. symbol like A.i.j.k as equivalent to an array element, which would be
  298. expressed as A[i][j][k] in C.
  299.  
  300. There are, however, significant differences between such "arrays" in
  301. REXX and arrays in other languages. Some of these differences represent
  302. advantages of REXX, but others are disadvantages. On the positive side,
  303. because of REXX's dynamic memory management, it is never necessary to
  304. declare in advance how large an array will be. It simply grows as
  305. needed, and (usually) does not consume storage for "unused" elements of
  306. the array.
  307.  
  308. In fact, a REXX array does not even have a specific "dimension" like an
  309. array in other languages. This is because the periods in a compound
  310. symbol have syntactic meaning only in the symbol. Once the name has been
  311. derived by substituting all values of simple symbols, there are really
  312. only two parts to it: the stem and the "tail". For instance if we have
  313.  
  314.     i = 3
  315.     j = 4
  316.  
  317. then the symbol A.i.j actually consists of just the stem, which is "A."
  318. and the tail, which is "3.4". At this point, the fact that the tail
  319. still contains a period is irrelevant. So, if we also have
  320.  
  321.     x = 34
  322.     y = 10
  323.     z = x/y
  324.  
  325. then the symbol A.z refers to exactly the same piece of data, because z
  326. has the value "3.4". Useful programs can actually be written that take
  327. advantage of this ambiguity of the "dimension" of a REXX array.
  328.  
  329. The tail of a REXX variable can consist of completely arbitrary data,
  330. including blanks and unprintable ASCII characters. In contrast to the
  331. usual situation in REXX, blanks are significant in a variable tail. Thus
  332. if
  333.  
  334.     x = ""
  335.     y = " "
  336.     z = "  "
  337.  
  338. then A.x, A.y, and A.z refer to three completely different data items,
  339. even though x, y, and z are "equal" when compared with the normal
  340. comparison operators. This is another respect in which REXX "arrays"
  341. differ from those in other languages.
  342.  
  343. In some ways, however, the REXX array notation is not as powerful, or at
  344. least as convenient, as the notation of other languages. In particular,
  345. it is not possible to have expressions in a REXX "subscript". For
  346. instance, A.i+j is the sum of A.i and j, instead of an array element
  347. with the subscript i+j. Even parentheses cannot be used to circumvent
  348. this problem, since A.(i+j) is actually a function call to a function
  349. named "A.".
  350.  
  351. This notation is usually the most inconvenient when you simply want to
  352. use another compound symbol as a subscript. So if i.j is a value you
  353. wish to use as a "subscript", you cannot just refer to A.i.j. You must
  354. assign i.j to a simple variable first:
  355.  
  356.     x = i.j
  357.     say A.x
  358.  
  359. Despite these syntactical inconveniences, the great power of REXX's
  360. notation lies in the fact that "subscripts" can be non-numeric. This
  361. allows you to build data structures which easily associate data values
  362. with data names. Suppose, for instance, that you want to work with a
  363. database of books. In REXX you can do this by having a number of arrays,
  364. each of which is subscripted by the name of the book. The names of these
  365. arrays might be "author", "date", "publisher", "ISBN", and so forth.
  366. Then if the name of a particular book is stored in the variable "title",
  367. you can retrieve all of the other information directly be referring to
  368. author.title, publisher.title, etc. Because of this direct association
  369. from a name to a value, such data structures are sometimes called
  370. "associative arrays".
  371.  
  372. As far as the language user is concerned, there is no search process at
  373. all involved in looking up the author of a given book. In reality, of
  374. course, REXX does need to do a search to find each piece of data. The
  375. advantage is that this search process is all built-in and transparent to
  376. the user.
  377.  
  378. To continue with the example, the collection of variables indexed by the
  379. book name is, in effect, a data structure much like a table. The rows of
  380. the table are labelled by book names, and the columns of the table have
  381. labels like "author", "publisher", etc. This is very typical of a more
  382. complex data structure in REXX: while it's conceptually a single object,
  383. it is actually composed of a number of REXX compound variables that have
  384. been "subscripted" in the same way.
  385.  
  386. Concretely, you would select appropriate compound variable stem names
  387. for each "column" of the table. For example:
  388.  
  389.     book_author.
  390.     book_publisher.
  391.     book_date.
  392.     book_isbn.
  393.  
  394. Perhaps, if you do some programming in C where case is significant, you
  395. might prefer to use:
  396.  
  397.     BookAuthor.
  398.     BookPublisher.
  399.     BookDate.
  400.     BookISBN.
  401.  
  402. Just remember that case is ignored in REXX stem names, so that
  403. "bookauthor." (for example) is not a different name.
  404.  
  405. In practice, you will have data on a number of books, and before you
  406. can use it in a program, it has to be loaded from somewhere. The data
  407. may normally be kept in a flat file, for instance. Unless you are
  408. importing the data from another source that has already determined a
  409. file format, you are relatively free to structure the data file any way
  410. you want. Let's say you decide to identify each data element with a
  411. tag so that your file contains lines like this:
  412.  
  413.     Title:          Programming in REXX
  414.     Author:         Charles Daney
  415.     Publisher:      McGraw-Hill
  416.     Date:           1992
  417.     ISBN:           0-07-015305-1
  418.  
  419. You may put some sort of delimiter between the lines corresponding
  420. to a single book. Or you may just assume that every time you find a
  421. line beginning with "Title:" is starts the data for a new book. Then
  422. you could use this code to read in the data:
  423.  
  424.     do while lines(input) \= 0
  425.         parse value linein(input) with label ':' data
  426.         data = strip(data)
  427.         label = translate(label)
  428.         select
  429.             when label = 'TITLE' then
  430.                 title = data
  431.             when label = 'AUTHOR' then
  432.                 book_author.title = data
  433.             when label = 'PUBLISHER' then
  434.                 book_publisher.title = data
  435.             when label = 'DATE' then
  436.                 book_date.title = data
  437.             when label = 'ISBN' then
  438.                 book_isbn.title = data
  439.             otherwise nop
  440.             end
  441.         end
  442.     call lineout input
  443.  
  444. This example is fairly straightforward, but we will come to some ways to
  445. simplify it later. As an aside, note the use of the STRIP function to
  446. remove leading and trailing blanks. We did not assume that the "data"
  447. part of the record was a single word, since we may very well want to
  448. have embedded blanks. And we used the TRANSLATE function to make sure we
  449. were working with the label in upper case. Also note that this example
  450. (as with most of the rest) has minimal error checking. We don't check
  451. that the labels are valid, for instance, except to provide an OTHERWISE
  452. case that does nothing in the SELECT statement.
  453.  
  454. Since a table is a typical sort of two dimensional array, you may be
  455. wondering whether it could be represented by using a single compound
  456. variable with two "subscripts". You might think of using a stem "book."
  457. and where one subscript is one of the column labels such as "author"
  458. while the other is the book name. In other words, a table entry would be
  459. referred to as "book.field.book_name", where "field" would be a variable
  460. with a value like "AUTHOR".
  461.  
  462. There are several pitfalls with this approach, which is why we didn't
  463. suggest it to begin with. The first is the fact that you will frequently
  464. want to refer to the elements of a particular column by putting the
  465. column name in explicitly:
  466.  
  467.     say book.author.title
  468.  
  469. in order to display the author of a book if you were given a title. This
  470. would actually work, provided there is no variable named "author" that
  471. has been assigned a value. (Since REXX uses a default value of "AUTHOR"
  472. - note the upper case.) However, if some time previously you had
  473.  
  474.     author = "William Shakespeare"
  475.  
  476. then you would be attempting to reference a compound variable whose tail
  477. begins with "William Shakespeare" instead of "AUTHOR".
  478.  
  479. There are various ways around this problem. For instance, you can always
  480. use an extra variable in the symbol:
  481.  
  482.     field = 'AUTHOR'
  483.     say book.field.title
  484.  
  485. But this is cumbersome, and you also have to be very careful about
  486. alphabetic case, which is significant in the tail of a compound
  487. variable. I. e.
  488.  
  489.     field = 'Author'
  490.     say book.field.title
  491.  
  492. wouldn't work unless you always use 'Author' instead of 'AUTHOR'.
  493.  
  494. Note that one thing you can't do is to use a literal in the compound
  495. variable name:
  496.  
  497.     say book.'AUTHOR'.title
  498.  
  499. doesn't work, since the expression following SAY is the concatenation
  500. of (the value of) book., the literal 'AUTHOR', and ".TITLE".
  501.  
  502. Even if you are careful never to assign anything to "author", there is a
  503. performance penalty with using it, because REXX will try to look up a
  504. value anyway. One thing you could do to get around that is to pick
  505. somewhat odd names for the columns, like '0AUTHOR'. This is actually
  506. legal, and will work, and will not try to perform unwanted substitution:
  507.  
  508.     say book.0author.title
  509.  
  510. but it certainly isn't elegant.
  511.  
  512. There is perhaps just one thing that can be said for the path we have
  513. been exploring where we use just one stem name for this collection
  514. of data. That is, you can more easily refer to the whole collection
  515. somewhat more compactly when you want to pass its name to a subroutine,
  516. or use it with EXPOSE or DROP. E. g.
  517.  
  518.     drop book.
  519.  
  520. makes the whole collection undefined, whereas otherwise you would need
  521. to be much more verbose:
  522.  
  523.     drop book_author book_publisher book_date book_isbn
  524.  
  525. and it could be a lot harder to maintain code that uses names in
  526. this form, since you must make a lot of explicit references to all of
  527. the columns of the table.
  528.  
  529. However, there is one thing that can be done to ameliorate this
  530. problem a little. REXX allows you to use a whole list of names with
  531. DROP and EXPOSE if you assign the list to another variable:
  532.  
  533.     book_stuff = 'book_author book_publisher book_date book_isbn'
  534.     drop (book_stuff)
  535.  
  536. But it's clumsy even so. Still, there is hope. It is possible to avoid
  537. having to refer explicitly to every "column" of the table in some cases.
  538. Earlier we said there was a more compact way to write the code for
  539. reading in the book data. It is based on the fact that the stem part of
  540. a compound variable name can be a variable or computed string if we use
  541. the VALUE function. Here is how the code to read in book data might look
  542. with this approach:
  543.  
  544.     do while lines(input) \= 0
  545.         parse value linein(input) with label ':' data
  546.         if label = '' then
  547.             iterate
  548.         data = strip(data)
  549.         label = translate(label)
  550.         if label = 'TITLE' then
  551.             title = data
  552.         else
  553.             call value 'book_'label'.title', data
  554.         end
  555.     call lineout input
  556.  
  557. This is a lot more compact, and doesn't need to be changed if arbitrary
  558. new types of book information (i. e. columns) are added. VALUE is a
  559. tricky function to understand, but it's very convenient once you get the
  560. hang of it. If it isn't clear, try running the code using TRACE R to get
  561. a feel for what is happening here. (Note that we tested for a null input
  562. line, which might occur at the end of file and could be elsewhere.)
  563.  
  564. There is another, entirely different, difficulty with this table
  565. structure as we have outlined it so far. As we have presented it, the
  566. code and data structure are well-designed for taking a complete book
  567. title and retrieving information about it, such as the book's author,
  568. publisher, etc. All you have to do is reference the data using the
  569. book title as an associative key.
  570.  
  571. You can even tell easily whether a new book to be added to the database
  572. is already included:
  573.  
  574.     if symbol('book_author.title') = 'VAR' then
  575.         say 'Already in database:' title
  576.  
  577. But it is an altogether different matter if you want to use the data
  578. some other way. Perhaps you want to list all books in the database that
  579. have a certain author or a certain character string in their title. Here
  580. we see a major disadvantage of non-numeric subscripts in REXX: there is
  581. no simple way (as there is with numbers) to iterate through "all values"
  582. of the subscript.
  583.  
  584. This is a problem that arises repeatedly when using non-numeric
  585. subscripts. There just isn't any way to find, in standard REXX, all the
  586. variables having a given stem. (Ironically, programs written in other
  587. languages that interface to REXX can do this through the shared variable
  588. interface.) But there are ways around the problem. One way to accomplish
  589. this objective is to make a compromise with the conceptual simplicity of
  590. pure associative arrays and re-introduce numerically subscripted arrays.
  591.  
  592. In our example, what we do is use the stem book_title. to store book
  593. titles. At the same time we build the rest of the table, we also set
  594. book_title.i to the title of book number i. Then, in any circumstance
  595. where we have to search through the whole list of books, we iterate
  596. through values of book_title.i for i=1 to whatever the largest book
  597. number is. Using the name stored in book_title.i we can then retrieve
  598. any of the other information, since it is "subscripted" with that name.
  599.  
  600. Here's how we might write the code to read in the data with this
  601. technique:
  602.  
  603.     count = 0
  604.     do while lines(input) \= 0
  605.         parse value linein(input) with label ':' data
  606.         if label = '' then
  607.             iterate
  608.         data = strip(data)
  609.         label = translate(label)
  610.         if label = 'TITLE' then do
  611.             count = count + 1
  612.             book_title.count = data
  613.             title = data
  614.             end
  615.         else
  616.             call value 'book_'label'.title', data
  617.         end
  618.     call lineout input
  619.     book_title.0 = count
  620.  
  621. Notice that we set the .0 element of the compound variable to the
  622. number of elements in the "array". This is a very common convention
  623. one finds in REXX, though it isn't an official part of the language.
  624.  
  625. Now we have a means of looking up book information either directly
  626. through the book name or by processing the whole table of information
  627. sequentially. But there is yet another problem to consider. It depends
  628. on the details of how REXX compound variables are actually implemented.
  629. Most implementations store the data in binary trees. For processing
  630. efficiency, each node of the tree contains the string value of the
  631. "subscript". If the same string is used as a subscript on many
  632. variables, it may therefore appear many times in storage. With names
  633. that are long (such as the names of books), this can waste a lot of
  634. storage.
  635.  
  636. One last variation of the sample code fixes this:
  637.  
  638.     count = 0
  639.     do while lines(input) \= 0
  640.         parse value linein(input) with label ':' data
  641.         if label = '' then
  642.             iterate
  643.         data = strip(data)
  644.         label = translate(label)
  645.         if label = 'TITLE' then do
  646.             count = count + 1
  647.             index.data = count
  648.             end
  649.         call value 'book_'label'.count', data
  650.         end
  651.     call lineout input
  652.     book_title.0 = count
  653.  
  654. Again the solution is to make another compromise and use numeric
  655. subscripts on most arrays. Since each book is associated with a unique
  656. index (the index in the "book_title" array), we might just as well use
  657. this numeric subscript to index all of the individual columns in the
  658. table. Then, to retain the ability to do associative lookups we
  659. introduce one more array, which will be the only one actually
  660. subscripted with the book title. We might call this array "index", and
  661. provide that index.title is the common numeric subscript for all the
  662. other arrays (the table's columns) which hold particular kinds of data
  663. about books. This data now includes the book's title, and each row is
  664. instead labeled by a number.
  665.  
  666. Then it will be true that if i = index.title, book_title.i = title. And
  667. all the other information is referenced as book_author.i,
  668. book_publisher.i, etc. So it is still possible to do associative
  669. retrieval. Suppose we want to display all information about a book in
  670. response to a query. We might use the code fragment:
  671.  
  672.     i = index.title
  673.     say 'Data for' title':' 'Author='book_author.i',',
  674.         'Publisher='book_publisher.i',' 'Pub. date='book_date.i
  675.  
  676. But now it is also very easy to produce a report on all books in the
  677. database:
  678.  
  679.     do i = 1 to book_title.0
  680.         say "Book:" book_title.i', Author:' book_author.i
  681.         end
  682.  
  683. Or you could insert any selection logic you want into to the loop if
  684. you need to limit the search based on date or publisher or whatever. For
  685. instance, if you want only books that contain a certain phrase in the
  686. title:
  687.  
  688.     do i = 1 to book_title.0
  689.         if pos(phrase, book_title.i) = 0 then
  690.             iterate
  691.         say "Book:" book_title.i', Author:' book_author.i
  692.         end
  693.  
  694. That's not a bad solution. We now have the means to do both associative
  695. retrieval and sequential search in our "database". Notice that our
  696. database is kept entirely in memory in REXX variables, using natural
  697. REXX data structures. Unless the database is very large (perhaps a
  698. megabyte or more), this isn't a problem in OS/2.
  699.  
  700. Even so, we might wonder whether it isn't possible to do better. It
  701. seems at least a little inelegant to have to do a sequential scan of the
  702. database every time we want to find something that has not been
  703. explicitly indexed by keeping a separate array. Particularly since
  704. complex conditions may run somewhat slowly. But it's not really possible
  705. to do much better with standard OS/2 REXX.
  706.  
  707. There are, however, third-party extensions to REXX that are available for
  708. adding all kinds of new capabilities to the language. One of these, Quercus
  709. Systems' REXXLIB, has a number of functions for working with REXX arrays
  710. and compound variables.
  711.  
  712. One of the functions, called CVTAILS, is capable of scanning all the tails
  713. of a given compound variable searching for matches on a particular string.
  714. Using CVTAILS, the above loop would reduce to:
  715.  
  716.     call cvtails 'index.', 'list.', phrase
  717.     do n=1 to list.0
  718.         title = list.n
  719.         i = index.title
  720.         say "Book:" book_title.i', Author:' book_author.i
  721.         end
  722.  
  723. What CVTAILS does is to construct another array (in the 'list.'
  724. variable) whose values are the selected titles. Although we
  725. still need a loop, it runs only over the list of actual "hits" we found
  726. in order to display them.
  727.  
  728. In order to understand this example, recall that the index. compound
  729. variable was "subscripted" by actual book titles, and the value of
  730. each item was the numeric index in a "conventional" array.
  731.  
  732. CVTAILS could have been used without a search phrase to actually
  733. generate the list of all tails of a compound variable without having
  734. had to set this up explicitly. (In the present case, this corresponds
  735. to the "book_title." array.)
  736.  
  737. Let's consider a slightly different problem. What if we wanted to find
  738. all the book titles by a particular author? We could do:
  739.  
  740.     do i = 1 to book_title.0
  741.         if book_author.i \= name then
  742.             iterate
  743.         say "Book:" book_title.i', Author:' book_author.i
  744.         end
  745.  
  746. All we've changed is to search a different column of the table. Can this
  747. be done with CVTAILS? No, because the author name was not kept in an
  748. index variable. But there is another function, CVSEARCH, in REXXLIB that
  749. will do what we want:
  750.  
  751.     call cvsearch 'book_author.', 'list.', name
  752.     do n=1 to list.0
  753.         i = list.n
  754.         say "Book:" book_title.i', Author:' book_author.i
  755.         end
  756.  
  757. Suppose we wanted to get fancier and product a report of books sorted
  758. alphabetically on the title. This could be a challenging exercise, since
  759. REXX does not have a built-in sort routine. But let's suppose for a minute
  760. that it did, called ARRAYSORT. This takes a REXX array (i. e. a compound
  761. variable indexed from 1 to whatever) and sorts it based on the value of
  762. each item. Or more generally, on one or more subfields within the value.
  763. One could write such a function in REXX itself, though it is not a trivial
  764. exercise, and the performance would probably by a little slow.
  765.  
  766. We still have to adapt such a routine to be used with the way we have
  767. stored the data. Suppose you just did:
  768.  
  769.     call arraysort 'book_title.'
  770.  
  771. in order to rearrange the 'book_title.' array. The problem is that this
  772. sorts only one column of the table. All other columns of the table would
  773. be unaffected, so all connection between titles and the other
  774. information would be lost. The relevance of this to the issue of exactly
  775. how we choose to store the data is that if we had continued to subscript
  776. all columns by the actual title there would not have been a problem. To
  777. reiterate, you have to consider how you will use the data at the time
  778. you decide how it will be stored.
  779.  
  780. But there are relatively simple expedients that can be employed if you
  781. decide you want to continue storing the data in numerically-subscripted
  782. arrays. You could, for instance, make a copy of the array of titles, and
  783. sort the copy:
  784.  
  785.     do i = 0 to book_title.0
  786.         sorted_book_title.i = book_title.i
  787.         end
  788.     call arraysort 'sorted_book_title.'
  789.  
  790. REXXLIB contains two functions, ARRAYCOPY and CVCOPY, either of which
  791. could be used to make the copy without using a loop:
  792.  
  793.     call arraycopy 'book_title.', 'sorted_book_title.'
  794.     call arraysort 'sorted_book_title.'
  795.  
  796. Then you could iterate through 'sorted_book_title.' and retrieve the
  797. proper subscript for each column using the 'index.' array:
  798.  
  799.     do n = 1 to sorted_book_title.0
  800.         title = sorted_book_title.n
  801.         i = index.title
  802.         say "Book:" title', Author:' book_author.i
  803.         end
  804.  
  805. But there are additional alternatives. For instance, you could construct
  806. a new array that contains both the title and its original numeric index:
  807.  
  808.     do i = 1 to book_title.0
  809.         sorted_list.i = right(i, 5) || book_title.i
  810.         end
  811.     sorted_list.0 = book_title.0
  812.     call arraysort 'sorted_list.',,, 6
  813.  
  814. The extra argument to ARRAYSORT is the position in the string at which
  815. sorting is to begin. It skips over the first 5 positions which contain
  816. the index number.
  817.  
  818. Then to use this array:
  819.  
  820.     do n = 1 to sorted_list.0
  821.         parse var sorted list.n i 6 title
  822.         say "Book:" title', Author:' book_author.i
  823.         end
  824.  
  825. There are many other techniques that could be used as well, and the
  826. choice of which to use depends on the nature of the problem, or your own
  827. personal taste. It can be hard to predict ahead of time which methods
  828. will perform best. So if this matters, the best advice is to actually
  829. benchmark different approaches.
  830.  
  831. Incidentally, ARRAYSORT is another function that is included in REXXLIB.
  832.  
  833. We're going to move on soon to the other important "structure" issue in
  834. REXX programming, namely overall program structure. But first let's look
  835. at another data structure issue that turns out to be relevant to program
  836. structure.
  837.  
  838. Up until now we have been supposing that our book database is stored in
  839. a flat ASCII file. It could just as well have been stored in a more
  840. conventional database file maintained by DB2/2 or some other database
  841. manager. If we used a conventional database manager, then many of the
  842. issues of data retrieval and sorting might be handled by the database
  843. manager itself. This would be convenient, since we wouldn't have to
  844. program the operations in REXX.
  845.  
  846. However, there are drawbacks to using a conventional database manager.
  847. For instance, a DBMS can be difficult to set up and use. Installation
  848. alone takes time, and then there is the problem of defining the files
  849. to be used and different record structures for each application. And,
  850. if you want to distribute your application to others, you need some
  851. assurance that others have the database software - perhaps you will
  852. have to supply it to them (which can get expensive).
  853.  
  854. If you are using one of the REXX GUI application builders, this job may
  855. be easier, since they all have various tools for using a number of OS/2
  856. database management systems.
  857.  
  858. However, we think that in fact a very large number of "database"
  859. applications can be programmed solely in REXX without an extra DBMS,
  860. simply by using the techniques presented here. Any time you have a set
  861. of data - whether it is about books, or people, or your multimedia
  862. CD-ROM collection - you have a database. This collection of data can be
  863. kept entirely in memory while you are processing it (if it isn't too
  864. large) by representing it in REXX variables. In other words, the
  865. aggregate of all of the variable values used in a REXX program (or suite
  866. of programs) constitutes a database.
  867.  
  868. Of course, you need some way to make this data persistent, so that it
  869. endures beyond the execution of any associated program, even though it
  870. may (and often will) be modified in part by the program. And this is
  871. certainly one thing that a conventional DBMS would do for you. But
  872. suppose we don't want to use a DBMS. Are there alternatives in REXX
  873. itself to storing everything in flat files?
  874.  
  875. Of course there are. For instance, we could store the data right inside
  876. the program. Returning to our book database example, let's first
  877. consider how we would do it in another language - say C. In the first
  878. place, C has real "structures". You might have this to represent a
  879. single book record (bear with us if you don't know C):
  880.  
  881.     struct book_record {
  882.         char *title;
  883.         char *author; };
  884.  
  885. (We'll omit other parts of the record for brevity.) You could then
  886. define all your data as a table of such structures:
  887.  
  888.     struct book_record book_table[] = {
  889.         { "Star Maker", "Olaf Stapledon" },
  890.         { "Brightness Falls from the Air", "James Tiptree" } };
  891.  
  892. How does one do a similar thing in REXX? Well, right away we run into
  893. the fact that REXX doesn't have data structure declarations analogous to
  894. what is in C or in many other languages. Consequently, it isn't possible
  895. to define data statically. If you're going to keep the data in the
  896. program, you pretty much have to do it with a series of assignments that
  897. are performed at run time:
  898.  
  899.     book_title.1 = "Star Maker"
  900.     book_author.1 = "Olaf Stapledon"
  901.     book_title.2 = "Brightness Falls from the Air"
  902.     book_author.2 = "James Tiptree"
  903.  
  904. And don't forget the to set the total size of each array:
  905.  
  906.     book_title.0 = 2
  907.     book_author.0 = 2
  908.  
  909. And create the index array too:
  910.  
  911.     do i = 1 to book_title.0
  912.         title = book_title.i
  913.         index.title = i
  914.         end
  915.  
  916. That looks like a lot more work than you have to do in C, and it is also
  917. more work than you have to do in order to read the data from a flat
  918. file. So what advantage could there be to keeping the data in the
  919. program itself? Well, if we could find a better way to do this in REXX,
  920. it might actually be less work to type in. Recall that we used labels in
  921. our ASCII file in order to identify different elements of a record.
  922. There's a lot of extra typing just for all those labels.
  923.  
  924. Here's an alternative. Create a function that can be called with
  925. arguments that identify each record element. Name the function
  926. "make_record" (for instance). Then you could put a series of calls in
  927. your program:
  928.  
  929.     call make_record "Star Maker", "Olaf Stapledon"
  930.     call make_record "Brightness Falls from the Air", "James Tiptree"
  931.  
  932. That's not really much more trouble than it is in C, since you can
  933. probably use features of your favorite text editor to insert the first
  934. part of each line. Here's what make_record looks like:
  935.  
  936.     make_record: procedure expose (book_stuff)
  937.     n = book_title.0 + 1
  938.     book_title.0 = n
  939.     book_author.0 = n
  940.     title = arg(1)
  941.     book_title.n = title
  942.     book_author.n = arg(2)
  943.     index.title = n
  944.     return
  945.  
  946. Of course, there will be even more run-time overhead with using this
  947. method of initializing your REXX data structures than there is with
  948. the series of assignments. But not much. I will mention later one way
  949. to mostly eliminate this overhead.
  950.  
  951. I wouldn't necessarily recommend you always use this approach instead of
  952. entering your data in a flat file. To some extent this is another matter
  953. of taste. Especially for relatively small collections of data (100
  954. records? 1000 records?) it can be very convenient to keep it inside the
  955. program that needs it, instead of in a separate file. You might well
  956. have some data that warrants this treatment because it is tightly
  957. coupled to the program and not very meaningful outside of it - tabulated
  958. numerical data, for instance. Also, if you are using a REXX GUI tool
  959. that builds a .EXE file, your data will automatically be incorporated in
  960. this file, and it may even be encrypted (if that is important to you).
  961.  
  962. Perhaps you want to write a data-entry program, using one of the REXX
  963. GUI tools, to help you actually enter the data you have (if it must be
  964. done manually). You will probably wind up having to write a function
  965. like make_record anyhow.
  966.  
  967. Further, this suggests an interesting possibility. How about having
  968. functions in your program to access data items as well as store them?
  969. If you frequently need to fetch the author of a particular title, it
  970. would probably be nice to have a function like this:
  971.  
  972.     author: procedure expose (book_stuff)
  973.     parse arg title
  974.     n = index.title
  975.     return book_author.n
  976.  
  977. Though this is only a few lines of code, it can be cumbersome to have to
  978. rewrite it every time you need it. This is an alternative that allows
  979. associative retrieval of authors given a title, without having to
  980. construct an author index.
  981.  
  982. Or you could get even more general, and write a function that would
  983. retrieve any field of the book record:
  984.  
  985.     book_data: procedure expose (book_stuff)
  986.     parse arg field_name, title
  987.     return value('book_'field_name'.'index.title)
  988.  
  989. Which is used like so:
  990.  
  991.     field = 'author'
  992.     Say 'The' field 'of "'title'" is' book_data(field, title)
  993.  
  994. Note how this looks syntactically like a 2-dimensional array reference.
  995.  
  996. One of the big advantages of this as a technique is that it encapsulates
  997. the details of the data structure used inside the access functions. So
  998. you are free to change these structures as you see fit, without having
  999. to rewrite a lot of code. This is a big advantage because you will
  1000. probably want to experiment with different data structures in an
  1001. application as your understanding of the problem evolves. You might even
  1002. choose at some point to move the data into a DBMS, yet most of the
  1003. program wouldn't need to be aware of this.
  1004.  
  1005. We noted earlier that there might be a lot of overhead upon startup if a
  1006. REXX program has to initialize each data record with a procedure call -
  1007. certainly this is so in comparison with a language like C where the data
  1008. can be stored statically. There may be just as much overhead, or more,
  1009. if the program has to read the data from a flat file, since each line of
  1010. the file has to be parsed for content. It would be good if there were
  1011. some way of storing the data in a file that could be loaded into REXX
  1012. variables with a minimum of overhead.
  1013.  
  1014. There is such a way. One of the facilities available in some REXX
  1015. function libraries is the ability to store a group of variables in an
  1016. external file with a single call and to reload the variables with
  1017. another call. In REXXLIB, for instance, there are actually a couple of
  1018. ways to do this. VARWRITE is the function that writes the data, and
  1019. VARREAD reads it. These functions can deal with either selected named
  1020. variables (or stems), or all of the variables in a given file or
  1021. program.
  1022.  
  1023. So, if you have initialized the book database as suggested above,
  1024.  
  1025.     call varwrite filename, 'i', 'book_title.', 'book_author.',,
  1026.         'book_publisher.', 'book_isbn.', 'book_date.', 'index.'
  1027.  
  1028. would be enough to save all of the information. (The second argument,
  1029. 'i', means that the remaining arguments are a list of the variables to
  1030. be included in the operation.) Reloading it would be even easier:
  1031.  
  1032.     call varread filename
  1033.  
  1034. Given this, you might consider putting all of the data initialization
  1035. calls into a separate REXX program that stores the data in a file with
  1036. VARWRITE. The main program can then load this data any time it is needed
  1037. with VARREAD, and the start-up overhead is minimized. If the data
  1038. doesn't change very often, you wouldn't need to write a special
  1039. data-entry program for it, since you could just edit the file creation
  1040. program.
  1041.  
  1042.  
  1043. Multiple-file program structure
  1044. ------------- ------- ---------
  1045.  
  1046. We have just indicated a special case where the separation of a single
  1047. REXX application into more than one source code file makes sense. I. e.,
  1048. when you have one program to initialize some data, and a different one
  1049. that uses the data. There are many more cases when an application might
  1050. be divided into two or more source files. Certainly, if there is a lot
  1051. of code in the application, it is much easier to maintain the code
  1052. (especially if more than one programmer is involved), if multiple files
  1053. are used. Or there may be a lot of code in the form of subroutines that
  1054. needs to be used in different places. It is obviously desirable to keep
  1055. only one copy of the subroutine code, which can be invoked as needed.
  1056.  
  1057. Another reason that it is sometimes necessary, or at least desirable,
  1058. to keep REXX code in separate source files has to do with the REXX GUI
  1059. tools. Each one has different requirements, but in general different
  1060. application windows may most easily be created and maintained when their
  1061. associated REXX code is kept in separate files.
  1062.  
  1063. Whatever the reason, using REXX code that resides in more than one
  1064. source file is a fact of life with applications of any appreciable size.
  1065. It turns out that this presents several problems. So the rest of this
  1066. paper will deal with various problems and solutions for working with
  1067. REXX code in multiple files. This is what we mean by "program structure"
  1068. (rather than issues having to do with how code might be structured
  1069. within a single file).
  1070.  
  1071. The main problem that arises with multiple REXX source files is the
  1072. difficulty of sharing data among them. There is no feature in the REXX
  1073. language itself that provides globally shared data in a more or less
  1074. transparent manner. In most other languages it is at least possible to
  1075. have static data that can be accessed by different source files that
  1076. have been linked together. With REXX, on the other hand, the situation
  1077. is always like that with respect to the sharing of data between separate
  1078. .EXE files.
  1079.  
  1080. There is a second problem we will touch on later - the sharing of code
  1081. (subroutines) among the different files in a large application.
  1082.  
  1083. In a nutshell, here are some of the ways that data can be shared or
  1084. passed around among a number of separate REXX programs:
  1085.  
  1086.     1. data files on disk (or virtual disk)
  1087.     2. OS/2 .INI files
  1088.     3. OS/2 "environment variables"
  1089.     4. data sharing mechanisms provided by the REXX GUI tools and other
  1090.        third-party REXX add-ons
  1091.     5. REXX external data queues
  1092.     6. other interprocess communication facilities such as named pipes
  1093.  
  1094. Possibly the most straightforward data sharing technique simply uses
  1095. data files. The files might be managed by a DBMS, which provides the
  1096. greatest amount of functionality, as well as safeguards to guarantee the
  1097. integrity of data in case of concurrent access by multiple programs. Or
  1098. the files could be flat files or files created and accessed by functions
  1099. like VARREAD and VARWRITE, such as we have already discussed. In this
  1100. case, the REXX program may have to assume responsibility for properly
  1101. handling concurrent access if there is a possibility that multiple
  1102. threads might need to access the data (while at least one thread might
  1103. be updating it). This can be done with OS/2 semaphores. There is no
  1104. support for semaphores in OS/2 REXX as delivered. But it is available in
  1105. some of the REXX GUI packages and some of the third-party REXX libraries
  1106. like REXXLIB.
  1107.  
  1108. Semaphores are easy to use, given a package that supports them. The
  1109. type of semaphore that has to be used is called a "mutual exclusion"
  1110. semaphore. Only one thread at a time can have "ownership" of a semaphore.
  1111. A thread gains ownership of a semaphore simply by requesting it. However,
  1112. if another thread already owns it, the second thread has to wait until
  1113. the semaphore is released.
  1114.  
  1115. Before a semaphore can be used at all it has to be created. Every
  1116. semaphore has a name, which resembles a file name that begins with
  1117. "\SEM32". Once it has been created, other threads simply refer to this
  1118. semaphore by its name. Using REXXLIB functions, the call to create a
  1119. semaphore looks like this:
  1120.  
  1121.     call mutexsem_create "\sem32\my_semaphore"
  1122.  
  1123. Suppose we want to have exclusive access to a file while it is being
  1124. updated. Although the file system itself provides some protection against
  1125. interference between different threads accessing a file, a program that
  1126. is merely trying to read the file may not be able to determine that the
  1127. reason it is unable to access the file at a given moment is due to file
  1128. system serialization. It may be easier to use semaphores explicitly. To
  1129. protect a file this way you might have:
  1130.  
  1131.     /* wait until resource is free */
  1132.     call mutexsem_request "\sem32\my_semaphore"
  1133.     do ...
  1134.         /* do something with the resource */
  1135.         end
  1136.     call mutexsem_release "\sem32\my_semaphore"
  1137.  
  1138. The resource in question here doesn't need to be a file. It could be
  1139. anything that might conceivably be shared between threads, such as an
  1140. external data queue or a shared variable. (We'll discuss these shortly.)
  1141.  
  1142. OS/2 .INI files present a special case of data files, because there is a
  1143. special access function provided in OS/2 REXX as delivered: SysIni. This
  1144. interface is at a fairly high level, and provides for several logical
  1145. levels of data. Separate .INI files can be used for different
  1146. applications. Even within the same file, data can be organized in a
  1147. two-level hierarchy of major categories and individual "keys". The
  1148. categories are called "applications", but in practice you would probably
  1149. want to use at least one separate .INI file for each application.
  1150.  
  1151. REXX programs can use the system .INI file (OS2.INI), but this should
  1152. usually be avoided for performance reasons, as well as to avoid possible
  1153. name-space conflicts. Another problem with using OS2.INI is that it is
  1154. vulnerable to corruption since it is used by so many other applications
  1155. as well as by OS/2 itself. OS2.INI is sometimes difficult to back up,
  1156. and it can be completely lost when system problems occur or OS/2 is
  1157. reinstalled.
  1158.  
  1159. .INI files can be used simply for communication among any number of
  1160. independent processes in the system, but they are best used when data
  1161. needs to be persistent. You should definitely avoid using OS2.INI if you
  1162. have a lot of data or if you are not concerned about data persistence.
  1163.  
  1164. OS/2 guarantees that access to the .INI files themselves is properly
  1165. protected with respect to concurrent updates. That is, calls to SysIni
  1166. (for the same file) are atomic. However, if you have to make a series of
  1167. calls to read or write a number of different data elements, you should
  1168. use semaphores (or an equivalent technique) to ensure consistency of the
  1169. data.
  1170.  
  1171. One of the nice things about .INI files is that the SysIni function
  1172. allows you to retrieve more than one piece of data at time. The highest
  1173. level of data organization in a .INI file is called the "application",
  1174. and you can retrieve the names of all applications in the file like this:
  1175.  
  1176.     call sysini filename, 'ALL:', 'applist.'
  1177.  
  1178. which puts the names into the array 'applist.', with the number of
  1179. items in applist.0.
  1180.  
  1181. For any specific application name, the second level of information is
  1182. called a "key". You can retrieve the names of all keys for an application
  1183. like this:
  1184.  
  1185.     call sysini filename, appname, 'ALL:', 'keylist.'
  1186.  
  1187. That yields only the names of the keys. You then have to make separate
  1188. calls to SysIni to retrieve the value associated with each key.
  1189.  
  1190. This framework is not especially well-suited for dealing with arrays,
  1191. such as repeated records of a database. But it can be done. For
  1192. instance, you could store the book database in one .INI file by making
  1193. each title a separate "application", since the title is the unique "key"
  1194. that identifies each record. (But watch out for possible duplication of
  1195. titles!) Then for each title there would be separate keys for "author",
  1196. "publisher", "date", and "isbn". (We'll assume, as we have all along,
  1197. that there is only one author per book - or else we keep all author
  1198. names somehow in the same data item.)
  1199.  
  1200. Given that assumption, then this code could write a .INI file with our
  1201. book database:
  1202.  
  1203.     do i = 1 to book_title.0
  1204.         call sysini filename, book_title.i, 'author', book_author.i
  1205.         call sysini filename, book_title.i, 'publisher', book_publisher.i
  1206.         call sysini filename, book_title.i, 'date', book_date.i
  1207.         call sysini filename, book_title.i, 'isbn', book_isbn.i
  1208.         end
  1209.  
  1210. And this code could read all the information back into variables:
  1211.  
  1212.     call sysini filename, 'ALL:', 'book_title.'
  1213.     do i = 1 to book_title.0
  1214.         call sysini filename, book_title.i, 'ALL:', 'list.'
  1215.         do j = 1 to list.0
  1216.             call value 'book_'list.j'.'i,,
  1217.                 sysini(filename, book_title.i, list.j)
  1218.             end
  1219.         end
  1220.  
  1221. Note that in this example we have allowed that there might be additional
  1222. "keys" for any book besides the standard ones we have been using for
  1223. illustration. (This example doesn't set the .0 element of arrays except
  1224. for book_title., and it doesn't build the index. compound variable.)
  1225.  
  1226. Although this code is quite a bit more complex than the equivalent using
  1227. VARWRITE and VARREAD shown above, it does use only facilities delivered
  1228. with OS/2. The code is also a little simpler than the equivalent for
  1229. writing and reading the data in a flat file.
  1230.  
  1231. There are other ways to store our book database in a .INI file, such as
  1232. including the numeric subscripts in individual keys. Whether you would
  1233. want to use a different approach, of course, depends ultimately on how
  1234. you most often need to use the data.
  1235.  
  1236. Another nice thing about .INI files is that they can be used by programs
  1237. in any language that can access OS/2 API functions, as well as from
  1238. REXX. So they offer one way to communicate between programs written in
  1239. REXX and those in other languages.
  1240.  
  1241. OS/2 environment variables present one of the easiest techniques for
  1242. sharing data between programs. REXX programs can read or write environment
  1243. variables by using the VALUE function:
  1244.  
  1245.     call value 'dirname', 'c:\myfiles', 'os2environment'
  1246.  
  1247. sets the environment variable called DIRNAME, and
  1248.  
  1249.     x = value('dirname', , 'os2environment')
  1250.  
  1251. retrieves it.
  1252.  
  1253. OS/2 environment variables are specific to one particular process within
  1254. the system. They can therefore be used to share data among REXX programs
  1255. that have a calling relationship or are otherwise part of the same
  1256. process. But they can't be used to exchange data across processes.
  1257.  
  1258. Another problem with environment variables is that there isn't any support
  1259. at all for easy use of arrays. You could create separate environment
  1260. variables with names like
  1261.  
  1262.     book_title.1
  1263.     book_title.2
  1264.  
  1265. and so forth, but you would have to make a separate call to VALUE to
  1266. read or write each data item.
  1267.  
  1268. Finally, as with most other data sharing techniques, you have to develop
  1269. your own access control mechanisms using semaphores to handle concurrent
  1270. update problems.
  1271.  
  1272. Each of the REXX GUI tools provides its own method of sharing REXX
  1273. variables between separate code files. VisPro/REXX, for instance, allows
  1274. you to define variables associated with each "form" (roughly speaking,
  1275. a window). These variables are accessible to all event procedures for
  1276. the form, and to all subforms.
  1277.  
  1278. In the first release of VisPro this technique was necessary, since REXX
  1279. variables were not global to an application by default. In release 2.0
  1280. of VisPro ordinary REXX variables did become global by default, and this
  1281. extra mechanism can instead be used as a way of keeping private data
  1282. associated with a form.
  1283.  
  1284. Variables to be handled this way are defined by entering their names in
  1285. one page of the settings notebook for the form. The nice thing about
  1286. this mechanism is that you can easily share all elements of a compound
  1287. variable simply by entering the stem name in the settings notebook. The
  1288. variables are accessed within the program just be referring to them in
  1289. the normal REXX way.
  1290.  
  1291. The downside to the way that VisPro handles this is that there is some
  1292. runtime overhead associated with making private copies of such global
  1293. variables when forms are opened or closed.
  1294.  
  1295. In VX-REXX there are methods called PutVar and GetVar that can be used
  1296. to set and retrieve global variable values. This is similar to how the
  1297. VALUE function is used for working with environment variables, and it is
  1298. less convenient than the way things are done in VisPro, since these
  1299. variables cannot be accessed in the normal REXX way. GetVar and PutVar
  1300. must be used. There is the further restriction that only compound
  1301. variables that are "arrays" (i. e. having positive integral tails with
  1302. the number of such elements in the .0 element) can be used this way.
  1303. The advantage of this approach is in reduced overhead in opening and
  1304. closing windows.
  1305.  
  1306. GpfRexx has a set of functions (QueryStem, QueryStemElement,
  1307. RemoveGlobal, RemoveStem, RemoveStemElement, SetGlobal, SetStem, and
  1308. SetStemElement) for managing global variables. This is basically like
  1309. the VX-REXX facilities, except that arbitrary compound variables can be
  1310. handled.
  1311.  
  1312. In all three cases, the "global" variable facilities actually apply only
  1313. to a single program (.EXE file). So they can be used to share data among
  1314. the different parts of one application, but not at all between
  1315. applications. Furthermore, the data is not persistent beyond the
  1316. lifetime of each invocation of the application.
  1317.  
  1318. Other third party tools can overcome some of these restrictions. Quercus
  1319. Systems' Personal REXX has a utility command called GLOBALV that is
  1320. capable of creating and maintaining true system-wide global variables.
  1321. Such global variables can even be made persistent for as long as OS/2
  1322. is running, or (optionally) even indefinitely by keeping the data in
  1323. special disk files.
  1324.  
  1325. GLOBALV also supports a two-level hierarchy for structuring data. The
  1326. first level is called a "table", and within each table can be any
  1327. number of individual variables. There is not, however, an exact and
  1328. automatic mapping onto REXX compound variables.
  1329.  
  1330. GLOBALV is patterned closely on a command of the same name available
  1331. in VM/CMS. This makes it possible for programs that use it to be portable
  1332. among systems that implement GLOBALV (which means, currently, DOS, OS/2,
  1333. VM/CMS, and Windows).
  1334.  
  1335. As with most other data sharing techniques, GLOBALV protects its own
  1336. internal data structures with respect to concurrent update. But
  1337. consistency of related data items remains the programmer's
  1338. responsibility.
  1339.  
  1340. Just as with files, GLOBALV makes it possible to share data among any
  1341. processes in OS/2. This may be important, because the REXX language does
  1342. not contain any multi-tasking capabilities of its own. Although there
  1343. are multi-threading functions provided in the REXX GUI tools and certain
  1344. third-party add-on function packages, the only way to achieve
  1345. concurrency in OS/2 REXX as it is delivered is to use multiple
  1346. processes. One might well choose to implement an application as multiple
  1347. processes running REXX code in order to use the multitasking capabilities
  1348. of OS/2. In this case, some means for communication between the processes
  1349. becomes necessary.
  1350.  
  1351. There are, of course, alternatives to GLOBALV for interprocess
  1352. communication. The two we will discuss here are REXX external data
  1353. queues and named pipes. There are other IPC mechanisms (such as a native
  1354. OS/2 "queue" mechanism, which is distinct from a REXX queue). But the
  1355. various techniques differ among themselves mostly by their syntax and
  1356. their performance.
  1357.  
  1358. There are two kinds of REXX external data queues: the unnamed "session"
  1359. queue and named queues. The "session" queue is unique to a particular
  1360. REXX session. This is broader in scope than a single OS/2 process, since
  1361. it will usually include all descendant processes as well. For instance,
  1362. one invocation of the CMD.EXE command shell, together with any commands
  1363. it may run, represent one session. This means that one REXX program can
  1364. save data in the session queue, and it will still be available to later
  1365. REXX programs until it has been fully consumed. This is true even if
  1366. the data is placed in the queue by a REXX-aware application (.EXE file)
  1367. or a .EXE file built by one of the GUI tools. The session queue
  1368. is not automatically destroyed when the .EXE file terminates. Instead, it
  1369. survives, and its data may persist, until the original copy of CMD.EXE is
  1370. closed.
  1371.  
  1372. Programs running in other sessions, however, have their own private
  1373. session queue and cannot access a different session queue. The privacy
  1374. of the session queue is an advantage, in that truly independent REXX
  1375. programs cannot interfere unintentionally with each other through the
  1376. session queue. But it also means that they can't use the session queue
  1377. to communicate with each other.
  1378.  
  1379. The other type of external data queue is a "named" queue. This name is
  1380. truly system-wide, and such queues can be used for communication between
  1381. "unrelated" programs, as long as they all use the same name for the
  1382. queue. It is the programmer's responsibility to ensure that unique names
  1383. are created when appropriate, and that every application which needs to
  1384. use a particular named queue has a means of finding out the correct
  1385. name.
  1386.  
  1387. It is safest to use the unnamed session queue only for communication
  1388. among different REXX files that have some calling relation to each other
  1389. or that are part of the same application which has been created by one
  1390. of the GUI tools. While data in the session queue will normally persist
  1391. between different invocations of a macro by a REXX-enabled application,
  1392. it may not be a good idea to rely on this, since the data will be lost
  1393. if the application (or the system) unexpectedly terminates. On the other
  1394. hand, this is appropriate behavior for transient, temporary data. So
  1395. one good use of the session queue is as a large "scratch pad" data area
  1396. that can hold data for use by several related REXX programs.
  1397.  
  1398. In particular, the session queue is a good way to pass relatively large
  1399. amounts of data to a subprocedure. One of the most frequently asked
  1400. questions about REXX is how to pass an array (especially a large one)
  1401. to an external subroutine and how to return an array. The answer is that
  1402. it just isn't possible directly. (For internal subroutines, of course, it
  1403. is possible to share compound variables by the use of the EXPOSE keyword
  1404. on a PROCEDURE statement.)
  1405.  
  1406. To pass an array to a subroutine through the queue you just QUEUE or
  1407. PUSH each element. The data queue is maintained as a double-ended list.
  1408. Data may be removed only from the front (or top) of the list, with the
  1409. PULL instruction, but it can be added to the list either at the end
  1410. (bottom) or the front of the list, depending on whether you use QUEUE or
  1411. PUSH, respectively. These two possibilities are referred to as "first-in
  1412. first-out" (FIFO) and "last-in first-out" (LIFO).
  1413.  
  1414. The FIFO case is normally the easiest to conceptualize, so it is the one
  1415. you would probably choose most frequently. The main reason to choose
  1416. LIFO is to avoid problems when the same queue is used in a nested
  1417. fashion by more than one level of subroutine. That is, if any given
  1418. routine adds data only to front of the queue before calling a
  1419. subroutine, and if the callee only removes as much as it needs, then it
  1420. is possible to nest such calls, even recursively, without disturbing
  1421. data placed on the queue for a different purpose.
  1422.  
  1423. If the queue needs to be used for several, or many, unrelated purposes,
  1424. it is probably safer to simply use multiple named queues, even if only
  1425. one session is actually involved. But one problem with using a named
  1426. queue this way is that you have to be careful that you construct a name
  1427. that will be unique, so that if the same application is invoked more
  1428. than once simultaneously then there is no interference between the
  1429. multiple invocations. You also need to have a means for the intended
  1430. user of the queue to find out what the actual name to be used is.
  1431.  
  1432. One other problem with named queues is simply that they are a little
  1433. inconvenient to work with. You first have to create the queue with a
  1434. call to the RXQUEUE built-in function. Then you have to check that
  1435. the name was not already in use (as indicated by RXQUEUE returning a
  1436. name other than the one you asked for). Finally, you have to make
  1437. the new queue the default queue by another call to RXQUEUE:
  1438.  
  1439.     qname = rxqueue('c', proposed_name)
  1440.     if proposed_name \= qname then  /* this queue must already exist */
  1441.         call rxqueue 'd', qname     /* destroy the unwanted new queue */
  1442.     call rxqueue 's', proposed_name
  1443.  
  1444. The last call here makes the named queue become the default. This is a
  1445. necessary step, since there is no way to indicate on the QUEUE or PUSH
  1446. instruction what queue is meant - it is always the "default" queue. Names
  1447. that are valid for data queues are just like names that are valid for
  1448. REXX variables - they must begin with a letter (or certain characters
  1449. like "!", "_") and not be longer than 250 characters.
  1450.  
  1451. This procedure can be simplified slightly if you let REXX choose a name
  1452. for you when creating the queue. This is probably the best approach to
  1453. use when you want to have a queue that is used only for temporary
  1454. scratch space. You might do this for communication between separate
  1455. REXX programs, or you might do it within a single program (even a single
  1456. source file) just to avoid any possibility of conflicting use of the
  1457. queue. To have REXX assign the name, just omit any name when you create
  1458. the queue:
  1459.  
  1460.     qname = rxqueue('c')
  1461.     call rxqueue 's', qname
  1462.  
  1463. Any time you do create a queue, you are responsible for eventually
  1464. destroying it, particularly if you are only using it for scratch space.
  1465. By the very nature of a named queue, it is persistent (as long as OS/2
  1466. is running), so it isn't automatically destroyed when the program (or
  1467. even the program's session) terminates. The 'd' option passed to RXQUEUE
  1468. destroys a queue:
  1469.  
  1470.     call rxqueue 'd', qname
  1471.  
  1472. Incidentally, a queue is not destroyed when all data is removed from it.
  1473. A queue can be empty, as it will be when it is initially created.
  1474.  
  1475. You can tell how many data items are in a queue with the QUEUED built-in
  1476. function. This function takes no arguments - it works only on the
  1477. current default queue. The fastest way to remove all items from a queue,
  1478. is just PULL until QUEUED becomes 0:
  1479.  
  1480.     do while queued() \= 0
  1481.         pull
  1482.         end
  1483.  
  1484. Note that it is legal to use PULL with nothing else on the line.
  1485.  
  1486. Named queues are often used with applications that are structured as
  1487. "client-server". This can be done only so long as the server and all
  1488. clients are on the same computer, since queues are not supported across
  1489. a network. (Named pipes, to be discussed shortly, are good for network
  1490. use.) In this case, there will probably be at least one queue whose
  1491. name is known "publicly", through which clients can contact the server.
  1492.  
  1493. When a REXX queue is used in a "public" way like this, it's a very good
  1494. idea to use semaphores to control access to it, just as with other forms
  1495. of interprocess communication. This is particularly true if several
  1496. separate data items have to be placed on the queue, because it would
  1497. probably not work to have messages from different clients intermixed.
  1498.  
  1499. In a client-server situation, it is necessary for the server to have
  1500. some way to wait for data to appear on a particular queue, and for the
  1501. clients to wait for data to be returned (in the same queue or a different
  1502. one). The PULL instruction, which is ordinarily used to read from a queue
  1503. is not appropriate for this situation, because it is defined to read from
  1504. the keyboard if no data is available in the default queue. One could
  1505. continually "poll" for data in the queue with the QUEUED function, but
  1506. this will eat up CPU cycles.
  1507.  
  1508. The solution is to use a poorly-documented feature of the REXX I/O
  1509. system. This is the fact that you can use a stream name of "QUEUE:" with
  1510. the LINEIN and LINEOUT functions. On output (LINEOUT), the effect is the
  1511. same as the QUEUE instruction. But on input,
  1512.  
  1513.     data = linein('QUEUE:')
  1514.  
  1515. has the effect of suspending the REXX program until data becomes
  1516. available in the queue, without wasting CPU time.
  1517.  
  1518. Just about any form of interprocess communication that can be done with
  1519. a REXX external data queue can also be done with a named pipe. In fact,
  1520. a lot more is possible, because named pipes work across a network, and
  1521. also because programs that do not even have special support for pipes
  1522. can use them simply by treating them as a file. This makes it possible
  1523. for an OS/2 REXX program to communication with a program running in a
  1524. DOS session, for instance.
  1525.  
  1526. The problem with named pipes is that REXX as delivered with OS/2 has
  1527. very little explicit support for named pipes, although a REXX program,
  1528. like any other, can treat an already existing pipe as if it were a file.
  1529. When used as a file, a pipe always has a name of the form "\PIPE\xxx"
  1530. (when the pipe has been created by a process on the same computer) or
  1531. "\\server\PIPE\xxx" when the pipe has been created by a process on a
  1532. network-connected computer called "server".
  1533.  
  1534. There is exactly one function in standard OS/2 REXX for working with
  1535. pipes: SysWaitNamedPipe. You have to use this in case a pipe is "busy",
  1536. i. e. already in use, which is indicated by an error in the STREAM
  1537. function used to open the pipe:
  1538.  
  1539.     parse value stream(pipename, 'c', 'open') with state ':' retc
  1540.     if retc = 231 then
  1541.         call syswaitnamedpipe(pipename, -1)
  1542.  
  1543. Various function packages available from third parties provide more
  1544. complete support for named pipes. REXXLIB, in particular, has such
  1545. support. The functions provided there make it possible to do just
  1546. about anything that is possible with named pipes, except that only
  1547. one instance of a pipe can be open a time, because of the single
  1548. threaded nature of REXX.
  1549.  
  1550. As an example of the use of named pipes, consider the problem of
  1551. debugging a complex REXX program, especially one that is constructed
  1552. using one of the GUI tools. Although these tools include debuggers,
  1553. sometimes the nature of the problem is such that the easiest technique
  1554. to use is for the program to send messages to a message log when
  1555. certain abnormal conditions are detected. The GUI tools provide for
  1556. this through a "console I/O window" which receives the output of SAY
  1557. instructions. However, if you aren't using a GUI tool, you have to
  1558. have another way to handle this. It may not be reasonable to mix SAY
  1559. debugging output with normal output of the program.
  1560.  
  1561. The solution is for the program being debugged to send output through a
  1562. named pipe to a simple server running in another process. The server can
  1563. display the information in its own window, or possibly even analyze it
  1564. for the occurrence of specific events. Here is how the server code might
  1565. look, using named pipe functions provided by REXXLIB:
  1566.  
  1567.     pipe = '\pipe\echo'
  1568.     call nmpipe_create pipe, 'm', 'm', 'w'
  1569.     do i=1
  1570.         call nmpipe_connect pipe
  1571.         say 'Connect RC =' result
  1572.         if result \= 0 then
  1573.             exit
  1574.         do forever
  1575.             message = nmpipe_read(pipe)
  1576.             if message = 'end' | message == '' then do
  1577.                 call nmpipe_disconnect pipe
  1578.                 iterate i
  1579.                 end
  1580.             say 'Message received: "'message'"'
  1581.             end
  1582.         end
  1583.  
  1584. The pipe is created with NMPIPE_CREATE. NMPIPE_CONNECT is called so that
  1585. the server can wait for a client to open its side of the pipe. After
  1586. this occurs, a series of calls to NMPIPE_READ retrieve all data sent
  1587. from the client, until either a null string or the string "end" is
  1588. received. This is taken to mean that the client is done (perhaps died)
  1589. or has closed its end of the pipe.
  1590.  
  1591. The client side of this is even simpler. The client might simply open
  1592. the pipe implicitly by calling LINEOUT with some data. Subsequent calls
  1593. to LINEOUT send additional messages, and when the client is all done, it
  1594. just calls LINEOUT with only the pipe name in order to close it.
  1595.  
  1596. The last topic we're going to look at in this paper is the question of
  1597. structuring a large application as a number of separate REXX source
  1598. files. What we've just covered is a variety of techniques for sharing
  1599. data among such files, which is a problem since "global" variable values
  1600. of a calling program are not "inherited" by external subprocedures. But
  1601. there are are other issues that arise too.
  1602.  
  1603. For one thing, state information of a running REXX program is not
  1604. inherited, either, in a call to an external routine: trace settings,
  1605. NUMERIC and ADDRESS settings, condition handlers, and timers. However,
  1606. this state information is inherited on internal procedure calls. This
  1607. can cause problems for you if you break a large program up into a main
  1608. routine and several external procedures, since these procedures may no
  1609. longer have the same state settings as before.
  1610.  
  1611. In spite of this, there are some features of a calling REXX program that
  1612. are inherited by a callee. You should be aware of this, since it is not
  1613. only unexpected (in light of the fact that most things aren't
  1614. inherited), but also quite undocumented as well. Most importantly, open
  1615. file information is inherited. This means, in particular, that a
  1616. subroutine inherits position information about all open files, and the
  1617. file can be left in a different position when the subprocedure returns.
  1618.  
  1619. Other information of somewhat lesser importance that is always inherited
  1620. includes the name of the current default external data queue. Also, if
  1621. you use any external function libraries that keep their own state
  1622. information, this will most likely be maintained independently of
  1623. external procedure calls.
  1624.  
  1625. Apart from the question of how various kinds of environmental
  1626. information behave with respect to external procedure calls, the main
  1627. issue you face in using external procedures is simply the extra overhead
  1628. of finding and loading them. Although it doesn't take much time to find
  1629. and load an external REXX procedure once, this can be a big factor if it
  1630. has to be done 1000 times in a loop. Many REXX applications run
  1631. surprisingly slowly for just this reason.
  1632.  
  1633. Yet there are good reasons for wanting to break up a large program -
  1634. the standard concerns of modularity, modifiability, sharing of code,
  1635. and so forth. Fortunately, there is an alternative that allows keeping
  1636. code in separate file, without the overhead of searching for them on
  1637. disk and loading them when required.
  1638.  
  1639. As supplied with OS/2, REXX supports a feature called "macro spaces".
  1640. Basically this is a capability for loading program files into shared
  1641. memory and keeping them resident as long as desired so that the search
  1642. and load overhead can be bypassed. All or parts of this shared code
  1643. space can be saved in disk files (in "tokenized" form) in order to create
  1644. (in effect) REXX subroutine libraries. Such libraries can be saved or
  1645. distributed and reloaded as a whole when appropriate.
  1646.  
  1647. Among other things, macro space libraries provide an answer to a
  1648. question that often arises with programmers who have to distribute REXX
  1649. code. For a variety of reasons (protection against modification, hiding
  1650. of confidential information, etc.) it is often desirable not to
  1651. distribute source code. Since the REXX code in a macro space is saved in
  1652. the "tokenized" form, one gets an immediate solution to this problem.
  1653. (Note that IBM does not guarantee tokenized code will continue to
  1654. operate in future releases of OS/2. If you do this, be prepared to
  1655. redistribute your code if an incompatible change does occur).
  1656.  
  1657. So macro spaces can help manage two distinct problems: the performance
  1658. of calls to external routines and the need for a way to avoid exposing
  1659. source code. Unfortunately, there is one hitch: REXX as distributed
  1660. doesn't provide REXX-callable functions for working with a macro space.
  1661. Third party libraries, again, provide the solution.
  1662.  
  1663. REXXLIB has a set of functions for managing macro spaces. The first
  1664. step is to load all of the source files you want into the macro space.
  1665. If you don't want to create a permanent macro library, that is all you
  1666. have to do. Otherwise, you make one more call that causes the library
  1667. to be written to disk.
  1668.  
  1669. For instance, suppose that the compound variable names. contains the
  1670. list of names of procedures required. Suppose the extension of all files
  1671. is ".CMD". Then you would use:
  1672.  
  1673.     do i = 1 to names.0
  1674.         call macroadd names.i, names.i".cmd", 'B'
  1675.         end
  1676.     call macrosave 'mymacros.mac', 'names.'
  1677.  
  1678. Although the file names used in this example are the same as the routine
  1679. names (except for the extension), this does not need to be the case. You
  1680. could associate any procedure name you want with any file.
  1681.  
  1682. The third argument of MACROADD is either 'B' ("before") or 'A'
  1683. ("after"), which indicates whether REXX will search for that particular
  1684. external procedure name in the macro space either before or after it
  1685. searches on disk. There would be no performance advantage of macro
  1686. spaces if you didn't use 'B', so it is the default. But you might want
  1687. to use 'A' to create a kind of "default" procedure which would be
  1688. executed only if the name wasn't found on disk. (This is a rather risky
  1689. thing to do, since your application will probably fail if an unrelated
  1690. REXX procedure with this name just happens to exist on disk. But it also
  1691. makes it possible to supply overriding procedures if that is desirable.)
  1692.  
  1693. When a procedure has been loaded into the macro space with the 'B'
  1694. option, REXX finds it before it searches the disk, and before it finds
  1695. functions registered in .DLL and .EXE files. This makes it possible for
  1696. you to override function names that you would otherwise not have
  1697. control over. However, the macro code is still executed in exactly the
  1698. same way any external procedure is. Consequently, you can't pass
  1699. compound variable stems to a macro space routine any more than you can
  1700. any other external REXX code. And all the other considerations listed
  1701. previously for calling external routines still apply. Also, you can't
  1702. override the names of built-in functions, since these are considered
  1703. to be internal.
  1704.  
  1705. In order to use a macro space library you have created at an earlier
  1706. time, it takes only one call to load it:
  1707.  
  1708.     call macroload 'mymacros.mac'
  1709.  
  1710. If the macro library is one you use regularly, you should probably just
  1711. load this in a STARTUP.CMD file. Unless you have a really large library,
  1712. the memory usage isn't too significant, and it will be swapped out pretty
  1713. soon anyhow if it's not actually used.
  1714.  
  1715. Although the macro space facility is a nice, little-known feature of
  1716. OS/2 REXX, it has a few problems (apart from the fact there's no
  1717. REXX-callable interface supplied by IBM). For one thing, the macro
  1718. space is global to the whole OS/2 system. Macros loaded by one process
  1719. immediately become visible to all other running processes. The same is
  1720. true of any other change made to the macro space, such as deletion of
  1721. routines. Of course, this isn't so different from the fact that a new
  1722. REXX program added to your disk immediately becomes available to all
  1723. processes as well. You just have to be careful, and you should probably
  1724. establish naming conventions to avoid unintended name collisions.
  1725.  
  1726. Also, you can't get a list of macros that have already been loaded into
  1727. the macro space. But you can query whether any particular name has been
  1728. loaded. If you are especially security conscious, you might want to
  1729. think about doing this to be sure a "trojan horse" REXX routine hasn't
  1730. been loaded into the macro space to replace a routine you rely upon.
  1731. (MACROQUERY is the REXXLIB function that provides this service.)
  1732.  
  1733. Bibliography
  1734. ------------
  1735.  
  1736. 1. Cowlishaw, M. F.; The REXX Language
  1737.    Prentice-Hall, ISBN 0-13-779067-8
  1738.  
  1739. 2. Daney, Charles; Programming in REXX
  1740.    McGraw-Hill, ISBN 0-07-015305-1
  1741.  
  1742. 3. German, Hallett; OS/2 2.1 REXX Handbook
  1743.    Van Nostrand Reinhold, ISBN 0-442-01734-0
  1744.  
  1745. 4. Goran, Dick; REXX Reference Summary Handbook
  1746.    CFS Nevada, ISBN 0-963-98541-8
  1747.  
  1748. 5. Rudd, Anthony; Application Development Using OS/2 REXX
  1749.    Wiley-QED, ISBN 0-471-60691-X
  1750.