Grammar
Grammar🔗
An informal grammar for the Bop language, plus the complete list of reserved words.
Reserved words🔗
let const pub fn return
if else while for in repeat break continue
use as struct enum match try
true false noneThese can’t be used as variable or function names. There are no word-spelled logical operators — use &&, ||, ! rather than and, or, not.
Built-in functions🔗
Always in scope; can’t be shadowed by user fns. See Built-in Functions for details.
| Function | Returns | Description |
|---|---|---|
print(args...) | none | Host-captured output |
range(n) / range(s, e) / range(s, e, step) | array | Integer range |
rand(n) | int | Pseudo-random 0..n |
try_call(f) | Result | Run f, return Ok(v) or Err(RuntimeError) |
panic(message) | never returns | Raise a non-fatal runtime error carrying message |
All math and conversion operations are methods on values: x.type(), x.to_str(), x.to_int(), x.to_float(), x.abs(), a.min(b), x.sqrt(), x.len(), etc.
Grammar🔗
Informal EBNF. Whitespace is insignificant except for newlines, which auto-insert semicolons (see below).
program = statement*
statement = letDecl | constDecl | assign | ifStmt | whileStmt | repeatStmt
| forStmt | fnDecl | returnStmt | breakStmt | continueStmt
| useStmt | structDecl | enumDecl | methodDecl
| exprStmt
letDecl = "let" IDENT "=" expr
constDecl = "const" IDENT "=" expr
assign = target ("=" | "+=" | "-=" | "*=" | "/=" | "%=") expr
target = IDENT | postfix "[" expr "]" | postfix "." IDENT
ifStmt = "if" expr block ("else" "if" expr block)* ("else" block)?
whileStmt = "while" expr block
repeatStmt = "repeat" expr block
forStmt = "for" IDENT "in" expr block
fnDecl = "pub"? "fn" IDENT "(" params? ")" block
returnStmt = "return" expr?
breakStmt = "break"
continueStmt = "continue"
useStmt = "use" path
| "use" path "." "{" IDENT ("," IDENT)* "}"
| "use" path "as" IDENT
| "use" path "." "{" IDENT ("," IDENT)* "}" "as" IDENT
path = IDENT ("." IDENT)*
structDecl = "struct" IDENT "{" fields? "}"
fields = IDENT ("," IDENT)*
enumDecl = "enum" IDENT "{" variants? "}"
variants = variant ("," variant)*
variant = IDENT // unit
| IDENT "(" IDENT ("," IDENT)* ")" // tuple
| IDENT "{" IDENT ("," IDENT)* "}" // struct
methodDecl = "fn" IDENT "." IDENT "(" params ")" block
exprStmt = expr
block = "{" statement* "}"
expr = or
or = and ("||" and)*
and = equality ("&&" equality)*
equality = comparison (("==" | "!=") comparison)*
comparison = addition (("<" | ">" | "<=" | ">=") addition)*
addition = multiply (("+" | "-") multiply)*
multiply = unary (("*" | "/" | "%") unary)*
unary = ("!" | "-" | "try") unary | postfix
postfix = primary (call | index | field | method | structLit | variantCtor)*
call = "(" args? ")"
index = "[" expr "]"
field = "." IDENT
method = "." IDENT "(" args? ")"
structLit = "{" (IDENT ":" expr ("," IDENT ":" expr)*)? "}" // only at expr position
variantCtor = "::" IDENT payload?
payload = "(" expr ("," expr)* ")" // tuple variant
| "{" IDENT ":" expr ("," IDENT ":" expr)* "}" // struct variant
primary = INT | NUMBER | STRING | "true" | "false" | "none"
| IDENT | resultShorthandExpr | "(" expr ")" | arrayLit | dictLit
| ifExpr | matchExpr | fnExpr
resultShorthandExpr = ("Ok" | "Err") "(" expr ("," expr)* ")" // sugar for Result::Ok/Err
arrayLit = "[" (expr ("," expr)* ","?)? "]"
dictLit = "{" (STRING ":" expr ("," STRING ":" expr)* ","?)? "}"
ifExpr = "if" expr "{" expr "}" "else" "{" expr "}"
matchExpr = "match" expr "{" arm ("," arm)* ","? "}"
arm = pattern ("if" expr)? "=>" expr
fnExpr = "fn" "(" params? ")" block
pattern = orPattern
orPattern = singlePattern ("|" singlePattern)*
singlePattern = "_" | IDENT | literal | variantPattern | structPattern | arrayPattern
| resultShorthand
variantPattern = (IDENT ".")? IDENT "::" IDENT
| (IDENT ".")? IDENT "::" IDENT "(" pattern ("," pattern)* ")"
| (IDENT ".")? IDENT "::" IDENT "{" IDENT ":" pattern ("," IDENT ":" pattern)* "}"
resultShorthand = ("Ok" | "Err") "(" pattern ("," pattern)* ")" // sugar for Result::Ok/Err
structPattern = (IDENT ".")? IDENT "{" IDENT ":" pattern ("," IDENT ":" pattern)* "}"
arrayPattern = "[" patternList? arrayRest? "]"
patternList = pattern ("," pattern)*
arrayRest = ".." | ".." IDENT
params = IDENT ("," IDENT)*
args = expr ("," expr)*INT is an exact signed 64-bit integer after unary parsing. Decimal magnitudes through 9223372036854775807 are ordinary primary expressions; the boundary spelling -9223372036854775808 is accepted when unary - directly owns that magnitude, including in literal patterns. A bare 9223372036854775808, 0 - 9223372036854775808, or any larger magnitude is out of range rather than being converted to a floating-point number.
Note: pub is accepted only on a named fn at the direct program root; it marks an entry for the stateful embedding ABI. It is not valid on methods, function expressions, or declarations nested in a block or callable.
methodDecl, enum variant IDENTs, and struct names must start with an uppercase letter. IDENT bound by let, fn, parameters, for, etc. must start with lowercase or _. const names must be all-caps. Mis-shaped declarations parse-error with a “did you mean?” suggestion — see Variables.
Automatic semicolons🔗
Bop automatically inserts a semicolon at the end of a line if the last token is one of:
- An identifier or literal (
int,number,string) true,false,nonebreak,continue,return),],}
Newlines do not insert semicolons while the innermost open delimiter is ( or [. This makes calls, conditions, array literals, and index expressions safe to lay out over multiple lines. A newline immediately before a closing ), ], or } is also ignored, so the final item in a multiline literal does not require a trailing comma.
Braces remain statement-capable even when their block is nested inside parentheses or brackets. Newlines between statements in a function or control-flow block still insert semicolons:
let callbacks = [
fn() {
let x = 1
return x
},
]A line starting with . continues a preceding value, including across blank lines or comments:
let size = values
// Continue the same expression.
.len()
.to_str()return itself remains a semicolon trigger. A newline immediately after it therefore means a bare return whose value is none; put the value on the same line, or open a parenthesized expression on that line when it needs multiline layout:
return (
left +
right
)This means the opening { of a block must be on the same line as its keyword:
// Correct
if x > 3 {
print("yes")
}
// Wrong — semicolon inserted after "3"
if x > 3
{
print("yes")
}You can also separate statements on the same line with an explicit ;:
let x = 1; let y = 2Comments🔗
// starts a line comment — everything to the end of line is ignored:
// Whole-line comment
let x = 5 // Inline trailing commentThere’s no block-comment syntax.
String interpolation🔗
Inside double-quoted strings, {identifier} inserts the value of a variable. Only plain variable names are allowed — no expressions, operators, or function calls:
let name = "Alice"
print("Hello, {name}!") // works
// print("Hello, {1 + 2}!") // error — expressions not allowedUse \{ and \} for literal braces in strings. Other supported escapes: \", \\, \n, \t, \r.