poke-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v2 4/4] std.pk: Implement pk_version_parse, pk_vercmp


From: Arsen Arsenović
Subject: [PATCH v2 4/4] std.pk: Implement pk_version_parse, pk_vercmp
Date: Sun, 29 Jan 2023 21:04:38 +0100

* doc/poke.texi (Other Functions): New node and section.
Documents miscellaneous functions.
(Concept Index): Merge the variable index into the concept index.
(Version Tools): New node.  Documents pk_version,
pk_version_parse, Pk_Version, pk_vercmp and the version string
format.
* libpoke/std.pk (pk_vercmp): New function.  Compares a pair of
Pk_Version structs.
(pk_version_parse): New function.  Parses a version string into a
new Pk_Version struct.
* testsuite/poke.std/std-test.pk: Add pk_version_parse, pk_vercmp
tests.
* autoconf/poke.m4 (PK_PROG_POKE): Update to use pk_vercmp.
---
 ChangeLog                      |  17 ++++
 autoconf/poke.m4               |   2 +-
 doc/poke.texi                  | 121 ++++++++++++++++++++++++-
 libpoke/std.pk                 | 155 +++++++++++++++++++++++++++++++++
 testsuite/poke.std/std-test.pk |  61 +++++++++++++
 5 files changed, 354 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ab6c47f0..7ce1d439 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2023-01-29  Arsen Arsenović  <arsen@aarsen.me>
+
+       std.pk: Implement pk_version_parse, pk_vercmp
+       * doc/poke.texi (Other Functions): New node and section.
+       Documents miscellaneous functions.
+       (Concept Index): Merge the variable index into the concept index.
+       (Version Tools): New node.  Documents pk_version,
+       pk_version_parse, Pk_Version, pk_vercmp and the version string
+       format.
+       * libpoke/std.pk (pk_vercmp): New function.  Compares a pair of
+       Pk_Version structs.
+       (pk_version_parse): New function.  Parses a version string into a
+       new Pk_Version struct.
+       * testsuite/poke.std/std-test.pk: Add pk_version_parse, pk_vercmp
+       tests.
+       * autoconf/poke.m4 (PK_PROG_POKE): Update to use pk_vercmp.
+
 2023-01-29  Arsen Arsenović  <arsen@aarsen.me>
 
        std.pk: Implement strtok
diff --git a/autoconf/poke.m4 b/autoconf/poke.m4
index 459efe78..dd491cfc 100644
--- a/autoconf/poke.m4
+++ b/autoconf/poke.m4
@@ -34,7 +34,7 @@ _ACEOF
     fi
     if test "x$ac_poke_has_pk_version" = "xyes"; then
       cat >conftest.pk <<_ACEOF
-exit (pk_version >= "$2" ? 0 : 1);
+exit ((pk_vercmp (pk_version_parse (pk_version), pk_version_parse ("$2")) >= 
0) ? 0 : 1);
 _ACEOF
       ac_prog_version=`$$1 --version 2>&1 | sed -n 's/^.*GNU poke.* 
\(.*$\)/\1/p'`
       if $$1 -L conftest.pk 2>&1 >/dev/null; then
diff --git a/doc/poke.texi b/doc/poke.texi
index 2ae005e2..cf82288c 100644
--- a/doc/poke.texi
+++ b/doc/poke.texi
@@ -259,6 +259,7 @@ The Standard Library
 * CRC Functions::               Cyclic Redundancy Checksums.
 * Dates and Times::             Processing and displaying dates and times.
 * Offset Functions::            Useful functions that operate on offsets.
+* Other Functions::             Miscellany.
 
 @c Hacking poke
 @c * Writing Commands::                Extending poke with new commands.
@@ -15416,6 +15417,7 @@ facilities provided by the library.
 * CRC Functions::               Cyclic Redundancy Checksums.
 * Dates and Times::             Processing and displaying dates and times.
 * Offset Functions::            Useful functions that operate on offsets.
+* Other Functions::             Miscellany.
 @end menu
 
 @node Standard Integral Types
@@ -16178,6 +16180,122 @@ fun alignto = (uoff64 offset, uoff64 to) uoff64:
 It returns an offset that is the result of aligning the given
 @var{offset} to the given alignment @var{to}.
 
+@node Other Functions
+@section Other Standard Library Functions
+
+The Poke standard library also provides the following functions that
+do not fit into the previous few categories:
+
+@menu
+* Version Tools::              GNU poke version string utilities.
+@end menu
+
+@node Version Tools
+@subsection GNU poke Version String Parsing Utilities
+@cindex version comparison
+@cindex version parsing
+The Poke standard library provides utilities for parsing and comparing
+GNU poke versions, to allow you to implement conditional code paths or
+to check prerequisites.
+
+@deftypevar string pk_version
+Version string of the current instance of GNU poke.
+@end deftypevar
+
+Note that these functions are @emph{not} generic to any version
+string, as they assume and verify the version strings generated by the
+GNU poke build system.
+
+@cindex version format
+The GNU poke version string that this family of functions accepts fit
+the following template:
+@samp{@var{X}.@var{Y}[.@var{Z}][-@var{BRANCH}-@var{NN}][-g@var{XDIGITS}][-dirty]}.
+Individually, these components are:
+
+@table @samp
+@item @var{X}
+The major component of this GNU poke version.  Mandatory.
+
+@item @var{Y}
+The minor component of this GNU poke version.  Mandatory.
+
+@item @var{Z}
+The subminor component of this GNU poke version.  Optional.
+
+@item @var{BRANCH}
+A free-form string signifying the current branch a given GNU poke
+build comes from.  Optional.  It has two special values:
+
+@table @samp
+@item maint
+For maintenance branches.
+@item dev
+For the @code{master} development branch.
+@end table
+
+All values other than @samp{dev} are considered equal and lower than
+@samp{dev} by the comparison helper described below..
+
+@item @var{NN}
+The number commits on @samp{@var{BRANCH}} since the
+@samp{@var{X}.@var{Y}[.@var{Z}]} tag.  Present if and only if
+@samp{@var{BRANCH}} is present.  This component is also called the
+offset.
+
+@item @var{XDIGITS}
+The abbreviated commit hash this build was generated on.  For the
+purposes of these functions, it is optional, however, in practice, it
+can only appear when @samp{@var{BRANCH}} is also present.  Due to the
+@samp{-g} prefix, this component also makes a poke version string
+appropriate for Git operations, for example: @code{git show
+3.0-dev-22-g2e092}.
+
+@item -dirty
+This suffix is added if the Git tree this GNU poke version was built
+from is dirty.
+@end table
+
+The standard library provides the following structured representation
+of GNU poke versions:
+
+@cindex @code{Pk_Version}
+@example
+type Pk_Version =
+  struct
+  @{
+    uint<8> major;
+    uint<8> minor;
+    uint<8> subminor;
+    string branch;
+    uint<32> offset;
+    string commit;
+    uint<8> dirty;
+  @};
+@end example
+
+@deftypefun Pk_Version pk_version_parse (string @var{version})
+Parses the string @var{version} into a new instance of
+@code{Pk_Version}, or raises @code{E_inval} if the string is invalid.
+@end deftypefun
+
+The standard library also provides the following helper function:
+
+@deftypefun int<32> pk_vercmp (Pk_Version @var{a}, Pk_Version @var{a})
+Returns a number greater than, equal to, or lower than zero if @var{a}
+compares greater than, equal to or lower than @var{b}, respectively.
+
+This function implements partial ordering, as it's not possible to
+distinguish some version numbers as greater or lower than each other,
+as some could be seen as ``lateral'' to eachother.  For instance,
+consider the strings @samp{3.0-a-11-gabcdef} and
+@samp{3.0-b-11-gabcdef}.  They are both 11 commits ahead of the 3.0
+release, but they aren't on the same lineage, and so, they can't be
+considered as greater or lower than eachother.  In cases like these,
+@code{pk_vercmp} will attempt to compare commit numbers as a crude
+measure.
+@end deftypefun
+
+
 @c @node Hacking Poke
 @c @chapter Hacking Poke
 
@@ -16355,8 +16473,9 @@ It returns an offset that is the result of aligning the 
given
 @node Concept Index
 @appendixsec Concept Index
 
-@c Merge the findex into the cpindex.
+@c Merge the findex and vindex into the cpindex.
 @syncodeindex fn cp
+@syncodeindex vr cp
 
 @printindex cp
 
diff --git a/libpoke/std.pk b/libpoke/std.pk
index 45b27f90..956dede5 100644
--- a/libpoke/std.pk
+++ b/libpoke/std.pk
@@ -650,3 +650,158 @@ fun strtok = (string a) String_Tokenizer:
 {
   return String_Tokenizer { str = a, i = 0 };
 }
+
+/* Structured representation of GNU poke version strings.  */
+
+type Pk_Version =
+  struct
+  {
+    uint<8> major;
+    uint<8> minor;
+    uint<8> subminor;
+    string branch;
+    uint<32> offset;
+    string commit;
+    uint<8> dirty;
+  };
+
+/* Parse the string A into a new Pk_Version object.  */
+
+fun pk_version_parse = (string a) Pk_Version:
+{
+  var res = Pk_Version {};
+
+  /* Strip the -dirty suffix, if any.  */
+  var dirty = "-dirty";
+  if (a'length >= dirty'length)
+    {
+      var i = a'length - dirty'length;
+      if (a[i:] == dirty)
+        {
+          a = a[:i];
+          res.dirty = 1;
+        };
+    }
+
+  /* With -dirty removed, we can be left with -g[[:xdigit:]]*.  We expect at
+     least a couple letters after a -g, so lets go with a safe three (+ 1 for
+     the ``g'').  Note that git-version-gen sets --abbrev=4 right now.  */
+  var dashi = strrchr (a, '-');
+  if (dashi != -1 && dashi + 4 < a'length && a[dashi + 1] == 'g')
+    {
+      var has_hash = 1;
+      for (var i = dashi + 2;
+           i < a'length;
+           i++)
+        {
+          if (isxdigit (a[i]))
+            continue;
+          has_hash = 0;
+          break;
+        };
+      res.commit = a[dashi + 2:];
+      if (has_hash)
+        a = a[:dashi];
+    };
+
+  var i = strtok (a);
+
+  if (!i.more)
+    raise E_inval;
+
+  /* Helper that pops an equal character from the tokenizer, and checks that
+     it's the desired character.  */
+  fun popassert = (uint<8> x) void:
+  {
+    try
+      {
+        if (i.peek () != x)
+          raise E_inval;
+        i.i++;
+      }
+    catch if E_out_of_bounds
+      {
+        raise E_inval;
+      }
+  };
+
+  try
+    {
+      /* Parsing over x.y[.z][-STR-n].  We expect a number, period, and number,
+         possibly followed by two one more component, a branch identifier and 
the
+         number of commits on said branch.  */
+      res.major = i.pop_number ();
+      /* Mandatory period.  */
+      popassert ('.');
+      res.minor = i.pop_number();
+    }
+  catch if E_out_of_bounds
+    {
+      /* If any of these two are missing, it's an invalid.  */
+      raise E_inval;
+    };
+
+  if (!i.more)
+    return res;
+
+  if (i.peek () == '.')
+    {
+      i.i++;
+      res.subminor = i.pop_number();
+    };
+
+  if (!i.more)
+    return res;
+
+  try
+    {
+      popassert ('-');
+      res.branch = i.poprdelim ("-");
+      /* The above should've popped the dash too.  */
+      res.offset = i.pop_number ();
+    }
+  catch if E_out_of_bounds
+    {
+      /* If any of these two are missing, it's an invalid.  */
+      raise E_inval;
+    };
+
+  /* We shouldn't have trailing data at this point, beyond the -gXDIGIT string,
+     or the -dirty string, but both should be stripped already.  */
+  if (i.more)
+    raise E_inval;
+
+  return res;
+};
+
+/* Compare a pair GNU poke version strings.  */
+
+fun pk_vercmp = (Pk_Version a, Pk_Version b) int<32>:
+{
+  fun cmp = (uint<32> a, uint<32> b) int<32>:
+  {
+    if (a > b)
+      return 1;
+    if (a < b)
+      return -1;
+    return 0;
+  };
+
+  var diff = cmp (a.major, b.major);
+  if (diff != 0)
+    return diff;
+  diff = cmp (a.minor, b.minor);
+  if (diff != 0)
+    return diff;
+  diff = cmp (a.subminor, b.subminor);
+  if (diff != 0)
+    return diff;
+  diff = cmp (a.subminor, b.subminor);
+  if (diff != 0)
+    return diff;
+  diff = cmp (a.branch == "dev" ? 1 : 0, b.branch == "dev" ? 1 : 0);
+  if (diff != 0)
+    return diff;
+  diff = cmp (a.offset, b.offset);
+  return diff;
+}
diff --git a/testsuite/poke.std/std-test.pk b/testsuite/poke.std/std-test.pk
index 5b8de358..5eeab351 100644
--- a/testsuite/poke.std/std-test.pk
+++ b/testsuite/poke.std/std-test.pk
@@ -424,6 +424,67 @@ var tests = [
           }
       },
   },
+  PkTest {
+    name = "version tools",
+    func = lambda (string name) void:
+      {
+        /* Normalizes a number to the [-1,1] integer range.  */
+        fun sign = (int<32> x) int<32>:
+        {
+          if (x == 0)
+            return x;
+
+          var y = x;
+          if (y < 0)
+            y = -y;
+          return x / y;
+        };
+        /* Helper to run a comparison in both directions.  */
+        fun cmptst = (string _a, string _b, int<32> expected) void:
+        {
+          var a = pk_version_parse (_a);
+          var b = pk_version_parse (_b);
+          var forward = sign (pk_vercmp (a, b));
+          var backward = sign (pk_vercmp (b, a));
+          assert (forward == expected, _a + " <=> " + _b + " was wrong");
+          assert (forward == -backward,
+                  _a + " <=> " + _b + " isn't antisymmetric");
+        };
+
+        cmptst ("3.0", "3.0", 0);
+        cmptst ("3.21", "3.3", 1);
+        cmptst ("3.21.0", "3.3.0", 1);
+
+        /* Ensure that -dirty / -gXDIGIT make no difference.  */
+        cmptst ("3.0", "3.0-dirty", 0);
+        cmptst ("3.0-g12345", "3.0-dirty", 0);
+        cmptst ("3.0-g12345-dirty", "3.0", 0);
+        cmptst ("3.0", "2.0-dirty", 1);
+        cmptst ("3.0-g12345", "2.0-dirty", 1);
+        cmptst ("3.0-g12345-dirty", "2.0", 1);
+
+        /* Ensure "dev" handling works.  */
+        cmptst ("3.0-dev-12", "3.0-nondev-12", 1);
+        cmptst ("3.0-dev-12", "3.0-dev-13", -1);
+        cmptst ("3.0-dev-12", "3.0-dev-22-g2e092-dirty", -1);
+        cmptst ("3.0-dev-12", "3.0-arsen/do-gettext-stuff-13", 1);
+        cmptst ("3.0-a/-b/c-12", "3.0-a/-b/c-13", -1);
+
+        /* Ensure invalid versions are discarded.  */
+        for (inv in ["", "abc", "2", "2.0.0.0", "3.0-dev", "-dirty", "3.0foo"])
+          {
+            try
+              {
+                pk_version_parse (inv);
+                assert (0, inv + " should not be valid!");
+              }
+            catch if E_inval
+              {
+                assert (1, "expects exception");
+              }
+          }
+      },
+  },
 ];
 
 exit (pktest_run (tests) ? 0 : 1);
-- 
2.39.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]