Brevi language (.bv)
Reference
Details kept out of the core guide. brevi guide WORD prints only the lines about one name.
Details kept out of the core guide, which every session reads. brevi guide SECTION prints one section; brevi guide WORD prints every line about a name.
Strings#
Strings interpolate: "{name} is {age}", "{-x} {xs.len()} {p.0}" ({ followed by a letter, (, -, ! or " starts an expression, and strings may appear inside: "{m["k"]}"). Any other { is a plain brace; \{ and {{ always are, so "{{" is ONE brace (write '{{' for two); template tags such as "{{#each ", "{{/if}}" and "{{else}}" keep both braces.
Python format specs work inside {}: "{x:.2f} {n:03d} {name:<10} {total:>8,}".
Escapes: \n \t \r \0 \" \\ \{ \}.
'single quotes' need no \" and keep { plain unless a name or ( follows ({{ is two braces), so JSON and template text is easy: '{"id":{i},"error":"not found"}', t.starts_with('{{#each ').
"a".code() is 97 and 97.char() is "a". "7".digit() is 7 (-1 if not a digit). len counts characters; chars gives a list of one-character strings; words splits on any run of whitespace; ints() gives all whitespace-separated ints.
s.slice(a) runs to the end and s.slice(i, i + 1) is one character; negative bounds count from the end. s.int().or(-1) tests a number. (a, _, b) = s.partition("=").
Lists#
[0].repeat(n) is n zeros; unique sorts and drops repeats; (0..10).step(3) is [0, 3, 6, 9]; (0..n).rev() counts down. join(sep) takes any items.
xs.sort_by(f, desc) sorts largest first when desc is true, keeping ties in order (as sorted(xs, key=f, reverse=desc)).
first, last and pop return the item itself and stop the program on an empty list; min and max fail on an empty list. pop and remove(i) return the removed item. In map/filter/any/all/count/sort_by, (a, b) => b - a unpacks a tuple item. and/or short-circuit.
2.5.fmt(2) is "2.50"; f64 round(n) rounds to n places.
Type details#
let (a, b) = t or (a, b) = t destructures a tuple. Point(x, y) works as a constructor when variables x and y exist. A mut parameter is called as f(x). A return type is optional (fn f(x: int) = x * 2); write -> T if an error asks.
A missing map key reads as the type's zero (0, 0.0, "", [], dec 0). print(a, b) puts a space between.
Python#
Python spellings models write by habit are accepted: True False, a if c else b, range(a, b, step), for c in s, enumerate(xs) int(s) ord chr reversed, lambda x: x * 2, min(xs, key=f), sorted(xs, key=f, reverse=True), print(x, end="", sep=", "), append strip split() startswith endswith isdigit items index get(k, d), ", ".join(xs), && || ! for and or not. f"..." is a plain string (Brevi strings already fill in {x}). Also set(xs) (a sorted list without repeats) with s.add(x), xs.extend(ys), xs[::-1] and xs[::2], divmod(a, b), round(x) and round(x, 2), enumerate(xs, 1), m.setdefault(k, []).append(x), defaultdict(int), "%5.2f %s" % (x, s), "{:>5} {}".format(a, b), math.sqrt floor ceil, for line in sys.stdin; import sys, math, re and from collections import ... lines are ignored. Also rstrip() lstrip() strip(chars) splitlines() isupper() islower() isalnum(), heapq.heappush/heappop/heapify on a plain list, deque with popleft/appendleft, map(f, xs), filter(f, xs), list(xs), int/str/float/len as function values (map(int, xs), key=len) and sys.stdout.write(s). Decimal(x) is x.dec() and keeps its written places as Python does (Decimal("1.50")), with d.quantize(Decimal("0.01")) (half to even, or rounding=ROUND_HALF_UP); csv.reader(sys.stdin) or csv.reader(lines) gives the rows; date.fromisoformat(s) is date(s) and (d2 - d1).days counts the days between. next(rows) takes the first item; xs.pop(i) and m.pop(k) take an item out and give it back; sorted(s), enumerate(s, 1) and "-".join(s) read a string by character; sorted(m) gives a map's keys; OrderedDict() is a map. The re module works as in Python: re.compile(p), re.match, re.fullmatch, re.search, re.findall, re.sub, re.split, and on a compiled pattern pat.match(s) etc.; a match is [whole, group1, ...] ([], which is false, when none), so if m:, m.group(1) and m.groups() work. next(rows, None) gives the empty value when nothing is left; next(x for x in xs if c) the first match. Also zip(xs, ys), Counter(xs) with most_common(n), dict(pairs), sys.exit(n), s.count(sub), zfill title center capitalize, xs.clear(), sys.argv, sorted(m, key=f), date(y, m, d), ljust rjust, split(sep, n), rsplit(sep, n), list(s), set(a) - set(b), s.update(xs) / m.update(m2), datetime.date(y, m, d), datetime.strptime(s, "%Y-%m-%d"), timedelta(days=n), d.isoformat() toordinal() weekday() isoweekday(), calendar.monthrange isleap, format(x, spec), isascii(), Fraction(...) (as a decimal), to_integral_value(), m.setdefault(k, v), defaultdict(lambda: 0), enumerate(xs, start=1), bool(x), sys.setrecursionlimit(n), json.loads(s), json.load(sys.stdin), isinstance(v, T) and isinstance(v, (T1, T2)), import aliases (from datetime import date as Date, import datetime as dt), datetime.timedelta(...), string.digits / ascii_lowercase and the other string letter constants, repr(x), next(iter(xs)), groupby(xs, key=f), del xs[a:b]. A file with def, class, global or import lines prints values the Python way (True, ['a'], {'k': 1}).
Functions are values: ops = [add, mul], for (name, f) in [("+", add)]: f(1, 2), ops[0](3, 4). A map of functions works when at least one value is a named function: ops = {"+": add, "-": lambda a, b: a - b}, then ops[op](a, b).
None works for int, float, str and tuple values: best = None, if best is None or v > best: best = v, return None from -> int. It is a reserved value, not an Option, so it cannot hold a user type.
Tools#
brevi build f.bv -o app --release builds an optimized binary. Runtime errors print {"e":"runtime","line":N,"msg":...}.
brevi emit f.bv shows the Rust. --explain on any command adds the guide lines under each error. brevi check f.bv --fast skips rustc. del fn name in a patch file removes a function.
A fixed typo prints {"line":N,"fixed":"totl -> total"}.
allow read, write at the top declares what a program touches (read, write, native for rust fn, py fn, crates and Python libraries such as import subprocess); in a Python file write it as a comment, # allow read, write, so the file stays valid Python. Under brevi run --sandbox and in the MCP tools only the caller grants capabilities (--allow read,write,native, --allow-py requests; MCP allow, allow_py), and an allow line in the file grants nothing; they refuse anything not granted, except path text (os.path.join, pathlib.PurePath) and random, which Brevi builds itself without native; a program with an allow line is held to it on every command. A refusal is final: it never falls back to a dynamic build, a library call or python3.