version

Versioning specification based on semver 2.0
git clone https://noulin.net/git/version.git
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