I/O

Contains simple I/O primitives.

input(prompt?)

Read a line from standard input, printing the optional argument prompt if provided.

var name = io.input("Enter your name> ")
io.println("Hello, {name}!")
$ bs demo.bs
Enter your name> Batman
Hello, Batman!

print(...)

Print all the arguments to standard output.

io.print("Hello", "world", 69)
$ bs demo.bs
Hello world 69⏎

The arguments are separated with spaces.

println(...)

Print all the arguments to standard output, with a following newline.

eprint(...)

Print all the arguments to standard error.

eprintln(...)

Print all the arguments to standard error, with a following newline.

readdir(path)

Read a directory into an array of DirEntry.

Returns nil if failed.

var entries = assert(io.readdir("."))

for _, e in entries {
    io.println(e.name(), e.isdir())
}
$ bs demo.bs
.git true
include true
demo.bs false
compile_flags.txt false
thirdparty true
lib true
tests true
.gitattributes false
README.md false
bin true
docs true
build true
.gitignore false
examples true
src true
LICENSE false
.github true

readfile(path, binary?)

Read a file into a string.

If the argument binary is provided to be true, then the file is opened in binary mode.

Returns nil if failed.

var contents = io.readfile("input.txt")
if !contents {
    io.eprintln("Error: could not read file 'input.txt'")
    os.exit(1)
}

io.print(contents)
$ bs demo.bs
Just a test file
Nothing to see here
Foo
Bar
Baz
People's dreams have no end! ~ Blackbeard

Reader(path, binary?)

Native C class that opens path in readable mode.

If the argument binary is provided to be true, then the file is opened in binary mode.

Returns nil if failed.

var f = io.Reader("input.txt")
if !f {
    io.eprintln("Error: could not read file!")
    os.exit(1)
}

var contents = f.read()
io.print(contents)

Create a file input.txt with some sample text and run it.

$ bs demo.bs
Just a test file
Nothing to see here
Foo
Bar
Baz
People's dreams have no end! ~ Blackbeard

Reader.close()

Close the file.

This is done automatically by the garbage collector, so can be omitted for short programs.

Reader.read(count?)

Read count bytes from the current position. If the argument count is not provided, it reads all of the available bytes.

Returns nil if failed.

var f = io.Reader("input.txt")
if !f {
    io.eprintln("Error: could not read file!")
    os.exit(1)
}

io.print("The first 16 bytes: [{f.read(16)}]\n")
io.print("The rest:", f.read())
$ bs demo.bs
The first 16 bytes: [Just a test file]
The rest:
Nothing to see here
Foo
Bar
Baz
People's dreams have no end! ~ Blackbeard

Reader.readln()

Read a line.

var f = io.Reader("input.txt")
if !f {
    io.eprintln("Error: could not read file!")
    os.exit(1)
}

while !f.eof() {
    var line = f.readln()
    io.println("Line:", line)
}
$ bs demo.bs
Line: Just a test file
Line: Nothing to see here
Line: Foo
Line: Bar
Line: Baz
Line: People's dreams have no end! ~ Blackbeard
Line:

Reader.eof()

Return whether the end of file has been reached.

Reader.seek(offset, whence)

Change the read position of the file.

Any of the following values can be used for whence.

- SEEK_SET - Seek from beginning of file - SEEK_CUR - Seek from current position - SEEK_END - Seek from end of file

This function only works if the file was opened in binary mode.

Returns true if succeeded and false if failed.

var f = io.Reader("input.txt", true)
if !f {
    io.eprintln("Error: could not read file!")
    os.exit(1)
}

io.print("The first 16 bytes: [{f.read(16)}]\n")

f.seek(5, io.SEEK_SET)

io.println("The full content offset by 5:")
io.print(f.read())
$ bs demo.bs
The first 16 bytes: [Just a test file]
The full content offset by 5:
a test file
Nothing to see here
Foo
Bar
Baz
People's dreams have no end! ~ Blackbeard

Reader.tell()

Get the current position of the file.

This function only works if the file was opened in binary mode.

Returns nil if failed.

var f = io.Reader("input.txt", true)
if !f {
    io.eprintln("Error: could not read file!")
    os.exit(1)
}

io.println("The first 3 lines:")
for i in 0..3 {
    io.println(f.readln())
}

io.println()
io.println("Read {f.tell()} bytes so far.")
$ bs demo.bs
The first 3 lines:
Just a test file
Nothing to see here
Foo

Read 41 bytes so far.

Writer(path, binary?)

Native C class that opens path in writeable mode.

If the argument binary is provided to be true, then the file is opened in binary mode.

Returns nil if failed.

var f = io.Writer("output.txt")
if !f {
    io.eprintln("Error: could not create file!")
    os.exit(1)
}

f.writeln("Hello there!")
f.writeln("General Kenobi!")
f.writeln(69, "Nice!")
$ bs demo.bs
$ cat output.txt # type output.txt on windows
Hello there!
General Kenobi!
69 Nice!

Writer.close()

Close the file.

This is done automatically by the garbage collector, so can be omitted for short programs.

Writer.flush()

Flush the contents of the file, since I/O is buffered in C, which is the language BS is written in.

Writer.write(...)

Write all the arguments into the file.

Returns false if any errors were encountered, else true.

Writer.writeln(...)

Write all the arguments into the file, with a following newline.

Returns false if any errors were encountered, else true.

DirEntry()

Native C class that wraps over a directory entry. This doesn't really do anything, and only serves as an implementation detail for readdir(). Attempt to call this constructor directly will throw a runtime error.

Example usecase:

var entries = assert(io.readdir("."))

for _, e in entries {
    io.println(e.name(), e.isdir())
}
$ bs demo.bs
.git true
include true
demo.bs false
compile_flags.txt false
thirdparty true
lib true
tests true
.gitattributes false
README.md false
bin true
docs true
build true
.gitignore false
examples true
src true
LICENSE false
.github true

DirEntry.name()

Get the name of the entry.

DirEntry.isdir()

Get whether the entry is a directory.

stdin

Reader for standard input.

stdout

Writer for standard output.

stderr

Writer for standard error.

OS

Contains simple primitives for OS functions.

exit(code)

Halt the BS runtime with exit code code.

This doesn't actually exit the process itself in embedded usecase. It just halts the BS interpreter, and the caller of the virtual machine can decide what to do.

os.exit(69)
$ bs demo.bs
$ echo $? # echo %errorlevel% on windows
69

clock()

Get the monotonic time passed since boot.

This function provides time with nanosecond level of precision but in the unit of seconds.

fn fib(n) {
    if n < 2 {
        return n
    }

    return fib(n - 1) + fib(n - 2)
}

var start = os.clock()
io.println(fib(30))

var elapsed = os.clock() - start
io.println("Elapsed: {elapsed}")
$ bs demo.bs
832040
Elapsed: 0.137143968999226

sleep(seconds)

Sleep for seconds interval, with nanosecond level of precision.

var start = os.clock()
os.sleep(0.69)

var elapsed = os.clock() - start
io.println("Elapsed: {elapsed}")
$ bs demo.bs
Elapsed: 0.690292477000185

getenv(name)

Get the environment variable name.

Returns nil if it doesn't exist.

io.println(os.getenv("FOO"))
io.println(os.getenv("BAR"))
$ export FOO=lmao # set FOO=lmao on windows
$ bs demo.bs
lmao
nil

setenv(name, value)

Set the environment variable name to value.

Returns true if successful, else false.

os.setenv("FOO", "lmao")
io.println(os.getenv("FOO"))
$ bs demo.bs
lmao

getcwd()

Get the current working directory.

io.println(os.getcwd())
$ bs demo.bs
/home/sk/Git/bs

setcwd(dir)

Set the current working directory to dir.

Returns true if successful, else false.

os.setcwd("/usr")
io.println(os.getcwd())
$ bs demo.bs
/usr

Process(args, capture_stdout?, capture_stderr?, capture_stdin?, binary?)

Native C class that spawns a process. Expects args to be an array of strings that represent the command line arguments.

If any of the optional capture arguments are provided to be true, then that corresponding file of the created process will be captured.

If the argument binary is provided to be true, then the captured files are opened in binary mode.

Returns nil if failed.

var p = os.Process(["ls", "-l"])
if !p {
    io.eprintln("ERROR: could not start process")
    os.exit(1)
}

p.wait()
$ bs demo.bs
total 4
-rw-r--r-- 1 shoumodip shoumodip 122 Oct  6 15:11 demo.bs

Process.stdout()

Get the standard output of the process as an io.Reader instance.

Returns nil if the process was spawned without capturing stdout.

var p = os.Process(["ls", "-l"], true)
if !p {
    io.eprintln("ERROR: could not start process")
    os.exit(1)
}

var stdout = p.stdout()
while !stdout.eof() {
    var line = stdout.readln()
    io.println("Line:", line)
}

p.wait()
$ bs demo.bs
Line: total 4
Line: -rw-r--r-- 1 shoumodip shoumodip 241 Oct  6 15:44 demo.bs
Line:

Process.stderr()

Get the standard error of the process as an io.Reader instance.

Returns nil if the process was spawned without capturing stderr.

Process.stdin()

Get the standard input of the process as an io.Writer instance.

Returns nil if the process was spawned without capturing stdin.

var p = os.Process(["grep", "foobar"], false, false, true)
if !p {
    io.eprintln("ERROR: could not start process")
    os.exit(1)
}

var stdin = p.stdin()
stdin.writeln("First line foobar lmao")
stdin.writeln("Second line lmao")
stdin.writeln("Third line lets goooo foobar")
stdin.close()
p.wait()
$ bs demo.bs
First line foobar lmao
Third line lets goooo foobar

Process.kill(signal)

Kill the process with signal.

On windows the process is just killed, since there is no concept of kill signals there.

Returns false if any errors were encountered, else true.

Process.wait()

Wait for the process to exit, and return its exit code.

Returns nil if failed.

args

Array of command line arguments. First element is the program name.

io.println(os.args)
$ bs demo.bs foo bar baz
["demo.bs", "foo", "bar", "baz"]

name

The name of the OS.

io.println(os.name)
$ bs demo.bs
Linux      # On linux
macOS      # On macOS
Windows    # On windows
Unknown    # On unknown

The output depends on the OS.

arch

The CPU architecture.

io.println(os.arch)
$ bs demo.bs
x86_64     # On x86_64
ARM64      # On arm64
Unknown    # On unknown

The output depends on the architecture.

Regex(pattern)

POSIX compatible regular expressions.

Returns nil if failed.

var r = Regex("([0-9]+) ([a-z]+)")
io.println("69 apples, 420 oranges".replace(r, "\{fruit: '\\2', count: \\1}"))
$ bs demo.bs
{fruit: 'apples', count: 69}, {fruit: 'oranges', count: 420}

String

Methods for the builtin string value.

string.slice(start, end?)

Slice a string from start (inclusive) to end (exclusive).

io.println("deez nuts".slice(0, 4))
io.println("deez nuts".slice(5, 9))
io.println("deez nuts".slice(5)) // No end defaults to string end
$ bs demo.bs
deez
nuts
nuts

string.reverse()

Reverse a string.

io.println("sllab amgil".reverse())
$ bs demo.bs
ligma balls

string.repeat(count)

Repeat a string count times.

io.println("foo".repeat(6))
$ bs demo.bs
foofoofoofoofoofoo

string.toupper()

Convert a string to uppercase.

io.println("Urmom".toupper())
$ bs demo.bs
URMOM

string.tolower()

Convert a string to lowercase.

io.println("Urmom".tolower())
$ bs demo.bs
urmom

string.tonumber()

Convert a string to a number.

io.println("69".tonumber())
io.println("420.69".tonumber())
io.println("0xff".tonumber())
io.println("69e3".tonumber())
io.println("nah".tonumber())
$ bs demo.bs
69
420.69
255
69000
nil

string.find(pattern, start?)

Find pattern within a string starting from position start (which defaults to 0).

Returns the position if found, else nil.

io.println("foo bar baz".find("ba"))
io.println("foo bar baz".find("ba", 5))
io.println("foo bar baz".find("ba", 9))
io.println("foo bar baz".find("lmao"))

var r = Regex("[0-9]")
io.println("69a".find(r))
io.println("69a".find(r, 1))
io.println("69a".find(r, 2))
io.println("yolol".find(r))
$ bs demo.bs
4
8
nil
nil
0
1
nil
nil

string.split(pattern)

Split string by pattern.

io.println("foo bar baz".split(""))
io.println("foo bar baz".split(" "))
io.println("foo bar baz".split("  "))

var r = Regex(" +")
io.println("foobar".split(r))
io.println("foo bar".split(r))
io.println("foo bar   baz".split(r))
$ bs demo.bs
["foo bar baz"]
["foo", "bar", "baz"]
["foo bar baz"]
["foobar"]
["foo", "bar"]
["foo", "bar", "baz"]

string.replace(pattern, replacement)

Replace pattern with replacement.

io.println("foo bar baz".replace("", "-"))
io.println("foo bar baz".replace(" ", "---"))
io.println("foo bar baz".replace("  ", "-"))

var r = Regex("([0-9]+) ([a-z]+)")
io.println("69 apples, 420 oranges".replace(r, "\{type: \\2, count: \\1}"))
io.println("69 apples, 420  oranges".replace(r, "\{type: \\2, count: \\1}"))
io.println("ayo noice!".replace(r, "\{type: \\2, count: \\1}"))
$ bs demo.bs
foo bar baz
foo---bar---baz
foo bar ba
{type: apples, count: 69}, {type: oranges, count: 420}
{type: apples, count: 69}, 420  oranges
ayo noice!

string.compare(other)

Compare two strings together.

- 0 means the string is equal to the other string - 1 means the string is "greater than" the other string - -1 means the string is "less than" the other string

io.println("foo".compare("bar"))
io.println("foo".compare("lol"))
io.println("foo".compare("foo"))
io.println("foo".compare("food"))
io.println("food".compare("foo"))
$ bs demo.bs
1
-1
0
-1
1

string.ltrim(pattern)

Trim pattern from the left of a string.

io.println("[" $ "   foo bar baz  ".ltrim(" ") $ "]")
$ bs demo.bs
[foo bar baz  ]

string.rtrim(pattern)

Trim pattern from the right of a string.

io.println("[" $ "   foo bar baz  ".rtrim(" ") $ "]")
$ bs demo.bs
[   foo bar baz]

string.trim(pattern)

Trim pattern from both sides of a string.

io.println("[" $ "   foo bar baz  ".trim(" ") $ "]")
$ bs demo.bs
[foo bar baz]

string.lpad(pattern, count)

Pad string with pattern on the left side, till the total length reaches count.

io.println("foo".lpad("69", 0))
io.println("foo".lpad("69", 3))
io.println("foo".lpad("69", 4))
io.println("foo".lpad("69", 5))
io.println("foo".lpad("69", 6))
$ bs demo.bs
foo
foo
6foo
69foo
696foo

string.rpad(pattern, count)

Pad string with pattern on the right side, till the total length reaches count.

io.println("foo".rpad("69", 0))
io.println("foo".rpad("69", 3))
io.println("foo".rpad("69", 4))
io.println("foo".rpad("69", 5))
io.println("foo".rpad("69", 6))
$ bs demo.bs
foo
foo
foo6
foo69
foo696

string.prefix(pattern)

Check whether string starts with pattern.

io.println("foobar".prefix("foo"))
io.println("foobar".prefix("Foo"))
io.println("foobar".prefix("deez nuts"))
$ bs demo.bs
true
false
false

string.suffix(pattern)

Check whether string ends with pattern.

io.println("foobar".suffix("bar"))
io.println("foobar".suffix("Bar"))
io.println("foobar".suffix("deez nuts"))
$ bs demo.bs
true
false
false

Bit

Contains bitwise operations which are not frequent enough to warrant a dedicated operator, but which still finds occasional uses.

ceil(n)

Return the bitwise ceiling of a number.

io.println(bit.ceil(7))
io.println(bit.ceil(8))
io.println(bit.ceil(9))
$ bs demo.bs
8
8
16

floor(n)

Return the bitwise floor of a number.

io.println(bit.floor(7))
io.println(bit.floor(8))
io.println(bit.floor(9))
$ bs demo.bs
4
8
8

ASCII

Contains functions for dealing with ASCII codes and characters.

char(code)

Return the character associated with an ASCII code.

code(char)

Return the ASCII code associated with a character.

Expects char to be a string of length 1.

isalnum(char)

Return whether a character is an alphabet or a digit.

Expects char to be a string of length 1.

isalpha(char)

Return whether a character is an alphabet.

Expects char to be a string of length 1.

iscntrl(char)

Return whether a character is a control character.

Expects char to be a string of length 1.

isdigit(char)

Return whether a character is a digit.

Expects char to be a string of length 1.

islower(char)

Return whether a character is lowercase.

Expects char to be a string of length 1.

isupper(char)

Return whether a character is uppercase.

Expects char to be a string of length 1.

isgraph(char)

Return whether a character is graphable.

Expects char to be a string of length 1.

isprint(char)

Return whether a character is printable.

Expects char to be a string of length 1.

ispunct(char)

Return whether a character is a punctuation.

Expects char to be a string of length 1.

isspace(char)

Return whether a character is whitespace.

Expects char to be a string of length 1.

Bytes(str?)

Strings are immutable in BS. The native class Bytes provides a mutable string builder for optimized string modification operations.

var b = Bytes()
b.push("Hello, ")
b.push("world!")
io.println(b)

var nice = Bytes("69 Hehe")
io.println(nice)
$ bs demo.bs
Hello, world!
69 Hehe

Bytes.count()

Return the current number of bytes written.

var b = Bytes()
b.push("Hello")
b.push(" world!")
io.println(b)

var n = b.count()
io.println("{n} bytes written.")
$ bs demo.bs
Hello world!
12 bytes written.

Bytes.reset(position)

Move back the writer head to position.

var b = Bytes()
b.push("Hello")

var p = b.count()
b.push(" world!")

io.println(b)
b.reset(p)
io.println(b)
$ bs demo.bs
Hello world!
Hello

Bytes.slice(start?, end?)

Return a slice from start (inclusive) to end (exclusive).

If no arguments are provided to this function, the whole builder is returned as a string.

var b = Bytes()
b.push("Hello world!")

io.println(b.slice())
io.println(b.slice(0, 5))
io.println(b.slice(6, 12))
$ bs demo.bs
Hello world!
Hello
world!

Bytes.push(value)

Push value to the end.

var buffer = Bytes()

// Operations can be chained
buffer
    .push("Hello")           // A String
    .push(Bytes(", world!")) // Another Bytes instance
    .push(32)                // An ASCII code, in this case ' '
    .push($69)               // To push the string representation, a string must be provided

io.println(buffer)
$ bs demo.bs
Hello, world! 69

Bytes.insert(position, value)

Insert value at position.

var buffer = Bytes("world!")

// Operations can be chained, just like Bytes.push()
buffer
    .insert(0, "Hell")      // A String
    .insert(4, Bytes(", ")) // Another Bytes instance
    .insert(4, 111)         // An ASCII code, in this case 'o'

io.println(buffer)
$ bs demo.bs
Hello, world!

Bytes.get(position)

Get the byte at position as a number.

var b = Bytes()
b.push("Hello")

for i in 0..b.count() {
    var c = b.get(i)
    io.println(ascii.char(c), c)
}
$ bs demo.bs
H 72
e 101
l 108
l 108
o 111

Bytes.set(position, value)

Set the byte at position to value.

The argument value has to be a number.

var b = Bytes()

b.push("Cello")
io.println(b)

b.set(0, ascii.code("H"))
io.println(b)
$ bs demo.bs
Cello
Hello

Array

Methods for the builtin array value.

array.map(f)

Functional map.

The provided function f must take a single argument.

var xs = [1, 2, 3, 4, 5]
var ys = xs.map(fn (x) -> x * 2)
io.println(xs)
io.println(ys)
$ bs demo.bs
[1, 2, 3, 4, 5]
[2, 4, 6, 8, 10]

array.filter(f)

Functional filter.

The provided function f must take a single argument.

var xs = [1, 2, 3, 4, 5]
var ys = xs.filter(fn (x) -> x % 2 == 0)
io.println(xs)
io.println(ys)
$ bs demo.bs
[1, 2, 3, 4, 5]
[2, 4]

array.reduce(f, accumulator?)

Functional reduce.

The provided function f must take two arguments. The first argument shall be the accumulator, and the second shall be the current value.

var xs = [1, 2, 3, 4, 5]

var a = xs.reduce(fn (x, y) -> x + y)
io.println(a)

var b = xs.reduce(fn (x, y) -> x + y, 10)
io.println(b)
$ bs demo.bs
15
25

array.join(separator)

Join the elements of an array, separated by separator into a single string.

io.println([1, 2, 3, 4, 5].join(" -> "))
$ bs demo.bs
1 -> 2 -> 3 -> 4 -> 5

array.find(value, start?)

Find value within an array starting from position start (which defaults to 0).

Returns the position if found, else nil.

var xs = [1, 2, 3, 4, 5, 3]
io.println(xs.find(3))
io.println(xs.find(3, 3))
io.println(xs.find(3, 6))
io.println(xs.find(true, 6))
$ bs demo.bs
2
5
nil
nil

If you just want to check if a value exists in an array, you can use the in operator.

var xs = [1, 2, 3, 4, 5, 3]
io.println(3 in xs)
io.println(6 in xs)
$ bs demo.bs
true
false

array.push(value)

Push value into an array.

This modifies the array.

var xs = []

for i in 0..5 {
    xs.push(i * 2)
}

io.println(xs)
$ bs demo.bs
[0, 2, 4, 6, 8]

Operations can be chained.

io.println(
    ["Nice!"]
        .push(69)
        .push(420))
$ bs demo.bs
["Nice!", 69, 420]

array.insert(position, value)

Insert value into an array at position.

This modifies the array.

var xs = []

for i in 0..5 {
    if i == 2 {
        continue
    }

    xs.push(i * 2)
}
io.println(xs)

xs.insert(2, 4)
io.println(xs)
$ bs demo.bs
[0, 2, 6, 8]
[0, 2, 4, 6, 6]

Operations can be chained, like array.push()

io.println(
    ["Nice!"]
        .insert(0, 69)
        .insert(1, 420))
$ bs demo.bs
[69, 420, "Nice!"]

array.pop()

Pop the last element from an array. If the array is empty, an error is thrown.

This modifies the array.

var xs = [1, 2, 3, 4, 5]
io.println(xs.pop())
io.println(xs)
$ bs demo.bs
5
[1, 2, 3, 4]

array.sort(compare)

Sort an array inplace with compare, and return itself.

var xs = [4, 2, 5, 1, 3]
xs.sort(fn (x, y) -> x < y) // This also returns the array so you can chain operations

io.println(xs)
$ bs demo.bs
[1, 2, 3, 4, 5]

The compare function must take two arguments. If it returns true, then the left argument shall be considered "less than", and vice versa for false.

array.resize(size)

Resize an array to one having size elements, and return itself.

If size is larger than the original size, the extra elements shall default to nil.

This modifies the array.

var xs = [1, 2, 3, 4, 5]
io.println(xs)

io.println(xs.resize(3))
io.println(xs)
$ bs demo.bs
[1, 2, 3, 4, 5]
[1, 2, 3]
[1, 2, 3]

array.reverse()

Reverse an array.

This modifies the array.

var xs = [1, 2, 3, 4, 5]
io.println(xs)

xs.reverse() // This also returns the array so you can chain operations
io.println(xs)
$ bs demo.bs
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]

array.fill(value)

Fill an array with value.

This modifies the array.

var xs = [1, 2, 3, 4, 5]
io.println(xs)

xs.fill(69) // This also returns the array so you can chain operations
io.println(xs)
$ bs demo.bs
[1, 2, 3, 4, 5]
[69, 69, 69, 69, 69]

Use this with array.resize() to create an array with a preset size and value.

var xs = [].resize(5).fill("foo")
io.println(xs)
$ bs demo.bs
["foo", "foo", "foo", "foo", "foo"]

Here's a 2D version.

var width = 5
var height = 6

var board = []
    .resize(height)
    .map(fn (x) -> [].resize(width).fill(0))

board[2][2] = 1

io.println(board)
$ bs demo.bs
[
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0]
]

array.slice(start, end?)

Slice an array from start (inclusive) to end (exclusive).

var xs = [1, 2, 3, 4, 5]
io.println(xs)
io.println(xs.slice(2))
io.println(xs.slice(1, 3))
$ bs demo.bs
[1, 2, 3, 4, 5]
[3, 4, 5]
[2, 3]

array.append(other)

Append an array.

This modifies the array.

var xs = [1, 2, 3, 4, 5]
var ys = [6, 7, 8, 9, 10]
io.println(xs)
io.println(xs.append(ys))
$ bs demo.bs
[1, 2, 3, 4, 5]
[
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10
]

Table

Methods for the builtin table value.

table.extend(src, overwrite)

Extend a table.

var xs = {
    foo = 69,
    bar = 420
}

var ys = {
    bar = 1337,
    baz = 42
}

var zs = {
    bar = 420,
    lol = 420
}

io.println(xs)

xs.extend(ys, true) // This also returns the table so you can chain operations
io.println(xs)

xs.extend(zs, false)
io.println(xs)
$ bs demo.bs
{
    bar = 420,
    foo = 69
}
{
    baz = 42,
    bar = 1337,
    foo = 69
}
{
    lol = 420,
    baz = 42,
    bar = 1337,
    foo = 69
}

Math

Contains simple mathematical primitives.

number.sin()

Sine in radians.

var theta = 0.5
io.println(theta.sin())
$ bs demo.bs
0.479425538604203

Note that the precision of the mathematical functions may vary depending on the compiler and the platform. This is inherent to computing in general.

number.cos()

Cosine in radians.

number.tan()

Tangent in radians.

number.asin()

Inverse sine in radians.

number.acos()

Inverse cosine in radians.

number.atan()

Inverse tangent in radians.

number.exp()

Return the exponential function of the number.

io.println(2.exp()) // Basically e^2
$ bs demo.bs
7.38905609893065

number.log()

Return the natural logarithm (base e) of the number.

number.log10()

Return the common logarithm (base 10) of the number.

number.pow(exponent)

Raise the number to exponent.

number.sqrt()

Square root.

number.ceil()

Ceiling.

number.floor()

Floor.

number.round()

Return the nearest integer.

number.abs()

Return the absolute value (Basically make a number positive).

number.sign()

Return the sign of the number.

io.println((0).sign())
io.println((69).sign())
io.println((-420).sign())
$ bs demo.bs
0
1
-1

number.max(...)

Return the maximum between the method receiver and the provided arguments.

io.println(1.max(2, 3))
io.println(3.max(0, 1, 2))
$ bs demo.bs
3
3

number.min(...)

Return the minimum between the method receiver and the provided arguments.

io.println(1.min(2, 3))
io.println(3.min(1, 2, 3))
$ bs demo.bs
1
1

number.clamp(low, high)

Clamp the number between low and high.

number.lerp(a, b, t)

Linear interpolation.

number.precise(level)

Set the precision (number of decimal digits).

var n = 69.1337
io.println(n)
io.println(n.precise(0))
io.println(n.precise(3))
$ bs demo.bs
69.1337
69
69.134

number.tohex()

Format an integer as a hexadecimal string.

io.println(69.tohex())
io.println((-420).tohex())
$ bs demo.bs
45
-1a4

Random(seed?)

Random number generator using the xoroshiro128+ algorithm.

If the seed argument is not provided, then a random seed is chosen at runtime.

var a = math.Random()
var b = math.Random(1337)

io.println(a.number())
io.println(a.number())
io.println(a.number(69, 420))
io.println(a.number(69, 420))

io.println()
io.println("==== Constant ====")
io.println()

io.println(b.number())
io.println(b.number())
io.println(b.number(69, 420))
io.println(b.number(69, 420))
$ bs demo.bs
0.279107155961776
0.73815524476827
225.323320231653
148.438554063034

==== Constant ====

0.0725452046400308
0.811773795162954
416.950161924657
182.365867621578

Random.number(min?, max?)

Return a random number between low and high.

If no arguments are provided, a number between 0 and 1 is returned.

var r = math.Random()
io.println(r.number())
io.println(r.number())
io.println(r.number(69, 420))
io.println(r.number(69, 420))
$ bs demo.bs
0.279107155961776
0.73815524476827
225.323320231653
148.438554063034

Random.bytes(count)

Return a random sequence of bytes.

var seq = math.Random().bytes(9)
io.println(seq.slice())
$ bs demo.bs
{03ah
"7f

range(begin, end, step?)

Return an array containing a range.

If step is not provided, it is automatically selected.

io.println(math.range(1, 6))
io.println(math.range(6, 1))

io.println(math.range(1, 6, 2))
io.println(math.range(6, 1, -2))
$ bs demo.bs
[1, 2, 3, 4, 5]
[6, 5, 4, 3, 2]
[1, 3, 5]
[6, 4, 2]

If step is provided such that it would run indefinitely, an error will be raised.

io.println(math.range(1, 6, -1))
$ bs demo.bs
demo.bs:1:29: error: a step of -1 in an ascending range would run indefinitely

    1 | io.println(math.range(1, 6, -1))
      |                             ^

E

Euler's constant.

PI

PI.

Meta

Contains simple metaprogramming primitives.

Error()

Native C class that wraps over a metaprogram error. This doesn't really do anything, and only serves as an implementation detail for the meta functions. Attempt to call this constructor directly will throw a runtime error.

Example usecase:

var f = meta.compile("Oops@")
if f is meta.Error {
    io.println("Row:", f.row())
    io.println("Col:", f.col())
    io.println("Path:", f.path())
    io.println("Line:", f.line())
    io.println("Message:", f.message())
    io.println("Explanation:", f.explanation())
    io.println("Example:", f.example())
}
$ bs demo.bs
Row: 1
Col: 5
Path: <meta>
Line: Oops@
Message: invalid character '@' (64)
Explanation: nil
Example: nil

Error.row()

Return the row in which the error occured.

Returns nil if the error occured in native code.

Error.col()

Return the column in which the error occured.

Returns nil if the error occured in native code.

Error.path()

Return the path in which the error occured.

Returns nil if the error occured in native code.

Error.line()

Return the line of meta source code in which the error occured as a string.

Returns nil if the error occured in native code.

Error.message()

Return the error message.

Error.explanation()

Return the explanation associated with the error.

Returns nil if there is no explanation associated with the error.

Error.example()

Return the example associated with the error.

Returns nil if there is no example associated with the error.

compile(str)

Compile a string into a function.

var f = meta.compile("34 + 35")
io.println(f())
$ bs demo.bs
69

If any errors were encountered while compiling the string, an Error instance is returned instead of a function.

var f = meta.compile("Oops@"); assert(f is meta.Error)

io.println("Row:", f.row())
io.println("Col:", f.col())
io.println("Path:", f.path())
io.println("Line:", f.line())
io.println("Message:", f.message())
io.println("Explanation:", f.explanation())
io.println("Example:", f.example())
$ bs demo.bs
Row: 1
Col: 5
Path: <meta>
Line: Oops@
Message: invalid character '@' (64)
Explanation: nil
Example: nil

So the usage becomes as straight forward as:

var f = meta.compile(...)
if f is meta.Error {
    panic(f.message()) // Error handling...
}

f() // Or whatever you want to do

The function is compiled such that the last expression in the body is returned, otherwise defaulting to nil.

var f = meta.compile({{
    for i in 0..5 {
        io.println('Nice!')
    }
}})

assert(f !is meta.Error)
io.println(f())

var g = meta.compile({{
    for i in 0..5 {
        io.println('Hehe!')
    }

    69
}})

assert(g !is meta.Error)
io.println(g())

var h = meta.compile({{
    for i in 0..5 {
        io.println('Bruh!')
    }

    return 420
}})

assert(h !is meta.Error)
io.println(h())
$ bs demo.bs
Nice!
Nice!
Nice!
Nice!
Nice!
nil
Hehe!
Hehe!
Hehe!
Hehe!
Hehe!
69
Bruh!
Bruh!
Bruh!
Bruh!
Bruh!
420

call(fn, ...args)

Basically a protected call.

fn handle(result) {
    if result is meta.Error {
        io.println("ERROR!")
        io.println("Row:", result.row())
        io.println("Col:", result.col())
        io.println("Path:", result.path())
        io.println("Line:", result.line())
        io.println("Message:", result.message())
        io.println("Explanation:", result.explanation())
        io.println("Example:", result.example())
    } else {
        io.println("OK!")
        io.println(result)
    }
}

// Ok
handle(meta.call(fn (a, b) -> a + b, 34, 35))
io.println()

// Error
handle(meta.call(fn () -> -nil))
$ bs demo.bs
OK!
69

ERROR!
Row: 22
Col: 27
Path: demo.bs
Line: handle(meta.call(fn () -> -nil))
Message: invalid operand to unary (-): nil
Explanation: nil
Example: nil

eval(str)

Evaluate a string.

io.println(meta.eval("34 + 35"))
io.println(meta.eval({{
    for i in 0..5 {
        io.println('Nice!')
    }
}}))
$ bs demo.bs
69
Nice!
Nice!
Nice!
Nice!
Nice!
nil

Errors are thrown as it is.

meta.eval("-nil")
$ bs demo.bs
<meta>:1:1: error: invalid operand to unary (-): nil

    1 | -nil
      | ^

demo.bs:1:10: in eval()

    1 | meta.eval("-nil")
      |          ^