Statement Grammar

Statements form the imperative core of Grove declaration bodies. While Grove is a declarative language at the top level, the bodies of actions, functions, workflows, activities, validates, and tests contain ordered sequences of statements that execute step by step.

Prerequisites: Lexical Grammar, Expression Grammar. What you'll learn: The EBNF grammar and semantics for every statement form in Grove.


Statement Overview

Statement = LetStmt
          | IfStmt
          | ForStmt
          | ReturnStmt
          | EmitStmt
          | FailStmt
          | PublishStmt
          | ExprStmt .

Statements do not require semicolons. Newlines serve as implicit statement separators.

Let Statement

LetStmt = "let" [ "mut" ] Identifier [ ":" TypeExpr ] "=" Expression .

Declares a local variable. Variables are immutable by default; use mut for mutable bindings.

let order_id = Id.generate()
let total: Decimal = 0.0
let mut count = 0

Type Inference

When the type annotation is omitted, the type is inferred from the right-hand expression:

let name = "Grove"        // inferred as String
let items = [1, 2, 3]     // inferred as List<Int>
let active = true          // inferred as Bool

Mutability

Mutable variables can be reassigned using let with the same name (shadowing) or via direct assignment in for loop contexts:

let mut total = 0.0
// ... later in a for loop:
total = total + item.price

Only variables declared with mut can appear as assignment targets.

If Statement

IfStmt    = "if" Expression "{" { Statement } "}" { ElseIf } [ Else ] .
ElseIf    = "else" "if" Expression "{" { Statement } "}" .
Else      = "else" "{" { Statement } "}" .

The condition expression must evaluate to Bool. Braces are always required; there is no braceless single-statement form.

if order.total > 1000 {
  emit HighValueOrder { order_id: order.id }
} else if order.total > 500 {
  emit MediumValueOrder { order_id: order.id }
} else {
  emit StandardOrder { order_id: order.id }
}

Null-Narrowing with If

When an if condition uses is not null, the variable is narrowed to its non-optional type within the if body:

let email = customer.email   // type: String?
if email is not null {
  // email is String here, not String?
  send_notification(email)
}

For Statement

ForStmt = "for" Identifier "in" Expression "{" { Statement } "}" .

Iterates over a List<T>. The loop variable is immutable and scoped to the loop body.

for item in order.items {
  let line_total = item.price * item.quantity
  emit LineItemProcessed { product_id: item.product_id, total: line_total }
}

For Loop Restrictions

For loops are subject to semantic constraints that preserve determinism:

See Semantic Constraints for the full list of restrictions.

Return Statement

ReturnStmt = "return" [ Expression ] .

Exits the current declaration body with an optional value. The return type must match the declaration's return type annotation.

function double(n: Int) -> Int {
  return n * 2
}

action delete_order(order_id: Id) {
  emit OrderDeleted { order_id: order_id }
  return
}

Emit Statement

EmitStmt = "emit" EventName "{" [ FieldInit { "," FieldInit } [ "," ] ] "}" .

Emits an event, appending it to the event stream. The event is validated (if a validate block exists) before being persisted.

emit OrderCreated {
  customer_id: customer_id,
  items: items,
  total: calculate_total(items),
}

Emit Restrictions

See Semantic Constraints for the full list of restrictions.

Fail Statement

FailStmt = "fail" Expression .

Aborts the current operation with an error. The expression must evaluate to String. In action and workflow contexts, a fail rolls back any events emitted in the current transaction.

if order.total <= 0 {
  fail "Order total must be positive"
}

if not customer.is_active {
  fail "Cannot create order for inactive customer: ${customer.id}"
}

In validate bodies, fail is the primary mechanism for rejecting invalid events:

validate OrderCreated {
  if items.is_empty() {
    fail "Order must have at least one item"
  }
}

Publish Statement

PublishStmt = "publish" TopicName "{" [ FieldInit { "," FieldInit } [ "," ] ] "}" .
TopicName   = Identifier .

Publishes a message to an external topic or message bus. This is used for cross-module or cross-system communication.

publish order_events {
  type: "order.created",
  order_id: order_id,
  total: total,
}

Expression Statements

ExprStmt = Expression .

An expression may appear as a statement when it has a side effect, typically a function or method call:

logger.info("Processing order ${order_id}")
notify_customer(customer_id)

The result of an expression statement is discarded.

Statement Blocks

Several declaration forms and control-flow constructs use statement blocks:

Block = "{" { Statement } "}" .

Variables declared within a block are scoped to that block:

if condition {
  let x = 42     // x is scoped to this block
}
// x is not accessible here

See Also