Statements are executable lines or blocks of code, which appear inside procedures. The Mars execution model is, when evaluating a procedures, to execute each statement, in sequence.
Statements are divided into two categories: basic statements (simple single-line statements) and compound statements (statements which contain nested statements or affect control flow).
statement ::= basic_statement | compound_statement
A basic statement is a single line statement, which doesn’t alter control flow.
basic_statement ::= expression_stmt | assignment_stmt | field_update_stmt | pass_stmt
An expression statement consists of a single expression:
expression_stmt ::= expression NEWLINE
Executing this statement simply evaluates the expression, and throws away its result. The only effect of this statement are the side-effects of the expression’s evaluation. (Typically the expression is a function call, with side-effects).
An assignment statement is used to bind or re-bind a local variable to a new value:
assignment_stmt ::= variable "=" expression NEWLINE
Executing this statement evaluates the expression, and updates the variable so it is bound to the result of the expression. Any side-effects of the expression’s evaluation take place during the execution of the statement.
The expression’s type must equal the variable’s declared return type, or the compiler MUST reject the program.
A field update statement is used to destructively modify a named field of a user-defined type:
field_update_stmt ::= primary "." lower_identifier "=!" expression NEWLINE
Mars is, fundamentally, a pure language. This means that no statement can modify an existing value or global state, only assign local variables (and perform input/output, which while technically impure, isn’t considered “impure” in Mars).
The field update statement breaks that purity by modifying a given value and all of its aliases. It should not be considered part of the language semantics, and is not intended to be used by most code (think of it as an “unofficial feature” – the “!” in the syntax is designed as a warning to this effect). It is provided to increase performance in certain situations. Use at your own risk.
See Field replace expressions for a pure way to update fields.
A field update statement of the form obj.field =! e modifies obj and all values aliased to it, replacing field field with the value of e. The same compile-time and runtime rules as field reference expressions apply. The expression e MUST have the same type as the type of the parameter named field in any constructor of the type of obj.
Executing this statement causes obj to be evaluated first. If the constructor used to construct obj has a parameter named field, then that paremeter is mutated to have the value of e. If the constructor used to construct obj does not have a parameter named field, then a runtime error occurs, terminating the program.
Note that obj is evaluated before e. If obj does not have an appropriate constructor, it is unspecified whether e is evaluated or not.
?> x = Cons(1, Nil) ?> y = x ?> x.head =! 2 ?> x.tail =! Cons(2, Nil) ?> x Cons(2, Cons(2, Nil)) ?> y Cons(2, Cons(2, Nil)) ?> Nil.head =! 1 Runtime Error: List instance has no field 'head'
The pass statement does nothing:
pass_stmt ::= "pass" NEWLINE
It is useful in the body of a procedure or compound statment which does nothing, for syntactic reasons. (At least one statement is required, so to have a body which does nothing, a pass statement is used as a dummy no-op statement).
Compound statements are statements with nested statement blocks, or which affect control flow in some way:
compound_statement ::= return_stmt | if_stmt | while_stmt | switch_stmt
The return statement terminates the current procedure, returning a computed value:
return_stmt ::= "return" expression NEWLINE
The expression is evaluated, and any side-effects of evaluating the expression take place during the execution of the return statement. The current procedure then terminates. The result of the entire evaluation of the procedure is the value of the expression.
The expression’s type must equal the procedure’s declared return type, or the compiler MUST reject the program.
return is not a basic statement, because it alters control flow. This means, for example, it doesn’t make sense for it to be used at the command-line.
The if statement conditionally executes a sequence of statements:
if_stmt ::= "if" expression ":" NEWLINE INDENT statement+ DEDENT [ "else" ":" NEWLINE INDENT statement+ DEDENT ]
The expression on the first line is called the “condition”. The condition’s type must be Int, or the compiler MUST reject the program. The first nested block of statements is called the “then” clause. If the optional else part is supplied, then the second nested block of statements is called the “else” clause.
The condition is evaluated, and any side-effects take place immediately. If the value is not 0, the “then” clause is executed. If the value is 0, then the “else” clause, if supplied, is executed.
The while statement repeatedly executes a sequence of statements as long as a condition is true:
while_stmt ::= "while" expression ":" NEWLINE INDENT statement+ DEDENT
The expression on the first line is called the “condition”. The condition’s type must be Int, or the compiler MUST reject the program.
On each iteration, the condition is evaluated, and any side-effects take place immediately. If the value is 0, execution of the while loop halts, and execution continues with the statement following it. If the value is not 0, the nested block of statements is executed, and then the while statement is executed again.
The switch statement is used to select from a number of alternatives, and also unpack values of types with several alternatives:
switch_stmt ::= "switch" expression ":" NEWLINE INDENT case+ DEDENT case ::= "case" pattern ":" NEWLINE INDENT statement+ DEDENT
The expression is first evaluated, and any side-effects take place immediately.
The expression’s value is then matched against each case’s pattern (in the order the cases appear) using the pattern matching algorithm defined below, until a match succeeds. After any variable binding (which may be caused by a successful match), the statements inside the successful case are executed.
It must be statically provable that all possible values are handled by at least one case statement, or the compiler MUST reject the program. This can be proven either by having a wildcard pattern (underscore or variable), or by matching all alternatives of a user-defined data type.
pattern ::= "_" | variable | int_literal_expr | constructor [ "(" [ pattern_list ] ")" ] pattern_list ::= pattern ("," pattern)*
The result is either failure or success. Failure means the case statement in question is rejected, and another one is tried. Success may be accompanied by variable binding; that is, one or more variables may be defined and initialised, or assigned a new value if they already exist.
Pattern matching is defined by the following rules:
The pattern matching algorithm is similar to a specialised version of the unification algorithm in mathematical logic. For a general description of this algorithm, see Unification on Wikipedia.
|||For a pattern to have the same arity as a constructor, it must match the constructor declaration exactly. Parameter-less constructors must have parameter-less patterns, and nullary constructors (with empty parentheses) must have nullary patterns. This is due to the very different semantics of parameter-less and nullary functions. See Procedure header.|