Compare commits

..

4 Commits

Author SHA1 Message Date
af08d1888a real relative imports 2026-07-15 18:26:28 +02:00
9bbd382e10 http server 2026-07-07 19:40:25 +02:00
aea9ce911a std json parser 2026-07-05 15:22:50 +02:00
873aa3e1cc normal destructuring with one line changed 2026-07-01 17:01:47 +02:00
55 changed files with 540 additions and 293 deletions

43
Cargo.lock generated
View File

@@ -2,49 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "cc"
version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "libmimalloc-sys"
version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
dependencies = [
"cc",
]
[[package]]
name = "mimalloc"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "zern"
version = "0.3.0"
dependencies = [
"mimalloc",
]

View File

@@ -3,9 +3,3 @@ name = "zern"
version = "0.3.0"
edition = "2024"
license = "BSD-2-Clause"
[dependencies]
mimalloc = { version = "0.1.52", optional = true }
[features]
mimalloc = ["dep:mimalloc"]

View File

@@ -11,6 +11,8 @@ A very cool language
## Syntax
```rust
include "$/io.zr"
func main[] : i64
answer := os.urandom_i64()->abs() % 100

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
// https://brainfuck.org/sierpinski.b

View File

@@ -1,6 +1,6 @@
// needs to be compiled with -m -C "-lraylib"
include "std/io.zr"
include "std/os.zr"
include "$/io.zr"
include "$/os.zr"
extern InitWindow
extern SetTargetFPS
@@ -52,7 +52,7 @@ func CHIP8.free[c: CHIP8] : void
c->keyboard->free()
c->display->free()
c->keyboard_map->free()
mem.free(c)
(c as ptr)->free()
func CHIP8.disassemble[c: CHIP8, ins_count: i64] : void
for i in 0..ins_count
@@ -278,7 +278,7 @@ func main[argc: i64, argv: ptr] : i64
disassemble := false
for i in 1..argc
arg := mem.read64(argv + i * 8) as str
arg := os.arg(argv, i)
if arg->equal("-d")
disassemble = true
else
@@ -291,7 +291,7 @@ func main[argc: i64, argv: ptr] : i64
c := new CHIP8
c->init()
~buffer, ok := io.read_binary_file(path)
buffer, ok := io.read_binary_file(path)
if !ok
panic("failed to read file")

View File

@@ -1,18 +1,18 @@
include "std/io.zr"
include "std/net.zr"
include "$/io.zr"
include "$/net.zr"
func main[argc: i64, argv: ptr] : i64
if argc < 2
io.println("ERROR: url is missing")
return 1
url := mem.read64(argv + 8) as str
url := os.arg(argv, 1)
if url->len() <= 7
io.println("ERROR: invalid url (Did you forget \"http://\"?)")
return 1
if !url->substr(0, 7)->equal("http://")
if !url->substr_n(0, 7)->equal("http://")
io.println("ERROR: only http scheme is supported")
return 1
@@ -24,23 +24,19 @@ func main[argc: i64, argv: ptr] : i64
break
i += 1
host := url->substr(host_start, i - host_start)
host := url->substr_n(host_start, i - host_start)
path := "/"
if i < url_len
path = url->substr(i, url_len - i)
path = url->substr_n(i, url_len - i)
~s, ok := net.connect(host, 80)
s, ok := net.connect(host, 80)
if !ok
panic("failed to connect")
req := new str.Builder
req->append("GET ")
req->append(path)
req->append(" HTTP/1.0\r\nHost: ")
req->append(host)
req->append("\r\nConnection: close\r\n\r\n")
net.send(s, req->data, req->size)
req->destroy()
// seems reasonable
req := _stackalloc(4096) as str
req_size := req->format_into(4096, "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host)
net.send(s, req as ptr, req_size)
header_buf := mem.alloc(8192) as str
header_size := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
sum := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
sum := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
sum := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
n := 600851475143

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
out := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
out := 1

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
sum_of_squares := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
found := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
n := "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
for a in 1..1000

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
sum := 0

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
N := 20

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func num_divisors[n: i64] : i64
end := n->isqrt()

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
n := 37107287533 + 46376937677 + 74324986199 + 91942213363 + 23067588207 + 89261670696 + 28112879812 + 44274228917 + 47451445736 + 70386486105 + 62176457141 + 64906352462 + 92575867718 + 58203565325 + 80181199384 + 35398664372 + 86515506006 + 71693888707 + 54370070576 + 53282654108 + 36123272525 + 45876576172 + 17423706905 + 81142660418 + 51934325451 + 62467221648 + 15732444386 + 55037687525 + 18336384825 + 80386287592 + 78182833757 + 16726320100 + 48403098129 + 87086987551 + 59959406895 + 69793950679 + 41052684708 + 65378607361 + 35829035317 + 94953759765 + 88902802571 + 25267680276 + 36270218540 + 24074486908 + 91430288197 + 34413065578 + 23053081172 + 11487696932 + 63783299490 + 67720186971 + 95548255300 + 76085327132 + 37774242535 + 23701913275 + 29798860272 + 18495701454 + 38298203783 + 34829543829 + 40957953066 + 29746152185 + 41698116222 + 62467957194 + 23189706772 + 86188088225 + 11306739708 + 82959174767 + 97623331044 + 42846280183 + 55121603546 + 32238195734 + 75506164965 + 62177842752 + 32924185707 + 99518671430 + 73267460800 + 76841822524 + 97142617910 + 87783646182 + 10848802521 + 71329612474 + 62184073572 + 66627891981 + 60661826293 + 85786944089 + 66024396409 + 64913982680 + 16730939319 + 94809377245 + 78639167021 + 15368713711 + 40789923115 + 44889911501 + 41503128880 + 81234880673 + 82616570773 + 22918802058 + 77158542502 + 72107838435 + 20849603980 + 53503534226
io.println(n->to_str()->substr(0, 10))
io.println(n->to_str()->substr_n(0, 10))

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func collatz_num[n: i64] : i64
if n % 2 == 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
n := 40

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
n := [1]

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
s1 := [0, 3, 3, 5, 4, 4, 3, 5, 5, 4]

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func findmax[triangle: Array, row: i64, col: i64] : i64
if row == 14

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func days[y: i64, m: i64] : i64
if m == 2

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func multiply[n: Array, x: i64] : void
carry := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func divisors_sum[n: i64] : i64
k := n

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
a := 0

View File

@@ -1,4 +1,4 @@
include "std/io.zr"
include "$/io.zr"
func main[] : i64
for i in 1..40

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/os.zr"
include "$/io.zr"
include "$/os.zr"
func main[] : i64
answer := os.urandom_i64()->abs() % 100

View File

@@ -1,4 +0,0 @@
include "std/io.zr"
func main[] : i64
io.println("Hello, World!")

View File

@@ -1,18 +0,0 @@
include "std/io.zr"
include "std/os.zr"
func main[] : i64
arr := []
for i in 0..10
arr->push(os.urandom_i64()->abs() % 1000)
for i in 0..arr->size
io.println_i64(arr->nth(i))
io.println("------------")
arr->quicksort()
for i in 0..arr->size
io.println_i64(arr->nth(i))
arr->free()

View File

@@ -1,5 +1,5 @@
// needs to be compiled with -m -C "-lraylib"
include "std/io.zr"
include "$/io.zr"
extern InitWindow
extern SetTargetFPS

View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/containers.zr"
include "$/io.zr"
include "$/containers.zr"
func rule110_step[state: Array] : void
new_state := []

96
examples/serve.zr Normal file
View File

@@ -0,0 +1,96 @@
// needs to be compiled with -m -C "-lpthread"
include "$/net.zr"
extern pthread_create
extern pthread_detach
struct ConnData
sock: i64
addr: str
func handle_connection[conn: ConnData] : void
req := _stackalloc(10001) as str
n := net.read(conn->sock, req as ptr, 10000)
if n <= 0
net.close(conn->sock)
conn->addr->free()
(conn as ptr)->free()
return
req[n] = 0
pos := req->find("\r\n")
if pos == -1
panic("TODO: bad request")
first_line := req->substr_n(0, pos)
parts := first_line->split(" ")
method := parts->nth(0) as str
path := parts->nth(1) as str
if !method->equal("GET")
// TODO: method not supported
return
if path->equal("/")
// TODO: index
return
// TODO: ANY validation
file_path := "."->concat(path)
file_size, ok := os.get_file_size(file_path)
if !ok
// TODO: not found
return
fd := _builtin_syscall(SYS_openat, AT_FDCWD, file_path, O_RDONLY, 0)
if fd < 0
// TODO: not found
return
resp := "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"
net.send(conn->sock, resp as ptr, resp->len())
offset := 0
while offset < file_size
sent := _builtin_syscall(SYS_sendfile, conn->sock, fd, ^offset, IP_MAXPACKET)
if sent <= 0
break
net.close(conn->sock)
_builtin_syscall(SYS_close, fd)
file_path->free()
first_line->free()
parts->free_with_items()
conn->addr->free()
(conn as ptr)->free()
func main[argc: i64, argv: ptr] : i64
port := 8000
if argc > 1
port = os.arg(argv, 1)->parse_i64()
server_sock, ok := net.listen("0.0.0.0", port)
if !ok
panic("failed to listen")
io.printf("Listening on :%d...\n", port)
addr := _stackalloc(16)
while true
conn := net.accept(server_sock, addr)
if conn < 0
io.println("ERROR: failed to accept connection")
continue
conn_data := new* ConnData
conn_data->sock = conn
conn_data->addr = mem.alloc(30) as str
conn_data->addr->format_into(30, "%d.%d.%d.%d:%d", addr[4], addr[5], addr[6], addr[7], mem.read16be(addr + 2))
thread := 0
pthread_create(^thread, 0, ^handle_connection, conn_data)
pthread_detach(thread)

View File

@@ -1,5 +1,5 @@
// needs to be compiled with -m -C "-lsqlite3"
include "std/io.zr"
include "$/io.zr"
extern sqlite3_open
extern sqlite3_exec

View File

@@ -1,26 +0,0 @@
include "std/io.zr"
include "std/net.zr"
func main[] : i64
~s, ok := net.listen("127.0.0.1", 8000)
if !ok
panic("failed to listen")
io.println("Listening on port 8000...")
resp := mem.alloc(IP_MAXPACKET)
addr := _stackalloc(16)
while true
conn := net.accept(s, addr)
if conn < 0
continue
io.printf("%d.%d.%d.%d:%d\n", addr[4], addr[5], addr[6], addr[7], mem.read16be(addr + 2))
n := net.read(conn, resp, IP_MAXPACKET)
io.print_sized(resp, n)
io.println("")
net.send(conn, resp, n)
net.close(conn)

View File

@@ -1,8 +1,8 @@
include "std/io.zr"
include "std/net.zr"
include "$/io.zr"
include "$/net.zr"
func main[] : i64
~s, ok := net.create_udp_server("127.0.0.1", 8000)
s, ok := net.create_udp_server("127.0.0.1", 8000)
if !ok
panic("net.create_udp_server failed")

View File

@@ -812,7 +812,6 @@ _builtin_environ:
if *use_heap {
emit!(&mut self.output, " mov rdi, {}", memory_size);
emit!(&mut self.output, " call mem.alloc");
emit!(&mut self.output, " push rax");
} else {
let aligned_size = (memory_size + 15) & !15;
emit!(&mut self.output, " sub rsp, {}", aligned_size);

View File

@@ -7,16 +7,11 @@ mod typechecker;
use std::{
collections::HashSet,
fs,
path::Path,
process::{self, Command},
};
use tokenizer::ZernError;
#[cfg(feature = "mimalloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn compile_file(args: Args) -> Result<(), ZernError> {
let source = match fs::read_to_string(&args.path) {
Ok(x) => x,
@@ -26,10 +21,8 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
}
};
let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap();
let mut included_paths = HashSet::new();
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source, &mut included_paths);
let tokenizer = tokenizer::Tokenizer::new(args.path, source, &mut included_paths);
let parser = parser::Parser::new(tokenizer.tokenize()?);
let statements = parser.parse()?;

View File

@@ -342,7 +342,7 @@ impl Parser {
Ok(Stmt::Break)
} else if self.match_token(&[TokenType::KeywordContinue]) {
Ok(Stmt::Continue)
} else if self.match_token(&[TokenType::Tilde]) {
} else if self.check(&TokenType::Identifier) && self.check_ahead(&TokenType::Comma) {
let mut targets = vec![];
loop {
targets.push(self.consume(Identifier, "expected an identifier")?);

View File

@@ -30,7 +30,6 @@ pub enum TokenType {
ShiftLeft,
ShiftRight,
Arrow,
Tilde,
Equal,
DoubleEqual,
@@ -179,7 +178,6 @@ impl<'a> Tokenizer<'a> {
'%' => self.add_token(TokenType::Mod)?,
'^' => self.add_token(TokenType::Xor)?,
':' => self.add_token(TokenType::Colon)?,
'~' => self.add_token(TokenType::Tilde)?,
'-' => {
if self.match_char('=') {
self.add_token(TokenType::MinusEqual)?
@@ -451,28 +449,40 @@ impl<'a> Tokenizer<'a> {
self.include_file(path)
}
fn include_file(&mut self, path: String) -> Result<(), ZernError> {
let canonical = match fs::canonicalize(&path) {
fn include_file(&mut self, mut path: String) -> Result<(), ZernError> {
if path.starts_with("$/") {
path = find_std_path()
.join(&path[2..])
.to_string_lossy()
.into_owned();
}
let base_dir = Path::new(&self.loc.filename).parent().unwrap();
let resolved_path = base_dir.join(&path);
let canonical = match fs::canonicalize(&resolved_path) {
Ok(p) => p,
Err(e) => {
return error!(self.loc, format!("failed to resolve {}: {}", path, e));
}
};
if !self.included_paths.insert(canonical) {
if !self.included_paths.insert(canonical.clone()) {
return Ok(());
}
let source = match fs::read_to_string(&path) {
let source = match fs::read_to_string(&canonical) {
Ok(x) => x,
Err(_) => {
return error!(self.loc, format!("failed to include {}", path));
}
};
let filename = Path::new(&path).file_name().unwrap().to_str().unwrap();
let tokenizer = Tokenizer::new(filename.to_owned(), source, &mut *self.included_paths);
let tokenizer = Tokenizer::new(
canonical.to_string_lossy().into_owned(),
source,
&mut *self.included_paths,
);
self.tokens.extend(tokenizer.tokenize()?);
self.tokens.pop(); // remove inner Eof
@@ -552,3 +562,15 @@ impl<'a> Tokenizer<'a> {
self.current >= self.source.len()
}
}
fn find_std_path() -> PathBuf {
let path = std::env::current_exe().unwrap();
for dir in path.ancestors() {
let candidate = dir.join("std");
if candidate.is_dir() {
return candidate;
}
}
panic!("could not find zern std directory");
}

View File

@@ -596,6 +596,15 @@ impl<'a> TypeChecker<'a> {
match &func_type.params {
FnParams::Normal(params) => {
if params.is_empty() || params[0] != receiver_type {
return error!(
method.loc,
format!(
"first parameter of the method must be of type {}",
receiver_type
)
);
}
if params.len() != args.len() + 1 {
return error!(
method.loc,
@@ -606,15 +615,6 @@ impl<'a> TypeChecker<'a> {
)
);
}
if params[0] != receiver_type {
return error!(
method.loc,
format!(
"first parameter of the method must be of type {}",
receiver_type
)
);
}
for (i, arg) in args.iter().enumerate() {
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
}

View File

@@ -1,4 +1,4 @@
include "std/mem.zr"
include "mem.zr"
struct Array
data: ptr
@@ -38,14 +38,14 @@ func Array.push[xs: Array, x: any] : void
func Array.free[xs: Array] : void
if xs->data != 0
xs->data->free()
mem.free(xs)
(xs as ptr)->free()
func Array.free_with_items[xs: Array] : void
if xs->data != 0
for i in 0..xs->size
mem.free(xs->nth(i))
(xs->nth(i) as ptr)->free()
xs->data->free()
mem.free(xs)
(xs as ptr)->free()
func Array.pop[xs: Array] : any
if xs->size == 0
@@ -137,7 +137,7 @@ func str.split[haystack: str, needle: str]: Array
return result
else
for i in 0..haystack_len
result->push(haystack->substr(i, 1))
result->push(haystack->substr_n(i, 1))
return result
start := 0
@@ -150,13 +150,13 @@ func str.split[haystack: str, needle: str]: Array
match = false
break
if match
result->push(haystack->substr(start, i - start))
result->push(haystack->substr_n(start, i - start))
start = i + needle_len
i += needle_len
continue
i += 1
result->push(haystack->substr(start, haystack_len - start))
result->push(haystack->substr_n(start, haystack_len - start))
return result
const HashMap._TABLE_SIZE = 100
@@ -214,27 +214,36 @@ func HashMap.delete[map: HashMap, key: str] : void
map->table->set(index, current->next)
current->key->free()
mem.free(current)
(current as ptr)->free()
return
prev = current
current = current->next
func HashMap.keys[map: HashMap] : Array
out := []
for i in 0..HashMap._TABLE_SIZE
current : HashMap._Node = map->table->nth(i)
while current as ptr
out->push(current->key)
current = current->next
return out
func HashMap.free_with_keys[map: HashMap] : void
for i in 0..HashMap._TABLE_SIZE
current : HashMap._Node = map->table->nth(i)
while current as ptr
tmp := current
current = current->next
tmp->key->free()
(tmp as ptr)->free()
map->table->free()
(map as ptr)->free()
func HashMap._djb2[key: str] : i64
hash := 5381
for i in 0..key->len()
hash = ((hash << 5) + hash) + key[i]
hash = (hash & 0x7fffffffffffffff) // prevent negative
return hash % HashMap._TABLE_SIZE
func HashMap.free[map: HashMap] : void
for i in 0..HashMap._TABLE_SIZE
current : HashMap._Node = map->table->nth(i)
while current as ptr
tmp := current
current = current->next
tmp->key->free()
mem.free(tmp)
map->table->free()
mem.free(map)

View File

@@ -1,14 +0,0 @@
include "std/linux_syscalls.zr"
include "std/posix.zr"
// weird like that so num doesnt depend on io
func panic[msg: str] : void
len := 0
while msg[len]
len += 1
_builtin_syscall(SYS_write, 2, "PANIC: ", 7)
_builtin_syscall(SYS_write, 2, msg, len)
_builtin_syscall(SYS_write, 2, "\n", 1)
// for gdb backtrace
_builtin_syscall(SYS_kill, _builtin_syscall(SYS_getpid), SIGABRT)
_builtin_syscall(SYS_exit, 1)

View File

@@ -1,4 +1,15 @@
include "std/str.zr"
include "str.zr"
include "os.zr"
func panic[msg: str] : void
io.printf("PANIC: %s\n", msg)
// for gdb backtrace
_builtin_syscall(SYS_kill, os.getpid(), SIGABRT)
os.exit(1)
func assert[cond: bool, msg: str] : void
if !cond
panic(msg)
func io.printf[..] : void
s := _var_arg(0) as ptr
@@ -85,9 +96,7 @@ func io.read_line[] : str
if c == '\n'
break
b->append_char(c)
s := b->build()
b->destroy()
return s
return b->build_and_free_buffer()
func io.read_text_file[path: str] : str, bool
fd := _builtin_syscall(SYS_openat, AT_FDCWD, path, O_RDONLY, 0)
@@ -128,7 +137,7 @@ func io.read_binary_file[path: str] : Blob, bool
return buf, true
func io.write_file[path: str, content: str] : bool
func io.write_text_file[path: str, content: str] : bool
return io.write_binary_file(path, content as ptr, content->len())
func io.write_binary_file[path: str, content: ptr, size: i64] : bool

203
std/json.zr Normal file
View File

@@ -0,0 +1,203 @@
include "containers.zr"
const json.OBJECT = 0
const json.ARRAY = 1
const json.STRING = 2
const json.NUMBER = 3
const json.NULL = 4
const json.BOOL = 5
struct json.Value
type: i64
value: any
func json.Value.dump[v: json.Value] : str
s := new str.Builder
if v->type == json.OBJECT
map := v->value as HashMap
keys := map->keys()
s->append_char('{')
for i in 0..keys->size
if i > 0
s->append_char(',')
s->append_char('"')
s->append(keys->nth(i) as str)
s->append_char('"')
s->append_char(':')
value, _ := map->get(keys->nth(i) as str)
s->append((value as json.Value)->dump())
s->append_char('}')
keys->free()
else if v->type == json.ARRAY
arr := v->value as Array
s->append_char('[')
for i in 0..arr->size
if i > 0
s->append_char(',')
s->append((arr->nth(i) as json.Value)->dump())
s->append_char(']')
else if v->type == json.STRING
// TODO: escape sequences
s->append_char('"')
s->append(v->value as str)
s->append_char('"')
else if v->type == json.NUMBER
s->append((v->value as i64)->to_str())
else if v->type == json.NULL
s->append("null")
else if v->type == json.BOOL
if v->value as bool
s->append("true")
else
s->append("false")
else
panic("json.Value.dump: unknown value type")
return s->build_and_free_buffer()
func json.Value.free[v: json.Value] : void
if v->type == json.OBJECT
map := v->value as HashMap
keys := map->keys()
for i in 0..keys->size
value, _ := map->get(keys->nth(i) as str)
(value as json.Value)->free()
keys->free()
map->free_with_keys()
else if v->type == json.ARRAY
arr := v->value as Array
for i in 0..arr->size
(arr->nth(i) as json.Value)->free()
arr->free()
else if v->type == json.STRING
(v->value as str)->free()
(v as ptr)->free()
func json.val[x: any] : any
return (x as json.Value)->value
func json.parse[s: str] : json.Value
i := 0
return json._parse_value(s, ^i)
func json._parse_value[s: str, i: ptr] : json.Value
out := new* json.Value
json._skip_whitespace(s, i)
if s[mem.read64(i)] == 0
panic("json.parse: unexpected EOF")
if s[mem.read64(i)] == '{'
mem.write64(i, mem.read64(i) + 1)
json._skip_whitespace(s, i)
out->type = json.OBJECT
out->value = HashMap.new()
if s[mem.read64(i)] != '}'
while true
json._skip_whitespace(s, i)
key := json._parse_string(s, i)
json._skip_whitespace(s, i)
if s[mem.read64(i)] != ':'
panic("json.parse: expected ':'")
mem.write64(i, mem.read64(i) + 1)
json._skip_whitespace(s, i)
value := json._parse_value(s, i)
(out->value as HashMap)->insert(key, value)
key->free()
json._skip_whitespace(s, i)
if s[mem.read64(i)] == ','
mem.write64(i, mem.read64(i) + 1)
else
break
json._skip_whitespace(s, i)
if s[mem.read64(i)] != '}'
panic("json.parse: expected '}'")
mem.write64(i, mem.read64(i) + 1)
else if s[mem.read64(i)] == '['
mem.write64(i, mem.read64(i) + 1)
json._skip_whitespace(s, i)
out->type = json.ARRAY
out->value = []
while s[mem.read64(i)] != ']'
json._skip_whitespace(s, i)
(out->value as Array)->push(json._parse_value(s, i))
json._skip_whitespace(s, i)
if s[mem.read64(i)] == ','
mem.write64(i, mem.read64(i) + 1)
else
break
json._skip_whitespace(s, i)
if s[mem.read64(i)] != ']'
panic("json.parse: expected ']'")
mem.write64(i, mem.read64(i) + 1)
else if s[mem.read64(i)] == '"'
out->type = json.STRING
out->value = json._parse_string(s, i)
else if s[mem.read64(i)]->is_digit() || s[mem.read64(i)] == '-'
start := mem.read64(i)
if s[mem.read64(i)] == '-'
mem.write64(i, mem.read64(i) + 1)
while s[mem.read64(i)]->is_digit()
mem.write64(i, mem.read64(i) + 1)
// TODO: float support once str.parse_f64 is a thing
if s[mem.read64(i)] == '.'
panic("json.parse: parsing floats not implemented yet")
len := mem.read64(i) - start
num_str := s->substr_n(start, len)
out->type = json.NUMBER
out->value = num_str->parse_i64()
num_str->free()
else if s[mem.read64(i)] == 'n' && s[mem.read64(i)+1] == 'u' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 'l'
out->type = json.NULL
mem.write64(i, mem.read64(i) + 4)
else if s[mem.read64(i)] == 't' && s[mem.read64(i)+1] == 'r' && s[mem.read64(i)+2] == 'u' && s[mem.read64(i)+3] == 'e'
out->type = json.BOOL
out->value = true
mem.write64(i, mem.read64(i) + 4)
else if s[mem.read64(i)] == 'f' && s[mem.read64(i)+1] == 'a' && s[mem.read64(i)+2] == 'l' && s[mem.read64(i)+3] == 's' && s[mem.read64(i)+4] == 'e'
out->type = json.BOOL
out->value = false
mem.write64(i, mem.read64(i) + 5)
else
panic("json.parse: unexpected character")
return out
func json._parse_string[s: str, i: ptr] : str
if s[mem.read64(i)] != '"'
panic("json.parse: expected '\"'")
mem.write64(i, mem.read64(i) + 1)
start := mem.read64(i)
// TODO: escape sequences
while s[mem.read64(i)] != '"'
if s[mem.read64(i)] == 0
panic("json.parse: unexpected EOF")
mem.write64(i, mem.read64(i) + 1)
len := mem.read64(i) - start
mem.write64(i, mem.read64(i) + 1)
return s->substr_n(start, len)
func json._skip_whitespace[s: str, i: ptr] : void
while s[mem.read64(i)]->is_whitespace()
mem.write64(i, mem.read64(i) + 1)

View File

@@ -1,5 +1,5 @@
include "std/linux_syscalls.zr"
include "std/num.zr"
include "linux_syscalls.zr"
include "num.zr"
const MEM_BLOCK_SIZE = 32
@@ -33,7 +33,7 @@ func mem._request_space[size: i64] : mem.Block
alloc_size := (needed + 4095) & -4096
blk := _builtin_syscall(SYS_mmap, 0, alloc_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) as mem.Block
if (blk as ptr as i64) == -1
if (blk as ptr as i64) == MAP_FAILED
panic("mem._request_space: failed to mmap")
blk->size = alloc_size - MEM_BLOCK_SIZE
@@ -72,9 +72,6 @@ func mem.alloc[size: i64] : ptr
return blk as ptr + MEM_BLOCK_SIZE
func mem.free[x: any] : void
ptr.free(x as ptr)
func ptr.free[x: ptr] : void
if x == 0
return
@@ -237,4 +234,4 @@ func Blob.alloc[size: i64] : Blob
func Blob.free[buf: Blob] : void
buf->data->free()
mem.free(buf)
(buf as ptr)->free()

View File

@@ -1,4 +1,4 @@
include "std/os.zr"
include "os.zr"
const AF_INET = 2
const SOCK_STREAM = 1
@@ -22,7 +22,7 @@ func net.listen[host: str, port: i64] : i64, bool
_builtin_syscall(SYS_close, s)
return -1, false
~packed_host, ok := net.resolve(host)
packed_host, ok := net.resolve(host)
if !ok
return -1, false
@@ -44,7 +44,7 @@ func net.connect[host: str, port: i64] : i64, bool
if s < 0
return -1, false
~packed_host, ok := net.resolve(host)
packed_host, ok := net.resolve(host)
if !ok
return -1, false
@@ -101,18 +101,18 @@ func net.UDPSocket.receive[s: net.UDPSocket, size: i64] : net.UDPPacket
func net.UDPSocket.close_and_free[s: net.UDPSocket] : void
_builtin_syscall(SYS_close, s->fd)
s->addr->free()
mem.free(s)
(s as ptr)->free()
func net.create_udp_client[host: str, port: i64] : net.UDPSocket, bool
s := new* net.UDPSocket
~packed_host, ok := net.resolve(host)
packed_host, ok := net.resolve(host)
if !ok
return 0 as net.UDPSocket, false
s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0)
if s->fd < 0
mem.free(s)
(s as ptr)->free()
return 0 as net.UDPSocket, false
s->addr = mem.alloc(16)
@@ -120,7 +120,7 @@ func net.create_udp_client[host: str, port: i64] : net.UDPSocket, bool
return s, true
func net.create_udp_server[host: str, port: i64] : net.UDPSocket, bool
~s, ok := net.create_udp_client(host, port)
s, ok := net.create_udp_client(host, port)
if !ok
return 0 as net.UDPSocket, false
@@ -141,7 +141,7 @@ struct net.UDPPacket
func net.UDPPacket.free[pkt: net.UDPPacket] : void
pkt->data->free()
pkt->source_addr->free()
mem.free(pkt)
(pkt as ptr)->free()
func net.resolve[domain: str] : i64, bool
// if domain is already an ip, skip dns resolution
@@ -172,7 +172,7 @@ func net.resolve[domain: str] : i64, bool
query := net.build_dns_query(domain, DNS_TYPE_A)
// TODO: this probably shouldnt be hardcoded
~s, ok := net.create_udp_client("1.1.1.1", 53)
s, ok := net.create_udp_client("1.1.1.1", 53)
if !ok
query->free()
return -1, false

View File

@@ -1,4 +1,4 @@
include "std/core.zr"
include "io.zr"
func i64.abs[n: i64] : i64
if n == -9223372036854775808
@@ -97,6 +97,16 @@ func i64.to_hex_str_buf[n: i64, buf: ptr] : void
buf[len] = 0
func i64.to_str[n: i64] : str
out := mem.alloc(21)
n->to_str_buf(out)
return out as str
func i64.to_hex_str[n: i64] : str
out := mem.alloc(17)
n->to_hex_str_buf(out)
return out as str
func f64.to_str_buf[x: i64, buf: ptr] : void
bits := x
sign := (bits >> 63) & 1

View File

@@ -1,7 +1,7 @@
include "std/posix.zr"
include "std/linux_syscalls.zr"
include "std/str.zr"
include "std/containers.zr"
include "posix.zr"
include "linux_syscalls.zr"
include "str.zr"
include "containers.zr"
func os.exit[code: i64] : void
_builtin_syscall(SYS_exit, code)
@@ -9,7 +9,10 @@ func os.exit[code: i64] : void
func os.getpid[] : i64
return _builtin_syscall(SYS_getpid)
func os.getenv[name: str] : str
func os.arg[argv: ptr, n: i64] : str
return mem.read64(argv + n * 8) as str
func os.getenv[name: str] : str, bool
name_len := name->len()
i := 0
while true
@@ -17,15 +20,24 @@ func os.getenv[name: str] : str
if entry as ptr == 0
break
if entry->equal_n(name, name_len) && entry[name_len] == '='
return (entry as ptr + name_len + 1) as str
return (entry as ptr + name_len + 1) as str, true
i += 1
return ""
return 0 as str, false
func os.basename[path: str] : str
i := path->len() - 1
len := path->len()
end := len
while end > 0 && path[end - 1] == '/'
end -= 1
if end == 0
return "/"
i := end - 1
while i >= 0 && path[i] != '/'
i -= 1
return path->substr(i + 1, path->len() - i - 1)
return path->substr_n(i + 1, end - i - 1)
struct os.Timespec
tv_sec: i64
@@ -87,6 +99,15 @@ func os.run_shell_command[command: str] : i64, bool
func os.file_exists[path: str] : bool
return _builtin_syscall(SYS_faccessat, AT_FDCWD, path, F_OK, 0) == 0
func os.get_file_size[path: str] : i64, bool
st := _stackalloc(256) // it has 21 mixed-size fields so ptr for now
rc := _builtin_syscall(SYS_newfstatat, AT_FDCWD, path, st, 0)
if rc != 0
return 0, false
return mem.read64(st + 48), true
func os.is_a_directory[path: str] : bool
st := _stackalloc(256) // it has 21 mixed-size fields so ptr for now

View File

@@ -8,10 +8,15 @@ const AT_FDCWD = -100
const S_IFDIR = 16384
const S_IFMT = 61440
const PROT_NONE = 0
const PROT_READ = 1
const PROT_WRITE = 2
const PROT_EXEC = 4
const MAP_FAILED = -1
const MAP_SHARED = 1
const MAP_PRIVATE = 2
const MAP_FIXED = 16
const MAP_ANONYMOUS = 32
const O_RDONLY = 0

View File

@@ -1,5 +1,5 @@
include "std/num.zr"
include "std/mem.zr"
include "num.zr"
include "mem.zr"
func u8.is_whitespace[x: u8] : bool
return x == ' ' || x == '\n' || x == '\t' || x == '\r'
@@ -29,7 +29,7 @@ func u8.to_str[c: u8] : str
return s
func str.free[s: str] : void
mem.free(s)
(s as ptr)->free()
func str.len[s: str] : i64
i := 0
@@ -103,7 +103,7 @@ func str.format_into[..] : i64
n += 1
i += 1
else if s[0] == 'f'
// TODO: fix when we implement f64 params
// TODO: use arrow when we implement f64 params
f64.to_str_buf(_var_arg(i), tmp as ptr)
tmp_len := tmp->len()
remaining := size - n - 1
@@ -166,7 +166,7 @@ func str.find[haystack: str, needle: str] : i64
return i
return -1
func str.substr[s: str, start: i64, length: i64] : str
func str.substr_n[s: str, start: i64, length: i64] : str
out := mem.alloc(length + 1) as str
mem.copy(s as ptr + start, out as ptr, length)
out[length] = 0
@@ -188,7 +188,7 @@ func str.trim[s: str] : str
while end >= start && s[end]->is_whitespace()
end -= 1
return s->substr(start, end - start + 1)
return s->substr_n(start, end - start + 1)
func str.reverse[s: str] : str
len := s->len()
@@ -286,19 +286,14 @@ func str.Builder.build[b: str.Builder] : str
s[b->size] = 0
return s
func str.Builder.destroy[b: str.Builder] : void
func str.Builder.build_and_free_buffer[b: str.Builder] : str
s := b->build()
b->free_buffer()
return s
func str.Builder.free_buffer[b: str.Builder] : void
if b->data != 0
b->data->free()
b->size = 0
b->capacity = 0
// here to prevent num depending on mem
func i64.to_str[n: i64] : str
out := mem.alloc(21)
n->to_str_buf(out)
return out as str
func i64.to_hex_str[n: i64] : str
out := mem.alloc(17)
n->to_hex_str_buf(out)
return out as str

19
test.zr
View File

@@ -1,5 +1,5 @@
include "std/io.zr"
include "std/os.zr"
include "$/io.zr"
include "$/os.zr"
struct TestRunner
build_blacklist: Array
@@ -8,7 +8,7 @@ struct TestRunner
custom_run_args: HashMap
func TestRunner.run_directory[tr: TestRunner, dir: str] : void
~files, ok := os.list_directory(dir)
files, ok := os.list_directory(dir)
if !ok
panic("failed to open test directory")
for i in 0..files->size
@@ -28,12 +28,12 @@ func TestRunner.run_test[tr: TestRunner, x: str] : void
io.printf("Building %s...\n", x)
build_cmd := "./target/release/zern "->concat(x)
~custom_build_flags, ok := tr->custom_build_flags->get(name)
custom_build_flags, ok := tr->custom_build_flags->get(name)
if ok
build_cmd = build_cmd->concat(" ")->concat(custom_build_flags)
build_start_time := os.time()
~status, ok := os.run_shell_command(build_cmd)
status, ok := os.run_shell_command(build_cmd)
if !ok || status != 0
os.exit(1)
build_end_time := os.time()
@@ -47,12 +47,12 @@ func TestRunner.run_test[tr: TestRunner, x: str] : void
io.printf("Running %s...\n", x)
run_cmd := "./out"
~custom_run_args, ok := tr->custom_run_args->get(name)
custom_run_args, ok := tr->custom_run_args->get(name)
if ok
run_cmd = run_cmd->concat(" ")->concat(custom_run_args)
run_start_time := os.time()
~status, ok := os.run_shell_command(run_cmd)
status, ok := os.run_shell_command(run_cmd)
if !ok || status != 0
io.println("Test failed.")
os.exit(1)
@@ -61,17 +61,18 @@ func TestRunner.run_test[tr: TestRunner, x: str] : void
io.printf("Running %s took %dms\n", x, run_end_time - run_start_time)
func main[] : i64
~status, ok := os.run_shell_command("cargo build --release")
status, ok := os.run_shell_command("cargo build --release")
if !ok || status != 0
os.exit(1)
tr := new TestRunner
tr->build_blacklist = []
tr->run_blacklist = ["raylib.zr", "sqlite_todo.zr", "guess_number.zr", "udp_server.zr", "chip8.zr", "tcp_server.zr"]
tr->run_blacklist = ["raylib.zr", "sqlite_todo.zr", "guess_number.zr", "udp_server.zr", "chip8.zr", "serve.zr"]
tr->custom_build_flags = HashMap.new()
tr->custom_build_flags->insert("raylib.zr", "-m -C \"-lraylib\"")
tr->custom_build_flags->insert("chip8.zr", "-m -C \"-lraylib\"")
tr->custom_build_flags->insert("sqlite_todo.zr", "-m -C \"-lsqlite3\"")
tr->custom_build_flags->insert("serve.zr", "-m -C \"-lpthread\"")
tr->custom_run_args = HashMap.new()
tr->custom_run_args->insert("curl.zr", "http://example.com")