Lexical Grammar
Grove source files are UTF-8 encoded text with the
.groveextension. The lexer breaks source text into a stream of tokens before parsing. This page defines every token class the Grove lexer recognizes, including keywords, identifiers, literals, operators, and comments.
Prerequisites: Familiarity with EBNF notation and general programming-language concepts. What you'll learn: The complete set of lexical elements that make up a valid Grove source file.
Source Encoding
Grove source files must be valid UTF-8. A byte-order mark (BOM) at the start of a file is permitted but ignored. Line endings may be LF (\n) or CRLF (\r\n); the lexer normalizes both to LF internally.
Comments
Grove supports two comment forms:
LineComment = "//" { any-char-except-newline } newline .
BlockComment = "/*" { any-char | newline } "*/" .
Block comments do not nest. Comments are treated as whitespace by the parser and carry no semantic meaning.
// This is a line comment
/* This is a
block comment */
Whitespace
Whitespace characters (space U+0020, horizontal tab U+0009, newline U+000A, carriage return U+000D) separate tokens but are otherwise insignificant. Grove is not indentation-sensitive.
Keywords
The following identifiers are reserved as keywords and cannot be used as user-defined names:
| Category | Keywords |
|---|---|
| Declarations | import, type, enum, record, event, action, validate, upcast, function, workflow, activity, service, route, query, trigger, schedule, webhook, test |
| Statements | let, if, else, for, in, return, emit, fail, publish |
| Types | Int, Decimal, Bool, String, Id, Date, Time, DateTime, Duration, Epoch, Value, Blob, List, Map |
| Literals | true, false, null |
| Operators | and, or, not, is |
| Modifiers | mut |
| Annotations | @create, @delete, @pii, @confidential |
Identifiers
Identifier = Letter { Letter | Digit | "_" } .
Letter = "a"..."z" | "A"..."Z" | "_" .
Digit = "0"..."9" .
Identifiers are case-sensitive. By convention:
- Type names, enum names, record names, and event names use
PascalCase. - Field names, function names, action names, and variable names use
snake_case. - Module names (directory names) use
snake_case.
Examples of valid identifiers: order_total, OrderCreated, _internal, x2.
Literals
Integer Literals
IntLiteral = DecimalInt .
DecimalInt = "0" | NonZeroDigit { Digit } .
NonZeroDigit = "1"..."9" .
Negative integers are expressed with the unary - operator applied to an integer literal: -42.
Examples: 0, 42, 1000.
Decimal Literals
DecimalLiteral = DecimalInt "." Digit { Digit } .
At least one digit must appear after the decimal point.
Examples: 3.14, 0.5, 100.00.
Boolean Literals
BoolLiteral = "true" | "false" .
String Literals
StringLiteral = '"' { StringChar } '"' .
StringChar = any-char-except-quote-or-backslash | EscapeSeq .
EscapeSeq = "\\" | '\"' | "\n" | "\t" | "\r" .
String literals are delimited by double quotes. Grove does not support single-quoted strings or raw string literals.
"hello world"
"line one\nline two"
"a quote: \"here\""
String Interpolation
String literals support interpolation with ${} syntax:
InterpolatedString = '"' { StringChar | "${" Expression "}" } '"' .
let name = "Grove"
let greeting = "Hello, ${name}!"
Null Literal
NullLiteral = "null" .
The null literal represents the absence of a value and is only valid where an optional type (T?) is expected.
Operators and Punctuation
Arithmetic Operators
| Operator | Meaning |
|---|---|
+ | Addition / string concatenation |
- | Subtraction / unary negation |
* | Multiplication |
/ | Division |
% | Modulo (remainder) |
Comparison Operators
| Operator | Meaning |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical Operators
| Operator | Meaning |
|---|---|
and | Logical AND (short-circuit) |
or | Logical OR (short-circuit) |
not | Logical NOT (prefix) |
is | Type / null check |
Assignment and Access
| Token | Meaning |
|---|---|
= | Assignment (in let) |
. | Member access |
?. | Optional chaining |
?? | Null coalescing |
Delimiters
| Token | Usage |
|---|---|
( ) | Grouping, function arguments |
{ } | Block bodies, object literals |
[ ] | List literals, index access |
< > | Type parameters |
, | Separator in lists, arguments, fields |
: | Type annotation, key-value separator |
-> | Return type annotation |
=> | Route / trigger / schedule handler arrow |
.. | Spread operator |
Token Precedence
When ambiguity arises, the lexer applies longest-match: it consumes the longest sequence of characters that forms a valid token. For example, >= is lexed as a single operator, not > followed by =.
Semicolons
Grove does not use semicolons as statement terminators. The lint command will warn if semicolons are present. Newlines serve as implicit statement separators where syntactically required.
See Also
- Declaration Grammar -- top-level syntactic forms
- Expression Grammar -- expression syntax and precedence
- Statement Grammar -- statement-level syntax
- Type System -- type names and composite type syntax