Semantic Versioning Parser for voc
This commit is contained in:
commit
052d41505e
7 changed files with 510 additions and 0 deletions
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Set the language to Oberon
|
||||
*.Mod linguist-language=Oberon
|
||||
*.mod linguist-language=Oberon
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build
|
||||
22
LICENSE.txt
Normal file
22
LICENSE.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright 2025 Antranig Vartanian.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
25
Makefile
Normal file
25
Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.POSIX:
|
||||
|
||||
SRC = ../src
|
||||
TESTS = ../tests
|
||||
BUILD = build
|
||||
|
||||
OBJS = $(BUILD)/SemVer.o
|
||||
SEMVERTEST = $(BUILD)/SemVerTest
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
mkdir -p $(BUILD)
|
||||
cd $(BUILD) && voc $(SRC)/SemVer.Mod -s 2>/dev/null
|
||||
|
||||
test: $(SEMVERTEST)
|
||||
cd $(BUILD) \
|
||||
&& ./SemVerTest
|
||||
|
||||
$(SEMVERTEST): build
|
||||
cd $(BUILD) \
|
||||
&& voc $(TESTS)/SemVerTest.Mod -m 2>/dev/null
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD)
|
||||
94
README.md
Normal file
94
README.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Semantic Versioning for Vishap Oberon Compiler (voc)
|
||||
|
||||
A strict, specification-compliant Semantic Versioning parser for [Vishap Oberon](https://vishap.oberon.am).
|
||||
Designed for use in package managers, version comparison tools, and system-level
|
||||
Oberon utilities.
|
||||
|
||||
## Features
|
||||
|
||||
- Parses full SemVer 2.0.0 strings (`MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]`)
|
||||
- Validates all pre-release and build metadata
|
||||
- Outputs parsed results into a structured Version record
|
||||
|
||||
## Example
|
||||
|
||||
Input:
|
||||
```
|
||||
1.1.2-prerelease+meta
|
||||
```
|
||||
|
||||
Result (parsed record):
|
||||
```
|
||||
major=1;
|
||||
minor=1;
|
||||
patch=2;
|
||||
preRelease=prerelease;
|
||||
build=meta;
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
MODULE SemVerExample;
|
||||
IMPORT SemVer, Out;
|
||||
VAR
|
||||
v: SemVer.Version;
|
||||
ok: BOOLEAN;
|
||||
BEGIN
|
||||
SemVer.Parse("1.2.3-alpha.1+build.5", v, ok);
|
||||
IF ok THEN
|
||||
Out.String("major="); Out.Int(v.Major, 1); Out.String("; ");
|
||||
Out.String("minor="); Out.Int(v.Minor, 1); Out.String("; ");
|
||||
Out.String("patch="); Out.Int(v.Patch, 1); Out.String("; ");
|
||||
Out.String("preRelease="); Out.String(v.PreRelease); Out.String("; ");
|
||||
Out.String("build="); Out.String(v.Build); Out.String(";");
|
||||
Out.Ln;
|
||||
ELSE
|
||||
Out.String("Invalid version string."); Out.Ln;
|
||||
END;
|
||||
END SemVerExample.
|
||||
```
|
||||
|
||||
```
|
||||
$ voc ../src/SemVer.Mod -s SemVerExample.Mod -m 2>/dev/null && ./SemVerExample
|
||||
../src/SemVer.Mod Compiling SemVer. New symbol file. 5302 chars.
|
||||
SemVerExample.Mod Compiling SemVerExample. Main program. 1204 chars.
|
||||
major=1; minor=2; patch=3; preRelease=alpha.1; build=build.5;
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
|
||||
Includes various valid and invalid test cases to confirm compliance with
|
||||
the SemVer 2.0.0 specification.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- For Building: `voc` (duh!)
|
||||
- Test runner: none
|
||||
|
||||
## TODO
|
||||
|
||||
- Compare
|
||||
- Equals
|
||||
- ToString
|
||||
- ParseConstraint
|
||||
- MatchConstraint
|
||||
- IsStable
|
||||
- IsPreRelease
|
||||
|
||||
## License
|
||||
|
||||
BSD 2-Clause License
|
||||
|
||||
168
src/SemVer.Mod
Normal file
168
src/SemVer.Mod
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
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.
|
||||
197
tests/SemVerTest.Mod
Normal file
197
tests/SemVerTest.Mod
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
MODULE SemVerTest;
|
||||
|
||||
IMPORT SemVer, Out;
|
||||
|
||||
TYPE
|
||||
TestCase = RECORD
|
||||
input: ARRAY 128 OF CHAR;
|
||||
valid: BOOLEAN;
|
||||
END;
|
||||
|
||||
VAR
|
||||
v: SemVer.Version;
|
||||
ok: BOOLEAN;
|
||||
i: INTEGER;
|
||||
tests: ARRAY 71 OF TestCase;
|
||||
|
||||
PROCEDURE PrintVersion(v: SemVer.Version);
|
||||
BEGIN
|
||||
Out.String("major="); Out.Int(v.Major, 1); Out.String("; ");
|
||||
Out.String("minor="); Out.Int(v.Minor, 1); Out.String("; ");
|
||||
Out.String("patch="); Out.Int(v.Patch, 1); Out.String("; ");
|
||||
|
||||
Out.String("preRelease=");
|
||||
Out.String(v.PreRelease); Out.String("; ");
|
||||
|
||||
Out.String("build=");
|
||||
Out.String(v.Build); Out.String("; ");
|
||||
Out.Ln;
|
||||
END PrintVersion;
|
||||
|
||||
PROCEDURE PrintBool(b: BOOLEAN);
|
||||
BEGIN
|
||||
IF b THEN Out.String("valid") ELSE Out.String("invalid") END
|
||||
END PrintBool;
|
||||
|
||||
BEGIN
|
||||
tests[0].input := "0.0.4";
|
||||
tests[0].valid := TRUE;
|
||||
tests[1].input := "1.2.3";
|
||||
tests[1].valid := TRUE;
|
||||
tests[2].input := "10.20.30";
|
||||
tests[2].valid := TRUE;
|
||||
tests[3].input := "1.1.2-prerelease+meta";
|
||||
tests[3].valid := TRUE;
|
||||
tests[4].input := "1.1.2+meta";
|
||||
tests[4].valid := TRUE;
|
||||
tests[5].input := "1.1.2+meta-valid";
|
||||
tests[5].valid := TRUE;
|
||||
tests[6].input := "1.0.0-alpha";
|
||||
tests[6].valid := TRUE;
|
||||
tests[7].input := "1.0.0-beta";
|
||||
tests[7].valid := TRUE;
|
||||
tests[8].input := "1.0.0-alpha.beta";
|
||||
tests[8].valid := TRUE;
|
||||
tests[9].input := "1.0.0-alpha.beta.1";
|
||||
tests[9].valid := TRUE;
|
||||
tests[10].input := "1.0.0-alpha.1";
|
||||
tests[10].valid := TRUE;
|
||||
tests[11].input := "1.0.0-alpha0.valid";
|
||||
tests[11].valid := TRUE;
|
||||
tests[12].input := "1.0.0-alpha.0valid";
|
||||
tests[12].valid := TRUE;
|
||||
tests[13].input := "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay";
|
||||
tests[13].valid := TRUE;
|
||||
tests[14].input := "1.0.0-rc.1+build.1";
|
||||
tests[14].valid := TRUE;
|
||||
tests[15].input := "2.0.0-rc.1+build.123";
|
||||
tests[15].valid := TRUE;
|
||||
tests[16].input := "1.2.3-beta";
|
||||
tests[16].valid := TRUE;
|
||||
tests[17].input := "10.2.3-DEV-SNAPSHOT";
|
||||
tests[17].valid := TRUE;
|
||||
tests[18].input := "1.2.3-SNAPSHOT-123";
|
||||
tests[18].valid := TRUE;
|
||||
tests[19].input := "1.0.0";
|
||||
tests[19].valid := TRUE;
|
||||
tests[20].input := "2.0.0";
|
||||
tests[20].valid := TRUE;
|
||||
tests[21].input := "1.1.7";
|
||||
tests[21].valid := TRUE;
|
||||
tests[22].input := "2.0.0+build.1848";
|
||||
tests[22].valid := TRUE;
|
||||
tests[23].input := "2.0.1-alpha.1227";
|
||||
tests[23].valid := TRUE;
|
||||
tests[24].input := "1.0.0-alpha+beta";
|
||||
tests[24].valid := TRUE;
|
||||
tests[25].input := "1.2.3----RC-SNAPSHOT.12.9.1--.12+788";
|
||||
tests[25].valid := TRUE;
|
||||
tests[26].input := "1.2.3----R-S.12.9.1--.12+meta";
|
||||
tests[26].valid := TRUE;
|
||||
tests[27].input := "1.2.3----RC-SNAPSHOT.12.9.1--.12";
|
||||
tests[27].valid := TRUE;
|
||||
tests[28].input := "1.0.0+0.build.1-rc.10000aaa-kk-0.1";
|
||||
tests[28].valid := TRUE;
|
||||
tests[29].input := "99999999999999999999999.999999999999999999.99999999999999999";
|
||||
tests[29].valid := TRUE;
|
||||
tests[30].input := "1.0.0-0A.is.legal";
|
||||
tests[30].valid := TRUE;
|
||||
|
||||
tests[31].input := "1";
|
||||
tests[31].valid := FALSE;
|
||||
tests[32].input := "1.2";
|
||||
tests[32].valid := FALSE;
|
||||
tests[33].input := "1.2.3-0123";
|
||||
tests[33].valid := FALSE;
|
||||
tests[34].input := "1.2.3-0123.0123";
|
||||
tests[34].valid := FALSE;
|
||||
tests[35].input := "1.1.2+.123";
|
||||
tests[35].valid := FALSE;
|
||||
tests[36].input := "+invalid";
|
||||
tests[36].valid := FALSE;
|
||||
tests[37].input := "-invalid";
|
||||
tests[37].valid := FALSE;
|
||||
tests[38].input := "-invalid+invalid";
|
||||
tests[38].valid := FALSE;
|
||||
tests[39].input := "-invalid.01";
|
||||
tests[39].valid := FALSE;
|
||||
tests[40].input := "alpha";
|
||||
tests[40].valid := FALSE;
|
||||
tests[41].input := "alpha.beta";
|
||||
tests[41].valid := FALSE;
|
||||
tests[42].input := "alpha.beta.1";
|
||||
tests[42].valid := FALSE;
|
||||
tests[43].input := "alpha.1";
|
||||
tests[43].valid := FALSE;
|
||||
tests[44].input := "alpha+beta";
|
||||
tests[44].valid := FALSE;
|
||||
tests[45].input := "alpha_beta";
|
||||
tests[45].valid := FALSE;
|
||||
tests[46].input := "alpha.";
|
||||
tests[46].valid := FALSE;
|
||||
tests[47].input := "alpha..";
|
||||
tests[47].valid := FALSE;
|
||||
tests[48].input := "beta";
|
||||
tests[48].valid := FALSE;
|
||||
tests[49].input := "1.0.0-alpha_beta";
|
||||
tests[49].valid := FALSE;
|
||||
tests[50].input := "-alpha.";
|
||||
tests[50].valid := FALSE;
|
||||
tests[51].input := "1.0.0-alpha..";
|
||||
tests[51].valid := FALSE;
|
||||
tests[52].input := "1.0.0-alpha..1";
|
||||
tests[52].valid := FALSE;
|
||||
tests[53].input := "1.0.0-alpha...1";
|
||||
tests[53].valid := FALSE;
|
||||
tests[54].input := "1.0.0-alpha....1";
|
||||
tests[54].valid := FALSE;
|
||||
tests[55].input := "1.0.0-alpha.....1";
|
||||
tests[55].valid := FALSE;
|
||||
tests[56].input := "1.0.0-alpha......1";
|
||||
tests[56].valid := FALSE;
|
||||
tests[57].input := "1.0.0-alpha.......1";
|
||||
tests[57].valid := FALSE;
|
||||
tests[58].input := "01.1.1";
|
||||
tests[58].valid := FALSE;
|
||||
tests[59].input := "1.01.1";
|
||||
tests[59].valid := FALSE;
|
||||
tests[60].input := "1.1.01";
|
||||
tests[60].valid := FALSE;
|
||||
tests[61].input := "1.2";
|
||||
tests[61].valid := FALSE;
|
||||
tests[62].input := "1.2.3.DEV";
|
||||
tests[62].valid := FALSE;
|
||||
tests[63].input := "1.2-SNAPSHOT";
|
||||
tests[63].valid := FALSE;
|
||||
tests[64].input := "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788";
|
||||
tests[64].valid := FALSE;
|
||||
tests[65].input := "1.2-RC-SNAPSHOT";
|
||||
tests[65].valid := FALSE;
|
||||
tests[66].input := "-1.0.3-gamma+b7718";
|
||||
tests[66].valid := FALSE;
|
||||
tests[67].input := "+justmeta";
|
||||
tests[67].valid := FALSE;
|
||||
tests[68].input := "9.8.7+meta+meta";
|
||||
tests[68].valid := FALSE;
|
||||
tests[69].input := "9.8.7-whatever+meta+meta";
|
||||
tests[69].valid := FALSE;
|
||||
tests[70].input := "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12";
|
||||
tests[70].valid := FALSE;
|
||||
|
||||
FOR i := 0 TO LEN(tests)-1 DO
|
||||
Out.String("Test "); Out.Int(i, 1); Out.String(": ");
|
||||
Out.String(tests[i].input);
|
||||
|
||||
SemVer.Parse(tests[i].input, v, ok);
|
||||
Out.String("; expected="); PrintBool(tests[i].valid);
|
||||
Out.String("; actual="); PrintBool(ok); Out.Ln;
|
||||
|
||||
IF ok # tests[i].valid THEN Out.String("Test failed!!!"); Out.Ln; RETURN END;
|
||||
|
||||
IF ok THEN
|
||||
PrintVersion(v);
|
||||
END;
|
||||
Out.Ln;
|
||||
END;
|
||||
Out.String("Test success!!!"); Out.Ln;
|
||||
END SemVerTest.
|
||||
Loading…
Add table
Add a link
Reference in a new issue