Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Buyan Documentation

Buyan is a compiled, strictly statically typed, stack-oriented, and concatenative programming language. It is inspired by Forth.

You can learn more about concatenative programming languages here.

Installation

To install Buyan using Cargo, run:

cargo install --git https://github.com/ya7on/buyan.git

Building from Source

To build Buyan, clone the repository and build it using Cargo:

git clone https://github.com/ya7on/buyan.git
cd buyan
cargo build --release

After building, you can find the executable in target/release/buyan.

Usage

To run a Buyan program, simply pass the file path to the buyan executable:

buyan <path>.by --target <target>

Where <target> is the target architecture to compile for.

Target Architectures

At the moment, Buyan supports the following target architectures:

  • interpreter - instantly executes the program using an interpreter
  • z80-unknown-cpm - compiles the program for the Z80 CP/M target, printing the assembled code to stdout

Tutorial

What Is a Stack-Oriented Programming Language?

A stack is a data structure that follows the LIFO rule: Last In, First Out.

A stack-oriented programming language is a language that uses a stack to store data and perform operations.

Buyan is a stack-oriented language. Every operation works with values stored on the stack.

Let us look at a simple program step by step. It adds two numbers and leaves the result on the stack. This example uses pseudocode:

2 2 +

There are three stack operations:

  • 2 puts the number 2 on the stack. The stack is now [2].
  • 2 puts another number 2 on the stack. The stack is now [2, 2].
  • + takes the top two numbers, adds them, and puts the result on the stack. The stack is now [4].

Stack Signature

In Buyan, a stack signature describes the types on the stack. Every word describes two stack states: the state it expects before it runs and the state it produces after it runs.

Here is a word that adds two numbers:

def add(usize, usize -- usize) ... end

The -- separates the two stack states. The signature usize, usize -- usize means that the word takes two usize values from the top of the stack and leaves one usize value on top.

Stack Polymorphism

Buyan supports stack polymorphism. It lets one word work with different types or different stack states. There are two syntax features for this:

  • Type variable. A type variable lets a stack signature use different types. Add the variable to the word declaration. For example: def example<T>(T, T -- T). This word takes two values of type T and leaves one value of type T. If you call it with usize values, it leaves a usize. If you call it with u8 values, it leaves a u8.
  • Stack variable. A stack variable represents a stack state. Add it to the word declaration. For example: def example<...T>(...T, usize -- ...T). This word takes a stack described by ...T with a usize value on top. It removes the usize and leaves the rest of the stack unchanged.

Structure of a Buyan Program

A Buyan program can contain:

  • Imports — words or structures from other modules.
  • A module name — every module has a unique name.
  • Structures — custom data types.
  • Words — operations similar to functions in other programming languages.

Control Flow

Control flow uses special words. Like everything else in Buyan, these words work through the stack.

Conditions

Conditions in Buyan work like conditions in other programming languages. They run different code depending on a Boolean value.

The word std.cfg.if expects these values on the stack:

  • A bool condition.
  • A lambda to run when the condition is true.
  • A lambda to run when the condition is false.

Example:

2u8 2u8 std.u8.add
4u8 std.u8.eq

| -- | { then }
| -- | { else }

std.cfg.if

First, the program adds 2 + 2 and puts 4u8 on the stack. It then compares this value with 4u8 and puts the result on the stack. In this example, the result is true.

Next, it puts the then lambda on the stack. This lambda runs when the condition is true. It then puts the else lambda on the stack. This lambda runs when the condition is false.

After std.cfg.if runs, the stack contains the result of the then or else lambda.

Important: the then and else lambdas must produce the same stack state.

Loops

The word std.cfg.while creates a loop. It expects these values on the stack:

  • A condition lambda. It leaves a bool on top of the stack and runs before every loop iteration.
  • A body lambda. It runs on every iteration while the condition is true.

Here is a loop that runs 10 times:

0u8

| u8 -- u8, bool | {
  std.stack.dup 10u8 std.u8.lt
}
| u8 -- u8 | {
  1u8 std.stack.add
}
std.cfg.while

First, the program puts 0u8 on the stack. This value is the counter.

Next, it puts a lambda on the stack. This lambda checks whether the counter is less than 10. It returns true when the counter is less than 10 and false otherwise.

It then puts another lambda on the stack. This lambda adds 1 to the counter and returns the new value.

The loop runs 10 times and leaves 10u8 on the stack.

Data Types

Integers

Buyan has several unsigned integer types:

  • usize — an unsigned integer. Its size depends on the target platform.
  • u8 — an 8-bit unsigned integer.
  • u16 — a 16-bit unsigned integer.

You can put integer values on the stack with this syntax:

  • 42 puts the usize value 42 on the stack.
  • 0x2A puts the hexadecimal usize value 42 on the stack.
  • 42u8 puts the u8 value 42 on the stack.
  • 0x2Au8 puts the hexadecimal u8 value 42 on the stack.
  • '*' puts the character * on the stack as the u8 value 42.
  • 42u16 puts the u16 value 42 on the stack.
  • 0x2Au16 puts the hexadecimal u16 value 42 on the stack.

You can learn more about each type in the standard library documentation:

Str

The Str type stores a sequence of characters. At this time, strings can contain only ASCII characters.

Use this syntax to put a string on the stack:

"Hello, World!"

When you create a string, the compiler reserves space in static memory. It stores the string as a packed array of ASCII bytes with the string length at the beginning. The string stays in memory until the program ends.

You can learn more in the standard library documentation for Str.

Structures

Structures are custom data types with one or more fields. They group stack values into one logical value.

Use this syntax to define a structure:

struct Point(u8, u8);

Use Point< to pack values into a structure. The required field values must be on top of the stack. A Point needs two u8 values. Its pack operation has this signature: u8, u8 -- Point.

Use Point> to unpack a structure. It removes the structure from the stack and puts its fields on the stack. Its signature is Point -- u8, u8.

Use Point.field to get a structure field. For example, use Point.0 to get the first field of Point. This operation removes the Point structure from the stack and puts its first field on top. Its signature is Point -- u8.

Lambda

A lambda is an anonymous word. You can pass it to another word or return it from another word.

Use this syntax to define a lambda:

| A -- B | { ops }

Here, A is the stack state before the lambda runs, B is the stack state after it runs, and ops is the list of operations in the lambda body.

A lambda signature looks like this:

| A -- B |

It looks like a lambda declaration, but it has no body.

Error Codes

B0001

Unknown Error

The compiler encountered an internal condition that does not have a more specific diagnostic.

Example

[B0001] Error: Unknown Error
  Invalid module for word

How to fix

This normally indicates a compiler bug rather than an error in Buyan source code. Save the source that triggered the error and the complete compiler output, then report them at the Buyan issue tracker.

Include the smallest source file that reproduces the error.

B0002

File Not Found

The entrypoint passed to the compiler does not exist or cannot be read.

Example

$ buyan ./missing.by

How to fix

Pass the path to an existing Buyan source file.

$ buyan ./examples/hello_world.by

B0003

Import Error

An imported module could not be found. Standard-library imports must also name a supported module.

Example

import missing;

module app;

How to fix

Create the imported module at the corresponding path, remove the import, or correct its name.

import std.io;

module app;

B0004

Unexpected Token

The lexer found a character or token that is not valid in Buyan source code.

Example

module app;
!

How to fix

Remove the invalid token or replace it with valid Buyan syntax.

module app;

B0005

Parse Error

The tokens are individually valid, but they do not form a valid Buyan program.

Example

module app;

def main( -- )
    1u8

How to fix

Complete the construct indicated by the diagnostic. This word is missing its closing end.

module app;

def main( -- u8)
    1u8
end

B0006

Invalid Attribute

The attribute attached to a word is not supported.

Example

module app;

#[inline]
def main( -- u8) 1u8 end

How to fix

Remove the unsupported attribute.

module app;

def main( -- u8) 1u8 end

B0007

Symbol Already Exists

Two modules, words, structs, or generic variables resolve to the same symbol name.

Example

module app;

def value( -- u8) 1u8 end
def value( -- u8) 2u8 end

How to fix

Give every symbol a unique name in its scope.

module app;

def first( -- u8) 1u8 end
def second( -- u8) 2u8 end

B0008

Symbol Not Found

The compiler could not resolve a referenced type, word, struct, or module.

Example

module app;

def main(Missing -- Missing) end

How to fix

Correct the symbol name, define it, or import the module that contains it.

module app;

def main( -- u8) 1u8 end

B0009

Invalid Symbol

A known symbol was used where the compiler expected a different kind of symbol. If valid Buyan source triggers this error, it may indicate an internal compiler inconsistency.

Example

[B0009] Error: Invalid Symbol
  symbol 'app.main' cannot be used here

How to fix

Check that calls refer to words and type positions refer to types or structs.

Include the source file and complete compiler output in the issue.

B0010

Recursive Struct

A struct contains itself directly or through another struct, so its size cannot be determined.

Example

module app;

struct A(B);
struct B(A);

How to fix

Break the recursive chain by replacing one of the recursive fields with a non-recursive type.

module app;

struct A(B);
struct B(u8);

B0011

Invalid Field Index

A struct field access uses an index outside the struct’s field range. Field indexes start at zero.

Example

module app;

struct Pair(u8, u16);

def first(Pair -- u8) Pair.2 end

How to fix

Use an index that exists on the struct.

module app;

struct Pair(u8, u16);

def first(Pair -- u8) Pair.0 end

B0012

Invalid Stack

The current stack does not match the inputs required by an instruction or the output declared by a word.

Example

import std.u8;
import std.str;

module app;

def main( -- u8)
    1u8
    "two"
    std.u8.add
end

How to fix

Make the produced values match the required stack types and order.

import std.u8;

module app;

def main( -- u8)
    1u8
    2u8
    std.u8.add
end

B0013

Empty Word

A non-builtin word has no instructions in its body. This is reported as a warning.

Example

module app;

def main( -- ) end

How to fix

Add the intended implementation or remove the unused word.

module app;

def main( -- u8)
    1u8
end

B0014

Unused Import

A module is imported but none of its words or structs are used. This is reported as a warning.

Example

import std.io;

module app;

def main( -- u8) 1u8 end

How to fix

Remove the import or use a symbol from the imported module.

module app;

def main( -- u8) 1u8 end

B0015

Runtime Error

The interpreter encountered an invalid runtime condition.

How to fix

Read the diagnostic message for the exact cause.

B0016

Cannot Infer Type

The compiler could not infer all polymorphic parameters from the available inputs.

Example

module app;

#[builtin]
def make<T>( -- T) end

def main( -- u8)
    make
end

How to fix

Provide an input or another type constraint from which the generic parameter can be inferred.

module app;

#[builtin]
def identity<T>(T -- T) end

def main( -- u8)
    1u8
    identity
end

B0017

Invalid String Literal

String literals may contain only ASCII characters.

Example

import std.str;

module app;

def main( -- std.str.Str)
    "Привет"
end

How to fix

Replace non-ASCII characters with an ASCII representation.

import std.str;

module app;

def main( -- std.str.Str)
    "Hello"
end

B0018

Data Overflow

The data is too large.

Example

import std.str;

module app;

def main( -- std.str.Str)
    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
end

How to fix

Reduce or split the data that exceeded the limit.

std.os.cpm

Structs

Words

bdos_call

Targets: z80-unknown-cpm

Signature

u16, u8 -- u16

p_termcpm

Targets: z80-unknown-cpm

Signature

--

c_read

Targets: z80-unknown-cpm

Signature

-- u8

c_write

Targets: z80-unknown-cpm

Signature

u8 --

std.os.interpreter

Structs

Words

put_char

Targets: interpreter

Signature

u8 --

read_char

Targets: interpreter

Signature

-- u8

std.unsafe.mem

Structs

Words

load

Signature

ptr -- u8

store

Signature

ptr, u8 --

copy

Signature

ptr, ptr, usize --

alloc

Targets: interpreter, z80-unknown-cpm

Signature

usize -- ptr

std.bool

Structs

Words

eq

Signature

bool, bool -- bool

std.bytearray

Structs

ByteArray

Fields

ptr, usize, usize

Words

new

Signature

usize -- ByteArray

is_full

Signature

ByteArray -- bool

push

Signature

ByteArray, u8 -- ByteArray

get

Signature

ByteArray, usize -- u8

std.cfg

Structs

Words

if

Stack vars: ...S, ...R

Signature

...S, bool, |...S -- ...R|, |...S -- ...R| -- ...R

while

Stack vars: ...S

Signature

...S, |...S -- ...S, bool|, |...S -- ...S| -- ...S

std.intrinsics

Structs

Words

add

Type vars: T

Signature

T, T -- T

sub

Type vars: T

Signature

T, T -- T

mul

Type vars: T

Signature

T, T -- T

div

Type vars: T

Signature

T, T -- T

eq

Type vars: T

Signature

T, T -- bool

gt

Type vars: T

Signature

T, T -- bool

lt

Type vars: T

Signature

T, T -- bool

drop

Type vars: T

Signature

T --

dup

Type vars: T

Signature

T -- T, T

swap

Type vars: A, B

Signature

A, B -- B, A

over

Type vars: A, B

Signature

A, B -- A, B, A

rotate_left

Type vars: A, B, C

Signature

A, B, C -- B, C, A

rotate_right

Type vars: A, B, C

Signature

A, B, C -- C, A, B

call

Stack vars: ...S, ...R

Signature

...S, |...S -- ...R| -- ...R

u8_to_u16

Signature

u8 -- u16

u16_to_u8

Signature

u16 -- u8

u8_to_usize

Signature

u8 -- usize

u16_to_usize

Signature

u16 -- usize

usize_to_u8

Signature

usize -- u8

usize_to_u16

Signature

usize -- u16

offset

Signature

ptr, u16 -- ptr

load

Signature

ptr -- u8

store

Signature

ptr, u8 --

std.io

Structs

Words

print

Signature

std.str.Str --

println

Signature

std.str.Str --

input

Signature

-- u8

_put_char

Targets: interpreter

Signature

u8 --

_read_char

Targets: interpreter

Signature

-- u8

_put_char

Targets: z80-unknown-cpm

Signature

u8 --

_read_char

Targets: z80-unknown-cpm

Signature

-- u8

std.ptr

Structs

Words

offset

Signature

ptr, u16 -- ptr

std.stack

Structs

Words

drop

Type vars: T

Signature

T --

dup

Type vars: T

Signature

T -- T, T

swap

Type vars: A, B

Signature

A, B -- B, A

over

Type vars: A, B

Signature

A, B -- A, B, A

rotate_left

Type vars: A, B, C

Signature

A, B, C -- B, C, A

rotate_right

Type vars: A, B, C

Signature

A, B, C -- C, A, B

call

Stack vars: ...S, ...R

Signature

...S, |...S -- ...R| -- ...R

std.str

Structs

Str

Fields

ptr

Words

len

Signature

Str -- u8

get

Signature

Str, u8 -- u8

std.u8

Structs

Words

add

Signature

u8, u8 -- u8

sub

Signature

u8, u8 -- u8

mul

Signature

u8, u8 -- u8

div

Signature

u8, u8 -- u8

eq

Signature

u8, u8 -- bool

gt

Signature

u8, u8 -- bool

lt

Signature

u8, u8 -- bool

std.u16

Structs

Words

from_u8

Signature

u8 -- u16

low_u8

Signature

u16 -- u8

add

Signature

u16, u16 -- u16

sub

Signature

u16, u16 -- u16

mul

Signature

u16, u16 -- u16

div

Signature

u16, u16 -- u16

eq

Signature

u16, u16 -- bool

gt

Signature

u16, u16 -- bool

lt

Signature

u16, u16 -- bool

std.usize

Structs

Words

from_u8

Targets: interpreter, z80-unknown-cpm

Signature

u8 -- usize

from_u16

Targets: interpreter, z80-unknown-cpm

Signature

u16 -- usize

low_u8

Targets: interpreter, z80-unknown-cpm

Signature

usize -- u8

low_u16

Targets: interpreter, z80-unknown-cpm

Signature

usize -- u16

add

Signature

usize, usize -- usize

sub

Signature

usize, usize -- usize

mul

Signature

usize, usize -- usize

div

Signature

usize, usize -- usize

eq

Signature

usize, usize -- bool

gt

Signature

usize, usize -- bool

lt

Signature

usize, usize -- bool

Hello World

import std.io;
import std.str;

module hello_world;

def main( -- )
    "Hello, world!"
    std.io.println
end
buyan hello_world.by