buzz
buzz copied to clipboard
👨🚀 buzz, A small/lightweight statically typed scripting language
👨🚀 buzz
A small/lightweight typed scripting language written in Zig
Note: This is very much in development. I continuously built it with zig master.
Goals
- Small in size and complexity (just a bit more than Lua though)
- Strict typing
- Unambiguous
- No nonsense coercion
- Fibers
- Tooling
- Generate doc from docblocks (in progress)
- LSP (in progress)
- TextMate syntax
Quick tour
Note: You can also take a look at tests/ for more examples.
- Types and variables
- Operators
- Functions
- Enums
- Control flow
- Objects and Classes
- Errors
- Import/Export
- Fibers
- Call C/Zig code
Types and variables
| Basic types
bool aBoolean = true;
str aString = "hello world";
num aNumber = 23;
aNumber = 0b110;
aNumber = 0xA12F;
pat aPattern = _hello [a-z]+_;
| A constant
const num pi = 3.14;
| Data structures
[num] aListOfNumbers = [1, 2, 3];
{str, num} aMap = {
"one": 1,
"two": 2,
"three": 3,
};
Operators
| Comparison
12 == 12;
12 != 13;
12 >= 12;
12 <= 12;
12 > 11;
12 < 13;
| Arithmetic
12 + 12 == 24;
12 - 12 == 0;
12 * 12 == 144;
12 / 12 == 1;
12 % 12 == 0;
| Logical
12 > 3 and 5 < 12;
12 > 3 or 12 < 5;
| String
"hello " + "world" == "hello world";
"hello" == "hello";
| Bitwise
15 << 3 == 120; | shift left
15 >> 3 == 1; | shift right
12 & 23 == 4 | and
15 ^ 3 == 12; | xor
15 \ 3 == 15; | or
~15 == -16; | not
Optionals
str? aStringOrNull = "hello";
| Null coalescing operator is `??`
str unwrapped = aStringOrNull ?? "default value"
| Force unwrapping with `!`
str unwrapped = aStringOrNull!;
| Graceful unwrapping
[num]? optList = null;
print(optList?.len()); | -> null
Functions
fun sayHiTo(str name, str? lastName, num age) > str {
| Interpolation with `{}`
return "Hi {name} {lastName ?? ""}!"
}
| Same could be an arrow function
fun sayHiTo(str name, str? lastName, num age) > str -> "Hi {name} {lastName ?? ""}!"
When called, only the first argument name of a function can be omitted, order is not required:
sayHiTo("Joe", age: 35, lastName: "Doe"); | -> "Hi Joe Doe!"
Functions are first-class citizens:
Function() fn = fun () > void -> print("hello world"); | Arrow function
fn(); | -> "hello world"
Enums
| Enums can have a type, if none is specified the type is `num` and values are ordinal.
| If a type is specified, all values must be initialized.
enum(str) Country {
usa = "United States of America",
uk = "United Kingdoms",
fr = "France",
}
| To get the value associated with a enum case
print(Country.usa.value); | -> "United States of America"
Control flow
| The usual
if (someCondition) {
| ...
} else if (anotherCondition) {
| ...
} else {
| ...
}
num i = 0;
while (i < 10) {
i = i + 1;
}
num j = 10;
do {
j = j - 1;
} until (j == 10)
for (num i = 0; i < 10; i = i + 1) {
| ...
break;
}
foreach
foreach can iterate over most data structures:
foreach (SomeEnum case in SomeEnum) {
| ...
}
foreach (num i, str value in listOfStrings) {
| ...
}
foreach (str key, num value in aMap) {
| ...
}
foreach (num i, str char in aString) {
| ...
}
Objects and Classes
An object is like a class except it can't be inherited from and can't inherit from anything:
object Person {
static population = 0;
str name = "Joe", | Fields can have default values
num age = 35,
| Method
fun sayHello() > void {
print("Hello {this.name}");
}
| Object and classes don't have constructor but you can implement one with a static method
static init(str name, num age) > Person {
Person.population = Person.population + 1;
return Person {
name = name,
age = age,
};
}
}
class act like you would expect. They don't have the central place they have in other languages (tbh I may end up removing them):
class Form {
num x,
num y,
fun toString() > str {
return "({this.x}, {this.y})";
}
}
| `Circle` inherits from `Form`
class Circle < Form {
num radius,
fun toString() > str {
return "center: {super.toString()}, radius: {this.radius}";
}
}
Errors
Right now errors can be anything.
enum(str) MyErrors {
failed = "Something failed",
bad = "Something bad",
ohno = "Oh no!",
}
enum(str) OtherErrors {
failed = "Something failed",
bad = "Something bad",
ohno = "Oh no!",
}
fun willFail() > num {
throw MyErrors.failed;
return 0;
}
| Use default value in case of any error
num result = willFail() catch 0;
| Handle different type of errors
num result = willFail() catch {
(MyErrors e) -> 0,
(OtherErrors e) -> 1,
default {
| Something unexpected
os.exit(1);
}
}
Import/Export
| hello.buzz
| Import std lib
import "lib/std";
fun sayHello() > void {
print("Hello world!");
}
| Make it visible when imported
export sayHello;
| main.buzz
import "hello";
fun main() > void {
sayHello();
}
Fibers
Similar to Lua's coroutines. Buzz's fibers have their own state and stack and can be switched in and out from.
Fibers can yield from within any call depth. Any function can be wrapped in a fiber. Unlike Lua, yield are evaluated and dismissed
if a function is not called within a fiber and do not raise an error.
resolve allows to run a fiber until completion without stopping for any yield. It can be called after the fiber is over in order to
get the wrapped function return value.
| Returns a string, yields numbers
| Always yields an optional type because null is returned if you resume a terminated fiber
fun count(num n) > str > num? {
for (num i = 0; i < n; i = i + 1) {
| If the function is not called in a fiber, yields are evaluated and dismissed
| Otherwise the value is returned as `resume` result
yield i;
}
return "Counting is done!";
}
fun main() > void {
| Wraps the call to `count` in a fiber, however the fiber is not started until a `resolve` or `resume` instruction
fib<str, num?> counter = &count(10);
num sum = 0;
while (!counter.over()) {
| resume returns null if nothing was yielded and/or fiber is over
sum = sum + resume counter ?? 0;
}
assert(resolve counter == "Counting is done!", message: "resolve returns fiber return value");
}
Call C/Zig code
First define the buzz interface. The extern keyword means that buzz we'll look for a dynamic library named libmylib.dylib (only dylib right now):
| mylib.buzz
extern fun assert(bool condition, str message) > void
Then implement it in Zig or C using the buzz_api:
// buzz_mylib.zig
const std = @import("std");
const api = @import("buzz_api.zig");
// We have to respect C ABI
export fn assert(vm: *api.VM) c_int {
var condition: bool = vm.bz_peek(1).bz_valueToBool();
if (!condition) {
vm.bz_throw(vm.bz_peek(0));
}
return 0;
}
Build a dynamic library for it (TODO: instructions for this) and you can use it in your buzz code:
| main.buzz
import "mylib"
fun main() > void {
assert(1 + 1 == 2, message: "Congrats on doing math!");
}
Native functions have all the same signature fn myfunction(vm: *VM) bool. If values must be returned, push them on the stack and return true.