home *** CD-ROM | disk | FTP | other *** search
/ Amiga ISO Collection / AmigaUtilCD2.iso / Programming / Misc / OB3.2D1.DMS / in.adf / ModulaToOberon / text0000.txt < prev   
Encoding:
Text File  |  1992-10-15  |  25.2 KB  |  523 lines

  1. In spite of its brevity, a description must be complete. Completeness is to
  2. be achieved within the framework of the chosen abstractions. Limitations
  3. imposed by particular implementations do not belong to a language
  4. definition proper. Examples of such restrictions are the maximum values of
  5. numbers, rounding and truncation errors in arithmetic, and actions taken
  6. when a program violates the stated rules. It should not be necessary to
  7. supplement a language definition with a voluminous standards document to
  8. cover "unforeseen" cases.
  9.  
  10. But neither should a programming language be a mathematical theory only. It
  11. must be a practical tool. This imposes certain limits on the terseness of
  12. the formalism. Several features of Oberon are superfluous from a purely
  13. theoretical point of view. They are nevertheless retained for practical
  14. reasons, either for programmers' convenience or to allow for efficient code
  15. generation without the necessity of complex, "optimizing" pattern matching
  16. algorithms in compilers. Examples of such features are the presence of
  17. several forms of repetitive statements, and of standard procedures such as
  18. INC, DEC, and ODD. They complicate neither the language conceptually nor
  19. the compiler to any significant degree.
  20.  
  21. These underlying premises must be kept in mind when comparing Oberon with
  22. other languages. Neither the language nor its defining document reach the
  23. ideal; but Oberon approximates these goals much better than its
  24. predecessors.
  25.  
  26. A compiler for Oberon has been implemented for the NS32000 processor family
  27. and is embedded in the Oberon operating environment [8]. The compiler
  28. requires less than 50 KByte of memory, consists of 6 modules with a total
  29. of about 4000 lines of source code, and compiles itself in about 15 seconds
  30. on a workstation with a 25MHz NS32532 processor.
  31.  
  32. After extensive experience in programming with Oberon, a revision was
  33. defined and implemented. The differences between the two versions are
  34. summarised towards the end of the paper. Subsequently, we present a brief
  35. introduction to (revised) Oberon assuming familiarity with Modula (or
  36. Pascal), concentrating on the added features and listing the eliminated
  37. ones. In order to be able to start with a clean slate, the latter are taken
  38. first.
  39.  
  40. Features omitted from Modula
  41.  
  42. Data types
  43.  
  44. Variant records are eliminated, because they constitute a genuine
  45. difficulty for the implementation of a reliable storage management system
  46. based on automatic garbage collection. The functionality of variant records
  47. is preserved by the introduction of extensible data types.
  48.  
  49. Opaque types cater for the concept of abstract data type and information
  50. hiding. They are eliminated as such, because again the concept is covered
  51. by the new facility of extended record types.
  52.  
  53. Enumeration types appear to be a simple enough feature to be
  54. uncontroversial. However, they defy extensibility over module boundaries.
  55. Either a facility to extend given enumeration types has to be introduced,
  56. or they have to be dropped. A reason in favour of the latter, radical
  57. solution was the observation that in a growing number of programs the
  58. indiscriminate use of enumerations (and subranges) had led to a type
  59. explosion that contributed not to program clarity but rather to verbosity.
  60. In connection with import and export, enumerations give rise to the
  61. exceptional rule that the import of a type identifier also causes the
  62. (automatic) import of all associated constant identifiers. This exceptional
  63. rule defies conceptual simplicity and causes unpleasant problems for the
  64. implementor.
  65.  
  66. Subrange types were introduced in Pascal (and adopted in Modula) for two
  67. reasons: (1) to indicate that a variable accepts a limited range of values
  68. of the base type and to allow a compiler to generate appropriate guards for
  69. assignments, and (2) to allow a compiler to allocate the minimal storage
  70. space needed to store values of the indicated subrange. This appeared
  71. desirable in connection with packed records. Very few implementations have
  72. taken advantage of this space saving facility, because the additional
  73. compiler complexity is very considerable. Reason 1 alone, however, did not
  74. appear to provide sufficient justification to retain the subrange facility
  75. in Oberon.
  76.  
  77. With the absence of enumeration and subrange types, the general possibility
  78. of defining set types based on given element types appeared as redundant.
  79. Instead, a single, basic type SET is introduced, whose values are sets of
  80. integers from 0 to an implementation-defined maximum.
  81.  
  82. The basic type CARDINAL had been introduced in Modula in order to allow
  83. address arithmetic with values from 0 to 216 on 16-bit computers. With the
  84. prevalence of 32-bit addresses in modern processors, the need for unsigned
  85. arithmetic has practically vanished, and therefore the type CARDINAL has
  86. been eliminated. With it, the bothersome incompatibilities of operands of
  87. types CARDINAL and INTEGER have disappeared.
  88.  
  89. Pointer types are restricted to be bound to a record type or to an array
  90. type.
  91.  
  92. The notion of a definable index type of arrays has also been abandoned: All
  93. indices are by default integers. Furthermore, the lower bound is fixed to
  94. 0; array declarations specify a number of elements (length) rather than a
  95. pair of bounds. This break with a long standing tradition since Algol 60
  96. clearly demonstrates the principle of eliminating the inessential. The
  97. specification of an arbitrary lower bound hardly provides any additional
  98. expressive power. It represents a rather limited kind of mapping of indices
  99. which introduces a hidden computational effort that is incommensurate with
  100. the supposed gain in convenience. This effort is particularly heavy in
  101. connection with bound checking and with dynamic arrays.
  102.  
  103. Modules and import/export rules
  104.  
  105. Experience with Modula over the last eight years has shown that local
  106. modules were rarely used. Considering the additional complexity of the
  107. compiler required to handle them, and the additional complications in the
  108. visiblity rules of the language definition, the elimination of local
  109. modules appears justified.
  110.  
  111. The qualification of an imported object's identifier x by the exporting
  112. module's name M, viz. M.x, can be circumvented in Modula by the use of the
  113. import clause FROM M IMPORT x. This facility has also been discarded.
  114. Experience in programming systems involving many modules has taught that
  115. the explicit qualification of each occurrence of x is actually preferable.
  116. A simplification of the compiler is a welcome side-effect.
  117.  
  118. The dual role of the main module in Modula is conceptually confusing. It
  119. constitutes a module in the sense of a package of data and procedures
  120. enclosed by a scope of visibility, and at the same time it constitutes a
  121. single procedure called main program. A module is composed of two textual
  122. pieces, called the definition part and the implementation part. The former
  123. is missing in the case of a main program module.
  124.  
  125. By contrast, a module in Oberon is in itself complete and constitutes a
  126. unit of compilation. Definition and implementation parts are merged; names
  127. to be visible in client modules, i.e. exported identifiers, are marked, and
  128. they typically precede the declarations of objects not exported. A
  129. compilation generates in general a changed object file and a new symbol
  130. file. The latter contains information about exported objects for use in the
  131. compilation of client modules. The generation of a new symbol file must,
  132. however, be specifically enabled by a compiler option, because it will
  133. invalidate previous compilations of clients.
  134.  
  135. The notion of a main program has been abandoned. Instead, the set of
  136. modules linked through imports typically contains (parameterless)
  137. procedures. They are to be considered as individually activatable, and they
  138. are called commands. Such an activation has the form M.P, where P denotes
  139. the command and M the module containing it. The effect of a command is
  140. considered - not like that of a main program as accepting input and
  141. transforming it to output - as a change of state represented by global
  142. data.
  143.  
  144. Statements
  145.  
  146. The with statement of Modula has been discarded. Like in the case of
  147. imported identifiers, the explicit qualification of field identifiers is to
  148. be preferred. Another form of with statement is introduced; it has a
  149. different function and is called a regional guard (see below).
  150.  
  151. The elimination of the for statement constitutes a break with another long
  152. standing tradition. The baroque mechanism of Algol 60's for statement had
  153. been trimmed significantly in Pascal (and Modula). Its marginal value in
  154. practice has led to its absence from Oberon.
  155.  
  156. Low-level facilities
  157.  
  158. Modula makes access to machine-specific facilities possible through
  159. low-level constructs, such as the data types ADDRESS and WORD, absolute
  160. addressing of variables, and type casting functions. Most of them are
  161. packaged in a module called SYSTEM. These features were supposed to be
  162. rarely used and easily visible through the presence of the identifier
  163. SYSTEM in a module's import list. Experience has revealed, however, that a
  164. significant number of programmers import this module quite
  165. indiscriminately. A particularly seductive trap is the use of  Modula's
  166. type transfer functions.
  167.  
  168. It appears preferable to drop the pretense of portability of programs that
  169. import a "standard", yet system-specific module. Type transfer functions
  170. denoted by type identifiers are therefore eliminated, and the module SYSTEM
  171. is restricted to providing a few machine-specific functions that typically
  172. are compiled into inline code. In particular, it does not contain any data
  173. types, such as ADDRESS and WORD. Individual implementations are free to
  174. provide appropriate versions of the module SYSTEM, but their facilities do
  175. not belong to the language definition. The use of SYSTEM declares a program
  176. to be patently implementation-specific and thereby non-portable.
  177.  
  178. Concurrency
  179.  
  180. The system Oberon does not require any language facilities for expressing
  181. concurrent processes. The pertinent rudimentary features of Modula, in
  182. particular the coroutine, were therefore not retained. This exclusion is
  183. merely a reflection of our actual needs within the concrete project, but
  184. not on the general relevance of concurrency in programming.
  185.  
  186. Features introduced in Oberon
  187.  
  188. In contrast to the number of eliminated features, there are only a few new
  189. ones. The important new concepts are type extension and type inclusion.
  190. Furthermore, open arrays may have several dimensions (indices), whereas in
  191. Modula they were confined to a single dimension.
  192.  
  193. Type extension
  194.  
  195. The most important addition is the facility of extended record types. It
  196. permits the construction of new types on the basis of existing types, and
  197. establishes a certain degree of compatibility between the new and old
  198. types. Assuming a given type
  199.  
  200. T  =  RECORD x, y: INTEGER END
  201.  
  202. extensions may be defined which contain certain fields in addition to the
  203. existing ones. For example
  204.  
  205. T0  =  RECORD (T)  z: REAL END
  206. T1  =  RECORD (T)  w: LONGREAL END
  207.  
  208. define types with fields x, y, z and x, y, w respectively. We define a type
  209. declared by
  210.  
  211. T'  =  RECORD (T) <field definitions> END
  212.  
  213. to be a (direct) extension of T, and conversely T to be the (direct) base
  214. type of T'. Extended types may be extended again, giving rise to the
  215. following definitions:
  216.  
  217. A type T' is an extension of T, if T' = T or T' is a direct extension of an
  218. extension of T. Conversely, T is a base type of T', if T = T' or T is the
  219. direct base type of a base type of T'. We denote this relationship by T' .
  220. T.
  221.  
  222. The rule of assignment compatibility states that values of an extended type
  223. are assignable to variables of their base types. For example, a record of
  224. type T0 can be assigned to a variable of the base type T. This assignment
  225. involves the fields x and y only, and in fact constitutes a projection of
  226. the value onto the space spanned by the base type.
  227.  
  228. It is important to allow modules which import a base type to be able to
  229. declare extended types. In fact, this is probably the normal usage.
  230.  
  231. This concept of extensible data type gains importance when extended to
  232. pointers. It is appropriate to say that a pointer type P' bound to T'
  233. extends a pointer type P, if P is bound to a base type T of T', and to
  234. extend the assignment rule to cover this case. It is now possible to form
  235. data structures whose nodes are of different types, i.e. inhomogeneous data
  236. structures. The inhomogeneity is automatically (and most sensibly) bounded
  237. by the fact that the nodes are linked by pointers of a common base type.
  238.  
  239. Typically, the pointer fields establishing the structure are contained in
  240. the base type T, and the procedures manipulating the structure are defined
  241. in the same (base) module as T. Individual extensions (variants) are
  242. defined in client modules together with procedures operating on nodes of
  243. the extended type. This scheme is in full accordance with the notion of
  244. system extensibility: new modules defining new extensions may be added to a
  245. system without requiring a change of the base modules, not even their
  246. recompilation.
  247.  
  248. As access to an individual node via a pointer bound to a base type provides
  249. a projected view of the node data only, a facility to widen the view is
  250. necessary. It depends on the ability to determine the actual type of the
  251. referenced node. This is achieved by a type test, a Boolean expression of
  252. the form
  253.  
  254. t IS T'  (or   p IS P')
  255.  
  256. If the test is affirmative, an assignment t' := t (t' of type T') or p' :=
  257. p (p' of type P') should be possible. The static view of types, however,
  258. prohibits this. Note that both assignments violate the rule of assignment
  259. compatibility. The desired assignment is made possible by providing a type
  260. guard of the form
  261.  
  262. t' := t(T')  (p' := p(P'))
  263.  
  264. and by the same token access to the field z of a T0 (see previous examples)
  265. is made possible by a type guard in the designator  t(T0).z. Here the guard
  266. asserts that t is (currently) of type T0. In analogy to array bound checks
  267. and case selectors, a failing guard leads to program abortion.
  268.  
  269. Whereas a guard of the form t(T) asserts that t is of type T for the
  270. designator (starting with) t only, a regional type guard maintains the
  271. assertion over an entire sequence of statements. It has the form
  272.  
  273. WITH t: T DO StatementSequence END
  274.  
  275. and specifies that t is to be regarded as of type T within the entire
  276. statement sequence. Typically, T is an extension of the declared type of t.
  277. Note that assignments to t within the region therefore require the assigned
  278. value to be (an extension) of type T. The regional guard serves to reduce
  279. the number of guard evaluations.
  280.  
  281. As an example of the use of type tests and guards, consider the following
  282. types Node and Object defined in a module M:
  283.  
  284. TYPE  Node =  POINTER TO Object;
  285.   Object =  RECORD key, x, y: INTEGER;
  286.          left, right: Node
  287.     END
  288.  
  289. Elements in a tree structure anchored in a variable called root (of type
  290. Node) are searched by the procedure  element  defined in M.
  291.  
  292. PROCEDURE element(k: INTEGER): Node;
  293.   VAR p: Node;
  294. BEGIN p := root;
  295.   WHILE (p # NIL) & (p.key # k) DO
  296.     IF p.key < k THEN p := p.left ELSE p := p.right END
  297.   END ;
  298.   RETURN p
  299. END element
  300.  
  301. Let extensions of the type Object be defined (together with their pointer
  302. types) in a module M1 which is a client of M:
  303.  
  304. TYPE  Rectangle =  POINTER TO RectObject;
  305.   RectObject =  RECORD (Object) w, h: REAL END ;
  306.   Circle =  POINTER TO CircleObject;
  307.   CircleObject =  RECORD (Object) rad: REAL; shaded: BOOLEAN END
  308.  
  309. After the search of an element, the type test is used to discriminate
  310. between the different extensions, and the type guard to access extension
  311. fields. For example:
  312.  
  313. p := M.element(K);
  314. IF p # NIL THEN
  315.   IF p IS Rectangle THEN  ...  p(Rectangle).w  ...
  316.   ELSIF (p IS Circle) & ~p(Circle).shaded THEN  ...  p(Circle).rad  ...
  317.   ELSIF  ...
  318.  
  319. The extensibility of a system rests upon the premise that new modules
  320. defining new extensions may be added without requiring adaptations nor even
  321. recompilation of the existing parts, although components of the new types
  322. are included in already existing data structures.
  323.  
  324. The type extension facility not only replaces Modula's variant records, but
  325. represents a type-safe alternative. Equally important is its effect of
  326. relating types in a type hierarchy. We compare, for example, the Modula
  327. types
  328.  
  329. T0'  =  RECORD t: T;  z: REAL END ;
  330. T1'  =  RECORD t: T;  w: LONGREAL END
  331.  
  332. which refer to the definition of T given above, with the extended Oberon
  333. types T0 and T1 defined above. First, the Oberon types refrain from
  334. introducing a new naming scope. Given a variable r0 of type T0, we write
  335. r0.x instead of r0.t.x as in Modula. Second, the types T, T0', and T1' are
  336. distinct and unrelated. In contrast, T0 and T1 are related to T as
  337. extensions. This becomes manifest through the type test, which asserts that
  338. variable r0 is not only of type T0, but also of base type T.
  339.  
  340. The declaration of extended record types, the type test, and the type guard
  341. are the only additional features introduced in this context. A more
  342. extensive discussion is provided in [2]. The concept is very similar to the
  343. class notion of Simula 67 [3], Smalltalk [4], Object Pascal [5], C++ [6],
  344. and others, where the properties of the base class are said to be inherited
  345. by the derived classes. The class facility stipulates that all procedures
  346. applicable to objects of the class be defined together with the data
  347. definition. This dogma stems from the notion of abstract data type, but it
  348. is a serious obstacle in the development of large systems, where the
  349. possibility to add further procedures defined in additional modules is
  350. highly desirable. It is awkward to be obliged to redefine a class solely
  351. because a method (procedure) has been added or changed, particularly when
  352. this change requires a recompilation of the class definition and of all its
  353. client modules.
  354.  
  355. We emphasise that the type extension facility - although gaining its major
  356. role in connection with pointers to build heterogeneous, dynamic data
  357. structures as shown in the example above - also applies to statically
  358. declared objects used as variable parameters. Such objects are allocated in
  359. a workspace organized as a stack of procedure activation records, and
  360. therefore take advantage of an extremely efficient allocation and
  361. deallocation scheme.
  362.  
  363. In Oberon, procedure types rather than procedures (methods) are connected
  364. with objects in the program text. The binding of actual methods (specific
  365. procedures) to objects (instances) is delayed until the program is
  366. executed. The association of a procedure type with a data type occurs
  367. through the declaration of a record field. This field is given a procedure
  368. type. The association of a method - to use Smalltalk terminology - with an
  369. object occurs through the assignment of a specific procedure as value to
  370. the field, and not through a static declaration in the extended type's
  371. definition which then "overrides" the declaration given in the base type.
  372. Such a procedure is called a handler. Using type tests, the handler is
  373. capable of discriminating among different extensions of the record's
  374. (object's) base type. In Smalltalk, the compatibility rules between a class
  375. and its subclasses are confined to pointers, thereby intertwining the
  376. concepts of access method and data type in an undesirable way. In Oberon,
  377. the relationship between a type and its extensions is based on the
  378. established mathematical concept of projection.
  379.  
  380. In Modula, it is possible to declare a pointer type within an
  381. implementation module, and to export it as an opaque type by listing the
  382. same identifier in the corresponding definition module. The net effect is
  383. that the type is exported while all its properties remain hidden (invisible
  384. to clients). In Oberon, this facility is generalized in the sense that the
  385. selection of the record fields to be exported is arbitrary and includes the
  386. cases all and none. The collection of exported fields defines a partial
  387. view - a public projection - to clients.
  388.  
  389. In client modules as well as in the module itself, it is possible to define
  390. extensions of the base type (e.g. TextViewers or GraphViewers). Of
  391. importance is also the fact that non-exported components (fields) may have
  392. types that are not exported either. Hence, it is possible to hide certain
  393. data types effectively, although components of (opaquely) exported types
  394. refer to them.
  395.  
  396. Type inclusion
  397.  
  398. Modern processors feature arithmetic operations on several number formats.
  399. It is desirable to have all these formats reflected in the language as
  400. basic types. Oberon features five of them:
  401.  
  402. LONGINT, INTEGER, SHORTINT  (integer types)
  403. LONGREAL, REAL  (real types)
  404.  
  405. With the proliferation of basic types, a relaxation of compatibility rules
  406. among them becomes almost mandatory. (Note that in Modula the numeric types
  407. INTEGER, CARDINAL, and REAL are incompatible). To this end, the notion of
  408. type inclusion is introduced: a type T includes a type T', if the values of
  409. type T' are also values of type T. Oberon postulates the following
  410. hierarchy:
  411.  
  412. LONGREAL  J  REAL  J  LONGINT  J  INTEGER  J  SHORTINT
  413.  
  414. The assignment rule is relaxed accordingly: A value of type T' can be
  415. assigned to a variable of type T, if T' is included in T (or if T' extends
  416. T), i.e. if  T J T'  or  T' . T. In this respect, we return to (and extend)
  417. the flexibility of Algol 60. For example, given variables
  418.  
  419. i: INTEGER;  k: LONGINT;  x: REAL
  420.  
  421. the assignments
  422.  
  423. k := i;  x := k;  x := 1;  k := k+i;  x := x*10 + i
  424.  
  425. conform to the rules, whereas the statements  i := k;  k := x  are not
  426. acceptable. x := k may involve truncation.
  427.  
  428. The presence of several numeric types is evidently a concession to
  429. implementations which can allocate different amounts of storage to
  430. variables of the different types, and which thereby offer an opportunity
  431. for storage economization. This practical aspect should - with due respect
  432. for mathematical abstraction - not be ignored. The notion of type inclusion
  433. minimises the consequences for the programmer and requires only few
  434. implicit instructions for changing the data representation, such as sign
  435. extensions and integer to floating-point conversions.
  436.  
  437. .
  438.  
  439. Differences between Oberon and Revised Oberon
  440.  
  441. A revision of Oberon was defined after extensive experience in the use and
  442. implementation of the language. Again, it is characterized by the desire to
  443. simplify and integrate. The differences between the original version [7]
  444. and the revised version [9] are the following:
  445.  
  446. 1.  Definition and implementation parts of a module are merged. It appeared
  447. as desirable to have a module's specification contained in a single
  448. document, both from the view of the programmer and the compiler. A
  449. specification of its interface to clients (the definition part) can be
  450. derived automatically. Objects previously declared in the definition part
  451. (and repeated in the implementation part), are specially marked for export.
  452. The need for a structural comparison of two texts by the compiler thereby
  453. vanishes.
  454.  
  455. 2.  The syntax of lists of parameter types in the declaration of a
  456. procedure type is the same as that for regular procedure headings. This
  457. implies that dummy identifiers are introduced; they may be useful as
  458. comments.
  459.  
  460. 3.  The rule that type declarations must follow constant declarations, and
  461. that variable declarations must follow type declarations is relaxed.
  462.  
  463. 4.  The apostrophe is eliminated as a string delimiter.
  464.  
  465. 5.  The relaxed parameter compatibility rule for the formal type ARRAY OF
  466. BYTE is applicable for variable parameters only.
  467.  
  468. Summary
  469.  
  470. The language Oberon has evolved from Modula-2 and incorporates the
  471. experiences of many years of programming in Modula. A significant number of
  472. features have been eliminated. They appear to have contributed more to
  473. language and compiler complexity than to genuine power and flexibility of
  474. expression. A small number of features have been added, the most
  475. significant one being the concept of type extension.
  476.  
  477. The evolution of a new language that is smaller, yet more powerful than its
  478. ancestor is contrary to common practices and trends, but has inestimable
  479. advantages. Apart from simpler compilers, it results in a concise defining
  480. document [9], an indispensible prerequisite for any tool that must serve in
  481. the construction of sophisticated and reliable systems.
  482.  
  483. Acknowlegement
  484.  
  485. It is impossible to explicitly acknowledge all contributions of ideas that
  486. ultimately simmered down to what is now Oberon. Most came from the use or
  487. study of existing languages, such as Modula-2, Ada, Smalltalk, and Cedar,
  488. which often taught us how not to do it. Of particular value was the
  489. contribution of Oberon's first user, J. Gutknecht. The author is grateful
  490. for his insistence on the elimination of dead wood and on basing the
  491. remaining features on a sound mathematical foundation. And last, thanks go
  492. to the anonymous referee who very carefully read the manuscript and
  493. contributed many valuable suggestions for improvement.
  494.  
  495. References
  496.  
  497. 1.  N. Wirth. Programming in Modula-2. Springer-Verlag, 1982.
  498.  
  499. 2.  N. Wirth. Type Extensions. ACM Trans. on Prog. Languages and Systems,
  500.     10, 2 (April 1988) 204-214.
  501.  
  502. 3.  G. Birtwistle, et al. Simula Begin. Auerbach, 1973.
  503.  
  504. 4.  A. Goldberg, D. Robson. Smalltalk-80: The Language and its
  505.     Implementation. Addison-Wesley, 1983.
  506.  
  507. 5.  L. Tesler. Object Pascal Report. Structured Language World, 9, 3
  508.     (1985), 10-14.
  509.  
  510. 6.  B. Stroustrup. The Programming Language C++. Addison-Wesley, 1986.
  511.  
  512. 7.  N. Wirth. The programming language Oberon. Software - Practice and
  513.     Experience, 18, 7 (July 1988), 671-690.
  514.  
  515. 8.  J. Gutknecht and N. Wirth. The Oberon System. Software - Practice and
  516.     Experience, 19, (1989)
  517.  
  518. 9.  N. Wirth. The programming language Oberon (Revised Report).  (companion
  519.     paper)
  520.  
  521. File: ModToOberon2.Doc  /  NW 25.8.89
  522.  
  523.