Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
platform:
- os: macos-13 # x64
- os: macos-15-intel # x64
id: x86_64-macos

- os: macos-latest # arm64
Expand All @@ -29,8 +29,8 @@ jobs:
id: x86_64-linux

# Unavailable until setup-alire/alr-install are updated to support alr 2.1
# - os: ubuntu-24.04-arm # new ARM runners
# id: aarch64-linux
- os: ubuntu-24.04-arm # new ARM runners
id: aarch64-linux

- os: windows-latest # x64
id: x86_64-windows
Expand Down
6 changes: 3 additions & 3 deletions grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ EVS_Nested ::= '(' EVS ')'
VS ::= V | OP V
V ::= <single version terminal>
list ::= list_and | list_or
list_and ::= '&' EVS_and
list_or ::= '|' EVS_or
list_and ::= ('&' | 'and') EVS_and
list_or ::= ('|' | 'or') EVS_or
EVS_and ::= (EVS_Nested | VS) [and_list]
EVS_or ::= (EVS_Nested | VS) [or_list]
OP ::= '<' | '>' etc <comparison operators>
NEG ::= '!'
NEG ::= '!' | 'not'
97 changes: 89 additions & 8 deletions src/semantic_versioning-extended.adb
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,20 @@ package body Semantic_Versioning.Extended is
end if;
end Match;

procedure Match (S : String) is
begin
if I > Str'Last then
Error ("Incomplete expression when expecting: " & S);
elsif I + S'Length - 1 > Str'Last then
Error ("Incomplete expression when expecting: " & S);
elsif ACH.To_Lower (Str (I .. I + S'Length - 1)) /= ACH.To_Lower (S) then
Error ("Got a '" & Str (I) & "' when expecting: " & S);
else
Trace ("Matching " & S & " at pos" & I'Img);
I := I + S'Length;
end if;
end Match;

-------------------
-- Next_Basic_VS --
-------------------
Expand All @@ -436,6 +450,28 @@ package body Semantic_Versioning.Extended is
end return;
end Next_Basic_VS;

----------------
-- Is_Keyword --
----------------

function Is_Keyword (Word : String) return Boolean is
Last : constant Integer := I + Word'Length - 1;
begin
if Last > Str'Last then
return False;
end if;

if ACH.To_Lower (Str (I .. Last)) /= Word then
return False;
end if;

if Last < Str'Last and then ACH.Is_Alphanumeric (Str (Last + 1)) then
return False;
end if;

return True;
end Is_Keyword;

----------------
-- Next_Token --
----------------
Expand All @@ -457,6 +493,14 @@ package body Semantic_Versioning.Extended is
return End_Of_Input;
end if;

if Is_Keyword ("and") then
return Ampersand;
elsif Is_Keyword ("or") then
return Pipe;
elsif Is_Keyword ("not") then
return Negation;
end if;

if Begins_With_Relational (Str (I .. Str'Last), Unicode) then
return VS;
end if;
Expand All @@ -479,6 +523,43 @@ package body Semantic_Versioning.Extended is
end return;
end Next_Token;

----------------
-- Match_Token --
----------------

procedure Match_Token (T : Tokens) is
begin
case T is
when Ampersand =>
if Is_Keyword ("and") then
Match ("and");
else
Match ('&');
end if;

when Pipe =>
if Is_Keyword ("or") then
Match ("or");
else
Match ('|');
end if;

when Negation =>
if Is_Keyword ("not") then
Match ("not");
else
Match ('!');
end if;

when Lparen =>
Match ('(');
when Rparen =>
Match (')');
when others =>
raise Program_Error;
end case;
end Match_Token;

function Prod_EVS_Nested return Version_Set;
function Prod_List (Head : Version_Set;
Kind : List_Kinds) return Version_Set;
Expand All @@ -496,7 +577,7 @@ package body Semantic_Versioning.Extended is
Trace ("Prod EVS");
case Next_Token is
when Negation =>
Match ('!');
Match_Token (Negation);
declare
Child : constant Version_Set := Prod_EVS (List_Kind, With_List => False);
begin
Expand Down Expand Up @@ -543,10 +624,10 @@ package body Semantic_Versioning.Extended is
function Prod_EVS_Nested return Version_Set is
begin
Trace ("Prod EVS Nested");
Match ('(');
Match_Token (Lparen);
return VS : Version_Set := Prod_EVS (Any, With_List => True) do
VS.Image := '(' & VS.Image & ')';
Match (')');
Match_Token (Rparen);
end return;
end Prod_EVS_Nested;

Expand All @@ -565,10 +646,10 @@ package body Semantic_Versioning.Extended is
procedure Check_Mismatch is
begin
if I <= Str'Last then
if (Kind = Anded and then Str (I) = '|') or else
(Kind = Ored and then Str (I) = '&')
if (Kind = Anded and then Next_Token = Pipe) or else
(Kind = Ored and then Next_Token = Ampersand)
then
Error ("Cannot mix '&' and '|' operators, use parentheses");
Error ("Cannot mix 'and' and 'or' operators, use parentheses");
end if;
end if;
end Check_Mismatch;
Expand All @@ -589,12 +670,12 @@ package body Semantic_Versioning.Extended is

when Anded =>
Check_Mismatch;
Match ('&');
Match_Token (Ampersand);
return New_Pair (Head, Prod_EVS (Anded, With_List => True), Anded);

when Ored =>
Check_Mismatch;
Match ('|');
Match_Token (Pipe);
return New_Pair (Head, Prod_EVS (Ored, With_List => True), Ored);
end case;
end Prod_List;
Expand Down
3 changes: 3 additions & 0 deletions tests/src/semver_tests-extended_expressions.adb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ begin
Assert (X.Parse ("((1&(2|3)))").Valid);
Assert (X.Value ("*").Image = X.Any.Image);
Assert (X.Value ("*").Image = X.Value ("any").Image);
Assert (X.Is_In (V ("1.1"), X.Value ("^1 and <2")));
Assert (X.Is_In (V ("2"), X.Value ("not =1 or =2"))); -- equivalent to "(!=1)|(=3)"
Assert (not X.Is_In (V ("1"), X.Value ("not =1 and =2"))); -- equivalent to "(!=1)&(=3)"
end Semver_Tests.Extended_Expressions;
45 changes: 45 additions & 0 deletions tests/src/semver_tests-precedences.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
procedure Semver_Tests.Precedences is
begin
Assert (X.Value ("not 1 and 2").Synthetic_Image = "!(=1.0.0)&=2.0.0");
Assert (X.Value ("not (1 and 2)").Synthetic_Image = "!(=1.0.0&=2.0.0)");
Assert (X.Value ("not 1 or 2").Synthetic_Image = "!(=1.0.0)|=2.0.0");
Assert (X.Value ("not (1 or 2)").Synthetic_Image = "!(=1.0.0|=2.0.0)");

-- Multiple and/or with not
Assert (X.Value ("not 1 and 2 and 3").Synthetic_Image =
"!(=1.0.0)&=2.0.0&=3.0.0");
Assert (X.Value ("not 1 or 2 or 3").Synthetic_Image =
"!(=1.0.0)|=2.0.0|=3.0.0");
Assert (X.Value ("not 1 and (2 or 3)").Synthetic_Image =
"!(=1.0.0)&(=2.0.0|=3.0.0)");
Assert (X.Value ("(not 1 and 2) or 3").Synthetic_Image =
"(!(=1.0.0)&=2.0.0)|=3.0.0");
Assert (X.Value ("not (1 and 2) or 3").Synthetic_Image =
"!(=1.0.0&=2.0.0)|=3.0.0");
Assert (X.Value ("not (1 or 2) and 3").Synthetic_Image =
"!(=1.0.0|=2.0.0)&=3.0.0");
Assert (X.Value ("not (1 or (2 and 3)) and (4 or 5)").Synthetic_Image =
"!(=1.0.0|(=2.0.0&=3.0.0))&(=4.0.0|=5.0.0)");
Assert (X.Value ("not 1 and 2 and (3 or not 4)").Synthetic_Image =
"!(=1.0.0)&=2.0.0&(=3.0.0|!(=4.0.0))");

-- Mixing and/or with/without parentheses
Assert (not X.Parse ("1 and 2 or 3").Valid);
Assert (not X.Parse ("1 or 2 and 3").Valid);
Assert (not X.Parse ("1 & 2 or 3").Valid);
Assert (not X.Parse ("1 | 2 and 3").Valid);
Assert (not X.Parse ("1 and 2 | 3").Valid);
Assert (not X.Parse ("1 or 2 & 3").Valid);
Assert (X.Value ("(1 and 2) | (3 and 4)").Synthetic_Image =
"(=1.0.0&=2.0.0)|(=3.0.0&=4.0.0)");
Assert (X.Value ("(1 or 2) & (3 or 4)").Synthetic_Image =
"(=1.0.0|=2.0.0)&(=3.0.0|=4.0.0)");
Assert (X.Value ("(1 & 2) or (3 & 4)").Synthetic_Image =
"(=1.0.0&=2.0.0)|(=3.0.0&=4.0.0)");
Assert (X.Value ("(1 | 2) and (3 | 4)").Synthetic_Image =
"(=1.0.0|=2.0.0)&(=3.0.0|=4.0.0)");

-- Some more
Assert (X.Parse ("1 and not 2").Valid);
Assert (X.Value ("1 and not 2").Synthetic_Image = "=1.0.0&!(=2.0.0)");
end Semver_Tests.Precedences;
18 changes: 18 additions & 0 deletions tests/src/semver_tests-word_operators.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
procedure Semver_Tests.Word_Operators is
begin
Assert (X.Parse ("1 and 2").Valid);
Assert (X.Parse ("1 or 2").Valid);
Assert (X.Parse ("not 1").Valid);
Assert (X.Parse ("NoT 1").Valid);
Assert (X.Parse ("1 and (2 or 3)").Valid);
Assert (X.Value ("not 1 and 2").Synthetic_Image = "!(=1.0.0)&=2.0.0");
Assert (X.Value ("not (1 or 2)").Synthetic_Image = "!(=1.0.0|=2.0.0)");

Assert (not X.Parse ("and 1").Valid);
Assert (not X.Parse ("1 and").Valid);
Assert (not X.Parse ("1 or").Valid);
Assert (not X.Parse ("not").Valid);
Assert (not X.Parse ("1 or not").Valid);
Assert (not X.Parse ("1 and or 2").Valid);
Assert (not X.Parse ("1 and 2 or 3").Valid);
end Semver_Tests.Word_Operators;