# Semver v1 — Base-only semantic versions. # # The accepted textual subset is exactly decimal-major "." decimal-minor # "." decimal-patch, with no whitespace, prefix, prerelease, or build # metadata. Decimal components use Base U32.read, so values must fit U32. # Leading zeroes are accepted in v1; show/to_string always emit canonical # decimal digits. Prerelease/build ordering is intentionally deferred. import Base type Semver is Data: Semver{major: U32, minor: U32, patch: U32} # Construct a semantic version from its three numeric components. def Semver.make(major: U32, minor: U32, patch: U32) -> Semver: Semver{major, minor, patch} # Parse one complete v1 version. The whole input must be consumed. def Semver.parse.patch( major: U32, minor: U32, +patch_text: String, m: Maybe<&2, U32> ) -> Maybe<&2, Semver>: match m: case None{}: None{} case Some{patch}: Some{Semver{major, minor, patch}} def Semver.parse.minor( major: U32, +minor_text: String, +patch_text: String, m: Maybe<&2, U32> ) -> Maybe<&2, Semver>: match m: case None{}: None{} case Some{minor}: Semver.parse.patch(major, minor, patch_text, U32.read(patch_text)) def Semver.parse.major( major_text: String, +minor_text: String, patch_text: String, m: Maybe<&2, U32> ) -> Maybe<&2, Semver>: match m: case None{}: None{} case Some{major}: Semver.parse.minor(major, minor_text, patch_text, U32.read(minor_text)) def Semver.parse.parts( ps: List<&2, String> ) -> Maybe<&2, Semver>: match ps: case Nil{}: None{} case +major_text <> rest: match rest: case Nil{}: None{} case +minor_text <> rest2: match rest2: case Nil{}: None{} case +patch_text <> tail: match tail: case Nil{}: Semver.parse.major( major_text, minor_text, patch_text, U32.read(major_text)) case _ <> _: None{} def Semver.parse(s: String) -> Maybe<&2, Semver>: Semver.parse.parts(String.split(s, '.')) # Canonical decimal rendering of a version. def Semver.to_string(v: Semver) -> String: match v: case Semver{major, minor, patch}: String.append( U32.show(major), String.append(".", String.append( U32.show(minor), String.append(".", U32.show(patch))))) # show is the conventional alias for to_string. def Semver.show(v: Semver) -> String: Semver.to_string(v) # Semantic-version precedence: major, then minor, then patch. def Semver.cmp.patch(minor_cmp: Cmp, patch_cmp: Cmp) -> Cmp: match patch_cmp: case LT{}: LT{} case GT{}: GT{} case EQ{}: minor_cmp def Semver.cmp.minor( major_cmp: Cmp, minor_cmp: Cmp, patch_cmp: Cmp ) -> Cmp: match minor_cmp: case LT{}: LT{} case GT{}: GT{} case EQ{}: Semver.cmp.patch(minor_cmp, patch_cmp) def Semver.cmp.major( major_cmp: Cmp, minor_cmp: Cmp, patch_cmp: Cmp ) -> Cmp: match major_cmp: case LT{}: LT{} case GT{}: GT{} case EQ{}: Semver.cmp.minor(major_cmp, minor_cmp, patch_cmp) def Semver.cmp(a: Semver, b: Semver) -> Cmp: match a b: case Semver{major_a, minor_a, patch_a} Semver{major_b, minor_b, patch_b}: Semver.cmp.major( U32.cmp(major_a, major_b), U32.cmp(minor_a, minor_b), U32.cmp(patch_a, patch_b)) # Ordering aliases for callers that prefer order/precedence terminology. def Semver.order(a: Semver, b: Semver) -> Cmp: Semver.cmp(a, b) def Semver.precedes(a: Semver, b: Semver) -> Bool: Cmp.is_lt(Semver.cmp(a, b))