Вот ещё один

## Код:
```
MODULE AixRdConf; (* Configuration reader; Alexander Shiryaev, 09.12.06 *)

   (* EBNF:
      conf = { line eoln } [ line ] 0X .
      line = [ spaces ] [ valuable ] [ spaces ] [ comment ] .
      valuable = key [ spaces ] '=' [ spaces ] value .
      comment = '#' IGNORE .
      key = key_ident { key_ident } .
      value = string | integer | boolean .
      string = { string_ident } .
      string_ident = key_ident | '/' | '.' | '-' .
      key_ident = 'a'..'z' | 'A'..'Z' | digit | '_' .
      integer = [ '+' | '-' ] digit { digit } .
      digit = '0'..'9' .
      boolean = "on" | "off" .
      spaces = { ' ' | 09X } .
      eoln = ( 0AX | 0DX ) { 0AX | 0DX } .
   *)
   
   CONST
      ok* = 0;
      unexpectedCharacter* = 1;
      identTooLong* = 2;
      integerTooLong* = 3;
      invalidKey* = 4;
      alreadyAccepted* = 5;
      notAllAccepted* = 6;

   TYPE
      Node* = POINTER TO ABSTRACT RECORD
         next: Node;
         key: POINTER TO ARRAY OF CHAR;
         accepted: BOOLEAN
      END;
      BooleanParameter* = POINTER TO RECORD
         bool-: BOOLEAN
      END;
      BooleanNode = POINTER TO RECORD (Node)
         p: BooleanParameter
      END;
      IntegerParameter* = POINTER TO RECORD
         int-: INTEGER
      END;
      IntegerNode = POINTER TO RECORD (Node)
         p: IntegerParameter
      END;
      StringParameter* = POINTER TO RECORD
         str-: POINTER TO ARRAY OF CHAR
      END;
      StringNode = POINTER TO RECORD (Node)
         p: StringParameter
      END;
      Stream* = POINTER TO ABSTRACT RECORD
      END;

   PROCEDURE CopyString (IN x: ARRAY OF CHAR): POINTER TO ARRAY OF CHAR;
      VAR s: POINTER TO ARRAY OF CHAR;
   BEGIN
      NEW(s, LEN(x$) + 1);
      s^ := x$;
      RETURN s
   END CopyString;

   PROCEDURE BindBoolean* (VAR node: Node; IN key: ARRAY OF CHAR; p: BooleanParameter);
      VAR nb: BooleanNode;
   BEGIN
      NEW(nb);
      nb.key := CopyString(key);
      nb.p := p;
      nb.accepted := FALSE;
      nb.next := node;
      node := nb
   END BindBoolean;

   PROCEDURE BindInteger* (VAR node: Node; IN key: ARRAY OF CHAR; p: IntegerParameter);
      VAR ni: IntegerNode;
   BEGIN
      NEW(ni);
      ni.key := CopyString(key);
      ni.p := p;
      ni.accepted := FALSE;
      ni.next := node;
      node := ni
   END BindInteger;

   PROCEDURE BindString* (VAR node: Node; IN key: ARRAY OF CHAR; p: StringParameter);
      VAR ns: StringNode;
   BEGIN
      NEW(ns);
      ns.key := CopyString(key);
      ns.p := p;
      ns.accepted := FALSE;
      ns.next := node;
      node := ns
   END BindString;
   
   PROCEDURE (s: Stream) Get- (OUT c: CHAR), NEW, ABSTRACT;
   
   PROCEDURE FindNode (n: Node; IN key: ARRAY OF CHAR): Node;
   BEGIN
      WHILE (n # NIL) & (n.key^ # key) DO
         n := n.next
      END;
      RETURN n
   END FindNode;
   
   PROCEDURE Read* (node: Node; s: Stream; OUT res: INTEGER);
      CONST bufLen = 1024;
      VAR c: CHAR;
         str: ARRAY bufLen OF CHAR;
         int: INTEGER;
         bool: BOOLEAN;
         n: Node;
      
      PROCEDURE Next;
      BEGIN
         s.Get(c)
      END Next;
      
      PROCEDURE Spaces;
      BEGIN
         WHILE (c = ' ') OR (c = 09X) DO
            Next
         END
      END Spaces;
      
      PROCEDURE IsKeyIdent (c: CHAR): BOOLEAN;
      BEGIN
         RETURN (c >= 'a') & (c <= 'z') OR (c >= 'A') & (c <= 'Z') OR (c >= '0') & (c <= '9') OR (c = '_')
      END IsKeyIdent;
      
      PROCEDURE IsStringIdent (c: CHAR): BOOLEAN;
      BEGIN
         RETURN IsKeyIdent(c) OR (c = '/') OR (c = '.') OR (c = '-')
      END IsStringIdent;
      
      PROCEDURE IsDigit (c: CHAR): BOOLEAN;
      BEGIN
         RETURN (c >= '0') & (c <= '9')
      END IsDigit;
      
      PROCEDURE EoLn;
      BEGIN
         WHILE (c = 0AX) OR (c = 0DX) DO
            Next
         END
      END EoLn;
      
      PROCEDURE Expect (ch: CHAR);
      BEGIN
         IF c = ch THEN
            Next
         ELSE
            res := unexpectedCharacter
         END
      END Expect;
      
      PROCEDURE Key;
         VAR i: INTEGER;
      BEGIN
         i := 0;
         WHILE (i < bufLen) & IsKeyIdent(c) DO
            str[i] := c;
            INC(i);
            Next
         END;
         
         IF i = bufLen THEN
            res := identTooLong
         ELSE
            str[i] := 0X
         END
      END Key;
      
      PROCEDURE String;
         VAR i: INTEGER;
      BEGIN
         i := 0;
         WHILE (i < bufLen) & IsStringIdent(c) DO
            str[i] := c;
            INC(i);
            Next
         END;
         
         IF i = bufLen THEN
            res := identTooLong
         ELSE
            str[i] := 0X
         END
      END String;
      
      PROCEDURE Integer;
         VAR sign, overflow: BOOLEAN;
            i: INTEGER;
      BEGIN
         IF c = '+' THEN
            sign := FALSE;
            Next
         ELSIF c = '-' THEN
            sign := TRUE;
            Next
         ELSE
            sign := FALSE
         END;
         IF IsDigit(c) THEN
            int := 0;
            LOOP
               i := ORD(c) - ORD('0');
               overflow := (int > (MAX(INTEGER) - i) DIV 10);
               IF overflow THEN EXIT END;
               int := int * 10 + i;
               Next;
               IF ~IsDigit(c) THEN EXIT END
            END;
            IF overflow THEN
               res := integerTooLong
            ELSIF sign THEN
               int := -int
            END
         ELSE
            res := unexpectedCharacter
         END
      END Integer;
      
      PROCEDURE Boolean; 
      BEGIN
         IF c = 'o' THEN
            Next;
            IF c = 'n' THEN
               Next;
               bool := TRUE
            ELSIF c = 'f' THEN
               Next;
               IF c = 'f' THEN
                  Next;
                  bool := FALSE
               ELSE
                  res := unexpectedCharacter
               END
            ELSE
               res := unexpectedCharacter
            END
         ELSE
            res := unexpectedCharacter
         END
      END Boolean;
      
      PROCEDURE Value;
      BEGIN
         n := FindNode(node, str);
         IF n # NIL THEN
            IF ~n.accepted THEN
               WITH n: StringNode DO
                  String
               | n: IntegerNode DO
                  Integer
               | n: BooleanNode DO
                  Boolean
               END
            ELSE
               res := alreadyAccepted
            END
         ELSE
            res := invalidKey
         END
      END Value;
      
      PROCEDURE Line;
      BEGIN
         Spaces;
         IF IsKeyIdent(c) THEN
            Key;
            IF res = ok THEN
               Spaces;
               Expect('=');
               IF res = ok THEN
                  Spaces;
                  Value
               END
            END
         END;
         
         IF res = ok THEN
            Spaces;
            
            IF c = '#' THEN
               Next;
               WHILE (c # 0AX) & (c # 0DX) & (c # 0X) DO
                  Next
               END
            END;
            
            IF (c = 0AX) OR (c = 0DX) THEN
               EoLn
            ELSIF c = 0X THEN
            ELSE
               res := unexpectedCharacter
            END;
            
            IF (res = ok) & (n # NIL) THEN
               WITH n: StringNode DO
                  n.p.str := CopyString(str)
               | n: IntegerNode DO
                  n.p.int := int
               | n: BooleanNode DO
                  n.p.bool := bool
               END;
               n.accepted := TRUE
            END
         END
      END Line;
      
   BEGIN
      ASSERT(n = NIL);
      
      res := ok;
      Next;
      WHILE (c # 0X) & (res = ok) DO
         Line
      END;
      
      IF res = ok THEN
         n := node;
         WHILE (n # NIL) & n.accepted DO
            n := n.next
         END;
         IF n # NIL THEN
            res := notAllAccepted
         END
      END
   END Read;

END AixRdConf.
```


## Код:
```
MODULE TmpRdConfTest;

   IMPORT Log := StdLog, RdConf := AixRdConf;

   TYPE
      Stream = POINTER TO RECORD (RdConf.Stream)
         i: INTEGER
      END;

   PROCEDURE (s: Stream) Get (OUT c: CHAR);
      VAR str: ARRAY 1024 OF CHAR;
   BEGIN
      str := "sleep_time_minutes = 60" + 0AX + "use_certificates = off" + 0AX + "ssh_user_keys_path=.ssh/authorized_keys";
      c := str[s.i];
      INC(s.i)
   END Get;

   PROCEDURE Do*;
      VAR conf: RdConf.Node;
         int: RdConf.IntegerParameter;
         bool: RdConf.BooleanParameter;
         str: RdConf.StringParameter;
         s: Stream;
         res: INTEGER;
   BEGIN
      NEW(int);
      NEW(bool);
      NEW(str);
      NEW(s); s.i := 0;
      
      ASSERT(conf = NIL);
      
      RdConf.BindInteger(conf, "sleep_time_minutes", int);
      RdConf.BindBoolean(conf, "use_certificates", bool);
      RdConf.BindString(conf, "ssh_user_keys_path", str);
      RdConf.Read(conf, s, res);
      
      IF res = RdConf.ok THEN
         Log.Int(int.int);
         Log.Tab;
         Log.Bool(bool.bool);
         Log.Tab;
         Log.String(str.str);
      ELSE
         Log.String("error:");
         Log.Tab;
         Log.Int(res)
      END;
      Log.Ln
   END Do;

END TmpRdConfTest.

(*)TmpRdConfTest.Do
```


Для пользователя исключены следующие ошибки:
* указаны не все параметры
* дублирование параметров

3 типа значений:
* STRING
* INTEGER
* BOOLEAN