Control Flow

John Horigan edited this page Oct 6, 2018 · 3 revisions

In a shape rule it is possible to put a control structure anywhere that it is legal to have a shape replacement. In a path it is possible to put a control structure anywhere that it is legal to have a path operation or a path command. The list of supported control structure are: loops, if statements, transform statements, and switch statements.

Loops

A loop structure takes a block of code (shape replacements, path operations, or path commands) and iterates over it several times. A loop consists of a loop header, a loop body, and an optional finally body. A loop header has an optional loop index variable, the loop count, and the loop transform. If a loop index variable is declared in the loop header then the current loop value is made available through that variable inside the loop body (and finally body). These kinds of loops are called named loops. If no loop index variable is declared in the loop header then the loop is an anonymous loop and the current loop value is not available. After each iteration of the loop the current world state is adjusted by the loop transform.

shape flower {
    // petals
    loop 6 [r 60] CIRCLE [[ r 30 x 0.5 s 1 0.25 ]]
    //center
    CIRCLE [ s 0.25 b 1 ]
}

is equivalent to

shape flower {
    // petals
    CIRCLE [[ r 30 x 0.5 s 1 0.25 ]]
    CIRCLE [[ r 60 r 30 x 0.5 s 1 0.25 ]]
    CIRCLE [[ r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
    CIRCLE [[ r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
    CIRCLE [[ r 60 r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
    CIRCLE [[ r 60 r 60 r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
    //center
    CIRCLE [ s 0.25 b 1 ]
}

Loop Index Variable

shape rightTriangle {
  loop i = 10 [x 1]     // named loop, index variable is i, loops from 0 to 9
    loop i+1 [y 1]      // anonymous loop, loops from 0 to i
      CIRCLE []
}

The example has the outer loop declaring a loop index variable by putting i = before the loop count. The inner loop does not have a variable = before the loop count, so it is anonymous. Note that the outer loop's variable is used in the loop count for the inner loop. We could even use it in the loop transform of the inner loop or outer. The scope for the loop index variable is the loop transform and the body of the loop.

Loop Count

The loop count is an expression of one, two, or three numbers. If all three numbers are specified then they are the initial loop value, the terminal loop value, and the loop step. The loop value starts with the initial loop value for the first iteration. The loop step is added to the loop value after each iteration. The loop terminates when the loop value is ≥ the terminal loop value (or ≤ the terminal loop value if loop step < 0). If two numbers are specified then they are the initial loop value and the terminal loop value, the loop step defaults to 1. If only one number is specified then it is the terminal loop value, the initial loop value defaults to zero and the loop step defaults to 1.

Loop Transform

The body of the loop inherits the world state for the shape or path that contains the loop. After each loop iteration the loop's world state is adjusted using the loop transform.

Finally Body

If the loop has a finally body then the code in the finally body will execute once after the last iteration of the loop. The world state for the finally body is the world state for the loop with one last adjustment by the loop transform. If the loop is a named loop then the loop index variable has scope inside the finally body.

shape foo2 {
    SQUARE [[r -22.5 s 0.1 14.5 y 0.5]]
    loop 10 [[x 0.5 s 0.99 x 0.5 r 3]] 
        CIRCLE []
    finally
        foo2 []
}

If Statements

An if statement evaluates an expression and executes some code (replacements, path elements, other control structures) if the expression value is non-zero. An if statement can also have an else clause that is executed if the expression value is zero. The syntax for an if statement is if (expression) then-body or if (expression) then-body else else-body. then-body and else-body can be simple bodies or compound bodies.

path flower(number petals, number filled)
{
  MOVETO(cos(-180/petals), sin(-180/petals))
  loop petals [r (360/petals)]
    ARCTO(cos(180/petals), sin(180/petals), 
          0.4, 0.4 + 0.2 * (petals - 5), 90)
  CLOSEPOLY(CF::Align)
  if (filled) FILL[a -0.5]
  MOVETO(0.65, 0)
  ARCTO(-0.65, 0, 0.65, CF::ArcCW)
  ARCTO( 0.65, 0, 0.65, CF::ArcCW)
  CLOSEPOLY()
  FILL[a -0.5]
}

Switch Statements

A switch statement allows one of several blocks of code to be executed based on the evaluation of of a selector expression. The switch statement contains one or more case elements. Each case element contains one or more constant case integers and a case body, which is a simple or compound statement. The case integers can be expressions or numbers, but they must be constant. As a convenience, the random range operator has a special meaning in case integer expressions: it evaluates to a range of case integers instead of evaluating to a random number.

There may also be an else element which also has a case body, but no expression. When a switch statement is executed the selector expression is evaluated and rounded to the next lowest integer. If there is a case element with a case integer matching this integer then the corresponding case body is executed. If there is no matching case element then the else body is executed, if there is one.

shape switchtest
{
  loop j=9 [x 2] {
    switch (j) {
      case 0,1,2:         // Multiple indices per case
        SQUARE []         // Simple case body
      case abs(-3),4…5:   // Constant expression, case range (Unicode form)
        TRIANGLE []
      case 6..8: {        // Case range ASCII form, compound case body
        CIRCLE []
        CIRCLE [s 0.5 b 1]
      }
    }
  }
}

Programmers note: This is not a C-style switch statement. Each piece of code has exactly one case header. There is no fall-through and no break statement.

Transform Statements

A transform statement has a list of adjustments and a body. The transform body is executed once for each adjustment in the adjustment list in the context of that adjustment. The transform body can be simple or compound. The adjustment list must have at least one adjustment and they are separated by commas. It has the same syntax as the CF::Symmetry configuration variable, except that the only symmetry operations that are allowed are CF::Cyclic and CF::Dihedral.

TTOP = sin(30)/sqrt(3)
HEXS = sqrt(3)/4
 
path triangle {
    MOVETO(-0.5, -TTOP)
    loop 3 [r 120]{ 
        CURVETO(0, -TTOP, -0.25, 0.05) 
        CURVETO(0.5, -TTOP, CF::Continuous) 
    }
    CLOSEPOLY(CF::Align)
    transform [s 0.41] {
        MOVETO(0.25, HEXS)
        loop 5 [r -60]
            LINETO(0.5, 0)
        CLOSEPOLY()  
    }
    FILL[]
}

In this example the designer wanted to knock-out a hexagonal hole in a path. Figuring out exactly how to position the hexagon path points is tricky. The transform statement allows the designer to plug in simple code for a hexagon and then tweak the entire hexagon path to achieve the desired effect.

Clone Statements

A clone statement has the same syntax and behavior as a transform statement except that all of the shapes executed inside a clone statement look the same, even if they are random. This is a violation of context-free purity, so you must set CF::Impure to 1.

CF::Impure = 1
 
shape SPIKE
rule {
	SQUARE []
	SPIKE [y 0.95 s 0.97]
}
rule 0.03 {
	SQUARE []
	SPIKE [r 60]
	SPIKE [r -60]
	SPIKE [y 0.95 s 0.97]
}
 
shape snowflake {
    clone CF::Dihedral, 6
    {
        SPIKE []
    }
}

Under the Hood: In addition to the public state that each shape has (color, geometry, z, time) there is a piece of secret state: a random number generator seed. Each shape has a unique seed and when a rule is executed the replacement shapes all get unique seeds. The clone statement overrides this process and forces each iteration of the clone to have the same seed.

Simple vs. Compound Bodies

The body of a control structure (loop-main, loop-finally, if, else, switch-case, transform, or clone) can have one of two forms: simple or compound. A simple loop body is a single shape replacement, path operation, path command, or another control structure. A compound loop body has an open curly brace {; any number of replacements, path elements, or control structures; and then a closing curly brace }.

shape rightTriangle {
  loop i = 10 [x 1]     // simple body
    loop i+1 [y 1] {    // compound body
      SQUARE []
      CIRCLE [b 1]
    }
}
 
TTOP = sin(30)/sqrt(3)
HEXS = sqrt(3)/4
 
path triangle {
    MOVETO(-0.5, -TTOP)
    loop 3 [r 120] {         // compound body
        CURVETO(0, -TTOP, -0.25, 0.05) 
        CURVETO(0.5, -TTOP, CF::Continuous) 
    }
    CLOSEPOLY(CF::Align)
    transform[s 0.41] {      // compound body
        MOVETO(0.25, HEXS)
        loop 5 [r -60]
            LINETO(0.5, 0)   // simple body
        CLOSEPOLY()  
    }
    FILL[]
}
 
shape randShape {
  switch (rand(3)) {
    case 0:
      SQUARE []      // simple body
    case 1:
      TRIANGLE []    // simple body
    case 2: {        // compound body
      CIRCLE []
    }
    else:            // simple body
      FILL []
  }
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.