diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 0b3ffb53..a9ec8129 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -20,6 +20,8 @@ * DEALINGS IN THE SOFTWARE. */ package hscript; + +import hscript.thx_semver.Version; import hscript.Expr; enum Token { @@ -62,6 +64,11 @@ class Parser { **/ public var preprocessorValues : Map = new Map(); + /** + defines preprocessor variables that will be considered false + **/ + public var invalidPreprocessValues : Array = [null, "0", 0, false]; + /** activate JSON compatibility **/ @@ -195,6 +202,10 @@ class Parser { push(tk); parseFullExpr(a); } + if(preprocStack.length > 0) + { + error(EInvalidPreprocessor("Unclosed"), tokenMin, tokenMax); + } return if( a.length == 1 ) a[0] else mk(EBlock(a),0); } @@ -1346,6 +1357,10 @@ class Parser { push(tk); decls.push(parseModuleDecl()); } + if(preprocStack.length > 0) + { + error(EInvalidPreprocessor("Unclosed"), tokenMin, tokenMax); + } return decls; } @@ -1908,93 +1923,216 @@ class Parser { var preprocStack : Array<{ r : Bool }>; - function parsePreproCond() { + function parsePreproCond():Expr { var tk = token(); - return switch( tk ) { - case TPOpen: - push(TPOpen); - parseExpr(); - case TId(id): - while(true) { - var tk = token(); - if(tk == TDot) { - id += "."; + switch( tk ) { + case TPOpen: + push(TPOpen); + return parseExpr(); + case TId(id): + var tk; + while(true) { tk = token(); - switch(tk) { - case TId(id2): - id += id2; - default: unexpected(tk); + if(tk == TDot) { + id += "."; + tk = token(); + switch(tk) { + case TId(id2): + id += id2; + default: unexpected(tk); + } + } else { + push(tk); + break; } - } else { - push(tk); - break; } - } - mk(EIdent(id), tokenMin, tokenMax); - case TOp("!"): - mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax); - default: - unexpected(tk); + return mk(EIdent(id), tokenMin, tokenMax); + case TConst(c): + return mk(EConst(c), tokenMin, tokenMax); + case TOp("!"): + return mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax); + default: + return unexpected(tk); } } - function evalPreproCond( e : Expr ) { - switch( expr(e) ) { - case EIdent(id): - return preprocValue(id) != null; - case EField(e2, f): - switch(expr(e2)) { - case EIdent(id): - return preprocValue(id + "." + f) != null; - default: - error(EInvalidPreprocessor("Can't eval " + expr(e).getName() + " with " + expr(e2).getName()), readPos, readPos); - return false; - } - case EUnop("!", _, e): - return !evalPreproCond(e); - case EParent(e): - return evalPreproCond(e); - case EBinop("&&", e1, e2): - return evalPreproCond(e1) && evalPreproCond(e2); - case EBinop("||", e1, e2): - return evalPreproCond(e1) || evalPreproCond(e2); - default: - error(EInvalidPreprocessor("Can't eval " + expr(e).getName()), readPos, readPos); - return false; + function getValFromPreproExpr( e : Expr ) : Dynamic + { + var edef:ExprDef = expr(e); + switch( edef ) + { + case EParent(e): + return getValFromPreproExpr(e); + case EIdent(id): + return preprocValue(id); + case EConst(c): + return switch (c) { + case CInt(v): v; + case CFloat(f): f; + case CString(s): s; + } + case ECall(expr(_) => EIdent("version"), expr(_[0]) => EConst(CString(s))): + try + { + (s : Version); + } + catch(_) + { + error(EInvalidPreprocessor('Invalid version string $s. Should follow SemVer.'), readPos, readPos); + } + return s; + + default: + error(EInvalidPreprocessor(edef.getName()), readPos, readPos); + return null; } } + function evalPreproCond( e : Expr ):Bool { + var edef:ExprDef = expr(e); + switch( edef ) { + case EConst(c): + return !invalidPreprocessValues.contains(switch (c) { + case CInt(v): v; + case CFloat(f): f; + case CString(s): s; + }); + case EIdent(id): + return !invalidPreprocessValues.contains(preprocValue(id)); + case EField(e1, f1): + switch(expr(e1)) { + case EIdent(id): + return !invalidPreprocessValues.contains(preprocValue('$id.$f1')); + case edef2: + error(EInvalidPreprocessor("Can't eval " + edef.getName() + " with " + edef2.getName()), readPos, readPos); + return false; + } + case EUnop("!", _, e): + return !evalPreproCond(e); + case EParent(e): + return evalPreproCond(e); + case EBinop(op, e1, e2): + switch (op) + { + case "&&": + return evalPreproCond(e1) && evalPreproCond(e2); + case "||": + return evalPreproCond(e1) || evalPreproCond(e2); + case op: + var e1:Dynamic = getValFromPreproExpr(e1); + var e2:Dynamic = getValFromPreproExpr(e2); + try { + var vers1:Version = Std.string(e1); + var vers2:Version = Std.string(e2); + switch (op) { + case "==": + return vers1 == vers2; + case "!=": + return vers1 != vers2; + case ">=": + return vers1 >= vers2; + case ">": + return vers1 > vers2; + case "<=": + return vers1 <= vers2; + case "<": + return vers1 < vers2; + default: + throw null; + } + } catch(e:String) { // doesn't capture SemVer error + } catch(e) { + error(EInvalidPreprocessor('Invalid operator \'$op\' for SemVer'), readPos, readPos); + return false; + } + try { + switch (op) { + case "==": + return e1 == e2; + case "!=": + return e1 != e2; + case ">=": + return e1 >= e2; + case ">": + return e1 > e2; + case "<=": + return e1 <= e2; + case "<": + return e1 < e2; + default: + } + } catch(_) { + error(EInvalidPreprocessor('Invalid operator \'$op\''), readPos, readPos); + return false; + } + error(EInvalidPreprocessor('Uncorrected operator \'$op\''), readPos, readPos); + return false; + } + default: + error(EInvalidPreprocessor("Can't eval " + edef.getName()), readPos, readPos); + return false; + } + } + + var _inIfPrepocess:Bool = false; + function preprocess( id : String ) : Token { - switch( id ) { - case "if": - var e = parsePreproCond(); - if( evalPreproCond(e) ) { - preprocStack.push({ r : true }); - return token(); + inline function returnToken() { + return switch (token()) { + case TPrepro(id = "if" | "else" | "elseif" | "end" | "error"): + preprocess(id); + case t: t; } - preprocStack.push({ r : false }); - skipTokens(); - return token(); - case "else", "elseif" if( preprocStack.length > 0 ): - if( preprocStack[preprocStack.length - 1].r ) { - preprocStack[preprocStack.length - 1].r = false; - skipTokens(); - return token(); - } else if( id == "else" ) { - preprocStack.pop(); - preprocStack.push({ r : true }); - return token(); - } else { - // elseif - preprocStack.pop(); - return preprocess("if"); + } + if ( !_inIfPrepocess ) { + switch( id ) { + case "if": + _inIfPrepocess = true; + var result = evalPreproCond(parsePreproCond()); + _inIfPrepocess = false; + preprocStack.push({ r : result }); + if(!result) { + skipTokens(); + } + return returnToken(); + case "else", "elseif": + if ( preprocStack.length > 0 ) { + var last = preprocStack[preprocStack.length - 1]; + if( last.r ) { + skipTokens(); + last.r = false; + return returnToken(); + } else if( id == "else" ) { + preprocStack.pop(); + preprocStack.push({ r : true }); + return returnToken(); + } else { + // elseif + preprocStack.pop(); + return preprocess("if"); + } + } + else + { + return unexpected(TPrepro(id)); + } + case "end": + if( preprocStack.length > 0 ) + { + preprocStack.pop(); + return returnToken(); + } + else + { + return unexpected(TPrepro(id)); + } + case "error" if ( preprocStack.length == 0 || preprocStack[preprocStack.length - 1].r): + var tokenMin = tokenMin; + error(ECustom(getValFromPreproExpr(parsePreproCond())), tokenMin,tokenMax); + return returnToken(); } - case "end" if( preprocStack.length > 0 ): - preprocStack.pop(); - return token(); - default: - return TPrepro(id); } + return TPrepro(id); } function skipTokens() { @@ -2003,7 +2141,7 @@ class Parser { var pos = readPos; while( true ) { var tk = token(); - + if( tk == TEof ) { if (preprocStack.length != 0) { error(EInvalidPreprocessor("Unclosed"), pos, pos); @@ -2073,24 +2211,24 @@ class Parser { function tokenString( t ) { return switch( t ) { - case TEof: ""; - case TConst(c): constString(c); - case TId(s): s; - case TOp(s): s; - case TPOpen: "("; - case TPClose: ")"; - case TBrOpen: "{"; - case TBrClose: "}"; - case TDot: "."; - case TQuestionDot: "?."; - case TComma: ","; - case TSemicolon: ";"; - case TBkOpen: "["; - case TBkClose: "]"; - case TQuestion: "?"; - case TDoubleDot: ":"; - case TMeta(id): "@" + id; - case TPrepro(id): "#" + id; + case TEof: ""; + case TConst(c): constString(c); + case TId(s): s; + case TOp(s): s; + case TPOpen: "("; + case TPClose: ")"; + case TBrOpen: "{"; + case TBrClose: "}"; + case TDot: "."; + case TQuestionDot: "?."; + case TComma: ","; + case TSemicolon: ";"; + case TBkOpen: "["; + case TBkClose: "]"; + case TQuestion: "?"; + case TDoubleDot: ":"; + case TMeta(id): "@" + id; + case TPrepro(id): "#" + id; } } diff --git a/hscript/thx_semver/Version.hx b/hscript/thx_semver/Version.hx new file mode 100644 index 00000000..90f70b26 --- /dev/null +++ b/hscript/thx_semver/Version.hx @@ -0,0 +1,204 @@ +package hscript.thx_semver; + +#if !thx.semver +using StringTools; + +// Grabbed from https://github.com/fponticelli/thx.semver/tree/bdb191fe7cf745c02a980749906dbf22719e200b + +abstract Version(SemVer) from SemVer to SemVer { + static var VERSION = ~/^(\d+)\.(\d+)\.(\d+)(?:[-]([a-z0-9.-]+))?(?:[+]([a-z0-9.-]+))?$/i; + @:from public static function stringToVersion(s : String) { + if(!VERSION.match(s)) throw 'Invalid SemVer format for "$s"'; + var major = Std.parseInt(VERSION.matched(1)), + minor = Std.parseInt(VERSION.matched(2)), + patch = Std.parseInt(VERSION.matched(3)), + pre = parseIdentifiers(VERSION.matched(4)), + build = parseIdentifiers(VERSION.matched(5)); + return new Version(major, minor, patch, pre, build); + } + + @:from public static function arrayToVersion(a : Array) { + a = (null == a ? [] : a).map(function(v) return v < 0 ? -v : v) + .concat([0,0,0]) + .slice(0, 3); + return new Version(a[0], a[1], a[2], [], []); + } + + inline function new(major : Int, minor : Int, patch : Int, pre : Array, build : Array) + this = { + version : [major, minor, patch], + pre : pre, + build : build + }; + + public var major(get, never) : Int; + public var minor(get, never) : Int; + public var patch(get, never) : Int; + public var pre(get, never) : String; + public var hasPre(get, never) : Bool; + public var build(get, never) : String; + public var hasBuild(get, never) : Bool; + + public function nextMajor() + return new Version(major+1, 0, 0, [], []); + + public function nextMinor() + return new Version(major, minor+1, 0, [], []); + + public function nextPatch() + return new Version(major, minor, patch+1, [], []); + + public function nextPre() + return new Version(major, minor, patch, nextIdentifiers((this : SemVer).pre), []); + + public function nextBuild() + return new Version(major, minor, patch, (this : SemVer).pre, nextIdentifiers((this : SemVer).build)); + + public function withPre(pre : String, ?build : String) + return new Version(major, minor, patch, parseIdentifiers(pre), parseIdentifiers(build)); + + public function withBuild(build : String) + return new Version(major, minor, patch, this.pre, parseIdentifiers(build)); + + public inline function satisfies(rule : VersionRule) : Bool + return rule.isSatisfiedBy(this); + + @:to public function toString() { + var v = this.version.join('.'); + if(this.pre.length > 0) + v += '-$pre'; + if(this.build.length > 0) + v += '+$build'; + return v; + } + + @:op(A==B) public function equals(other : Version) { + if(major != other.major || minor != other.minor || patch != other.patch) + return false; + return equalsIdentifiers(this.pre, (other : SemVer).pre); + } + + @:op(A!=B) public function different(other : Version) + return !(other.equals(this)); + + @:op(A>B) public function greaterThan(other : Version) { + if(hasPre && other.hasPre) { + return major == other.major + && minor == other.minor + && patch == other.patch + && greaterThanIdentifiers(this.pre, (other : SemVer).pre); + } else if(other.hasPre) { + if(major != other.major) + return major > other.major; + if(minor != other.minor) + return minor > other.minor; + if(patch != other.patch) + return patch > other.patch; + return !hasPre || greaterThanIdentifiers(this.pre, (other : SemVer).pre); + } else if(!hasPre) { + if(major != other.major) + return major > other.major; + if(minor != other.minor) + return minor > other.minor; + if(patch != other.patch) + return patch > other.patch; + return greaterThanIdentifiers(this.pre, (other : SemVer).pre); + } else { + return false; + } + } + + @:op(A>=B) public function greaterThanOrEqual(other : Version) + return equals(other) || greaterThan(other); + + @:op(A 0; + inline function get_build() return identifiersToString(this.build); + inline function get_hasBuild() return this.build.length > 0; + + static function identifiersToString(ids : Array) + return ids.map(function(id) return switch id { + case StringId(s): s; + case IntId(i): '$i'; + }).join('.'); + + static function parseIdentifiers(s : String) : Array + return (null == s ? '' : s).split('.') + .map(sanitize) + .filter(function(s) return s != '') + .map(parseIdentifier); + + static function parseIdentifier(s : String) : Identifier { + var i = Std.parseInt(s); + return null == i ? StringId(s) : IntId(i); + } + + static function equalsIdentifiers(a : Array, b : Array) { + if(a.length != b.length) + return false; + for(i in 0...a.length) + switch [a[i], b[i]] { + case [StringId(a), StringId(b)] if(a != b): return false; + case [IntId(a), IntId(b)] if(a != b): return false; + case _: + } + return true; + } + + static function greaterThanIdentifiers(a : Array, b : Array) { + for(i in 0...a.length) + switch [a[i], b[i]] { + case [StringId(a), StringId(b)] if(a == b): continue; + case [IntId(a), IntId(b)] if(a == b): continue; + case [StringId(a), StringId(b)] if(a > b): return true; + case [IntId(a), IntId(b)] if(a > b): return true; + case [StringId(_), IntId(_)]: return true; + case _: return false; + } + return false; + } + + static function nextIdentifiers(identifiers : Array) : Array { + var identifiers = identifiers.copy(), + i = identifiers.length; + while(--i >= 0) switch (identifiers[i]) { + case IntId(id): + identifiers[i] = IntId(id+1); + break; + case _: + } + if(i < 0) throw 'no numeric identifier found in $identifiers'; + return identifiers; + } + + static var SANITIZER = ~/[^0-9A-Za-z-]/g; + static function sanitize(s : String) : String + return SANITIZER.replace(s, ''); +} + +enum Identifier { + StringId(value : String); + IntId(value : Int); +} + +typedef SemVer = { + version : Array, + pre : Array, + build : Array +} +#else +typedef Version = thx.semver.Version; +typedef Identifier = thx.semver.Version.Identifier; +typedef SemVer = thx.semver.Version.SemVer; +#end \ No newline at end of file diff --git a/hscript/thx_semver/VersionRule.hx b/hscript/thx_semver/VersionRule.hx new file mode 100644 index 00000000..d7e6ecc6 --- /dev/null +++ b/hscript/thx_semver/VersionRule.hx @@ -0,0 +1,217 @@ +package hscript.thx_semver; + +#if !thx.semver +using hscript.thx_semver.Version; +using StringTools; + +// Grabbed from https://github.com/fponticelli/thx.semver/tree/bdb191fe7cf745c02a980749906dbf22719e200b + +abstract VersionRule(VersionComparator) from VersionComparator to VersionComparator { + static var VERSION = ~/^(>=|<=|[v=><~^])?(\d+|[x*])(?:\.(\d+|[x*]))?(?:\.(\d+|[x*]))?(?:[-]([a-z0-9.-]+))?(?:[+]([a-z0-9.-]+))?$/i; + @:from public static function stringToVersionRule(s : String) : VersionRule { + var ors = s.split("||").map(function(comp) { + comp = comp.trim(); + var p = comp.split(" - "); + return if(p.length == 1) { + comp = comp.trim(); + p = (~/\s+/).split(comp); + if(p.length == 1) { + if(comp.length == 0) { + GreaterThanOrEqualVersion(Version.arrayToVersion([0,0,0]).withPre(VERSION.matched(5), VERSION.matched(6))); + } else if(!VERSION.match(comp)) { + throw 'invalid single pattern "$comp"'; + } else { + // one term pattern + var v:Array = versionArray(VERSION), + vf = v.concat([0, 0, 0]).slice(0, 3); + switch [VERSION.matched(1), v.length] { + case ["v", 0], ["=", 0], ["", 0], [null, 0]: + GreaterThanOrEqualVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case ["v", 1], ["=", 1], ["", 1], [null, 1]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.nextMajor()) + ); + case ["v", 2], ["=", 2], ["", 2], [null, 2]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.nextMinor()) + ); + case ["v", 3], ["=", 3], ["", 3], [null, 3]: + EqualVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case [">", _]: + GreaterThanVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case [">=", _]: + GreaterThanOrEqualVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case ["<", _]: + LessThanVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case ["<=", _]: + LessThanOrEqualVersion(Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6))); + case ["~", 1]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.nextMajor()) + ); + case ["~", 2], ["~", 3]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.nextMinor()) + ); + case ["^", 1]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.nextMajor()) + ); + case ["^", 2]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.major == 0 ? version.nextMinor() : version.nextMajor()) + ); + case ["^", 3]: + var version = Version.arrayToVersion(vf).withPre(VERSION.matched(5), VERSION.matched(6)); + AndRule( + GreaterThanOrEqualVersion(version), + LessThanVersion(version.major == 0 ? (version.minor == 0 ? version.nextPatch() : version.nextMinor()) : version.nextMajor()) + ); + case [p, _]: throw 'invalid prefix "$p" for rule $comp'; + }; + } + } else if(p.length == 2) { + if(!VERSION.match(p[0])) + throw 'left hand parameter is not a valid version rule "${p[0]}"'; + var lp = VERSION.matched(1), + lva = versionArray(VERSION), + lvf = lva.concat([0, 0, 0]).slice(0, 3), + lv = Version.arrayToVersion(lvf).withPre(VERSION.matched(5), VERSION.matched(6)); + + if(lp != ">" && lp != ">=") + throw 'invalid left parameter version prefix "${p[0]}", should be either > or >='; + if(!VERSION.match(p[1])) + throw 'left hand parameter is not a valid version rule "${p[0]}"'; + var rp = VERSION.matched(1), + rva = versionArray(VERSION), + rvf = rva.concat([0, 0, 0]).slice(0, 3), + rv = Version.arrayToVersion(rvf).withPre(VERSION.matched(5), VERSION.matched(6)); + if(rp != "<" && rp != "<=") + throw 'invalid right parameter version prefix "${p[1]}", should be either < or <='; + + AndRule( + lp == ">" ? GreaterThanVersion(lv) : GreaterThanOrEqualVersion(lv), + rp == "<" ? LessThanVersion(rv) : LessThanOrEqualVersion(rv) + ); + } else { + throw 'invalid multi pattern $comp'; + } + } else if(p.length == 2) { + if(!VERSION.match(p[0])) + throw 'left range parameter is not a valid version rule "${p[0]}"'; + if(VERSION.matched(1) != null && VERSION.matched(1) != "") + throw 'left range parameter should not be prefixed "${p[0]}"'; + var lv = Version.arrayToVersion(versionArray(VERSION).concat([0, 0, 0]).slice(0, 3)).withPre(VERSION.matched(5), VERSION.matched(6)); + if(!VERSION.match(p[1])) + throw 'right range parameter is not a valid version rule "${p[1]}"'; + if(VERSION.matched(1) != null && VERSION.matched(1) != "") + throw 'right range parameter should not be prefixed "${p[1]}"'; + var rva = versionArray(VERSION), + rv = Version.arrayToVersion(rva.concat([0, 0, 0]).slice(0, 3)).withPre(VERSION.matched(5), VERSION.matched(6)); + + if(rva.length == 1) + rv = rv.nextMajor(); + else if(rva.length == 2) + rv = rv.nextMinor(); + + AndRule( + GreaterThanOrEqualVersion(lv), + rva.length == 3 ? LessThanOrEqualVersion(rv) : LessThanVersion(rv) + ); + } else { + throw 'invalid pattern "$comp"'; + } + }); + + var rule = null; + while(ors.length > 0) { + var r = ors.pop(); + if(null == rule) + rule = r; + else + rule = OrRule(r, rule); + } + return rule; + } + + static var IS_DIGITS = ~/^\d+$/; + static function versionArray(re : EReg) { + var arr:Array = []; + var t:String; + for(i in 2...5) { + t = re.matched(i); + if(null != t && IS_DIGITS.match(t)) + arr.push(Std.parseInt(t)); + else + break; + } + return arr; + } + + public static function versionRuleIsValid(rule : String) + return try stringToVersionRule(rule) != null catch(e : Dynamic) false; + + public function isSatisfiedBy(version : Version) : Bool { + return switch this { + case EqualVersion(ver): + version == ver; + case GreaterThanVersion(ver): + version > ver; + case GreaterThanOrEqualVersion(ver): + version >= ver; + case LessThanVersion(ver): + version < ver; + case LessThanOrEqualVersion(ver): + version <= ver; + case AndRule(a, b): + (a : VersionRule).isSatisfiedBy(version) && (b : VersionRule).isSatisfiedBy(version); + case OrRule(a, b): + (a : VersionRule).isSatisfiedBy(version) || (b : VersionRule).isSatisfiedBy(version); + }; + } + + @:to public function toString() : String + return switch ((this : VersionComparator)) { + case EqualVersion(ver): + ver; + case GreaterThanVersion(ver): + '>$ver'; + case GreaterThanOrEqualVersion(ver): + '>=$ver'; + case LessThanVersion(ver): + '<$ver'; + case LessThanOrEqualVersion(ver): + '<=$ver'; + case AndRule(a, b): { + (a : VersionRule) + ' ' + (b : VersionRule); + } + case OrRule(a, b): + (a : VersionRule) + ' || ' + (b : VersionRule); + }; +} + +enum VersionComparator { + EqualVersion(ver : Version); + GreaterThanVersion(ver : Version); + GreaterThanOrEqualVersion(ver : Version); + LessThanVersion(ver : Version); + LessThanOrEqualVersion(ver : Version); + AndRule(a : VersionComparator, b : VersionComparator); + OrRule(a : VersionComparator, b : VersionComparator); +} +#else +typedef VersionRule = thx.semver.VersionRule; +typedef VersionComparator = thx.semver.VersionRule.VersionComparator; +#end \ No newline at end of file