foundation-module: Chapter 7: Nowebify.
This commit is contained in:
parent
4eee2a66d4
commit
a15028316e
2 changed files with 82 additions and 79 deletions
|
@ -2,25 +2,26 @@
|
|||
|
||||
Ranges of acceptable version numbers.
|
||||
|
||||
@h Ranges.
|
||||
@ \section{Ranges.}
|
||||
We often want to check if a semver lies in a given precedence range, which we
|
||||
store as an "interval" in the mathematical sense. For example, the range |[2,6)|
|
||||
store as an "interval" in the mathematical sense. For example, the range [[[2,6)]]
|
||||
means all versions from 2.0.0 (inclusve) up to, but not equal to, 6.0.0. The
|
||||
lower end is called "closed" because it includes the end-value 2.0.0, and the
|
||||
upper end "open" because it does not. An infinite end means that there
|
||||
os no restriction in that direction; an empty end means that, in fact, the
|
||||
interval is the empty set, that is, that no version number can ever satisfy it.
|
||||
|
||||
The |end_value| element is meaningful only for |CLOSED_RANGE_END| and |OPEN_RANGE_END|
|
||||
ends. If one end is marked |EMPTY_RANGE_END|, so must the other be: it makes
|
||||
The [[end_value]] element is meaningful only for [[CLOSED_RANGE_END]] and [[OPEN_RANGE_END]]
|
||||
ends. If one end is marked [[EMPTY_RANGE_END]], so must the other be: it makes
|
||||
no sense for an interval to be empty seen from one end but not the other.
|
||||
|
||||
@e CLOSED_RANGE_END from 1
|
||||
@e OPEN_RANGE_END
|
||||
@e INFINITE_RANGE_END
|
||||
@e EMPTY_RANGE_END
|
||||
<<*>>=
|
||||
enum CLOSED_RANGE_END from 1
|
||||
enum OPEN_RANGE_END
|
||||
enum INFINITE_RANGE_END
|
||||
enum EMPTY_RANGE_END
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct range_end {
|
||||
int end_type;
|
||||
struct semantic_version_number end_value;
|
||||
|
@ -32,10 +33,10 @@ typedef struct semver_range {
|
|||
CLASS_DEFINITION
|
||||
} semver_range;
|
||||
|
||||
@ As hinted above, the notation |[| and |]| is used for closed ends, and |(|
|
||||
and |)| for open ones.
|
||||
@ As hinted above, the notation [[[]] and [[]]] is used for closed ends, and [[(]]
|
||||
and [[)]] for open ones.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void VersionNumberRanges::write_range(OUTPUT_STREAM, semver_range *R) {
|
||||
if (R == NULL) internal_error("no range");
|
||||
switch(R->lower.end_type) {
|
||||
|
@ -54,7 +55,7 @@ void VersionNumberRanges::write_range(OUTPUT_STREAM, semver_range *R) {
|
|||
@ The "allow anything" range runs from minus to plus infinity. Every version
|
||||
number lies in this range.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
semver_range *VersionNumberRanges::any_range(void) {
|
||||
semver_range *R = CREATE(semver_range);
|
||||
R->lower.end_type = INFINITE_RANGE_END;
|
||||
|
@ -72,18 +73,18 @@ int VersionNumberRanges::is_any_range(semver_range *R) {
|
|||
}
|
||||
|
||||
@ The "compatibility" range for a given version lies at the heart of semver:
|
||||
to be compatible with version |V|, version |W| must be of equal or greater
|
||||
to be compatible with version [[V]], version [[W]] must be of equal or greater
|
||||
precedence, and must have the same major version number. For example,
|
||||
for |2.1.7| the range will be |[2.1.7, 3-A)|, all versions at least 2.1.7 but
|
||||
for [[2.1.7]] the range will be [[[2.1.7, 3-A)]], all versions at least 2.1.7 but
|
||||
not as high as 3.0.0-A.
|
||||
|
||||
Note that |3.0.0-A| is the least precendent version allowed by semver with
|
||||
major version 3. The |-| gives it lower precedence than all release versions of
|
||||
3.0.0; the fact that upper case |A| is alphabetically the earliest non-empty
|
||||
Note that [[3.0.0-A]] is the least precendent version allowed by semver with
|
||||
major version 3. The [[-]] gives it lower precedence than all release versions of
|
||||
3.0.0; the fact that upper case [[A]] is alphabetically the earliest non-empty
|
||||
alphanumeric string gives it lower precendence than all other prerelease
|
||||
versions.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
semver_range *VersionNumberRanges::compatibility_range(semantic_version_number V) {
|
||||
semver_range *R = VersionNumberRanges::any_range();
|
||||
if (VersionNumbers::is_null(V) == FALSE) {
|
||||
|
@ -102,7 +103,7 @@ semver_range *VersionNumberRanges::compatibility_range(semantic_version_number V
|
|||
@ More straightforwardly, these ranges are for anything from V, or up to V,
|
||||
inclusive:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
semver_range *VersionNumberRanges::at_least_range(semantic_version_number V) {
|
||||
semver_range *R = VersionNumberRanges::any_range();
|
||||
R->lower.end_type = CLOSED_RANGE_END;
|
||||
|
@ -119,7 +120,7 @@ semver_range *VersionNumberRanges::at_most_range(semantic_version_number V) {
|
|||
|
||||
@ Here we test whether V is at least a given end, and then at most:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumberRanges::version_ge_end(semantic_version_number V, range_end E) {
|
||||
switch (E.end_type) {
|
||||
case CLOSED_RANGE_END:
|
||||
|
@ -154,7 +155,7 @@ int VersionNumberRanges::version_le_end(semantic_version_number V, range_end E)
|
|||
|
||||
@ This allows a simple way to write:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumberRanges::in_range(semantic_version_number V, semver_range *R) {
|
||||
if (R == NULL) return TRUE;
|
||||
if ((VersionNumberRanges::version_ge_end(V, R->lower)) &&
|
||||
|
@ -163,18 +164,18 @@ int VersionNumberRanges::in_range(semantic_version_number V, semver_range *R) {
|
|||
}
|
||||
|
||||
@ The following decides which end restriction is stricter: it returns 1
|
||||
of |E1| is, -1 if |E2| is, and 0 if they are equally onerous.
|
||||
of [[E1]] is, -1 if [[E2]] is, and 0 if they are equally onerous.
|
||||
|
||||
The empty set is as strict as it gets: nothing qualifies.
|
||||
|
||||
Similarly, infinite ends are as relaxed as can be: everything qualifies.
|
||||
|
||||
And otherwise, we need to know which end we're looking at in order to decide:
|
||||
a lower end of |[4, ...]| is stricter than a lower end of |[3, ...]|, but an
|
||||
upper end of |[..., 4]| is not as strict as an upper end of |[..., 3]|. Where
|
||||
a lower end of [[[4, ...]]] is stricter than a lower end of [[[3, ...]]], but an
|
||||
upper end of [[[..., 4]]] is not as strict as an upper end of [[[..., 3]]]. Where
|
||||
the boundary value is the same, open ends are stricter than closed ends.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumberRanges::stricter(range_end E1, range_end E2, int lower) {
|
||||
if ((E1.end_type == EMPTY_RANGE_END) && (E2.end_type == EMPTY_RANGE_END)) return 0;
|
||||
if (E1.end_type == EMPTY_RANGE_END) return 1;
|
||||
|
@ -192,13 +193,13 @@ int VersionNumberRanges::stricter(range_end E1, range_end E2, int lower) {
|
|||
}
|
||||
|
||||
@ And so we finally arrive at the following, which intersects two ranges:
|
||||
that is, it changes |R1| to the range of versions which lie inside both the
|
||||
original |R1| and also |R2|. (This is used by Inbuild when an extension is
|
||||
that is, it changes [[R1]] to the range of versions which lie inside both the
|
||||
original [[R1]] and also [[R2]]. (This is used by Inbuild when an extension is
|
||||
included in two different places in the source text, but with possibly
|
||||
different version needs.) The return value is true if an actual change took
|
||||
place, and false otherwise.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumberRanges::intersect_range(semver_range *R1, semver_range *R2) {
|
||||
int lc = VersionNumberRanges::stricter(R1->lower, R2->lower, TRUE);
|
||||
int uc = VersionNumberRanges::stricter(R1->upper, R2->upper, FALSE);
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
Semantic version numbers such as 3.7.1.
|
||||
|
||||
@h Standard adoption.
|
||||
@ \section{Standard adoption.}
|
||||
The Semantic Version Number standard, semver 2.0.0, provides a strict set
|
||||
of rules for the format and meaning of version numbers: see |https://semver.org|.
|
||||
of rules for the format and meaning of version numbers: see [[https://semver.org]].
|
||||
|
||||
Prior to the standard most version numbers in computing usage looked like
|
||||
dot-divided runs of non-negative integers: for example, 4, 7.1, and 0.2.3.
|
||||
|
@ -13,53 +13,54 @@ therefore formally incorrect to have a version 2, or a version 2.3. We will
|
|||
not be so strict on the textual form, which we will allow to be abbreviated.
|
||||
Thus:
|
||||
|
||||
(a) The text |6.4.7| is understood to mean 6.4.7 and printed back as |6.4.7|
|
||||
(b) The text |6| is understood to mean 6.0.0 and printed back as |6|
|
||||
(c) The text |6.1| is understood to mean 6.1.0 and printed back as |6.1|
|
||||
(d) The text |6.1.0| is understood to mean 6.1.0 and printed back as |6.1.0|
|
||||
(a) The text [[6.4.7]] is understood to mean 6.4.7 and printed back as [[6.4.7]]
|
||||
(b) The text [[6]] is understood to mean 6.0.0 and printed back as [[6]]
|
||||
(c) The text [[6.1]] is understood to mean 6.1.0 and printed back as [[6.1]]
|
||||
(d) The text [[6.1.0]] is understood to mean 6.1.0 and printed back as [[6.1.0]]
|
||||
|
||||
Similarly, the absence of a version number (called "null" below) will be
|
||||
understood to mean 0.0.0, but will be distinguished from the explicit choice
|
||||
to number something as 0.0.0.
|
||||
|
||||
@ A complication is that Inform 7 extensions have for many years allowed two
|
||||
forms of version number: either just |N|, which fits the scheme above, or
|
||||
|N/DDDDDD|, which does not. This is a format which was chosen for sentimental
|
||||
forms of version number: either just [[N]], which fits the scheme above, or
|
||||
[[N/DDDDDD]], which does not. This is a format which was chosen for sentimental
|
||||
reasons: IF enthusiasts know it well from the banner text of the Infocom
|
||||
titles of the 1980s. This story file, for instance, was compiled at the
|
||||
time of the Reykjavik summit between Presidents Gorbachev and Reagan:
|
||||
= (text)
|
||||
|
||||
Moonmist
|
||||
Infocom interactive fiction - a mystery story
|
||||
Copyright (c) 1986 by Infocom, Inc. All rights reserved.
|
||||
Moonmist is a trademark of Infocom, Inc.
|
||||
Release number 9 / Serial number 861022
|
||||
=
|
||||
Story file collectors customarily abbreviate this in catalogues to |9/861022|.
|
||||
|
||||
So we will allow this notation, but will convert |N/DDDDDD| to |N.0.DDDDDD|,
|
||||
Story file collectors customarily abbreviate this in catalogues to [[9/861022]].
|
||||
|
||||
So we will allow this notation, but will convert [[N/DDDDDD| to |N.0.DDDDDD]],
|
||||
pushing the six date digits into the minor patch. (See the discussion of the
|
||||
bug report I7-2130, where users curating old extensions recommended this as
|
||||
easier to deal with than |N.DDDDDD|.) So, then, |9/861022| means 9.0.861022 in
|
||||
easier to deal with than [[N.DDDDDD]].) So, then, [[9/861022]] means 9.0.861022 in
|
||||
semver precedence order.
|
||||
|
||||
In all non-textual respects, and in particular on precedence rules, we follow
|
||||
the standard exactly.
|
||||
|
||||
@ In the array below, unspecified numbers are stored as |-1|. The three
|
||||
@ In the array below, unspecified numbers are stored as [[-1]]. The three
|
||||
components are otherwise required to be non-negative integers.
|
||||
|
||||
Semver allows for more elaborate forms: for example |3.1.41-alpha.72.zeta+6Q45|
|
||||
would mean 3.1.41 but with prerelease versioning |alpha.72.zeta| and build
|
||||
metadata |6Q45|. The |prerelease_segments| list for this would be a list of
|
||||
three texts: |alpha|, |72|, |zeta|.
|
||||
Semver allows for more elaborate forms: for example [[3.1.41-alpha.72.zeta+6Q45]]
|
||||
would mean 3.1.41 but with prerelease versioning [[alpha.72.zeta]] and build
|
||||
metadata [[6Q45]]. The [[prerelease_segments]] list for this would be a list of
|
||||
three texts: [[alpha]], [[72]], [[zeta]].
|
||||
|
||||
@d SEMVER_NUMBER_DEPTH 3 /* major, minor, patch */
|
||||
<<*>>=
|
||||
#define SEMVER_NUMBER_DEPTH 3 /* major, minor, patch */
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct semantic_version_number {
|
||||
int version_numbers[SEMVER_NUMBER_DEPTH];
|
||||
struct linked_list *prerelease_segments; /* of |text_stream| */
|
||||
struct linked_list *prerelease_segments; /* of [[text_stream]] */
|
||||
struct text_stream *build_metadata;
|
||||
} semantic_version_number;
|
||||
|
||||
|
@ -71,9 +72,9 @@ typedef struct semantic_version_number_holder {
|
|||
@ All invalid strings of numbers -- i.e., breaking the above rules -- are
|
||||
called "null" versions, and can never be valid as the version of anything.
|
||||
Instead they are used to represent the absence of a version number.
|
||||
(In particular, a string of |-1|s is null.)
|
||||
(In particular, a string of [[-1]]s is null.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
semantic_version_number VersionNumbers::null(void) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
|
||||
|
@ -95,10 +96,10 @@ int VersionNumbers::is_null(semantic_version_number V) {
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
@h Printing and parsing.
|
||||
@ \section{Printing and parsing.}
|
||||
Printing is simple enough:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void VersionNumbers::to_text(OUTPUT_STREAM, semantic_version_number V) {
|
||||
if (VersionNumbers::is_null(V)) { WRITE("null"); return; }
|
||||
for (int i=0; (i<SEMVER_NUMBER_DEPTH) && (V.version_numbers[i] >= 0); i++) {
|
||||
|
@ -116,30 +117,31 @@ void VersionNumbers::to_text(OUTPUT_STREAM, semantic_version_number V) {
|
|||
if (V.build_metadata) WRITE("+%S", V.build_metadata);
|
||||
}
|
||||
|
||||
@ And this provides for the |%v| escape, though we must be careful when
|
||||
@ And this provides for the [[%v]] escape, though we must be careful when
|
||||
using this to pass a pointer to the version, not the version itself;
|
||||
variadic macros are not carefully enough type-checked by |clang| or |gcc|
|
||||
variadic macros are not carefully enough type-checked by [[clang]] or [[gcc]]
|
||||
to catch this sort of slip.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void VersionNumbers::writer(OUTPUT_STREAM, char *format_string, void *vE) {
|
||||
semantic_version_number *V = (semantic_version_number *) vE;
|
||||
VersionNumbers::to_text(OUT, *V);
|
||||
}
|
||||
|
||||
@ Parsing is much more of a slog. The following returns a null version if
|
||||
the text |T| is in any respect malformed, i.e., if it deviates from the
|
||||
the text [[T]] is in any respect malformed, i.e., if it deviates from the
|
||||
above specification in even the most trivial way. We parse the three parts
|
||||
of a semver version in order: e.g. |3.1.41-alpha.72.zeta+6Q45| the first
|
||||
of a semver version in order: e.g. [[3.1.41-alpha.72.zeta+6Q45]] the first
|
||||
part is up to the hyphen, the second part between the hyphen and the plus
|
||||
sign, and the third part runs to the end. The second and third parts are
|
||||
optional, but if both are given, they must be in that order.
|
||||
|
||||
@e MMP_SEMVERPART from 1
|
||||
@e PRE_SEMVERPART
|
||||
@e BM_SEMVERPART
|
||||
<<*>>=
|
||||
enum MMP_SEMVERPART from 1
|
||||
enum PRE_SEMVERPART
|
||||
enum BM_SEMVERPART
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
semantic_version_number VersionNumbers::from_text(text_stream *T) {
|
||||
semantic_version_number V = VersionNumbers::null();
|
||||
int component = 0, val = -1, dots_used = 0, slashes_used = 0, count = 0;
|
||||
|
@ -151,7 +153,7 @@ semantic_version_number VersionNumbers::from_text(text_stream *T) {
|
|||
case MMP_SEMVERPART:
|
||||
if (c == '.') dots_used++;
|
||||
if (c == '/') slashes_used++;
|
||||
if ((c == '.') || (c == '/') || (c == '-') || (c == '+')) {
|
||||
if ((c == '.') [[| (c == '/') || (c == '-') |]] (c == '+')) {
|
||||
if (val == -1) return VersionNumbers::null();
|
||||
if (component >= SEMVER_NUMBER_DEPTH) return VersionNumbers::null();
|
||||
V.version_numbers[component] = val;
|
||||
|
@ -168,9 +170,9 @@ semantic_version_number VersionNumbers::from_text(text_stream *T) {
|
|||
break;
|
||||
case PRE_SEMVERPART:
|
||||
if (c == '.') {
|
||||
@<Add prerelease content@>;
|
||||
<<Add prerelease content>>;
|
||||
} else if (c == '+') {
|
||||
@<Add prerelease content@>; part = BM_SEMVERPART;
|
||||
<<Add prerelease content>>; part = BM_SEMVERPART;
|
||||
} else {
|
||||
PUT_TO(prerelease, c);
|
||||
}
|
||||
|
@ -181,7 +183,7 @@ semantic_version_number VersionNumbers::from_text(text_stream *T) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if ((part == PRE_SEMVERPART) && (Str::len(prerelease) > 0)) @<Add prerelease content@>;
|
||||
if ((part == PRE_SEMVERPART) && (Str::len(prerelease) > 0)) <<Add prerelease content>>;
|
||||
DISCARD_TEXT(prerelease)
|
||||
if ((dots_used > 0) && (slashes_used > 0)) return VersionNumbers::null();
|
||||
if (slashes_used > 0) {
|
||||
|
@ -198,13 +200,13 @@ semantic_version_number VersionNumbers::from_text(text_stream *T) {
|
|||
return V;
|
||||
}
|
||||
|
||||
@<Add prerelease content@> =
|
||||
<<Add prerelease content>>=
|
||||
if (Str::len(prerelease) == 0) return VersionNumbers::null();
|
||||
if (V.prerelease_segments == NULL) V.prerelease_segments = NEW_LINKED_LIST(text_stream);
|
||||
ADD_TO_LINKED_LIST(Str::duplicate(prerelease), text_stream, V.prerelease_segments);
|
||||
Str::clear(prerelease);
|
||||
|
||||
@h Precedence.
|
||||
@ \section{Precedence.}
|
||||
The most important part of the semver standard is the rule on which versions
|
||||
take precedence over which others, and we follow it exactly. The following
|
||||
criteria are used in turn: major version; minor version; patch version;
|
||||
|
@ -214,7 +216,7 @@ compared numerically if consisting of digits only, and alphabetically
|
|||
otherwise; and finally the number of prerelease elements. Build metadata is
|
||||
disregarded entirely.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumbers::le(semantic_version_number V1, semantic_version_number V2) {
|
||||
for (int i=0; i<SEMVER_NUMBER_DEPTH; i++) {
|
||||
int N1 = VersionNumbers::floor(V1.version_numbers[i]);
|
||||
|
@ -252,17 +254,17 @@ int VersionNumbers::le(semantic_version_number V1, semantic_version_number V2) {
|
|||
@ The effect of this is to read unspecified versions of major, minor or patch
|
||||
as if they were 0:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumbers::floor(int N) {
|
||||
if (N < 0) return 0;
|
||||
return N;
|
||||
}
|
||||
|
||||
@ This returns a non-negative integer if |T| contains only digits, and |-1|
|
||||
@ This returns a non-negative integer if [[T]] contains only digits, and [[-1]]
|
||||
otherwise. If the value has more than about 10 digits, then the result will
|
||||
not be meaningful, which I think is a technical violation of the standard.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumbers::strict_atoi(text_stream *T) {
|
||||
LOOP_THROUGH_TEXT(pos, T)
|
||||
if (Characters::isdigit(Str::get(pos)) == FALSE)
|
||||
|
@ -272,13 +274,13 @@ int VersionNumbers::strict_atoi(text_stream *T) {
|
|||
return Str::atoi(T, 0);
|
||||
}
|
||||
|
||||
@h Trichotomy.
|
||||
@ \section{Trichotomy.}
|
||||
We now use the above function to construct ordering relations on semvers.
|
||||
These are trichotomous, that is, for each pair |V1, V2|, exactly one of the
|
||||
|VersionNumbers::eq(V1, V2)|, |VersionNumbers::gt(V1, V2)|, |VersionNumbers::lt(V1, V2)|
|
||||
These are trichotomous, that is, for each pair [[V1, V2]], exactly one of the
|
||||
[[VersionNumbers::eq(V1, V2)]], [[VersionNumbers::gt(V1, V2)]], [[VersionNumbers::lt(V1, V2)]]
|
||||
is true.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumbers::eq(semantic_version_number V1, semantic_version_number V2) {
|
||||
if ((VersionNumbers::le(V1, V2)) && (VersionNumbers::le(V2, V1)))
|
||||
return TRUE;
|
||||
|
@ -301,9 +303,9 @@ int VersionNumbers::lt(semantic_version_number V1, semantic_version_number V2) {
|
|||
return (VersionNumbers::ge(V1, V2))?FALSE:TRUE;
|
||||
}
|
||||
|
||||
@ And the following can be used for sorting, following the |strcmp| convention.
|
||||
@ And the following can be used for sorting, following the [[strcmp]] convention.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int VersionNumbers::cmp(semantic_version_number V1, semantic_version_number V2) {
|
||||
if (VersionNumbers::eq(V1, V2)) return 0;
|
||||
if (VersionNumbers::gt(V1, V2)) return 1;
|
Loading…
Reference in a new issue