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.
:=or keyword for handling errors inlineQuick Start
Installation
Bishop is currently in alpha and available for Linux x64 only. Download the latest release from GitHub Releases.
Debian/Ubuntu
# Download from https://github.com/chrishayen/bishop/releases
sudo dpkg -i bishop-linux-x64-v0.14.0.deb
Manual Installation
# 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
# 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 |
|---|---|
int | Integer |
str | String |
bool | Boolean (true/false) |
f32 | 32-bit float |
f64 | 64-bit float |
u32 | Unsigned 32-bit integer |
u64 | Unsigned 64-bit integer |
Optional Types
Any type can be made optional by appending ?:
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
int x = 42;
str name = "Chris";
bool flag = true;
f64 pi = 3.14159;
Type Inference
Use := for type inference:
x := 100; // inferred as int
name := "Hello"; // inferred as str
pi := 3.14; // inferred as f64
Constants
Use const to declare immutable values:
const int MAX_SIZE = 100;
const str APP_NAME = "MyApp";
// With type inference
const MAX := 100;
const NAME := "Bishop";
Functions
Basic Function
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:
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
// 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
ch := Channel<int>();
go fn() {
ch.send(42);
}();
val := ch.recv(); // 42
Structs
// 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
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
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:
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
if condition {
// then
}
if condition {
// then
} else {
// else
}
Loops
While Loop
i := 0;
while i < 5 {
print(i);
i = i + 1;
}
Range-based For
for i in 0..10 {
print(i); // prints 0 through 9
}
Foreach
nums := [1, 2, 3];
for n in nums {
print(n);
}
Strings
Strings can use double or single quotes:
str greeting = "Hello, World!";
str name = 'Alice';
// Raw strings (no escape processing)
pattern := r"\d+\.\d+";
path := r"C:\Users\name";
| Method | Description |
|---|---|
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
// 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:
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:
t := Tuple<int>(10, 20, 30);
x := t.get(0) default 0; // 10
y := t.get(1) default 0; // 20
Error Types
// 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:
// 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
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
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
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:
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
import http;
fn handle(http.Request req) -> http.Response {
return http.text("Hello");
}
fn main() {
http.serve(8080, handle);
}
App-based Routing
fn main() {
app := http.App {};
app.get("/", home);
app.get("/about", about);
app.listen(8080);
}
fs
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
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
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
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
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
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
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
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
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
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
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
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
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
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
import http;
import fs;
import tests.testlib;
testlib.greet();
result := testlib.add(2, 3);
Using
Bring module members into local namespace:
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:
@private
fn internal_helper() -> int {
return 42;
}
@private
MyStruct :: struct {
value int
}
FFI
| Type | Description |
|---|---|
cint | C int |
cstr | C string (const char*) |
void | void return type |
@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:
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
}