Bern Language Documentation

A comprehensive guide to the Bern programming language and its standard libraries

Basics

Literals

In Bern, literals are automatically interpreted and printed. You can write values directly and they'll be evaluated immediately:

"Hello, World!"
23
3.14
true

Variables

Variables are defined using the simple name = value syntax. Bern supports various data types including integers, doubles, strings, and booleans:

integer = 2
double = 3.14
helloworld = "Hello, World!"
isActive = true

Evaluation

Like literals, variables can be called directly and will be properly evaluated. Simply write the variable name to see its value:

helloworld
-- Output: "Hello, World!"

integer
-- Output: 2

Type Checking and Length

Bern provides two special operators for introspection:

  • :: (typeof) - Returns the type of a value as a string
  • :> (length) - Returns the length of a list, set, or string
typeof_integer = :: 2
-- Output: "Int"

typeof_string = :: "hello"
-- Output: "String"

length_list = :> [1,2,3,4] 
-- Output: 4

length_string = :> "hello"
-- Output: 5

Conditionals

Bern supports conditional execution through if-then-else statements. These allow your code to make decisions based on conditions.

If-Then-Else Statement

The basic conditional structure lets you execute different code based on whether a condition is true or false:

age = 18
if age >= 18 then
    "You are an adult."
else
    "You are a minor."
end
-- Output: "You are an adult."

Else-If Chains

For multiple conditions, use else if to check additional cases in sequence:

score = 85
if score >= 90 then
    "You got an A."
else if score >= 80 then
    "You got a B."
else if score >= 70 then
    "You got a C."
else
    "You need to improve."
end
-- Output: "You got a B."

Example: FizzBuzz

A classic programming challenge demonstrating nested conditionals:

numbers = [1..100]
for n : numbers do 
    if n % 15 == 0 then 
        "FizzBuzz"
    else if n % 3 == 0 then
        "Fizz"
    else if n % 5 == 0 then
        "Buzz"
    else
        n
    end
end

Functions

Bern functions are powerful and flexible, supporting pattern matching, inline arrow syntax, and block bodies with explicit returns.

Simple Function Definition

For one-line functions, use the arrow syntax def name(params) -> expr:

def add(x, y) -> x + y
add(2, 3)
-- Output: 5

def square(x) -> x * x
square(4)
-- Output: 16

Pattern Matching

Define multiple clauses for a function. Bern will use the first matching pattern:

def sign(0) -> "zero"
def sign(n) -> "positive"

sign(0)
-- Output: "zero"

sign(42)
-- Output: "positive"

Wildcards and Literal Matching

Use _ to ignore parameters, or match specific literal values:

def greet("Alice") -> "Hi, Alice!"
def greet(_) -> "Hello, someone else!"

greet("Alice")
-- Output: "Hi, Alice!"

greet("Bob")
-- Output: "Hello, someone else!"

Block-Bodied Functions

For complex functions, use do ... end blocks with explicit return statements:

def sumList(xs) do
    total = 0
    for n : xs do
        total = total + n
    end
    return total
end

sumList([1,2,3])
-- Output: 6

Lambda Functions

Anonymous functions use backslash syntax and can be assigned to variables or passed as arguments:

inc = \x -> x + 1
pairSwap = \a, b -> [b, a]

inc(10)
-- Output: 11

pairSwap(1, 2)
-- Output: [2, 1]

Higher-Order Functions

Functions can accept other functions as parameters:

def applyTwice(f, x) -> f(f(x))

applyTwice(\n -> n * 2, 5)
-- Output: 20 (5 * 2 * 2)

Loops

Bern uses Odin-inspired loop syntax where for is the only loop keyword. The behavior changes based on how you use it.

Repeat For

Execute a block a specific number of times:

for 3 do
    "Hello, Loops!"
end
-- Output: "Hello, Loops!" (three times)

Conditional For

Repeat while a condition is true (like a while loop):

counter = 2
for counter > 0 do
    "Hello, Loops!"
    counter = counter - 1
end
-- Output: "Hello, Loops!" (twice)

Infinite Loop

Create an infinite loop with for true:

for true do
    "This runs forever!"
end
Note: Be careful with infinite loops! Make sure you have a way to break out of them in your actual programs.

For-In Loop

Iterate over elements in lists, sets, or strings:

text = "Bern"
for char : text do
    "Current char is: " + char
end
-- Output: 
-- "Current char is: B"
-- "Current char is: e"
-- "Current char is: r"
-- "Current char is: n"

For-In with Index

Get both the element and its index during iteration:

text = "Bern"
for char, index : text do
    "Char: " + char + " at index: " + index
end
-- Output:
-- "Char: B at index: 0"
-- "Char: e at index: 1"
-- "Char: r at index: 2"
-- "Char: n at index: 3"

Lists

A list is an ordered collection of elements of the same type. Lists allow duplicates and preserve insertion order. In Bern, strings are also treated as lists of characters.

Declaring Lists

numbers_list = [1,2,3,4,5]
numbers_list
-- Output: [1,2,3,4,5]

Range Syntax

Create lists using Haskell-style range notation:

range_list = [5..10]
-- Output: [5,6,7,8,9,10]

small_range = [1..5]
-- Output: [1,2,3,4,5]

Indexing

Access specific elements using zero-based indexing:

range_list = [5..10]
range_list[3]
-- Output: 8 (fourth element)

List Arithmetic

Arithmetic operations with a scalar apply to every element:

[1,2] + 2
-- Output: [3,4]

[10,20,30] * 2
-- Output: [20,40,60]

Operations between two lists require equal lengths:

[1,2,3] + [4,5,6]
-- Output: [5,7,9]

[10,20] - [5,3]
-- Output: [5,17]

List Set Operations

Lists support set-like operations while preserving order and allowing duplicates:

  • <> - Union (concatenation)
  • </> - Difference (elements in first but not second)
  • |> - Intersection (common elements)
  • <| - Symmetric difference (elements in either but not both)
[1,2,3] <> [3,4,5]
-- Output: [1,2,3,3,4,5]

[1,2,3]  [2,3]
-- Output: [1]

[1,2,3] |> [2,3]
-- Output: [2,3]

[1,2,3] <| [2,3]
-- Output: [1]

Complex List Operations

result = ([1,2,3,4] <> [3..5]) <| ([2,3,4] |> [4,5])  [1]
-- Combines concatenation, symmetric difference, 
-- intersection, and regular difference

Sets

A set is an unordered collection of unique elements that can be of different types. Sets automatically remove duplicates and don't preserve order.

Declaring Sets

numbers_set = {1,2,3,4,5}
numbers_set
-- Output: {1,2,3,4,5}

mixed_set = {1, "hello", 3.14}
-- Sets can contain different types

Indexing

While sets are unordered, you can still access elements by index:

numbers_set = {1,2,3,4,5}
numbers_set[2]
-- Output: (some element from the set)

Set Arithmetic

Adding to a set inserts elements (if not already present):

{1,2} + 3
-- Output: {1,2,3}

{1,2} + {3,4}
-- Output: {1,2,3,4}

{1,2,3} + 2
-- Output: {1,2,3} (2 already exists)

Subtraction removes elements:

{1,2,3} - 2
-- Output: {1,3}

Set Operations

Mathematical set operations with automatic duplicate removal:

{1,2,3} <> {3,4,5}
-- Union: {1,2,3,4,5}

{1,2,'a',3.5} <> {3,4,'b',1}
-- Union with mixed types: {1,2,'a',3.5,3,4,'b'}

{1,2,3} |> {2,3,4}
-- Intersection: {2,3}

{1,2,3}  {2,3,4}
-- Difference: {1}

{1,2,3} <| {2,3}
-- Symmetric difference: {1}
Remember: Sets automatically remove duplicates and don't guarantee order. If you need ordered data or duplicates, use lists instead.

Objects

Bern supports objects (hashmaps/hashtables) using the #{...}# notation for key-value pairs.

Declaring Objects

obj = #{
    key: "value",
    hello: "world"
}#
obj
-- Output: #{key: "value", hello: "world"}#

Updating Values

Reassign values using bracket notation:

obj["key"] = "new_value"
obj
-- Output: #{key: "new_value", hello: "world"}#

Adding Fields

Add new key-value pairs by assigning to a new key:

obj["new_key"] = "added_value"
obj
-- Output: #{key: "new_value", hello: "world", new_key: "added_value"}#

Nested Objects

Objects can contain other objects. Access nested values with multiple indexing:

obj["test"] = #{
    nested_key: "nested_value"
}#

obj["test"]["nested_key"]
-- Output: "nested_value"

Complete Object Example

person = #{
    name: "Alice",
    age: 25,
    address: #{
        city: "Boston",
        zip: "02101"
    }#
}#

person["address"]["city"]
-- Output: "Boston"

Algebraic Data Types (ADTs)

ADTs allow you to define custom data types with multiple constructors, similar to enums or tagged unions in other languages.

Defining ADTs

Use the adt keyword followed by type name and constructors:

adt Shape = Circle Double | Rectangle Double Double

Each constructor can have zero or more fields of specific types.

Creating Instances

Call constructors like functions to create instances:

c = Circle(5.0)
r = Rectangle(3.0, 4.0)

Pattern Matching with ADTs

Destructure ADTs in function definitions to access their fields:

def area(Circle(r)) -> 3.14159 * r * r
def area(Rectangle(w, h)) -> w * h

area(c)
-- Output: 78.53975 (π * 5²)

area(r)
-- Output: 12.0 (3 * 4)

The Maybe Type

A common ADT pattern for representing optional values:

adt Maybe = Just Int | None

def safeDivide(n, 0) -> None()
def safeDivide(n, m) -> Just(n / m)

safeDivide(10, 3)
-- Output: Just(3)

safeDivide(5, 0)
-- Output: None()
Use Case: The Maybe type is perfect for operations that might fail, allowing you to handle success and failure cases explicitly without exceptions.

Standard Library Reference

Bern comes with several standard libraries and bundled vendor modules. Import standard libraries with import library_name and vendor modules with full paths like import vendor/csv.

core - Core Standard Library

The foundational library providing list operations, functional programming utilities, and type conversions.

import core

List Operations

map(list, function) → list

Apply a function to each element of a list.

map([1,2,3], \x -> x * 2)
-- Output: [2,4,6]
filter(list, predicate) → list

Keep only elements where predicate returns true.

filter([1,2,3,4,5], \x -> x % 2 == 0)
-- Output: [2,4]
foldl(list, accumulator, function) → value

Reduce a list from left to right.

foldl([1,2,3], 0, \acc, x -> acc + x)
-- Output: 6
foldr(list, accumulator, function) → value

Reduce a list from right to left.

Aggregate Functions

sum(list) → number

Sum all elements in a list.

sum([1,2,3,4])
-- Output: 10
product(list) → number

Multiply all elements in a list.

min(list) → number

Find the minimum value in a list.

max(list) → number

Find the maximum value in a list.

List Manipulation

reverse(collection) → collection

Reverse a list or set. Automatically detects type.

reverse([1,2,3])
-- Output: [3,2,1]
take(n, list) → list

Take first n elements from a list.

drop(n, list) → list

Drop first n elements from a list.

head(list) → element

Get the first element of a list.

tail(list) → list

Get all elements except the first.

Predicates

isEmpty(list) → boolean

Check if a list is empty.

any(list, predicate) → boolean

Check if any element satisfies the predicate.

all(list, predicate) → boolean

Check if all elements satisfy the predicate.

find(list, predicate) → element | false

Return first element matching predicate, or false if none found.

Combining Lists

zip(list1, list2) → list

Combine two lists into pairs.

zip([1,2,3], ['a','b','c'])
-- Output: [[1,'a'], [2,'b'], [3,'c']]
zipWith(list1, list2, function) → list

Combine two lists using a function.

Utility Functions

typeOf(value) → string

Get the type of a value (abstraction for ::).

length(value) → int

Get length of string, list, or set (abstraction for :>).

index_of(collection, target) → int

Find the index of an element in a list or set. Returns -1 if not found.

to_int(string) → int

Convert a string to an integer.

to_int("42")
-- Output: 42
int_to_double(int) → double

Convert an integer to a double.

math - Mathematical Functions

Comprehensive math library inspired by C's math.h, providing constants, basic operations, and transcendental functions.

import math

Constants

  • pi = 3.141592653589793
  • tau = 6.283185307179586 (2π)
  • e = 2.718281828459045

Basic Operations

abs(x) → number

Absolute value of a number.

fabs(x) → double

Alias for abs (mirrors C's fabs).

fmin(a, b) → double

Return the smaller of two numbers.

fmax(a, b) → double

Return the larger of two numbers.

clamp(x, lo, hi) → number

Constrain x to the range [lo, hi].

fmod(x, y) → number

Floating-point remainder (C's fmod).

Power and Root Functions

pow(base, exponent) → number

Raise base to the power of exponent. Uses integer exponentiation by squaring.

pow(2, 8)
-- Output: 256
sqrt(x) → double

Square root using Newton-Raphson method.

hypot(a, b) → double

Hypotenuse length: √(a² + b²)

Exponential and Logarithmic

exp(x) → double

Exponential function e^x using Taylor series.

log(x) → double

Natural logarithm (base e) using Newton's method.

log10(x) → double

Base-10 logarithm.

Trigonometric Functions

sin(radians) → double

Sine of angle in radians.

cos(radians) → double

Cosine of angle in radians.

tan(radians) → double

Tangent of angle in radians.

atan(z) → double

Inverse tangent.

atan2(y, x) → double

Two-argument arctangent respecting quadrants.

Angle Conversion

toRadians(degrees) → double

Convert degrees to radians.

toDegrees(radians) → double

Convert radians to degrees.

strings - String Manipulation

Comprehensive string processing utilities for common text operations.

import strings

String Extraction

substring(string, start, end) → string

Extract a portion of a string from start to end index.

substring("Hello", 1, 4)
-- Output: "ell"
char_at(string, index) → char

Get character at specific index.

String Splitting and Joining

split(string, delimiter) → list

Split a string by delimiter into a list of strings.

split("a,b,c", ",")
-- Output: ["a", "b", "c"]

Case Conversion

to_upper(string) → string

Convert string to uppercase.

to_lower(string) → string

Convert string to lowercase.

char_to_upper(char) → char

Convert single character to uppercase.

char_to_lower(char) → char

Convert single character to lowercase.

String Modification

trim(string) → string

Remove leading and trailing whitespace, newlines, and tabs.

trim("  hello  ")
-- Output: "hello"
replace(string, old, new) → string

Replace all occurrences of old substring with new substring.

replace("hello world", "world", "Bern")
-- Output: "hello Bern"

String Searching

contains(string, substring) → boolean

Check if string contains substring.

starts_with(string, prefix) → boolean

Check if string starts with prefix.

ends_with(string, suffix) → boolean

Check if string ends with suffix.

random - Random Number Generation

Cross-platform random number generation using foreign function interface to system libraries.

Note: Only available in Bern versions 0.1.2 and above.
import random
Note: The random library automatically detects your operating system and uses the appropriate C library (msvcrt.dll on Windows, libc.so.6 on Unix-like systems).

Random Number Generation

get_random_int() → int

Generate a random integer.

get_random_int()
-- Output: (random integer)
get_random_double() → double

Generate a random double between 0 and 1.

get_random_number() → number

Generate either a random int or random double (randomly chosen).

Random Selection

random_choice(list) → element

Return a random element from a list.

random_choice(["apple", "banana", "cherry"])
-- Output: (random fruit)

Random Strings

get_random_string(length) → string

Generate a random alphanumeric string of specified length.

get_random_string(10)
-- Output: "aB3xK9mP2q" (example)

assert - Testing Utilities

Simple assertion functions for writing tests in Bern.

import assert

Assertion Functions

assert_equals(name, got, expected) → string

Assert that two values are equal. Returns "PASS" or "FAIL" with test name.

assert_equals("Addition test", 2 + 2, 4)
-- Output: "Addition test PASS"

assert_equals("Wrong test", 2 + 2, 5)
-- Output: "Wrong test FAIL"
assert_approx(name, got, expected, tolerance) → string

Assert that two numbers are approximately equal within a tolerance.

assert_approx("Pi test", 3.14159, 3.14, 0.01)
-- Output: "Pi test PASS"
approx_eq(a, b, tolerance) → boolean

Check if two numbers are approximately equal. Helper function for assert_approx.

path - Filesystem Path Utilities

Utilities for joining, normalizing, and inspecting filesystem paths.

import path

Path Building

path_join(left, right) → string

Join two path segments with a single separator.

path_join("a/", "/b")
-- Output: "a/b"
path_join_many(parts) → string

Join a list of path segments in order.

path_join_many(["a", "b", "c"])
-- Output: "a/b/c"

Path Inspection

path_is_absolute(path) → boolean

Check if a path is absolute (supports Unix and drive-prefixed paths).

path_basename(path) → string

Return the last segment of a path.

path_dirname(path) → string

Return the parent directory path.

path_extname(path) → string

Return extension including leading dot, or empty string.

path_stem(path) → string

Return basename without extension.

Normalization

path_normalize(path) → string

Resolve redundant separators and dot segments like . and ...

path_normalize("a/./b/../c")
-- Output: "a/c"

path_normalize("/a//b/../c")
-- Output: "/a/c"

url - URL Encoding and Parsing

Helpers for URL encoding, decoding, query processing, and full URL parsing/building.

import url

Component Encoding

url_encode_component(text) → string

Percent-encode a URL component.

url_encode_component("a b&c")
-- Output: "a%20b%26c"
url_decode_component(text) → string

Decode a percent-encoded URL component.

Query Utilities

build_query(pairs) → string

Build query text from key/value pairs.

build_query([["a", "1"], ["name", "bern lang"]])
-- Output: "a=1&name=bern%20lang"
parse_query(query) → object

Parse query string into an object of decoded key/value pairs.

Full URL Operations

parse_url(url_text) → object

Parse URL into scheme, host, port, path, query, and fragment.

parsed = parse_url("http://example.com:8080/docs/api?x=1#top")
parsed["host"]
-- Output: "example.com"
build_url(scheme, host, port, path, query, fragment) → string

Build a URL from components (default ports are omitted when applicable).

build_url("https", "example.com", 443, "/hello", "a=1", "frag")
-- Output: "https://example.com/hello?a=1#frag"

crypto - Token and Hash Utilities

Pure-Bern helpers for token generation, checksums, and UUID-like identifiers.

Note: These helpers are useful for IDs/checksums, but are not a replacement for modern audited cryptographic algorithms.
import crypto

Random Utilities

crypto_random_token(length) → string

Generate a random token string with the given length.

crypto_random_hex(byte_count) → string

Generate random hex text (2 characters per requested byte).

crypto_random_hex(8)
-- Output: (16 hex chars)

Hashing and Comparison

crypto_checksum32(text) → int

Compute a 32-bit checksum for text.

crypto_hash32_hex(text) → string

Checksum represented as an 8-character hexadecimal string.

crypto_secure_compare(a, b) → boolean

Compare two strings in fixed-length iteration style to reduce timing leakage.

UUID-Like IDs

crypto_uuid_v4_like() → string

Create a random UUID v4-like identifier string.

uuid = crypto_uuid_v4_like()
-- Output: "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"

vendor/csv - CSV Parsing and Writing

Bundled module for reading and writing CSV data from strings or files.

import vendor/csv

Reading and Parsing

parse_csv(content, delimiter, has_header, skip_empty_lines) → object | rows

Parse CSV text. With headers enabled, returns objects keyed by header names.

csv = "name,age\nAna,30\nBob,25"
rows = parse_csv(csv)
rows["0"]["name"]
-- Output: "Ana"
read_csv(file_name, delimiter, has_header, skip_empty_lines) → object | rows

Read a CSV file and parse it in one step.

parse_csv_rows(content, delimiter, skip_empty_lines) → rows

Parse raw rows without converting to header-keyed objects.

Writing

write_csv_from_list(headers, data_list, file_name, delimiter) → void

Write a list-of-lists dataset to CSV.

write_csv_from_objects(headers, data_object, file_name, delimiter) → void

Write an indexed object of rows to CSV using specified header order.

vendor/http/http - Lightweight HTTP Server

FFI-backed HTTP server module for static route responses from Bern.

Note: Requires the platform binary in lib/vendor/http (for example linux/libbern_http.so or windows/bern_http.dll).
import vendor/http/http

Server Lifecycle

http_server_start(port) → ptr

Start a server bound to 0.0.0.0.

http_server_start_on(port, host) → ptr

Start a server on a specific host address.

http_server_stop(server) → boolean

Stop a running server.

http_server_running(server) → boolean

Check whether server is still running.

Routes and Responses

http_server_set_default(server, status, content_type, body) → boolean

Set fallback response for unmatched routes.

http_server_get(server, path, body) → boolean

Add a simple GET route with plain-text response.

http_server_add_route_html(server, path, html) → boolean

Add a GET route with HTML response.

http_server_add_route_json(server, path, json) → boolean

Add a GET route with JSON response.

http_server_poll(server, timeout_ms) → int

Run one poll tick; useful for controlled event loops.

http_server_serve_forever(server, poll_timeout_ms) → void

Keep polling while the server is running.

server = http_server_start(8080)
http_server_get(server, "/", "hello from bern http")
http_server_add_route_json(server, "/health", "{\"ok\":true}")
http_server_serve_forever(server, 50)

Imports

Bern allows importing libraries to extend functionality. Use the import keyword to load standard libraries or custom modules.

Importing the Core Library

import core

Core Library Functions

Once imported, you can use functions like map, filter, and type conversions:

map([1,2,3,4,5], \x -> x + 2)
-- Output: [3,4,5,6,7]

def isEven(x) -> x % 2 == 0
filter([1,2,3,4,5,6,7,8,9,10], isEven)
-- Output: [2,4,6,8,10]

Type Conversion

The core library provides conversion functions for handling input:

import core
age_text = input("Type your age: ")
age = to_int(age_text)
"Your age in 5 years: " + (age + 5)

User Input

Capture user input with the input() function, similar to Python.

Basic Input

name = input("Type your name: ")
name
-- Displays whatever the user typed

Converting Input

By default, input() returns text. Convert to other types using core library functions:

import core
age_text = input("Type your age: ")
age = to_int(age_text)
"You'll be " + (age + 10) + " in 10 years"

Global Functions

Bern provides built-in functions for file operations and system utilities.

File Operations

  • read_file(path) - Read file contents as a string
  • write_file(path, content) - Write content to a file
  • get_current_dir() - Get the current working directory
content = read_file("myfile.txt")
write_file("output.txt", "Hello, Bern!")
write_file("data.txt", content + "\nNew line")

System Information

Note: Only available in Bern versions 0.1.2 and above.

Returns the system the user is running. Useful for bindings.

os = get_host_machine()
-- Returns OS/system information

fmap

Note: Only available in Bern versions 0.1.2 and above.

Applies a function to each element of a collection. Used too in Algebraic Data Types (ADTs) to modify contained values without breaking functorial context.

ast Maybe = Just Int | None
fmap(Just(5), \x -> x * 2)

-- Output: Just(10)

C Bindings

Note: Only available in Bern versions 0.1.2 and above.

You can call C functions directly from Bern using the foreign function interface (FFI). This allows you to leverage existing C libraries and system calls.

For this, you need to declare the C function signature using the foreign keyword, specifying the library, its function name, argument types and return type.

foreign example("lib.so", "int") -> "void"

Recommendation: Use get_host_machine() inside an auxiliary function to get the correct library path for multiple operating systems.

def get_path() do
    os = get_host_machine()
    if os == "mingw64" || os == "mingw32" then
        return "windows_lib.dll"
    else
        return "linux_lib.so.6"
    end
end