Milestone 4: Control Flow

Dear Computer

Project: Spreadsheet

Milestone 4: Control Flow

In this fourth milestone, you'll add conditional statements, while loops, for-each loops, and local functions to your spreadsheet. These features will build on your previous work in the lexer, parser, and model classes. You'll need to expand your environment abstraction and expression hierarchy from milestone 1. You'll be adding new structures to the spreadsheet grammar, which means you'll be extending the lexer and parser from milestone 2. If your model and view are cleanly separated, you shouldn't need to make many changes to your user interface from milestone 3.

Let's examine each of these new features in detail.

Conditional Statements

Add support for conditional statements so that users can write code like this:

if #[2, 1] > #[2, 0]
  print("then")
  1
else
  print("else")
  0
end
if #[2, 1] > #[2, 0]
  print("then")
  1
else
  print("else")
  0
end

Make a conditional node in your model classes that stores the condition, a then block, and an optional else block. Augment your lexer, parser, and visitors to handle conditional nodes. When evaluated, a conditional examines the condition and chooses which block to evaluate. The value returned by the conditional is the value returned by the block that gets executed. If the condition is not true and no else block is present, return null as a model primitive.

While Loops

Add support for while loops so that users can write code like this:

i = 1
sum = 0
while i <= 100
  sum = sum + i
  i = i + 1
end
print sum
i = 1
sum = 0
while i <= 100
  sum = sum + i
  i = i + 1
end
print sum

Make a while node type in your model classes that stores the condition expression and the block. Augment your lexer, parser, and visitors to handle while loops. When evaluated, it checks the condition. If it's true, it executes the block. It repeats this until the condition is false. Watch out for infinite loops.

For-each Loops

Add support for for-each loops so that users can write code like this:

count = 0
for value in [4, 0]..[5, 3]
  if value > 0.0
    count = count + 1
  end
end
count = 0
for value in [4, 0]..[5, 3]
  if value > 0.0
    count = count + 1
  end
end

Make a for-each node in your model classes that stores the name of the loop variable, the starting cell address, the ending cell address, and the block. Augment your lexer, parser, and visitors to handle for-each loops.

When evaluated, it iterates through the two-dimensional window of cells and assigns each cell's value to a local variable with the given name. The block is evaluated for each cell in the given window. The value returned by the block on the final iteration is the value returned by the larger loop.

Function Definitions

Add support for function definitions so that users can write code like this:

function double(x)
  xx = x + x
  return xx
end
function double(x)
  xx = x + x
  return xx
end

Make a function definition node type in your model classes that stores the function's name, a list of formal parameter names, and the function body. Augment your lexer, parser, and visitors to handle function definitions. Add a dictionary of function definitions to your runtime object. When this node is evaluated, store this node in the runtime's function definitions keyed by its name. Do not execute the block, as the function has not yet been called.

Also add a return node type. Since a return statement upsets the normal control flow of a sequential execution, have it throw an exception carrying the returned value. The exception isn't actually bad. It's just a convenient way to jump to the caller instead of running the next statement in the body.

Function Calls

Add support for function calls so that users can write code like this:

print(double(4 + 1))
print(double(4 + 1))

Make a function call node in your model classes that stores the called function's name and a list of actual parameter expressions. Augment your lexer, parser, and visitors to handle function calls.

When a function call is evaluated, execute the named function's body. But don't use the same runtime. Since functions run in a different scope, make a new runtime with bindings for the parameters. If the body throws a return statement exception, catch it and return the value. Any other exception should propagate. If there was no exception and return value from the body, return null as a model primitive.

Submission

To submit your milestone, follow the instructions. In particular, do these three things:

In your video, demonstrate entering if statements, while loops, for-each loops, and local functions using the Curses interface that you built for milestone 3.

Don't comment on or show every single line of code in your video. Select a few representative snippets. Do comment on programming language ideas that interest you or challenged you.

← Milestone 3: Interface