---
title: "Parameters and expressions"
description: "The five parameter types, and the restricted expression language — what it can compute, what names it can see, and the one hyphen rule that trips models up."
url: https://kaicad.barpom.xyz/docs/schema/parameters-and-expressions
schema: "0.1"
source: packages/resolver/src/expression.ts
---

# Parameters and expressions

`parameters` are the numbers a person would want to change: the mattress size, the timber, the
deck height. Everything else in the document should be **derived** from them, so that changing
one moves the whole piece rather than leaving half of it behind.

```yaml
parameters:
  - id: deck_height
    label: Height of the slat top above the floor
    type: length
    value: 300
    min: 180
    max: 500

  - id: deck_width
    label: Clear width between the rails
    type: length
    derived: mattress.width + 2 * mattress_clearance
```

Every parameter has an `id` and a `type`, and optionally a `label` and a `description`. The
label is what a person sees; write it as a sentence, not a variable name.

## `value` or `derived`, never both

`value` is entered. `derived` is computed, and is always an expression string. A parameter
with neither is [`SCH-002`](/docs/validation/error-codes#sch-002).

The distinction is the difference between a piece that can be resized and one that cannot. If
you find yourself typing the same arithmetic into two `blank:` entries, that number wanted to
be a derived parameter.

## The five types

### `length`

Millimetres. `min` and `max` bound it, and are themselves expressions.

```yaml
- id: headboard_height
  type: length
  value: 900
  min: 600
  max: 1400
```

### `number`, `count`, `angle`

The same shape as `length` — `value` or `derived`, with optional `min` and `max`. `count` is
a whole number of things; `angle` is degrees.

### `enum`

A closed list, and a chosen value. Referencing it yields the **string**, so it is normally fed
into a material's `species` rather than into arithmetic.

```yaml
- id: species
  type: enum
  values: [walnut, white_oak, ash, maple, cherry]
  value: walnut
```

### `boolean`

```yaml
- id: add_centre_rail
  type: boolean
  value: true
```

### `catalog_ref`

Names a catalogue family and one entry in it. This is the useful one: the parameter then
**behaves like the entry**, so its fields read as sub-references.

```yaml
- id: mattress
  type: catalog_ref
  catalog: standards.mattress
  value: us-queen
```

`mattress.width` is then 1524 and `mattress.length` is 2032, and changing `value` to
`uk-double` moves every part derived from them. A value that is not in the family is
[`RES-005`](/docs/validation/error-codes#res-005), and the report lists the ones that are.

## The expression language

Expressions are the only computation in the format, and the evaluator is a hand-written
recursive-descent parser. It never calls `eval`, never builds a function and never reaches a
global. The only names it can see are the ones listed below.

**Operators**, in the usual precedence: `^` · `* / %` · `+ -` · `< <= > >=` · `== !=` · `&&` ·
`||` · `? :`, with unary `-` and `!`, and parentheses.

**Functions**: `min`, `max` (both variadic), `round`, `ceil`, `floor`, `abs`, `sqrt`,
`clamp(value, low, high)`.

```yaml
count: ceil((slat_cleat.blank.length - slat.blank.width) / (slat.blank.width + max_slat_gap)) + 1
```

There are no user-defined functions, no loops and no conditionals beyond the ternary. If a
computation does not fit, the answer is another derived parameter, not a longer expression.

## What an expression can see

| Reference | Yields |
| --- | --- |
| `deck_height` | A parameter's value |
| `mattress.width` | A field of a `catalog_ref` parameter's entry |
| `rail_stock.thickness`, `.width` | A resolved material's actual milled dimension |
| `rail_stock.density`, `.modulus_of_elasticity` | That material's species properties |
| `side_rail.blank.length`, `.width`, `.thickness` | Another part's resolved blank |
| `standards.bed.slat_gap_max` | A catalogue standard |

That table is the whole reach of the language. A bare part or material name is not a value —
`side_rail` on its own resolves to nothing. Local names win over catalogue names, and nothing
local may be called `standards`.

Resolution is lazy and memoised, so these may depend on each other in any order. A real loop
is [`RES-003`](/docs/validation/error-codes#res-003), and the finding names the cycle.

## Where expressions are allowed

Anywhere the reference says `Expr`: blank dimensions, feature sizes and positions, offsets,
pattern counts and spacings, `min`/`max` bounds, and array counts.

**In a number-typed field, a string is always an expression** — no marker needed:

```yaml
blank:
  length: deck_length          # expression
  width: rail_stock.width      # expression
  thickness: 38                # number
```

**In a text-typed field, an expression needs a leading `=`**, because the literal text is
usually what you meant:

```yaml
species: walnut      # the literal string "walnut"
species: "=species"  # the value of the `species` parameter
```

## The hyphen rule

Catalogue keys contain hyphens — `standards.mattress.us-queen.width` — and so does
subtraction. The rule the tokeniser applies:

> A hyphen continues an identifier **only** when it sits directly between an identifier
> character and a *letter or underscore*, with no whitespace.

| Written | Read as |
| --- | --- |
| `us-queen` | one name |
| `deck_height - rail_stock.width` | subtraction |
| `width-10` | subtraction — a digit follows the hyphen |
| `a-b` | **one name** |

So if you mean subtraction between two single-word names, put spaces around the minus. Getting
it wrong is a clear [`RES-002`](/docs/validation/error-codes#res-002) — an unknown name — not
a wrong number quietly compiled.

## Worked: sizing a bed from its mattress

```yaml
parameters:
  - { id: mattress, type: catalog_ref, catalog: standards.mattress, value: us-queen }
  - { id: mattress_clearance, type: length, value: 6, min: 3, max: 15 }
  - { id: deck_height, type: length, value: 300 }

  - id: deck_width
    type: length
    derived: mattress.width + 2 * mattress_clearance

  - id: max_slat_gap
    type: length
    derived: standards.bed.slat_gap_max

  - id: post_centre_offset
    type: length
    derived: deck_width / 2 + rail_stock.thickness / 2
```

Nothing in that block is a measurement of this particular bed. Change `us-queen` to
`uk-double` and the rails, the cleats, the slat count and the gap between slats all follow.
