From Context Free Art
Ordinary Expressions
Context Free allows expressions to be used anywhere that the compiler accepts a number, except for rule/path weights.
The following components are allowed in an expression:
- constants
- simple numeric constants (e.g., 12.45)
- ∞
- percentages (e.g., 9.6%)
- functions
- (expression) : parentheses
- ^ : exponentiation
- - : negation
- * / : multiplication and division
- + - -- : addition, subtraction, and proper subtraction
- .. … +- ± : random number operators
- x .. y or x … y is equivalent to rand(x, y)
- x +- y or x ± y is equivalent to rand(x-y, x+y)
- < > <= ≤ >= ≥ == <> ≠ : comparison
- && || ^^ : boolean and, or, and exclusive-or
Standard rules of operator ordering and precedence are supported; i.e., 2 + 3 * 4 is equivalent to 2 + (3 * 4).
Functions
The list of supported functions are:
- cos(x) - cosine of x in degrees
- sin(x) - sine of x in degrees
- tan(x) - tangent of x in degrees
- acos(x) - arc-cosine of x, returned in degrees
- asin(x) - arc-sine of x, returned in degrees
- atan(x) - arc-tangent of x, returned in degrees
- atan2(y, x) - arc-tangent of y/x, returned in degrees
- log(x) - natural logarithm of x
- log10(x) - decimal logarithm of x
- exp(x) - e^x
- sqrt(x) - square root of x
- abs(x) - absolute value of x
- floor(x) - rounds x to the next lower integer
- infinity() - ∞
- infinity(x) - ∞ if x≥0 or -∞ if x<0
- factorial(x) - x!
- sg(x) - returns 0 if x=0 or 1 if x≠0
- mod(x, y) - x modulo y
- div(x, y) - x÷y in the integer domain
- divides(x, y) - return 1 if y divides x or 0 if y does not divide x
- min(x0, x1, x2, …) - evaluates arguments and returns the smallest one
- max(x0, x1, x2, …) - evaluates arguments and returns the largest one
- frame() - current animation frame number or CF::Frame if not animating
- ftime() - current animation time or CF::FrameTime if not animating
- rand_static() - returns a static random number in the interval [0,1)
- rand_static(x) - returns a static random number in the interval [0,x) (if x > 0) or [x,0) (if x < 0)
- rand_static(x, y) - returns a static random number in the interval [x,y) (if y > x) or [y,x) (if x > y)
- rand() - returns a dynamic random number in the interval [0,1)
- rand(x) - returns a dynamic random number in the interval [0,x) (if x > 0) or [x,0) (if x < 0)
- rand(x, y) - returns a dynamic random number in the interval [x,y) (if y > x) or [y,x) (if x > y)
- randint() - returns a dynamic random integer in the interval [0,2) (i.e., returns 0 or 1)
- randint(x) - returns a dynamic random integer in the interval [0,x) (if x > 0) or [x,0) (if x < 0)
- randint(x, y) - returns a dynamic random integer in the interval [x,y) (if y > x) or [y,x) (if x > y)
The rand_static() functions are converted into a random number when the cfdg file is compiled. So the value is constant for the entire run, but it is different for each variation. A rand_static() function inside of a loop has the same value for each iteration of the loop. This is not as useful as the full dynamic random function that everyone fervently desires. But it is the best that can be done with the Context Free 2.x execution model and it is moderately useful.
rand() and randint() return new values each time they are executed. randint() is equivalent to floor(rand()) and is provided as a convenience.
Special Functions
- select(n, expr0, expr1, expr2, expr3, …) - evaluates n and then evaluates and returns expr0 if n<1, expr1 if 1≤n<2, expr2 if 2≤n<3, etc. Must have at least two arguments. The expr0, expr1, expr2, etc. need not be numeric expressions, they can all be shape adjustments or shape specifications. They must all be the same type.
- if(n, expr_true, expr_false) - evaluates n and then evaluates and returns expr_true if n≠0 or expr_false if n=0. This is just w:syntactic sugar for the select() function.
- let(var1=expr1; var2=expr2; … ; expression) - evaluates each of the argument expressions, expr1, expr2, etc., and binds them to var1, var2, etc. Then it evaluates the last expression in the context of the bound variables and returns this value. NB: the variable bindings are separated by semicolons, not commas.
Let() Examples
shape test {
// let() returns a vector2, which is used in a size adjustment
CIRCLE[s let(n=5…6;m=7…8;n,m) a -0.5 sat 1 b 1 y 5 h 90]
// let() returns a shape adjustment, which is inserted into another shape adjustment
CIRCLE[trans let(n=5…6;m=7…8;[s n m x (2.5 + 0.5 * n)]) a -0.5 sat 1 b 1]
// Use lat to specify a shape and then draw with it
draw = let(n = randint(3); select(n, CIRCLE, SQUARE, TRIANGLE))
draw[s 5 7 a -0.5 sat 1 b 1 x -5 h 180]
// Directly use let() function as a shape specifier
let(n = randint(3); select(n, CIRCLE, SQUARE, TRIANGLE))[s 5 7 a -0.5 sat 1 b 1 y -5 h 270]
}
Let us know if there is a function that you would like to see added to Context Free.
startshape foo
path trill {
MOVETO(cos(234), sin(234))
loop 5 [r -144]
CURVETO(0, 1, cos(234) + cos(324), sin(234) + sin(324), 1, 1)
CLOSEPOLY(CF::Align)
FILL(CF::EvenOdd)[]
STROKE[a -0.5]
}
shape foo {
trill[]
}
Simple Expressions
inside shape adjustments there are limitations placed on the types of expressions that are allowed. Expressions in shape adjustments are called simple expressions, while the full strength expressions described above are called ordinary expressions.
simple expression :==
- numeric constant
- simple numeric constants (e.g., 12.45)
- ∞
- percentages (e.g., 9.6%)
- (ordinary expression)
- -simple expression
- +simple expression
- variable
- function(ordinary expression) or function()
- simple expression … simple expression or simple expression .. simple expression
- simple expression ± simple expression or simple expression +- simple expression
You can get around this restriction by wrapping an ordinary expression in parentheses.