Overview

Bishop is a modern programming language that transpiles to C++. It combines the power of C++ with an elegant, Python-inspired syntax designed for readability and ease of use.

Type Safe
Strong static typing with type inference using :=
Error Handling
Elegant or keyword for handling errors inline
Concurrent
Built-in goroutines and channels for concurrency
Modern Syntax
Clean, readable code without boilerplate

Quick Start

Installation

Bishop is currently in alpha and available for Linux x64 only. Download the latest release from GitHub Releases.

Debian/Ubuntu

bash
# Download from https://github.com/chrishayen/bishop/releases
sudo dpkg -i bishop-linux-x64-v0.14.0.deb

Manual Installation

bash
# Download bishop-linux-x64-v0.14.0.tar.gz from
# https://github.com/chrishayen/bishop/releases
tar -xzf bishop-linux-x64-v0.14.0.tar.gz
cd bishop-linux-x64-v0.14.0

# Install binary, libraries, and includes
sudo mv bin/bishop /usr/local/bin/
sudo cp -r lib/* /usr/local/lib/
sudo cp -r include/* /usr/local/include/
sudo ldconfig

Usage

bash
# Run a Bishop program
bishop run examples/serve.b

# Run tests
bishop test tests/

Bishop source files use the .b extension.

Primitive Types

Type Description
intInteger
strString
boolBoolean (true/false)
f3232-bit float
f6464-bit float
u32Unsigned 32-bit integer
u64Unsigned 64-bit integer

Optional Types

Any type can be made optional by appending ?:

bishop
int? maybe_num = none;
int? value = 42;

// Check for none
if value is none { }
if value { }  // shorthand for is not none

Variables

Explicit Type Declaration

bishop
int x = 42;
str name = "Chris";
bool flag = true;
f64 pi = 3.14159;

Type Inference

Use := for type inference:

bishop
x := 100;        // inferred as int
name := "Hello"; // inferred as str
pi := 3.14;      // inferred as f64

Constants

Use const to declare immutable values:

bishop
const int MAX_SIZE = 100;
const str APP_NAME = "MyApp";

// With type inference
const MAX := 100;
const NAME := "Bishop";

Functions

Basic Function

bishop
fn add(int a, int b) -> int {
    return a + b;
}

// Void function (no return type)
fn greet() {
    print("Hello");
}

Function References

Functions can be passed as arguments:

bishop
fn apply_op(int x, int y, fn(int, int) -> int op) -> int {
    return op(x, y);
}

result := apply_op(3, 4, add);  // result = 7

Anonymous Functions

bishop
// Basic anonymous function
doubler := fn(int x) -> int { return x * 2; };
result := doubler(21);  // 42

// Closures capture variables
multiplier := 10;
scale := fn(int x) -> int { return x * multiplier; };
result := scale(4);  // 40

Lambdas with Goroutines

bishop
ch := Channel<int>();

go fn() {
    ch.send(42);
}();

val := ch.recv();  // 42

Structs

bishop
// Definition
Person :: struct {
    name str,
    age int
}

// Instantiation
p := Person { name: "Chris", age: 32 };

// Field access
print(p.name);
p.age = 33;

Pass by Reference

bishop
fn set_age(Person *p, int new_age) {
    p.age = new_age;  // auto-deref, always mutable
}

bob := Person { name: "Bob", age: 25 };
set_age(&bob, 26);
assert_eq(bob.age, 26);  // mutation visible

Methods

bishop
Person :: get_name(self) -> str {
    return self.name;
}

Person :: set_age(self, int new_age) {
    self.age = new_age;
}

p := Person { name: "Chris", age: 32 };
p.get_name();     // "Chris"
p.set_age(33);    // mutates p.age

Static Methods

Static methods belong to the type itself. Define them with the @static decorator:

bishop
Counter :: struct {
    value int
}

@static
Counter :: create() -> Counter {
    return Counter { value: 0 };
}

@static
Counter :: from_value(int val) -> Counter {
    return Counter { value: val };
}

// Call on type name
c := Counter.create();           // Counter { value: 0 }
c := Counter.from_value(42);     // Counter { value: 42 }

If/Else

bishop
if condition {
    // then
}

if condition {
    // then
} else {
    // else
}

Loops

While Loop

bishop
i := 0;

while i < 5 {
    print(i);
    i = i + 1;
}

Range-based For

bishop
for i in 0..10 {
    print(i);  // prints 0 through 9
}

Foreach

bishop
nums := [1, 2, 3];

for n in nums {
    print(n);
}

Strings

Strings can use double or single quotes:

bishop
str greeting = "Hello, World!";
str name = 'Alice';

// Raw strings (no escape processing)
pattern := r"\d+\.\d+";
path := r"C:\Users\name";
String Methods Reference
MethodDescription
length()Returns string length
empty()Returns true if empty
contains(str)Check if substring exists
starts_with(str)Check prefix
ends_with(str)Check suffix
substr(start, len)Extract substring
upper()Convert to uppercase
lower()Convert to lowercase
trim()Remove whitespace
split(sep)Split into list
replace(old, new)Replace first match
replace_all(old, new)Replace all matches
to_int()Parse as integer
to_float()Parse as float

Lists

bishop
// Creation
nums := List<int>();
names := ["a", "b", "c"];

// Methods
nums.append(42);
nums.length();
nums.get(0);
nums.first();
nums.last();
nums.pop();
nums.contains(42);

// String lists can join
parts := ["hello", "world"];
parts.join(" ");  // "hello world"

Pairs

Pairs hold exactly two values of the same type:

bishop
p := Pair<int>(10, 20);
x := p.first;   // 10
y := p.second;  // 20

// With default
x := p.get(0) default 0;
z := p.get(2) default 99;  // out of bounds, uses default

Tuples

Tuples hold 2-5 values of the same type:

bishop
t := Tuple<int>(10, 20, 30);
x := t.get(0) default 0;   // 10
y := t.get(1) default 0;   // 20

Error Types

bishop
// Simple error
ParseError :: err;

// Error with custom fields
IOError :: err {
    code int,
    path str
}

// Fallible function
fn read_config(str path) -> Config or err {
    if !fs.exists(path) {
        fail IOError { message: "not found", code: 404, path: path };
    }
    return parse(content);
}

The or Keyword

Handle errors elegantly inline:

bishop
// Return early
x := fallible() or return;

// Return with default value
x := fallible() or return default_value;

// Propagate error
x := fallible() or fail err;

// Standalone guard clause
fs.exists(path) or fail "skill not found";

// Block with error access
x := fallible() or {
    print("Error:", err.message);
    return;
};

// Pattern match on error type
x := fallible() or match err {
    IOError    => default_config,
    ParseError => fail err,
    _          => fail ConfigError { message: "unknown", cause: err }
};

// Continue/break in loops
for item in items {
    result := process(item) or continue;
}

Error Chaining

bishop
fn load_config(str path) -> Config or err {
    content := fs.read_file(path)
        or fail ConfigError { message: "can't read " + path, cause: err };

    return parse(content)
        or fail ConfigError { message: "invalid config", cause: err };
}

// Access error chain
config := load_config("app.conf") or {
    print(err.message);        // "can't read app.conf"
    print(err.cause.message);  // underlying error
    print(err.root_cause.message);  // original error
    return;
};

Goroutines

bishop
fn sender(Channel<int> ch, int val) {
    ch.send(val);
}

fn main() {
    ch := Channel<int>();
    go sender(ch, 42);  // spawn goroutine
    val := ch.recv();    // blocks until value available
    print(val);          // 42
}

Channels

bishop
ch := Channel<int>();
ch_str := Channel<str>();
ch_bool := Channel<bool>();

// Send and receive
ch.send(42);
val := ch.recv();

Select Statement

Wait on multiple channel operations:

bishop
ch1 := Channel<int>();
ch2 := Channel<int>();

go sender(ch1, 41);

select {
    case val := ch1.recv() {
        print("received from ch1:", val);
    }
    case val := ch2.recv() {
        print("received from ch2:", val);
    }
}

http

bishop
import http;

fn handle(http.Request req) -> http.Response {
    return http.text("Hello");
}

fn main() {
    http.serve(8080, handle);
}

App-based Routing

bishop
fn main() {
    app := http.App {};
    app.get("/", home);
    app.get("/about", about);
    app.listen(8080);
}

fs

bishop
import fs;

// Reading and writing
content := fs.read_file("path");
_ := fs.write_file("path", "content") or fail err;

// Checks
fs.exists("path");
fs.is_dir("path");
fs.is_file("path");

// Paths
fs.join("dir", "file.txt");
fs.dirname("/home/user/file.txt");
fs.basename("/home/user/file.txt");

// Directory walking
entries := fs.walk("src") or fail err;
for entry in entries {
    print(entry.path);
}

crypto

bishop
import crypto;

// Hashing
hash := crypto.sha256("hello") or return;
hash := crypto.md5("hello") or return;

// Base64
encoded := crypto.base64_encode("Hello!");
decoded := crypto.base64_decode(encoded) or return;

// UUID
id := crypto.uuid() or return;

net

bishop
import net;

// TCP Server
server := net.listen("127.0.0.1", 8080) or return;

with server as s {
    conn := s.accept() or return;
    data := conn.read(1024) or return;
    conn.write("Hello");
    conn.close();
}

// TCP Client
conn := net.connect("example.com", 80) or return;

process

bishop
import process;

// Execute command
result := process.run("ls", ["-la"]) or return;
print(result.output);
print(result.exit_code);

// Environment variables
home := process.env("HOME") or fail err;
process.set_env("MY_VAR", "value");

// Working directory
dir := process.cwd() or fail err;
args := process.args();

regex

bishop
import regex;

re := regex.compile(r"(\d+)-(\d+)") or return;

// Matching
re.matches("123");      // full match
re.contains("abc123");  // partial match

// Finding
m := re.find("Price: 100-200");
if m.found() {
    print(m.text);       // "100-200"
    print(m.group(1));   // "100"
}

// Replacement
re.replace("123-456", "$2-$1");  // "456-123"

math

bishop
import math;

// Constants
math.PI;   // 3.14159...
math.E;    // 2.71828...

// Operations
math.abs(-5.5);
math.sqrt(16.0);
math.pow(2.0, 10.0);
math.sin(math.PI / 2.0);
math.floor(3.7);
math.ceil(3.2);
math.min(3.0, 7.0);
math.max(3.0, 7.0);

time

bishop
import time;

// Duration
d := time.seconds(90);
d := time.minutes(5);
d := time.hours(2);

// Timestamp
now := time.now();
print(now.year, now.month, now.day);

// Arithmetic
future := now + time.days(7);
elapsed := time.since(start);

// Formatting
formatted := now.format("%Y-%m-%d %H:%M:%S");

random

bishop
import random;

dice := random.int(1, 6);
chance := random.float();
coin := random.bool();

items := ["a", "b", "c"];
pick := random.choice(items) or return;
random.shuffle(items);

// Seed for reproducibility
random.seed(42);

algo

bishop
import algo;

nums := [3, 1, 4, 1, 5];

// Sorting
algo.sort_int(nums);

// Aggregation
algo.sum_int(nums);
algo.min_int(nums) or return;

// Transformations
doubled := algo.map_int(nums, fn(int x) -> int { return x * 2; });
large := algo.filter_int(nums, fn(int x) -> bool { return x > 2; });

// Predicates
algo.all_int(nums, fn(int x) -> bool { return x > 0; });
algo.any_int(nums, fn(int x) -> bool { return x > 3; });

json

bishop
import json;

// Parsing
data := json.parse('{"name": "Alice", "age": 30}') or return;
name := data.get_str("name") or return;

// Creating
obj := json.object();
obj.set_str("name", "Charlie");
obj.set_int("age", 25);

// Serialization
json_str := json.stringify(obj);
json_str := json.stringify_pretty(obj);

log

bishop
import log;

log.debug("Debug info");
log.info("App started");
log.warn("High memory");
log.error("Connection failed");

// With key-value
log.info_kv("User login", "user_id", "12345");

// Configuration
log.set_level(log.INFO);
log.add_file("/var/log/app.log");

sync

bishop
import sync;

// Mutex
mtx := sync.mutex_create();
sync.mutex_lock(mtx);
// critical section
sync.mutex_unlock(mtx);

// WaitGroup
wg := sync.waitgroup_create();
sync.waitgroup_add(wg, 3);

go fn() {
    // work
    sync.waitgroup_done(wg);
}();

sync.waitgroup_wait(wg);

// Atomic
counter := sync.atomic_int_create(0);
sync.atomic_int_add(counter, 5);

yaml

bishop
import yaml;

data := yaml.parse("name: Alice\nage: 30") or return;
name := data.get_str("name") or return;

obj := yaml.object();
obj.set_str("name", "Charlie");

yaml_str := yaml.stringify(obj);

markdown

bishop
import markdown;

// Convert to HTML
html := markdown.to_html("# Title\n\n**bold**");

// Extract plain text
text := markdown.to_text("# Hello **World**");

// Document parsing
doc := markdown.parse("# Title") or return;
html := doc.to_html();

Import System

bishop
import http;
import fs;
import tests.testlib;

testlib.greet();
result := testlib.add(2, 3);

Using

Bring module members into local namespace:

bishop
import log;
using log.info, log.debug, log.warn, log.error;

fn main() {
    info("Application started");
}

// Wildcard import
import math;
using math.*;

x := sin(PI / 2.0);

Visibility

Use @private to restrict visibility to the current file:

bishop
@private
fn internal_helper() -> int {
    return 42;
}

@private
MyStruct :: struct {
    value int
}

FFI

TypeDescription
cintC int
cstrC string (const char*)
voidvoid return type
bishop
@extern("c")
fn puts(cstr s) -> cint;

@extern("m")
fn sqrt(f64 x) -> f64;

fn main() {
    puts("Hello from C!");
}

Resource Management

The with statement provides automatic resource cleanup:

bishop
Resource :: struct {
    name str
}

Resource :: close(self) {
    print("Closing resource");
}

fn main() {
    with create_resource("myfile") as res {
        print(res.name);
    }  // res.close() called automatically
}