[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
- [PATCH v2 0/4] Implement version tools (was: add pk_vercmp), Arsen Arsenović, 2023/01/29
- [PATCH v2 2/4] std.pk: Refactor atoi into strtoi, Arsen Arsenović, 2023/01/29
- [PATCH v2 1/4] std.pk: Implement strrchr, Arsen Arsenović, 2023/01/29
- [PATCH v2 4/4] std.pk: Implement pk_version_parse, pk_vercmp,
Arsen Arsenović <=
- [PATCH v2 3/4] std.pk: Implement strtok, Arsen Arsenović, 2023/01/29
- Re: [PATCH v2 0/4] Implement version tools, Jose E. Marchesi, 2023/01/29