Every dependency you install runs through code like this. Each lesson is one concrete spec with exact values pinned to the SemVer 2.0.0 spec and the node-semver range grammar: parse 1.2.3-beta.1+build.5 into its parts, reject a leading zero, order the full prerelease chain from 1.0.0-alpha up to 1.0.0, desugar ~1.2.3 and ^0.2.3 and 1.x into comparator pairs, and select the max-satisfying version from a list. No pointers, no I/O, no dependencies - just a version string in and a precise answer out.
Over 24 lessons you build a complete Semantic Versioning toolkit - the same job that npm, Cargo, and Go modules do every time you install a package - as a small, dependency-free library. It parses a version string into its parts, orders any two versions by precedence, parses a range expression, and tells you whether a version satisfies a range and which version from a list is the best match. Every lesson is one concrete spec with exact values anchored to the SemVer 2.0.0 specification and the node-semver range grammar, so the library you write behaves identically in any language.
You start by parsing a version - the numeric core, the optional prerelease, and the build metadata - and rejecting the malformed ones (a leading zero, a missing part, a bad identifier). Then you build precedence: comparing the numeric core, ranking a prerelease below its release, and walking prerelease identifiers left to right to reproduce the exact chain the spec spells out, ending in a sort. From there you parse ranges - comparators, comparator sets combined with AND, alternatives joined by OR, and the sugar that expands into them: hyphen ranges, tilde, caret with its zero-component special cases, and x-ranges. The capstone answers a resolver's real questions: does a version satisfy a range, and what is the max-satisfying pick from a list.
This is a teaching-grade but genuinely faithful implementation of SemVer 2.0.0 precedence and the common npm range operators in their full MAJOR.MINOR.PATCH spellings. It is a library you import, not a program you run against a registry: there is no network, no lockfile, and no dependency-graph resolution - it answers one version-versus-range question at a time, which is exactly the primitive those larger systems are built on.
A semantic version is not a string, it is a structured value - a major, minor, and patch number plus optional prerelease and build parts. Today you define that value and parse the numeric core out of a clean version string.
Parse a well-formed "MAJOR.MINOR.PATCH" string into a Version with its three numbers filled in.
Semantic Versioning gives a release a three-number name: MAJOR.MINOR.PATCH,
like 1.2.3. Major changes when you break compatibility, minor when you
add features, patch when you fix bugs. Everything this library does - ordering
versions, matching ranges - starts by pulling those three numbers out of a string
and holding them as a real value instead of text.
Define the Version value with all five of its fields today even though only the
first three get filled in: the optional Prerelease and Build parts arrive in
the next few lessons, and giving the value its final shape now means later lessons
only add parsing, never reshape the type. Keep today honest and small - assume the
input is a clean MAJOR.MINOR.PATCH with no prerelease, no build, and no
malformed parts. Rejecting bad input is tomorrow’s job.
// Front-load the WHOLE shape now; the last two fields stay empty for a few lessons.type Version struct {Major, Minor, Patch intPrerelease []string // dot-separated identifiers, empty when absentBuild []string // dot-separated identifiers, empty when absent}// Split on ".", convert each of the three parts to an int. Assume a clean input today.func Parse(s string) (Version, error) { /* ... */ }
The core parser, precedence engine, and range matcher are complete and faithful to SemVer 2.0.0 and the common node-semver operators (comparators, hyphen, tilde, caret including the 0.x cases, x-ranges, AND/OR, the prerelease-satisfaction rule, and max-satisfying selection), but it targets the full MAJOR.MINOR.PATCH spellings: partial tilde/caret forms (~1.2, ^1), the != operator, x in comparator position, and a leading v prefix are unsupported, and ParseRange silently drops an unparseable token rather than erroring (the finalize CLI validates instead).
The specification this whole project implements. Eleven numbered clauses define the version grammar, the leading-zero and identifier rules, and - in clause 11 - the exact precedence algorithm and the worked prerelease chain the precedence chapter reproduces value for value.
The reference implementation of npm ranges. Its README is the grammar for comparators, hyphen ranges, tilde, caret (including the 0.x and 0.0.x special cases), x-ranges, and the prerelease-satisfaction rule that the ranges and satisfaction chapters follow.
The npm docs orientation on how versions and ranges are used in practice - why caret and tilde exist and what a range like ^1.0.4 actually admits. The gentle companion to the node-semver grammar.
How Go modules define and compare semantic versions, including its stricter take on prerelease and build metadata. A useful second data point on precedence and how a real tool applies it.
Cargo default-caret requirements and comparison operators, a slightly different range dialect built on the same version precedence - good for seeing which choices in this project are SemVer core versus one ecosystem convention.