version

Log

Files

Refs

README

LICENSE

version.c (47289B)

     1 
     2 
     3 /* Libsheepy documentation: http://spartatek.se/libsheepy/ */
     4 
     5 #include "libsheepyObject.h"
     6 #include "version.h"
     7 #include "versionInternal.h"
     8 #include "shpPackages/short/short.h"
     9 
    10 #include <stdlib.h>
    11 #include <string.h>
    12 #include <stdio.h>
    13 
    14 void initiateVersion(versiont *self);
    15 void registerMethodsVersion(versionFunctionst *f);
    16 void initiateAllocateVersion(versiont **self);
    17 void finalizeVersion(void);
    18 versiont* allocVersion(const char *v);
    19 local void freeVersion(versiont *self);
    20 local void terminateVersion(versiont **self);
    21 local char* toStringVersion(versiont *self);
    22 local versiont* duplicateVersion(versiont *self);
    23 local void smashVersion(versiont **self);
    24 local void finishVersion(versiont **self);
    25 local const char* helpVersion(versiont *self);
    26 local void logVersion(versiont *self);
    27 local bool validVersion(versiont *self, const char *v);
    28 local bool parseVersion(versiont *self, const char *v);
    29 local bool parseStrictVersion(versiont *self, const char *v);
    30 local bool cleanVersion(versiont *self, char *v);
    31 local bool equalVersion(versiont *self, versiont *ver);
    32 local bool equalSVersion(versiont *self, const char *v);
    33 local bool equalSSVersion(versiont *self, const char *v1, const char *v2);
    34 local int cmpVersion(versiont *self, versiont *ver);
    35 local int  cmpSVersion(versiont *self, const char *v);
    36 local int  cmpSSVersion(versiont *self, const char *v1, const char *v2);
    37 local bool incrVersion(versiont *self, u32 level);
    38 local smallArrayt* sortVersion(versiont *self, smallArrayt *vlist);
    39 local bool satisfiesVersion(versiont *self, const char *vrange);
    40 local smallJsont* toJsonVersion(versiont *self);
    41 local char* toJsonStrVersion(versiont *self);
    42 local bool fromJsonVersion(versiont *self, smallJsont *json);
    43 local bool fromJsonStrVersion(versiont *self, const char *json);
    44 
    45 /* enable/disable logging */
    46 #undef pLog
    47 #define pLog(...)
    48 
    49 // all valid characters in version strings
    50 #define DELIMITER     "."
    51 #define PR_DELIMITER  "-"
    52 #define BD_DELIMITER  "+"
    53 #define NUMBERS       "0123456789"
    54 #define ALPHA         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    55 #define DELIMITERS    DELIMITER PR_DELIMITER BD_DELIMITER
    56 #define ANYTOKENS     "xX*"
    57 #define VALID_CHARS   NUMBERS ALPHA DELIMITERS ANYTOKENS
    58 
    59 // bitset used to strip invalid characters
    60 staticBitsetT(validt, u64 , 256);
    61 // bug in gcc 4.9, ignore: warning: missing braces around initializer [-Wmissing-braces]
    62 local validt validChars = {0};
    63 
    64 // parser states
    65 // STATE[START] to convert state value to string
    66 enum {START, REL, MAJOR, MINOR, PATCH, OTHERS, PRE, BUILD, CSTRING, NUMBER, ANY};
    67 local const char *STATE[] UNUSED = {"START", "REL", "MAJOR", "MINOR", "PATCH", "OTHERS", "PRE", "BUILD", "STRING", "NUMBER", "ANY xX*"};
    68 
    69 /**
    70  * true when c is a valid character
    71  */
    72 local inline bool isValidChar(char c) {
    73   ret staticBitsetGet(&validChars, c);
    74 }
    75 
    76 void initiateVersion(versiont *self) {
    77 
    78   self->type = "version";
    79   if (!versionF) {
    80     versionF            = malloc(sizeof(versionFunctionst));
    81     registerMethodsVersion(versionF);
    82     pErrorNot0(atexit(finalizeVersion));
    83 
    84     // initialize list of valid characters
    85     range(i,strlen(VALID_CHARS)) {
    86       staticBitset1(&validChars, VALID_CHARS[i]);
    87     }
    88   }
    89   self->f = versionF;
    90 
    91   #define setToZero\
    92     self->originalVer        = NULL;\
    93     self->empty              = true;\
    94     self->mainComponentCount = 0;\
    95     self->release            = 0;\
    96     self->major              = 0;\
    97     self->minor              = 0;\
    98     self->patch              = 0
    99   setToZero;
   100   initiateG(&self->others);
   101   initiateG(&self->prerelease);
   102   initiateG(&self->build);
   103 
   104 }
   105 
   106 void registerMethodsVersion(versionFunctionst *f) {
   107 
   108   f->free        = freeVersion;
   109   f->terminate   = terminateVersion;
   110   f->toString    = toStringVersion;
   111   f->duplicate   = duplicateVersion;
   112   f->smash       = smashVersion;
   113   f->finish      = finishVersion;
   114   f->help        = helpVersion;
   115   f->log         = logVersion;
   116   f->valid       = validVersion;
   117   f->parse       = parseVersion;
   118   f->parseStrict = parseStrictVersion;
   119   f->clean       = cleanVersion;
   120   f->equal       = equalVersion;
   121   f->equalS      = equalSVersion;
   122   f->equalSS     = equalSSVersion;
   123   f->cmp         = cmpVersion;
   124   f->cmpS        = cmpSVersion;
   125   f->cmpSS       = cmpSSVersion;
   126   f->incr        = incrVersion;
   127   f->sort        = sortVersion;
   128   f->satisfies   = satisfiesVersion;
   129   f->toJson      = toJsonVersion;
   130   f->toJsonStr   = toJsonStrVersion;
   131   f->fromJson    = fromJsonVersion;
   132   f->fromJsonStr = fromJsonStrVersion;
   133 }
   134 
   135 void initiateAllocateVersion(versiont **self) {
   136 
   137   if (self) {
   138     (*self) = malloc(sizeof(versiont));
   139     if (*self) {
   140       initiateVersion(*self);
   141     }
   142   }
   143 }
   144 
   145 void finalizeVersion(void) {
   146 
   147   if (versionF) {
   148     free(versionF);
   149     versionF = NULL;
   150   }
   151 }
   152 
   153 versiont* allocVersion(const char *v) {
   154   versiont *r = NULL;
   155 
   156   initiateAllocateVersion(&r);
   157   parseVersion(r, v);
   158   ret r;
   159 }
   160 
   161 
   162 local void freeVersion(versiont *self) {
   163 
   164   free(self->originalVer);
   165   setToZero;
   166   freeG(&self->others);
   167   freeG(&self->prerelease);
   168   freeG(&self->build);
   169 }
   170 
   171 local void terminateVersion(versiont **self) {
   172 
   173   freeVersion(*self);
   174   finishVersion(self);
   175 }
   176 
   177 
   178 local char* toStringVersion(versiont *self) {
   179 
   180   char *r = NULL;
   181   r = formatS("%"PRIu64".%"PRIu64".%"PRIu64, self->release, self->major, self->minor);
   182   if (self->patch) {
   183     pushNFreeG(&r, formatS(".%"PRIu64, self->patch));
   184   }
   185   if (!isEmptyG(&self->others)) {
   186     var o = joinSG(&self->others, '.');
   187     pushNFreeG(&r, formatS(".%s", o));
   188     free(o);
   189   }
   190   if (!isEmptyG(&self->prerelease)) {
   191     #define labelString(label, delim) \
   192       iter(&self->label, c) {\
   193         if (isOSmallIntG(c) and iterIndexG(&self->label)) {\
   194           /* the left delimiter for numbers is . (DELIMITER) */\
   195           pushG(&r, DELIMITER);\
   196         }\
   197         else {\
   198           pushG(&r, delim);\
   199         }\
   200         var cs = toStringO(c);\
   201         pushNFreeG(&r, cs);\
   202       }
   203     labelString(prerelease, PR_DELIMITER);
   204   }
   205   if (!isEmptyG(&self->build)) {
   206     labelString(build, BD_DELIMITER);
   207   }
   208   ret r;
   209 }
   210 
   211 local versiont* duplicateVersion(versiont *self) {
   212 
   213   createAllocateVersion(dup);
   214   dup->originalVer        = dupG(self->originalVer);
   215   dup->empty              = self->empty;
   216   dup->mainComponentCount = self->mainComponentCount;
   217   dup->release            = self->release;
   218   dup->major              = self->major;
   219   dup->minor              = self->minor;
   220   dup->patch              = self->patch;
   221 
   222   smallArrayt *dp;
   223   #define dupComponents(label)\
   224   dp = dupG(&self->label);\
   225   setsoG(&dup->label, getsoG(dp));\
   226   finishG(dp)
   227   dupComponents(others);
   228   dupComponents(prerelease);
   229   dupComponents(build);
   230   ret dup;
   231 }
   232 
   233 local void smashVersion(versiont **self) {
   234 
   235   finishVersion(self);
   236 }
   237 
   238 #if NFreeStackCheck
   239 local void finishVersion(versiont **self) {
   240 
   241   register u64 rsp asm("rsp");
   242   if ((u64)*self > rsp) {
   243     logW("Probably trying to free a smallArray on stack: "BLD"%p"RST" sp: "BLD"%p"RST, *self, rsp);
   244     logBtrace;
   245   }
   246   else {
   247     free(*self);
   248     *self = NULL;
   249   }
   250 }
   251 #else
   252 // #if NFreeStackCheck
   253 local void finishVersion(versiont **self) {
   254 
   255   free(*self);
   256   *self = NULL;
   257 }
   258 #endif
   259 // #if NFreeStackCheck
   260 
   261 local const char* helpVersion(versiont UNUSED *self) {
   262   ret "TODO - version help";
   263 }
   264 
   265 
   266 
   267 
   268 local void logVersion(versiont *self) {
   269   var s = toStringVersion(self);
   270   puts(s);
   271   free(s);
   272 }
   273 
   274 
   275 /**
   276  * internal function parsing a version string
   277  *
   278  * version string format is Release.Major.minor.patch.others-prerelease+build
   279  *
   280  * when validate is set, self is not updated
   281  * when loose is set, incomplete version strings are accepted
   282  * set isCmp to true when parsing version in comparators
   283  */
   284 local bool parse(versiont *self UNUSED, const char *ver, bool validate, bool loose, bool isCmp) {
   285   if (isBlankG(ver)) ret false;
   286 
   287   // the code for the parser is in !validate blocks
   288   // the code interpreting an invalid version string as much as possible is in loose blocks
   289   // the code for version in compares is isCmp blocks (used in satisfiesVersion)
   290 
   291   // remove spaces and duplicate the string
   292   char *v      = trimS(ver);
   293 
   294   // main state
   295   u8 state     = START;
   296   // state for parsing others, prerelease and build
   297   u8 state2    = START;
   298 
   299   // for detecting state transition and empty components
   300   u8 prevState;
   301 
   302   // leading 0 are not accepted for any version number
   303   bool leading0 = false;
   304   // allow prerelease and build anywhere (0-pre) when true
   305   bool release0 = loose;
   306 
   307   // curs is cursor in version string
   308   // component is the start of a component string
   309   char *curs, *component;
   310   /* lv(isCmp); */
   311   range(i, strlen(v)) {
   312     curs = &v[i];
   313     /* lv(i); */
   314     /* lv(STATE[state]); */
   315     /* lv(STATE[state2]); */
   316     /* lv(leading0); */
   317     /* putchar(v[i]); */
   318     /* put */
   319 
   320     // fail when a character is invalid
   321     if (!isValidChar(v[i])) goto fail;
   322     switch(state) {
   323       case START:
   324         // find the start of the version string
   325         if (!isdigit(v[i]) and v[i] != 'x' and v[i] != 'X' and v[i] != '*') {
   326           // ignore characters in front of release version when loose
   327           if (loose) continue; else goto fail;
   328         }
   329         state     = REL;
   330         prevState = state;
   331         leading0  = v[i] == '0' ? true : false;
   332         // dont set self when validating
   333         if (!validate) component = curs;
   334         break;
   335       case REL:
   336         if (isCmp and (v[i-1] == 'x' or v[i-1] == 'X' or v[i-1] == '*')) {
   337           // set component to -1 when an any character is detected in a comparator
   338           #define validStrictNumIsCmp(comp, delimIndex, nstate)\
   339             self->comp = -1;\
   340             prevState = state;\
   341             /* must be x. or x- or x+ or x */\
   342             /* when delimiter is at the end of the string, parsing is finished */\
   343             if (!v[i+delimIndex] /* avoid reading outside the buffer */ or (hasG(DELIMITERS, v[i+delimIndex]) and !v[i+delimIndex+1])) goto parsingFinish;\
   344             if (!delimIndex) switch(v[i+delimIndex]) {\
   345               case '.':\
   346                 state = nstate;\
   347                 break;\
   348               case '-':\
   349                 state = PRE;\
   350                 break;\
   351               case '+':\
   352                 state = BUILD;\
   353                 break;\
   354               default:\
   355                 goto fail;\
   356             }\
   357             break;
   358           validStrictNumIsCmp(release, 0/*delimIndex*/, MAJOR);
   359         }
   360         // any characters are not accepted in normal version strings
   361         if (!isCmp and (v[i-1] == 'x' or v[i-1] == 'X' or v[i-1] == '*')) goto fail;
   362         #define parseNumber(comp) \
   363           /* state switch, point to next component */\
   364           if (!validate) {\
   365             char c = *curs;\
   366             *curs  = 0;\
   367             if (!isCmp or !hasG(ANYTOKENS, v[i-1])) self->comp = parseI64(component);\
   368             *curs  = c;\
   369             component = curs+1;\
   370             self->mainComponentCount++;\
   371           }
   372         #define validStrictNum(comp, nextState, transCond, code, leadingCond)\
   373           if (isCmp) {\
   374             if (hasG(ANYTOKENS, v[i])) {\
   375               /* multiple separator following each other is not accepted */\
   376               if (v[i-1] != '.') goto fail;\
   377               if (loose) {\
   378                 /* set component to -1 when an any character is detected in a comparator */\
   379                 validStrictNumIsCmp(comp, 1 /*delimIndex*/, nextState)\
   380               }\
   381             }\
   382           }\
   383           /* any characters are not accepted in normal version strings */\
   384           elif (!isCmp and (v[i] == 'x' or v[i] == 'X' or v[i] == '*')) goto fail;\
   385           u8 UNIQVAR(nextSte) = nextState;\
   386           if (!isdigit(v[i])) {\
   387             /* end of component detected */\
   388             /* no empty components */\
   389             if (i and hasG(DELIMITERS, v[i-1])) goto fail;\
   390             if (i == 1 and leading0) {\
   391               /* prerelease 0 allow prerelease and build components anywhere*/\
   392               if   (v[i] == PR_DELIMITER[0]) {\
   393                 release0 = true;\
   394                 state    = PRE;\
   395                 parseNumber(comp);\
   396                 break;\
   397               }\
   398               elif (v[i] == BD_DELIMITER[0]) {\
   399                 release0 = true;\
   400                 state    = BUILD;\
   401                 parseNumber(comp);\
   402                 break;\
   403               }\
   404             }\
   405             if (release0) {\
   406               /* prerelease 0 allow prerelease and build components anywhere*/\
   407               if   (v[i] == PR_DELIMITER[0]) {\
   408                 state    = PRE;\
   409                 parseNumber(comp);\
   410                 break;\
   411               }\
   412               elif (v[i] == BD_DELIMITER[0]) {\
   413                 state    = BUILD;\
   414                 parseNumber(comp);\
   415                 break;\
   416               }\
   417             }\
   418             /* end of release version or invalid, must have one digit */\
   419             if ((prevState != state) or (v[i] and (v[i] != DELIMITER[0] transCond))) goto fail;\
   420             code;\
   421             /* parse component and go to next state */\
   422             prevState = state;\
   423             state     = UNIQVAR(nextSte);\
   424             leading0  = v[i+1] == '0' ? true : false;\
   425             /* parse numbers only, not any tokens "xX*" */\
   426             parseNumber(comp);\
   427             break;\
   428           }\
   429           /* search end of component */\
   430           /* no leading 0 */\
   431           if (leading0 and v[i-1] != DELIMITER[0] leadingCond) goto fail;\
   432           /* not in state transition anymore */\
   433           prevState = state;
   434         // set release0 to true when loose to allow incomplete versions, the missing numbers are set to 0
   435         validStrictNum(release, MAJOR,/*transCond*/,if (loose) release0 = true; else release0 = leading0 ? true : false,/*leadingCond*/);
   436         break;
   437       case MAJOR:
   438         validStrictNum(major, MINOR,/*transCond*/,/*code*/,/*leadingCond*/);
   439         break;
   440       case MINOR:
   441         validStrictNum(minor, PATCH, and v[i] != PR_DELIMITER[0] and v[i] != BD_DELIMITER[0] /*transCond*/, if (v[i] == PR_DELIMITER[0]) UNIQVAR(nextSte) = PRE; /*code*/, and v[i-1] != PR_DELIMITER[0] and v[i-1] != BD_DELIMITER[0] /*leadingCond*/);
   442         break;
   443       case PATCH:
   444         // check if prerelease only when the state was changed
   445         if (prevState != state and v[i-1] == PR_DELIMITER[0]) {
   446           prevState = state;
   447           state     = PRE;
   448           if (!validate) component = curs;
   449           i--;
   450           break;
   451         }
   452         validStrictNum(patch, OTHERS, and v[i] != PR_DELIMITER[0] and v[i] != BD_DELIMITER[0] /*transCond*/, if (v[i] == PR_DELIMITER[0]) UNIQVAR(nextSte) = PRE; /*code*/, and v[i-1] != PR_DELIMITER[0] and v[i-1] != BD_DELIMITER[0] /*leadingCond*/);
   453         break;
   454       case OTHERS:
   455         // no empty components
   456         if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail;
   457         #define switchLabel(delim, nextState, label) \
   458           if (v[i] == delim) {\
   459             prevState = state;\
   460             state     = nextState;\
   461             if (!validate) {\
   462               /* parse number or string or any character */\
   463               char c = *curs;\
   464               *curs  = 0;\
   465               if (state2 == NUMBER) {\
   466                 pushG(&self->label, parseI64(component));\
   467               }\
   468               elif (state2 == CSTRING) {\
   469                 pushG(&self->label, component);\
   470               }\
   471               elif (state2 == ANY) {\
   472                 pushG(&self->label, -1);\
   473               }\
   474               *curs     = c;\
   475               state2    = START;\
   476               component = curs+1;\
   477             }\
   478             break;\
   479           }
   480         #define validStrictLabel(label)\
   481           /* after state transition, select prerelease state, stored in state2 */\
   482           if (hasG(DELIMITERS, v[i-1])) {\
   483             if (!validate and prevState == state) {\
   484               char c = *(curs-1);\
   485               /* dont parse just after a state switch since the component token doesn't exist */\
   486               *(curs-1) = 0;\
   487               if (state2 == NUMBER) {\
   488                 pushG(&self->label, parseI64(component));\
   489               }\
   490               elif (state2 == CSTRING) {\
   491                 pushG(&self->label, component);\
   492               }\
   493               elif (state2 == ANY) {\
   494                 pushG(&self->label, -1);\
   495               }\
   496               *(curs-1) = c;\
   497               component = curs;\
   498             }\
   499             if (isCmp and (v[i] == 'x' or v[i] == 'X' or v[i] == '*')) {\
   500               /* must be x. or x- or x+ or x */\
   501               state2 = ANY;\
   502             }\
   503             else {\
   504               state2   = !isdigit(v[i]) ? CSTRING : NUMBER;\
   505             }\
   506             leading0 = v[i] == '0' ? true : false;\
   507           }\
   508           else {\
   509             /* in a component */\
   510             if (state2 == NUMBER and !hasG(DELIMITERS, v[i])) {\
   511               /* number must have only digit */\
   512               if (!isdigit(v[i])) goto fail;\
   513               /* no leading 0 in numbers */\
   514               if (leading0) goto fail;\
   515             }\
   516           }\
   517           prevState = state
   518 
   519         switchLabel(PR_DELIMITER[0], PRE, others);
   520         switchLabel(BD_DELIMITER[0], BUILD, others);
   521         validStrictLabel(others);
   522         break;
   523       case PRE:
   524         // no empty components
   525         if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail;
   526         switchLabel(BD_DELIMITER[0], BUILD, prerelease);
   527         validStrictLabel(prerelease);
   528         break;
   529       case BUILD:
   530         if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail;
   531         validStrictLabel(build);
   532         break;
   533     }
   534   }
   535   parsingFinish:
   536 
   537   /* the version string has been parsed */
   538   self->empty = false;
   539 
   540   /* lv(STATE[state]); */
   541   /* lv(STATE[prevState]); */
   542   /* lv(STATE[state2]); */
   543   /* lv(leading0); */
   544 
   545   // MUST NOT end with empty component
   546   if (loose) {
   547     if (hasG(DELIMITER, *curs)) goto fail;
   548     prevState = state;
   549 
   550     if (isCmp) {
   551       // handle x alone "x" to match any version, signaled with self->empty=true
   552       if (state == REL and (hasG(ANYTOKENS, *curs) or (curs != v and hasG(ANYTOKENS, *(curs-1))))) {
   553         self->empty = true;
   554         goto end;
   555       }
   556       // handle x in components
   557       if (state == MAJOR and hasG(DELIMITERS, *(curs+1)) and hasG(ANYTOKENS, *curs)) {
   558         // ignore last - or + for compares
   559         self->major = -1;
   560         goto end;
   561       }
   562       // if last component has an any token, it doesn't need to be parsed
   563       if (hasG(ANYTOKENS, component) and state < OTHERS) goto end;
   564     }
   565   }
   566   else {
   567     if (hasG(DELIMITERS, curs)) goto fail;
   568   }
   569 
   570   // check for incomplete components
   571   if (prevState != state) goto fail;
   572 
   573   // versions must have 3 components (except when loose is set) Release.Major.minor
   574   if (state < MINOR and !release0) goto fail;
   575 
   576   if (!validate) {
   577     // parse the last component
   578     switch(state) {
   579       case REL:
   580         self->release = parseI64(component);
   581         self->mainComponentCount++;
   582         break;
   583       case MAJOR:
   584         self->major = parseI64(component);
   585         self->mainComponentCount++;
   586         break;
   587       case MINOR:
   588         self->minor = parseI64(component);
   589         self->mainComponentCount++;
   590         break;
   591       case PATCH:
   592         self->patch = parseI64(component);
   593         self->mainComponentCount++;
   594         break;
   595       case OTHERS:
   596         #define lastComponent(label)\
   597         if (state2 == NUMBER) {\
   598           pushG(&self->label, parseI64(component));\
   599         }\
   600         elif (state2 == CSTRING) {\
   601           pushG(&self->label, component);\
   602         }\
   603         elif (state2 == ANY) {\
   604           pushG(&self->label, -1);\
   605         }
   606         lastComponent(others);
   607         break;
   608       case PRE:
   609         lastComponent(prerelease);
   610         break;
   611       case BUILD:
   612         lastComponent(build);
   613         break;
   614     }
   615   }
   616 
   617   end:
   618   free(v);
   619   ret true;
   620 
   621   fail:
   622   free(v);
   623   ret false;
   624 }
   625 
   626 local bool validVersion(versiont *self UNUSED, const char *v) {
   627   ret parse(self, (char*)v, true /*validate*/, false /*loose*/, false /*isCmp*/);
   628 }
   629 
   630 local bool parseCmpVersion(versiont *self, const char *v, bool isCmp) {
   631   if (!v) ret false;
   632 
   633   freeVersion(self);
   634   self->originalVer = strdup(v);
   635 
   636   if (!cleanVersion(self, self->originalVer)) ret false;
   637 
   638   ret parse(self, self->originalVer, false /*validate*/, true /*loose*/, isCmp);
   639 }
   640 
   641 local bool parseVersion(versiont *self, const char *v) {
   642   ret parseCmpVersion(self, v, false /*isCmp*/);
   643 }
   644 
   645 local bool parseStrictVersion(versiont *self, const char *v) {
   646 
   647   if (!v) ret false;
   648 
   649   freeVersion(self);
   650   self->originalVer = strdup(v);
   651 
   652   if (!cleanVersion(self, self->originalVer) or !validVersion(self, self->originalVer)) ret false;
   653 
   654   ret parse(self, self->originalVer, false /*validate*/, false /*loose*/, false /*isCmp*/);
   655 }
   656 
   657 local bool cleanVersion(versiont *self UNUSED, char *v) {
   658   if (!v) ret false;
   659 
   660   size_t dest = 0;
   661   // keep only valid characters
   662   range(i, strlen(v)) {
   663     if (isValidChar(v[i])) {
   664       if (dest < i) {
   665         v[dest] = v[i];
   666       }
   667       dest++;
   668     }
   669   }
   670 
   671   // terminate string
   672   v[dest] = 0;
   673 
   674   ret true;
   675 }
   676 
   677 local bool equalVersion(versiont *self, versiont *ver) {
   678   if (!ver) ret false;
   679 
   680   #define eqComp(comp) if (self->comp != ver->comp) ret false
   681   eqComp(release);
   682   eqComp(major);
   683   eqComp(minor);
   684   eqComp(patch);
   685   #define eqLabel(label) if (!eqG(&self->label, &ver->label)) ret false
   686   eqLabel(others);
   687   eqLabel(prerelease);
   688   eqLabel(build);
   689 
   690   ret true;
   691 }
   692 
   693 local bool equalSVersion(versiont *self, const char *v) {
   694   if (!v) ret false;
   695 
   696   bool r = false;
   697   createVersion(ver);
   698   r = parseO(&ver, v);
   699   if (!r) /* error parsing*/ goto end;
   700 
   701   r = equalVersion(self, &ver);
   702 
   703   end:
   704   freeO(&ver);
   705   ret r;
   706 }
   707 
   708 local bool equalSSVersion(versiont *self UNUSED, const char *v1, const char *v2) {
   709   if (!v1 or !v2) ret false;
   710 
   711   bool r = false;
   712   createVersion(ver1);
   713   r = parseO(&ver1, v1);
   714   if (!r) /* error parsing*/ ret false;
   715   createVersion(ver2);
   716   r = parseO(&ver2, v2);
   717   if (!r) /* error parsing*/ ret false;
   718 
   719   r = eqO(&ver1, &ver2);
   720 
   721   freeO(&ver1);
   722   freeO(&ver2);
   723   ret r;
   724 }
   725 
   726 local int cmpVersion(versiont *self, versiont *ver) {
   727   if (!ver) ret 0;
   728 
   729   int r = 0;
   730 
   731   #define cmpComp(comp)\
   732     r = CMP(self->comp, ver->comp);\
   733     if (r) ret r;
   734   cmpComp(release);
   735   cmpComp(major);
   736   cmpComp(minor);
   737   cmpComp(patch);
   738 
   739   if (lenG(&self->others) > lenG(&ver->others)) {
   740     #define cmpLabel(label, x, y, returnSign)\
   741       iter(&x->label, c) {\
   742         if (isOSmallIntG(c) and isEIntG(&y->label, iterIndexG(&x->label))) {\
   743           /* both ints */\
   744           var a = getG(&y->label, rtI64, iterIndexG(&x->label));\
   745           cast(smallIntt*, b, c);\
   746           r = cmpOrder(a, getValG(b));\
   747           if (r) ret r;\
   748         }\
   749         elif (!isOSmallIntG(c) and !isEIntG(&y->label, iterIndexG(&x->label))) {\
   750           /* both strings */\
   751           var a = getG(&y->label, rtChar, iterIndexG(&x->label));\
   752           r = scmpOrder(a, ssGet(c));\
   753           if (r) ret r;\
   754         }\
   755         else {\
   756           /* one is int and the other is string */\
   757           baset *ao = getG(&y->label, rtBaset, iterIndexG(&x->label));\
   758           char *a = toStringO(ao);\
   759           finishG(ao);\
   760           char *b = toStringO(c);\
   761           r = scmpOrder(a,b);\
   762           freeManyS(a,b);\
   763           if (r) ret r;\
   764         }\
   765       }\
   766       /* y has components left, so it is greater than x */\
   767       if (lenG(&y->label) > lenG(&x->label)) ret 1 * returnSign;\
   768       if (lenG(&y->label) < lenG(&x->label)) ret -1 * returnSign
   769     #define cmpOrder(a,b) CMP(a,b)
   770     #define scmpOrder(a,b) strcmp(a,b)
   771     cmpLabel(others, ver, self, 1 /*returnSign*/);
   772     #undef cmpOrder
   773     #undef scmpOrder
   774   }
   775   else {
   776     #define cmpOrder(a,b) CMP(b,a)
   777     #define scmpOrder(a,b) strcmp(b,a)
   778     cmpLabel(others, self, ver, -1 /*returnSign*/);
   779     #undef cmpOrder
   780     #undef scmpOrder
   781   }
   782   // prereleases have lower precedence
   783   if (!lenG(&self->prerelease) and lenG(&ver->prerelease)) ret 1;
   784   if (lenG(&self->prerelease) and !lenG(&ver->prerelease)) ret -1;
   785   // self and ver are both normal versions or both prereleases
   786   if (lenG(&self->prerelease) > lenG(&ver->prerelease)) {
   787     #define cmpOrder(a,b) CMP(a,b)
   788     #define scmpOrder(a,b) strcmp(a,b)
   789     cmpLabel(prerelease, ver, self, 1 /*returnSign*/);
   790     #undef cmpOrder
   791     #undef scmpOrder
   792   }
   793   else {
   794     #define cmpOrder(a,b) CMP(b,a)
   795     #define scmpOrder(a,b) strcmp(b,a)
   796     cmpLabel(prerelease, self, ver, -1 /*returnSign*/);
   797     #undef cmpOrder
   798     #undef scmpOrder
   799   }
   800   if (lenG(&self->build) > lenG(&ver->build)) {
   801     #define cmpOrder(a,b) CMP(a,b)
   802     #define scmpOrder(a,b) strcmp(a,b)
   803     cmpLabel(build, ver, self, 1 /*returnSign*/);
   804     #undef cmpOrder
   805     #undef scmpOrder
   806   }
   807   else {
   808     #define cmpOrder(a,b) CMP(b,a)
   809     #define scmpOrder(a,b) strcmp(b,a)
   810     cmpLabel(build, self, ver, -1 /*returnSign*/);
   811     #undef cmpOrder
   812     #undef scmpOrder
   813   }
   814 
   815   ret r;
   816 }
   817 
   818 local int  cmpSVersion(versiont *self, const char *v) {
   819   if (!v) ret 0;
   820 
   821   int r = 0;
   822   createVersion(ver);
   823   r = parseO(&ver, v);
   824   if (!r) /* error parsing*/ ret 0;
   825 
   826   r = cmpVersion(self, &ver);
   827 
   828   freeO(&ver);
   829   ret r;
   830 }
   831 
   832 local int  cmpSSVersion(versiont *self UNUSED, const char *v1, const char *v2) {
   833   if (!v1 or !v2) ret 0;
   834 
   835   int r = 0;
   836   createVersion(ver1);
   837   r = parseO(&ver1, v1);
   838   if (!r) /* error parsing*/ ret 0;
   839   createVersion(ver2);
   840   r = parseO(&ver2, v2);
   841   if (!r) /* error parsing*/ ret 0;
   842 
   843   r = cmpO(&ver1, &ver2);
   844 
   845   freeO(&ver1);
   846   freeO(&ver2);
   847   ret r;
   848 }
   849 
   850 local bool incrVersion(versiont *self, u32 level) {
   851   switch(level) {
   852     case releaseVer:
   853       self->release++;
   854       break;
   855     case majorVer:
   856       self->major++;
   857       // Patch and minor version MUST be reset to 0 when major version is incremented
   858       self->minor = 0;
   859       self->patch = 0;
   860       emptyG(&self->others);
   861       emptyG(&self->prerelease);
   862       emptyG(&self->build);
   863       break;
   864     case minorVer:
   865       self->minor++;
   866       // Patch version MUST be reset to 0 when major version is incremented
   867       self->patch = 0;
   868       emptyG(&self->others);
   869       emptyG(&self->prerelease);
   870       emptyG(&self->build);
   871       break;
   872     case patchVer:
   873       self->patch++;
   874       break;
   875     default:
   876       level -= 4;
   877       if (lenG(&self->others)) {
   878         baset *c = getG(&self->others, rtBaset, level);
   879         if (!c) {
   880           // out of range
   881           level -= lenG(&self->others);
   882           if (lenG(&self->prerelease)) goto prerel;
   883           if (lenG(&self->build)) goto buildl;
   884           ret false;
   885         }
   886         #define incC\
   887           if (!isOSmallInt(c)) {\
   888             finishG(c);\
   889             ret false;\
   890           }\
   891           cast(smallIntt*, v, c);\
   892           (*getPG(v))++;\
   893           finishG(c)
   894         incC;
   895       }
   896       elif (lenG(&self->prerelease)) {
   897         prerel:;
   898         baset *c = getG(&self->prerelease, rtBaset, level);
   899         if (!c) {
   900           // out of range
   901           level -= lenG(&self->prerelease);
   902           if (lenG(&self->build)) goto buildl;
   903           ret false;
   904         }
   905         incC;
   906       }
   907       elif (lenG(&self->build)) {
   908         buildl:;
   909         baset *c = getG(&self->build, rtBaset, level);
   910         if (!c) {
   911           // out of range
   912           ret false;
   913         }
   914         incC;
   915       }
   916   }
   917   ret true;
   918 }
   919 
   920 local int sortVer(const void * A, const void * B) {
   921   castS(a, A);
   922   castS(b, B);
   923   createVersion(va);
   924   parseO(&va, ssGet(a));
   925   createVersion(vb);
   926   parseO(&vb, ssGet(b));
   927 
   928   int r = cmpO(&va, &vb);
   929 
   930   freeO(&va);
   931   freeO(&vb);
   932 
   933   ret r;
   934 }
   935 
   936 local smallArrayt* sortVersion(versiont *self UNUSED, smallArrayt *vlist) {
   937   if (!vlist and !isOSmallArray(vlist)) ret NULL;
   938 
   939   createAllocateSmallArray(r);
   940   createVersion(v);
   941 
   942   iter(vlist, VS) {
   943     if (!isOSmallString(VS)) continue;
   944     // keep only valid version string
   945     if (!parseO(&v, ssGet(VS))) continue;
   946     pushG(r, ssGet(VS));
   947   }
   948 
   949   freeO(&v);
   950 
   951   sortFG(r, sortVer);
   952   ret r;
   953 }
   954 
   955 
   956 
   957 local bool satisfiesVersion(versiont *self, const char *vrange) {
   958   if (!vrange) ret false;
   959 
   960   if (isBlankG(vrange)) {
   961     /* range string is empty, match anything */
   962     ret true;
   963   }
   964 
   965   bool r = false;
   966 
   967   enum {SEARCH, SEARCH_OR, SEARCH_OR_END, EQ, GT, GTE, LT, LTE, CMP_LAST,
   968         /*hyphen state*/ SPACE, HYPHEN, HYPHEN_CHAR, HYPHEN_SEARCH, HYPHEN_LAST,
   969         TILDE,
   970         CARET};
   971   const char* STATE[] UNUSED = {"SEARCH", "SEARCH_OR", "SEARCH_OR_END", "EQ", "GT", "GTE", "LT", "LTE", "CMP_LAST",
   972                          /*hyphen state*/ "SPACE", "HYPHEN", "HYPHEN_CHAR", "HYPHEN_SEARCH", "HYPHEN_LAST",
   973                          "TILDE",
   974                          "CARET"};
   975   u8 state   = SEARCH;
   976   // First or second comparator in range: >=ver1 <ver2
   977   enum {FIRST, LAST, NONE};
   978   const char*CMPTOR[] UNUSED = {"FIRST", "LAST", "NONE"};
   979   u8 cmptor  = NONE;
   980   char *vr   = dupG(vrange);
   981   char *c    = vr;
   982   char *vers = NULL;
   983 
   984   enum {NOP, CMP_EQ, CMP_LT, CMP_LTE, CMP_GT, CMP_GTE,
   985         HYPHEN_GTE, HYPHEN_LT,
   986         CMP_XRANGE,
   987         CMP_TILDE,
   988         CMP_CARET};
   989   const char* OPTYPE[] UNUSED = {"NOP", "CMP_EQ", "CMP_LT", "CMP_LTE", "CMP_GT", "CMP_GTE",
   990         "HYPHEN_GTE", "HYPHEN_LT",
   991         "CMP_XRANGE",
   992         "CMP_TILDE",
   993         "CMP_CARET"};
   994 
   995   // parsed comparator
   996   struct {
   997     u8 type;
   998     versiont v;
   999   } cmp;
  1000   // comparator results
  1001   bool res[2];
  1002 
  1003   cmp.type = NOP;
  1004   initiateVersion(&cmp.v);
  1005 
  1006   void doLT(void) {
  1007     logI("<");
  1008 
  1009     #define parseCmpVer(str, freeV)\
  1010       if (!parseCmpVersion(&v, str, true /*isCmp*/)) {\
  1011         r = false;\
  1012         freeV;\
  1013         goto end;\
  1014       }
  1015     #define anyValue(comp) if ((i64)v.comp == -1) v.comp = self->comp;
  1016     #define anyLabel(label)\
  1017       iter(&v.label, o) {\
  1018         if (isOSmallInt(o)) {\
  1019           cast(smallIntt*, i, o);\
  1020           if (getValG(i) == -1) {\
  1021             baset *val = getG(&self->label, rtBaset, iterIndexG(&v.label));\
  1022             if (val) {\
  1023               setNFreeG(&v.label, iterIndexG(&v.label), dupG(val));\
  1024             }\
  1025           }\
  1026         }\
  1027       }
  1028     #define anyVer\
  1029       anyValue(release);\
  1030       anyValue(major);\
  1031       anyValue(minor);\
  1032       anyValue(patch);\
  1033       anyLabel(others);\
  1034       anyLabel(prerelease);\
  1035       anyLabel(build)
  1036 
  1037     // TODO assign any in LT LTE GT GTE
  1038     #define doComp(comp) if (self->comp != v.comp) { res[cmptor] = false; freeO(&v); goto end;}
  1039     #define doLabel(label) if (!eqG(&self->label, &v.label)) { res[cmptor] = false; freeO(&v); goto end;}
  1040     #define genVerForCmp(compareResult, checkEqual, isLT)\
  1041       /* when there is -, prereleases are not matched */\
  1042       /* when there is no -, prereleases are matched */\
  1043       char *cver;\
  1044       char *sl = sliceS(vers, 0, -1);\
  1045       cver = strdup(vers);\
  1046       createVersion(v);\
  1047       if (!hasG(sl, '-') and *(c-1) != '-') {\
  1048         /* not selecting a prerelease and not - at the end */\
  1049         /* self MUST be a normal version */\
  1050         if (isLT and !isEmptyG(&self->prerelease)) { res[cmptor] = false; goto end;}\
  1051         if (checkEqual) {res[cmptor] = true; goto end;}\
  1052         if (isLT) pushG(&cver, "-0"); /* for GT: dont add -0 so that prereleases are not selected */\
  1053         if (!isLT and !isEmptyG(&self->prerelease)) {\
  1054           /* select normal versions only for GT ops */\
  1055           res[cmptor] = false; goto end;\
  1056       }\
  1057       parseCmpVer(cver,);\
  1058       anyVer;\
  1059       }\
  1060       else {\
  1061         if (!isEmptyG(&self->prerelease)) {\
  1062           /* consider prerelease of version in the compare only */\
  1063           parseCmpVer(cver,);\
  1064           anyVer;\
  1065           if (self->release != v.release) { res[cmptor] = false; freeO(&v); goto end;}\
  1066           doComp(release);\
  1067           doComp(major);\
  1068           doComp(minor);\
  1069           doComp(patch);\
  1070           doLabel(others);\
  1071         }\
  1072         if (!isLT and !hasG(sl, '-') and *(c-1) == '-') pushG(&cver, "0"); /* for GT: add -0 so that prereleases are selected */\
  1073         parseCmpVer(cver,);\
  1074       }\
  1075       int R = cmpVersion(self, &v);\
  1076       compareResult\
  1077       end:\
  1078       freeO(&v);\
  1079       free(sl);\
  1080       free(cver);
  1081     genVerForCmp(if (R < 0) res[cmptor] = true; else res[cmptor] = false;, false/*checkEqual*/, true/*isLT*/);
  1082   }
  1083 
  1084   void doLTE(void) {
  1085     logI("<=");
  1086 
  1087     genVerForCmp(if (R <= 0) res[cmptor] = true; else res[cmptor] = false;, equalSVersion(self, cver)/*checkEqual*/, true/*isLT*/);
  1088   }
  1089 
  1090   void doGT(void) {
  1091     logI(">");
  1092 
  1093     genVerForCmp(if (R > 0) res[cmptor] = true; else res[cmptor] = false;, false/*checkEqual*/, false/*isLT*/);
  1094   }
  1095 
  1096   void doGTE(void) {
  1097     logI(">=");
  1098 
  1099     genVerForCmp(if (R >= 0) res[cmptor] = true; else res[cmptor] = false;, equalSVersion(self, cver)/*checkEqual*/, false/*isLT*/);
  1100   }
  1101 
  1102   void doEq(void) {
  1103     createVersion(v);
  1104     parseCmpVer(vers,);
  1105     if (v.empty) {
  1106       // v is any version, eq always matches self
  1107       r = true;
  1108       goto end;
  1109     }
  1110     // check if it is x range
  1111     // maybe any component is at the end
  1112     anyValue(release);
  1113     char c = getG(vers,unusedV,-1);
  1114     if (c == 'x' or c == 'X' or c == '*') {
  1115       #define xrange(high, relOrPreOrBuild)\
  1116         v.minor  = 0;\
  1117         vers     = toStringO(&v);\
  1118         relOrPreOrBuild;\
  1119         cmptor   = FIRST;\
  1120         doGTE();\
  1121         free(vers);\
  1122         v.high++;\
  1123         vers     = toStringO(&v);\
  1124         cmptor   = LAST;\
  1125         doLT();\
  1126         free(vers)
  1127       #define xrangeRelOrPreOrBuild(relOrPreOrBuild)\
  1128         if ((i64)v.major != -1 and (i64)v.minor == -1) {\
  1129           xrange(major, relOrPreOrBuild);\
  1130         }\
  1131         elif ((v.minor == 0 or (i64)v.minor == -1) and (i64)v.major == -1) {\
  1132           v.major = 0;\
  1133           xrange(release, relOrPreOrBuild);\
  1134         }
  1135       xrangeRelOrPreOrBuild();
  1136 
  1137       #define xrangeIsDetected\
  1138         cmp.type = CMP_XRANGE;\
  1139         cmptor   = LAST;\
  1140         goto end
  1141       xrangeIsDetected;
  1142     }
  1143     elif (c == '-') {
  1144       c = getG(vers,unusedV,-2);
  1145       if (c == 'x' or c == 'X' or c == '*') {
  1146         xrangeRelOrPreOrBuild(pushG(&vers, '-'));
  1147         if (res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) {
  1148           // prerelease on lower bound of xrange is accepted when - is at the end of comparator
  1149           res[LAST] = true;
  1150         }
  1151         xrangeIsDetected;
  1152       }
  1153     }
  1154     elif (c == '+') {
  1155       c = getG(vers,unusedV,-2);
  1156       if (c == 'x' or c == 'X' or c == '*') {
  1157         xrangeRelOrPreOrBuild();
  1158         xrangeIsDetected;
  1159       }
  1160     }
  1161     anyVer;
  1162     r = res[0] = res[1] = equalVersion(self, &v);
  1163     logP(BLD WHT"Cmptor result: %b"RST, r);
  1164     end:
  1165     freeO(&v);
  1166   }
  1167 
  1168   i16 index = 0;
  1169   while(*c) {
  1170     /* lv(STATE[state]); */
  1171     /* lv(CMPTOR[cmptor]); */
  1172     /* lv(index); */
  1173     /* lv(res[cmptor]); */
  1174 
  1175     if (*c == '|' and (*(c+1) == '|')) {
  1176       /* lv(STATE[state]); */
  1177       /* lv(CMPTOR[cmptor]); */
  1178       /* lv(OPTYPE[cmp.type]); */
  1179       if (cmptor == FIRST) {
  1180         r = res[0];
  1181         logP(BLD WHT"Cmptor result: %b"RST, r);
  1182         if (r) /* matching, stop. */ goto end;
  1183         cmptor = NONE;
  1184       }
  1185       state  = SEARCH_OR_END;
  1186     }
  1187     switch(state) {
  1188       #define skipSpace if (isspace(*c)) break
  1189       #define switchCmptor\
  1190         if (cmptor == LAST) goto fail;\
  1191         if (cmptor == FIRST) cmptor = LAST;\
  1192         if (cmptor == NONE) {\
  1193           /* reset results */\
  1194           res[0] = res[1] = true;\
  1195           cmptor = FIRST;\
  1196         }
  1197       case EQ:
  1198       case TILDE:
  1199       case CARET:
  1200         cmp_eq:
  1201         if (isspace(*c)) {
  1202           state = CMP_LAST;
  1203           goto cmp_last;
  1204         }
  1205         break;
  1206       case LT:
  1207       case LTE:
  1208       case GT:
  1209       case GTE:
  1210         if (!vers) {
  1211           skipSpace;
  1212           if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') vers = c;
  1213           break;
  1214         }
  1215         if (isspace(*c)) {
  1216           state = CMP_LAST;
  1217           goto cmp_last;
  1218         }
  1219         break;
  1220       case HYPHEN_CHAR:
  1221         if (*c == '-') state = HYPHEN_SEARCH;
  1222         break;
  1223       case HYPHEN:
  1224         if (isspace(*c)) {
  1225           state = HYPHEN_LAST;
  1226           goto hyphen_last;
  1227         }
  1228         break;
  1229       case HYPHEN_SEARCH:
  1230         skipSpace;
  1231         if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') {
  1232           switchCmptor;
  1233           cmp.type = HYPHEN_LT;
  1234           vers     = c;
  1235           state    = HYPHEN;
  1236           break;
  1237         }
  1238         goto fail;
  1239         break;
  1240       case SEARCH:
  1241         skipSpace;
  1242         // any must be x, X or * in a component
  1243         if ((*c == 'x' or *c == 'X' or *c == '*') and (*(c+1) != '.' and *(c+1) != '-' and *(c+1) != '+') and *(c+1) != ' ' and *(c+1) != 0) goto fail;
  1244         if (*c == '=') {
  1245           cmp.type = CMP_EQ;
  1246           vers     = c+1;
  1247           state    = EQ;
  1248           //allow equal in comparators - if (cmptor != NONE) goto fail;
  1249           cmptor   = LAST;
  1250           break;
  1251         }
  1252         if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') {
  1253           // scan further to distinguish between equal hyphen range
  1254           char *sc = c+1;
  1255           bool isHyphen = false;
  1256           int hyphenState = SEARCH;
  1257           do {
  1258             if (*sc == '|' and *(sc+1) == '|') /*end of comparator*/ break;
  1259             if (hyphenState == SPACE and *sc == '-') {
  1260               isHyphen = true;
  1261               break;
  1262             }
  1263             if (hyphenState == SEARCH and isspace(*sc)) hyphenState = SPACE;
  1264           } while(*(sc++));
  1265           if (isHyphen) {
  1266             cmp.type = HYPHEN_GTE;
  1267             vers     = c;
  1268             state    = HYPHEN;
  1269             /* reset results */
  1270             res[0] = res[1] = true;
  1271             cmptor   = FIRST;
  1272             break;
  1273           }
  1274           else {
  1275             cmp.type = CMP_EQ;
  1276             vers     = c;
  1277             state    = EQ;
  1278             //allow equal in comparators - if (cmptor != NONE) goto fail;
  1279             cmptor   = LAST;
  1280             goto cmp_eq;
  1281           }
  1282         }
  1283         if (*c == '>') {
  1284           switchCmptor;
  1285           vers = NULL;
  1286           if (*(c+1) == '=') {
  1287             cmp.type = CMP_GTE;
  1288             state    = GTE;
  1289           }
  1290           else {
  1291             cmp.type = CMP_GT;
  1292             state    = GT;
  1293           }
  1294           break;
  1295         }
  1296         if (*c == '<') {
  1297           switchCmptor;
  1298           vers = NULL;
  1299           if (*(c+1) == '=') {
  1300             cmp.type = CMP_LTE;
  1301             state    = LTE;
  1302           }
  1303           else {
  1304             cmp.type = CMP_LT;
  1305             state    = LT;
  1306           }
  1307           break;
  1308         }
  1309         if (*c == '~') {
  1310           cmp.type = CMP_TILDE;
  1311           vers     = c+1;
  1312           state    = TILDE;
  1313           cmptor   = LAST;
  1314           break;
  1315         }
  1316         if (*c == '^') {
  1317           cmp.type = CMP_CARET;
  1318           vers     = c+1;
  1319           state    = CARET;
  1320           cmptor   = LAST;
  1321           break;
  1322         }
  1323         goto fail;
  1324         break;
  1325       case SEARCH_OR:
  1326         // empty because the comparator has 1 or 2 compares, extra compares are ignored
  1327         break;
  1328       case SEARCH_OR_END:
  1329         if (*(c+1) != '|') state = SEARCH;
  1330         break;
  1331       case CMP_LAST:
  1332         cmp_last:
  1333         switch(cmp.type) {
  1334           case CMP_EQ:;
  1335             char cc = *c;
  1336             *c      = 0;
  1337             doEq();
  1338             if (cmp.type == CMP_XRANGE) goto next;
  1339             if (r) /* matching, stop. */ goto end;
  1340             *c      = cc;
  1341             break;
  1342           case CMP_LT:
  1343             #define you(op)\
  1344               cc = *c;\
  1345               *c = 0;\
  1346               op();\
  1347               *c      = cc
  1348             you(doLT);
  1349             break;
  1350           case CMP_LTE:
  1351             you(doLTE);
  1352             break;
  1353           case CMP_GT:
  1354             you(doGT);
  1355             break;
  1356           case CMP_GTE:
  1357             you(doGTE);
  1358             break;
  1359           case CMP_TILDE:
  1360             // TODO any component
  1361             #define tilde(comp, zero)\
  1362               bool acceptPreRel = false;\
  1363               if (hasG(vers, '-')) {\
  1364                 pushG(&v.prerelease, 0);\
  1365                 acceptPreRel = true;\
  1366               }\
  1367               cmptor = FIRST;\
  1368               doGTE();\
  1369               v.comp++;\
  1370               zero;\
  1371               v.patch = 0;\
  1372               freeG(&v.others);\
  1373               freeG(&v.prerelease);\
  1374               freeG(&v.build);\
  1375               vers     = toStringO(&v);\
  1376               cmptor   = LAST;\
  1377               doLT();\
  1378               free(vers);\
  1379               if (acceptPreRel and res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) {\
  1380                 /* prerelease on lower bound of xrange is accepted when - is at the end of comparator */\
  1381                 res[LAST] = true;\
  1382               }
  1383             #define tildeCmp(label)\
  1384               createVersion(v);\
  1385               parseCmpVer(vers, freeO(&v));\
  1386               if (v.mainComponentCount >= 3) {\
  1387                 tilde(minor,);\
  1388                 freeO(&v);\
  1389                 goto label;\
  1390               }\
  1391               elif (v.mainComponentCount == 2) {\
  1392                 tilde(major, v.minor = 0);\
  1393                 freeO(&v);\
  1394                 goto label;\
  1395               }\
  1396               elif (v.mainComponentCount == 1) {\
  1397                 tilde(release, v.major = 0 ; v.minor = 0);\
  1398                 freeO(&v);\
  1399                 goto label;\
  1400               }\
  1401               freeO(&v)
  1402             tildeCmp(next);
  1403             break;
  1404           case CMP_CARET: {
  1405             // TODO any component
  1406             #define caret(comp, zero)\
  1407               bool acceptPreRel = false;\
  1408               if (hasG(vers, '-')) {\
  1409                 pushG(&v.prerelease, 0);\
  1410                 acceptPreRel = true;\
  1411               }\
  1412               cmptor = FIRST;\
  1413               doGTE();\
  1414               v.comp++;\
  1415               zero;\
  1416               v.minor = 0;\
  1417               v.patch = 0;\
  1418               freeG(&v.others);\
  1419               freeG(&v.prerelease);\
  1420               freeG(&v.build);\
  1421               vers     = toStringO(&v);\
  1422               cmptor   = LAST;\
  1423               doLT();\
  1424               free(vers);\
  1425               if (acceptPreRel and res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) {\
  1426                 /* prerelease on lower bound of xrange is accepted when - is at the end of comparator */\
  1427                 res[LAST] = true;\
  1428               }
  1429             #define caretCmp(label)\
  1430               createVersion(v);\
  1431               parseCmpVer(vers, freeO(&v));\
  1432               if (v.mainComponentCount >= 2) {\
  1433                 caret(major,);\
  1434                 freeO(&v);\
  1435                 goto label;\
  1436               }\
  1437               elif (v.mainComponentCount == 1) {\
  1438                 caret(release, v.major = 0);\
  1439                 freeO(&v);\
  1440                 goto label;\
  1441               }\
  1442               freeO(&v)
  1443             caretCmp(next);
  1444             }
  1445             break;
  1446         }
  1447         next:
  1448         cmp.type = NOP;
  1449         switch(cmptor) {
  1450           case FIRST:
  1451             state = SEARCH;
  1452             break;
  1453           case LAST:
  1454             r = res[0] and res[1];
  1455             logP(BLD WHT"Cmptor result: %b"RST, r);
  1456             if (r) /* matching, stop. */ goto end;
  1457             cmptor = NONE;
  1458             state  = SEARCH_OR;
  1459             break;
  1460         }
  1461         break;
  1462       case HYPHEN_LAST:
  1463         hyphen_last:
  1464         switch(cmp.type) {
  1465           char cc;
  1466           case HYPHEN_GTE:
  1467             you(doGTE);
  1468             state = HYPHEN_CHAR;
  1469             break;
  1470           case HYPHEN_LT:
  1471             #define hyphenLTVer\
  1472               char *cs = toStringO(&v);\
  1473               vers     = cs;\
  1474               doLT();\
  1475               free(cs)
  1476             #define hyphenCmp\
  1477               /* LTE when there are a least 3 components
  1478                  LT when there are less than 3 components */\
  1479               createVersion(v);\
  1480               parseCmpVer(vers, freeO(&v));\
  1481               if (v.minor) {\
  1482                 UNIQVAR(atleast):\
  1483                 /*at least 3 components: LTE*/\
  1484                 doLTE();\
  1485               }\
  1486               else {\
  1487                 if (v.patch or lenG(&v.others)) goto UNIQVAR(atleast);\
  1488                 /*less than 3 components LT */\
  1489                 if (v.major) {\
  1490                   v.major++;\
  1491                   hyphenLTVer;\
  1492                 }\
  1493                 elif (v.release) {\
  1494                   v.release++;\
  1495                   hyphenLTVer;\
  1496                 }\
  1497                 else doLT();\
  1498               }\
  1499               r = res[0] and res[1];\
  1500               logP(BLD WHT"Hyphen Cmptor result: %b"RST, r);\
  1501               if (r) /* matching, stop. */ { freeO(&v); goto end;}\
  1502               freeO(&v)
  1503             hyphenCmp;
  1504             cmptor = NONE;
  1505             state  = SEARCH_OR;
  1506             break;
  1507         }
  1508         break;
  1509     }
  1510     c++;
  1511     index++;
  1512   }
  1513 
  1514   // finish comparing
  1515   switch(state) {
  1516     case SEARCH_OR:
  1517       finalResult:
  1518       lv(res[0]);
  1519       lv(res[1]);
  1520       r = res[0] and res[1];
  1521       logP("Cmptor result: %b", r);
  1522       break;
  1523     case EQ:
  1524       doEq();
  1525       if (cmp.type == CMP_XRANGE) goto finalResult;
  1526       break;
  1527     case LT:
  1528       #define finalOp(op)\
  1529         if (!vers) goto fail;\
  1530         op();\
  1531         goto finalResult;\
  1532         break
  1533       finalOp(doLT);
  1534     case LTE:
  1535       finalOp(doLTE);
  1536     case GT:
  1537       finalOp(doGT);
  1538     case GTE:
  1539       finalOp(doGTE);
  1540     case HYPHEN:
  1541       if (cmp.type == HYPHEN_GTE) /*incomplete hyphen range*/ goto fail;
  1542       if (cmp.type == HYPHEN_LT) {
  1543         hyphenCmp;
  1544       }
  1545       break;
  1546     case HYPHEN_CHAR:
  1547     case HYPHEN_SEARCH:
  1548       /*incomplete hyphen range*/
  1549       goto fail;
  1550       break;
  1551     case TILDE:
  1552       tildeCmp(finalResult);
  1553       goto fail;
  1554       break;
  1555     case CARET: {
  1556       caretCmp(finalResult);
  1557       goto fail;
  1558       }
  1559       break;
  1560   }
  1561 
  1562   end:
  1563   free(vr);
  1564   ret r;
  1565 
  1566   fail:
  1567   free(vr);
  1568   ret false;
  1569 }
  1570 
  1571 local smallJsont* toJsonVersion(versiont *self) {
  1572   createAllocateSmallJson(r);
  1573   setG(r, "release", self->release);
  1574   setG(r, "major", self->major);
  1575   setG(r, "minor", self->minor);
  1576   setG(r, "patch", self->patch);
  1577   setNFreeG(r, "others", dupG(&self->others));
  1578   setNFreeG(r, "prerelease", dupG(&self->prerelease));
  1579   setNFreeG(r, "build", dupG(&self->build));
  1580   ret r;
  1581 }
  1582 
  1583 local char* toJsonStrVersion(versiont *self) {
  1584   var j = toJsonVersion(self);
  1585   char *r = toStringG(j);
  1586   terminateG(j);
  1587   ret r;
  1588 }
  1589 
  1590 local bool fromJsonVersion(versiont *self, smallJsont *json) {
  1591   freeVersion(self);
  1592   if (!json or isEmptyG(json)) ret false;
  1593 
  1594   if (!hasG(json, "release")) ret false;
  1595 
  1596   self->release = getG(json, rtI64, "release");
  1597   self->major = getG(json, rtI64, "major");
  1598   self->minor = getG(json, rtI64, "minor");
  1599   self->patch = getG(json, rtI64, "patch");
  1600 
  1601   smallArrayt *a;
  1602   a = getNDupG(json, rtSmallArrayt, "others");
  1603   if (a) {
  1604     setsoG(&self->others, getsoG(a));
  1605     finishG(a);
  1606   }
  1607   a = getNDupG(json, rtSmallArrayt, "prerelease");
  1608   if (a) {
  1609     setsoG(&self->prerelease, getsoG(a));
  1610     finishG(a);
  1611   }
  1612   a = getNDupG(json, rtSmallArrayt, "build");
  1613   if (a) {
  1614     setsoG(&self->build, getsoG(a));
  1615     finishG(a);
  1616   }
  1617 
  1618   ret true;
  1619 }
  1620 
  1621 local bool fromJsonStrVersion(versiont *self, const char *json) {
  1622   freeVersion(self);
  1623   if (!json) ret false;
  1624   createSmallJson(j);
  1625   parseG(&j, (char*)json);
  1626   bool r = fromJsonVersion(self, &j);
  1627   freeG(&j);
  1628   ret r;
  1629 }
  1630 
  1631 // vim: set expandtab ts=2 sw=2:
  1632 
  1633 bool checkLibsheepyVersionVersion(const char *currentLibsheepyVersion) {
  1634   return eqG(currentLibsheepyVersion, LIBSHEEPY_VERSION);
  1635 }
  1636