Skip to content

Architecture

Position in the dependency graph

cl-cc-php is a domain-layer package: it depends on the compiler core and is depended upon by nothing else in the org.

cl-cc-php
  ├── cl-cc-ast        (AST node definitions)
  ├── cl-cc-bootstrap  (compiler self-hosting core)
  ├── cl-cc-parse      (shared parsing infrastructure)
  └── cl-cc-vm         (bytecode VM)

cl-cc-php/test  (additionally)
  ├── cl-cc-pipeline   (full compile-and-run, for the e2e suites)
  └── cl-weave         (test framework)

The split between the two is deliberate. The end-to-end suites need to compile and run PHP source through cl-cc/compile:compile-string and cl-cc/vm:run-compiled, which pulls in codegen, optimize, regalloc, and emit. Folding cl-cc-pipeline into the main system would make every consumer of the PHP frontend drag along the entire backend for no reason.

Why dependencies come from a cl-cc checkout

cl-cc-ast, cl-cc-bootstrap, cl-cc-parse, and cl-cc-vm are not separate flakes here. They are read out of packages/<name>/ inside a checkout of the cl-cc monorepo, registered by run-tests.lisp as ASDF source trees.

This is a consequence of the ongoing repository split, not a design preference. Bootstrap, VM, and parse are the compiler's self-referential core and are an explicit non-goal for further splitting, so there is no independent repository to depend on. flake.nix therefore takes cl-cc as a flake = false source input.

Known issue: duplicated systems in cl-cc

cl-cc/packages/ currently defines some systems twice under the same name with differing contents. Which definition wins depends on ASDF source registry ordering. This is a known design problem in cl-cc and is out of scope for this repository. run-tests.lisp pins an explicit, ordered list of packages/ subdirectories partly to make that ordering deterministic.

Source layout

src/ is flat and grouped by prefix.

Prefix Contents
package Package definition and exports
lexer, lexer-ops Tokeniser and table-driven operator dispatch
runtime-helpers-* Shared helpers for the runtime layer
runtime-constants +php-null+ and friends
runtime-builtins-* The builtin families (see below)
parser, parser-support Parser core
parser-expr-* Expression parsing, by construct
parser-stmt-* Statement and declaration parsing
parser-class, parser-trait, parser-interface Type declarations
parser-attributes, parser-attribute-passes PHP 8 attributes
parser-call-args Named arguments and first-class callables
parser-property-hooks Property hooks, asymmetric visibility, readonly
runtime-fibers PHP 8.1 Fibers
unsupported Detection and reporting of unhandled constructs
grammar, grammar-stmt CST grammar

Builtin families, in load order, are: core, array, array sorting, string (data, core, format, multibyte, encoding, transform, analysis, ctype, extra, serialization, JSON, digest), regex (preg_*, date, number, callback, array), math, types, and I/O (data, scan, files, objects, SPL, reflection, compat, image, output, cookie/session, tokenizer, URI). runtime-builtins-register loads last because it references all of them; the ~700 name-to-symbol entries it walks are data and live in runtime-builtins-register-names*.

Load order is significant: the .asd uses :serial t.

Test layout

Every suite is named <source>-test.lisp after the src/ file it covers, or <source>-<aspect>-test.lisp where one source has several aspects worth separating. Shared fixtures are helpers-*.lisp and are not suites.

The *-e2e-test.lisp suites are the ones that require the full pipeline: they compile and run PHP source rather than inspecting an AST.

Build and CI

flake.nix declares x86_64-linux and aarch64-darwin. CI verifies the former; maintainers verify the latter on their development machines. No other platform is declared, because no one verifies any other platform.

Granularity lives in checks.*, not in extra CI jobs:

Check What it gates
checks.default The SBCL test suite via run-tests.lisp
checks.formatting treefmt/nixfmt over the Nix sources
checks.docs mkdocs build --strict, so broken links fail the PR

The package version is read out of cl-cc-php.asd at evaluation time, so the .asd is the single source of truth and no Nix-side number can drift from it.