Blocks

So far, we’ve only seen examples of very straightforward Cf0x10 programs, which execute one line after another except for a comefrom jump. At this point, you might be thinking, “Meh, I could do that in Intercal,” so it is time to see where Cf0x10 separates itself the rest: it allows structured programming with lexical scope!

That’s right, it really is a language for the twentieth century.

Like all the good modern languages, Cf0x10 uses syntactically significant indentation. That is, indentation creates a block:

Program Output
leave_this_place
  "Goodbye, cruel world"

"Hello, world"
Hello, world

In the above, execution begins in the top-level scope. Nothing ever jumps into the block named leave_this_place, so none of the code in that block executes.

For the moment, you can think of blocks as coroutines in other languages, but that’s not really accurate, as we’ll soon see.

Qualified comefrom

First, how to jump into and between blocks? In the previous part, we saw “conditional” comefrom, now we see “qualified” comefrom:

Program Output
foo
  1

  comefrom bar
  3

bar
  comefrom foo
  2

  4
1
2
3
4

When qualified, as in comefrom foo, the program only jumps to that location from yield points in the block named foo.

Let’s break down how this works.

Execution begins at the line just after foo, which prints 1. Cf0x10 has no magic names like “main;” if there is no code outside of a block, execution begins at the first block in the file. Next:

  1. Blank line yields
  2. Jumps to the bar block, at the line comefrom foo
  3. Prints 2
  4. Blank line yields
  5. Jumps back to the foo block, at the line comefrom bar
  6. Prints 3

So far this is all pretty obvious, but now we get to the amazing part. How does 4 get printed? We just jumped back to foo, so how do we get to bar again?

Recall that the program jumped from the middle of the bar to foo. After printing 3, the foo block ends and execution returns from whence it came. The next line in bar prints 4. Isn’t structured programming great?

Notice that even though the previous jump was from foo to bar, execution never returned to the blank line in foo, otherwise the program would have printed another 3 at the end. This is one way Cf0x10 blocks are significantly different, (and simpler!) than coroutines.

Comefrom ordering

We previously saw that multiple eligible comefrom jumps are resolved in the order they appear in the source. This only applies, however, within the a single scope. When multiple comefroms are eligible targets in separate scopes, all of them will execute.

Comefrom0x10 picks exactly one comefrom in each scope to execute, then jumps to the comefroms in a well-defined order. The comefrom in the current scope executes last, that is, after comefroms in nested scopes.

This ordering is not recursive. That is, comefroms in nested scopes execute top-down, in source code order, depth-first.

While these rules may seem complex, they are actually the most intuitive known solution to the multiple-comefrom problem [1]. An example may make this more clear:

Program Output
outside
  # blocks cannot start with blank line

  "don't print this"
  comefrom
  5
  inside
    comefrom
    "don't print this"
    comefrom
    1
    deeper_one
      comefrom
      inception
        comefrom
        3
      2
    deeper_two
      comefrom
      4
1
2
3
4
5

When this program arrives at the first blank line in the outside block, all comefrom statements are eligible targets. They execute in this order:

  1. Second comefrom in the inside block, by the last-first and top-down rules
  2. Only comefrom in deeper_one, by the first-first and top-down rules
  3. Only comefrom in the inception block, by the depth-first rule
  4. Only comefrom in deeper_two (figure out why)
  5. Only comefrom in the outside block, by the current-scope-last rule

Notice that each block finishes executing before the program moves to the next comefrom.

Scope

Naturally, you’ll need to modularize your Cf0x10 programs. Block-scoping lets you do this without worrying about things like “classes” in other languages. Block scope works essentially the same way as in every other modern language, so we can put this all together into an idiomatic Cf0x10 program:

'start program'
until = 6
loops = 0

'n is ' n ' after ' loops
count
  comefrom
  'start counting'
  n = 0
  #
  next
    comefrom if next
    next = 1/0
    loops = loops + 1
    n = n + 1

  comefrom next if n < until
  'n is ' n
  next = 1
  'done counting'

never_runs
  comefrom next if loops
  'foobar'

This is a rather complicated method of printing the numbers from 1 to 5. It prints:

start program
start counting
n is 0
n is 1
n is 2
n is 3
n is 4
n is 5
done counting
n is  after 6

This is a big jump in complexity, so let’s break it down line-by-line.

The program starts at the first line, which just prints “start program”:

'start program'

Since changing until does not affect any comefrom statements, there are no jumps at the assignment:

until = 6

Next, the blank line yields and the program jumps to the first line in the count block:

comefrom

Then it prints “start counting”:

'start counting'

Initializing n to zero actually does not cause a jump, because the only comefrom that refers to n is qualified by comefrom next:

n = 0

The empty comment line is for aesthetic reasons, just to separate the block declaration for next from the lines above it:

#

Execution passes the next block declaration. Blank lines that immediately follow blocks do not trigger jumps, so the next line that executes is a comefrom. To help write robust programs, Cf0x10 is designed to avoid side-effects as much as possible, thus, executing a comefrom lines never does anything:

comefrom next if n < until

Now, it prints the current value of n, that is “n is 0”:

'n is ' n

Whoa! It looks like the next line redefines next. In fact, it does not. For easier programming, Cf0x10 doesn’t have first-class blocks, as languages like Java has shown us that most programmers don’t want or understand them. It’s quite common in Cf0x10 to use a variable with the same name as a block. In this case, we’re using it to cause a jump:

next = 1

Because next, the variable changes from undefined to 1, the program continues in next, the block:

comefrom if next

Resetting next, the variable, to undefined does not cause a jump, since the if next evaluates to falsy after this assignment (assigning to zero would have worked just as well):

next = 1/0

The variable loops is defined in the outermost scope. This does not trigger a jump into the block never_runs because the block next is not visible from the scope of never_runs:

loops = loops + 1

Incrementing n triggers a jump back into the count block:

n = n + 1

So, count continues at:

comefrom next if n < until

And it prints the updated value of n, “n is 1”:

'n is ' n

Resetting next, the variable, jumps back into next, the block, and repeats printing incremented values of n until 6, when if n < until evaluates to false. Instead of jumping, next, the block, finishes execution. The last line executed before jumping into next was next = 1, so the next line to execute prints “done counting”:

'done counting'

Finally, the program returns to the line after the very first jump and prints “n is after 6”, illustrating that n is defined only within the lexical scope of the count block, but loops is visible:

'n is ' n ' after ' loops
[1]In many languages with comefrom-based constructs, multiple comefroms are simply an error. See Comefrom for more motivation.