Windows.OberonConfiguration.Mod 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. MODULE OberonConfiguration; (** fof **)
  2. (* read from and write to configuration files / texts etc. *)
  3. CONST
  4. TAB = 9X; CR = 0DX; LF = 0AX; MaxStrLen = 512; MaxIdLen = 64;
  5. eot = 0; lbrace = 2; rbrace = 3; eol = 4; equals = 6; char = 7; whitespace = 8; EOR* = 0X; cr* = 0; crlf* = 1;
  6. lf* = 2; lfcr* = 3;
  7. TYPE
  8. tName = ARRAY MaxIdLen + 1 OF CHAR;
  9. tString = ARRAY MaxStrLen OF CHAR; (* probably too long *)
  10. tEntry* = POINTER TO RECORD
  11. name-: tName;
  12. next: tEntry;
  13. father-: tSegment;
  14. END;
  15. KeyValueEnumerator* = PROCEDURE ( key, value: ARRAY OF CHAR );
  16. EntryEnumerator* = PROCEDURE ( entry: tEntry );
  17. tValue* = POINTER TO RECORD (tEntry)
  18. value*: tString;
  19. END;
  20. tSegment* = POINTER TO RECORD (tEntry)
  21. nEntries: LONGINT;
  22. entries: tEntry;
  23. END;
  24. WriterProc* = PROCEDURE ( ch: CHAR );
  25. ReaderProc* = PROCEDURE ( VAR ch: CHAR );
  26. TYPE
  27. tWriter = OBJECT
  28. VAR PutCh: WriterProc;
  29. EndOfLine: SHORTINT;
  30. PROCEDURE & Init*( w: WriterProc; eol: SHORTINT );
  31. BEGIN
  32. PutCh := w; EndOfLine := eol;
  33. END Init;
  34. PROCEDURE WriteSpace( level: LONGINT );
  35. VAR tab: CHAR;
  36. BEGIN
  37. tab := TAB;
  38. WHILE (level > 0) DO PutCh( TAB ); DEC( level ); END;
  39. END WriteSpace;
  40. PROCEDURE WriteLn;
  41. BEGIN
  42. CASE EndOfLine OF
  43. cr: PutCh( CR );
  44. | crlf:
  45. PutCh( CR ); PutCh( LF );
  46. | lf: PutCh( LF );
  47. | lfcr:
  48. PutCh( LF ); PutCh( CR );
  49. END;
  50. END WriteLn;
  51. (*
  52. PROCEDURE WriteQuotedString(str: ARRAY OF CHAR);
  53. VAR i: LONGINT; q1 (* " *) ,q2 (* ' *) : BOOLEAN; ch: CHAR;
  54. BEGIN
  55. ch := str[i]; q1 := FALSE; q2 := FALSE;
  56. WHILE(ch #0X) DO
  57. IF ch= '"' THEN q1 := TRUE;
  58. ELSIF ch = "'" THEN q2 := TRUE
  59. END;
  60. INC( i ); ch := str[i];
  61. END;
  62. IF q1=FALSE THEN
  63. PutCh('"'); WriteString(str); PutCh('"');
  64. ELSIF q2=FALSE THEN
  65. PutCh("'"); WriteString(str); PutCh("'");
  66. ELSE (* no good *)
  67. PutCh('"'); WriteString(str); PutCh('"');
  68. END
  69. END WriteQuotedString;
  70. *)
  71. PROCEDURE WriteString( str: ARRAY OF CHAR );
  72. VAR ch: CHAR; i: LONGINT;
  73. BEGIN
  74. ch := str[i];
  75. WHILE (ch # 0X) DO PutCh( ch ); INC( i ); ch := str[i]; END;
  76. END WriteString;
  77. PROCEDURE WriteEntry( e: tEntry; VAR level: LONGINT );
  78. BEGIN
  79. IF e IS tValue THEN
  80. WITH e: tValue DO
  81. WriteLn; WriteSpace( level );
  82. IF (level >=0) & (e.name # "") THEN WriteString( e.name ); WriteString( "=" ); PutCh( TAB ) END;
  83. WriteString( e.value );
  84. END;
  85. ELSIF e IS tSegment THEN
  86. WITH e: tSegment DO
  87. IF (level >=0) & (e.name # "") THEN
  88. WriteLn; WriteSpace( level ); WriteString( e.name ); WriteString( "=" ); PutCh( TAB )
  89. END;
  90. WriteSegment( e, level );
  91. END;
  92. ELSE WriteLn;
  93. (* empty line or empty assignment*)
  94. END;
  95. END WriteEntry;
  96. PROCEDURE WriteSegment( s: tSegment; VAR level: LONGINT );
  97. VAR entry: tEntry;
  98. BEGIN
  99. IF s = NIL THEN RETURN END;
  100. entry := s.entries;
  101. IF level >= 0 THEN WriteLn; WriteSpace( level ); PutCh( "{" ); END;
  102. INC( level );
  103. WHILE (entry # NIL ) DO WriteEntry( entry, level ); entry := entry.next; END;
  104. DEC( level );
  105. IF level >= 0 THEN WriteLn; WriteSpace( level ); PutCh( "}" ); END;
  106. END WriteSegment;
  107. END tWriter;
  108. tParser = OBJECT
  109. VAR writer: tWriter;
  110. ch, lastch: CHAR;
  111. noerr: BOOLEAN;
  112. pos: LONGINT;
  113. buflen, bufpos: LONGINT;
  114. buf: ARRAY MaxIdLen + 2 OF CHAR;
  115. och: CHAR;
  116. GetCh: ReaderProc;
  117. (** scanner *)
  118. PROCEDURE & Init*( r: ReaderProc; w: WriterProc );
  119. BEGIN
  120. GetCh := r; NEW( writer, w, EndOfLine ); pos := 0;
  121. END Init;
  122. PROCEDURE err( n: ARRAY OF CHAR );
  123. PROCEDURE Int( x: LONGINT );
  124. VAR i: LONGINT;
  125. a: ARRAY 12 OF CHAR;
  126. BEGIN
  127. i := 0;
  128. REPEAT a[i] := CHR( x MOD 10 + 30H ); x := x DIV 10; INC( i ) UNTIL x = 0;
  129. REPEAT DEC( i ); writer.PutCh( a[i] ) UNTIL i = 0
  130. END Int;
  131. BEGIN
  132. noerr := FALSE; writer.WriteString( n ); writer.WriteString( ". Error at pos " ); Int( pos );
  133. writer.WriteLn;
  134. END err;
  135. PROCEDURE Getchb( VAR ch: CHAR );
  136. BEGIN
  137. ch := buf[bufpos]; INC( bufpos );
  138. IF bufpos = buflen THEN buflen := 0; bufpos := 0; END;
  139. END Getchb;
  140. PROCEDURE Getch( VAR ch: CHAR );
  141. BEGIN
  142. IF ~noerr THEN ch := 0X; RETURN END;
  143. lastch := ch;
  144. IF buflen > 0 THEN Getchb( ch ) ELSE GetCh( ch ); INC( pos ); END;
  145. END Getch;
  146. PROCEDURE Get( VAR sym: SHORTINT );
  147. VAR s: SHORTINT;
  148. BEGIN
  149. IF och # 0X THEN (* ignore controls in string *)
  150. IF ch = och THEN och := 0X; s := char; Getch( ch ) ELSE
  151. CASE ch OF
  152. | CR:
  153. s := eol; och := 0X; Getch( ch );
  154. IF (ch = LF) THEN Getch( ch ) END;
  155. | LF: s := eol; och := 0X; Getch( ch );
  156. IF (ch = CR) THEN Getch( ch ) END;
  157. | EOR:
  158. s := eot;
  159. ELSE s := char; Getch( ch );
  160. END;
  161. END;
  162. ELSE
  163. CASE ch OF
  164. | 22X, 27X:
  165. och := ch; s := char; Getch( ch );
  166. | "{":
  167. s := lbrace; Getch( ch );
  168. | "}":
  169. s := rbrace; Getch( ch );
  170. | CR:
  171. s := eol; Getch( ch );
  172. IF (ch = LF) THEN Getch( ch ) END;
  173. | LF: s := eol; Getch( ch );
  174. IF (ch = CR) THEN Getch( ch ) END;
  175. | "=":
  176. s := equals; Getch( ch );
  177. | " ", TAB:
  178. s := whitespace; Getch( ch );
  179. | EOR:
  180. s := eot;
  181. ELSE s := char; Getch( ch );
  182. END;
  183. END;
  184. sym := s;
  185. END Get;
  186. (** parser *)
  187. PROCEDURE White( VAR sym: SHORTINT );
  188. BEGIN
  189. WHILE (sym = whitespace) OR (sym = eol) DO Get( sym ) END;
  190. END White;
  191. PROCEDURE SkipNL( VAR sym: SHORTINT );
  192. BEGIN
  193. WHILE (sym = whitespace) DO Get( sym ); END;
  194. IF sym = eol THEN Get( sym ) END;
  195. WHILE (sym = whitespace) DO Get( sym ); END;
  196. END SkipNL;
  197. PROCEDURE Entry( VAR sym: SHORTINT; name: ARRAY OF CHAR; segment: tSegment );
  198. VAR value: tString; i: LONGINT;
  199. BEGIN
  200. i := 0;
  201. WHILE (sym = char) OR (sym = whitespace) OR (sym = equals) DO
  202. value[i] := lastch; INC( i ); (* ASSERT(lastch # "}"); *)
  203. Get( sym );
  204. END;
  205. value[i] := 0X; AddValue( segment, name, value ); SkipNL( sym );
  206. END Entry;
  207. PROCEDURE Subsection( VAR sym: SHORTINT; name: ARRAY OF CHAR; segment: tSegment ): BOOLEAN;
  208. VAR seg: tSegment;
  209. BEGIN
  210. IF sym # lbrace THEN RETURN FALSE END;
  211. Get( sym ); SkipNL( sym ); seg := AddSegment( segment, name ); Segment( sym, seg );
  212. IF sym = rbrace THEN Get( sym ); SkipNL( sym ); ELSE err( "'}' expected" ); Get( sym ) END;
  213. RETURN TRUE
  214. END Subsection;
  215. PROCEDURE AssignHead( VAR sym: SHORTINT; VAR name: ARRAY OF CHAR ): BOOLEAN;
  216. VAR lastsym: SHORTINT; buflastch: CHAR;
  217. BEGIN
  218. ASSERT( buflen = 0 );
  219. IF sym # char THEN RETURN FALSE END;
  220. bufpos := 0; buflastch := lastch; lastsym := sym;
  221. WHILE (sym = char) & (bufpos < MaxIdLen) DO
  222. buf[bufpos] := ch; name[bufpos] := lastch; INC( bufpos ); Get( sym );
  223. END;
  224. name[bufpos] := 0X;
  225. WHILE (sym = whitespace) & (bufpos < MaxIdLen) DO buf[bufpos] := ch; INC( bufpos ); Get( sym );
  226. END;
  227. buf[bufpos] := ch; INC( bufpos ); buf[bufpos] := 0X;
  228. IF sym = equals THEN bufpos := 0; buflen := 0; Get( sym ); White( sym ); RETURN TRUE
  229. ELSE
  230. buflen := bufpos; ch := buflastch; bufpos := 0; och := 0X; Get( sym ); (* repeats GetSym before entry *)
  231. RETURN FALSE;
  232. END;
  233. END AssignHead;
  234. PROCEDURE Segment( VAR sym: SHORTINT; segment: tSegment );
  235. VAR name: tName;
  236. BEGIN
  237. WHILE (sym # eot) & (sym # rbrace) DO
  238. WHILE (sym = whitespace) DO Get( sym ); END;
  239. IF AssignHead( sym, name ) THEN
  240. IF Subsection( sym, name, segment ) THEN ELSE Entry( sym, name, segment ) END;
  241. ELSIF Subsection( sym, "", segment ) THEN
  242. ELSE Entry( sym, "", segment );
  243. END;
  244. END;
  245. END Segment;
  246. PROCEDURE Configuration( ): tSegment;
  247. VAR s: SHORTINT; segment: tSegment;
  248. BEGIN
  249. buflen := 0; bufpos := 0; noerr := TRUE; Getch( ch ); Get( s ); White( s );
  250. segment := AddSegment( NIL , "" ); Segment( s, segment );
  251. IF noerr THEN RETURN segment ELSE RETURN NIL END;
  252. END Configuration;
  253. END tParser;
  254. VAR
  255. EndOfLine*: SHORTINT; PathSeparator-: CHAR;
  256. (** output *)
  257. PROCEDURE Write*( e: tEntry; w: WriterProc; level: LONGINT );
  258. VAR writer: tWriter;
  259. BEGIN
  260. IF e = NIL THEN RETURN END;
  261. NEW( writer, w, EndOfLine ); writer.WriteEntry( e, level );
  262. END Write;
  263. (** tree generation *)
  264. PROCEDURE Append( e: tEntry; VAR to: tEntry );
  265. VAR r: tEntry;
  266. BEGIN
  267. IF to = NIL THEN to := e;
  268. ELSE
  269. r := to;
  270. WHILE (r.next # NIL ) DO r := r.next; END;
  271. r.next := e;
  272. END;
  273. END Append;
  274. PROCEDURE AddSegment*( to: tSegment; name: ARRAY OF CHAR ): tSegment;
  275. VAR s: tSegment;
  276. BEGIN
  277. NEW( s ); COPY( name, s.name );
  278. IF to # NIL THEN INC( to.nEntries ); Append( s, to.entries ); s.father := to; END;
  279. RETURN s;
  280. END AddSegment;
  281. PROCEDURE AddValue*( to: tSegment; name, value: ARRAY OF CHAR );
  282. VAR v: tValue; e: tEntry;
  283. BEGIN
  284. IF value # "" THEN
  285. NEW( v ); COPY( name, v.name ); COPY( value, v.value ); Append( v, to.entries ); v.father := to;
  286. ELSE NEW( e ); COPY( name, e.name ); Append( e, to.entries ); e.father := to;
  287. END;
  288. INC( to.nEntries )
  289. END AddValue;
  290. (** reading *)
  291. PROCEDURE NullCh( ch: CHAR );
  292. END NullCh;
  293. PROCEDURE Read*( r: ReaderProc; err: WriterProc ): tSegment;
  294. VAR p: tParser;
  295. BEGIN
  296. IF err = NIL THEN err := NullCh END;
  297. NEW( p, r, err ); RETURN p.Configuration();
  298. END Read;
  299. (** tree manipulation *)
  300. PROCEDURE Delete( e: tEntry; VAR from: tEntry );
  301. VAR r: tEntry;
  302. BEGIN
  303. IF from = e THEN from := from.next;
  304. ELSE
  305. r := from;
  306. WHILE (r.next # NIL ) DO
  307. IF r.next = e THEN
  308. r.next := e.next; e.next := NIL;
  309. RETURN; (* only one entry allowed *)
  310. END;
  311. r := r.next;
  312. END;
  313. END;
  314. END Delete;
  315. PROCEDURE StripQuotes*( VAR n: ARRAY OF CHAR );
  316. VAR i: LONGINT; ch, och: CHAR; (* strips the quotes and deletes rest (!) *)
  317. BEGIN
  318. ch := n[0];
  319. IF (ch = "'") OR (ch = '"') THEN
  320. och := ch;
  321. REPEAT INC( i ); ch := n[i]; n[i - 1] := ch; UNTIL (ch = 0X) OR (ch = och);
  322. n[i - 1] := 0X;
  323. END;
  324. END StripQuotes;
  325. PROCEDURE SameName( n1, n2: ARRAY OF CHAR ): BOOLEAN;
  326. BEGIN
  327. StripQuotes( n1 ); StripQuotes( n2 );
  328. IF n1 = n2 THEN RETURN TRUE ELSE RETURN FALSE END;
  329. END SameName;
  330. PROCEDURE FindNamedEntry*( in: tSegment; name: ARRAY OF CHAR ): tEntry;
  331. VAR entry: tEntry;
  332. BEGIN
  333. IF in = NIL THEN RETURN NIL END;
  334. entry := in.entries;
  335. WHILE (entry # NIL ) & (~SameName( name, entry.name )) DO entry := entry.next; END;
  336. RETURN entry;
  337. END FindNamedEntry;
  338. PROCEDURE RenameEntry*( e: tEntry; name: ARRAY OF CHAR );
  339. BEGIN
  340. IF e = NIL THEN RETURN END;
  341. COPY( name, e.name );
  342. END RenameEntry;
  343. PROCEDURE RemoveEntry*( e: tEntry );
  344. BEGIN
  345. IF (e = NIL ) OR (e.father = NIL ) THEN RETURN END;
  346. Delete( e, e.father.entries ); DEC( e.father.nEntries ); e.father := NIL;
  347. END RemoveEntry;
  348. PROCEDURE AddEntry*( e: tEntry; to: tSegment );
  349. BEGIN
  350. IF (e = NIL ) OR (to = NIL ) THEN RETURN END;
  351. ASSERT( e.father = NIL ); Append( e, to.entries ); INC( to.nEntries )
  352. END AddEntry;
  353. PROCEDURE EnumerateEntries*( enum: EntryEnumerator; segment: tEntry );
  354. VAR entry: tEntry;
  355. BEGIN
  356. IF (segment = NIL ) OR (~(segment IS tSegment)) THEN RETURN END;
  357. entry := segment( tSegment ).entries;
  358. WHILE (entry # NIL ) DO enum( entry ); entry := entry.next END;
  359. END EnumerateEntries;
  360. PROCEDURE EnumerateVals*( enum: KeyValueEnumerator; segment: tEntry );
  361. VAR entry: tEntry;
  362. BEGIN
  363. IF (segment = NIL ) OR (~(segment IS tSegment)) THEN RETURN END;
  364. entry := segment( tSegment ).entries;
  365. WHILE (entry # NIL ) DO
  366. IF entry IS tValue THEN
  367. WITH entry: tValue DO enum( entry.name, entry.value );
  368. END;
  369. END;
  370. entry := entry.next;
  371. END;
  372. END EnumerateVals;
  373. PROCEDURE Find*( root: tSegment; name: ARRAY OF CHAR ): tEntry;
  374. VAR name0: tName; ch: CHAR; i, j: LONGINT; entry: tEntry;
  375. BEGIN
  376. i := 1; j := 0; ch := name[0];
  377. WHILE (ch # 0X) DO
  378. IF ch = PathSeparator THEN
  379. name0[j] := 0X; entry := FindNamedEntry( root, name0 );
  380. IF (entry = NIL ) OR (~(entry IS tSegment)) THEN RETURN NIL ELSE root := entry( tSegment ); END;
  381. j := 0;
  382. ELSE name0[j] := ch; INC( j );
  383. END;
  384. ch := name[i]; INC( i );
  385. END;
  386. name0[j] := 0X; entry := FindNamedEntry( root, name0 ); RETURN entry;
  387. END Find;
  388. PROCEDURE FindOrCreateSegment(root: tSegment; name: ARRAY OF CHAR): tSegment;
  389. VAR name0: tName; ch: CHAR; i, j: LONGINT; entry: tEntry;
  390. BEGIN
  391. i := 1; j := 0; ch := name[0];
  392. WHILE (ch # 0X) DO
  393. IF ch = PathSeparator THEN
  394. name0[j] := 0X;
  395. entry := FindNamedEntry( root, name0 );
  396. IF entry = NIL THEN root := AddSegment(root,name0)
  397. ELSIF entry IS tSegment THEN root := entry(tSegment)
  398. ELSE RETURN NIL
  399. END;
  400. j := 0;
  401. ELSE name0[j] := ch; INC( j );
  402. END;
  403. ch := name[i]; INC( i );
  404. END;
  405. name0[j] := 0X;
  406. entry := FindNamedEntry( root, name0 );
  407. IF entry = NIL THEN root := AddSegment(root,name0)
  408. ELSIF entry IS tSegment THEN root := entry(tSegment)
  409. ELSE RETURN NIL
  410. END;
  411. RETURN root;
  412. END FindOrCreateSegment;
  413. PROCEDURE DeletePath*(root: tSegment; name: ARRAY OF CHAR): BOOLEAN;
  414. VAR name0: tName; ch: CHAR; i, j: LONGINT; entry: tEntry;
  415. BEGIN
  416. i := 1; j := 0; ch := name[0];
  417. WHILE (ch # 0X) DO
  418. IF ch = PathSeparator THEN
  419. name0[j] := 0X; entry := FindNamedEntry( root, name0 );
  420. IF (entry = NIL ) OR (~(entry IS tSegment)) THEN RETURN FALSE ELSE root := entry( tSegment ); END;
  421. j := 0;
  422. ELSE name0[j] := ch; INC( j );
  423. END;
  424. ch := name[i]; INC( i );
  425. END;
  426. name0[j] := 0X; entry := FindNamedEntry( root, name0 );
  427. IF (entry#NIL)& (entry IS tSegment) THEN
  428. RemoveEntry(entry) ;RETURN TRUE;
  429. ELSE
  430. RETURN FALSE
  431. END;
  432. END DeletePath;
  433. PROCEDURE DeleteKeyValue*( root: tSegment; path, key: ARRAY OF CHAR ): BOOLEAN;
  434. VAR entry: tEntry;
  435. BEGIN
  436. entry := Find( root, path );
  437. IF (entry = NIL ) OR (~(entry IS tSegment)) THEN RETURN FALSE END;
  438. entry := FindNamedEntry( entry( tSegment ), key );
  439. IF (entry = NIL ) OR (~(entry IS tValue)) THEN RETURN FALSE END;
  440. RemoveEntry( entry );
  441. RETURN TRUE
  442. END DeleteKeyValue;
  443. PROCEDURE SetKeyValue*( root: tSegment; path, key, value: ARRAY OF CHAR ): BOOLEAN;
  444. VAR entry: tEntry;
  445. BEGIN
  446. root := FindOrCreateSegment( root, path );
  447. IF (root = NIL ) THEN RETURN FALSE END;
  448. entry := FindNamedEntry( root, key );
  449. IF (entry # NIL ) & (~(entry IS tValue)) THEN RETURN FALSE END;
  450. IF entry = NIL THEN AddValue( root, key, value ) ELSE COPY( value, entry( tValue ).value ); END;
  451. RETURN TRUE;
  452. END SetKeyValue;
  453. PROCEDURE GetKeyValue*( root: tSegment; path, key: ARRAY OF CHAR; VAR value: ARRAY OF CHAR ): BOOLEAN;
  454. VAR entry: tEntry;
  455. BEGIN
  456. entry := Find( root, path );
  457. IF (entry = NIL ) OR (~(entry IS tSegment)) THEN RETURN FALSE END;
  458. entry := FindNamedEntry( entry( tSegment ), key );
  459. IF (entry = NIL ) OR (~(entry IS tValue)) THEN RETURN FALSE END;
  460. COPY( entry( tValue ).value, value );
  461. RETURN TRUE;
  462. END GetKeyValue;
  463. PROCEDURE SetPathSeparator*( ch: CHAR );
  464. BEGIN
  465. PathSeparator := ch;
  466. END SetPathSeparator;
  467. BEGIN
  468. EndOfLine := cr; PathSeparator := ".";
  469. END OberonConfiguration.
  470. (*
  471. Assignemnts:
  472. name = values ... (EOL)
  473. or
  474. "name" = values. ... (EOL)
  475. An Assignment like
  476. mytest = { "gjhsgdfsdf" }
  477. makes mytest a SEGMENT, no value, i.e. in most cases DISABLES mytest
  478. except mytest is really meant to be a segment like in the following example
  479. Gadgets = {
  480. Defaults={
  481. ...
  482. }
  483. }
  484. Configuration = White Segment eot
  485. Segment = {whitespace} {[AssignHead] Subsection | [AssignHead] Entry}
  486. AssignHead = {char|String} {whitespace} "=" White
  487. Subsection = "{" SkipNL Segment "}" Nl
  488. Entry = {whitespace|char|String|equals} Nl (* may be empty = eol only *)
  489. White = {whitespace|Eol}
  490. Nl = {whitespace} Eol {whitespace}
  491. Eol = lf[cr]|cr[lf].
  492. String = '"' {char|controls} '"' | "'" {char|controls} "'"
  493. informal:
  494. lf = LF
  495. cr = CR
  496. char = letter | digit | symbol \ {controls}
  497. controls = "{","}","="
  498. whitespace = tab|" "
  499. *)