Skip to content
beloch

Names and definitions

A statement refers to what earlier statements made by name, and nothing else carries a value from one statement to the next. This page states what a name is, how a statement binds one, and the three constructs that give a program structure beyond a list of statements: a definition records a sequence of statements under a name, an application runs it on operands of the caller's choosing, and an export brings the names the application made into the scope of the caller. The three share one evaluation path. A definition runs nothing, an application is the only form that runs a body, and an export reads from the result of an application and runs nothing.

A name is a sigil followed by an identifier. The identifier is one or more of the letters a to z and A to Z, the digits and _, and the sigil says what the name stands for:

sigilstands forbound by
.a point.p = …, a parameter, an export
--a line or a crease (Parameter types)--l = …, … as --c, a parameter, an export
$an instance, the result of an application$i = apply …
nonea definition, a shape, or a parameter of a shapedef, shape

Points, lines and instances are three namespaces: .m, --m and $m may stand side by side. Definitions and shapes share one namespace for the whole file, so a def of a shape's name is an error (<name> is already defined as a shape) and a second def of a name is one too (def <name> is already defined). A ; starts a comment that runs to the end of the line.

A program runs top to bottom, and a name is bound before it is used. A reference to a name no statement bound is an error that names the kind and lists the names of that kind in scope, at most twelve before an ellipsis (undefined point .m, undefined crease --m, undefined instance $m).

Bindings. = binds a point or a line to the value on its right, and the right side says which sort the name gets:

.o  = --ac * --bd                   ; a point: the meet of two lines
.p  = free on --ac from .a at 1/4   ; a point: a free point
--m = (map --ab onto --cd)          ; a line: a construction, scoring nothing
--e = --ac & .o                     ; a line: a selection from a crease
2 assertions passed
assert .o = (1/2, 1/2)
assert faces = 1

A point is bound by a meet (Reads and writes) or by free on (Free points), and by nothing else: there is no way to give a second name to a point that has one. A line is bound by a construction, which computes the line and scores nothing, or by an operand that selects from the lines and creases already there (Constructions). A crease is bound by the output clause of a write, as --c (Write statements), and an instance by an application (below).

One binding per name. Within one scope a name without the _ prefix is bound at most once. A second binding is an error (point .m is already bound; only _-prefixed temps rebind, and the same for a crease and an instance), and the corners .a to .d and the edges of the sheet count as bound by paper. The one exception is the output clause of a write: as --c! rebinds --c on purpose, and the ! is the mark that the reader sees it (Write statements).

Temps. A name whose identifier starts with _ (._m, --_h, $_i) is a temp. A temp may be bound again in the same scope, and the _ shows at every use that it may have changed since the last one. A temp is invisible from outside its scope: it is no member of an instance, an export cannot name it, and the output does not list it. A write bound to a temp crease still scores and folds; its crease is unnamed in the output.

._x = --ac * --ab        ; the corner .a
._x = --ac * --bd        ; rebound: now the center
mark (map .a onto ._x) as --_fold
1 assertion passed
assert faces = 1

A definition records a sequence of statements under a name, with the points and lines the statements need as parameters:

def corner(.p .o) {
  fold (map .p onto .o) (moving .p) as --c
}

apply corner(.a .o)
apply corner(.b .o)
apply corner(.c .o)
apply corner(.d .o)
1 of 3 assertions failed
assert steps = 4
assert faces = 8 faces = 5, expected 8
assert .a = .o
  • A definition stands at the top level of a file. Its name is a bare identifier, so it is never an operand and needs no sigil. The grammar admits no def inside a body, and a shape body holds none (a shape body holds no def).
  • Each parameter carries its sigil and is a point (.p) or a line (--l); a parameter of another type is not in the language. Two parameters of one name are an error (duplicate parameter <name>), and a definition without parameters is written with empty parentheses, def name() { … }.
  • The body holds the statements of a program: writes, bindings, applications, exports and annotations (Annotations). It holds no paper, no shape and no def.
  • The scope of a body is closed. A statement in the body sees the parameters, the names the body itself bound before it, and the definitions written earlier in the file. It sees nothing of the scope the application stands in: not the corners and edges of the sheet, whose places depend on the folds before the application, and not the names the program bound at the top level. A body that needs a corner takes it as a parameter, as corner takes .o. Because a body sees only earlier definitions, a definition cannot apply itself, directly or through another (def <name> is not defined before this body).
  • A definition runs nothing. The body is parsed when the file is read and evaluated at each application. A body no program applies is never evaluated, so its errors of evaluation are never reported.

An application runs a definition on operands, against the state the program is in, and yields an instance: the names the body bound.

def corner(.p .o) {
  fold (map .p onto .o) (moving .p) as --c
}

$a = apply corner(.a .o)           ; runs now, the instance is kept
$b = apply corner(.b .o)
export { --c as --ca } $a
export { --c as --cb } $b
.e = --ca * --cb                   ; the point where the two creases meet
1 of 2 assertions failed
assert faces = 6 faces = 3, expected 6
assert .e = (1/2, 0)
  • $name = apply def(args) keeps the instance under the name; apply def(args) runs the body and keeps nothing. Both run the body at once, so the program's state after the statement is the state the body left.
  • apply is the only right side a $-binding takes, and $name is bound once like every other name (instance $i is already bound; only _-prefixed temps rebind). A temp instance, $_i, rebinds.
  • The arguments are operands, written as in any statement, and resolved in the scope of the application before the body runs. They match the parameters by position, and the sigil of each argument has to agree with its parameter (def <name> takes 2 argument(s), got 1, parameter .p of <name> needs a point argument, parameter --l of <name> needs a line argument).
  • A crease named as an argument passes the crease itself, its material (def-bundle): the body can meet it and fold along it as later folds bend it. A selection or a filter (--[…], &, \, […]) passes the line it resolves to at the application.
  • The instance holds the parameters and every point and line the body bound, except the temps. An instance the body bound is no member: a nested application reaches the caller only through what the body exports from it.

In the output. An application has an entry of its own in beloch:statements, naming the definition, and the entries of its body follow it, each naming the application as its parent; a body statement is logged once per application that runs it (ADR 0030). A crease scored under a kept instance carries the instance's name before its own, a.c for --c of $a, and o.in.pq through a nested application; under a bare apply or a temp instance it is unnamed. beloch:named_points and beloch:named_lines list the names of the top level, the exported names among them (Output format).

An export copies members of an instance into the current scope. It reads from the instance and runs nothing, so the geometry it lands is the geometry the application left.

def corner(.p .o) {
  fold (map .p onto .o) (moving .p) as --c
  .x = --c * --h                       ; where the crease meets --h
  .t = free on --c from .x             ; a point on the crease
}

$a = apply corner(.a .o)
export { .t --c } $a                   ; selective: these two names
export { .t as .left } $a              ; renamed on landing
export $a                              ; every member: .p .o .x .t --c
error: undefined crease --h
 --> BELOCH-NAMES.md:3:14
  |
1 | def corner(.p .o) {
2 |   fold (map .p onto .o) (moving .p) as --c
3 |   .x = --c * --h                       ; where the crease meets --h
  |              ^^^ undefined crease --h
  = help: in scope: --c
  • An entry names a member with its sigil. as lands it under another name of the same sigil (export rename must keep the kind). The landed name is the member itself: beloch:named_points[].statement and beloch:named_lines[].statement report the statement of the body that made it, not the export (Output format).
  • export $i lands every member of $i, the parameters included, and checks each landed name on its own. Two applications of one definition exported whole collide on every member name; as on a selective export tells them apart.
  • A landed name is bound once like every other. Landing on a name that is bound is an error (--c exists, with the hint use ! to shadow), and ! after the member marks a deliberate shadow: export { --c! } $a rebinds --c. A ! where nothing is bound is an error too (nothing to shadow with --c, hint remove !). Landing on a temp, export { .t as ._t } $a, needs no !, since a temp rebinds anyway.
  • A member the instance does not have is an error (instance $a has no point member x), and a temp of the body is never a member, so naming one is the same error. An instance no application bound is undefined instance $a.
  • An export inside a body lands into the body's scope. A nested application's names reach the top level by two exports, one in the body and one at the top:
def midpoint(.p .q) {
  --pq = (through .p .q)
  .m = free on --pq from .p
}
def diagonal(.p .q) {
  mark (through .p .q) as --d
  $mid = apply midpoint(.p .q)
  export { .m } $mid
}

$ac = apply diagonal(.a .c)
export { .m as .o } $ac
error: --pq is a line; free on needs a crease
 --> BELOCH-NAMES.md:3:16
  |
1 | def midpoint(.p .q) {
2 |   --pq = (through .p .q)
3 |   .m = free on --pq from .p
  |                ^^^^ --pq is a line; free on needs a crease
def diagonal(.p .q) {
  mark (through .p .q) as --d
}

$ac = apply diagonal(.a .c)
export { --d as --ac } $ac
error: --ac exists
 --> BELOCH-NAMES.md:6:10
  |
3 | }
4 | 
5 | $ac = apply diagonal(.a .c)
6 | export { --d as --ac } $ac
  |          ^^^^^^^^^^^ --ac exists
  = help: use ! to shadow

One rule covers the top level, the bodies of definitions and the landing names of exports: a name without the _ prefix is bound at most once per scope.

situationresult
a name bound twice in one scope, at the top level or in a bodyerror
a corner .a to .d bound at the top levelerror
a definition's name used for a second definition or a shapeerror
an export lands on a bound name without !error
an export lands with ! on a name that is not bounderror
a write's as --c on a bound name without !error
a temp bound more than onceallowed
an export lands on a temp, with or without !allowed
a write's as --c! on a bound nameallowed