Lexical rules#
Whitespace#
Spaces (' '), tabs ('\t'), and carriage returns ('\r') are insignificant and skipped
between tokens. Newlines ('\n') are significant tokens used as statement separators.
The parser skips newlines between statements but they are preserved in the token stream.
Semicolons (';') are also accepted as optional statement separators in statement-list
contexts (top-level items, block statements, tool bodies, and skill fields), but they
are non-canonical input syntax. harn fmt normalizes them back to newline-separated form.
Backslash line continuation#
A backslash (\) immediately before a newline joins the current line with the next.
Both the backslash and the newline are removed from the token stream, so the two
physical lines are treated as a single logical line by the lexer.
const total = 1 + 2 \
+ 3 + 4
// equivalent to: const total = 1 + 2 + 3 + 4
This is useful for breaking long expressions that do not involve a binary operator eligible for multiline continuation (see "Multiline expressions").
Comments#
// Line comment: everything until the next newline is ignored.
/* Block comment: can span multiple lines.
/* Nesting is supported. */
Still inside the outer comment. */
Block comments track nesting depth, so /* /* */ */ is valid. An unterminated block comment produces a lexer error.
Keywords#
The following identifiers are reserved:
| Keyword | Token |
|---|---|
pipeline | .pipeline |
extends | .extends |
override | .overrideKw |
let | .letKw |
const | .constKw |
var | .varKw (reserved for the removed-keyword migration diagnostic) |
if | .ifKw |
else | .elseKw |
for | .forKw |
in | .inKw |
match | .matchKw |
retry | .retry |
parallel | .parallel |
defer | .defer |
return | .returnKw |
import | .importKw |
true | .trueKw |
false | .falseKw |
nil | .nilKw |
try | .tryKw |
catch | .catchKw |
throw | .throwKw |
throws | .throwsKw |
finally | .finally |
fn | .fnKw |
emit | .emit |
spawn | .spawnKw |
while | .whileKw |
type | .typeKw |
enum | .enum |
eval_pack | .evalPack |
struct | .struct |
interface | .interface |
pub | .pub |
from | .from |
to | .to |
tool | .tool |
skill | .skill |
exclusive | .exclusive |
guard | .guard |
require | .require |
deadline | .deadline |
yield | .yield |
mutex | .mutex |
break | .break |
continue | .continue |
select | .select |
impl | .impl |
Identifiers#
An identifier starts with a letter or underscore, followed by zero or more letters, digits, or underscores:
identifier ::= [a-zA-Z_][a-zA-Z0-9_]*
Number literals#
int_literal ::= digit+
float_literal ::= digit+ '.' digit+
A number followed by . where the next character is not a digit is lexed as an integer
followed by the . operator (enabling 42.method).
Duration literals#
A duration literal is an integer followed immediately (no whitespace) by a time-unit suffix:
duration_literal ::= digit+ ('ms' | 's' | 'm' | 'h' | 'd' | 'w')
| Suffix | Unit | Equivalent |
|---|---|---|
ms | milliseconds | -- |
s | seconds | 1000 ms |
m | minutes | 60 s |
h | hours | 60 m |
d | days | 24 h |
w | weeks | 7 d |
Duration literals evaluate to an integer number of milliseconds. They can be used anywhere an expression is expected:
harness.clock.sleep_ms(500ms)
deadline 30s { /* ... */ }
const one_day = 1d // 86400000
const two_weeks = 2w // 1209600000
String literals#
Single-line strings#
string_literal ::= '"' (char | escape | interpolation)* '"'
escape ::= '\' ('n' | 'r' | 't' | '0' | '\\' | '"' | 'Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes
Lexical rules#
Whitespace#
Spaces (' '), tabs ('\t'), and carriage returns ('\r') are insignificant and skipped
between tokens. Newlines ('\n') are significant tokens used as statement separators.
The parser skips newlines between statements but they are preserved in the token stream.
Semicolons (';') are also accepted as optional statement separators in statement-list
contexts (top-level items, block statements, tool bodies, and skill fields), but they
are non-canonical input syntax. harn fmt normalizes them back to newline-separated form.
Backslash line continuation#
A backslash (\) immediately before a newline joins the current line with the next.
Both the backslash and the newline are removed from the token stream, so the two
physical lines are treated as a single logical line by the lexer.
const total = 1 + 2 \
+ 3 + 4
// equivalent to: const total = 1 + 2 + 3 + 4
This is useful for breaking long expressions that do not involve a binary operator eligible for multiline continuation (see "Multiline expressions").
Comments#
// Line comment: everything until the next newline is ignored.
/* Block comment: can span multiple lines.
/* Nesting is supported. */
Still inside the outer comment. */
Block comments track nesting depth, so /* /* */ */ is valid. An unterminated block comment produces a lexer error.
Keywords#
The following identifiers are reserved:
| Keyword | Token |
|---|---|
pipeline | .pipeline |
extends | .extends |
override | .overrideKw |
let | .letKw |
const | .constKw |
var | .varKw (reserved for the removed-keyword migration diagnostic) |
if | .ifKw |
else | .elseKw |
for | .forKw |
in | .inKw |
match | .matchKw |
retry | .retry |
parallel | .parallel |
defer | .defer |
return | .returnKw |
import | .importKw |
true | .trueKw |
false | .falseKw |
nil | .nilKw |
try | .tryKw |
catch | .catchKw |
throw | .throwKw |
throws | .throwsKw |
finally | .finally |
fn | .fnKw |
emit | .emit |
spawn | .spawnKw |
while | .whileKw |
type | .typeKw |
enum | .enum |
eval_pack | .evalPack |
struct | .struct |
interface | .interface |
pub | .pub |
from | .from |
to | .to |
tool | .tool |
skill | .skill |
exclusive | .exclusive |
guard | .guard |
require | .require |
deadline | .deadline |
yield | .yield |
mutex | .mutex |
break | .break |
continue | .continue |
select | .select |
impl | .impl |
Identifiers#
An identifier starts with a letter or underscore, followed by zero or more letters, digits, or underscores:
identifier ::= [a-zA-Z_][a-zA-Z0-9_]*
Number literals#
int_literal ::= digit+
float_literal ::= digit+ '.' digit+
A number followed by . where the next character is not a digit is lexed as an integer
followed by the . operator (enabling 42.method).
Duration literals#
A duration literal is an integer followed immediately (no whitespace) by a time-unit suffix:
duration_literal ::= digit+ ('ms' | 's' | 'm' | 'h' | 'd' | 'w')
| Suffix | Unit | Equivalent |
|---|---|---|
ms | milliseconds | -- |
s | seconds | 1000 ms |
m | minutes | 60 s |
h | hours | 60 m |
d | days | 24 h |
w | weeks | 7 d |
Duration literals evaluate to an integer number of milliseconds. They can be used anywhere an expression is expected:
harness.clock.sleep_ms(500ms)
deadline 30s { /* ... */ }
const one_day = 1d // 86400000
const two_weeks = 2w // 1209600000
String literals#
Single-line strings#
string_literal ::= '"' (char | escape | interpolation)* '"'
escape ::= '\' ('n' | 'r' | 't' | '0' | '\\' | '"' | '