1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-26 04:00:43 +03:00

Implemented semantic versioning 2.0.0

This commit is contained in:
Graham Nelson 2020-03-02 12:55:33 +00:00
parent b5a2f39552
commit 67fc2ae91c
14 changed files with 618 additions and 154 deletions

View file

@ -44,6 +44,7 @@ int main(int argc, char **argv) {
else BM = BuildMethodology::new(Pathnames::up(path_to_inbuild), TRUE, use);
if (Str::len(unit_test) > 0) {
if (Str::eq(unit_test, I"compatibility")) Compatibility::test(STDOUT);
else if (Str::eq(unit_test, I"semver")) VersionNumbers::test(STDOUT);
else Errors::with_text("no such unit test: %S", unit_test);
} else {
if (Str::len(filter_text) > 0) {

View file

@ -0,0 +1,41 @@
'1' --> 1
'1.2' --> 1.2
'1.2.3' --> 1.2.3
'71.0.45672' --> 71.0.45672
'1.2.3.4' --> null
'9/861022' --> 9.861022
'9/86102' --> null
'9/8610223' --> null
'9/861022.2' --> null
'9/861022/2' --> null
'1.2.3-alpha.0.x45.1789' --> 1.2.3-alpha.0.x45.1789
'1+lobster' --> 1+lobster
'1.2+lobster' --> 1.2+lobster
'1.2.3+lobster' --> 1.2.3+lobster
'1.2.3-beta.2+shellfish' --> 1.2.3-beta.2+shellfish
3 < 5
3 = 3
3 = 3.0
3 = 3.0.0
3.1.41 > 3.1.5
3.1.41 < 3.2.5
3.1.41 = 3.1.41+arm64
3.1.41 < 3.1.41-pre.0.1
3.1.41-alpha.72 > 3.1.41-alpha.8
3.1.41-alpha.72a < 3.1.41-alpha.8a
3.1.41-alpha.72 < 3.1.41-beta.72
3.1.41-alpha.72 < 3.1.41-alpha.72.zeta
1.2.3+lobster.54 = 1.2.3+lobster.100
Compatibility range of 6.4.2-kappa.17 = [6.4.2-kappa.17,7-A)
At-least range of 6.4.2-kappa.17 = [6.4.2-kappa.17,infty)
At-most range of 6.4.2-kappa.17 = (-infty,6.4.2-kappa.17]
[6.4.2-kappa.17,7-A) intersect [3.5.5,4-A) = empty -- changed
[6.4.2-kappa.17,7-A) intersect [6.9.1,7-A) = [6.9.1,7-A) -- changed
[6.9.1,7-A) intersect [6.4.2-kappa.17,7-A) = [6.9.1,7-A)
[6.4.2,infty) intersect [3.5.5,infty) = [6.4.2,infty)
[6.4.2,infty) intersect (-infty,3.5.5] = empty -- changed
(-infty,6.4.2] intersect [3.5.5,infty) = [3.5.5,6.4.2] -- changed
(-infty,6.4.2] intersect (-infty,3.5.5] = (-infty,3.5.5] -- changed

View file

View file

@ -25,8 +25,8 @@
mkdir: $PATH/_Results_Actual
mkdir: $PATH/_Results_Ideal
debugger: lldb -f inbuild/Tangled/inbuild -- -nest inbuild/Tests/Zoo $[$PATH/$CASE.txt$]
step: inbuild/Tangled/inbuild -nest inbuild/Tests/Zoo $[$PATH/$CASE.txt$] >$A 2>&1
debugger: lldb -f inbuild/Tangled/inbuild -- -external inbuild/Tests/Zoo $[$PATH/$CASE.txt$]
step: inbuild/Tangled/inbuild -external inbuild/Tests/Zoo $[$PATH/$CASE.txt$] >$A 2>&1
or: 'failed to produce output' $A
show: $A

View file

@ -12,12 +12,14 @@ Setting up the use of this module.
@e target_vm_MT
@e compatibility_specification_MT
@e semantic_version_number_holder_MT
@e semver_range_MT
=
ALLOCATE_INDIVIDUALLY(inter_architecture)
ALLOCATE_INDIVIDUALLY(target_vm)
ALLOCATE_INDIVIDUALLY(compatibility_specification)
ALLOCATE_INDIVIDUALLY(semantic_version_number_holder)
ALLOCATE_INDIVIDUALLY(semver_range)
@h The beginning.
@ -38,7 +40,7 @@ void ArchModule::start(void) {
;
@<Register this module's stream writers@> =
;
Writers::register_writer('v', &VersionNumbers::writer);
@

View file

@ -2,25 +2,28 @@
Semantic version numbers such as 3.7.1.
@ Traditional semantic version numbers look like dot-divided runs of
non-negative integers: for example, 4, 7.1, and 0.2.3. Up to |VERSION_NUMBER_DEPTH|
components can be given. The tail of the array should be padded with |-1| values;
otherwise, components should all be non-negative integers.
@h 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|.
@d VERSION_NUMBER_DEPTH 4
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.
The standard now requires exactly three: major, minor and patch. It's
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:
=
typedef struct semantic_version_number {
int version_numbers[VERSION_NUMBER_DEPTH];
} semantic_version_number;
(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|
typedef struct semantic_version_number_holder {
struct semantic_version_number version;
MEMORY_MANAGEMENT
} semantic_version_number_holder;
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.
@ However, Inform 7 extensions have for many years allowed two forms of
version number: either just |N|, which clearly fits the scheme above, or
@ 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
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
@ -35,7 +38,36 @@ time of the Reykjavik summit between Presidents Gorbachev and Reagan:
Story file collectors customarily abbreviate this in catalogues to |9/861022|.
We will therefore allow this notation, and convert it silently each way.
|N/DDDDDD| is equivalent to |N.DDDDDD|.
|N/DDDDDD| is equivalent to |N.DDDDDD|. Thus, |9/861022| means 9.861022.0 in
semver precedence order.
In all non-textual respects, and in particular on precedence rules, we follow
the standard exactly. The only reason we allow these abbreviations is because
we don't want to force Inform extension writers to type "Version 3.4.1 of
Such-and-Such by Me begins here", and so on: it would break all existing
extensions, for one thing, and it looks unfriendly.
@ 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|.
@d 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 text_stream *build_metadata;
} semantic_version_number;
typedef struct semantic_version_number_holder {
struct semantic_version_number version;
MEMORY_MANAGEMENT
} 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.
@ -47,127 +79,548 @@ semantic_version_number VersionNumbers::null(void) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
semantic_version_number V;
for (int i=0; i<VERSION_NUMBER_DEPTH; i++) V.version_numbers[i] = -1;
for (int i=0; i<SEMVER_NUMBER_DEPTH; i++) V.version_numbers[i] = -1;
V.prerelease_segments = NULL;
V.build_metadata = NULL;
return V;
#pragma clang diagnostic pop
}
semantic_version_number VersionNumbers::from_major(int major) {
semantic_version_number V = VersionNumbers::null();
V.version_numbers[0] = major;
return V;
}
semantic_version_number VersionNumbers::from_pair(int major, int minor) {
semantic_version_number V = VersionNumbers::null();
V.version_numbers[0] = major;
V.version_numbers[1] = minor;
return V;
}
int VersionNumbers::is_null(semantic_version_number V) {
for (int i=0, allow=TRUE; i<VERSION_NUMBER_DEPTH; i++) {
if (V.version_numbers[i] < -1)
return TRUE;
if (V.version_numbers[i] == -1)
allow = FALSE;
else if (allow == FALSE) return TRUE;
for (int i=0, allow=TRUE; i<SEMVER_NUMBER_DEPTH; i++) {
if (V.version_numbers[i] < -1) return TRUE; /* should never happen */
if (V.version_numbers[i] == -1) allow = FALSE;
else if (allow == FALSE) return TRUE; /* should never happen */
}
if (V.version_numbers[0] < 0) return TRUE;
return FALSE;
}
@ Here we print and parse:
@h Printing and parsing.
Printing is simple enough:
=
void VersionNumbers::to_text(OUTPUT_STREAM, semantic_version_number V) {
if (VersionNumbers::is_null(V)) WRITE("null");
else
for (int i=0; (i<VERSION_NUMBER_DEPTH) && (V.version_numbers[i] >= 0); i++) {
if (i>0) WRITE(".");
WRITE("%d", V.version_numbers[i]);
}
if (VersionNumbers::is_null(V)) { WRITE("null"); return; }
for (int i=0; (i<SEMVER_NUMBER_DEPTH) && (V.version_numbers[i] >= 0); i++) {
if (i>0) WRITE(".");
WRITE("%d", V.version_numbers[i]);
}
if (V.prerelease_segments) {
int c = 0;
text_stream *T;
LOOP_OVER_LINKED_LIST(T, text_stream, V.prerelease_segments) {
if (c++ == 0) WRITE("-"); else WRITE(".");
WRITE("%S", T);
}
}
if (V.build_metadata) WRITE("+%S", V.build_metadata);
}
@ 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|
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
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
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
=
semantic_version_number VersionNumbers::from_text(text_stream *T) {
semantic_version_number V;
semantic_version_number V = VersionNumbers::null();
int component = 0, val = -1, dots_used = 0, slashes_used = 0, count = 0;
int part = MMP_SEMVERPART;
TEMPORARY_TEXT(prerelease);
LOOP_THROUGH_TEXT(pos, T) {
wchar_t c = Str::get(pos);
if (c == '.') dots_used++;
if (c == '/') slashes_used++;
if ((c == '.') || (c == '/')) {
if (val == -1) return VersionNumbers::null();
if (component >= VERSION_NUMBER_DEPTH) return VersionNumbers::null();
V.version_numbers[component] = val;
component++; val = -1; count = 0;
} else if (Characters::isdigit(c)) {
int digit = c - '0';
if ((val == 0) && (slashes_used == 0))
return VersionNumbers::null();
if (val < 0) val = digit; else val = 10*val + digit;
count++;
} else return VersionNumbers::null();
switch (part) {
case MMP_SEMVERPART:
if (c == '.') dots_used++;
if (c == '/') slashes_used++;
if ((c == '.') || (c == '/') || (c == '-') || (c == '+')) {
if (val == -1) return VersionNumbers::null();
if (component >= SEMVER_NUMBER_DEPTH) return VersionNumbers::null();
V.version_numbers[component] = val;
component++; val = -1; count = 0;
if (c == '-') part = PRE_SEMVERPART;
if (c == '+') part = BM_SEMVERPART;
} else if (Characters::isdigit(c)) {
int digit = c - '0';
if ((val == 0) && (slashes_used == 0))
return VersionNumbers::null();
if (val < 0) val = digit; else val = 10*val + digit;
count++;
} else return VersionNumbers::null();
break;
case PRE_SEMVERPART:
if (c == '.') {
@<Add prerelease content@>;
} else if (c == '+') {
@<Add prerelease content@>; part = BM_SEMVERPART;
} else {
PUT_TO(prerelease, c);
}
break;
case BM_SEMVERPART:
if (V.build_metadata == NULL) V.build_metadata = Str::new();
PUT_TO(V.build_metadata, c);
break;
}
}
if (val == -1) return VersionNumbers::null();
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) {
if (component > 1) return VersionNumbers::null();
if (count != 6) return VersionNumbers::null();
}
if (component >= VERSION_NUMBER_DEPTH) return VersionNumbers::null();
V.version_numbers[component] = val;
for (int i=component+1; i<VERSION_NUMBER_DEPTH; i++) V.version_numbers[i] = -1;
if (part == MMP_SEMVERPART) {
if (val == -1) return VersionNumbers::null();
if (component >= SEMVER_NUMBER_DEPTH) return VersionNumbers::null();
V.version_numbers[component] = val;
}
return V;
}
@ And now comparison operators. Note that all null versions are equal, and
are always both |<=| and |>=| all versions. This means our ordering is not
trichotomous (though it is on the set of non-null versions), but this
ensures that null versions can be used to mean "unlimited" in either direction.
@<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 Precendence.
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;
any prerelease elements, which must be 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]);
int N2 = VersionNumbers::floor(V2.version_numbers[i]);
if (N1 > N2) return FALSE;
if (N1 < N2) return TRUE;
}
linked_list_item *I1 = (V1.prerelease_segments)?(LinkedLists::first(V1.prerelease_segments)):NULL;
linked_list_item *I2 = (V2.prerelease_segments)?(LinkedLists::first(V2.prerelease_segments)):NULL;
while ((I1) && (I2)) {
text_stream *T1 = (text_stream *) LinkedLists::content(I1);
text_stream *T2 = (text_stream *) LinkedLists::content(I2);
int N1 = VersionNumbers::strict_atoi(T1);
int N2 = VersionNumbers::strict_atoi(T2);
if ((N1 >= 0) && (N2 >= 0)) {
if (N1 < N2) return TRUE;
if (N1 > N2) return FALSE;
} else {
if (Str::ne(T1, T2)) {
int c = Str::cmp(T1, T2);
if (c < 0) return TRUE;
if (c > 0) return FALSE;
}
}
I1 = LinkedLists::next(I1);
I2 = LinkedLists::next(I2);
}
if ((I1 == NULL) && (I2)) return TRUE;
if ((I1) && (I2 == NULL)) return FALSE;
return TRUE;
}
@ 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|
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)
return -1;
wchar_t c = Str::get_first_char(T);
if ((c == '0') && (Str::len(T) > 1)) return -1;
return Str::atoi(T, 0);
}
@h 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)|
is true.
=
int VersionNumbers::eq(semantic_version_number V1, semantic_version_number V2) {
if (VersionNumbers::is_null(V1)) return VersionNumbers::is_null(V2);
if (VersionNumbers::is_null(V2)) return FALSE;
for (int i=0; i<VERSION_NUMBER_DEPTH; i++)
if (V1.version_numbers[i] != V2.version_numbers[i])
return FALSE;
return TRUE;
if ((VersionNumbers::le(V1, V2)) && (VersionNumbers::le(V2, V1)))
return TRUE;
return FALSE;
}
int VersionNumbers::ne(semantic_version_number V1, semantic_version_number V2) {
return (VersionNumbers::eq(V1, V2))?FALSE:TRUE;
}
int VersionNumbers::le(semantic_version_number V1, semantic_version_number V2) {
if (VersionNumbers::is_null(V1)) return TRUE;
if (VersionNumbers::is_null(V2)) return TRUE;
for (int i=0; i<VERSION_NUMBER_DEPTH; i++)
if (V1.version_numbers[i] > V2.version_numbers[i])
return FALSE;
return TRUE;
}
int VersionNumbers::gt(semantic_version_number V1, semantic_version_number V2) {
return (VersionNumbers::le(V1, V2))?FALSE:TRUE;
}
int VersionNumbers::ge(semantic_version_number V1, semantic_version_number V2) {
if (VersionNumbers::is_null(V1)) return TRUE;
if (VersionNumbers::is_null(V2)) return TRUE;
for (int i=0; i<VERSION_NUMBER_DEPTH; i++)
if (V1.version_numbers[i] < V2.version_numbers[i])
return FALSE;
return TRUE;
return VersionNumbers::le(V2, V1);
}
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.
=
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;
return -1;
}
@h 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)|
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
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
=
typedef struct range_end {
int end_type;
struct semantic_version_number end_value;
} range_end;
typedef struct semver_range {
struct range_end lower;
struct range_end upper;
MEMORY_MANAGEMENT
} semver_range;
@ As hinted above, the notation |[| and |]| is used for closed ends, and |(|
and |)| for open ones.
=
void VersionNumbers::write_range(OUTPUT_STREAM, semver_range *R) {
if (R == NULL) internal_error("no range");
switch(R->lower.end_type) {
case CLOSED_RANGE_END: WRITE("[%v,", &(R->lower.end_value)); break;
case OPEN_RANGE_END: WRITE("(%v,", &(R->lower.end_value)); break;
case INFINITE_RANGE_END: WRITE("(-infty,"); break;
case EMPTY_RANGE_END: WRITE("empty"); break;
}
switch(R->upper.end_type) {
case CLOSED_RANGE_END: WRITE("%v]", &(R->upper.end_value)); break;
case OPEN_RANGE_END: WRITE("%v)", &(R->upper.end_value)); break;
case INFINITE_RANGE_END: WRITE("infty)"); break;
}
}
@ The "allow anything" range runs from minus to plus infinity. Every version
number lies in this range.
=
semver_range *VersionNumbers::any_range(void) {
semver_range *R = CREATE(semver_range);
R->lower.end_type = INFINITE_RANGE_END;
R->lower.end_value = VersionNumbers::null();
R->upper.end_type = INFINITE_RANGE_END;
R->upper.end_value = VersionNumbers::null();
return R;
}
int VersionNumbers::is_any_range(semver_range *R) {
if (R == NULL) return TRUE;
if ((R->lower.end_type == INFINITE_RANGE_END) && (R->upper.end_type == INFINITE_RANGE_END))
return TRUE;
return FALSE;
}
@ 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
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
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
alphanumeric string gives it lower precendence than all other prerelease
versions.
=
semver_range *VersionNumbers::compatibility_range(semantic_version_number V) {
semver_range *R = VersionNumbers::any_range();
if (VersionNumbers::is_null(V) == FALSE) {
R->lower.end_type = CLOSED_RANGE_END;
R->lower.end_value = V;
R->upper.end_type = OPEN_RANGE_END;
semantic_version_number W = VersionNumbers::null();
W.version_numbers[0] = V.version_numbers[0] + 1;
W.prerelease_segments = NEW_LINKED_LIST(text_stream);
ADD_TO_LINKED_LIST(I"A", text_stream, W.prerelease_segments);
R->upper.end_value = W;
}
return R;
}
@ More straightforwardly, these ranges are for anything from V, or up to V,
inclusive:
=
semver_range *VersionNumbers::at_least_range(semantic_version_number V) {
semver_range *R = VersionNumbers::any_range();
R->lower.end_type = CLOSED_RANGE_END;
R->lower.end_value = V;
return R;
}
semver_range *VersionNumbers::at_most_range(semantic_version_number V) {
semver_range *R = VersionNumbers::any_range();
R->upper.end_type = CLOSED_RANGE_END;
R->upper.end_value = V;
return R;
}
@ Here we test whether V is at least a given end, and then at most:
=
int VersionNumbers::version_ge_end(semantic_version_number V, range_end E) {
switch (E.end_type) {
case CLOSED_RANGE_END:
if (VersionNumbers::is_null(V)) return FALSE;
if (VersionNumbers::ge(V, E.end_value)) return TRUE;
break;
case OPEN_RANGE_END:
if (VersionNumbers::is_null(V)) return FALSE;
if (VersionNumbers::gt(V, E.end_value)) return TRUE;
break;
case INFINITE_RANGE_END: return TRUE;
case EMPTY_RANGE_END: return FALSE;
}
return FALSE;
}
int VersionNumbers::version_le_end(semantic_version_number V, range_end E) {
switch (E.end_type) {
case CLOSED_RANGE_END:
if (VersionNumbers::is_null(V)) return FALSE;
if (VersionNumbers::le(V, E.end_value)) return TRUE;
break;
case OPEN_RANGE_END:
if (VersionNumbers::is_null(V)) return FALSE;
if (VersionNumbers::lt(V, E.end_value)) return TRUE;
break;
case INFINITE_RANGE_END: return TRUE;
case EMPTY_RANGE_END: return FALSE;
}
return FALSE;
}
@ This allows a simple way to write:
=
int VersionNumbers::in_range(semantic_version_number V, semver_range *R) {
if (R == NULL) return TRUE;
if ((VersionNumbers::version_ge_end(V, R->lower)) &&
(VersionNumbers::version_le_end(V, R->upper))) return TRUE;
return FALSE;
}
@ 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.
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
the boundary value is the same, open ends are stricter than closed ends.
=
int VersionNumbers::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;
if (E2.end_type == EMPTY_RANGE_END) return -1;
if ((E1.end_type == INFINITE_RANGE_END) && (E2.end_type == INFINITE_RANGE_END)) return 0;
if (E1.end_type == INFINITE_RANGE_END) return -1;
if (E2.end_type == INFINITE_RANGE_END) return 1;
int c = VersionNumbers::cmp(E1.end_value, E2.end_value);
if (c != 0) {
if (lower) return c; else return -c;
}
if (E1.end_type == E2.end_type) return 0;
if (E1.end_type == CLOSED_RANGE_END) return -1;
return 1;
}
@ And so we finally arrive at the following, which intersects two ranges:
that is, it changes |R1| to the range of versions which lie ibside 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 VersionNumbers::intersect_range(semver_range *R1, semver_range *R2) {
int lc = VersionNumbers::stricter(R1->lower, R2->lower, TRUE);
int uc = VersionNumbers::stricter(R1->upper, R2->upper, FALSE);
if ((lc >= 0) && (uc >= 0)) return FALSE;
if (lc < 0) R1->lower = R2->lower;
if (uc < 0) R1->upper = R2->upper;
if (R1->lower.end_type == EMPTY_RANGE_END) R1->upper.end_type = EMPTY_RANGE_END;
else if (R1->upper.end_type == EMPTY_RANGE_END) R1->lower.end_type = EMPTY_RANGE_END;
else if ((R1->lower.end_type != INFINITE_RANGE_END) && (R1->upper.end_type != INFINITE_RANGE_END)) {
int c = VersionNumbers::cmp(R1->lower.end_value, R1->upper.end_value);
if ((c > 0) ||
((c == 0) && ((R1->lower.end_type == OPEN_RANGE_END) ||
(R1->upper.end_type == OPEN_RANGE_END)))) {
R1->lower.end_type = EMPTY_RANGE_END; R1->upper.end_type = EMPTY_RANGE_END;
}
}
return TRUE;
}
@h Unit tests.
The Inbuild test case |semver| exercises the following.
=
void VersionNumbers::test_range(OUTPUT_STREAM, text_stream *text) {
semantic_version_number V = VersionNumbers::from_text(text);
semver_range *R = VersionNumbers::compatibility_range(V);
WRITE("Compatibility range of %v = ", &V);
VersionNumbers::write_range(OUT, R);
WRITE("\n");
R = VersionNumbers::at_least_range(V);
WRITE("At-least range of %v = ", &V);
VersionNumbers::write_range(OUT, R);
WRITE("\n");
R = VersionNumbers::at_most_range(V);
WRITE("At-most range of %v = ", &V);
VersionNumbers::write_range(OUT, R);
WRITE("\n");
}
void VersionNumbers::test_intersect(OUTPUT_STREAM,
text_stream *text1, int r1, text_stream *text2, int r2) {
semantic_version_number V1 = VersionNumbers::from_text(text1);
semver_range *R1 = NULL;
if (r1 == 0) R1 = VersionNumbers::compatibility_range(V1);
else if (r1 > 0) R1 = VersionNumbers::at_least_range(V1);
else if (r1 < 0) R1 = VersionNumbers::at_most_range(V1);
semantic_version_number V2 = VersionNumbers::from_text(text2);
semver_range *R2 = NULL;
if (r2 == 0) R2 = VersionNumbers::compatibility_range(V2);
else if (r2 > 0) R2 = VersionNumbers::at_least_range(V2);
else if (r2 < 0) R2 = VersionNumbers::at_most_range(V2);
VersionNumbers::write_range(OUT, R1);
WRITE(" intersect ");
VersionNumbers::write_range(OUT, R2);
WRITE(" = ");
int changed = VersionNumbers::intersect_range(R1, R2);
VersionNumbers::write_range(OUT, R1);
if (changed) WRITE (" -- changed");
WRITE("\n");
}
void VersionNumbers::test_read_write(OUTPUT_STREAM, text_stream *text) {
semantic_version_number V = VersionNumbers::from_text(text);
WRITE("'%S' --> %v\n", text, &V);
}
void VersionNumbers::test_precedence(OUTPUT_STREAM, text_stream *text1, text_stream *text2) {
semantic_version_number V1 = VersionNumbers::from_text(text1);
semantic_version_number V2 = VersionNumbers::from_text(text2);
int gt = VersionNumbers::gt(V1, V2);
int eq = VersionNumbers::eq(V1, V2);
int lt = VersionNumbers::lt(V1, V2);
if (lt) WRITE("%v < %v", &V1, &V2);
if (eq) WRITE("%v = %v", &V1, &V2);
if (gt) WRITE("%v > %v", &V1, &V2);
WRITE("\n");
}
void VersionNumbers::test(OUTPUT_STREAM) {
VersionNumbers::test_read_write(OUT, I"1");
VersionNumbers::test_read_write(OUT, I"1.2");
VersionNumbers::test_read_write(OUT, I"1.2.3");
VersionNumbers::test_read_write(OUT, I"71.0.45672");
VersionNumbers::test_read_write(OUT, I"1.2.3.4");
VersionNumbers::test_read_write(OUT, I"9/861022");
VersionNumbers::test_read_write(OUT, I"9/86102");
VersionNumbers::test_read_write(OUT, I"9/8610223");
VersionNumbers::test_read_write(OUT, I"9/861022.2");
VersionNumbers::test_read_write(OUT, I"9/861022/2");
VersionNumbers::test_read_write(OUT, I"1.2.3-alpha.0.x45.1789");
VersionNumbers::test_read_write(OUT, I"1+lobster");
VersionNumbers::test_read_write(OUT, I"1.2+lobster");
VersionNumbers::test_read_write(OUT, I"1.2.3+lobster");
VersionNumbers::test_read_write(OUT, I"1.2.3-beta.2+shellfish");
WRITE("\n");
VersionNumbers::test_precedence(OUT, I"3", I"5");
VersionNumbers::test_precedence(OUT, I"3", I"3");
VersionNumbers::test_precedence(OUT, I"3", I"3.0");
VersionNumbers::test_precedence(OUT, I"3", I"3.0.0");
VersionNumbers::test_precedence(OUT, I"3.1.41", I"3.1.5");
VersionNumbers::test_precedence(OUT, I"3.1.41", I"3.2.5");
VersionNumbers::test_precedence(OUT, I"3.1.41", I"3.1.41+arm64");
VersionNumbers::test_precedence(OUT, I"3.1.41", I"3.1.41-pre.0.1");
VersionNumbers::test_precedence(OUT, I"3.1.41-alpha.72", I"3.1.41-alpha.8");
VersionNumbers::test_precedence(OUT, I"3.1.41-alpha.72a", I"3.1.41-alpha.8a");
VersionNumbers::test_precedence(OUT, I"3.1.41-alpha.72", I"3.1.41-beta.72");
VersionNumbers::test_precedence(OUT, I"3.1.41-alpha.72", I"3.1.41-alpha.72.zeta");
VersionNumbers::test_precedence(OUT, I"1.2.3+lobster.54", I"1.2.3+lobster.100");
WRITE("\n");
VersionNumbers::test_range(OUT, I"6.4.2-kappa.17");
WRITE("\n");
VersionNumbers::test_intersect(OUT, I"6.4.2-kappa.17", 0, I"3.5.5", 0);
VersionNumbers::test_intersect(OUT, I"6.4.2-kappa.17", 0, I"6.9.1", 0);
VersionNumbers::test_intersect(OUT, I"6.9.1", 0, I"6.4.2-kappa.17", 0);
VersionNumbers::test_intersect(OUT, I"6.4.2", 1, I"3.5.5", 1);
VersionNumbers::test_intersect(OUT, I"6.4.2", 1, I"3.5.5", -1);
VersionNumbers::test_intersect(OUT, I"6.4.2", -1, I"3.5.5", 1);
VersionNumbers::test_intersect(OUT, I"6.4.2", -1, I"3.5.5", -1);
}

View file

@ -93,7 +93,6 @@ void InbuildModule::start(void) {
Memory::reason_name(EXTENSION_DICTIONARY_MREASON, "extension dictionary");
@<Register this module's stream writers@> =
Writers::register_writer('v', &VersionNumbers::writer);
Writers::register_writer('X', &Works::writer);
@

View file

@ -8,24 +8,21 @@ with a given title, and/or version number.
=
typedef struct inbuild_requirement {
struct inbuild_work *work;
struct semantic_version_number min_version;
struct semantic_version_number max_version;
struct semver_range *version_range;
int allow_malformed;
MEMORY_MANAGEMENT
} inbuild_requirement;
inbuild_requirement *Requirements::new(inbuild_work *work,
semantic_version_number min, semantic_version_number max) {
inbuild_requirement *Requirements::new(inbuild_work *work, semver_range *R) {
inbuild_requirement *req = CREATE(inbuild_requirement);
req->work = work;
req->min_version = min;
req->max_version = max;
req->version_range = R;
req->allow_malformed = FALSE;
return req;
}
inbuild_requirement *Requirements::any_version_of(inbuild_work *work) {
return Requirements::new(work, VersionNumbers::null(), VersionNumbers::null());
return Requirements::new(work, VersionNumbers::any_range());
}
inbuild_requirement *Requirements::anything_of_genre(inbuild_genre *G) {
@ -100,22 +97,21 @@ void Requirements::impose_clause(inbuild_requirement *req, text_stream *T, text_
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->min_version = V;
req->max_version = V;
req->version_range = VersionNumbers::compatibility_range(V);
} else if (Str::eq(clause, I"min")) {
semantic_version_number V = VersionNumbers::from_text(value);
if (VersionNumbers::is_null(V)) {
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->min_version = V;
req->version_range = VersionNumbers::at_least_range(V);
} else if (Str::eq(clause, I"max")) {
semantic_version_number V = VersionNumbers::from_text(value);
if (VersionNumbers::is_null(V)) {
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->max_version = V;
req->version_range = VersionNumbers::at_most_range(V);
} else {
if (Str::len(errors) == 0)
WRITE_TO(errors, "no such term as '%S'", clause);
@ -144,13 +140,9 @@ void Requirements::write(OUTPUT_STREAM, inbuild_requirement *req) {
if (claused) WRITE(","); claused = TRUE;
WRITE("author=%S", req->work->author_name);
}
if (VersionNumbers::is_null(req->min_version) == FALSE) {
if (VersionNumbers::is_any_range(req->version_range) == FALSE) {
if (claused) WRITE(","); claused = TRUE;
WRITE("min=%v", &(req->min_version));
}
if (VersionNumbers::is_null(req->max_version) == FALSE) {
if (claused) WRITE(","); claused = TRUE;
WRITE("max=%v", &(req->max_version));
WRITE("range="); VersionNumbers::write_range(OUT, req->version_range);
}
if (claused == FALSE) WRITE("all");
if (req->allow_malformed) WRITE("*");
@ -173,24 +165,5 @@ int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) {
return FALSE;
}
}
if (VersionNumbers::is_null(req->min_version) == FALSE) {
if (VersionNumbers::is_null(edition->version)) return FALSE;
if (VersionNumbers::lt(edition->version, req->min_version)) return FALSE;
}
if (VersionNumbers::is_null(req->max_version) == FALSE) {
if (VersionNumbers::is_null(edition->version)) return TRUE;
if (VersionNumbers::gt(edition->version, req->max_version)) return FALSE;
}
return TRUE;
}
int Requirements::ratchet_minimum(semantic_version_number V, inbuild_requirement *req) {
if (req == NULL) internal_error("no requirement");
if (VersionNumbers::is_null(V)) return FALSE;
if ((VersionNumbers::is_null(req->min_version)) ||
(VersionNumbers::gt(V, req->min_version))) {
req->min_version = V;
return TRUE;
}
return FALSE;
return VersionNumbers::in_range(edition->version, req->version_range);
}

View file

@ -204,7 +204,7 @@ int Graphs::build(OUTPUT_STREAM, build_vertex *V, build_methodology *meth) {
int Graphs::rebuild(OUTPUT_STREAM, build_vertex *V, build_methodology *meth) {
return Graphs::build_r(OUT, BUILD_GB + FORCE_GB, V, meth);
}
int trace_ibg = TRUE;
int trace_ibg = FALSE;
int Graphs::build_r(OUTPUT_STREAM, int gb, build_vertex *V, build_methodology *meth) {
if (trace_ibg) { WRITE_TO(STDOUT, "Build: "); Graphs::describe(STDOUT, V, FALSE); }

View file

@ -71,7 +71,9 @@ int BuildSteps::execute(build_vertex *V, build_step *S, build_methodology *meth)
IMETHOD_CALL(returned, S->what_to_do, BUILD_SKILL_INTERNAL_MTID, S, meth);
if (returned != TRUE) rv = FALSE;
}
#ifndef CORE_MODULE
if (rv == FALSE) WRITE_TO(STDERR, "Build failed at '%S'\n", command);
#endif
DISCARD_TEXT(command);
return rv;
}

View file

@ -284,14 +284,10 @@ void Extensions::make_standard(inform_extension *E) {
void Extensions::must_satisfy(inform_extension *E, inbuild_requirement *req) {
if (E->must_satisfy == NULL) E->must_satisfy = req;
else {
semantic_version_number V = req->min_version;
if (VersionNumbers::is_null(V) == FALSE)
if (Requirements::ratchet_minimum(V, E->must_satisfy)) {
#ifdef CORE_MODULE
Extensions::set_inclusion_sentence(E, current_sentence);
#endif
}
else if (VersionNumbers::intersect_range(E->must_satisfy->version_range, req->version_range)) {
#ifdef CORE_MODULE
Extensions::set_inclusion_sentence(E, current_sentence);
#endif
}
}

View file

@ -298,6 +298,7 @@ void Projects::construct_build_target(inform_project *project, target_vm *VM,
if (compile_only) {
project->chosen_build_target = inf_V;
inf_V->force_this = TRUE;
inter_V->force_this = TRUE;
} else if (releasing) project->chosen_build_target = project->blorbed_vertex;
else project->chosen_build_target = project->unblorbed_vertex;
}

View file

@ -1,20 +1,13 @@
Inform 7 build 6M55 has started.
I've now read your source text, which is 11 words long.
I've also read Standard Rules by Graham Nelson, which is 42526 words long.
I've also read English Language by Graham Nelson, which is 2288 words long.
Inform 7.10.1 build 6Q21 has started.
'inform7' -kit BasicInformKit -kit EnglishLanguageKit -kit WorldModelKit -kit CommandParserKit -format=z8 -external 'inform7/Tests' -internal 'inform7/Internal' -project '/Users/gnelson/Natural Inform/intest/Workspace/T0/Example.inform'
I've now read your source text, which is 11 words long.
I've also read Basic Inform by Graham Nelson, which is 7645 words long.
I've also read English Language by Graham Nelson, which is 2328 words long.
I've also read Standard Rules by Graham Nelson, which is 32123 words long.
Problem__ PM_RoomInIgnoredSource
>--> This is supposed to be a source text which only contains release
instructions to bind up an existing story file (for instance, one produced
using Inform 6). That's because the instruction 'Release along with an
existing story file' is present. So the source text must not contain rooms
or other game design - these would be ignored.
Problem__ PM_UnreleasedRelease
>--> This is supposed to be a source text which only contains release
instructions to bind up an existing story file (for instance, one produced
using Inform 6). That's because the instruction 'Release along with an
existing story file' is present. So the only way to build the project is to
use the Release option - not, for instance, Go or Replay, because it would
make no sense to translate the source text into something to play. (Of
course, you can play the released story file using an interpreter such as
Zoom or Windows Frotz, etc.: just not here, within Inform.)
Inform 7 has finished: 28 centiseconds used.
Inform 7 has finished.

View file

@ -131,9 +131,12 @@ parse tree.
WRITE_TO(exfa, "%+W", AW);
inbuild_work *work = Works::new(extension_genre, exft, exfa);
Works::add_to_database(work, LOADED_WDBC);
semantic_version_number min = VersionNumbers::null();
if (version_word >= 0) min = Extensions::Inclusion::parse_version(version_word);
inbuild_requirement *req = Requirements::new(work, min, VersionNumbers::null());
semantic_version_number V = VersionNumbers::null();
if (version_word >= 0) V = Extensions::Inclusion::parse_version(version_word);
semver_range *R = NULL;
if (VersionNumbers::is_null(V)) R = VersionNumbers::any_range();
else R = VersionNumbers::compatibility_range(V);
inbuild_requirement *req = Requirements::new(work, R);
DISCARD_TEXT(exft);
DISCARD_TEXT(exfa);