home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / DIALATE.ZIP / DIALATE.TXT < prev    next >
Encoding:
Text File  |  1986-03-04  |  14.4 KB  |  349 lines

  1.                A Technique for Automatically Porting
  2.                  Dialects of Pascal to Each Other
  3.  
  4.                       QCAD Systems, Inc.
  5.                       1164 Hyde Avenue
  6.                       San Jose CA 95125
  7.  
  8.                        Michael J. Sorens
  9.  
  10.  
  11.      In the course of programming events, there have
  12. been those who have found it necessary to
  13. write Pascal code that could be ported between dialects of Pascal
  14. which differ from machine to machine.  This task cannot be
  15. accomplished just by writing "vanilla" code to a maximal degree,
  16. as there are inevitably syntactical and semantic differences from
  17. one dialect to another.  We have developed a technique which,
  18. in conjunction with vanilla code, allows translation from
  19. one dialect to another to be performed automatically.
  20. Since it is generally the case that when a file of program text needs
  21. to be translated to a new dialect it also needs to be transmitted
  22. to a physically separate computer, our translation utility exists in
  23. two forms: a stand-alone translation utility, and a combination
  24. translation/transmission utility.
  25.  
  26. FINDING THE DENOMINATOR
  27.  
  28.      The primary task is to reduce functions and procedures to common
  29. denominators.
  30. That is, if a function does not exist in one dialect, it must be
  31. written for that dialect using the dialect's own primitives.
  32. This could be a simple renaming.  For example, consider the two
  33. functions shown below:
  34.  
  35. Turbo Pascal:  copy(string, location, length)
  36.  
  37. VAX Pascal:    substr(string, location, length)
  38.  
  39. As it happens, these functions are semantically identical, only the
  40. names have been changed to confuse the unwary.
  41. If we have written code in Turbo Pascal, then we simply create a
  42. copy function for VAX Pascal:
  43.  
  44. function copy(STR: string255;
  45.               WHERE, LEN: integer): string255;
  46. begin
  47.   copy := substr(str, where, len);
  48. end;
  49.  
  50. Thereafter, we can use the copy function in either Turbo Pascal
  51. or VAX Pascal.
  52. Of course, we only want the above definition of copy to exist
  53. in the VAX environment, as it will generate compilation errors in Turbo;
  54. we will see how to selectively compile this kind of entity shortly.
  55.  
  56.      A second case might be this: in evaluating a logical expression,
  57. HP Pascal, for example, has a compiler option to do only partial
  58. evaluation (a la C) where an expression is evaluated only until the
  59. point where its result is determinable.  C programmers use this
  60. in establishing and avoiding side effects.   (It is quite a useful thing
  61. to have, but, alas, having it in only one Pascal dialect makes life difficult.)
  62.  
  63.      For example, if the function
  64. foo changes some global variable y, then (false and foo(x)) will
  65. not change y if the dialect uses partial evaluation.
  66. This is due to the way the boolean and operates.  Both terms
  67. must be true for the conjunction to be true.  Scanning from left to right,
  68. since the first term false is false, we do not need to even look at
  69. foo(x) to determine the result.  Without the partial evaluation feature,
  70. foo(x) will always be evaluated, potentially yielding differing results.
  71.  
  72.      A second example might be a test such as
  73.  
  74. while (count <= length(str)) and (str[count] = 'X') do...
  75.  
  76. which is a valid statement with partial evaluation but can cause
  77. a runtime error without it.  Why?  Well, as long as count is less than
  78. or equal to the length of the string str there is no problem.
  79. But when count becomes one larger than the length of str
  80. referencing str[count] may cause a runtime error, depending on
  81. whether range checking is enabled for a particular compiler.
  82.  
  83.      Therefore, we must modify the code in which results might vary depending on
  84. whether partial evaluation is available.  This could be done by introducing
  85. a two-step evaluation.  That is, rather than if (false and foo(x)) then...
  86. we substitute if false then if foo(x) then... which guarantees an
  87. equivalent partial evaluation.  The while loop requires introducing some
  88. ugliness.  We need a boolean variable, call it done, which we initialize
  89. to false.  Then, the code might be
  90.  
  91. while (count <= length(str)) and (not done) do begin
  92.   done := (str[count] = 'X');
  93.   if not done then ...
  94.  
  95. The simple elegance and power of partial evaluation begins to shine through,
  96. no?
  97.  
  98.      A more cumbersome reduction involves string comparisons.
  99. In HP Pascal or Turbo Pascal one could compare
  100. strings s1 and s2 by merely
  101. interposing a relational operator, e.g., (s1 < s2) or (s1 = s2).
  102. In VAX Pascal, however, only strings of the same length can be compared
  103. (not strings of the same maximal length, but strings of the same length
  104. at the moment of comparison).  Thus, we need to introduce an added
  105. functional level in all three Pascals so that the code can be identical.
  106. We create the function StrCmp:
  107.  
  108. function StrCmp(S, T: string80): integer;
  109. { return -1 if s<t; 0 if s=t; 1 if s>t }
  110. var Result: integer;
  111. begin
  112.   if length(s) < length(t) then begin
  113.     result := StrCmp(s, copy(t, 1, length(s)));
  114.     if result = 0 then StrCmp := -1 else StrCmp := result
  115.     end
  116.   else if length(s) > length(t) then begin
  117.     result := StrCmp(copy(s, 1, length(t)), t);
  118.     if result = 0 then StrCmp := 1 else StrCmp := result
  119.     end
  120.   else if s = t then StrCmp := 0
  121.   else if s < t then StrCmp := -1
  122.   else if s > t then StrCmp := 1
  123. end;
  124.  
  125. Then, if we have existing code which needs to be retrofitted,
  126. we must change occurrences of (s1 = s2) to (strcmp(s1, s2) = 0),
  127. and likewise for the other relational operators.
  128.  
  129.  
  130. MAKE THAT CODE DISAPPEAR
  131.  
  132.      At some point, there will be pieces of code that must be seen
  133. by one compiler but must be hidden from another.
  134. Enter DIALATE.  DIALATE -- a combination of the words dialect
  135. and translate -- converts one dialect of Pascal to another provided
  136. a program text file has been set up according to the guidelines about
  137. to be discussed.
  138.  
  139.      We introduce a meta-notation to be used in any Pascal we are interested
  140. in.  This is just a notation that is in some sense "above" the Pascal code
  141. in that our translator will be looking only at the meta-notation and not
  142. the Pascal text itself.  In our meta-notation we have
  143. meta-brackets which are the only constructs that DIALATE looks for:
  144.  
  145. {@x}     opening meta-bracket for dialect x
  146. {@}      closing meta-bracket
  147.  
  148. We use the curly braces "{" and "}" as the basis of our dialect
  149. notation, since they already have the capability of hiding text from
  150. a Pascal compiler -- the simple comment.  DIALATE scans for comments
  151. that immediately begin with an "@" symbol, indicating that the comment
  152. is a special, dialectic comment.  Immediately following the "@" can
  153. be one or more dialect designations.
  154. We currently use the following conventions:
  155.  
  156. T   - Turbo Pascal (IBM PC)
  157. V   - VAX Pascal (VMS)
  158. H   - HP Pascal
  159. A   - Apple Pascal (Macintosh)
  160.  
  161. Any piece of code that cannot run on all relevant machines
  162. must be surrounded by meta-brackets.  DIALATE inserts
  163. and removes the right curly brace of the opening meta-bracket in a judicious
  164. manner so that the compiler "sees" only valid language constructs.
  165.  
  166.      Let's look at an example.  Consider opening a file for input
  167. in Turbo Pascal and HP Pascal:
  168.  
  169. Turbo Pascal:  assign(MyFile, FileName);
  170.                reset(MyFile);
  171.  
  172. HP Pascal:     reset(MyFile, FileName);
  173.  
  174. Since there are significant differences, it is perhaps wise to
  175. create a procedure OpenInputFile which can be used in both Pascal
  176. dialects.  Here is what the procedure will look like in Turbo Pascal:
  177.  
  178. (1)  procedure OpenInputFile(var F: text; NAME: string80);
  179. (2)  begin
  180. (3)    {@T}
  181. (4)    assign(f, name);
  182. (5)    reset(f);
  183. (6)    {@}
  184. (7)    {@H
  185. (8)    reset(f, name);
  186. (9)    {@}
  187. (10) end;
  188.  
  189. Carefully examine the position of each of the curly braces.
  190. Notice in line 3 that there is a "}" but that in line 7 there is not.
  191. Thus, lines 4 and 5 are active code while line 8 is passive code.
  192. Lines 6 and 9 are closing meta-brackets which never change.  The "{"
  193. in line 6 begins a comment which hides the "@" character, while the "{"
  194. in line 9 is ignored since it is already inside a comment.
  195. Now let's run the procedure through DIALATE, converting it to
  196. HP Pascal code:
  197.  
  198. (1)  procedure OpenInputFile(var F: text; NAME: string80);
  199. (2)  begin
  200. (3)    {@T
  201. (4)    assign(f, name);
  202. (5)    reset(f);
  203. (6)    {@}
  204. (7)    {@H}
  205. (8)    reset(f, name);
  206. (9)    {@}
  207. (10) end;
  208.  
  209. The only difference is that the "}" in line 3 is gone, and there is
  210. a new "}" in line 7.  This has reversed the active and passive sections
  211. of code.
  212. Hence, to write a piece of
  213. automatically translatable code, decide on which dialect you wish
  214. to write in, and use the appropriate meta-brackets.
  215.  
  216.      The technique is extensible to multiple machines with a concise
  217. notation.  Take, for example a function to find the location of a pattern
  218. string within some other string.  In Turbo Pascal and HP Pascal this
  219. function is called pos, while in VAX Pascal it is called index.
  220. We could then write a compatibility function called index to be used
  221. in Turbo or HP Pascal, or one called pos to be used in VAX Pascal.
  222. A third alternative, though, is to write a function with a new name,
  223. perhaps  LocateSubString, to be used in all three languages.
  224. Stylistically, it might be better to use a very different name so that
  225. there is no chance of confusing the name with some other valid language
  226. construct.
  227. As it happens, the functions pos and index do precisely the
  228. same thing, though their parameter order is different, so we do not
  229. have to write much code.
  230.  
  231. (1)  function LocateSubString(Object,
  232.                               Target: string80): integer;
  233. (2)  begin
  234. (3)    {@HT
  235. (4)    LocateSubString := pos(object, target);
  236. (5)    {@}
  237. (6)    {@V}
  238. (7)    LocateSubString := index(target, object);
  239. (8)    {@}
  240. (9)  end;
  241.  
  242. We can see that the above function is written in VAX Pascal since
  243. line 7, the VAX code, is active.  Line 4 is passive code which is
  244. for both the HP and the Turbo dialects, since line 3 has both a "T" and
  245. an "H".  When translated to either of these dialects, line 4 will
  246. become active while line 7 becomes passive.
  247.  
  248.  
  249. CASE IN POINT
  250.  
  251.      There is one final twist which has precipitated out of the Babel-like
  252. differences in Pascals, and that is the else clause of a case
  253. statement.  Some Pascals, such as Turbo, use the keyword else to
  254. indicate any cases not explicitly enumerated.  Other Pascals,
  255. such as VAX, HP, and Macintosh, use the keyword otherwise.
  256. We can certainly handle this discrepancy using our standard meta-notation
  257. described in the previous section.  A typical case statement might
  258. look like this:
  259.  
  260. case SelectionChar of
  261.   'R':  RunIt;
  262.   'P':  ProcessIt;
  263.   'E':  EditIt;
  264.   {@T}   else      {@}
  265.   {@HVA  otherwise {@}
  266.     writeln('Invalid selection character);
  267.   end { case } ;
  268.  
  269. But this can quickly become an annoyance.  Since there is no
  270. way to make some kind of generic function which handles all case
  271. statements (like we did with OpenInputFile),
  272. we must use the meta-brackets every time we have a case statement.
  273. Or rather, we would have to, if DIALATE didn't have a
  274. better solution.
  275.  
  276.      What we would like to do is have the translator automatically convert
  277. an else to an otherwise if we are going from Turbo to either
  278. HP, VAX, or Macintosh Pascal, and convert an otherwise to an else
  279. if we are going in the converse direction.
  280. But wait, what about the oft-found if... then... else... statement?
  281. How will we know if an else belongs to an if or to a case?
  282.  
  283.      What we have chosen to do is have a special ELSE-OTHERWISE convention.
  284. In Turbo Pascal, we write "ELSE" to denote an else of a case
  285. statement, and "else" to denote an else of an if statement.
  286. In our other Pascals, we write "OTHERWISE" to denote an otherwise
  287. of a case statement, and "else" to denote an else of an if
  288. statement.  That is, the case clause -- whatever it is called --
  289. must be in uppercase letters, while the if clause must be in
  290. lowercase letters.
  291.  
  292.  
  293. DISCUSSION
  294.  
  295.      There are minor disadvantages to this dialect translation technique.
  296. The foremost is that you must have all meta-brackets balanced
  297. and correct, otherwise you might hide too little or too much code from
  298. the compiler.  This might cause compilation errors, but it might not,
  299. creating possibly subtle bugs.
  300. For example, if we accidentally made the VAX code above into passive code,
  301. then the LocateSubString function would never be assigned a value.
  302. This could cause random or unpredictable results in the program.
  303. On the other hand, if we made both assignments active, a compiler
  304. should complain over the unknown function index or pos,
  305. depending on which machine the code is compiled.
  306. It takes a little getting used to, but typing correct meta-brackets
  307. is, after all, no more than typing correct syntax in a programming
  308. language.
  309.  
  310.      Second, it is not possible to put actual comments inside a region
  311. that is meta-bracketed. This may cause compilation errors when the
  312. entire region is supposed to be hidden, since the closing comment bracket
  313. could inadvertantly reactivate a portion of the hidden code.
  314. This is most insidious, however, when it does not cause compilation
  315. errors, as, for example:
  316.  
  317. procedure DUMMY;
  318. begin
  319.   {@H
  320.   a := 10;
  321.   b := 5;
  322.   {@}
  323.   {@T}
  324.   a := 5;    { some global variables }
  325.   b := 10;
  326.   {@}
  327. end;
  328.  
  329. The above code, as written, will work fine with the Turbo compiler.
  330. However, when we translate it to run with HP Pascal, we will get the
  331. wrong value for b, since the closing "}" of the "real" comment will
  332. prematurely close the comment created by the meta-brackets.
  333.  
  334.      The dialect translation technique discussed in this paper is an
  335. effective and rapid way to work in several different Pascals with virtually
  336. the same program text.  It may seem awkward at first, but one can readily
  337. get used to the style.  And for those who need to work with more than
  338. one dialect or more than one machine, the tool may prove invaluable.
  339. DIALATE was developed due to a perceived need here at QCAD.
  340. We manufacture a large software package (a parser generator) which runs
  341. on the machines discussed above; DIALATE allows us to keep the same
  342. code on all of the machines.
  343.  
  344.      Should there be any readers who are so amazed by the technique revealed
  345. in this paper (for the first time anywhere) I will gladly supply both
  346. object and source code for DIALATE for a mere $10.24 (a kilo-penny)
  347. to cover diskette, postage, copying, etc.  I will also be happy to
  348. tell you about some of the neat software tools that QCAD sells for
  349. real money.