Skip to content

Core Concepts

Parsing

How expressions become structured data.

CROFF uses a real tokenizer and a recursive descent parser, not string splitting, because expressions nest.

The AST

ts
import { parse } from "@croff/core";

const result = parse("md:bg(alpha(brand,50))");
if (result.ok) {
  console.log(result.node);
}
json
{
  "type": "utility",
  "source": "md:bg(alpha(brand,50))",
  "name": "bg",
  "variants": [{ "type": "variant", "name": "md" }],
  "arguments": [
    {
      "type": "call",
      "name": "alpha",
      "arguments": [
        { "type": "word", "value": "brand" },
        { "type": "word", "value": "50" }
      ]
    }
  ]
}

Every node also carries a span with its position in the source, which is how errors can point at the right character. Bracket values appear as an arbitrary node holding both the raw text and the decoded value.

Parsing never throws

parse returns { ok: true, node } or { ok: false, error }. Errors have a stable code, a message, a span and sometimes a hint.

text
Expected "," or ")" here. [unexpected-token]
  gap(a:b)
       ^

Two kinds of error

An expression can be wrong in two different ways, and CROFF handles them differently.

Syntax errorResolution error
QuestionIs this shaped like an expression?Does it mean anything here?
Found byThe parserThe registry, after parsing
Depends on your configNeverYes: your theme and plugins
Examplegap(4gap(banana)

Both are reported by croff check and fail croff build. Neither stops other expressions from compiling: each class stands alone, so croff dev, Vite and PostCSS report the problem and carry on.

Syntax errors

CodeExample
emptyAn empty string.
whitespacegap( 4 )
invalid-charactergap("4")
invalid-nameFlex(row)
missing-valueflex
unexpected-tokengap(4)x
unterminated-callgap(4
empty-argumentgap(4,)
unterminated-bracketwidth[
unbalanced-bracketwidth[1px)]
empty-bracketwidth[]
misplaced-bracketgap([1px])
trailing-escapegap(4)\
duplicate-varianthover:hover:gap(4)
too-deepNesting beyond 16 levels.
unsupported-grouphover:(gap(4),gap(8))

unsupported-group means CROFF has no grouping syntax. Write each utility as its own expression: hover:gap(4) hover:padding(8).

Syntax never depends on your configuration. Whether gap(banana) is valid isn't the parser's question; that's decided afterwards by the registry and your theme.

Resolution errors

These come from the registry, so the same expression can be valid in one project and not in another.

CodeExample
unknown-utilitypad(4)
unknown-varianthovr:gap(4)
invalid-argumentgap(banana)
missing-argumentgap()
too-many-argumentsgap(1,2)
call-not-supportedA plugin utility that only takes a bracket value.
bracket-not-supportedflex[row]
unsafe-valuewidth[1px;color:red]
utility-errorA plugin utility that throws an exception.
unsafe-outputA plugin utility that returns 1px}body{color:red.

The last two are reported wherever the expression was found, because they mean a utility has a bug. Only that expression fails, and everything else still compiles.