--- title: 'Statements' weight: 7 toc: true --- ## Overview ```grammar {.good} Statement = DefStmt | IfStmt | ForStmt | SimpleStmt . SimpleStmt = SmallStmt {';' SmallStmt} [';'] '\n' . SmallStmt = ReturnStmt | BreakStmt | ContinueStmt | PassStmt | AssignStmt | ExprStmt | LoadStmt . ``` ## Pass statements A `pass` statement does nothing. Use a `pass` statement when the syntax requires a statement but no behavior is required, such as the body of a function that does nothing. ```grammar {.good} PassStmt = 'pass' . ``` Example: ```python def noop(): pass def list_to_dict(items): # Convert list of tuples to dict m = {} for k, m[k] in items: pass return m ``` ## Assignments An assignment statement has the form `lhs = rhs`. It evaluates the expression on the right-hand side then assigns its value (or values) to the variable (or variables) on the left-hand side. ```grammar {.good} AssignStmt = Expression '=' Expression . ``` The expression on the left-hand side is called a _target_. The simplest target is the name of a variable, but a target may also have the form of an index expression, to update the element of a list or dictionary, or a dot expression, to update the field of an object: ```python k = 1 a[i] = v m.f = "" ``` Compound targets may consist of a comma-separated list of subtargets, optionally surrounded by parentheses or square brackets, and targets may be nested arbitarily in this way. An assignment to a compound target checks that the right-hand value is a sequence with the same number of elements as the target. Each element of the sequence is then assigned to the corresponding element of the target, recursively applying the same logic. It is a static error if the sequence is empty. ```python pi, e = 3.141, 2.718 (x, y) = f() [zero, one, two] = range(3) [(a, b), (c, d)] = {"a": "b", "c": "d"}.items() a, b = {"a": 1, "b": 2} ``` The same process for assigning a value to a target expression is used in `for` loops and in comprehensions. Implementation note: In the Java implementation, targets cannot be dot expressions. ## Augmented assignments An augmented assignment, which has the form `lhs op= rhs` updates the variable `lhs` by applying a binary arithmetic operator `op` (one of `+`, `-`, `*`, `/`, `//`, `%`, `&`, `|`, `^`, `<<`, `>>`) to the previous value of `lhs` and the value of `rhs`. ```grammar {.good} AssignStmt = Expression ('+=' | '-=' | '*=' | '/=' | '//=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') Expression . ``` The left-hand side must be a simple target: a name, an index expression, or a dot expression. ```python x -= 1 x.filename += ".star" a[index()] *= 2 ``` Any subexpressions in the target on the left-hand side are evaluated exactly once, before the evaluation of `rhs`. The first two assignments above are thus equivalent to: ```python x = x - 1 x.filename = x.filename + ".star" ``` and the third assignment is similar in effect to the following two statements but does not declare a new temporary variable `i`: ```python i = index() a[i] = a[i] * 2 ``` ## Function definitions A `def` statement creates a named function and assigns it to a variable. ```grammar {.good} DefStmt = 'def' identifier '(' [Parameters [',']] ')' ':' Suite . ``` Example: ```python def twice(x): return x * 2 str(twice) # "" twice(2) # 4 twice("two") # "twotwo" ``` The function's name is preceded by the `def` keyword and followed by the parameter list (which is enclosed in parentheses), a colon, and then an indented block of statements which form the body of the function. The parameter list is a comma-separated list whose elements are of several kinds. First come zero or more required parameters, which are simple identifiers; all calls must provide an argument value for these parameters. The required parameters are followed by zero or more optional parameters, of the form `name=expression`. The expression specifies the default value for the parameter for use in calls that do not provide an argument value for it. The required parameters are optionally followed by a single parameter name preceded by a `*`. This is the called the _varargs_ parameter, and it accumulates surplus positional arguments specified by a call. It is conventionally named `*args`. The varargs parameter may be followed by zero or more parameters, again of the forms `name` or `name=expression`, but these parameters differ from earlier ones in that they are _keyword-only_: if a call provides their values, it must do so as keyword arguments, not positional ones. ```python def f(a, *, b=2, c): print(a, b, c) f(1) # error: function f missing 1 argument (c) f(1, 3) # error: function f accepts 1 positional argument (2 given) f(1, c=3) # "1 2 3" def g(a, *args, b=2, c): print(a, b, c, args) g(1, 3) # error: function g missing 1 argument (c) g(1, 4, c=3) # "1 2 3 (4,)" ``` A non-variadic function may also declare keyword-only parameters, by using a bare `*` in place of the `*args` parameter. This form does not declare a parameter but marks the boundary between the earlier parameters and the keyword-only parameters. This form must be followed by at least one optional parameter. Finally, there may be an optional parameter name preceded by `**`. This is called the _keyword arguments_ parameter, and accumulates in a dictionary any surplus `name=value` arguments that do not match a prior parameter. It is conventionally named `**kwargs`. The final parameter may be followed by a trailing comma. Here are some example parameter lists: ```python def f(): pass def f(a, b, c): pass def f(a, b, c=1): pass def f(a, b, c=1, *args): pass def f(a, b, c=1, *args, **kwargs): pass def f(**kwargs): pass def f(a, b, c=1, *, d=1): pass def f( a, *args, **kwargs, ) ``` Execution of a `def` statement creates a new function object. The function object contains: the syntax of the function body; the default value for each optional parameter; the value of each free variable referenced within the function body; and the global dictionary of the current module. Implementation note: The Go implementation of Starlark requires the `-nesteddef` flag to enable support for nested `def` statements. The Java implementation does not permit a `def` expression to be nested within the body of another function. ## Return statements A `return` statement ends the execution of a function and returns a value to the caller of the function. ```grammar {.good} ReturnStmt = 'return' [Expression] . ``` A return statement may have zero, one, or more result expressions separated by commas. With no expressions, the function has the result `None`. With a single expression, the function's result is the value of that expression. With multiple expressions, the function's result is a tuple. ```python return # returns None return 1 # returns 1 return 1, 2 # returns (1, 2) ``` ## Expression statements An expression statement evaluates an expression and discards its result. ```grammar {.good} ExprStmt = Expression . ``` Any expression may be used as a statement, but an expression statement is most often used to call a function for its side effects. ```python list.append(1) ``` ## If statements An `if` statement evaluates an expression (the _condition_), then, if the truth value of the condition is `True`, executes a list of statements. ```grammar {.good} IfStmt = 'if' Test ':' Suite {'elif' Test ':' Suite} ['else' ':' Suite] . ``` Example: ```python if score >= 100: print("You win!") return ``` An `if` statement may have an `else` block defining a second list of statements to be executed if the condition is false. ```python if score >= 100: print("You win!") return else: print("Keep trying...") continue ``` It is common for the `else` block to contain another `if` statement. To avoid increasing the nesting depth unnecessarily, the `else` and following `if` may be combined as `elif`: ```python if x > 0: result = +1 elif x < 0: result = -1 else: result = 0 ``` An `if` statement is permitted only within a function definition. An `if` statement at top level results in a static error. Implementation note: The Go implementation of Starlark permits `if`-statements to appear at top level if the `-globalreassign` flag is enabled. ## While loops A `while` loop evaluates an expression (the _condition_) and if the truth value of the condition is `True`, it executes a list of statement and repeats the process until the truth value of the condition becomes `False`. ```grammar {.good} WhileStmt = 'while' Test ':' Suite . ``` Example: ```python while n > 0: r = r + n n = n - 1 ``` A `while` statement is permitted only within a function definition. A `while` statement at top level results in a static error. Implementation note: The Go implementation of Starlark permits `while` loops only if the `-recursion` flag is enabled. A `while` statement is permitted at top level if the `-globalreassign` flag is enabled. ## For loops A `for` loop evaluates its operand, which must be an iterable value. Then, for each element of the iterable's sequence, the loop assigns the successive element values to one or more variables and executes a list of statements, the _loop body_. ```grammar {.good} ForStmt = 'for' LoopVariables 'in' Expression ':' Suite . ``` Example: ```python for x in range(10): print(10) ``` The assignment of each value to the loop variables follows the same rules as an ordinary assignment. In this example, two-element lists are repeatedly assigned to the pair of variables (a, i): ```python for a, i in [["a", 1], ["b", 2], ["c", 3]]: print(a, i) # prints "a 1", "b 2", "c 3" ``` Because Starlark loops always iterate over a finite sequence, they are guaranteed to terminate, unlike loops in most languages which can execute an arbitrary and perhaps unbounded number of iterations. Within the body of a `for` loop, `break` and `continue` statements may be used to stop the execution of the loop or advance to the next iteration. In Starlark, a `for` loop is permitted only within a function definition. A `for` loop at top level results in a static error. Implementation note: The Go implementation of Starlark permits loops to appear at top level if the `-globalreassign` flag is enabled. ## Break and Continue The `break` and `continue` statements terminate the current iteration of a `for` loop. Whereas the `continue` statement resumes the loop at the next iteration, a `break` statement terminates the entire loop. ```grammar {.good} BreakStmt = 'break' . ContinueStmt = 'continue' . ``` Example: ```python for x in range(10): if x%2 == 1: continue # skip odd numbers if x > 7: break # stop at 8 print(x) # prints "0", "2", "4", "6" ``` Both statements affect only the innermost lexically enclosing loop. It is a static error to use a `break` or `continue` statement outside a loop. ## Load statements The `load` statement loads another Starlark module, extracts one or more values from it, and binds them to names in the current module. Syntactically, a load statement looks like a function call `load(...)`. ```grammar {.good} LoadStmt = 'load' '(' string {',' [identifier '='] string} [','] ')' . ``` A load statement requires at least two "arguments". The first must be a literal string; it identifies the module to load. Its interpretation is determined by the application into which the Starlark interpreter is embedded, and is not specified here. During execution, the application determines what action to take for a load statement. A typical implementation locates and executes a Starlark file, populating a cache of files executed so far to avoid duplicate work, to obtain a module, which is a mapping from global names to values. The remaining arguments are a mixture of literal strings, such as `"x"`, or named literal strings, such as `y="x"`. The literal string (`"x"`), which must denote a valid identifier not starting with `_`, specifies the name to extract from the loaded module. In effect, names starting with `_` are not exported. The name (`y`) specifies the local name; if no name is given, the local name matches the quoted name. ```python load("module.star", "x", "y", "z") # assigns x, y, and z load("module.star", "x", y2="y", "z") # assigns x, y2, and z ``` A load statement within a function is a static error.