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 interpreterz80-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:
2puts the number 2 on the stack. The stack is now[2].2puts 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 typeTand leaves one value of typeT. If you call it withusizevalues, it leaves ausize. If you call it withu8values, it leaves au8. - 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...Twith ausizevalue on top. It removes theusizeand 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
boolcondition. - 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
boolon 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:
42puts theusizevalue42on the stack.0x2Aputs the hexadecimalusizevalue42on the stack.42u8puts theu8value42on the stack.0x2Au8puts the hexadecimalu8value42on the stack.'*'puts the character*on the stack as theu8value42.42u16puts theu16value42on the stack.0x2Au16puts the hexadecimalu16value42on 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
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