Control Structures

Single Line and Multi-Line Commands

  • if, while, and forEach support single line commands as well as multi-line commands.
  • If a control structure has commands on the same line after the condition, it is taken to be a single line statement - all commands on that line (separated by semi-colons) are part of the body and no terminal end marker is required.
  • If a control structure doesn't have commands immediately following the condition, it's considered a multi-line command and an appropriate "end" command is looked for.
  • A multi-part if/elseIf/else cannot mix and match single and multi-line blocks. They must all be single or all be multi (otherwise nested if statements could be ambiguous).
  • Single line statements may not have any nested control structures.

 

If

Syntax:

if (condition)
  ...
elseIf (condition)
  ...
else
  ...
endIf

if (condition) single; line; commands
elseIf (condition) single; line; commands
else single; line; commands

Examples:

if (x > 0) println( "$ is positive" (x) )
elseIf (x < 0) println( "$ is negative" (x) )
else println( "0 is zero" )
  • Like all conditionals in Slag, if requires parentheses around the condition.
  • escapeIf skips to the command after the enclosing endIf.

 

Which

Examples:

which (ch)
  case  9: return "TAB"
  case 10, 13: return "EOL"  # well, kinda...
  case 27: return "ESC"
  case 32..126: return ""+Char(ch)
  others: return "^" + Char(ch+64)
endWhich

forEach (a in 1..2)
  forEach (b in 1..2)
    which (a,b)
      case (1,1): println( "R" )
      case (1,2): println( "S" )
      case (2,1): println( "T" )
      case (2,2): println( "U" )
    endWhich
  endForEach
endForEach
  • The which structure replaces the switch or select of other languages.
  • Its cases may be any expressions, not just constants.
  • Internally which statements are converted into if statements. The expression is saved in a local variable and then compared to each case value.
  • No "break" commands are necessary.
  • "others:" replaces "default:"
  • Independent values can be part of the same case when separated by commas ("case a,b,c:" ).
  • A case may contain a simple range of values, e.g. "case 1..10:".  This is converted into a two-part conditional ("if (temp_value >= 1 and temp_value <= 10)").
  • escapeWhich skips to the command after the enclosing endWhich.

 

Contingent

Syntax:

contingent
  # body (regular commands)
  ...
  necessary (condition)
  ...
  sufficient (condition)
  ...
satisfied
  # commands if execution reaches end of body or any 'sufficient' cmd true 
unsatisfied
  # commands if any 'necessary' tests false.
endContingent

Example:

method print_primes( Int32 low, Int32 high ):
  forEach (n in low..high)
    contingent
      necessary (n >= 2)
      sufficient (n == 2)
      forEach (d in {2,3..sqrt(n) step 2})
        necessary (n % d != 0)
      endForEach
    satisfied
      println( "$ is a prime number" (n) )
    endContingent
  endForEach
  • Slag introduces contingents as a new kind of conditional. They are similar in flow to a try/catch block but they deal with positive and negative logic tests rather than error generation.
  • They are useful when it will take several consecutive commands to determine whether to execute "success" or "failure" code.
  • Any commands may be placed in the body. These commands can include necessary() and sufficient() logic tests.
  • If a necessary condition is true, execution proceeds to the next command. If false, execution jumps to the unsatisfied clause.
  • If a sufficient condition is false, execution proceeds. If true, execution jumps to the satisfied clause.
  • If all the body code is executed with no necessary conditions being false and no sufficient conditions being true, execution proceeds with the satisfied clause.
  • If the satisfied clause executes, the unsatisfied clause is skipped.
  • The satisfied and unsatisfied clauses are both optional.  If not present the contingent behaves as if they were empty.
  • escapeContingent skips to the command after the enclosing endContingent.

 

While

Syntax:

while (condition)
  ...
endWhile

while (condition) single; line; commands

Example:

# Sum together the digits of a non-negative integer until one digit is left.
while (n > 0)
  n = n/10 + n%10
endWhile
  • escapeWhile skips to the command after the enclosing endWhile.
  • nextIteration skips the remainder of the current iteration and proceeds onto the next iteration.

 

Loop

Syntax:

loop
  ...
endLoop

Example:

# Keep reading in numbers from some reader until a zero is read.
loop
  local var n = reader.read
  if (n == 0) escapeLoop
  list.add(n)
endLoop
  • loop loops indefinitely and is a convenient alternative to "while (true) ...".
  • escapeLoop skips to the command after the enclosing endLoop.
  • nextIteration skips the remainder of the current iteration and proceeds onto the next iteration.

 

ForEach

Syntax:

# multi-line variants
forEach (range)
  ...
endForEach

forEach (var_name in range)
  ...
endForEach

forEach (var_name of range)
  ...
endForEach

# single line variants
forEach (range) single; line; commands

forEach (var_name in range) single; line; commands

forEach (var_name of range) single; line; commands

Examples:

forEach (1..100) println("I will use iteration to make my life easier.")

forEach (line in LineReader(File("settings.txt"))) println(line)

forEach (ch in "nilbog".reverse_order) print(ch)
println

forEach (entity in mobs)
  entity.update
  if (entity.is_dead) removeCurrent entity
endForEach

method index_of( Real64 n ).Int32:
  forEach (index of data)
    if (data[index] == n) return index
  endForEach
  return -1
  • escapeForEach skips to the command after the enclosing endForEach.
  • nextIteration skips the remainder of the current iteration and proceeds onto the next iteration.
  • Read the notes on advanced forEach usage to learn about:
    • How forEach loops are processed internally.
    • How to write classes that are compatible with forEach.
    • What causes concurrent modification errors and how to avoid them.
    • How the removeCurrent command works.

 

Try/Catch & Throw

Syntax:

try
  ...
catch (MostSpecificType err)
  ...
catch (LessSpecificType err)
  ...
catch (MostGeneralType err)
  ...
endTry

...
throw Error("badwrong")

Example:

try
  load_save_data( File("save.txt") )
catch (FileError err)
  set_up_default_data()
endTry
  • The throw command takes an Exception (or subclass) object and unwinds the call stack to the most recently-defined catch of the correct type, no matter many nested calls you're currently in.
  • try/catch and throw operate with objects extended from class Exception.
  • Class Error extends class Exception and all built-in exceptions are extended from type Error.
  • There are no checked exceptions in Slag - errors may be thrown at any time (without writing a declaration in the method signature) and enclosing try/catch blocks are entirely optional.
  • Exception objects that are instanceOf the first catch type are handled by that catch and the other catches are skipped.
  • Slag does not have a finally clause.