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.
| Precedence | Operators | Description | Associativity |
|---|---|---|---|
| 1 | . ?. () [] | Member access, call, index | Left |
| 2 | - (unary), not | Negation, logical NOT | Right (prefix) |
| 3 | * / % | Multiplicative | Left |
| 4 | + - | Additive | Left |
| 5 | < > <= >= | Relational | Left |
| 6 | == != | Equality | Left |
| 7 | is | Type / null test | Left |
| 8 | and | Logical AND | Left |
| 9 | or | Logical OR | Left |
| 10 | ?? | Null coalescing | Right |
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
| Method | Signature | Description |
|---|---|---|
map | List<T>.map(T => U) -> List<U> | Transform each element |
filter | List<T>.filter(T => Bool) -> List<T> | Keep matching elements |
reduce | List<T>.reduce(U, (U, T) => U) -> U | Fold into a single value |
find | List<T>.find(T => Bool) -> T? | First matching element |
any | List<T>.any(T => Bool) -> Bool | True if any element matches |
all | List<T>.all(T => Bool) -> Bool | True if all elements match |
sum | List<Decimal>.sum() -> Decimal | Sum of numeric list |
length | List<T>.length -> Int | Number of elements |
is_empty | List<T>.is_empty() -> Bool | True if list has no elements |
first | List<T>.first() -> T? | First element or null |
last | List<T>.last() -> T? | Last element or null |
flat_map | List<T>.flat_map(T => List<U>) -> List<U> | Map and flatten |
sort_by | List<T>.sort_by(T => U) -> List<T> | Sort by derived key |
distinct | List<T>.distinct() -> List<T> | Remove duplicates |
take | List<T>.take(Int) -> List<T> | First N elements |
skip | List<T>.skip(Int) -> List<T> | Skip first N elements |
contains | List<T>.contains(T) -> Bool | True if element exists |
Common String Methods
| Method | Signature | Description |
|---|---|---|
to_upper | String.to_upper() -> String | Uppercase |
to_lower | String.to_lower() -> String | Lowercase |
trim | String.trim() -> String | Strip leading/trailing whitespace |
starts_with | String.starts_with(String) -> Bool | Prefix test |
ends_with | String.ends_with(String) -> Bool | Suffix test |
contains | String.contains(String) -> Bool | Substring test |
split | String.split(String) -> List<String> | Split by delimiter |
replace | String.replace(String, String) -> String | Replace occurrences |
length | String.length -> Int | Character count |
substring | String.substring(Int, Int) -> String | Extract range |
Common Map Methods
| Method | Signature | Description |
|---|---|---|
get | Map<K,V>.get(K) -> V? | Value for key or null |
keys | Map<K,V>.keys() -> List<K> | All keys |
values | Map<K,V>.values() -> List<V> | All values |
entries | Map<K,V>.entries() -> List<{key: K, value: V}> | Key-value pairs |
contains_key | Map<K,V>.contains_key(K) -> Bool | Key existence test |
size | Map<K,V>.size -> Int | Number of entries |
See Also
- Lexical Grammar -- token definitions
- Statement Grammar -- statements that contain expressions
- Type System -- type expressions referenced in
isand casts - Declaration Grammar -- declaration contexts where expressions appear