poke-devel
[Top][All Lists]
Advanced

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

[PATCH 4/4] std.pk: Implement pk_vercmp


From: Arsen Arsenović
Subject: [PATCH 4/4] std.pk: Implement pk_vercmp
Date: Sun, 29 Jan 2023 15:54:41 +0100

* doc/poke.texi (Other Functions): New node and section.
Documents miscellaneous functions.
(pk_vercmp): New node.  Documents pk_vercmp and the version string
format.
* libpoke/std.pk (pk_vercmp): New function.  Compares a pair of
GNU poke version strings.
(_pk_ver_strip): New function.  Strips incomparable bits of GNU
poke version strings.
* testsuite/poke.std/std-test.pk: Add pk_vercmp tests.
* autoconf/poke.m4 (PK_PROG_POKE): Update to use pk_vercmp.
---
 ChangeLog                      |  14 +++
 autoconf/poke.m4               |   2 +-
 doc/poke.texi                  |  79 ++++++++++++++++
 libpoke/std.pk                 | 159 +++++++++++++++++++++++++++++++++
 testsuite/poke.std/std-test.pk |  59 ++++++++++++
 5 files changed, 312 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index e0df6736..bc37f7c6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2023-01-29  Arsen Arsenović  <arsen@aarsen.me>
+
+       std.pk: Implement pk_vercmp
+       * doc/poke.texi (Other Functions): New node and section.
+       Documents miscellaneous functions.
+       (pk_vercmp): New node.  Documents pk_vercmp and the version string
+       format.
+       * libpoke/std.pk (pk_vercmp): New function.  Compares a pair of
+       GNU poke version strings.
+       (_pk_ver_strip): New function.  Strips incomparable bits of GNU
+       poke version strings.
+       * testsuite/poke.std/std-test.pk: Add 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..da0f6a0e 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, "$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 ff498220..40f55712 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,83 @@ 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
+* pk_vercmp::                   GNU poke version string comparison.
+@end menu
+
+@node pk_vercmp
+@subsection @code{pk_vercmp}
+@cindex version comparison
+Compares a pair of GNU poke strings.  Note that this function is
+@emph{not} generic to any version string, as it assumes and verifies
+the version strings generated by the GNU poke build system.
+
+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.
+
+@deftypefun int<32> pk_vercmp (string @var{a}, string @var{b})
+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.
+@end deftypefun
+
+@cindex version format
+The GNU poke version string that this function 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 patch 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}.
+
+@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.
+
+@item @var{XDIGITS}
+The hash this build was generated on.  For the purposes of version
+comparison, 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
+
+
 @c @node Hacking Poke
 @c @chapter Hacking Poke
 
diff --git a/libpoke/std.pk b/libpoke/std.pk
index 02776052..28d8c05c 100644
--- a/libpoke/std.pk
+++ b/libpoke/std.pk
@@ -650,3 +650,162 @@ fun strtok = (string a) Tokenizer:
 {
   return Tokenizer { str = a, i = 0 };
 }
+
+fun _pk_ver_strip = (string x) string:
+{
+  var dirty = "-dirty";
+  if (x'length >= dirty'length)
+    {
+      /* Strip the -dirty suffix, if any.  */
+      var i = x'length - dirty'length;
+      if (x[i:] == dirty)
+        x = x[:i];
+    }
+
+  /* With -dirty removed, we can be left with -g[[:xdigit:]]*.  This hash is an
+     opaque value, and so, can be ignored.  */
+  var dashi = strrchr (x, '-');
+  if (dashi == -1)
+    return x;
+  if (x[dashi + 1] != 'g')
+    return x;
+
+  for (var i = dashi + 2;
+       i < x'length;
+       i++)
+    {
+      if (!isxdigit (x[i]))
+        return x;
+    }
+
+  /* We're sure at this point that the suffix looks like a commit suffix.  */
+  return x[:dashi];
+}
+
+/* Compare a pair GNU poke version strings.  */
+
+fun pk_vercmp = (string a, string b) int<32>:
+{
+  a = _pk_ver_strip (a);
+  b = _pk_ver_strip (b);
+
+  var i = strtok (a);
+  var j = strtok (b);
+
+  /* Helper that pops an equal character from both tokenizers, and checks that
+     they're the desired character.  */
+  fun popassert = (uint<8> x) void:
+  {
+    try
+      {
+        if (i.peek () != j.peek () || j.peek () != x)
+          raise E_inval;
+        i.i++;
+        j.i++;
+      }
+    catch if E_out_of_bounds
+      {
+        raise E_inval;
+      }
+  };
+
+  /* Integer comparison helper.  */
+  fun cmp = (uint<64> a, uint<64> b) int<32>:
+  {
+    if (a > b)
+      return 1;
+    if (a < b)
+      return -1;
+    return 0;
+  }
+
+  /* Integer comparison helper, except it compounds difference.  This permits
+     us to continue validating the version string.  */
+  var cmpres = 0;
+  fun end = (uint<64> a, uint<64> b) int<32>:
+  {
+    if (cmpres == 0)
+      cmpres = cmp (a, b);
+    return cmpres;
+  }
+
+  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.  */
+      var x = i.pop_number ();
+      var y = j.pop_number ();
+
+      end (x, y);
+
+      /* Mandatory period.  */
+      popassert ('.');
+
+      /* Another numeric component.  */
+      x = i.pop_number ();
+      y = j.pop_number ();
+
+      end (x, y);
+    }
+  catch if E_out_of_bounds
+    {
+      /* If any of these two are missing, it's an invalid.  */
+      raise E_inval;
+    }
+  /* Following the two mandatory components, we might run into either a dot or
+     a dash.  Let's try parsing a third component out of both, defaulting to
+     zero.  */
+  var x = 0;
+  var y = 0;
+  if (i.more && i.peek () == '.')
+    {
+      i.pop ();
+      x = i.pop_number ();
+    }
+  if (j.more && j.peek () == '.')
+    {
+      j.pop ();
+      y = j.pop_number ();
+    }
+
+  end (x, y);
+
+  /* Here we break out into two variables, one for isdev and one for commit.
+     The component here is all or nothing - either branch and commit number,
+     or EOF.  */
+  var b1d = 0;
+  var b2d = 0;
+  x = 0;
+  y = 0;
+
+  try
+    {
+      if (i.more && i.pop () == '-')
+        {
+          b1d = i.poprdelim ("-") == "dev";
+          /* ``-'' is popped too, there should be a number.  */
+          x = i.pop_number ();
+        };
+      if (j.more && j.pop () == '-')
+        {
+          b2d = j.poprdelim ("-") == "dev";
+          /* ``-'' is popped too, there should be a number.  */
+          y = j.pop_number ();
+        };
+    }
+  catch if E_out_of_bounds
+    {
+      /* The pop_number operation failed.  */
+      raise E_inval;
+    }
+
+  end (b1d, b2d);
+
+  /* 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 || j.more)
+    raise E_inval;
+
+  return end (x, y);
+}
diff --git a/testsuite/poke.std/std-test.pk b/testsuite/poke.std/std-test.pk
index 5b8de358..3985d18f 100644
--- a/testsuite/poke.std/std-test.pk
+++ b/testsuite/poke.std/std-test.pk
@@ -424,6 +424,65 @@ var tests = [
           }
       },
   },
+  PkTest {
+    name = "pk_vercmp",
+    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 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_vercmp (inv, 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]