# Expressions, functions and measures

[Start here](https://pegmatite.dev/guide/start.md). Fetch this topic when a size is worked out: variable scope, operators, choosing a value, functions, reading back a labelled volume's or a shape's size, or what a prism's size is measured along.

## Variables

**Syntax:** **`set`** _variable_ _value_

A variable holds a number or a double-quoted string. Numbers supply numeric arguments; strings supply `text` and `path`. Variables cannot supply colors or references. Types are checked at evaluation: a string used as a number is an error, and reassigning another type is allowed.

`set` assigns immediately and affects only the statements after it. A persistent setting stores the value, not the variable:

```pegm
set $spacing 10
!@right $spacing

set $spacing 20
cube 10
cube 10
```

The gap is 10. A variable used before it is set is an error:

**Expected error:** a variable must be assigned before use.

```pegm
/* Expected error: undefined variable $size. */
cube $size
set $size 10
```

This reports `Undefined variable: $size`.

### Scope

A list opens no scope. A compound does: it inherits the outer variables, a `set` inside shadows the outer value, and the change ends at the closing brace:

```pegm
set $size 20
{
  cube $size
  set $size 10
  cube $size
}
cube $size
```

The cubes are 20, 10 and 20.

## Operators

A numeric argument is a number, a variable, a call, a measure, `pi`, or a parenthesized expression; a compound expression always needs parentheses. A sign alone needs none and negates a variable, parentheses, a call, a measure or `pi`: `x -$inset`, `y -($inset * 2)`. The sign must touch its operand; `- $inset` is not an argument. A volume [signed for a difference](https://pegmatite.dev/guide/booleans.md) starts with a command, macro, brace or face, never a value, so `cube 10 -cube 4` is still a difference.

| Operator                       | Meaning                     |
| ------------------------------ | --------------------------- |
| `**`                           | power, right to left        |
| `-` (unary)                    | negation                    |
| `*` `/` `%`                    | multiply, divide, remainder |
| `+` `-`                        | add, subtract               |
| `<` `<=` `>` `>=` `==` `!=`    | comparison                  |
| `not`, then `and`, then `or`   | joining tests               |
| _test_ `?` _value_ `:` _value_ | choice                      |

Tightest first; one row applies left to right.

- `**` binds tighter than a leading minus and to the right: `-2 ** 2` is `-4`, `2 ** 3 ** 2` is `2 ** 9`. A square root is `** 0.5`.
- `%` agrees with `floor`: `a` is `floor(a / b) * b + a % b`, so a remainder takes the divisor's sign, `-1 % 4` is `3`. Inside parentheses `%` is always the remainder; a percentage such as `25%` is a literal written outside any expression, in an alignment or a `cut` level.
- Division or remainder by zero is an error, and so is a power with no real value, `(-8) ** 0.5`.

```pegm
grid 4 3 2 with index $i
  cylinder 8 (4 + $i % 4 * 3 + floor($i / 4) * 6)
```

The column is `$i % 4`, the row `floor($i / 4)`.

**Expected error:** a remainder needs a divisor other than zero.

```pegm
/* Expected error: modulo by zero. */
cube (7 % 0)
```

### Choosing a value

_test_ `?` _value_ `:` _otherwise_ picks a value. A test is a comparison between two numbers, or comparisons joined by `and`, `or`, `not` and regrouped by parentheses. The choice binds loosest, so nothing inside needs parentheses, and a value may be another choice, read left to right:

```pegm
set $n 7
cube ($n < 5 ? 4 : $n < 10 ? 8 : 12)   /* 8 */
```

Only the picked value is evaluated, and the right side of `and` or `or` only when it can change the answer, so the other may be one that could not be:

```pegm
set $n 0
cube ($n == 0 or 10 / $n > 1 ? 5 : 2)   /* 5 */
```

Numbers closer than 0.000001 are equal: `(0.1 + 0.2 == 0.3 ? 1 : 2)` is `1`. A test is not a number; it stands only before `?` or after [`if`](https://pegmatite.dev/guide/macros.md). A string compares only by `==` and `!=`, equals only the same string, and never equals a number:

```pegm
set $style "round"
cylinder 10 ($style == "round" ? 20 : 10)
```

**Expected error:** a text has no order.

```pegm
/* Expected error: a text can only be compared with == or !=. */
set $style "round"
cube ($style < "square" ? 1 : 2)
```

## Functions

A call attaches `(` to the name, separates arguments with commas, returns a number, needs no extra parentheses as an argument, and nests. A name against `(` is always a call. Angles are degrees, given and returned.

| Function                                                 | Value                                                      |
| -------------------------------------------------------- | ---------------------------------------------------------- |
| `sin(`_degrees_`)` `cos(`_degrees_`)` `tan(`_degrees_`)` | sine, cosine, tangent                                      |
| `asin(`_value_`)` `acos(`_value_`)` `atan(`_value_`)`    | angle: `asin`, `atan` −90 to 90, `acos` 0 to 180           |
| `atan2(`_y_`,` _x_`)`                                    | angle of point x, y from +X, −180 to 180                   |
| `sqrt(`_value_`)`                                        | square root                                                |
| `exp(`_value_`)` `log(`_value_`)`                        | e to the value; natural logarithm of a value above zero    |
| `abs(`_value_`)`                                         | value without its sign                                     |
| `min(`_value_`,` …`)` `max(`_value_`,` …`)`              | smallest or largest of one or more values                  |
| `round(`_value_`)`                                       | nearest whole number; a half rounds away from zero         |
| `floor(`_value_`)` `ceil(`_value_`)`                     | nearest whole number below or above, or the value if whole |
| `lerp(`_from_`,` _to_`,` _t_`)`                          | _from_ at t 0, _to_ at 1, continuing past either end       |
| `curve(`_value_`,` _value_`,` …`,` _t_`)`                | smooth curve through values spaced evenly from t 0 to t 1  |

- A turn landing on a quarter is exact: `sin(180)` is `0`.
- `curve` passes through each value, never overshoots between two, is level where values turn back, holds the end value outside 0 to 1, and with two values is `lerp`. An [extrude's `size`](https://pegmatite.dev/guide/extrude.md) list follows it.
- `pi` is written bare like a number, so it cannot be a label. There is no `e`; write `exp(1)`.
- Unknown functions, wrong argument counts and undefined results such as `sqrt(-1)`, `asin(2)`, `acos(2)`, `tan(90)` are errors.

```pegm
set $w 14
block max($w, 10) (pi * 4) (40 * tan(30))
```

**Expected error:** a negative number has no square root.

```pegm
/* Expected error: sqrt(-1) has no value. */
cube sqrt(-1)
```

## Measures

**Syntax:** _label_**`.width`** | _label_**`.depth`** | _label_**`.height`**

A measure reads the size along X, Y or Z of a labelled volume's [slot](https://pegmatite.dev/guide/booleans.md), and stands like a call:

```pegm
base: block 30 20 5
on base #top cylinder base.depth (base.width / 3)
```

- It is measured in the frame the volume was placed in, in `block`'s order: `block base.width base.depth base.height` is the slot's size. A volume built on a face measures along its own axes; a rotated solid measures its silhouette.
- The slot, not the material, is measured, since placement reads the slot. A partial revolve, circular pattern or helix measures its whole circle, or with `shrink` the arc it covers. A [`slot`](https://pegmatite.dev/guide/booleans.md) measures a solid without building it. A [zero-sized volume](https://pegmatite.dev/guide/booleans.md) measures 0 along its zero side.
- A labelled macro call measures the part its body built.
- The label must be built and visible where the measure is written; a solid cannot measure itself. Unlike `on`, a measure reads a label from outside a compound's braces, a braced pattern body included; a label of the same name inside wins. No outside label reaches a macro body; pass the size as an argument.

```pegm
title: slot text "PEGMATITE" | extrude 1
@bottom block (title.width + 6) (title.depth + 6) 2
on #top text "PEGMATITE" | extrude 1
```

**Expected error:** a measure needs its label built first.

```pegm
/* Expected error: there is no volume labelled base yet. */
base: block 30 20 base.width
```

## Measuring a shape

**Syntax:** **`width(`**_2d-shape_ [**`,`** _y_]**`)`** | **`height(`**_2d-shape_ [**`,`** _x_]**`)`**

`width` is how far a [2D shape](https://pegmatite.dev/guide/shapes.md) reaches along x, `height` along y. Given a y, `width` spans from the shape's leftmost to rightmost point at that y, holes and gaps included; `height` at an x likewise. The shape is written as a sweep profile, modifiers included, and ends at the comma. It is measured in its own frame: a circle is centred on 0, so `width(circle 20, 10)` is 0. A `path` stands upright as a sweep builds it, y up. A y the shape does not reach is an error. Measured along a sweep, a silhouette sizes a [varying profile](https://pegmatite.dev/guide/sweeps.md).

```pegm
cube width(circle 20)          /* 20 */
cube width(circle 20, 5)       /* about 17.3 */
cube height(rect 30 10 | rotate 90)   /* 30 */
```

**Expected error:** a circle 20 across reaches only from y -10 to 10.

```pegm
/* Expected error: width() at y 11 has no value. */
cube width(circle 20, 11)
```

## Measuring a solid's size

A word before the size of a prism, pyramid or platonic solid says what it is measured along:

| Word       | Measures                                                        |
| ---------- | --------------------------------------------------------------- |
| `diameter` | circle through the base corners, or sphere through the vertices |
| `edge`     | one edge, of the base for a prism or pyramid                    |
| `across`   | distance between two parallel flats                             |

Left off, a prism or pyramid takes `diameter`, fitting the cylinder or cone of that size, and a platonic solid `edge`, as a cube does. The word changes only the measure, not what is measured: a pyramid's size is still its base. A square by `edge` or `across` has that side, so `prism 4 edge 20 10` fills `block 20 20 10`. A flat faces front, so a prism's depth is its `across`, and `across 17` is the hexagon a 17 spanner fits. A platonic solid stands on a face, so its `across` is its height:

```pegm
!@right 6
prism 4 edge 20 10
prism 6 across 17 8
octahedron across 20
tetrahedron diameter 20
```

Only an even polygon has a flat opposite every flat, and a tetrahedron has no face opposite a face, so neither takes `across`:

**Expected error:** an odd prism has no distance across its flats.

```pegm
/* Expected error: 5 sides leave no flat opposite a flat */
prism 5 across 20 10
```
