The Wayback Machine - https://web.archive.org/web/20230313162517/https://github.com/buzz-language/buzz/
Skip to content

buzz-language/buzz

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
doc
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

buzz

πŸ‘¨β€πŸš€ buzz

A small/lightweight statically typed scripting language written in Zig

buzz code example

Features

Progress

I'm not far from completing milestone 0.1.0. As writing buzz code is the best way of finding bugs, I also implement a lot of features of the next milestones too. I recently got sidetracked a bit and started implementing the JIT compiler with LLVM.

How to build

Requirements

  • Since this is built with Zig, you should be able to build buzz on a wide variety of architectures even though this has only been tested on x86/M1.
  • Linux or macOS (not much work is needed to make it work on Windows)
  • libpcre (not libpcre2)
  • libc
  • mimalloc (can be turned off by building buzz with -Duse_mimalloc=false)
  • LLVM 15.0.6+: you can set the environment variable LLVM_PATH if zig can't find it on its own
  • zig master

Steps

  1. Clone the project: git clone https://github.com/buzz-language/buzz <buzz_dir>
  2. Checkout submodules: git submodule update --init
  3. Add to your shell rc:
export BUZZ_PATH="/path/to/buzz/dist"
export PATH="$BUZZ_PATH:$PATH"
  1. Build it: zig build -Doptimize=ReleaseSafe
  2. Have fun: buzz <myscript.buzz>

Additionally, install the VS Code extension to get syntax highlighting. If you don't use VS Code but your editor supports TextMate grammar files, you can use that.

Quick tour

Note: You can also take a look at tests/ for more examples.

Types and variables

| Basic types
bool aBoolean = true;
| Immutable sequence of bytes
str aString = "hello world";
str multiline = `
    i'm on several
    lines
    yes
`;
int aNumber = 23; | i32
aNumber = 0b110;
aNumber = 0xA12F;
float aFloat = 23.123; | f64
| A PCRE regex
pat aPattern = _hello [a-z]+_;
| Userdata are pointers to foreign data wrapped inside a buzz obj
ud userdata = GetSomeForeignData();

| A constant
const float pi = 3.14;

| Data structures
[int] aListOfNumbers = [1, 2, 3];
| Keys and values can be of any type
{str, int} 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
[int]? optList = null;

print(optList?.len()); | -> null

| If unwrap
if (aStringOrNull -> aString) {
    print("{aString} is not null");
} else {
    print("aString was null");
}

Functions

fun sayHiTo(str name, str? lastName, int age) > str {
    | Interpolation with `{}`
    return "Hi {name} {lastName ?? ""}!"
}

| Same could be an arrow function
fun sayHiTo(str name, str? lastName, int 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"

Generics

fun countMap(<K, V>, {K, V} map) > int {
    return map.size();
}

{str, int} map = {
    "one": 1,
    "two": 2,
    "three": 3,
};

countMap(<str, int>, map) == 3;

Enums

| Enums can have number or string values
enum Natural {
    zero,
    one,
    two,
}

Natural.zero.value == 0;

| Values must either be implicit or be all defined
enum(int) NumEnum {
    three = 3,
    four = 4,
    five = 5,
}

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"

enum(str) Locale {
    fr,
    en,
    it,
}

Locale.fr.value == "fr";

| Create enum instance from its value
Locale? locale = Locale("fr");
locale == Locale.fr

Control flow

| The usual
if (someCondition) {
    | ...
} else if (anotherCondition) {
    | ...
} else {
    | ...
}

int i = 0;
while (i < 10) {
    i = i + 1;
}

int j = 10;
do {
    j = j - 1;
} until (j == 0)

for (int i = 0; i < 10; i = i + 1) {
    | ...
    break;
}

| inline if
int value = if (something > 0) 12 else 13;

foreach

foreach can iterate over most data structures:

foreach (SomeEnum case in SomeEnum) {
    | ...
}

foreach (int i, str value in listOfStrings) {
    | ...
}

foreach (str key, int value in aMap) {
    | ...
}

foreach (int i, str char in aString) {
    | ...
}

fib<void, int?> fibonnaciFib = &fibonnaci(10);
foreach (int value in fibonnaciFib) {
    | ...
}

Objects

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
    int age = 35,

    | Method
    fun sayHello() > void {
        print("Hello {this.name}");
    }

    | Object don't have constructor but you can implement one with a static method
    static init(str name, int age) > Person {
        Person.population = Person.population + 1;

        return Person {
            name = name,
            age = age,
        };
    }
}

Anonymous objects

| Anonymous objects don't have methods, static fields or default values
fun getInfo() > obj{ str name, int age } {
    return .{
        name = "Joe",
        age = 36,
    };
}

| ...

obj{ str name, int age } info = getInfo();

Protocols

A protocol defines a set of methods. Objects can conform to any number of them:

protocol Translatable {
    fun translate(dx: int, dy: int) > void;
}

protocol Printable {
    fun print() > void;
}

object(Translatable, Printable) Point {
    int x,
    int y,

    fun translate(dx: int, dy: int) > void {
        this.x = this.x + dx;
        this.y = this.y + dy;
    }

    fun print() > void {
        print("Point ({this.x}, {this.y})");
    }
}

object(Printable) Line {
    Point start,
    Point end,

    fun print() > void {
        print("Line ({this.start.x}, {this.start.y}) ({this.end.x}, {this.end.y})");
    }
}

| ...

[Printable] elements = [
    Point{ x = 0, y = 0 },
    Line{
        start = Point{ x = 10, y = 10 },
        end = Point{ x = 15, y = 12 },
    },
];

foreach (int i, Printable element in elements) {
    element.print();
}

Errors

Functions must specify which error they can raise with !> type1, type2, .... Error can't be raised in the global scope. test functions ignore errors.

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() > int !> MyErrors, OtherErrors, str {
    int random = rand();
    if (random == 1) {
        throw MyErrors.failed;
    } else if (random == 0) {
        throw OtherErrors.failed;
    }

    throw "something else";

    return 0;
}

| Use default value in case of any error
int result = willFail() catch 0;

| Try catch works as you would expect
try {
    willFail();
} catch (str error) {
    print("Caught error {error}");
} catch {
    print("Catches everything");
}

| Throwing an object instance with a `message` field will print the message
throw .{ message = "Something's wrong" } | -> Error: Something's wrong
throw SomeObject{ number = 12 }          | -> Error: object instance 0x1feb12 `SomeObject`

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([str] args) > 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(int n) > str > int? {
    for (int 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([str] args) > void {
    | Wraps the call to `count` in a fiber, however the fiber is not started until a `resolve` or `resume` instruction
    fib<str, int?> counter = &count(10);

    int 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/so/dll):

| 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");

// The function must always have this signature
// It returns: 
//     - 0 if no return value
//     - 1 if there's a return value (that you must push on the stack before returning)
//     - -1 if there's an error (that you also push on the stack before returning)
export fn assert(ctx: *api.NativeCtx) c_int {
    const condition: bool = ctx.vm.bz_peek(1).bz_valueToBool();

    if (!condition) {
        ctx.vm.bz_throw(ctx.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([str] args) > void {
    assert(1 + 1 == 2, message: "Congrats on doing math!");
}

About

πŸ‘¨β€πŸš€ buzz, A small/lightweight statically typed scripting language (in development)

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages