SemVer/src/SemVer.Mod
2025-06-16 07:49:29 +04:00

168 lines
3.7 KiB
Modula-2

MODULE SemVer;
IMPORT Out;
TYPE
StringIdentifier = ARRAY 256 OF CHAR;
Version* = RECORD
Major*, Minor*, Patch*: INTEGER;
PreRelease*, Build*: StringIdentifier;
END;
PROCEDURE IsDigit(ch: CHAR): BOOLEAN;
BEGIN
RETURN (ch >= '0') & (ch <= '9');
END IsDigit;
PROCEDURE HasLeadingZero(s: ARRAY OF CHAR; i: INTEGER): BOOLEAN;
BEGIN
RETURN (s[i] = '0') & IsDigit(s[i + 1]);
END HasLeadingZero;
PROCEDURE ParseInt(s: ARRAY OF CHAR; VAR i, n: INTEGER; VAR ok: BOOLEAN);
VAR digit: INTEGER;
BEGIN
n := 0;
IF ~IsDigit(s[i]) THEN ok := FALSE; RETURN END;
WHILE (i < LEN(s)) & IsDigit(s[i]) DO
digit := ORD(s[i]) - ORD('0');
n := n * 10 + digit;
INC(i);
END;
ok := TRUE;
END ParseInt;
PROCEDURE IsIdentChar(ch: CHAR): BOOLEAN;
BEGIN
RETURN ((ch >= '0') & (ch <= '9')) OR
((ch >= 'A') & (ch <= 'Z')) OR
((ch >= 'a') & (ch <= 'z')) OR
(ch = '-');
END IsIdentChar;
PROCEDURE ParseIdentifiers(
s: ARRAY OF CHAR;
VAR i: INTEGER;
VAR dest: StringIdentifier;
checkLeadingZero: BOOLEAN;
VAR ok: BOOLEAN);
VAR
j, segLen: INTEGER;
ch: CHAR;
isNumeric, hasLeadingZero: BOOLEAN;
BEGIN
j := 0; ok := FALSE;
(* Require at least one character *)
IF (s[i] = '.') OR (s[i] = '+') OR (s[i] = 0X) THEN RETURN END;
LOOP
segLen := 0;
isNumeric := TRUE;
hasLeadingZero := FALSE;
(* Empty identifier is invalid *)
IF (s[i] = '.') OR (s[i] = '+') OR (s[i] = 0X) THEN RETURN END;
WHILE (s[i] # '.') & (s[i] # '+') & (s[i] # 0X) DO
ch := s[i];
IF ~IsIdentChar(ch) THEN RETURN END;
(* Check for leading-zero numeric identifiers *)
IF checkLeadingZero THEN
IF segLen = 0 THEN
IF ch = '0' THEN hasLeadingZero := TRUE END;
ELSE
IF hasLeadingZero & IsDigit(ch) THEN RETURN END;
END;
IF ~IsDigit(ch) THEN isNumeric := FALSE END;
END;
IF j >= LEN(dest) - 1 THEN RETURN END;
dest[j] := ch; INC(i); INC(j); INC(segLen);
END;
(* Add dot if needed *)
IF s[i] = '.' THEN
IF j >= LEN(dest) - 1 THEN RETURN END;
dest[j] := '.'; INC(i); INC(j);
ELSE
EXIT;
END;
END;
dest[j] := 0X;
ok := TRUE;
END ParseIdentifiers;
PROCEDURE ParsePreRelease(
s: ARRAY OF CHAR;
VAR i: INTEGER;
VAR PreRelease: StringIdentifier;
VAR ok: BOOLEAN);
BEGIN
IF s[i] # '-' THEN ok := FALSE; RETURN END;
INC(i);
ParseIdentifiers(s, i, PreRelease, TRUE (* checkLeadingZero *), ok);
END ParsePreRelease;
PROCEDURE ParseBuild(
s: ARRAY OF CHAR;
VAR i: INTEGER;
VAR Build: StringIdentifier;
VAR ok: BOOLEAN);
BEGIN
IF s[i] # '+' THEN ok := FALSE; RETURN END;
INC(i);
ParseIdentifiers(s, i, Build, FALSE (* checkLeadingZero *), ok);
END ParseBuild;
PROCEDURE Parse*(s: ARRAY OF CHAR; VAR v: Version; VAR finalok: BOOLEAN);
VAR
i: INTEGER;
ok: BOOLEAN;
BEGIN
(* Make sure it's clean *)
v.Major := 0;
v.Minor := 0;
v.Patch := 0;
v.PreRelease[0] := 0X;
v.Build[0] := 0X;
ok := FALSE;
finalok := FALSE;
i := 0;
(* Major *)
IF HasLeadingZero(s, i) THEN RETURN END;
ParseInt(s, i, v.Major, ok); IF ~ok THEN RETURN END;
IF s[i] # '.' THEN RETURN END; INC(i);
(* Minor *)
IF HasLeadingZero(s, i) THEN RETURN END;
ParseInt(s, i, v.Minor, ok); IF ~ok THEN RETURN END;
IF s[i] # '.' THEN RETURN END; INC(i);
(* Patch *)
IF HasLeadingZero(s, i) THEN RETURN END;
ParseInt(s, i, v.Patch, ok); IF ~ok THEN RETURN END;
(* PreRelease, if exists *)
IF s[i] = '-' THEN
ParsePreRelease(s, i, v.PreRelease, ok)
END;
(* Build, if exists *)
IF s[i] = '+' THEN
ParseBuild(s, i, v.Build, ok)
END;
(* Must end cleanly *)
IF s[i] # 0X THEN ok := FALSE; RETURN END;
finalok := TRUE;
END Parse;
END SemVer.