Function

--name(), $name()

Define a function of your own, then call it anywhere in the doodle. A call is replaced by the function's body, with the arguments in place of the parameters.

Syntax

  • --<name>(<param>, ...): <body>;
  • --<name>(<param> = <default>, ...): <body>;
  • --<name>(): { <body> }
  • --<name>(<param>, ...): { result: <value>; }
  • $<name>(<argument>, ...)

A definition goes at the top level of the doodle, before or after its calls, or inside an @svg(), and emits nothing. In the body, $p is the argument passed for the parameter p. The rules for names, arguments and errors are in the table below.

Values

The simplest body is one value. = value gives a parameter a default, used when a call leaves it out:

@grid: 6 / 60vmin _2px;
--dot(r, c = #000): radial-gradient($c $r, #0000 0);
background: $dot(30%), $dot(60%, #e6437d);

Inside $() the parameter can be written without $:

@grid: 9 / 60vmin;
--wave(v): $(sin(v / 1.5) / 2 + .5);
background: #000;
border-radius: 50%;
scale: $wave(@x);
opacity: $wave(@y);

Inside $() a call is one value, so with --inc(x): x + 1, $(2 * $inc(3)) is 8. There, in unit forms such as $px() and in the test of a @match(…) { } block, the $ can be left out: $(2 * inc(3)) is 8 too, and @match(inc(x) > 3) { … } calls it for each cell.

Declarations and elements

A body in braces can hold several declarations, pasted where the function is called:

@grid: 7 / 60vmin _2px;
--fill(c, r): {
  background: $c;
  border-radius: $r;
}
@even { $fill(#000, 50%) }
@odd { $fill(#e6437d, 10%) }

The same works for elements inside @svg. A definition can also go inside the @svg(), where only that @svg() sees it:

@grid: 1 / 60vmin;
background: @svg(
  viewBox: -10 -10 20 20;
  --dot(r, c = #000): circle { r: $r; fill: $c }
  $dot(8) $dot(5, #e6437d) $dot(2)
);

In @pattern and @scene

Calls work inside @pattern and @scene bodies too, where a parameter reads bare. A body that assigns result gives a value, and the names it assigns are its own for each call, so the two rings below each have their own d:

@grid: 1 / 60vmin;
--ring(p, r, w = .01): {
  d: abs(length(p) - r);
  result: 1 - smoothstep(0, w, d);
}
@content: @pattern(
  a: $ring(pos, .3);
  b: $ring(pos - vec2(.15, 0), .15, .03);
  fill: mix(#fff, #000, max(a, b));
);

Reference

Definitions and calls

PartRule
nameletters, digits, _, and - between words, starting with a letter or _. A call reads the whole name, so $rand-int() does not call a function named rand.
parametera name such as x, size or a_b, with an optional = default, used when a call passes fewer arguments
bodyone statement, up to its ; or through the block it opens, or any text inside braces. A call is replaced by it before the code is read, so it works wherever its text would. A statement after a body without braces that reads a parameter is reported as --f() needs {}.
$pthe argument passed for p. Inside $(), unit forms such as $px() and the test of a @match(…) { } block a bare p works too, and reads the argument in parentheses.
bare name(…)in the same places, calls the function as $name(…) does, unless a built-in such as sin or pick has the name. Elsewhere, @pattern and @scene included, it is text.
argumentsa random or picking argument such as @r(5) is worked out once at the call, so every use of the parameter gets the same number. Other arguments are pasted as written. Outside $() an argument is text, so pass a parameter on to another function as $p.
resulta body made only of result declarations gives the last one's value, as a CSS @function does: with --double(x): { result: $(x * 2); }, $double(2) is 4. A body with any other statement is pasted as written, so an @svg-filter element keeps its result attribute.
scopecalls work anywhere but inside quotes, nested @doodle code included. A definition inside an @svg() is seen only in that @svg(), where it hides one of the same name. Code inserted with @use can call the doodle's functions, and the functions it defines stay inside it.
no such function$name without parentheses is still the custom property --name, and $name(…) still appends a unit, as in $px(…)
errorsa function that calls itself, directly or through another, is reported, and the call expands to nothing. A call with more arguments than parameters, or one that leaves a parameter with no argument and no default, is reported too.

In @pattern and @scene

PartRule
parameterreads bare, as $p does, and p.xy takes its swizzle. It hides a built-in of the same name.
argumentone value, so with --f(a): a * 2, $f(x + 1) reads (x + 1) * 2
expression bodyone value too, so with --g(a): a + 1, $g(x) * 2 reads (x + 1) * 2
resultthe body's statements run before the statement that calls it, and the call reads as the last result assigned. To assign it inside a match block, give it a value before the block. Such a call cannot stand in a repeat() head or in the test of a match arm.
match blocks@scene has no blocks, so there a match block in a result body runs as values: a name an arm assigns keeps its old value where the arm does not run
namesuntil the body assigns a name, the name reads the caller's. p: p * 2 changes the function's own copy, while $p: value writes the variable passed for p, the way to give back more than one value.
without resultthe body is pasted where it is called and writes the caller's names, as its text would

See also