2.3 KiB
Decisions — tofu-core
Architectural choices and rationales discovered during work on this plan.
Auto-scaffolded by /start-work. Append new entries below - never overwrite.
Task 4: version → ver rename
version is a reserved keyword in D (conditional compilation). All struct fields bearing this name are renamed to ver — PackageIndex.ver, Recipe.ver, DepConstraint.ver, CacheManifest.ver, BinaryCheckResult.ver. This keeps the API readable while avoiding the keyword conflict. Downstream modules referencing these structs must use .ver for the version field.
Task 4: DepConstraint uses typed DepOp enum, not string op
The Lua reference stores op as a string (nil, ">=", "==", etc.). In D we use a typed DepOp enum (ge, le, eq, ne, gt, lt, none) for type safety and exhaustive switching. The parse factory handles string-to-enum conversion at parse time.
Task 4: BuildPlan is a plain container — no sorting
The plan specifies that order() returns entries in deps-first order guaranteed by the caller. The struct itself is just a container with add() and order() — no topological sort or dependency resolution. Sorting logic belongs in a later module (tofu.resolver or similar).
Task 4: Single exception type TypesException
One exception class for the entire types module — no separate subclasses per error category. The parse failures (bad dep spec, invalid pool) all throw TypesException with a descriptive message. Callers catch TypesException for all type-parsing errors.
Task 4: Manual char scanning for dep spec parsing
Rather than pulling in std.regex, the parser uses hand-written isNameChar() and isWhite() helpers with a simple position cursor. This keeps the module dependency-light (only std.ascii). The logic is a direct 1:1 port of the Lua reference patterns: [A-Za-z0-9_.+-] for names/versions, whitespace [ \t] for separators.
Task 12: delegate keyword in test lambdas for delegate-typed parameters
D non-capturing lambdas infer as function pointers, which cannot implicitly convert to delegate-typed parameters. The explicit delegate keyword (scope f = delegate (...) { ... };) forces the correct type. This pattern is needed for resolution seam delegates that production code will instantiate with captures (e.g. closing over Config cfg).