cmvbt/src/IPTypeinVBT.m3


MODULE IPTypeinVBT;
IMPORT TypeinVBT, KeyFilter, TextPort, VBTClass, VBT;
IMPORT IP, Scan, TextRd, Text, Fmt, Lex, Rd, Thread, FloatMode;

REVEAL
  T = TypeinVBT.T BRANDED "IPTypeinVBT.T" OBJECT OVERRIDES
        key := Key;
      END;

PROCEDURE IsComplete(ip: TEXT): BOOLEAN =
  TYPE
    Ch = {Digit, Dot};
  CONST
    mustfind = ARRAY OF Ch {Ch.Digit, Ch.Dot, Ch.Digit, Ch.Dot, Ch.Digit, Ch.Dot, Ch.Digit };
  VAR
    where := FIRST(mustfind);
    a := ARRAY [1..15] OF CHAR { VAL(0,CHAR), ..};
  BEGIN
    Text.SetChars (a, ip);
    FOR i := FIRST(a) TO LAST(a) DO
      CASE a[i] OF
      | '0'..'9' =>
        IF mustfind[where] = Ch.Dot THEN
          INC(where);
        END;
        IF mustfind[where] # Ch.Digit THEN
          RETURN FALSE;
        END;
      | '.' =>
        <* ASSERT mustfind[where] # Ch.Dot *>
        INC(where);
      ELSE
        EXIT;
      END;
    END;
    RETURN where = LAST(mustfind);
 END IsComplete;

PROCEDURE Key (t: T; READONLY cd: VBT.KeyRec) =
  <* FATAL FloatMode.Trap, Lex.Error *>
  BEGIN
    IF cd.wentDown AND cd.whatChanged # VBT.NoKey AND
        NOT KeyFilter.IsModifier (cd.whatChanged) THEN
          WITH txt = TextPort.GetText(t),
               len = Text.Length (txt),
               lastthree = Text.Sub (txt, MAX(len-3,0), 3),
               dot_in_lastthree = Text.FindChar (lastthree, '.') # -1 DO
            CASE cd.whatChanged OF
            | ORD('0')..ORD('9') =>
               IF IsComplete(txt) THEN
                 IF NOT dot_in_lastthree THEN RETURN END;
               ELSIF len >= 3 AND NOT dot_in_lastthree THEN
                 VAR fake := cd; BEGIN
                   fake.whatChanged := ORD('.');
                   VBTClass.Key (t, fake);
                   TypeinVBT.T.key (t, cd);
                   RETURN;
                 END;
               END;
               WITH lastnum =
                 Text.Sub (lastthree, MAX(0, Text.FindCharR(lastthree, '.')+1), 3) DO
                 IF NOT Text.Empty (lastnum) THEN
                   CASE Scan.Int(lastnum) OF
                   | 0..24 => (* continue *)
                   | 25    => IF cd.whatChanged > ORD('5') THEN RETURN END;
                   ELSE     RETURN
                   END;
                 END;
               END;
               TypeinVBT.T.key (t, cd);
            | ORD('.') =>
              IF IsComplete (txt) THEN
                (* ignore incoming keys *)
              ELSIF len # 0 AND Text.GetChar(txt, len-1) # '.' THEN
                TypeinVBT.T.key (t, cd);
              END;
            | ORD(FIRST(CHAR))..ORD('.')-1,
              ORD('.')+1..ORD('0')-1,
              ORD('9')+1..ORD(LAST(CHAR)) =>
              (* ignore any printable characters *)
            ELSE (* other keys, like Return, tab, etc... *)
              TypeinVBT.T.key (t, cd);
            END
        END;
    END
  END Key;

PROCEDURE Get(v: T): IP.Address RAISES {InvalidAddress} =
  VAR
    ip: IP.Address;
    rd := TextRd.New(TextPort.GetText(v));
  BEGIN
    TRY
      FOR i := FIRST(ip.a) TO LAST(ip.a) DO
        ip.a[i] := Lex.Int (rd);
        IF ip.a[i] < 0 OR ip.a[i] > 255 THEN RAISE InvalidAddress END;
        IF i # LAST(ip.a) THEN Lex.Match (rd, ".") END;
      END;
    EXCEPT
    | Lex.Error, Rd.Failure, Thread.Alerted, FloatMode.Trap =>
        RAISE InvalidAddress;
    END;
    RETURN ip;
  END Get;

PROCEDURE Put (v: T; addr: IP.Address) RAISES {InvalidAddress} =
  PROCEDURE Conv (i: INTEGER): TEXT RAISES {InvalidAddress} =
    BEGIN
      IF i >= 0 AND i <= 255 THEN
        RETURN Fmt.Int(i);
      END;
      RAISE InvalidAddress;
    END Conv;
  VAR
    txt := Conv(addr.a[0]);
  BEGIN
    FOR i := 1 TO 3 DO
      txt := txt & "." & Conv(addr.a[i]);
    END;
    TextPort.SetText(v, txt);
  END Put;

BEGIN
END IPTypeinVBT.