Expression Grammar

Expressions are the building blocks of computation in Grove. They produce values by combining literals, variables, operators, function calls, and method chains. This page defines the complete expression grammar, the operator precedence table, and the rules for method chaining.

Prerequisites: Lexical Grammar for token definitions, Type System for type expressions. What you'll learn: Every expression form available in Grove and how they compose.


Expression Overview

Expression = Literal
           | Identifier
           | PrefixExpr
           | InfixExpr
           | ParenExpr
           | CallExpr
           | MemberExpr
           | IndexExpr
           | OptionalChainExpr
           | NullCoalesceExpr
           | ObjectExpr
           | ListExpr
           | MapExpr
           | LambdaExpr
           | IsExpr
           | SpreadExpr
           | InterpolatedString .

Literals

Literal = IntLiteral
        | DecimalLiteral
        | StringLiteral
        | BoolLiteral
        | NullLiteral .
42
3.14
"hello"
true
null

Identifiers

An identifier in expression position resolves to a variable, parameter, function, or enum variant.

order_total
OrderStatus.Pending

Prefix Expressions

PrefixExpr = PrefixOp Expression .
PrefixOp   = "-" | "not" .
-amount
not is_active

Infix Expressions

InfixExpr = Expression InfixOp Expression .
InfixOp   = "+" | "-" | "*" | "/" | "%"
           | "==" | "!=" | "<" | ">" | "<=" | ">="
           | "and" | "or" .
a + b * c
total >= 100 and status == OrderStatus.Pending

Operator Precedence

Operators are listed from highest to lowest precedence. Operators on the same row share the same precedence level and associate left-to-right unless otherwise noted.

PrecedenceOperatorsDescriptionAssociativity
1. ?. () []Member access, call, indexLeft
2- (unary), notNegation, logical NOTRight (prefix)
3* / %MultiplicativeLeft
4+ -AdditiveLeft
5< > <= >=RelationalLeft
6== !=EqualityLeft
7isType / null testLeft
8andLogical ANDLeft
9orLogical ORLeft
10??Null coalescingRight

Parentheses override precedence in the usual way: (a + b) * c.

Parenthesized Expressions

ParenExpr = "(" Expression ")" .
(base_price + tax) * quantity

Call Expressions

CallExpr = Expression "(" [ ArgList ] ")" .
ArgList  = Arg { "," Arg } .
Arg      = [ Identifier ":" ] Expression .

Arguments may be positional or named. Named arguments use the name: value syntax.

calculate_tax(100.00, 0.08)
create_order(customer_id: cust_id, items: order_items)
Id.generate()

Member Access

MemberExpr = Expression "." Identifier .

Accesses a field on a record or a method on a value.

order.customer_id
order.items.length
"hello".to_upper()

Index Access

IndexExpr = Expression "[" Expression "]" .

Accesses an element by index (for List) or by key (for Map).

items[0]
headers["Content-Type"]

Optional Chaining

OptionalChainExpr = Expression "?." Identifier .

Short-circuits to null if the left-hand side is null. The result type is always optional.

order?.customer?.email

Null Coalescing

NullCoalesceExpr = Expression "??" Expression .

Returns the left-hand side if it is not null, otherwise the right-hand side. Associates right-to-left.

user.nickname ?? user.name ?? "Anonymous"

Object Expressions

ObjectExpr = TypeName "{" [ FieldInit { "," FieldInit } [ "," ] ] "}" .
FieldInit  = FieldName ":" Expression .

Creates an instance of a named record or event type.

Address {
  street: "123 Main St",
  city: "Springfield",
  state: "IL",
  zip: "62704"
}

When a field name matches the variable name, the shorthand form is permitted:

let city = "Springfield"
Address { street: "123 Main St", city, state: "IL", zip: "62704" }

List Expressions

ListExpr = "[" [ Expression { "," Expression } [ "," ] ] "]" .
[1, 2, 3]
["a", "b", "c"]
[]

Map Expressions

MapExpr = "{" [ MapEntry { "," MapEntry } [ "," ] ] "}" .
MapEntry = Expression ":" Expression .
{ "name": "Grove", "version": "1.0" }

Lambda Expressions

LambdaExpr  = LambdaParams "=>" LambdaBody .
LambdaParams = Identifier | "(" ParamList ")" .
LambdaBody   = Expression | "{" { Statement } "}" .

Lambdas are anonymous functions used primarily with collection methods.

items.map(i => i.price * i.quantity)
items.filter(i => i.quantity > 0)
items.reduce(0, (acc, i) => acc + i.total)

A block body is permitted when multiple statements are needed:

items.map(i => {
  let subtotal = i.price * i.quantity
  let tax = calculate_tax(subtotal, i.tax_rate)
  return subtotal + tax
})

Is Expressions

IsExpr = Expression "is" TypeExpr
       | Expression "is" "null"
       | Expression "is" "not" "null" .

Tests the type or nullability of a value.

if value is String { ... }
if order.shipped_at is not null { ... }

Spread Expressions

SpreadExpr = ".." Expression .

Spreads the fields of a record into an object expression or the elements of a list into a list expression.

let updated = OrderItem { ..existing, quantity: 5 }
let all_items = [..first_batch, ..second_batch]

Method Chains

Grove supports fluent method chaining on built-in types. Methods are called using dot notation and can be chained without intermediate variables.

items
  .filter(i => i.in_stock)
  .map(i => i.price * i.quantity)
  .sum()

Common List Methods

MethodSignatureDescription
mapList<T>.map(T => U) -> List<U>Transform each element
filterList<T>.filter(T => Bool) -> List<T>Keep matching elements
reduceList<T>.reduce(U, (U, T) => U) -> UFold into a single value
findList<T>.find(T => Bool) -> T?First matching element
anyList<T>.any(T => Bool) -> BoolTrue if any element matches
allList<T>.all(T => Bool) -> BoolTrue if all elements match
sumList<Decimal>.sum() -> DecimalSum of numeric list
lengthList<T>.length -> IntNumber of elements
is_emptyList<T>.is_empty() -> BoolTrue if list has no elements
firstList<T>.first() -> T?First element or null
lastList<T>.last() -> T?Last element or null
flat_mapList<T>.flat_map(T => List<U>) -> List<U>Map and flatten
sort_byList<T>.sort_by(T => U) -> List<T>Sort by derived key
distinctList<T>.distinct() -> List<T>Remove duplicates
takeList<T>.take(Int) -> List<T>First N elements
skipList<T>.skip(Int) -> List<T>Skip first N elements
containsList<T>.contains(T) -> BoolTrue if element exists

Common String Methods

MethodSignatureDescription
to_upperString.to_upper() -> StringUppercase
to_lowerString.to_lower() -> StringLowercase
trimString.trim() -> StringStrip leading/trailing whitespace
starts_withString.starts_with(String) -> BoolPrefix test
ends_withString.ends_with(String) -> BoolSuffix test
containsString.contains(String) -> BoolSubstring test
splitString.split(String) -> List<String>Split by delimiter
replaceString.replace(String, String) -> StringReplace occurrences
lengthString.length -> IntCharacter count
substringString.substring(Int, Int) -> StringExtract range

Common Map Methods

MethodSignatureDescription
getMap<K,V>.get(K) -> V?Value for key or null
keysMap<K,V>.keys() -> List<K>All keys
valuesMap<K,V>.values() -> List<V>All values
entriesMap<K,V>.entries() -> List<{key: K, value: V}>Key-value pairs
contains_keyMap<K,V>.contains_key(K) -> BoolKey existence test
sizeMap<K,V>.size -> IntNumber of entries

See Also