MODULE*************************************************************************** Code common to all subtypes. ***************************************************************************; IMPORT ASCII, Fmt, IP, MD5Digest, RCSDate, RCSError, SupMisc, Text, Time, Word, Long; TokScan
TYPE Common = T OBJECT text: TEXT; sep: SET OF CHAR; len: CARDINAL; pos: CARDINAL; emptyTokens: BOOLEAN; onEmptyLastField := FALSE; METHODS init(t: TEXT; READONLY separators: SET OF CHAR := Blanks; emptyTokens := FALSE): T := Init; OVERRIDES getToken := GetToken; getChar := GetChar; getInt := GetInt; getTime := GetTime; getRCSDate := GetRCSDate; getMD5 := GetMD5; getEndpoint := GetEndpoint; getLiteral := GetLiteral; getFolded := GetFolded; getEnd := GetEnd; END; PROCEDURE*************************************************************************** Subclass for basic token scanning. ***************************************************************************GetChar (self: Common; what: TEXT := "single-character token"): CHAR RAISES {Error} = VAR t: TEXT; BEGIN t := self.getToken(what); IF Text.Length(t) # 1 THEN RAISE Error("Invalid " & what); END; RETURN Text.GetChar(t, 0); END GetChar; PROCEDUREGetEnd (self: Common; what: TEXT := "end") RAISES {Error} = VAR tok: TEXT; BEGIN IF self.next(tok) THEN RAISE Error("Expected " & what & ", got something else"); END; END GetEnd; PROCEDUREGetFolded (self: Common; what: TEXT) RAISES {Error} = BEGIN WITH qWhat = "\"" & what & "\"", t = self.getToken(qWhat) DO IF NOT EqualFolded(t, what) THEN RAISE Error("Expected " & qWhat & ", got something else"); END; END; END GetFolded; PROCEDUREGetEndpoint (self: Common; what: TEXT := "IP endpoint"): IP.Endpoint RAISES {Error} = VAR ep: IP.Endpoint; BEGIN TRY ep.addr.a[0] := self.getInt(); ep.addr.a[1] := self.getInt(); ep.addr.a[2] := self.getInt(); ep.addr.a[3] := self.getInt(); ep.port := self.getInt(); RETURN ep; EXCEPT Error => RAISE Error("Invalid " & what); END; END GetEndpoint; PROCEDUREGetInt (self: Common; what: TEXT := "integer"; radix: [2..16] := 10): Word.T RAISES {Error} = BEGIN RETURN AtoI(self.getToken(what), what, radix); END GetInt; PROCEDUREGetLiteral (self: Common; what: TEXT) RAISES {Error} = BEGIN WITH qWhat = "\"" & what & "\"", t = self.getToken(qWhat) DO IF NOT Text.Equal(t, what) THEN RAISE Error("Expected " & qWhat & ", got something else"); END; END; END GetLiteral; PROCEDUREGetMD5 (self: Common; what: TEXT := "MD5 checksum"): MD5Digest.T RAISES {Error} = BEGIN TRY RETURN MD5Digest.FromText(self.getToken(what)); EXCEPT MD5Digest.Malformed => RAISE Error("Invalid " & what); END; END GetMD5; PROCEDUREGetRCSDate (self: Common; what: TEXT := "RCS date"): Time.T RAISES {Error} = BEGIN TRY RETURN RCSDate.ToTime(self.getToken(what)); EXCEPT RCSError.E(msg) => RAISE Error("Invalid " & what & ": " & msg); END; END GetRCSDate; PROCEDUREGetTime (self: Common; what: TEXT := "time"): Time.T RAISES {Error} = BEGIN RETURN DecodeTime(self.getToken(what)); END GetTime; PROCEDUREGetToken (self: Common; what: TEXT := "token"): TEXT RAISES {Error} = VAR t: TEXT; BEGIN IF NOT self.next(t) THEN RAISE Error("Missing " & what); END; RETURN t; END GetToken; PROCEDUREInit (self: Common; t: TEXT; READONLY separators: SET OF CHAR := Blanks; emptyTokens := FALSE): T = BEGIN self.text := t; self.sep := separators; self.emptyTokens := emptyTokens; self.len := Text.Length(t); self.pos := 0; IF NOT self.emptyTokens THEN (* Skip a leading string of separators. *) WHILE self.pos < self.len AND Text.GetChar(self.text, self.pos) IN self.sep DO INC(self.pos); END; END; RETURN self; END Init;
TYPE Raw = Common OBJECT METHODS init(t: TEXT; READONLY separators: SET OF CHAR := Blanks; emptyTokens := FALSE): T := RawInit; OVERRIDES next := RawNext; getRest := RawGetRest; END; PROCEDURE*************************************************************************** Subclass for scanning and decoding escaped text. ***************************************************************************New (t: TEXT; READONLY separators: SET OF CHAR := Blanks; emptyTokens := FALSE): T = BEGIN RETURN NEW(Raw).init(t, separators, emptyTokens); END New; PROCEDURERawGetRest (self: Raw): TEXT = BEGIN WHILE self.pos < self.len AND Text.GetChar(self.text, self.pos) IN self.sep DO INC(self.pos); END; RETURN Text.Sub(self.text, self.pos); END RawGetRest; PROCEDURERawInit (self: Raw; t: TEXT; READONLY separators: SET OF CHAR := Blanks; emptyTokens := FALSE): T = BEGIN EVAL Common.init(self, t, separators, emptyTokens); RETURN self; END RawInit; PROCEDURERawNext (self: Raw; VAR tok: TEXT): BOOLEAN = VAR start: CARDINAL; BEGIN (* Upon entry we are positioned at the beginning of the new token. *) IF self.pos >= self.len THEN IF self.onEmptyLastField THEN self.onEmptyLastField := FALSE; ELSE RETURN FALSE; END; END; (* Scan the token. *) start := self.pos; WHILE self.pos < self.len AND NOT Text.GetChar(self.text, self.pos) IN self.sep DO INC(self.pos); END; tok := Text.Sub(self.text, start, self.pos - start); (* Skip the separator(s). *) IF self.pos < self.len THEN INC(self.pos); IF self.emptyTokens THEN IF self.pos = self.len THEN self.onEmptyLastField := TRUE; END; ELSE (* Skip a string of separators. *) WHILE self.pos < self.len AND Text.GetChar(self.text, self.pos) IN self.sep DO INC(self.pos); END; END; END; RETURN TRUE; END RawNext;
TYPE Dec = Raw OBJECT METHODS init(t: TEXT): T := DecInit; OVERRIDES next := DecNext; getRest := DecGetRest; END; PROCEDURE*************************************************************************** Handy utility procedures. ***************************************************************************DecGetRest (self: Dec): TEXT RAISES {Error} = BEGIN TRY RETURN SupMisc.DecodeWS(Raw.getRest(self)); EXCEPT SupMisc.InvalidEscape => RAISE Error("Invalid escape sequence"); END; END DecGetRest; PROCEDUREDecInit (self: Dec; t: TEXT): T = BEGIN EVAL Raw.init(self, t); RETURN self; END DecInit; PROCEDUREDecNext (self: Dec; VAR tok: TEXT): BOOLEAN RAISES {Error} = BEGIN TRY IF NOT Raw.next(self, tok) THEN RETURN FALSE END; tok := SupMisc.DecodeWS(tok); RETURN TRUE; EXCEPT SupMisc.InvalidEscape => RAISE Error("Invalid escape sequence"); END; END DecNext; PROCEDURENewDec (t: TEXT): T = BEGIN RETURN NEW(Dec).init(t); END NewDec;
PROCEDUREAtoI (t: TEXT; what: TEXT := "integer"; radix: [2..16] := 10): Word.T RAISES {Error} = VAR len := Text.Length(t); val: Word.T := 0; digit: INTEGER; BEGIN IF len = 0 THEN RAISE Error("Invalid " & what); END; FOR i := 0 TO len-1 DO WITH ch = Text.GetChar(t, i) DO CASE ch OF | '0'..'9' => digit := ORD(ch) - ORD('0'); | 'a'..'f' => digit := ORD(ch) - ORD('a') + 10; | 'A'..'F' => digit := ORD(ch) - ORD('A') + 10; ELSE digit := radix; END; IF digit >= radix THEN RAISE Error("Invalid " & what); END; val := Word.Plus(Word.Times(val, radix), digit); END; END; RETURN val; END AtoI; PROCEDUREAtoL (t: TEXT; what: TEXT := "integer"; radix: [2..16] := 10): Long.T RAISES {Error} = VAR len := Text.Length(t); val: Long.T := 0L; digit: INTEGER; BEGIN IF len = 0 THEN RAISE Error("Invalid " & what); END; FOR i := 0 TO len-1 DO WITH ch = Text.GetChar(t, i) DO CASE ch OF | '0'..'9' => digit := ORD(ch) - ORD('0'); | 'a'..'f' => digit := ORD(ch) - ORD('a') + 10; | 'A'..'F' => digit := ORD(ch) - ORD('A') + 10; ELSE digit := radix; END; IF digit >= radix THEN RAISE Error("Invalid " & what); END; val := Long.Plus(Long.Times(val, VAL(radix, LONGINT)), VAL(digit, LONGINT)); END; END; RETURN val; END AtoL; PROCEDUREDecodeTime (text: TEXT): Time.T RAISES {Error} = VAR negative := FALSE; time: Time.T; BEGIN IF Text.Length(text) > 0 AND Text.GetChar(text, 0) = '-' THEN negative := TRUE; text := Text.Sub(text, 1); END; time := FLOAT(AtoI(text), Time.T); IF negative THEN time := -time; END; RETURN time; END DecodeTime; PROCEDUREEncodeEndpoint (READONLY ep: IP.Endpoint; VAR toks: ARRAY [0..4] OF TEXT) = BEGIN toks[0] := Fmt.Int(ep.addr.a[0]); toks[1] := Fmt.Int(ep.addr.a[1]); toks[2] := Fmt.Int(ep.addr.a[2]); toks[3] := Fmt.Int(ep.addr.a[3]); toks[4] := Fmt.Int(ep.port); END EncodeEndpoint; PROCEDUREEncodeTime (time: Time.T): TEXT = VAR absTime: Word.T; BEGIN absTime := ROUND(ABS(time)); IF time < 0.0d0 AND absTime # 0 THEN RETURN "-" & Fmt.Unsigned(absTime, 10); ELSE RETURN Fmt.Unsigned(absTime, 10); END; END EncodeTime; PROCEDUREEqualFolded (a, b: TEXT): BOOLEAN = VAR len := Text.Length(a); BEGIN IF Text.Length(b) # len THEN RETURN FALSE END; FOR i := 0 TO len-1 DO IF ASCII.Upper[Text.GetChar(a, i)] # ASCII.Upper[Text.GetChar(b, i)] THEN RETURN FALSE; END; END; RETURN TRUE; END EqualFolded; PROCEDUREScanLeadingInt (t: TEXT; VAR pos: CARDINAL): Word.T = VAR tLen := Text.Length(t); val: Word.T := 0; BEGIN WHILE pos < tLen DO WITH ch = Text.GetChar(t, pos) DO IF ch < '0' OR ch > '9' THEN EXIT END; val := Word.Plus(Word.Times(val, 10), ORD(ch) - ORD('0')); END; INC(pos); END; RETURN val; END ScanLeadingInt; PROCEDURETrim (t: TEXT): TEXT = CONST WhiteSpace = SET OF CHAR{' ', '\t'}; VAR start := 0; limit := Text.Length(t); BEGIN WHILE start < limit AND Text.GetChar(t, start) IN WhiteSpace DO INC(start); END; WHILE start < limit AND Text.GetChar(t, limit-1) IN WhiteSpace DO DEC(limit); END; RETURN Text.Sub(t, start, limit-start); END Trim; BEGIN END TokScan.