Compare commits

..

81 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
bc4e09805b split std 2026-07-01 14:43:39 +02:00
3a4a499cf6 optional mimalloc dep for 30% speed up 2026-07-01 10:51:36 +02:00
131db6a453 fix bugs found by the fuzzer 2026-07-01 10:49:38 +02:00
43a20c99f6 remove enums 2026-06-26 17:38:55 +02:00
6fe971c615 simplify emit_debug with gas magic 2026-06-26 16:50:05 +02:00
8ad69be5aa switched to gnu assembler for 5x speed up 2026-06-26 16:30:04 +02:00
5a09699845 include "statement" 2026-06-26 14:38:39 +02:00
3f3f161151 VERY hacky f64 printing 2026-06-26 09:34:18 +02:00
e4582ec85a float literals 2026-06-24 18:06:58 +02:00
213e3d4b37 floats 2026-06-24 17:20:57 +02:00
7259f13191 simple enums 2026-06-08 19:46:29 +02:00
2864548daf finally allow bare returns 2026-06-07 13:10:36 +02:00
d4fe032a3a emit debug flag 2026-06-07 12:03:22 +02:00
802a6d8b53 remove aoc solutions 2026-06-07 09:36:21 +02:00
a9bd4fe9f9 string hosts in net.* 2026-06-07 09:33:26 +02:00
a727a52ffb ptr.free 2026-06-06 23:55:02 +02:00
670596e1ff sort std 2026-06-06 22:47:16 +02:00
3098d0abf9 uniform function call syntax 2026-06-06 22:29:25 +02:00
3a53cd4f32 remove the pipe operator 2026-06-06 19:41:54 +02:00
c41c22e936 compute for's end only once 2026-06-06 19:36:28 +02:00
22874181cd desguar += and -= 2026-06-06 18:53:11 +02:00
535364931e parse negative constants 2026-06-06 09:26:34 +02:00
a6d44ec5e0 calculate needed stack space with a disgusting hack 2026-06-05 20:24:50 +02:00
be42267a59 io.snprintf 2026-06-05 09:47:59 +02:00
803aee772e net._build_sa, str.from_i64_buf 2026-06-04 21:35:02 +02:00
0b1b424478 remove array.new, std optimizations 2026-06-03 20:07:31 +02:00
a81742a7ec str.Builder 2026-06-03 19:06:50 +02:00
31b71a5e19 _stackalloc 2026-05-30 21:01:01 +02:00
f181cfe675 replace the horrible error system with multiple returns 2026-05-28 17:28:57 +02:00
0b16cc4513 destructuring multiple returns 2026-05-28 16:55:16 +02:00
eb14607f2a allow returning a second value via rdx 2026-05-28 09:41:11 +02:00
fa5ff0aaa5 turn Assign into a statement 2026-05-28 08:49:18 +02:00
45253bf004 stack allocated structs, print ips in net examples 2026-05-27 20:34:26 +02:00
4fda79f0bc new var declaration syntax 2026-05-27 20:25:58 +02:00
284bc61f24 typecheck chained pointer operations 2026-05-13 14:11:05 +02:00
027245684d type inference for variables 2026-05-13 14:01:44 +02:00
5f2082ef57 attach id to expressions 2026-05-13 13:14:55 +02:00
52443c837b --no-stdlib 2026-04-11 23:59:15 +02:00
3fd4fcd062 hex improvements 2026-04-09 15:01:36 +02:00
a17ffa184a _var_arg 2026-04-04 13:14:58 +02:00
bb42e1fcf7 parse variadic functions 2026-04-04 12:17:44 +02:00
86de997be3 hashmaps, enforce main function type 2026-03-20 19:27:51 +01:00
3efbb3ecd1 io.is_a_directory, octal literal constants 2026-03-20 15:35:35 +01:00
efe9dfe238 parse cli on our own 2026-03-19 13:42:12 +01:00
8758295641 Analyzer -> SymbolTable 2026-03-19 13:18:24 +01:00
e24763dfb6 gut analyzer by moving the arity check to the typechecker 2026-03-19 13:16:34 +01:00
2d2a2c667f LICENSE 2026-03-19 13:14:50 +01:00
625fc0d811 typecheck_expr doesnt need to mutate the TypeChecker 2026-03-18 19:08:10 +01:00
0dbe7560b6 refactor the nasty RefCell by parsing everything first 2026-03-18 18:53:13 +01:00
e774f06e65 validate types in New/Struct/Cast 2026-03-18 11:47:29 +01:00
2137d49abd typecheck if and while conditions, disallow ptr multiplication 2026-03-18 11:34:25 +01:00
3efb3820bc fix a bug that caused stuff to not get typechecked 2026-03-17 17:34:49 +01:00
4154901339 typecheck return types 2026-03-17 17:11:14 +01:00
664a51c88d add casts to examples 2026-03-17 16:34:20 +01:00
15a68a41d1 use FnType instead of the tuple mess 2026-03-17 16:27:39 +01:00
a3f480dc1d typecheck function calls 2026-03-17 16:10:28 +01:00
5846f7e730 casts, typecheck struct field access 2026-03-17 15:25:28 +01:00
13fa93c611 start implementing typechecking 2026-03-17 14:55:07 +01:00
69c254382d TypeChecker 2026-03-17 14:04:14 +01:00
677521ca98 clean up main.rs 2026-03-16 13:07:33 +01:00
fee5dc3099 more error handling 2026-03-16 12:26:58 +01:00
720218cf31 error handling 2026-03-16 10:59:55 +01:00
964bbe4ec0 make mem.free O(1) with some linked list voodoo thank you bill gates 2026-03-16 09:46:41 +01:00
78282679e7 a few more fixes 2026-03-16 09:32:12 +01:00
7b09b505b5 a few fixes 2026-03-16 09:20:40 +01:00
2f632077b8 restore curl.zr example now that we have DNS resolution once again 2026-03-14 13:10:45 +01:00
21aabca9f7 very simple dns resolver 2026-03-14 13:10:26 +01:00
9f4c494e09 start working on the dns resolver 2026-03-14 12:24:51 +01:00
3f9df4a32f don't heap allocate in io.printf 2026-03-14 11:44:14 +01:00
4a60c1fc12 io.printf, please dont look at it 2026-03-14 11:24:51 +01:00
100b752eee udp server example 2026-03-12 17:50:01 +01:00
f2a7179b03 remove crypto, just use libsodium 2026-03-12 17:49:42 +01:00
f21abaafff udp sockets 2026-03-12 16:57:28 +01:00
c67abf5a8e some new std functions 2026-03-12 16:06:22 +01:00
1ac7401e50 here's why you cant trust LLMs even with 20 line snippets 2026-03-12 14:40:25 +01:00
192cff8f8d drop gcc by default, any type 2026-03-12 14:24:44 +01:00
70640b3062 use mmap instead of brk in mem.alloc to fix a glibc segfault 2026-03-12 14:23:57 +01:00
81 changed files with 4781 additions and 3711 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
/target /target
/out* /out*
/TODO /TODO
/musl-*
/vscode /vscode
/fuzz
/perf.data*

239
Cargo.lock generated
View File

@@ -2,243 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "zern" name = "zern"
version = "0.1.0" version = "0.3.0"
dependencies = [
"clap",
]

View File

@@ -1,7 +1,5 @@
[package] [package]
name = "zern" name = "zern"
version = "0.1.0" version = "0.3.0"
edition = "2024" edition = "2024"
license = "BSD-2-Clause"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2025-2026, Antoni Piasecki
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -4,21 +4,21 @@ A very cool language
## Features ## Features
* Clean indentation-based syntax * Clean indentation-based syntax
* Compiles to x86_64 Assembly * Compiles to x86-64 Assembly
* Growing [standard library](https://github.com/antpiasecki/zern/tree/main/src/std)
* Produces tiny static executables (~30KB with musl)
* No libc required! * No libc required!
* Sometimes works * Produces tiny static executables (11KB for `hello.zr`)
* Has the pipe operator * Has static typing, [UFCS](https://en.wikipedia.org/wiki/Uniform_function_call_syntax), variadics, dynamic arrays, hashmaps, DNS resolver, etc.
## Syntax ## Syntax
```rust ```rust
include "$/io.zr"
func main[] : i64 func main[] : i64
let answer: i64 = math.abs(os.urandom_i64()) % 100 answer := os.urandom_i64()->abs() % 100
while true while true
io.println("Guess a number: ") io.println("Guess a number: ")
let guess: i64 = io.read_line() |> str.trim() |> str.parse_i64() guess := io.read_line()->parse_i64()
if guess == answer if guess == answer
io.println("You win!") io.println("You win!")
@@ -29,22 +29,8 @@ func main[] : i64
io.println("Too high!") io.println("Too high!")
``` ```
```rust
func square[x: i64] : i64
return x * x
func sum[a: i64, b: i64] : i64
return a + b
func main[] : i64
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
|> alg.map(^square)
|> alg.reduce(^sum, 0)
|> io.println_i64()
```
## Quickstart ## Quickstart
``` ```
cargo install --git https://github.com/antpiasecki/zern cargo install --git https://git.ton1.dev/toni/zern
zern -m -r hello.zr zern -r hello.zr
``` ```

View File

@@ -1,49 +1,51 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
// https://brainfuck.org/sierpinski.b // https://brainfuck.org/sierpinski.b
let src: str = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[-<<<[->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<<]>.>+[>>]>+]" src := "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[-<<<[->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<<]>.>+[>>]>+]"
let src_len: i64 = str.len(src) src_len := src->len()
let i = 0 i := 0
let memory: ptr = mem.alloc(30000) memory := mem.alloc(30000)
mem.zero(memory, 30000) mem.zero(memory, 30000)
let p = 0 p := 0
while i < src_len while i < src_len
let op: u8 = src[i] op := src[i]
if op == '>' if op == '>'
p = p + 1 p += 1
else if op == '<' else if op == '<'
p = p - 1 p -= 1
else if op == '+' else if op == '+'
memory[p] = memory[p] + 1 memory[p] += 1
else if op == '-' else if op == '-'
memory[p] = memory[p] - 1 memory[p] -= 1
else if op == '.' else if op == '.'
io.print_char(memory[p]) io.print_char(memory[p])
else if op == ',' else if op == ','
memory[p] = io.read_char() memory[p] = io.read_char()
else if op == '[' else if op == '['
if !memory[p] if !memory[p]
i = i + 1 i += 1
let opened = 0 opened := 0
while i < src_len && !(src[i] == ']' && !opened) while i < src_len && !(src[i] == ']' && !opened)
if src[i] == '[' if src[i] == '['
opened = opened + 1 opened += 1
else if src[i] == ']' else if src[i] == ']'
opened = opened - 1 opened -= 1
i = i + 1 i += 1
else if op == ']' else if op == ']'
if memory[p] if memory[p]
i = i - 1 i -= 1
let closed = 0 closed := 0
while i >= 0 && !(src[i] == '[' && !closed) while i >= 0 && !(src[i] == '[' && !closed)
if src[i] == ']' if src[i] == ']'
closed = closed + 1 closed += 1
else if src[i] == '[' else if src[i] == '['
closed = closed - 1 closed -= 1
i = i - 1 i -= 1
i = i + 1 i += 1
mem.free(memory) memory->free()

View File

@@ -1,4 +1,7 @@
// needs to be compiled with -m -C="-lraylib" // needs to be compiled with -m -C "-lraylib"
include "$/io.zr"
include "$/os.zr"
extern InitWindow extern InitWindow
extern SetTargetFPS extern SetTargetFPS
extern WindowShouldClose extern WindowShouldClose
@@ -15,14 +18,13 @@ struct CHIP8
sp: i64 sp: i64
reg: ptr reg: ptr
I: i64 I: i64
delay_timer: i64 delay_timer: u8
sound_timer: i64 sound_timer: u8
keyboard: Array keyboard: Array
display: ptr display: ptr
keyboard_map: Array keyboard_map: Array
func chip8_create[] : CHIP8 func CHIP8.init[c: CHIP8] : void
let c: CHIP8 = new CHIP8
c->memory = mem.alloc(4096) c->memory = mem.alloc(4096)
mem.zero(c->memory, 4096) mem.zero(c->memory, 4096)
c->display = mem.alloc(64*32) c->display = mem.alloc(64*32)
@@ -33,254 +35,162 @@ func chip8_create[] : CHIP8
c->stack = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] c->stack = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
c->keyboard = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] c->keyboard = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let fonts: Array = [0xf0, 0x90, 0x90, 0x90, 0xf0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xf0, 0x10, 0xf0, 0x80, 0xf0, 0xf0, 0x10, 0xf0, 0x10, 0xf0, 0x90, 0x90, 0xf0, 0x10, 0x10, 0xf0, 0x80, 0xf0, 0x10, 0xf0, 0xf0, 0x80, 0xf0, 0x90, 0xf0, 0xf0, 0x10, 0x20, 0x40, 0x40, 0xf0, 0x90, 0xf0, 0x90, 0xf0, 0xf0, 0x90, 0xf0, 0x10, 0xf0, 0xf0, 0x90, 0xf0, 0x90, 0x90, 0xe0, 0x90, 0xe0, 0x90, 0xe0, 0xf0, 0x80, 0x80, 0x80, 0xf0, 0xe0, 0x90, 0x90, 0x90, 0xe0, 0xf0, 0x80, 0xf0, 0x80, 0xf0, 0xf0, 0x80, 0xf0, 0x80, 0x80] fonts := [0xf0, 0x90, 0x90, 0x90, 0xf0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xf0, 0x10, 0xf0, 0x80, 0xf0, 0xf0, 0x10, 0xf0, 0x10, 0xf0, 0x90, 0x90, 0xf0, 0x10, 0x10, 0xf0, 0x80, 0xf0, 0x10, 0xf0, 0xf0, 0x80, 0xf0, 0x90, 0xf0, 0xf0, 0x10, 0x20, 0x40, 0x40, 0xf0, 0x90, 0xf0, 0x90, 0xf0, 0xf0, 0x90, 0xf0, 0x10, 0xf0, 0xf0, 0x90, 0xf0, 0x90, 0x90, 0xe0, 0x90, 0xe0, 0x90, 0xe0, 0xf0, 0x80, 0x80, 0x80, 0xf0, 0xe0, 0x90, 0x90, 0x90, 0xe0, 0xf0, 0x80, 0xf0, 0x80, 0xf0, 0xf0, 0x80, 0xf0, 0x80, 0x80]
for i in 0..80 for i in 0..80
c->memory[i] = array.nth(fonts, i) c->memory[i] = fonts->nth(i)
mem.free(fonts) fonts->free()
c->pc = 0x200 c->pc = 0x200
// see KeyboardKey in raylib.h // see KeyboardKey in raylib.h
c->keyboard_map = [49, 50, 51, 52, 53, 265, 32, 263, 264, 262, 87, 69, 82, 84, 89, 85] c->keyboard_map = [49, 50, 51, 52, 53, 265, 32, 263, 264, 262, 87, 69, 82, 84, 89, 85]
return c func CHIP8.free[c: CHIP8] : void
c->memory->free()
c->stack->free()
c->reg->free()
c->keyboard->free()
c->display->free()
c->keyboard_map->free()
(c as ptr)->free()
func chip8_disassemble[c: CHIP8, ins_count: i64] : void func CHIP8.disassemble[c: CHIP8, ins_count: i64] : void
for i in 0..ins_count for i in 0..ins_count
io.print("0x") io.printf("0x%x: ", c->pc)
io.print_i64_hex(c->pc)
io.print(": ")
let high: i64 = c->memory[c->pc] high := c->memory[c->pc] as i64
let low: i64 = c->memory[c->pc + 1] low := c->memory[c->pc + 1] as i64
c->pc = c->pc + 2 c->pc += 2
let ins: i64 = (high << 8) | low ins := (high << 8) | low
let nnn: i64 = ins & 0x0fff nnn := ins & 0x0fff
let kk: i64 = ins & 0x00ff kk := ins & 0x00ff
let n: i64 = ins & 0x000f n := ins & 0x000f
let x: i64 = (ins >> 8) & 0x0f x := (ins >> 8) & 0x0f
let y: i64 = (ins >> 4) & 0x0f y := (ins >> 4) & 0x0f
let op: i64 = (ins >> 12) & 0x0f op := (ins >> 12) & 0x0f
if op == 0x0 if op == 0x0
if nnn == 0x0e0 if nnn == 0x0e0
io.println("CLS") io.println("CLS")
else if nnn == 0x0ee else if nnn == 0x0ee
io.println("RET") io.println("RET")
else else
io.print("SYS ") io.printf("SYS %d\n", nnn)
io.println_i64(nnn)
else if op == 0x1 else if op == 0x1
io.print("JP 0x") io.printf("JP 0x%x\n", nnn)
io.print_i64_hex(nnn)
io.print("\n")
else if op == 0x2 else if op == 0x2
io.print("CALL ") io.printf("CALL %d\n", nnn)
io.println_i64(nnn)
else if op == 0x3 else if op == 0x3
io.print("SE V") io.printf("SE V%x, %d\n", x, kk)
io.print_i64_hex(x)
io.print(", ")
io.println_i64(kk)
else if op == 0x4 else if op == 0x4
io.print("SNE V") io.printf("SNE V%x, %d\n", x, kk)
io.print_i64_hex(x)
io.print(", ")
io.println_i64(kk)
else if op == 0x5 else if op == 0x5
io.print("SE V") io.printf("SE V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if op == 0x6 else if op == 0x6
io.print("LD V") io.printf("LD V%x, %d\n", x, kk)
io.print_i64_hex(x)
io.print(", ")
io.println_i64(kk)
else if op == 0x7 else if op == 0x7
io.print("ADD V") io.printf("ADD V%x, %d\n", x, kk)
io.print_i64_hex(x)
io.print(", ")
io.println_i64(kk)
else if op == 0x8 else if op == 0x8
if n == 0x0 if n == 0x0
io.print("LD V") io.printf("LD V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x1 else if n == 0x1
io.print("OR V") io.printf("OR V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x2 else if n == 0x2
io.print("AND V") io.printf("AND V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x3 else if n == 0x3
io.print("XOR V") io.printf("XOR V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x4 else if n == 0x4
io.print("ADD V") io.printf("ADD V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x5 else if n == 0x5
io.print("SUB V") io.printf("SUB V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0x6 else if n == 0x6
io.print("SHR V") io.printf("SHR V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if n == 0x7 else if n == 0x7
io.print("SUBN V") io.printf("SUBN V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if n == 0xE else if n == 0xE
io.print("SHL V") io.printf("SHL V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else else
io.print("??? (") io.printf("??? (%x)\n", ins)
io.print_i64_hex(ins)
io.println(")")
else if op == 0x9 else if op == 0x9
io.print("SNE V") io.printf("SNE V%x, V%x\n", x, y)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print("\n")
else if op == 0xa else if op == 0xa
io.print("LD I, 0x") io.printf("LD I, 0x%x\n", nnn)
io.print_i64_hex(nnn)
io.print("\n")
else if op == 0xb else if op == 0xb
io.print("JP V0, ") io.printf("JP V0, %d\n", nnn)
io.println_i64(nnn)
else if op == 0xc else if op == 0xc
io.print("RND V") io.printf("RND V%x, %d\n", x, kk)
io.print_i64_hex(x)
io.print(", ")
io.println_i64(kk)
else if op == 0xd else if op == 0xd
io.print("DRW V") io.printf("DRW V%x, V%x, %d\n", x, y, n)
io.print_i64_hex(x)
io.print(", V")
io.print_i64_hex(y)
io.print(", ")
io.println_i64(n)
else if op == 0xe else if op == 0xe
if kk == 0x9e if kk == 0x9e
io.print("SKP V") io.printf("SKP V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0xa1 else if kk == 0xa1
io.print("SKNP V") io.printf("SKNP V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else else
io.print("??? (") io.printf("??? (%x)\n", ins)
io.print_i64_hex(ins)
io.println(")")
else if op == 0xf else if op == 0xf
if kk == 0x07 if kk == 0x07
io.print("LD V") io.printf("LD V%x, DT\n", x)
io.print_i64_hex(x)
io.println(", DT")
else if kk == 0x0A else if kk == 0x0A
io.print("LD V") io.printf("LD V%x, K\n", x)
io.print_i64_hex(x)
io.println(", K")
else if kk == 0x15 else if kk == 0x15
io.print("LD DT, V") io.printf("LD DT, V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x18 else if kk == 0x18
io.print("LD ST, V") io.printf("LD ST, V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x1E else if kk == 0x1E
io.print("ADD I, V") io.printf("ADD I, V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x29 else if kk == 0x29
io.print("LD F, V") io.printf("LD F, V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x33 else if kk == 0x33
io.print("LD B, V") io.printf("LD B, V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x55 else if kk == 0x55
io.print("LD [I], V") io.printf("LD [I], V%x\n", x)
io.print_i64_hex(x)
io.print("\n")
else if kk == 0x65 else if kk == 0x65
io.print("LD V") io.printf("LD V%x, [I]\n", x)
io.print_i64_hex(x)
io.println(", [I]")
else else
io.print("??? (") io.printf("??? (%x)\n", ins)
io.print_i64_hex(ins)
io.println(")")
else else
io.print("??? (") io.printf("??? (%x)\n", ins)
io.print_i64_hex(ins)
io.println(")")
func chip8_step[c: CHIP8] : void func CHIP8.step[c: CHIP8] : void
let high: i64 = c->memory[c->pc] high := c->memory[c->pc] as i64
let low: i64 = c->memory[c->pc + 1] low := c->memory[c->pc + 1] as i64
c->pc = c->pc + 2 c->pc += 2
let ins: i64 = (high << 8) | low ins := (high << 8) | low
let nnn: i64 = ins & 0x0fff nnn := ins & 0x0fff
let kk: i64 = ins & 0x00ff kk := ins & 0x00ff
let n: i64 = ins & 0x000f n := ins & 0x000f
let x: i64 = (ins >> 8) & 0x0f x := (ins >> 8) & 0x0f
let y: i64 = (ins >> 4) & 0x0f y := (ins >> 4) & 0x0f
let op: i64 = (ins >> 12) & 0x0f op := (ins >> 12) & 0x0f
if op == 0x0 if op == 0x0
if nnn == 0x0e0 if nnn == 0x0e0
mem.zero(c->display, 64 * 32) mem.zero(c->display, 64 * 32)
else if nnn == 0x0ee else if nnn == 0x0ee
c->pc = array.nth(c->stack, c->sp) c->pc = c->stack->nth(c->sp)
c->sp = c->sp - 1 c->sp = c->sp - 1
else if op == 0x1 else if op == 0x1
c->pc = nnn c->pc = nnn
else if op == 0x2 else if op == 0x2
c->sp = c->sp + 1 c->sp += 1
array.set(c->stack, c->sp, c->pc) c->stack->set(c->sp, c->pc)
c->pc = nnn c->pc = nnn
else if op == 0x3 else if op == 0x3
if c->reg[x] == kk if c->reg[x] == kk
c->pc = c->pc + 2 c->pc += 2
else if op == 0x4 else if op == 0x4
if c->reg[x] != kk if c->reg[x] != kk
c->pc = c->pc + 2 c->pc += 2
else if op == 0x5 else if op == 0x5
if c->reg[x] == c->reg[y] if c->reg[x] == c->reg[y]
c->pc = c->pc + 2 c->pc += 2
else if op == 0x6 else if op == 0x6
c->reg[x] = kk c->reg[x] = kk
else if op == 0x7 else if op == 0x7
c->reg[x] = c->reg[x] + kk c->reg[x] += kk
else if op == 0x8 else if op == 0x8
if n == 0x0 if n == 0x0
c->reg[x] = c->reg[y] c->reg[x] = c->reg[y]
@@ -291,57 +201,57 @@ func chip8_step[c: CHIP8] : void
else if n == 0x3 else if n == 0x3
c->reg[x] = c->reg[x] ^ c->reg[y] c->reg[x] = c->reg[x] ^ c->reg[y]
else if n == 0x4 else if n == 0x4
let res: i64 = c->reg[x] + c->reg[y] res : u8 = c->reg[x] + c->reg[y]
c->reg[0xf] = res > 0xff c->reg[0xf] = (res > 0xff) as u8
c->reg[x] = res c->reg[x] = res
else if n == 0x5 else if n == 0x5
c->reg[0xf] = c->reg[x] > c->reg[y] c->reg[0xf] = (c->reg[x] > c->reg[y]) as u8
c->reg[x] = c->reg[x] - c->reg[y] c->reg[x] = c->reg[x] - c->reg[y]
else if n == 0x6 else if n == 0x6
c->reg[0xf] = c->reg[x] & 0x1 c->reg[0xf] = c->reg[x] & 0x1
c->reg[x] = c->reg[x] >> 1 c->reg[x] = c->reg[x] >> 1
else if n == 0x7 else if n == 0x7
c->reg[0xf] = c->reg[y] > c->reg[x] c->reg[0xf] = (c->reg[y] > c->reg[x]) as u8
c->reg[x] = c->reg[y] - c->reg[x] c->reg[x] = c->reg[y] - c->reg[x]
else if n == 0xE else if n == 0xE
c->reg[0xf] = (c->reg[x] & 0x80) >> 7 c->reg[0xf] = (c->reg[x] & 0x80) >> 7
c->reg[x] = c->reg[x] << 1 c->reg[x] = c->reg[x] << 1
else if op == 0x9 else if op == 0x9
if c->reg[x] != c->reg[y] if c->reg[x] != c->reg[y]
c->pc = c->pc + 2 c->pc += 2
else if op == 0xa else if op == 0xa
c->I = nnn c->I = nnn
else if op == 0xb else if op == 0xb
c->pc = nnn + c->reg[0] c->pc = nnn + c->reg[0]
else if op == 0xc else if op == 0xc
c->reg[x] = (math.abs(os.urandom_i64()) % 256) & kk c->reg[x] = (os.urandom_i64()->abs() % 256) & kk
else if op == 0xd else if op == 0xd
c->reg[0xf] = 0 c->reg[0xf] = 0
for row in 0..n for row in 0..n
for col in 0..8 for col in 0..8
if (c->memory[c->I + row] & (0x80 >> col)) != 0 if (c->memory[c->I + row] & (0x80 >> col)) != 0
let pixel_x: i64 = (c->reg[x] + col) % 64 pixel_x : u8 = (c->reg[x] + col) % 64
let pixel_y: i64 = (c->reg[y] + row) % 32 pixel_y : u8 = (c->reg[y] + row) % 32
let offset: i64 = pixel_x + (pixel_y * 64) offset := pixel_x as i64 + (pixel_y * 64)
if c->display[offset] == 1 if c->display[offset] == 1
c->reg[0xf] = 1 c->reg[0xf] = 1
c->display[offset] = c->display[offset] ^ 1 c->display[offset] = c->display[offset] ^ 1
else if op == 0xe else if op == 0xe
if kk == 0x9e if kk == 0x9e
if IsKeyDown(array.nth(c->keyboard_map, c->reg[x])) if IsKeyDown(c->keyboard_map->nth(c->reg[x] as i64))
c->pc = c->pc + 2 c->pc += 2
else if kk == 0xa1 else if kk == 0xa1
if !IsKeyDown(array.nth(c->keyboard_map, c->reg[x])) if !IsKeyDown(c->keyboard_map->nth(c->reg[x] as i64))
c->pc = c->pc + 2 c->pc += 2
else if op == 0xf else if op == 0xf
if kk == 0x07 if kk == 0x07
c->reg[x] = c->delay_timer c->reg[x] = c->delay_timer
else if kk == 0x0A else if kk == 0x0A
let key_pressed: bool = false key_pressed := false
while !key_pressed && !WindowShouldClose() while !key_pressed && !WindowShouldClose()
for i in 0..16 for i in 0..16
if IsKeyDown(array.nth(c->keyboard_map, i)) if IsKeyDown(c->keyboard_map->nth(i))
key_pressed = true key_pressed = true
break break
else if kk == 0x15 else if kk == 0x15
@@ -349,9 +259,9 @@ func chip8_step[c: CHIP8] : void
else if kk == 0x18 else if kk == 0x18
c->sound_timer = c->reg[x] c->sound_timer = c->reg[x]
else if kk == 0x1E else if kk == 0x1E
c->I = c->I + c->reg[x] c->I += c->reg[x]
else if kk == 0x29 else if kk == 0x29
c->I = c->reg[x] * 5 c->I = c->reg[x] as i64 * 5
else if kk == 0x33 else if kk == 0x33
c->memory[c->I] = c->reg[x] / 100 c->memory[c->I] = c->reg[x] / 100
c->memory[c->I + 1] = (c->reg[x] / 10) % 10 c->memory[c->I + 1] = (c->reg[x] / 10) % 10
@@ -363,39 +273,33 @@ func chip8_step[c: CHIP8] : void
for i in 0..x+1 for i in 0..x+1
c->reg[i] = c->memory[c->I + i] c->reg[i] = c->memory[c->I + i]
func chip8_free[c: CHIP8] : void
mem.free(c->memory)
array.free(c->stack)
mem.free(c->reg)
array.free(c->keyboard)
mem.free(c->display)
array.free(c->keyboard_map)
mem.free(c)
func main[argc: i64, argv: ptr] : i64 func main[argc: i64, argv: ptr] : i64
let path: str = 0 path := 0 as str
let disassemble: bool = 0 disassemble := false
for i in 1..argc for i in 1..argc
let arg: str = mem.read64(argv + i * 8) arg := os.arg(argv, i)
if str.equal(arg, "-d") if arg->equal("-d")
disassemble = 1 disassemble = true
else else
path = arg path = arg
if path == 0 if path as i64 == 0
io.println("Usage: chip8 -d <path>") io.println("Usage: chip8 -d <path>")
return 1 return 1
let c: CHIP8 = chip8_create() c := new CHIP8
c->init()
let buffer: io.Buffer = io.read_binary_file(path) buffer, ok := io.read_binary_file(path)
if !ok
panic("failed to read file")
for i in 0..buffer->size for i in 0..buffer->size
c->memory[0x200 + i] = buffer->data[i] c->memory[0x200 + i] = buffer->data[i]
if disassemble if disassemble
chip8_disassemble(c, buffer->size / 2) c->disassemble(buffer->size / 2)
return 0 return 0
InitWindow(640, 320, "CHIP-8") InitWindow(640, 320, "CHIP-8")
@@ -409,7 +313,7 @@ func main[argc: i64, argv: ptr] : i64
// TODO: buzzer // TODO: buzzer
for i in 0..25 for i in 0..25
chip8_step(c) c->step()
BeginDrawing() BeginDrawing()
@@ -419,4 +323,4 @@ func main[argc: i64, argv: ptr] : i64
if c->display[x + y * 64] == 1 if c->display[x + y * 64] == 1
DrawRectangle(x * 10, y * 10, 10, 10, 0xff000000) DrawRectangle(x * 10, y * 10, 10, 10, 0xff000000)
EndDrawing() EndDrawing()

View File

@@ -1,59 +0,0 @@
func main[] : i64
// Blake2b
let out: ptr = crypto.blake2b.hash(32, 0, 0, "Hello, World!", 13)
io.print("Blake2b-256: ")
io.println(str.hex_encode(out, 32))
io.print("Blake2b-512: ")
out = crypto.blake2b.hash(64, 0, 0, "Hello, World!", 13)
io.println(str.hex_encode(out, 64))
// XChaCha20
let key: ptr = str.hex_decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
let nonce: ptr = str.hex_decode("000102030405060708090a0b0c0d0e0f1011121314151617")
let input: str = "Hello, World!"
let input_len: i64 = str.len(input)
let ciphertext: ptr = crypto.xchacha20.xor_no_auth(key, nonce, input, input_len)
io.println(str.hex_encode(ciphertext, input_len))
// X25519
let scalar: ptr = str.hex_decode("a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4")
let point: ptr = str.hex_decode("e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c")
let expected: ptr = str.hex_decode("c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552")
out = crypto.x25519.scalarmult(scalar, point)
io.print("Computed: ")
io.println(str.hex_encode(out, 32))
io.print("Expected: ")
io.println(str.hex_encode(expected, 32))
let base_point: ptr = mem.alloc(32)
mem.zero(base_point, 32)
base_point[0] = 9
let alice_private: ptr = str.hex_decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
io.print("A_priv: ")
io.println(str.hex_encode(alice_private, 32))
let alice_public: ptr = crypto.x25519.scalarmult(alice_private, base_point)
io.print("A_pub: ")
io.println(str.hex_encode(alice_public, 32))
let bob_private: ptr = str.hex_decode("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6dbddb79b1732920165")
io.print("B_priv: ")
io.println(str.hex_encode(bob_private, 32))
let bob_public: ptr = crypto.x25519.scalarmult(bob_private, base_point)
io.print("B_pub: ")
io.println(str.hex_encode(bob_public, 32))
let alice_shared: ptr = crypto.x25519.scalarmult(alice_private, bob_public)
io.print("A_shared: ")
io.println(str.hex_encode(alice_shared, 32))
let bob_shared: ptr = crypto.x25519.scalarmult(bob_private, alice_public)
io.print("B_shared: ")
io.println(str.hex_encode(bob_shared, 32))

72
examples/curl.zr Normal file
View File

@@ -0,0 +1,72 @@
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 := os.arg(argv, 1)
if url->len() <= 7
io.println("ERROR: invalid url (Did you forget \"http://\"?)")
return 1
if !url->substr_n(0, 7)->equal("http://")
io.println("ERROR: only http scheme is supported")
return 1
url_len := url->len()
host_start := 7
i := host_start
while i < url_len
if url[i] == '/'
break
i += 1
host := url->substr_n(host_start, i - host_start)
path := "/"
if i < url_len
path = url->substr_n(i, url_len - i)
s, ok := net.connect(host, 80)
if !ok
panic("failed to connect")
// 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
found := false
end_index := -1
while !found && header_size < 8192
n := net.read(s, header_buf as ptr + header_size, 8192 - header_size)
if n <= 0
break
current_size := header_size + n
i = 0
while i <= current_size - 4
if header_buf[i] == '\r' && header_buf[i + 1] == '\n' && header_buf[i + 2] == '\r' && header_buf[i + 3] == '\n'
found = true
end_index = i + 4
break
i += 1
header_size = current_size
if end_index < header_size
io.print_sized(header_buf as ptr + end_index, header_size as i64 - end_index)
header_buf->free()
buffer := mem.alloc(4096)
while true
n := net.read(s, buffer, 4096)
if n <= 0
break
io.print_sized(buffer, n)
buffer->free()
net.close(s)

View File

@@ -1,8 +1,10 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let sum = 0 sum := 0
for i in 0..266000 for i in 0..266000
if i % 2 != 0 if i % 2 != 0
sum = sum + i * i sum += i * i
io.println_i64(sum) io.println_i64(sum)

View File

@@ -1,7 +1,10 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let sum = 0 sum := 0
for i in 0..1000 for i in 0..1000
if i % 5 == 0 || i % 3 == 0 if i % 5 == 0 || i % 3 == 0
sum = sum + i sum += i
io.println_i64(sum) io.println_i64(sum)

View File

@@ -0,0 +1,15 @@
include "$/io.zr"
func main[] : i64
sum := 0
a := 0
b := 1
while a < 4000000
if a % 2 == 0
sum += a
temp := b
b += a
a = temp
io.println_i64(sum)

View File

@@ -1,11 +1,13 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let n = 600851475143 n := 600851475143
let f = 2 f := 2
while f * f <= n while f * f <= n
if n % f == 0 if n % f == 0
n = n / f n = n / f
else else
f = f + 1 f += 1
io.println_i64(n) io.println_i64(n)

View File

@@ -0,0 +1,15 @@
include "$/io.zr"
func main[] : i64
out := 0
for a in 500..1000
for b in 500..1000
if a * b > out
s := (a * b)->to_str()
s_rev := s->reverse()
if s->equal(s_rev)
out = a * b
s->free()
s_rev->free()
io.println_i64(out)

View File

@@ -1,5 +1,7 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let out = 1 out := 1
for i in 1..21 for i in 1..21
out = math.lcm(out, i) out = math.lcm(out, i)

View File

@@ -1,11 +1,13 @@
func main[] : i64 include "$/io.zr"
let sum_of_squares = 0
for i in 1..101
sum_of_squares = sum_of_squares + i * i
let square_of_sum = 0 func main[] : i64
sum_of_squares := 0
for i in 1..101 for i in 1..101
square_of_sum = square_of_sum + i sum_of_squares += i * i
square_of_sum := 0
for i in 1..101
square_of_sum += i
square_of_sum = square_of_sum * square_of_sum square_of_sum = square_of_sum * square_of_sum
io.println_i64(square_of_sum - sum_of_squares) io.println_i64(square_of_sum - sum_of_squares)

View File

@@ -1,11 +1,13 @@
func main[] : i64 include "$/io.zr"
let found = 0
let i = 1 func main[] : i64
found := 0
i := 1
while true while true
if math.is_prime(i) if i->is_prime()
found = found + 1 found += 1
if found == 10001 if found == 10001
io.println_i64(i) io.println_i64(i)
break break
i = i + 1 i += 1

View File

@@ -0,0 +1,16 @@
include "$/io.zr"
func main[] : i64
n := "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"
out := 0
max := n->len() - 13
for i in 0..max
s := 1
j := 0
while j < 13
s = s * (n[i + j] - '0')
j += 1
if s > out
out = s
io.println_i64(out)

View File

@@ -1,7 +1,9 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
for a in 1..1000 for a in 1..1000
for b in 1..1000 for b in 1..1000
let c: i64 = 1000 - b - a c := 1000 - b - a
if a * a + b * b == c * c if a * a + b * b == c * c
io.println_i64(a * b * c) io.println_i64(a * b * c)
return 0 return 0

View File

@@ -0,0 +1,9 @@
include "$/io.zr"
func main[] : i64
sum := 0
for i in 0..2000000
if i->is_prime()
sum += i
io.println_i64(sum)

View File

@@ -0,0 +1,39 @@
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
N := 20
grid := []
grid->push([8, 2, 22, 97, 38, 15, 0, 40, 0, 75, 4, 5, 7, 78, 52, 12, 50, 77, 91, 8])
grid->push([49, 49, 99, 40, 17, 81, 18, 57, 60, 87, 17, 40, 98, 43, 69, 48, 4, 56, 62, 0])
grid->push([81, 49, 31, 73, 55, 79, 14, 29, 93, 71, 40, 67, 53, 88, 30, 3, 49, 13, 36, 65])
grid->push([52, 70, 95, 23, 4, 60, 11, 42, 69, 24, 68, 56, 1, 32, 56, 71, 37, 2, 36, 91])
grid->push([22, 31, 16, 71, 51, 67, 63, 89, 41, 92, 36, 54, 22, 40, 40, 28, 66, 33, 13, 80])
grid->push([24, 47, 32, 60, 99, 3, 45, 2, 44, 75, 33, 53, 78, 36, 84, 20, 35, 17, 12, 50])
grid->push([32, 98, 81, 28, 64, 23, 67, 10, 26, 38, 40, 67, 59, 54, 70, 66, 18, 38, 64, 70])
grid->push([67, 26, 20, 68, 2, 62, 12, 20, 95, 63, 94, 39, 63, 8, 40, 91, 66, 49, 94, 21])
grid->push([24, 55, 58, 5, 66, 73, 99, 26, 97, 17, 78, 78, 96, 83, 14, 88, 34, 89, 63, 72])
grid->push([21, 36, 23, 9, 75, 0, 76, 44, 20, 45, 35, 14, 0, 61, 33, 97, 34, 31, 33, 95])
grid->push([78, 17, 53, 28, 22, 75, 31, 67, 15, 94, 3, 80, 4, 62, 16, 14, 9, 53, 56, 92])
grid->push([16, 39, 5, 42, 96, 35, 31, 47, 55, 58, 88, 24, 0, 17, 54, 24, 36, 29, 85, 57])
grid->push([86, 56, 0, 48, 35, 71, 89, 7, 5, 44, 44, 37, 44, 60, 21, 58, 51, 54, 17, 58])
grid->push([19, 80, 81, 68, 5, 94, 47, 69, 28, 73, 92, 13, 86, 52, 17, 77, 4, 89, 55, 40])
grid->push([4, 52, 8, 83, 97, 35, 99, 16, 7, 97, 57, 32, 16, 26, 26, 79, 33, 27, 98, 66])
grid->push([88, 36, 68, 87, 57, 62, 20, 72, 3, 46, 33, 67, 46, 55, 12, 32, 63, 93, 53, 69])
grid->push([4, 42, 16, 73, 38, 25, 39, 11, 24, 94, 72, 18, 8, 46, 29, 32, 40, 62, 76, 36])
grid->push([20, 69, 36, 41, 72, 30, 23, 88, 34, 62, 99, 69, 82, 67, 59, 85, 74, 4, 36, 16])
grid->push([20, 73, 35, 29, 78, 31, 90, 1, 74, 31, 49, 71, 48, 86, 81, 16, 23, 57, 5, 54])
grid->push([1, 70, 54, 71, 83, 51, 54, 69, 16, 92, 33, 48, 61, 43, 52, 1, 89, 19, 67, 48])
out := 0
for i in 0..N-3
for j in 0..N-3
h : i64 = (grid->nth(i) as Array)->nth(j) * (grid->nth(i) as Array)->nth(j+1) * (grid->nth(i) as Array)->nth(j+2) * (grid->nth(i) as Array)->nth(j+3)
v : i64 = (grid->nth(j) as Array)->nth(i) * (grid->nth(j+1) as Array)->nth(i) * (grid->nth(j+2) as Array)->nth(i) * (grid->nth(j+3) as Array)->nth(i)
d1 : i64 = (grid->nth(i) as Array)->nth(j) * (grid->nth(i+1) as Array)->nth(j+1) * (grid->nth(i+2) as Array)->nth(j+2) * (grid->nth(i+3) as Array)->nth(j+3)
d2 : i64 = (grid->nth(i) as Array)->nth(N-j-1) * (grid->nth(i+1) as Array)->nth(N-j-2) * (grid->nth(i+2) as Array)->nth(N-j-3) * (grid->nth(i+3) as Array)->nth(N-j-4)
out = math.max(out, math.max(h, math.max(v, math.max(d1, d2))))
io.println_i64(out)

View File

@@ -1,21 +1,23 @@
func num_divisors[n: i64] : i64 include "$/io.zr"
let end: i64 = math.isqrt(n)
let out = 0 func num_divisors[n: i64] : i64
end := n->isqrt()
out := 0
for i in 1..end+1 for i in 1..end+1
if n % i == 0 if n % i == 0
out = out + 2 out += 2
if end * end == n if end * end == n
out = out - 1 out -= 1
return out return out
func main[] : i64 func main[] : i64
let n = 0 n := 0
let i = 1 i := 1
while true while true
n = n + i n += i
if num_divisors(n) > 500 if num_divisors(n) > 500
io.println_i64(n) io.println_i64(n)
break break
i = i + 1 i += 1

View File

@@ -0,0 +1,5 @@
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_n(0, 10))

View File

@@ -1,21 +1,23 @@
include "$/io.zr"
func collatz_num[n: i64] : i64 func collatz_num[n: i64] : i64
if n % 2 == 0 if n % 2 == 0
return n / 2 return n / 2
return n * 3 + 1 return n * 3 + 1
func collatz_seq[n: i64]: i64 func collatz_seq[n: i64]: i64
let i = 1 i := 1
while n != 1 while n != 1
n = collatz_num(n) n = collatz_num(n)
i = i + 1 i += 1
return i return i
func main[] : i64 func main[] : i64
let max = 0 max := 0
let max_index = 0 max_index := 0
for i in 1..1000000 for i in 1..1000000
let seq: i64 = collatz_seq(i) seq := collatz_seq(i)
if seq > max if seq > max
max = seq max = seq
max_index = i max_index = i

View File

@@ -1,7 +1,9 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let n = 40 n := 40
let r = 20 r := 20
let out = 1 out := 1
for i in 1..r+1 for i in 1..r+1
out = out * (n - (r - i)) / i out = out * (n - (r - i)) / i

View File

@@ -0,0 +1,21 @@
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
n := [1]
for j in 0..1000
carry := 0
for i in 0..n->size
tmp : i64 = n->nth(i) * 2 + carry
n->set(i, tmp % 10)
carry = tmp / 10
while carry > 0
n->push(carry % 10)
carry = carry / 10
sum := 0
for i in 0..n->size
sum += n->nth(i)
io.println_i64(sum)

View File

@@ -0,0 +1,29 @@
include "$/io.zr"
include "$/containers.zr"
func main[] : i64
s1 := [0, 3, 3, 5, 4, 4, 3, 5, 5, 4]
s2 := [3, 6, 6, 8, 8, 7, 7, 9, 8, 8]
s3 := [0, 0, 6, 6, 5, 5, 5, 7, 6, 6]
sum := 0
for i in 1..10
sum += s1->nth(i)
for i in 0..10
sum += s2->nth(i)
for i in 20..100
sum += s3->nth(i / 10) + s1->nth(i % 10)
for i in 1..10
sum += s1->nth(i) + 7
for j in 1..10
sum += s1->nth(i) + 7 + 3 + s1->nth(j)
for j in 0..10
sum += s1->nth(i) + 7 + 3 + s2->nth(j)
for j in 20..100
sum += s1->nth(i) + 7 + 3 + s3->nth(j / 10) + s1->nth(j % 10)
sum += s1->nth(1) + 8
io.println_i64(sum)

View File

@@ -0,0 +1,31 @@
include "$/io.zr"
include "$/containers.zr"
func findmax[triangle: Array, row: i64, col: i64] : i64
if row == 14
return (triangle->nth(row) as Array)->nth(col)
left := findmax(triangle, row + 1, col)
right := findmax(triangle, row + 1, col + 1)
return (triangle->nth(row) as Array)->nth(col) + math.max(left, right)
func main[] : i64
triangle := []
triangle->push([75])
triangle->push([95, 64])
triangle->push([17, 47, 82])
triangle->push([18, 35, 87, 10])
triangle->push([20, 4, 82, 47, 65])
triangle->push([19, 1, 23, 75, 3, 34])
triangle->push([88, 2, 77, 73, 7, 63, 67])
triangle->push([99, 65, 4, 28, 6, 16, 70, 92])
triangle->push([41, 41, 26, 56, 83, 40, 80, 70, 33])
triangle->push([41, 48, 72, 33, 47, 32, 37, 16, 94, 29])
triangle->push([53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14])
triangle->push([70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57])
triangle->push([91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48])
triangle->push([63, 66, 4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31])
triangle->push([4, 62, 98, 27, 23, 9, 70, 98, 73, 93, 38, 53, 60, 4, 23])
io.println_i64(findmax(triangle, 0, 0))

View File

@@ -1,3 +1,5 @@
include "$/io.zr"
func days[y: i64, m: i64] : i64 func days[y: i64, m: i64] : i64
if m == 2 if m == 2
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
@@ -10,13 +12,13 @@ func days[y: i64, m: i64] : i64
return 31 return 31
func main[] : i64 func main[] : i64
let wday = 0 wday := 0
let sun = 0 sun := 0
for year in 1901..2001 for year in 1901..2001
for mon in 1..13 for mon in 1..13
if wday == 5 if wday == 5
sun = sun + 1 sun += 1
wday = (wday + days(year, mon)) % 7 wday = (wday + days(year, mon)) % 7
io.println_i64(sun) io.println_i64(sun)

View File

@@ -1,21 +1,24 @@
include "$/io.zr"
include "$/containers.zr"
func multiply[n: Array, x: i64] : void func multiply[n: Array, x: i64] : void
let carry = 0 carry := 0
for i in 0..n->size for i in 0..n->size
let prod: i64 = array.nth(n, i) * x + carry prod : i64 = n->nth(i) * x + carry
array.set(n, i, prod % 10) n->set(i, prod % 10)
carry = prod / 10 carry = prod / 10
while carry > 0 while carry > 0
array.push(n, carry % 10) n->push(carry % 10)
carry = carry / 10 carry = carry / 10
func main[] : i64 func main[] : i64
let n: Array = [1] n := [1]
for i in 2..101 for i in 2..101
multiply(n, i) multiply(n, i)
let sum = 0 sum := 0
for i in 0..n->size for i in 0..n->size
sum = sum + array.nth(n, i) sum += n->nth(i)
io.println_i64(sum) io.println_i64(sum)

View File

@@ -1,9 +1,11 @@
include "$/io.zr"
func divisors_sum[n: i64] : i64 func divisors_sum[n: i64] : i64
let k: i64 = n k := n
let sum = 1 sum := 1
for i in 2..k+1 for i in 2..k+1
let p = 1 p := 1
while k % i == 0 while k % i == 0
p = p * i p = p * i
k = k / i k = k / i
@@ -11,11 +13,11 @@ func divisors_sum[n: i64] : i64
return sum - n return sum - n
func main[] : i64 func main[] : i64
let sum = 0 sum := 0
for i in 2..10000 for i in 2..10000
let d: i64 = divisors_sum(i) d := divisors_sum(i)
if i < d && i == divisors_sum(d) if i < d && i == divisors_sum(d)
sum = sum + i + d sum += i + d
io.println_i64(sum) io.println_i64(sum)

View File

@@ -1,9 +1,11 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
let a = 0 a := 0
let b = 1 b := 1
while a < 100000 while a < 100000
io.println_i64(a) io.println_i64(a)
let temp: i64 = b temp := b
b = a + b b += a
a = temp a = temp

View File

@@ -1,3 +1,5 @@
include "$/io.zr"
func main[] : i64 func main[] : i64
for i in 1..40 for i in 1..40
if i % 15 == 0 if i % 15 == 0

View File

@@ -1,11 +0,0 @@
func square[x: i64] : i64
return x * x
func sum[a: i64, b: i64] : i64
return a + b
func main[] : i64
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
|> alg.map(^square)
|> alg.reduce(^sum, 0)
|> io.println_i64()

View File

@@ -1,9 +1,12 @@
include "$/io.zr"
include "$/os.zr"
func main[] : i64 func main[] : i64
let answer: i64 = math.abs(os.urandom_i64()) % 100 answer := os.urandom_i64()->abs() % 100
while true while true
io.println("Guess a number: ") io.println("Guess a number: ")
let guess: i64 = io.read_line() |> str.trim() |> str.parse_i64() guess := io.read_line()->parse_i64()
if guess == answer if guess == answer
io.println("You win!") io.println("You win!")

View File

@@ -1,2 +0,0 @@
func main[] : i64
io.println("Hello, World!")

View File

@@ -1,35 +0,0 @@
func part1[l1: Array, l2: Array] : void
let out = 0
for i in 0..l1->size
out = out + math.abs(array.nth(l1, i) - array.nth(l2, i))
io.println_i64(out)
func part2[l1: Array, l2: Array] : void
let out = 0
for i in 0..l1->size
out = out + array.nth(l1, i) * alg.count(l2, array.nth(l1, i))
io.println_i64(out)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
let l1: Array = []
let l2: Array = []
for i in 0..lines->size
let line: str = array.nth(lines, i)
let parts: Array = str.split(line, " ")
array.push(l1, str.parse_i64(array.nth(parts, 0)))
array.push(l2, str.parse_i64(array.nth(parts, 1)))
alg.quicksort(l1)
alg.quicksort(l2)
part1(l1, l2)
part2(l1, l2)

View File

@@ -1,55 +0,0 @@
func check[report: Array] : bool
let increasing: bool = array.nth(report, 0) < array.nth(report, 1)
for i in 0..report->size-1
if array.nth(report, i) > array.nth(report, i + 1) && increasing
return false
if array.nth(report, i) < array.nth(report, i + 1) && !increasing
return false
let diff: i64 = math.abs(array.nth(report, i) - array.nth(report, i + 1))
if diff < 1 || diff > 3
return false
return true
func part1[data: Array] : void
let out = 0
for i in 0..data->size
if check(array.nth(data, i))
out = out + 1
io.println_i64(out)
func part2[data: Array] : void
let out = 0
for i in 0..data->size
if check(array.nth(data, i))
out = out + 1
else
let arr: Array = array.nth(data, i)
for j in 0..arr->size
let sliced: Array = array.concat(array.slice(arr, 0, j), array.slice(arr, j + 1, arr->size - (j + 1)))
if check(sliced)
out = out + 1
break
io.println_i64(out)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
let data: Array = []
for i in 0..lines->size
let line: str = array.nth(lines, i)
let report: Array = str.split(line, " ")
for i in 0..report->size
array.set(report, i, str.parse_i64(array.nth(report, i)))
array.push(data, report)
part1(data)
part2(data)

View File

@@ -1,62 +0,0 @@
func check[lines: Array, x: i64, y: i64, dx: i64, dy: i64] : bool
if x + dx * 3 < 0 || x + dx * 3 >= lines->size || y + dy * 3 < 0 || y + dy * 3 >= str.len(array.nth(lines, 0))
return false
if array.nth(lines, x)[y] != 'X'
return false
if array.nth(lines, x + dx)[y + dy] != 'M'
return false
if array.nth(lines, x + dx * 2)[y + dy * 2] != 'A'
return false
if array.nth(lines, x + dx * 3)[y + dy * 3] != 'S'
return false
return true
func part1[lines: Array] : void
let out = 0
for x in 0..lines->size
for y in 0..str.len(array.nth(lines, x))
if check(lines, x, y, 0, 1)
out = out + 1
if check(lines, x, y, 0, -1)
out = out + 1
if check(lines, x, y, 1, 0)
out = out + 1
if check(lines, x, y, -1, 0)
out = out + 1
if check(lines, x, y, 1, 1)
out = out + 1
if check(lines, x, y, -1, -1)
out = out + 1
if check(lines, x, y, 1, -1)
out = out + 1
if check(lines, x, y, -1, 1)
out = out + 1
io.println_i64(out)
func part2[lines: Array] : void
let out = 0
for x in 1..lines->size-1
for y in 1..str.len(array.nth(lines, x))-1
if array.nth(lines, x)[y] == 'A'
let s: str = mem.alloc(5)
s[0] = array.nth(lines, x - 1)[y - 1]
s[1] = array.nth(lines, x + 1)[y - 1]
s[2] = array.nth(lines, x + 1)[y + 1]
s[3] = array.nth(lines, x - 1)[y + 1]
s[4] = 0
if str.equal(s, "MSSM") || str.equal(s, "SMMS") || str.equal(s, "MMSS") || str.equal(s, "SSMM")
out = out + 1
io.println_i64(out)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
part1(lines)
part2(lines)

View File

@@ -1,73 +0,0 @@
func rule_exists[rules_left: Array, rules_right: Array, a: i64, b: i64] : bool
for k in 0..rules_left->size
if array.nth(rules_left, k) == a && array.nth(rules_right, k) == b
return true
return false
func check[update: Array, rules_left: Array, rules_right: Array] : bool
for i in 0..update->size
for j in 0..i
if rule_exists(rules_left, rules_right, array.nth(update, i), array.nth(update, j))
return false
return true
func sort_by_rules[update: Array, rules_left: Array, rules_right: Array] : void
let swapped: bool = true
while swapped
swapped = false
for i in 0..update->size-1
let a: i64 = array.nth(update, i)
let b: i64 = array.nth(update, i+1)
if rule_exists(rules_left, rules_right, b, a)
let tmp: i64 = a
array.set(update, i, b)
array.set(update, i+1, tmp)
swapped = true
func part1[updates: Array, rules_left: Array, rules_right: Array] : void
let out = 0
for i in 0..updates->size
let update: Array = array.nth(updates, i)
if check(update, rules_left, rules_right)
out = out + array.nth(update, update->size / 2)
io.println_i64(out)
func part2[updates: Array, rules_left: Array, rules_right: Array] : void
let out = 0
for i in 0..updates->size
let update: Array = array.nth(updates, i)
if !check(update, rules_left, rules_right)
sort_by_rules(update, rules_left, rules_right)
out = out + array.nth(update, update->size / 2)
io.println_i64(out)
func main[] : i64
let data: Array = io.read_file("input.txt") |> str.split("\n\n")
let rules_left: Array = []
let rules_right: Array = []
let rules_lines: Array = str.split(array.nth(data, 0), "\n")
for i in 0..rules_lines->size
let line: str = array.nth(rules_lines, i)
let parts: Array = str.split(line, "|")
array.push(rules_left, str.parse_i64(array.nth(parts, 0)))
array.push(rules_right, str.parse_i64(array.nth(parts, 1)))
let updates: Array = []
let updates_lines: Array = str.split(array.nth(data, 1), "\n")
for i in 0..updates_lines->size
let line: str = array.nth(updates_lines, i)
let xs: Array = str.split(line, ",")
for i in 0..xs->size
array.set(xs, i, str.parse_i64(array.nth(xs, i)))
array.push(updates, xs)
part1(updates, rules_left, rules_right)
part2(updates, rules_left, rules_right)

View File

@@ -1,78 +0,0 @@
func concat[a: i64, b: i64] : i64
let a_str: str = str.from_i64(a)
let b_str: str = str.from_i64(b)
let ab_str: str = str.concat(a_str, b_str)
let out: i64 = str.parse_i64(ab_str)
// without freeing the program works but leaks 2GB of memory :p
mem.free(a_str)
mem.free(b_str)
mem.free(ab_str)
return out
func solve[ops: Array, e: Array] : i64
let e_1: Array = array.nth(e, 1)
let n: i64 = e_1->size - 1
let indices: Array = []
for i in 0..n
array.push(indices, 0)
while true
let res: i64 = array.nth(e_1, 0)
for i in 0..n
let op: str = array.nth(ops, array.nth(indices, i))
if str.equal(op, "add")
res = res + array.nth(e_1, i + 1)
else if str.equal(op, "mul")
res = res * array.nth(e_1, i + 1)
else if str.equal(op, "concat")
res = concat(res, array.nth(e_1, i + 1))
if res == array.nth(e, 0)
return res
let done: bool = true
let i: i64 = n - 1
while i >= 0
array.set(indices, i, array.nth(indices, i) + 1)
if array.nth(indices, i) < ops->size
done = false
break
array.set(indices, i, 0)
i = i - 1
if done
return 0
func part1[equations: Array] : void
let out = 0
for i in 0..equations->size
out = out + solve(["add", "mul"], array.nth(equations, i))
io.println_i64(out)
func part2[equations: Array] : void
let out = 0
for i in 0..equations->size
out = out + solve(["add", "mul", "concat"], array.nth(equations, i))
io.println_i64(out)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
let equations: Array = []
for i in 0..lines->size
let line: str = array.nth(lines, i)
let parts: Array = str.split(line, ": ")
let xs: Array = str.split(array.nth(parts, 1), " ")
for j in 0..xs->size
array.set(xs, j, str.parse_i64(array.nth(xs, j)))
array.push(equations, [str.parse_i64(array.nth(parts, 0)), xs])
part1(equations)
part2(equations)

View File

@@ -1,53 +0,0 @@
func part1[lines: Array] : void
let password = 0
let dial = 50
for i in 0..lines->size
let line: str = array.nth(lines, i)
let dir: u8 = line[0]
let n: i64 = str.substr(line, 1, str.len(line) - 1) |> str.parse_i64()
if dir == 'L'
dial = dial - n
while dial < 0
dial = 100 + dial
else
dial = dial + n
while dial >= 100
dial = dial - 100
if dial == 0
password = password + 1
io.println_i64(password)
func part2[lines: Array] : void
let password = 0
let dial = 50
for i in 0..lines->size
let line: str = array.nth(lines, i)
let dir: u8 = line[0]
let n: i64 = str.substr(line, 1, str.len(line) - 1) |> str.parse_i64()
if dir == 'L'
for i in 0..n
dial = dial - 1
if dial == 0
password = password + 1
if dial == -1
dial = 99
else
for i in 0..n
dial = dial + 1
if dial == 100
dial = 0
password = password + 1
io.println_i64(password)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
part1(lines)
part2(lines)

View File

@@ -1,60 +0,0 @@
func part1_is_invalid_id[s: str] : bool
let len: i64 = str.len(s)
if len % 2 != 0
return false
let first: str = str.substr(s, 0, len / 2)
let second: str = str.substr(s, len / 2, len / 2)
return str.equal(first, second)
func part1[ranges: Array] : void
let sum = 0
for i in 0..ranges->size
let parts: Array = array.nth(ranges, i) |> str.split("-")
let start: i64 = array.nth(parts, 0) |> str.parse_i64()
let end: i64 = array.nth(parts, 1) |> str.parse_i64()
for n in (start)..end+1
if part1_is_invalid_id(str.from_i64(n))
sum = sum + n
io.println_i64(sum)
// had to cheat this one
func part2_is_invalid_id[s: str] : bool
let len: i64 = str.len(s)
if len < 2
return false
for div in 1..(len / 2 + 1)
if len % div == 0
let u: str = str.substr(s, 0, div)
let is_repeat: bool = true
for k in 1..(len / div)
let segment: str = str.substr(s, k * div, div)
if !str.equal(segment, u)
is_repeat = false
if is_repeat
return true
return false
func part2[ranges: Array] : void
let sum = 0
for i in 0..ranges->size
let parts: Array = array.nth(ranges, i) |> str.split("-")
let start: i64 = array.nth(parts, 0) |> str.parse_i64()
let end: i64 = array.nth(parts, 1) |> str.parse_i64()
for n in (start)..end+1
if part2_is_invalid_id(str.from_i64(n))
sum = sum + n
io.println_i64(sum)
func main[] : i64
let ranges: Array = io.read_file("input.txt") |> str.split(",")
part1(ranges)
part2(ranges)

View File

@@ -1,51 +0,0 @@
func part1[lines: Array] : void
let sum = 0
for i in 0..lines->size
let line: str = array.nth(lines, i)
let largest = 0
for j in 0..str.len(line)
for k in (j+1)..str.len(line)
let s: str = mem.alloc(3)
s[0] = line[j]
s[1] = line[k]
s[2] = 0
let n: i64 = str.parse_i64(s)
if n > largest
largest = n
sum = sum + largest
io.println_i64(sum)
// had to cheat this one
func part2_rec[bank: str, start: i64, remaining: i64] : i64
let largest = 0
let largest_idx: i64 = start
let len: i64 = str.len(bank)
for i in (start)..(len-remaining+1)
let v: i64 = bank[i] - '0'
if v > largest
largest = v
largest_idx = i
if remaining > 1
return largest * math.pow(10, remaining - 1) + part2_rec(bank, largest_idx + 1, remaining - 1)
else
return largest
func part2[lines: Array] : void
let sum = 0
for i in 0..lines->size
let line: str = array.nth(lines, i)
sum = sum + part2_rec(line, 0, 12)
io.println_i64(sum)
func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n")
part1(lines)
part2(lines)

View File

@@ -1,13 +0,0 @@
func main[] : i64
let sum = 0
let a = 0
let b = 1
while a < 4000000
if a % 2 == 0
sum = sum + a
let temp: i64 = b
b = a + b
a = temp
io.println_i64(sum)

View File

@@ -1,13 +0,0 @@
func main[] : i64
let out = 0
for a in 500..1000
for b in 500..1000
if a * b > out
let s: str = str.from_i64(a * b)
let s_rev: str = str.reverse(s)
if str.equal(s, s_rev)
out = a * b
mem.free(s)
mem.free(s_rev)
io.println_i64(out)

View File

@@ -1,14 +0,0 @@
func main[] : i64
let n: str = "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"
let out = 0
let max: i64 = str.len(n) - 13
for i in 0..max
let s = 1
let j = 0
while j < 13
s = s * (n[i + j] - '0')
j = j + 1
if s > out
out = s
io.println_i64(out)

View File

@@ -1,7 +0,0 @@
func main[] : i64
let sum = 0
for i in 0..2000000
if math.is_prime(i)
sum = sum + i
io.println_i64(sum)

View File

@@ -1,36 +0,0 @@
func main[] : i64
let N = 20
let grid: Array = []
array.push(grid, [8, 2, 22, 97, 38, 15, 0, 40, 0, 75, 4, 5, 7, 78, 52, 12, 50, 77, 91, 8])
array.push(grid, [49, 49, 99, 40, 17, 81, 18, 57, 60, 87, 17, 40, 98, 43, 69, 48, 4, 56, 62, 0])
array.push(grid, [81, 49, 31, 73, 55, 79, 14, 29, 93, 71, 40, 67, 53, 88, 30, 3, 49, 13, 36, 65])
array.push(grid, [52, 70, 95, 23, 4, 60, 11, 42, 69, 24, 68, 56, 1, 32, 56, 71, 37, 2, 36, 91])
array.push(grid, [22, 31, 16, 71, 51, 67, 63, 89, 41, 92, 36, 54, 22, 40, 40, 28, 66, 33, 13, 80])
array.push(grid, [24, 47, 32, 60, 99, 3, 45, 2, 44, 75, 33, 53, 78, 36, 84, 20, 35, 17, 12, 50])
array.push(grid, [32, 98, 81, 28, 64, 23, 67, 10, 26, 38, 40, 67, 59, 54, 70, 66, 18, 38, 64, 70])
array.push(grid, [67, 26, 20, 68, 2, 62, 12, 20, 95, 63, 94, 39, 63, 8, 40, 91, 66, 49, 94, 21])
array.push(grid, [24, 55, 58, 5, 66, 73, 99, 26, 97, 17, 78, 78, 96, 83, 14, 88, 34, 89, 63, 72])
array.push(grid, [21, 36, 23, 9, 75, 0, 76, 44, 20, 45, 35, 14, 0, 61, 33, 97, 34, 31, 33, 95])
array.push(grid, [78, 17, 53, 28, 22, 75, 31, 67, 15, 94, 3, 80, 4, 62, 16, 14, 9, 53, 56, 92])
array.push(grid, [16, 39, 5, 42, 96, 35, 31, 47, 55, 58, 88, 24, 0, 17, 54, 24, 36, 29, 85, 57])
array.push(grid, [86, 56, 0, 48, 35, 71, 89, 7, 5, 44, 44, 37, 44, 60, 21, 58, 51, 54, 17, 58])
array.push(grid, [19, 80, 81, 68, 5, 94, 47, 69, 28, 73, 92, 13, 86, 52, 17, 77, 4, 89, 55, 40])
array.push(grid, [4, 52, 8, 83, 97, 35, 99, 16, 7, 97, 57, 32, 16, 26, 26, 79, 33, 27, 98, 66])
array.push(grid, [88, 36, 68, 87, 57, 62, 20, 72, 3, 46, 33, 67, 46, 55, 12, 32, 63, 93, 53, 69])
array.push(grid, [4, 42, 16, 73, 38, 25, 39, 11, 24, 94, 72, 18, 8, 46, 29, 32, 40, 62, 76, 36])
array.push(grid, [20, 69, 36, 41, 72, 30, 23, 88, 34, 62, 99, 69, 82, 67, 59, 85, 74, 4, 36, 16])
array.push(grid, [20, 73, 35, 29, 78, 31, 90, 1, 74, 31, 49, 71, 48, 86, 81, 16, 23, 57, 5, 54])
array.push(grid, [1, 70, 54, 71, 83, 51, 54, 69, 16, 92, 33, 48, 61, 43, 52, 1, 89, 19, 67, 48])
let out = 0
for i in 0..N-3
for j in 0..N-3
let h: i64 = array.nth(array.nth(grid, i), j) * array.nth(array.nth(grid, i), j+1) * array.nth(array.nth(grid, i), j+2) * array.nth(array.nth(grid, i), j+3)
let v: i64 = array.nth(array.nth(grid, j), i) * array.nth(array.nth(grid, j+1), i) * array.nth(array.nth(grid, j+2), i) * array.nth(array.nth(grid, j+3), i)
let d1: i64 = array.nth(array.nth(grid, i), j) * array.nth(array.nth(grid, i+1), j+1) * array.nth(array.nth(grid, i+2), j+2) * array.nth(array.nth(grid, i+3), j+3)
let d2: i64 = array.nth(array.nth(grid, i), N-j-1) * array.nth(array.nth(grid, i+1), N-j-2) * array.nth(array.nth(grid, i+2), N-j-3) * array.nth(array.nth(grid, i+3), N-j-4)
out = math.max(out, math.max(h, math.max(v, math.max(d1, d2))))
io.println_i64(out)

View File

@@ -1,6 +0,0 @@
func main[] : i64
// leaks a bit of memory but looks very cool
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
|> str.from_i64()
|> str.substr(0, 10)
|> io.println()

View File

@@ -1,19 +0,0 @@
func main[] : i64
let n: Array = []
array.push(n, 1)
for j in 0..1000
let carry = 0
for i in 0..n->size
let tmp: i64 = array.nth(n, i) * 2 + carry
array.set(n, i, tmp % 10)
carry = tmp / 10
while carry > 0
array.push(n, carry % 10)
carry = carry / 10
let sum = 0
for i in 0..n->size
sum = sum + array.nth(n, i)
io.println_i64(sum)

View File

@@ -1,26 +0,0 @@
func main[] : i64
let s1: Array = [0, 3, 3, 5, 4, 4, 3, 5, 5, 4]
let s2: Array = [3, 6, 6, 8, 8, 7, 7, 9, 8, 8]
let s3: Array = [0, 0, 6, 6, 5, 5, 5, 7, 6, 6]
let sum = 0
for i in 1..10
sum = sum + array.nth(s1, i)
for i in 0..10
sum = sum + array.nth(s2, i)
for i in 20..100
sum = sum + array.nth(s3, i / 10) + array.nth(s1, i % 10)
for i in 1..10
sum = sum + array.nth(s1, i) + 7
for j in 1..10
sum = sum + array.nth(s1, i) + 7 + 3 + array.nth(s1, j)
for j in 0..10
sum = sum + array.nth(s1, i) + 7 + 3 + array.nth(s2, j)
for j in 20..100
sum = sum + array.nth(s1, i) + 7 + 3 + array.nth(s3, j / 10) + array.nth(s1, j % 10)
sum = sum + array.nth(s1, 1) + 8
io.println_i64(sum)

View File

@@ -1,28 +0,0 @@
func findmax[triangle: Array, row: i64, col: i64] : i64
if row == 14
return array.nth(array.nth(triangle, row), col)
let left: i64 = findmax(triangle, row + 1, col)
let right: i64 = findmax(triangle, row + 1, col + 1)
return array.nth(array.nth(triangle, row), col) + math.max(left, right)
func main[] : i64
let triangle: Array = []
array.push(triangle, [75])
array.push(triangle, [95, 64])
array.push(triangle, [17, 47, 82])
array.push(triangle, [18, 35, 87, 10])
array.push(triangle, [20, 4, 82, 47, 65])
array.push(triangle, [19, 1, 23, 75, 3, 34])
array.push(triangle, [88, 2, 77, 73, 7, 63, 67])
array.push(triangle, [99, 65, 4, 28, 6, 16, 70, 92])
array.push(triangle, [41, 41, 26, 56, 83, 40, 80, 70, 33])
array.push(triangle, [41, 48, 72, 33, 47, 32, 37, 16, 94, 29])
array.push(triangle, [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14])
array.push(triangle, [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57])
array.push(triangle, [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48])
array.push(triangle, [63, 66, 4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31])
array.push(triangle, [4, 62, 98, 27, 23, 9, 70, 98, 73, 93, 38, 53, 60, 4, 23])
io.println_i64(findmax(triangle, 0, 0))

View File

@@ -1,15 +0,0 @@
func main[] : i64
let arr: Array = []
for i in 0..10
array.push(arr, math.abs(os.urandom_i64()) % 1000)
for i in 0..arr->size
io.println_i64(array.nth(arr, i))
io.println("------------")
alg.quicksort(arr)
for i in 0..arr->size
io.println_i64(array.nth(arr, i))
array.free(arr)

View File

@@ -1,4 +1,6 @@
// needs to be compiled with -m -C="-lraylib" // needs to be compiled with -m -C "-lraylib"
include "$/io.zr"
extern InitWindow extern InitWindow
extern SetTargetFPS extern SetTargetFPS
extern WindowShouldClose extern WindowShouldClose
@@ -18,21 +20,21 @@ const WHITE = 0xffffffff
const RED = 0xff0000ff const RED = 0xff0000ff
func main[] : i64 func main[] : i64
let x = 200 x := 200
let y = 200 y := 200
InitWindow(800, 600, "Hello, World!") InitWindow(800, 600, "Hello, World!")
SetTargetFPS(60) SetTargetFPS(60)
while !WindowShouldClose() while !WindowShouldClose()
if IsKeyDown(KEY_W) & 255 if IsKeyDown(KEY_W) & 255
y = y - 10 y -= 10
if IsKeyDown(KEY_S) & 255 if IsKeyDown(KEY_S) & 255
y = y + 10 y += 10
if IsKeyDown(KEY_A) & 255 if IsKeyDown(KEY_A) & 255
x = x - 10 x -= 10
if IsKeyDown(KEY_D) & 255 if IsKeyDown(KEY_D) & 255
x = x + 10 x += 10
BeginDrawing() BeginDrawing()
ClearBackground(WHITE) ClearBackground(WHITE)

View File

@@ -1,38 +1,42 @@
func rule110_step[state: Array] : Array include "$/io.zr"
let new_state: Array = [] include "$/containers.zr"
func rule110_step[state: Array] : void
new_state := []
for i in 0..state->size for i in 0..state->size
let left: bool = false left := false
if i - 1 >= 0 if i - 1 >= 0
left = array.nth(state, i - 1) left = state->nth(i - 1)
let center: bool = array.nth(state, i) center : bool = state->nth(i)
let right: bool = false right := false
if i + 1 < state->size if i + 1 < state->size
right = array.nth(state, i + 1) right = state->nth(i + 1)
array.push(new_state, !((!left && !center && !right) || (left && !center && !right) || (left && center && right))) new_state->push(!((!left && !center && !right) || (left && !center && !right) || (left && center && right)))
return new_state mem.copy(new_state->data, state->data, state->size * 8)
new_state->free()
func print_state[state: Array]: void func print_state[state: Array]: void
for i in 0..state->size for i in 0..state->size
if array.nth(state, i) if state->nth(i)
io.print_char('#') io.print_char('#')
else else
io.print_char(' ') io.print_char(' ')
io.println("") io.println("")
func main[] : i64 func main[] : i64
let SIZE = 60 SIZE := 60
let state: Array = [] state := []
for i in 0..SIZE for i in 0..SIZE
array.push(state, false) state->push(false)
array.push(state, true) state->push(true)
print_state(state) print_state(state)
for i in 0..SIZE for i in 0..SIZE
state = rule110_step(state) rule110_step(state)
print_state(state) print_state(state)
array.free(state) state->free()

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,4 +1,6 @@
// needs to be compiled with -m -C="-lsqlite3" // needs to be compiled with -m -C "-lsqlite3"
include "$/io.zr"
extern sqlite3_open extern sqlite3_open
extern sqlite3_exec extern sqlite3_exec
extern sqlite3_prepare_v2 extern sqlite3_prepare_v2
@@ -10,10 +12,13 @@ extern sqlite3_column_int
extern sqlite3_column_text extern sqlite3_column_text
extern sqlite3_finalize extern sqlite3_finalize
const SQLITE_ROW = 100
const SQLITE_DONE = 101
func main[] : i64 func main[] : i64
let rc = 0 rc := 0
let db = 0 db := 0
let stmt = 0 stmt := 0
rc = sqlite3_open("todo.db", ^db) rc = sqlite3_open("todo.db", ^db)
if rc if rc
@@ -30,7 +35,7 @@ func main[] : i64
io.println("0. Quit") io.println("0. Quit")
io.print("\n> ") io.print("\n> ")
let choice: i64 = io.read_line() |> str.parse_i64() choice := io.read_line()->parse_i64()
if choice == 0 if choice == 0
break break
@@ -38,32 +43,29 @@ func main[] : i64
io.println("============") io.println("============")
sqlite3_prepare_v2(db, "SELECT * FROM todo", -1, ^stmt, 0) sqlite3_prepare_v2(db, "SELECT * FROM todo", -1, ^stmt, 0)
while sqlite3_step(stmt) == 100 while sqlite3_step(stmt) == SQLITE_ROW
let id: i64 = sqlite3_column_int(stmt, 0) id := sqlite3_column_int(stmt, 0) as i64
let task: str = sqlite3_column_text(stmt, 1) task := sqlite3_column_text(stmt, 1) as str
io.printf("%d - %s\n", id, task)
io.print_i64(id)
io.print(" - ")
io.println(task)
io.println("============") io.println("============")
else if choice == 2 else if choice == 2
io.print("\nEnter new task: ") io.print("\nEnter new task: ")
let task: str = io.read_line() |> str.trim() task := io.read_line()->trim()
sqlite3_prepare_v2(db, "INSERT INTO todo(task) VALUES (?);", -1, ^stmt, 0) sqlite3_prepare_v2(db, "INSERT INTO todo(task) VALUES (?);", -1, ^stmt, 0)
sqlite3_bind_text(stmt, 1, task, -1, 0) sqlite3_bind_text(stmt, 1, task, -1, 0)
if sqlite3_step(stmt) != 101 if sqlite3_step(stmt) != SQLITE_DONE
panic(sqlite3_errmsg(db)) panic(sqlite3_errmsg(db))
io.println("\nTask added\n") io.println("\nTask added\n")
else if choice == 3 else if choice == 3
io.print("\nEnter task id: ") io.print("\nEnter task id: ")
let id: i64 = io.read_line() |> str.parse_i64() id := io.read_line()->parse_i64()
sqlite3_prepare_v2(db, "DELETE FROM todo WHERE id = ?;", -1, ^stmt, 0) sqlite3_prepare_v2(db, "DELETE FROM todo WHERE id = ?;", -1, ^stmt, 0)
sqlite3_bind_int(stmt, 1, id, -1, 0) sqlite3_bind_int(stmt, 1, id, -1, 0)
if sqlite3_step(stmt) != 101 if sqlite3_step(stmt) != SQLITE_DONE
panic(sqlite3_errmsg(db)) panic(sqlite3_errmsg(db))
io.println("\nTask deleted\n") io.println("\nTask deleted\n")

View File

@@ -1,13 +0,0 @@
func main[] : i64
let host: i64 = net.pack_addr(127, 0, 0, 1)
let s: i64 = net.listen(host, 8000)
let resp: str = mem.alloc(60000)
while true
let conn: i64 = net.accept(s)
if conn < 0
continue
let n: i64 = net.read(conn, resp, 60000)
net.send(conn, resp, n)
net.close(conn)

22
examples/udp_server.zr Normal file
View File

@@ -0,0 +1,22 @@
include "$/io.zr"
include "$/net.zr"
func main[] : i64
s, ok := net.create_udp_server("127.0.0.1", 8000)
if !ok
panic("net.create_udp_server failed")
io.println("Listening on port 8000...")
while true
pkt := s->receive(IP_MAXPACKET)
if pkt->size <= 0
pkt->free()
continue
io.printf("%d.%d.%d.%d:%d\n", pkt->source_addr[4], pkt->source_addr[5], pkt->source_addr[6], pkt->source_addr[7], mem.read16be(pkt->source_addr + 2))
io.print_sized(pkt->data, pkt->size)
io.println("")
s->send_to(pkt->source_addr, pkt->data, pkt->size)
pkt->free()

View File

@@ -1,212 +0,0 @@
use std::collections::HashMap;
use crate::{
parser::{Expr, Stmt},
tokenizer::{ZernError, error},
};
pub struct Analyzer {
pub functions: HashMap<String, i32>,
pub constants: HashMap<String, u64>,
pub structs: HashMap<String, HashMap<String, usize>>,
}
impl Analyzer {
pub fn new() -> Analyzer {
Analyzer {
functions: HashMap::new(),
constants: HashMap::new(),
structs: HashMap::new(),
}
}
pub fn register_function(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
if let Stmt::Function {
name,
params,
return_type: _,
body: _,
exported: _,
} = stmt
{
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions
.insert(name.lexeme.clone(), params.len() as i32);
}
Ok(())
}
pub fn analyze_stmt(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Expression(expr) => self.analyze_expr(expr)?,
Stmt::Let {
name: _,
var_type: _,
initializer,
} => {
self.analyze_expr(initializer)?;
}
Stmt::Const { name, value } => {
if self.constants.contains_key(&name.lexeme)
|| self.functions.contains_key(&name.lexeme)
{
return error!(
name.loc,
format!("tried to redefine constant '{}'", name.lexeme)
);
}
if value.lexeme.starts_with("0x") {
self.constants.insert(
name.lexeme.clone(),
u64::from_str_radix(&value.lexeme[2..], 16).unwrap(),
);
} else {
self.constants
.insert(name.lexeme.clone(), value.lexeme.parse().unwrap());
}
}
Stmt::Block(statements) => {
for stmt in statements {
self.analyze_stmt(stmt)?;
}
}
Stmt::If {
condition,
then_branch,
else_branch,
} => {
self.analyze_expr(condition)?;
self.analyze_stmt(then_branch)?;
self.analyze_stmt(else_branch)?;
}
Stmt::While { condition, body } => {
self.analyze_expr(condition)?;
self.analyze_stmt(body)?;
}
Stmt::Function {
name,
params: _,
return_type,
body,
exported: _,
} => {
if name.lexeme == "main" && return_type.lexeme != "i64" {
return error!(&name.loc, "main must return i64");
}
self.analyze_stmt(body)?;
}
Stmt::Return(expr) => {
self.analyze_expr(expr)?;
}
Stmt::For {
var: _,
start,
end,
body,
} => {
self.analyze_expr(start)?;
self.analyze_expr(end)?;
self.analyze_stmt(body)?;
}
Stmt::Break => {}
Stmt::Continue => {}
Stmt::Extern(name) => {
if self.functions.contains_key(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions.insert(name.lexeme.clone(), -1);
}
Stmt::Struct { name, fields } => {
let mut fields_map: HashMap<String, usize> = HashMap::new();
let mut offset: usize = 0;
for field in fields {
fields_map.insert(field.var_name.lexeme.clone(), offset);
offset += 8;
}
self.structs.insert(name.lexeme.clone(), fields_map);
}
}
Ok(())
}
pub fn analyze_expr(&mut self, expr: &Expr) -> Result<(), ZernError> {
match expr {
Expr::Binary { left, op: _, right } => {
self.analyze_expr(left)?;
self.analyze_expr(right)?;
}
Expr::Logical { left, op: _, right } => {
self.analyze_expr(left)?;
self.analyze_expr(right)?;
}
Expr::Grouping(expr) => self.analyze_expr(expr)?,
Expr::Literal(_) => {}
Expr::Unary { op: _, right } => {
self.analyze_expr(right)?;
}
Expr::Variable(_) => {}
Expr::Assign { left, op: _, value } => {
self.analyze_expr(left)?;
self.analyze_expr(value)?;
}
Expr::Call {
callee,
paren,
args,
} => {
if let Expr::Variable(callee_name) = *callee.clone() {
if callee_name.lexeme.starts_with("_builtin_")
|| self.functions.contains_key(&callee_name.lexeme)
{
// its a function (defined/builtin/extern)
if let Some(arity) = self.functions.get(&callee_name.lexeme) {
if *arity >= 0 && *arity != args.len() as i32 {
return error!(
&paren.loc,
format!("expected {} arguments, got {}", arity, args.len())
);
}
} else if !callee_name.lexeme.starts_with("_builtin_") {
return error!(
&paren.loc,
format!("undefined function: {}", callee_name.lexeme)
);
}
} else {
// its a variable containing function address
self.analyze_expr(callee)?;
}
} else {
// its an expression that evalutes to function address
self.analyze_expr(callee)?;
}
for arg in args {
self.analyze_expr(arg)?;
}
}
Expr::ArrayLiteral(exprs) => {
for expr in exprs {
self.analyze_expr(expr)?;
}
}
Expr::Index { expr, index } => {
self.analyze_expr(expr)?;
self.analyze_expr(index)?;
}
Expr::AddrOf { op: _, expr } => {
self.analyze_expr(expr)?;
}
Expr::New(_) => {}
Expr::MemberAccess { left, field: _ } => {
self.analyze_expr(left)?;
}
}
Ok(())
}
}

View File

@@ -1,13 +1,14 @@
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write};
use crate::{ use crate::{
analyzer::Analyzer, parser::{Expr, ExprKind, Params, Stmt},
parser::{Expr, Stmt}, symbol_table::SymbolTable,
tokenizer::{Token, TokenType, ZernError, error}, tokenizer::{Token, TokenType, ZernError, error},
}; };
struct Var { struct Var {
pub stack_offset: usize, pub stack_offset: usize,
#[allow(unused)]
pub var_type: String, pub var_type: String,
} }
@@ -69,25 +70,27 @@ macro_rules! emit {
static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; static REGISTERS: [&str; 6] = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"];
// TODO: currently they are all just 64 bit values
static BUILTIN_TYPES: [&str; 6] = ["void", "u8", "i64", "str", "bool", "ptr"];
pub struct CodegenX86_64<'a> { pub struct CodegenX86_64<'a> {
output: String, output: String,
data_section: String, data_section: String,
label_counter: usize, label_counter: usize,
data_counter: usize, data_counter: usize,
pub analyzer: &'a mut Analyzer, pub symbol_table: &'a SymbolTable,
pub expr_types: &'a HashMap<usize, String>,
} }
impl<'a> CodegenX86_64<'a> { impl<'a> CodegenX86_64<'a> {
pub fn new(analyzer: &'a mut Analyzer) -> CodegenX86_64<'a> { pub fn new(
symbol_table: &'a SymbolTable,
expr_types: &'a HashMap<usize, String>,
) -> CodegenX86_64<'a> {
CodegenX86_64 { CodegenX86_64 {
output: String::new(), output: String::new(),
data_section: String::new(), data_section: String::new(),
label_counter: 0, label_counter: 0,
data_counter: 1, data_counter: 1,
analyzer, symbol_table,
expr_types,
} }
} }
@@ -97,70 +100,127 @@ impl<'a> CodegenX86_64<'a> {
} }
pub fn get_output(&self) -> String { pub fn get_output(&self) -> String {
format!("section .data\n{}{}", self.data_section, self.output) format!(".section .data\n{}{}", self.data_section, self.output)
} }
pub fn emit_prologue(&mut self) -> Result<(), ZernError> { pub fn emit_prologue(&mut self, use_crt: bool) -> Result<(), ZernError> {
emit!( emit!(
&mut self.output, &mut self.output,
"section .note.GNU-stack ".intel_syntax noprefix
db 0
section .bss .section .note.GNU-stack
_heap_head: resq 1 .byte 0
_heap_tail: resq 1
section .text._builtin_heap_head .section .bss
_heap_head: .zero 8
_heap_tail: .zero 8
_environ: .zero 8
.section .text._builtin_heap_head
_builtin_heap_head: _builtin_heap_head:
lea rax, [rel _heap_head] lea rax, [rip + _heap_head]
ret ret
section .text._builtin_heap_tail .section .text._builtin_heap_tail
_builtin_heap_tail: _builtin_heap_tail:
lea rax, [rel _heap_tail] lea rax, [rip + _heap_tail]
ret ret
section .text._builtin_read64 .section .text._builtin_read64
_builtin_read64: _builtin_read64:
mov rax, qword [rdi] mov rax, QWORD PTR [rdi]
ret ret
section .text._builtin_set64 .section .text._builtin_set64
_builtin_set64: _builtin_set64:
mov [rdi], rsi mov [rdi], rsi
ret ret
section .text._builtin_syscall .section .text._builtin_cvtsi2sd
_builtin_cvtsi2sd:
cvtsi2sd xmm0, rdi
movq rax, xmm0
ret
.section .text._builtin_cvttsd2si
_builtin_cvttsd2si:
cvttsd2si rax, xmm0
ret
.section .text._builtin_f64_to_f32
_builtin_f64_to_f32:
cvtsd2ss xmm0, xmm0
movd eax, xmm0
ret
.section .text._builtin_syscall
_builtin_syscall: _builtin_syscall:
mov rax, rdi mov rax, rdi
mov rdi, rsi mov rdi, rsi
mov rsi, rdx mov rsi, rdx
mov rdx, rcx mov rdx, rcx
mov r10, r8 mov r10, r8
mov r8, r9 mov r8, r9
mov r9, [rsp+8] mov r9, [rsp+8]
syscall syscall
ret ret
section .text._builtin_environ
_builtin_environ:
extern environ
mov rax, [rel environ]
ret
" "
); );
if !use_crt {
emit!(
&mut self.output,
"
.section .text._builtin_environ
_builtin_environ:
lea rax, [rip + _environ]
mov rax, [rax]
ret
.globl _start
.section .text
_start:
xor rbp, rbp
// setup args
pop rdi
mov rsi, rsp
// save environ
lea rdx, [rsi + rdi*8 + 8]
lea rax, [rip + _environ]
mov [rax], rdx
// align stack
and rsp, -16
// exit(main())
call main
mov rdi, rax
mov rax, 60
syscall
"
);
} else {
emit!(
&mut self.output,
"
.section .text._builtin_environ
_builtin_environ:
.extern environ
mov rax, [rip + environ]
ret
"
);
}
Ok(()) Ok(())
} }
pub fn compile_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> { pub fn compile_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> {
match stmt { match stmt {
Stmt::Expression(expr) => self.compile_expr(env, expr)?, Stmt::Expression(expr) => self.compile_expr(env, expr)?,
Stmt::Let { Stmt::Declare {
name, name,
var_type, var_type,
initializer, initializer,
} => { } => {
// TODO: move to analyzer // TODO: move to typechecker?
if env.get_var(&name.lexeme).is_some() { if env.get_var(&name.lexeme).is_some() {
return error!( return error!(
name.loc, name.loc,
@@ -170,28 +230,87 @@ _builtin_environ:
let var_type: String = match var_type { let var_type: String = match var_type {
Some(t) => t.lexeme.clone(), Some(t) => t.lexeme.clone(),
None => match &initializer { None => match self.expr_types[&initializer.id].as_str() {
Expr::Literal(token) => { "any" => return error!(name.loc, "cannot infer type from any"),
if token.token_type == TokenType::Number { t => t.into(),
"i64".into()
} else {
return error!(&name.loc, "unable to infer variable type");
}
}
_ => return error!(&name.loc, "unable to infer variable type"),
}, },
}; };
if !self.is_valid_type_name(&var_type) {
return error!(&name.loc, "unrecognized type: ".to_owned() + &var_type);
}
self.compile_expr(env, initializer)?; self.compile_expr(env, initializer)?;
let offset = env.define_var(name.lexeme.clone(), var_type); let offset = env.define_var(name.lexeme.clone(), var_type);
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); emit!(&mut self.output, " mov QWORD PTR [rbp-{}], rax", offset);
} }
Stmt::Const { name: _, value: _ } => { Stmt::Assign { left, op, value } => {
// handled in the analyzer self.compile_expr(env, value)?;
match &left.kind {
ExprKind::Variable(name) => {
// already ensured by the typechecker
let var = env.get_var(&name.lexeme).unwrap();
emit!(
&mut self.output,
" mov QWORD PTR [rbp-{}], rax",
var.stack_offset,
);
}
ExprKind::Index {
expr,
bracket: _,
index,
} => {
emit!(&mut self.output, " push rax");
self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax");
self.compile_expr(env, index)?;
emit!(&mut self.output, " pop rbx");
emit!(&mut self.output, " add rbx, rax");
emit!(&mut self.output, " pop rax");
emit!(&mut self.output, " mov BYTE PTR [rbx], al");
}
ExprKind::MemberAccess { left, field } => {
emit!(&mut self.output, " push rax");
let offset = self.get_field_offset(left, field)?;
self.compile_expr(env, left)?;
emit!(&mut self.output, " pop rbx");
emit!(&mut self.output, " mov QWORD PTR [rax+{}], rbx", offset);
}
_ => return error!(&op.loc, "invalid assignment target"),
};
}
Stmt::Destructure { targets, op, value } => {
self.compile_expr(env, value)?;
for (i, target) in targets.iter().enumerate() {
let reg = match i {
0 => "rax",
1 => "rdx",
_ => {
return error!(
&op.loc,
"destructuring more than 2 values not implemented yet"
);
}
};
let offset = match env.get_var(&target.lexeme) {
Some(var) => var.stack_offset,
None => {
let types: Vec<&str> = self.expr_types[&value.id].split(',').collect();
env.define_var(target.lexeme.clone(), types[i].to_string())
}
};
emit!(
&mut self.output,
" mov QWORD PTR [rbp-{}], {}",
offset,
reg
);
}
}
Stmt::Const { .. } => {
// handled in SymbolTable
} }
Stmt::Block(statements) => { Stmt::Block(statements) => {
env.push_scope(); env.push_scope();
@@ -201,6 +320,7 @@ _builtin_environ:
env.pop_scope(); env.pop_scope();
} }
Stmt::If { Stmt::If {
keyword: _,
condition, condition,
then_branch, then_branch,
else_branch, else_branch,
@@ -217,7 +337,11 @@ _builtin_environ:
self.compile_stmt(env, else_branch)?; self.compile_stmt(env, else_branch)?;
emit!(&mut self.output, "{}:", end_label); emit!(&mut self.output, "{}:", end_label);
} }
Stmt::While { condition, body } => { Stmt::While {
keyword: _,
condition,
body,
} => {
let old_loop_begin_label = env.loop_begin_label.clone(); let old_loop_begin_label = env.loop_begin_label.clone();
let old_loop_end_label = env.loop_end_label.clone(); let old_loop_end_label = env.loop_end_label.clone();
let old_loop_continue_label = env.loop_continue_label.clone(); let old_loop_continue_label = env.loop_continue_label.clone();
@@ -240,46 +364,57 @@ _builtin_environ:
Stmt::Function { Stmt::Function {
name, name,
params, params,
return_type, return_types: _,
body, body,
exported, exported,
} => { } => {
if *exported || name.lexeme == "main" { let name = &name.lexeme;
emit!(&mut self.output, "global {}", name.lexeme); if *exported || name == "main" {
emit!(&mut self.output, ".globl {0}", name);
} }
emit!(&mut self.output, "section .text.{}", name.lexeme); emit!(&mut self.output, ".type {0}, @function", name);
emit!(&mut self.output, "{}:", name.lexeme); emit!(&mut self.output, ".section .text.{}", name);
emit!(&mut self.output, "{}:", name);
emit!(&mut self.output, " push rbp"); emit!(&mut self.output, " push rbp");
emit!(&mut self.output, " mov rbp, rsp"); emit!(&mut self.output, " mov rbp, rsp");
emit!(&mut self.output, " sub rsp, 256"); // TODO
if !self.is_valid_type_name(&return_type.lexeme) { let prologue_offset = self.output.len();
return error!( emit!(&mut self.output, " sub rsp, {:<10}", 0);
&return_type.loc,
"unrecognized type: ".to_owned() + &return_type.lexeme
);
}
for (i, param) in params.iter().enumerate() { match params {
if !self.is_valid_type_name(&param.var_type.lexeme) { Params::Normal(params) => {
return error!( for (i, param) in params.iter().enumerate() {
&param.var_name.loc, let offset = env.define_var(
"unrecognized type: ".to_owned() + &param.var_type.lexeme param.var_name.lexeme.clone(),
); param.var_type.lexeme.clone(),
);
if let Some(reg) = REGISTERS.get(i) {
emit!(
&mut self.output,
" mov QWORD PTR [rbp-{}], {}",
offset,
reg
);
} else {
let stack_offset = 16 + 8 * (i - REGISTERS.len());
emit!(
&mut self.output,
" mov rax, QWORD PTR [rbp+{}]",
stack_offset
);
emit!(&mut self.output, " mov QWORD PTR [rbp-{}], rax", offset);
}
}
} }
Params::Variadic => {
let offset = env emit!(&mut self.output, " sub rsp, 48");
.define_var(param.var_name.lexeme.clone(), param.var_type.lexeme.clone()); emit!(&mut self.output, " mov [rbp - 8], rdi");
if let Some(reg) = REGISTERS.get(i) { emit!(&mut self.output, " mov [rbp - 16], rsi");
emit!(&mut self.output, " mov QWORD [rbp-{}], {}", offset, reg); emit!(&mut self.output, " mov [rbp - 24], rdx");
} else { emit!(&mut self.output, " mov [rbp - 32], rcx");
let stack_offset = 16 + 8 * (i - REGISTERS.len()); emit!(&mut self.output, " mov [rbp - 40], r8");
emit!( emit!(&mut self.output, " mov [rbp - 48], r9");
&mut self.output, env.next_offset += 48;
" mov rax, QWORD [rbp+{}]",
stack_offset
);
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset);
} }
} }
@@ -293,9 +428,28 @@ _builtin_environ:
emit!(&mut self.output, " pop rbp"); emit!(&mut self.output, " pop rbp");
emit!(&mut self.output, " ret"); emit!(&mut self.output, " ret");
} }
emit!(&mut self.output, ".size {0}, . - {0}", name);
// patch the stack size after we know how much we actually need
let patch = format!(" sub rsp, {:<10}", (env.next_offset + 15) & !15);
self.output
.replace_range(prologue_offset..prologue_offset + patch.len(), &patch);
} }
Stmt::Return(expr) => { Stmt::Return { keyword: _, exprs } => {
self.compile_expr(env, expr)?; match exprs.len() {
2 => {
self.compile_expr(env, &exprs[1])?;
emit!(&mut self.output, " push rax");
self.compile_expr(env, &exprs[0])?;
emit!(&mut self.output, " pop rdx");
}
1 => {
self.compile_expr(env, &exprs[0])?;
}
0 => {}
_ => todo!(),
}
emit!(&mut self.output, " mov rsp, rbp"); emit!(&mut self.output, " mov rsp, rbp");
emit!(&mut self.output, " pop rbp"); emit!(&mut self.output, " pop rbp");
emit!(&mut self.output, " ret"); emit!(&mut self.output, " ret");
@@ -317,19 +471,29 @@ _builtin_environ:
let offset = env.define_var(var.lexeme.clone(), "i64".into()); let offset = env.define_var(var.lexeme.clone(), "i64".into());
self.compile_expr(env, start)?; self.compile_expr(env, start)?;
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); emit!(&mut self.output, " mov QWORD PTR [rbp-{}], rax", offset);
emit!(&mut self.output, "{}:", env.loop_begin_label);
emit!(&mut self.output, " mov rax, QWORD [rbp-{}]", offset);
emit!(&mut self.output, " push rax");
self.compile_expr(env, end)?; self.compile_expr(env, end)?;
emit!(&mut self.output, " pop rcx"); let end_offset = env.next_offset;
emit!(&mut self.output, " cmp rcx, rax"); env.next_offset += 8;
emit!(
&mut self.output,
" mov QWORD PTR [rbp-{}], rax",
end_offset
);
emit!(&mut self.output, "{}:", env.loop_begin_label);
emit!(&mut self.output, " mov rax, QWORD PTR [rbp-{}]", offset);
emit!(
&mut self.output,
" mov rcx, QWORD PTR [rbp-{}]",
end_offset
);
emit!(&mut self.output, " cmp rax, rcx");
emit!(&mut self.output, " jge {}", env.loop_end_label); emit!(&mut self.output, " jge {}", env.loop_end_label);
self.compile_stmt(env, body)?; self.compile_stmt(env, body)?;
emit!(&mut self.output, "{}:", env.loop_continue_label); emit!(&mut self.output, "{}:", env.loop_continue_label);
emit!(&mut self.output, " mov rax, QWORD [rbp-{}]", offset); emit!(&mut self.output, " mov rax, QWORD PTR [rbp-{}]", offset);
emit!(&mut self.output, " add rax, 1"); emit!(&mut self.output, " add rax, 1");
emit!(&mut self.output, " mov QWORD [rbp-{}], rax", offset); emit!(&mut self.output, " mov QWORD PTR [rbp-{}], rax", offset);
emit!(&mut self.output, " jmp {}", env.loop_begin_label); emit!(&mut self.output, " jmp {}", env.loop_begin_label);
emit!(&mut self.output, "{}:", env.loop_end_label); emit!(&mut self.output, "{}:", env.loop_end_label);
env.pop_scope(); env.pop_scope();
@@ -345,18 +509,18 @@ _builtin_environ:
emit!(&mut self.output, " jmp {}", env.loop_continue_label); emit!(&mut self.output, " jmp {}", env.loop_continue_label);
} }
Stmt::Extern(name) => { Stmt::Extern(name) => {
emit!(&mut self.output, "extern {}", name.lexeme); emit!(&mut self.output, ".extern {}", name.lexeme);
} }
Stmt::Struct { name: _, fields: _ } => { Stmt::Struct { .. } => {
// handled in the analyzer // handled in SymbolTable
} }
} }
Ok(()) Ok(())
} }
pub fn compile_expr(&mut self, env: &mut Env, expr: &Expr) -> Result<(), ZernError> { pub fn compile_expr(&mut self, env: &mut Env, expr: &Expr) -> Result<(), ZernError> {
match expr { match &expr.kind {
Expr::Binary { left, op, right } => { ExprKind::Binary { left, op, right } => {
self.compile_expr(env, left)?; self.compile_expr(env, left)?;
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
self.compile_expr(env, right)?; self.compile_expr(env, right)?;
@@ -432,7 +596,7 @@ _builtin_environ:
_ => unreachable!(), _ => unreachable!(),
} }
} }
Expr::Logical { left, op, right } => { ExprKind::Logical { left, op, right } => {
let end_label = self.label(); let end_label = self.label();
match op.token_type { match op.token_type {
TokenType::LogicalAnd => { TokenType::LogicalAnd => {
@@ -451,50 +615,57 @@ _builtin_environ:
} }
emit!(&mut self.output, "{}:", end_label); emit!(&mut self.output, "{}:", end_label);
} }
Expr::Grouping(expr) => self.compile_expr(env, expr)?, ExprKind::Grouping(expr) => self.compile_expr(env, expr)?,
Expr::Literal(token) => match token.token_type { ExprKind::Literal(token) => match token.token_type {
TokenType::Number => { TokenType::IntLiteral => {
emit!(&mut self.output, " mov rax, {}", token.lexeme); emit!(&mut self.output, " mov rax, {}", token.lexeme);
} }
TokenType::Char => { TokenType::FloatLiteral => {
let value: f64 = token.lexeme.parse().unwrap();
emit!(&mut self.output, " mov rax, {}", value.to_bits());
}
TokenType::CharLiteral => {
emit!( emit!(
&mut self.output, &mut self.output,
" mov rax, {}", " mov rax, {}",
token.lexeme.chars().nth(1).unwrap() as u8 token.lexeme.chars().nth(1).unwrap() as u8
); );
} }
TokenType::String => { TokenType::StringLiteral => {
// TODO: actual string parsing in the tokenizer // TODO: actual string parsing in the tokenizer
let value = &token.lexeme[1..token.lexeme.len() - 1]; let value = &token.lexeme[1..token.lexeme.len() - 1];
let label = format!("str_{:03}", self.data_counter); let label = format!("str_{:03}", self.data_counter);
if value.is_empty() { let charcodes = value
emit!(&mut self.data_section, " {} db 0", label); .chars()
} else { .map(|x| (x as u8).to_string())
let charcodes = value .chain(std::iter::once("0".into()))
.chars() .collect::<Vec<_>>()
.map(|x| (x as u8).to_string()) .join(",");
.collect::<Vec<String>>() emit!(&mut self.data_section, " {}: .byte {}", label, charcodes);
.join(",");
emit!(&mut self.data_section, " {} db {},0", label, charcodes);
}
emit!(&mut self.output, " mov rax, {}", label);
self.data_counter += 1; self.data_counter += 1;
emit!(&mut self.output, " lea rax, [rip + {}]", label);
} }
TokenType::True => { TokenType::KeywordTrue => {
emit!(&mut self.output, " mov rax, 1"); emit!(&mut self.output, " mov rax, 1");
} }
TokenType::False => { TokenType::KeywordFalse => {
emit!(&mut self.output, " mov rax, 0"); emit!(&mut self.output, " mov rax, 0");
} }
_ => unreachable!(), _ => unreachable!(),
}, },
Expr::Unary { op, right } => { ExprKind::Unary { op, right } => {
self.compile_expr(env, right)?; self.compile_expr(env, right)?;
match op.token_type { match op.token_type {
TokenType::Minus => { TokenType::Minus => {
emit!(&mut self.output, " neg rax"); if self.expr_types[&right.id] == "f64" {
emit!(&mut self.output, " mov rbx, 0x8000000000000000");
emit!(&mut self.output, " xor rax, rbx");
} else {
emit!(&mut self.output, " neg rax");
}
} }
TokenType::Bang => { TokenType::Bang => {
emit!(&mut self.output, " test rax, rax"); emit!(&mut self.output, " test rax, rax");
@@ -504,115 +675,61 @@ _builtin_environ:
_ => unreachable!(), _ => unreachable!(),
} }
} }
Expr::Variable(name) => { ExprKind::Variable(name) => {
if self.analyzer.constants.contains_key(&name.lexeme) { if self.symbol_table.constants.contains_key(&name.lexeme) {
emit!( emit!(
&mut self.output, &mut self.output,
" mov rax, {}", " mov rax, {}",
self.analyzer.constants[&name.lexeme] self.symbol_table.constants[&name.lexeme]
); );
} else { } else {
// TODO: move to analyzer // already ensured by the typechecker
let var = match env.get_var(&name.lexeme) { let var = env.get_var(&name.lexeme).unwrap();
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
};
emit!( emit!(
&mut self.output, &mut self.output,
" mov rax, QWORD [rbp-{}]", " mov rax, QWORD PTR [rbp-{}]",
var.stack_offset, var.stack_offset,
); );
} }
} }
Expr::Assign { left, op, value } => { ExprKind::Call {
self.compile_expr(env, value)?;
match left.as_ref() {
Expr::Variable(name) => {
// TODO: move to analyzer
let var = match env.get_var(&name.lexeme) {
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
};
emit!(
&mut self.output,
" mov QWORD [rbp-{}], rax",
var.stack_offset,
);
}
Expr::Index { expr, index } => {
emit!(&mut self.output, " push rax");
self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax");
self.compile_expr(env, index)?;
emit!(&mut self.output, " pop rbx");
emit!(&mut self.output, " add rbx, rax");
emit!(&mut self.output, " pop rax");
emit!(&mut self.output, " mov BYTE [rbx], al");
}
Expr::MemberAccess { left, field } => {
emit!(&mut self.output, " push rax");
let offset = self.get_field_offset(env, left, field)?;
self.compile_expr(env, left)?;
emit!(&mut self.output, " pop rbx");
emit!(&mut self.output, " mov QWORD [rax+{}], rbx", offset);
}
_ => return error!(&op.loc, "invalid assignment target"),
};
}
Expr::Call {
callee, callee,
paren: _, paren: _,
args, args,
} => { } => {
if let ExprKind::Variable(callee_name) = &callee.kind
&& callee_name.lexeme == "_var_arg"
{
return self.emit_var_arg(env, &args[0]);
}
if let ExprKind::Variable(callee_name) = &callee.kind
&& callee_name.lexeme == "_stackalloc"
{
self.compile_expr(env, &args[0])?;
emit!(&mut self.output, " add rax, 15");
emit!(&mut self.output, " and rax, -16");
emit!(&mut self.output, " sub rsp, rax");
emit!(&mut self.output, " mov rax, rsp");
return Ok(());
}
for arg in args { for arg in args {
self.compile_expr(env, arg)?; self.compile_expr(env, arg)?;
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
} }
let arg_count = args.len(); let arg_types: Vec<String> = args
if arg_count <= 6 { .iter()
for i in (0..arg_count).rev() { .map(|a| self.expr_types[&a.id].clone())
emit!(&mut self.output, " pop {}", REGISTERS[i]); .collect();
} self.emit_call_setup(&arg_types);
} else {
for (i, reg) in REGISTERS.iter().enumerate() {
let offset = 8 * (arg_count - 1 - i);
emit!(
&mut self.output,
" mov {}, QWORD [rsp + {}]",
reg,
offset
);
}
let num_stack = arg_count - 6;
for i in 0..num_stack {
let arg_idx = arg_count - 1 - i;
let offset = 8 * (arg_count - 1 - arg_idx);
emit!(
&mut self.output,
" mov rax, QWORD [rsp + {}]",
offset + 8 * i
);
emit!(&mut self.output, " push rax");
}
}
if let Expr::Variable(callee_name) = &**callee { if let ExprKind::Variable(callee_name) = &callee.kind {
if callee_name.lexeme.starts_with("_builtin_") if self
|| self.analyzer.functions.contains_key(&callee_name.lexeme) .symbol_table
.functions
.contains_key(&callee_name.lexeme)
{ {
// its a function (defined/builtin/extern) // its a function (defined/builtin/extern)
emit!(&mut self.output, " call {}", callee_name.lexeme); emit!(&mut self.output, " call {}", callee_name.lexeme);
@@ -627,14 +744,16 @@ _builtin_environ:
emit!(&mut self.output, " call rax"); emit!(&mut self.output, " call rax");
} }
if arg_count > 6 { self.emit_call_cleanup(args.len());
let num_stack = arg_count - 6;
emit!(&mut self.output, " add rsp, {}", 8 * num_stack);
emit!(&mut self.output, " add rsp, {}", 8 * arg_count);
}
} }
Expr::ArrayLiteral(exprs) => { ExprKind::ArrayLiteral(exprs) => {
emit!(&mut self.output, " call array.new"); emit!(&mut self.output, " mov rdi, 24");
emit!(&mut self.output, " call mem.alloc");
emit!(&mut self.output, " push rax");
emit!(&mut self.output, " mov rdi, rax");
emit!(&mut self.output, " mov rsi, 24");
emit!(&mut self.output, " call mem.zero");
emit!(&mut self.output, " pop rax");
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
for expr in exprs { for expr in exprs {
@@ -642,22 +761,26 @@ _builtin_environ:
emit!(&mut self.output, " mov rsi, rax"); emit!(&mut self.output, " mov rsi, rax");
emit!(&mut self.output, " pop rdi"); emit!(&mut self.output, " pop rdi");
emit!(&mut self.output, " push rdi"); emit!(&mut self.output, " push rdi");
emit!(&mut self.output, " call array.push"); emit!(&mut self.output, " call Array.push");
} }
emit!(&mut self.output, " pop rax"); emit!(&mut self.output, " pop rax");
} }
Expr::Index { expr, index } => { ExprKind::Index {
expr,
bracket: _,
index,
} => {
self.compile_expr(env, expr)?; self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
self.compile_expr(env, index)?; self.compile_expr(env, index)?;
emit!(&mut self.output, " pop rbx"); emit!(&mut self.output, " pop rbx");
emit!(&mut self.output, " add rax, rbx"); emit!(&mut self.output, " add rax, rbx");
emit!(&mut self.output, " movzx rax, BYTE [rax]"); emit!(&mut self.output, " movzx rax, BYTE PTR [rax]");
} }
Expr::AddrOf { op, expr } => match *expr.clone() { ExprKind::AddrOf { op, expr } => match &expr.kind {
Expr::Variable(name) => { ExprKind::Variable(name) => {
if self.analyzer.functions.contains_key(&name.lexeme) { if self.symbol_table.functions.contains_key(&name.lexeme) {
emit!(&mut self.output, " mov rax, {}", name.lexeme); emit!(&mut self.output, " lea rax, [rip + {}]", name.lexeme);
} else { } else {
let var = match env.get_var(&name.lexeme) { let var = match env.get_var(&name.lexeme) {
Some(x) => x, Some(x) => x,
@@ -670,7 +793,7 @@ _builtin_environ:
}; };
emit!( emit!(
&mut self.output, &mut self.output,
" lea rax, QWORD [rbp-{}]", " lea rax, QWORD PTR [rbp-{}]",
var.stack_offset, var.stack_offset,
); );
} }
@@ -679,70 +802,158 @@ _builtin_environ:
return error!(&op.loc, "can only take address of variables and functions"); return error!(&op.loc, "can only take address of variables and functions");
} }
}, },
Expr::New(struct_name) => { ExprKind::New {
let struct_fields = &self.analyzer.structs[&struct_name.lexeme]; struct_name,
use_heap,
} => {
let struct_fields = &self.symbol_table.structs[&struct_name.lexeme];
let memory_size = struct_fields.len() * 8; let memory_size = struct_fields.len() * 8;
emit!(&mut self.output, " mov rdi, {}", memory_size);
emit!(&mut self.output, " call mem.alloc"); if *use_heap {
emit!(&mut self.output, " mov rdi, {}", memory_size);
emit!(&mut self.output, " call mem.alloc");
} else {
let aligned_size = (memory_size + 15) & !15;
emit!(&mut self.output, " sub rsp, {}", aligned_size);
emit!(&mut self.output, " mov rax, rsp");
}
emit!(&mut self.output, " push rax"); emit!(&mut self.output, " push rax");
emit!(&mut self.output, " sub rsp, 8");
emit!(&mut self.output, " mov rdi, rax"); emit!(&mut self.output, " mov rdi, rax");
emit!(&mut self.output, " mov rsi, {}", memory_size); emit!(&mut self.output, " mov rsi, {}", memory_size);
emit!(&mut self.output, " call mem.zero"); emit!(&mut self.output, " call mem.zero");
emit!(&mut self.output, " add rsp, 8");
emit!(&mut self.output, " pop rax"); emit!(&mut self.output, " pop rax");
} }
Expr::MemberAccess { left, field } => { ExprKind::MemberAccess { left, field } => {
let offset = self.get_field_offset(env, left, field)?; let offset = self.get_field_offset(left, field)?;
self.compile_expr(env, left)?; self.compile_expr(env, left)?;
emit!(&mut self.output, " mov rax, QWORD [rax+{}]", offset); emit!(&mut self.output, " mov rax, QWORD PTR [rax+{}]", offset);
}
ExprKind::Cast { expr, type_name: _ } => {
self.compile_expr(env, expr)?;
}
ExprKind::MethodCall { expr, method, args } => {
let receiver_type = &self.expr_types[&expr.id];
let func_name = format!("{}.{}", receiver_type, method.lexeme);
self.compile_expr(env, expr)?;
emit!(&mut self.output, " push rax");
for arg in args {
self.compile_expr(env, arg)?;
emit!(&mut self.output, " push rax");
}
let mut arg_types = vec![];
arg_types.push(receiver_type.clone());
arg_types.extend(args.iter().map(|a| self.expr_types[&a.id].clone()));
self.emit_call_setup(&arg_types);
emit!(&mut self.output, " call {}", func_name);
self.emit_call_cleanup(1 + args.len());
} }
} }
Ok(()) Ok(())
} }
fn is_valid_type_name(&self, name: &str) -> bool { fn emit_call_setup(&mut self, arg_types: &[String]) {
if BUILTIN_TYPES.contains(&name) { let arg_count = arg_types.len();
return true;
let to_register = arg_count.min(6);
let mut fp_idx = 0;
let mut int_idx = 0;
for (i, arg_type) in arg_types.iter().enumerate().take(to_register) {
let offset = 8 * (arg_count - 1 - i);
emit!(
&mut self.output,
" mov rax, QWORD PTR [rsp + {}]",
offset
);
if arg_type == "f64" {
emit!(&mut self.output, " movq xmm{}, rax", fp_idx);
fp_idx += 1;
} else {
emit!(&mut self.output, " mov {}, rax", REGISTERS[int_idx]);
int_idx += 1;
}
} }
if self.analyzer.structs.contains_key(name) {
return true; // TODO: since all zern values are 64bit large we currently cannot call
// external functions that expect a non-64bit value past the 6th argument
let num_stack = arg_count.saturating_sub(6);
for i in 0..num_stack {
let arg_idx = arg_count - 1 - i;
let offset = 8 * (arg_count - 1 - arg_idx);
emit!(
&mut self.output,
" mov rax, QWORD PTR [rsp + {}]",
offset + 8 * i
);
emit!(&mut self.output, " push rax");
}
emit!(&mut self.output, " mov al, {}", fp_idx);
if num_stack == 0 {
emit!(&mut self.output, " add rsp, {}", 8 * to_register);
} }
false
} }
fn get_field_offset( fn emit_call_cleanup(&mut self, arg_count: usize) {
&self, let num_stack = arg_count.saturating_sub(6);
env: &mut Env, if num_stack > 0 {
left: &Expr, emit!(
field: &Token, &mut self.output,
) -> Result<usize, ZernError> { " add rsp, {}",
let struct_name = match left { 8 * (arg_count + num_stack)
Expr::Variable(name) => match env.get_var(&name.lexeme) { );
Some(v) => v.var_type.clone(), }
None => { }
return error!(name.loc, format!("undefined variable: {}", &name.lexeme));
}
},
_ => {
return error!(
&field.loc,
"cannot determine struct type for member assignment"
);
}
};
let fields = match self.analyzer.structs.get(&struct_name) { fn get_field_offset(&self, left: &Expr, field: &Token) -> Result<usize, ZernError> {
let struct_name = &self.expr_types[&left.id];
let fields = match self.symbol_table.structs.get(struct_name) {
Some(f) => f, Some(f) => f,
None => { None => {
return error!(&field.loc, format!("unknown struct type: {}", struct_name)); return error!(&field.loc, format!("unknown struct type: {}", struct_name));
} }
}; };
let offset = match fields.get(&field.lexeme) { let field = match fields.get(&field.lexeme) {
Some(o) => *o, Some(o) => o,
None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)), None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)),
}; };
Ok(offset) Ok(field.offset)
}
fn emit_var_arg(&mut self, env: &mut Env, index_expr: &Expr) -> Result<(), ZernError> {
self.compile_expr(env, index_expr)?;
emit!(&mut self.output, " mov r10, rax");
let stack_label = self.label();
let done_label = self.label();
emit!(&mut self.output, " cmp r10, 6");
emit!(&mut self.output, " jge {}", stack_label);
// < 6
emit!(&mut self.output, " mov rax, r10");
emit!(&mut self.output, " inc rax");
emit!(&mut self.output, " shl rax, 3");
emit!(&mut self.output, " neg rax");
emit!(&mut self.output, " mov rax, [rbp + rax]");
emit!(&mut self.output, " jmp {}", done_label);
// >= 6
emit!(&mut self.output, "{}:", stack_label);
emit!(&mut self.output, " mov rax, r10");
emit!(&mut self.output, " sub rax, 6");
emit!(&mut self.output, " shl rax, 3");
emit!(&mut self.output, " mov rax, [rbp + 16 + rax]");
emit!(&mut self.output, "{}:", done_label);
Ok(())
} }
} }

View File

@@ -1,41 +1,83 @@
mod analyzer;
mod codegen_x86_64; mod codegen_x86_64;
mod parser; mod parser;
mod symbol_table;
mod tokenizer; mod tokenizer;
mod typechecker;
use std::{ use std::{
collections::HashSet,
fs, fs,
path::Path,
process::{self, Command}, process::{self, Command},
}; };
use tokenizer::ZernError; use tokenizer::ZernError;
use clap::Parser; fn compile_file(args: Args) -> Result<(), ZernError> {
let source = match fs::read_to_string(&args.path) {
Ok(x) => x,
Err(e) => {
eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}: {e}", args.path);
process::exit(1);
}
};
fn compile_file_to( let mut included_paths = HashSet::new();
codegen: &mut codegen_x86_64::CodegenX86_64, let tokenizer = tokenizer::Tokenizer::new(args.path, source, &mut included_paths);
filename: &str, let parser = parser::Parser::new(tokenizer.tokenize()?);
source: String,
) -> Result<(), ZernError> {
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source);
let tokens = tokenizer.tokenize()?;
let parser = parser::Parser::new(tokens);
let statements = parser.parse()?; let statements = parser.parse()?;
let mut symbol_table = symbol_table::SymbolTable::new();
for stmt in &statements { for stmt in &statements {
codegen.analyzer.register_function(stmt)?; symbol_table.register_declaration(stmt)?;
} }
let mut typechecker = typechecker::TypeChecker::new(&symbol_table);
for stmt in &statements { for stmt in &statements {
codegen.analyzer.analyze_stmt(stmt)?; typechecker.typecheck_stmt(&mut typechecker::Env::new(), stmt)?;
} }
let mut codegen = codegen_x86_64::CodegenX86_64::new(&symbol_table, &typechecker.expr_types);
codegen.emit_prologue(args.use_crt)?;
for stmt in statements { for stmt in statements {
// top level statements are all function/const/extern declarations so a new env for each
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?; codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
} }
if !args.emit_only {
let out = args.out.unwrap_or_else(|| "out".into());
fs::write(format!("{out}.s"), codegen.get_output()).unwrap();
let debug_flag = if args.emit_debug { "-g" } else { "" };
run_command(format!("as --64 {debug_flag} -o {out}.o {out}.s"));
if args.use_crt {
run_command(format!(
"cc -no-pie -o {out} {out}.o -flto -Wl,--gc-sections {}",
args.cflags
));
} else {
run_command(format!(
"ld -static -o {out} {out}.o --gc-sections -e _start"
));
}
if args.run_exe {
run_command(
std::fs::canonicalize(out)
.unwrap()
.to_string_lossy()
.into_owned(),
);
}
} else {
fs::write(
args.out.unwrap_or_else(|| "out.s".into()),
codegen.get_output(),
)
.unwrap();
}
Ok(()) Ok(())
} }
@@ -50,88 +92,85 @@ fn run_command(cmd: String) {
} }
} }
fn compile_file(args: Args) -> Result<(), ZernError> {
let source = match fs::read_to_string(&args.path) {
Ok(x) => x,
Err(_) => {
eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}", args.path);
process::exit(1);
}
};
let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap();
let mut analyzer = analyzer::Analyzer::new();
let mut codegen = codegen_x86_64::CodegenX86_64::new(&mut analyzer);
codegen.emit_prologue()?;
compile_file_to(
&mut codegen,
"syscalls.zr",
include_str!("std/syscalls.zr").into(),
)?;
compile_file_to(&mut codegen, "std.zr", include_str!("std/std.zr").into())?;
compile_file_to(
&mut codegen,
"crypto.zr",
include_str!("std/crypto.zr").into(),
)?;
compile_file_to(&mut codegen, filename, source)?;
if !args.output_asm {
fs::write(format!("{}.s", args.out), codegen.get_output()).unwrap();
run_command(format!("nasm -f elf64 -o {}.o {}.s", args.out, args.out));
if args.use_glibc {
run_command(format!(
"gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
args.out, args.out, args.cflags
));
} else {
run_command(format!(
"musl-gcc -static -o {} {}.o -flto -Wl,--gc-sections {}",
args.out, args.out, args.cflags
));
}
if args.run_exe {
run_command(
std::fs::canonicalize(args.out)
.unwrap()
.to_string_lossy()
.into_owned(),
);
}
} else {
fs::write(&args.out, codegen.get_output()).unwrap();
}
Ok(())
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args { struct Args {
path: String, path: String,
out: Option<String>,
#[arg(short, default_value = "out", help = "Output path")] emit_only: bool,
out: String, emit_debug: bool,
#[arg(short = 'S', help = "Only generate assembly")]
output_asm: bool,
#[arg(short = 'r', help = "Run the compiled executable")]
run_exe: bool, run_exe: bool,
use_crt: bool,
#[arg(short = 'm', help = "Use glibc instead of musl")]
use_glibc: bool,
#[arg(short = 'C', default_value = "", help = "Extra flags to pass to gcc")]
cflags: String, cflags: String,
} }
impl Args {
fn parse(mut args: std::env::Args) -> Args {
_ = args.next(); // skip the program name
let mut out = Args {
path: String::new(),
out: None,
emit_only: false,
emit_debug: false,
run_exe: false,
use_crt: false,
cflags: String::new(),
};
while let Some(arg) = args.next() {
if arg == "-o" {
match args.next() {
Some(s) => out.out = Some(s),
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a path");
process::exit(1);
}
}
} else if arg == "--emit-only" {
out.emit_only = true;
} else if arg == "-r" {
out.run_exe = true;
} else if arg == "-m" {
out.use_crt = true;
} else if arg == "-g" {
out.emit_debug = true;
} else if arg == "-C" {
match args.next() {
Some(s) => out.cflags = s,
None => {
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a value");
process::exit(1);
}
}
} else if arg == "-h" || arg == "--help" {
println!("Usage: zern [-o path] [-r] [-m] [-g] [-C cflags] [--emit-only] path");
process::exit(0);
} else if arg.starts_with('-') {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {arg}");
process::exit(1);
} else if out.path.is_empty() {
out.path = arg
} else {
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized argument: {arg}");
process::exit(1);
}
}
if out.path.is_empty() {
eprintln!("\x1b[91mERROR\x1b[0m: you must provide a path");
process::exit(1);
}
if !out.use_crt && !out.cflags.is_empty() {
eprintln!("You can't set CFLAGS if you're not using the C runtime. Add the -m flag.");
process::exit(1);
}
out
}
}
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse(std::env::args());
if let Err(err) = compile_file(args) { if let Err(err) = compile_file(args) {
eprintln!("{}", err); eprintln!("{}", err);

View File

@@ -1,4 +1,10 @@
use crate::tokenizer::{Token, TokenType, ZernError, error}; use std::sync::atomic::{AtomicUsize, Ordering};
use crate::tokenizer::{
Token,
TokenType::{self, Identifier},
ZernError, error,
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Param { pub struct Param {
@@ -6,25 +12,44 @@ pub struct Param {
pub var_name: Token, pub var_name: Token,
} }
#[derive(Debug, Clone)]
pub enum Params {
Normal(Vec<Param>),
Variadic,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
Expression(Expr), Expression(Expr),
Let { Declare {
name: Token, name: Token,
var_type: Option<Token>, var_type: Option<Token>,
initializer: Expr, initializer: Expr,
}, },
Assign {
left: Expr,
op: Token,
value: Expr,
},
Destructure {
targets: Vec<Token>,
op: Token,
value: Expr,
},
Const { Const {
name: Token, name: Token,
value: Token, value: Token,
neg: bool,
}, },
Block(Vec<Stmt>), Block(Vec<Stmt>),
If { If {
keyword: Token,
condition: Expr, condition: Expr,
then_branch: Box<Stmt>, then_branch: Box<Stmt>,
else_branch: Box<Stmt>, else_branch: Box<Stmt>,
}, },
While { While {
keyword: Token,
condition: Expr, condition: Expr,
body: Box<Stmt>, body: Box<Stmt>,
}, },
@@ -36,12 +61,15 @@ pub enum Stmt {
}, },
Function { Function {
name: Token, name: Token,
params: Vec<Param>, params: Params,
return_type: Token, return_types: Vec<Token>,
body: Box<Stmt>, body: Box<Stmt>,
exported: bool, exported: bool,
}, },
Return(Expr), Return {
keyword: Token,
exprs: Vec<Expr>,
},
Break, Break,
Continue, Continue,
Extern(Token), Extern(Token),
@@ -51,8 +79,26 @@ pub enum Stmt {
}, },
} }
pub static NEXT_EXPR_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expr { pub struct Expr {
pub id: usize,
pub kind: ExprKind,
}
impl Expr {
pub fn new(kind: ExprKind) -> Expr {
NEXT_EXPR_ID.fetch_add(1, Ordering::SeqCst);
Expr {
id: NEXT_EXPR_ID.load(Ordering::SeqCst),
kind,
}
}
}
#[derive(Debug, Clone)]
pub enum ExprKind {
Binary { Binary {
left: Box<Expr>, left: Box<Expr>,
op: Token, op: Token,
@@ -70,11 +116,6 @@ pub enum Expr {
right: Box<Expr>, right: Box<Expr>,
}, },
Variable(Token), Variable(Token),
Assign {
left: Box<Expr>,
op: Token,
value: Box<Expr>,
},
Call { Call {
callee: Box<Expr>, callee: Box<Expr>,
paren: Token, paren: Token,
@@ -83,23 +124,37 @@ pub enum Expr {
ArrayLiteral(Vec<Expr>), ArrayLiteral(Vec<Expr>),
Index { Index {
expr: Box<Expr>, expr: Box<Expr>,
bracket: Token,
index: Box<Expr>, index: Box<Expr>,
}, },
AddrOf { AddrOf {
op: Token, op: Token,
expr: Box<Expr>, expr: Box<Expr>,
}, },
New(Token), New {
struct_name: Token,
use_heap: bool,
},
MemberAccess { MemberAccess {
left: Box<Expr>, left: Box<Expr>,
field: Token, field: Token,
}, },
Cast {
expr: Box<Expr>,
type_name: Token,
},
MethodCall {
expr: Box<Expr>,
method: Token,
args: Vec<Expr>,
},
} }
pub struct Parser { pub struct Parser {
tokens: Vec<Token>, tokens: Vec<Token>,
current: usize, current: usize,
is_inside_function: bool, is_inside_function: bool,
depth: usize,
} }
impl Parser { impl Parser {
@@ -108,6 +163,7 @@ impl Parser {
tokens, tokens,
current: 0, current: 0,
is_inside_function: false, is_inside_function: false,
depth: 0,
} }
} }
@@ -143,35 +199,45 @@ impl Parser {
); );
} }
if self.match_token(&[TokenType::KeywordLet]) { self.statement()
self.let_declaration()
} else {
self.statement()
}
} }
fn func_declaration(&mut self, exported: bool) -> Result<Stmt, ZernError> { fn func_declaration(&mut self, exported: bool) -> Result<Stmt, ZernError> {
let name = self.consume(TokenType::Identifier, "expected function name")?; let name = self.consume(TokenType::Identifier, "expected function name")?;
self.consume(TokenType::LeftBracket, "expected '[' after function name")?; self.consume(TokenType::LeftBracket, "expected '[' after function name")?;
let mut is_variadic = false;
let mut params = vec![]; let mut params = vec![];
if !self.check(&TokenType::RightBracket) { if !self.check(&TokenType::RightBracket) {
loop { if self.match_token(&[TokenType::DoubleDot]) {
let var_name = self.consume(TokenType::Identifier, "expected parameter name")?; is_variadic = true;
self.consume(TokenType::Colon, "expected ':' after parameter name")?; } else {
loop {
let var_name =
self.consume(TokenType::Identifier, "expected parameter name")?;
self.consume(TokenType::Colon, "expected ':' after parameter name")?;
let var_type = self.consume(TokenType::Identifier, "expected parameter type")?; let var_type =
self.consume(TokenType::Identifier, "expected parameter type")?;
params.push(Param { var_type, var_name }); params.push(Param { var_type, var_name });
if !self.match_token(&[TokenType::Comma]) { if !self.match_token(&[TokenType::Comma]) {
break; break;
}
} }
} }
} }
self.consume(TokenType::RightBracket, "expected ']' after arguments")?; self.consume(TokenType::RightBracket, "expected ']' after arguments")?;
self.consume(TokenType::Colon, "expected ':' after '['")?; self.consume(TokenType::Colon, "expected ':' after '['")?;
let return_type = self.consume(TokenType::Identifier, "expected return type")?;
let mut return_types = vec![];
loop {
return_types.push(self.consume(TokenType::Identifier, "expected return type")?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
self.is_inside_function = true; self.is_inside_function = true;
let body = Box::new(self.block()?); let body = Box::new(self.block()?);
@@ -179,8 +245,12 @@ impl Parser {
Ok(Stmt::Function { Ok(Stmt::Function {
name, name,
params, params: if is_variadic {
return_type, Params::Variadic
} else {
Params::Normal(params)
},
return_types,
body, body,
exported, exported,
}) })
@@ -201,35 +271,17 @@ impl Parser {
fields.push(Param { var_type, var_name }); fields.push(Param { var_type, var_name });
} }
self.consume(TokenType::Dedent, "expected dedent after the struct fields")?; self.consume(TokenType::Dedent, "expected dedent after struct fields")?;
Ok(Stmt::Struct { name, fields }) Ok(Stmt::Struct { name, fields })
} }
fn let_declaration(&mut self) -> Result<Stmt, ZernError> {
let name = self.consume(TokenType::Identifier, "expected variable name")?;
let var_type = if self.match_token(&[TokenType::Colon]) {
let token = self.consume(TokenType::Identifier, "expected variable type")?;
Some(token)
} else {
None
};
self.consume(TokenType::Equal, "expected '=' after variable type")?;
let initializer = self.expression()?;
Ok(Stmt::Let {
name,
var_type,
initializer,
})
}
fn const_declaration(&mut self) -> Result<Stmt, ZernError> { fn const_declaration(&mut self) -> Result<Stmt, ZernError> {
let name = self.consume(TokenType::Identifier, "expected const name")?; let name = self.consume(TokenType::Identifier, "expected const name")?;
self.consume(TokenType::Equal, "expected '=' after const name")?; self.consume(TokenType::Equal, "expected '=' after const name")?;
let value = self.consume(TokenType::Number, "expected a number after '='")?; let neg = self.match_token(&[TokenType::Minus]);
Ok(Stmt::Const { name, value }) let value = self.consume(TokenType::IntLiteral, "expected a number after '='")?;
Ok(Stmt::Const { name, value, neg })
} }
fn extern_declaration(&mut self) -> Result<Stmt, ZernError> { fn extern_declaration(&mut self) -> Result<Stmt, ZernError> {
@@ -250,24 +302,105 @@ impl Parser {
} }
fn statement(&mut self) -> Result<Stmt, ZernError> { fn statement(&mut self) -> Result<Stmt, ZernError> {
if self.match_token(&[TokenType::KeywordIf]) { if self.check_ahead(&TokenType::Colon) {
let name = self.consume(TokenType::Identifier, "expected variable name")?;
self.consume(TokenType::Colon, "expected ':'")?;
let var_type = if self.match_token(&[TokenType::Equal]) {
None
} else {
let var_type = self.consume(TokenType::Identifier, "expected variable type")?;
self.consume(TokenType::Equal, "expected '=' after varaible type")?;
Some(var_type)
};
let initializer = self.expression()?;
Ok(Stmt::Declare {
name,
var_type,
initializer,
})
} else if self.match_token(&[TokenType::KeywordIf]) {
self.if_statement() self.if_statement()
} else if self.match_token(&[TokenType::KeywordWhile]) { } else if self.match_token(&[TokenType::KeywordWhile]) {
self.while_statement() self.while_statement()
} else if self.match_token(&[TokenType::KeywordFor]) { } else if self.match_token(&[TokenType::KeywordFor]) {
self.for_statement() self.for_statement()
} else if self.match_token(&[TokenType::KeywordReturn]) { } else if self.match_token(&[TokenType::KeywordReturn]) {
Ok(Stmt::Return(self.expression()?)) let keyword = self.previous().clone();
let mut exprs = vec![];
if !self.check(&TokenType::Dedent) {
loop {
exprs.push(self.expression()?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
}
Ok(Stmt::Return { keyword, exprs })
} else if self.match_token(&[TokenType::KeywordBreak]) { } else if self.match_token(&[TokenType::KeywordBreak]) {
Ok(Stmt::Break) Ok(Stmt::Break)
} else if self.match_token(&[TokenType::KeywordContinue]) { } else if self.match_token(&[TokenType::KeywordContinue]) {
Ok(Stmt::Continue) Ok(Stmt::Continue)
} else if self.check(&TokenType::Identifier) && self.check_ahead(&TokenType::Comma) {
let mut targets = vec![];
loop {
targets.push(self.consume(Identifier, "expected an identifier")?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
let op = self.consume(TokenType::Colon, "expected ':'")?;
self.consume(TokenType::Equal, "expected '=' after ':'")?;
let value = self.expression()?;
Ok(Stmt::Destructure { targets, op, value })
} else { } else {
Ok(Stmt::Expression(self.expression()?)) let expr = self.expression()?;
if self.match_token(&[TokenType::Equal]) {
let op = self.previous().clone();
let value = self.expression()?;
Ok(Stmt::Assign {
left: expr,
op,
value,
})
} else if self.match_token(&[TokenType::PlusEqual, TokenType::MinusEqual]) {
let op = self.previous().clone();
let right = self.expression()?;
let binary_token = Token {
token_type: match op.token_type {
TokenType::PlusEqual => TokenType::Plus,
TokenType::MinusEqual => TokenType::Minus,
_ => unreachable!(),
},
lexeme: match op.token_type {
TokenType::PlusEqual => String::from("+"),
TokenType::MinusEqual => String::from("-"),
_ => unreachable!(),
},
loc: op.loc.clone(),
};
Ok(Stmt::Assign {
left: expr.clone(),
op: Token {
token_type: TokenType::Equal,
lexeme: String::from("="),
loc: op.loc,
},
value: Expr::new(ExprKind::Binary {
left: Box::new(expr),
op: binary_token,
right: Box::new(right),
}),
})
} else {
Ok(Stmt::Expression(expr))
}
} }
} }
fn if_statement(&mut self) -> Result<Stmt, ZernError> { fn if_statement(&mut self) -> Result<Stmt, ZernError> {
let keyword = self.previous().clone();
let condition = self.expression()?; let condition = self.expression()?;
let then_branch = self.block()?; let then_branch = self.block()?;
let else_branch = if self.match_token(&[TokenType::KeywordElse]) { let else_branch = if self.match_token(&[TokenType::KeywordElse]) {
@@ -280,6 +413,7 @@ impl Parser {
Box::new(Stmt::Block(vec![])) Box::new(Stmt::Block(vec![]))
}; };
Ok(Stmt::If { Ok(Stmt::If {
keyword,
condition, condition,
then_branch: Box::new(then_branch), then_branch: Box::new(then_branch),
else_branch, else_branch,
@@ -287,9 +421,11 @@ impl Parser {
} }
fn while_statement(&mut self) -> Result<Stmt, ZernError> { fn while_statement(&mut self) -> Result<Stmt, ZernError> {
let keyword = self.previous().clone();
let condition = self.expression()?; let condition = self.expression()?;
let body = self.block()?; let body = self.block()?;
Ok(Stmt::While { Ok(Stmt::While {
keyword,
condition, condition,
body: Box::new(body), body: Box::new(body),
}) })
@@ -312,54 +448,14 @@ impl Parser {
} }
fn expression(&mut self) -> Result<Expr, ZernError> { fn expression(&mut self) -> Result<Expr, ZernError> {
self.assignment() self.depth += 1;
} if self.depth > 200 {
return error!(self.previous().loc, "maximum expression depth reached");
fn assignment(&mut self) -> Result<Expr, ZernError> {
let expr = self.pipe()?;
if self.match_token(&[TokenType::Equal]) {
let equals = self.previous().clone();
let value = self.assignment()?;
return Ok(Expr::Assign {
left: Box::new(expr),
op: equals,
value: Box::new(value),
});
} }
Ok(expr) let out = self.or_and();
} self.depth -= 1;
out
fn pipe(&mut self) -> Result<Expr, ZernError> {
let mut expr = self.or_and()?;
while self.match_token(&[TokenType::Pipe]) {
let pipe = self.previous().clone();
let right = self.equality()?;
match right {
Expr::Call {
callee,
paren,
args,
} => {
let mut new_args = args;
new_args.insert(0, expr);
expr = Expr::Call {
callee,
paren,
args: new_args,
}
}
_ => {
return error!(pipe.loc, "tried to pipe into a non-call expression");
}
};
}
Ok(expr)
} }
fn or_and(&mut self) -> Result<Expr, ZernError> { fn or_and(&mut self) -> Result<Expr, ZernError> {
@@ -368,11 +464,11 @@ impl Parser {
while self.match_token(&[TokenType::LogicalOr, TokenType::LogicalAnd]) { while self.match_token(&[TokenType::LogicalOr, TokenType::LogicalAnd]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.equality()?; let right = self.equality()?;
expr = Expr::Logical { expr = Expr::new(ExprKind::Logical {
left: Box::new(expr), left: Box::new(expr),
op, op,
right: Box::new(right), right: Box::new(right),
} })
} }
Ok(expr) Ok(expr)
@@ -384,11 +480,11 @@ impl Parser {
while self.match_token(&[TokenType::DoubleEqual, TokenType::NotEqual]) { while self.match_token(&[TokenType::DoubleEqual, TokenType::NotEqual]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.comparison()?; let right = self.comparison()?;
expr = Expr::Binary { expr = Expr::new(ExprKind::Binary {
left: Box::new(expr), left: Box::new(expr),
op, op,
right: Box::new(right), right: Box::new(right),
} })
} }
Ok(expr) Ok(expr)
@@ -405,11 +501,11 @@ impl Parser {
]) { ]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.term()?; let right = self.term()?;
expr = Expr::Binary { expr = Expr::new(ExprKind::Binary {
left: Box::new(expr), left: Box::new(expr),
op, op,
right: Box::new(right), right: Box::new(right),
} })
} }
Ok(expr) Ok(expr)
@@ -427,18 +523,18 @@ impl Parser {
]) { ]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.factor()?; let right = self.factor()?;
expr = Expr::Binary { expr = Expr::new(ExprKind::Binary {
left: Box::new(expr), left: Box::new(expr),
op, op,
right: Box::new(right), right: Box::new(right),
} })
} }
Ok(expr) Ok(expr)
} }
fn factor(&mut self) -> Result<Expr, ZernError> { fn factor(&mut self) -> Result<Expr, ZernError> {
let mut expr = self.unary()?; let mut expr = self.cast()?;
while self.match_token(&[ while self.match_token(&[
TokenType::Star, TokenType::Star,
@@ -449,11 +545,25 @@ impl Parser {
]) { ]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.unary()?; let right = self.unary()?;
expr = Expr::Binary { expr = Expr::new(ExprKind::Binary {
left: Box::new(expr), left: Box::new(expr),
op, op,
right: Box::new(right), right: Box::new(right),
} })
}
Ok(expr)
}
fn cast(&mut self) -> Result<Expr, ZernError> {
let mut expr = self.unary()?;
while self.match_token(&[TokenType::KeywordAs]) {
let type_name = self.consume(TokenType::Identifier, "expected type after 'as'")?;
expr = Expr::new(ExprKind::Cast {
expr: Box::new(expr),
type_name,
})
} }
Ok(expr) Ok(expr)
@@ -463,18 +573,18 @@ impl Parser {
if self.match_token(&[TokenType::Xor]) { if self.match_token(&[TokenType::Xor]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.unary()?; let right = self.unary()?;
return Ok(Expr::AddrOf { return Ok(Expr::new(ExprKind::AddrOf {
op, op,
expr: Box::new(right), expr: Box::new(right),
}); }));
} }
if self.match_token(&[TokenType::Bang, TokenType::Minus]) { if self.match_token(&[TokenType::Bang, TokenType::Minus]) {
let op = self.previous().clone(); let op = self.previous().clone();
let right = self.unary()?; let right = self.unary()?;
return Ok(Expr::Unary { return Ok(Expr::new(ExprKind::Unary {
op, op,
right: Box::new(right), right: Box::new(right),
}); }));
} }
self.call() self.call()
@@ -484,6 +594,10 @@ impl Parser {
let mut expr = self.primary()?; let mut expr = self.primary()?;
loop { loop {
if self.peek().loc.line != self.previous().loc.line {
break;
}
if self.match_token(&[TokenType::LeftParen]) { if self.match_token(&[TokenType::LeftParen]) {
let mut args = vec![]; let mut args = vec![];
if !self.check(&TokenType::RightParen) { if !self.check(&TokenType::RightParen) {
@@ -497,25 +611,46 @@ impl Parser {
let paren = self.consume(TokenType::RightParen, "expected ')' after arguments")?; let paren = self.consume(TokenType::RightParen, "expected ')' after arguments")?;
expr = Expr::Call { expr = Expr::new(ExprKind::Call {
callee: Box::new(expr), callee: Box::new(expr),
paren, paren,
args, args,
}; })
} else if self.match_token(&[TokenType::LeftBracket]) { } else if self.match_token(&[TokenType::LeftBracket]) {
let index = self.expression()?; let index = self.expression()?;
self.consume(TokenType::RightBracket, "expected ']' after index")?; let bracket = self.consume(TokenType::RightBracket, "expected ']' after index")?;
expr = Expr::Index { expr = Expr::new(ExprKind::Index {
expr: Box::new(expr), expr: Box::new(expr),
bracket,
index: Box::new(index), index: Box::new(index),
} })
} else if self.match_token(&[TokenType::Arrow]) { } else if self.match_token(&[TokenType::Arrow]) {
let field = if self.check(&TokenType::Identifier) && self.check_ahead(&TokenType::LeftParen) {
self.consume(TokenType::Identifier, "expected field name after '->'")?; let method = self.consume(TokenType::Identifier, "expected method name")?;
expr = Expr::MemberAccess { self.consume(TokenType::LeftParen, "expected '('")?;
left: Box::new(expr), let mut args = vec![];
field, if !self.check(&TokenType::RightParen) {
}; loop {
args.push(self.expression()?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
}
self.consume(TokenType::RightParen, "expected ')'")?;
expr = Expr::new(ExprKind::MethodCall {
expr: Box::new(expr),
method,
args,
});
} else {
let field =
self.consume(TokenType::Identifier, "expected field name after '->'")?;
expr = Expr::new(ExprKind::MemberAccess {
left: Box::new(expr),
field,
});
}
} else { } else {
break; break;
} }
@@ -526,17 +661,18 @@ impl Parser {
fn primary(&mut self) -> Result<Expr, ZernError> { fn primary(&mut self) -> Result<Expr, ZernError> {
if self.match_token(&[ if self.match_token(&[
TokenType::Number, TokenType::IntLiteral,
TokenType::Char, TokenType::FloatLiteral,
TokenType::String, TokenType::CharLiteral,
TokenType::True, TokenType::StringLiteral,
TokenType::False, TokenType::KeywordTrue,
TokenType::KeywordFalse,
]) { ]) {
Ok(Expr::Literal(self.previous().clone())) Ok(Expr::new(ExprKind::Literal(self.previous().clone())))
} else if self.match_token(&[TokenType::LeftParen]) { } else if self.match_token(&[TokenType::LeftParen]) {
let expr = self.expression()?; let expr = self.expression()?;
self.consume(TokenType::RightParen, "expected ')' after expression")?; self.consume(TokenType::RightParen, "expected ')' after expression")?;
Ok(Expr::Grouping(Box::new(expr))) Ok(Expr::new(ExprKind::Grouping(Box::new(expr))))
} else if self.match_token(&[TokenType::LeftBracket]) { } else if self.match_token(&[TokenType::LeftBracket]) {
let mut xs = vec![]; let mut xs = vec![];
if !self.check(&TokenType::RightBracket) { if !self.check(&TokenType::RightBracket) {
@@ -549,13 +685,17 @@ impl Parser {
} }
self.consume(TokenType::RightBracket, "expected ']' after values")?; self.consume(TokenType::RightBracket, "expected ']' after values")?;
Ok(Expr::ArrayLiteral(xs)) Ok(Expr::new(ExprKind::ArrayLiteral(xs)))
} else if self.match_token(&[TokenType::KeywordNew]) { } else if self.match_token(&[TokenType::KeywordNew]) {
let use_heap = self.match_token(&[TokenType::Star]);
let struct_name = let struct_name =
self.consume(TokenType::Identifier, "expected struct name after 'new'")?; self.consume(TokenType::Identifier, "expected struct name after 'new'")?;
Ok(Expr::New(struct_name)) Ok(Expr::new(ExprKind::New {
struct_name,
use_heap,
}))
} else if self.match_token(&[TokenType::Identifier]) { } else if self.match_token(&[TokenType::Identifier]) {
Ok(Expr::Variable(self.previous().clone())) Ok(Expr::new(ExprKind::Variable(self.previous().clone())))
} else { } else {
error!( error!(
self.peek().loc, self.peek().loc,
@@ -583,6 +723,14 @@ impl Parser {
false false
} }
fn check_ahead(&self, token_type: &TokenType) -> bool {
if self.current + 1 >= self.tokens.len() {
false
} else {
self.tokens[self.current + 1].token_type == *token_type
}
}
fn check(&self, token_type: &TokenType) -> bool { fn check(&self, token_type: &TokenType) -> bool {
if self.eof() { if self.eof() {
false false

View File

@@ -1,412 +0,0 @@
// absolutely not production-ready
func crypto.rotl32[x: i64, r: i64] : i64
return ((x << r) | (x >> (32 - r))) & 0xffffffff
func crypto.rotr64[x: i64, y: i64] : i64
y = y & 63
if y == 0
return x
let lhs_mask: i64 = ((1 << (64 - y)) - 1)
let r: i64 = (x >> y) & lhs_mask
let l: i64 = (x << (64 - y))
return (r | l)
func crypto.blake2b._G[v: ptr, a: i64, b: i64, c: i64, d: i64, x: i64, y: i64] : void
mem.write64(v + a * 8, mem.read64(v + a * 8) + mem.read64(v + b * 8) + x)
mem.write64(v + d * 8, crypto.rotr64(mem.read64(v + d * 8) ^ mem.read64(v + a * 8), 32))
mem.write64(v + c * 8, mem.read64(v + c * 8) + mem.read64(v + d * 8))
mem.write64(v + b * 8, crypto.rotr64(mem.read64(v + b * 8) ^ mem.read64(v + c * 8), 24))
mem.write64(v + a * 8, mem.read64(v + a * 8) + mem.read64(v + b * 8) + y)
mem.write64(v + d * 8, crypto.rotr64(mem.read64(v + d * 8) ^ mem.read64(v + a * 8), 16))
mem.write64(v + c * 8, mem.read64(v + c * 8) + mem.read64(v + d * 8))
mem.write64(v + b * 8, crypto.rotr64(mem.read64(v + b * 8) ^ mem.read64(v + c * 8), 63))
func crypto.blake2b._compress[h: ptr, block: ptr, t0: i64, t1: i64, last: i64, iv: Array, sigma: Array] : void
let v: ptr = mem.alloc(16 * 8)
let m: ptr = mem.alloc(16 * 8)
for j in 0..8
mem.write64(v + j * 8, mem.read64(h + j * 8))
mem.write64(v + (j + 8) * 8, array.nth(iv, j))
mem.write64(v + 12 * 8, mem.read64(v + 12 * 8) ^ t0)
mem.write64(v + 13 * 8, mem.read64(v + 13 * 8) ^ t1)
if last
mem.write64(v + 14 * 8, mem.read64(v + 14 * 8) ^ 0xffffffffffffffff)
for j in 0..16
let w = 0
for k in 0..8
w = w | ((block[j * 8 + k] & 0xff) << (8 * k))
mem.write64(m + j * 8, w)
for r in 0..12
let base: i64 = r * 16
crypto.blake2b._G(v, 0, 4, 8, 12, mem.read64(m + array.nth(sigma, base + 0) * 8), mem.read64(m + array.nth(sigma, base + 1) * 8))
crypto.blake2b._G(v, 1, 5, 9, 13, mem.read64(m + array.nth(sigma, base + 2) * 8), mem.read64(m + array.nth(sigma, base + 3) * 8))
crypto.blake2b._G(v, 2, 6, 10, 14, mem.read64(m + array.nth(sigma, base + 4) * 8), mem.read64(m + array.nth(sigma, base + 5) * 8))
crypto.blake2b._G(v, 3, 7, 11, 15, mem.read64(m + array.nth(sigma, base + 6) * 8), mem.read64(m + array.nth(sigma, base + 7) * 8))
crypto.blake2b._G(v, 0, 5, 10, 15, mem.read64(m + array.nth(sigma, base + 8) * 8), mem.read64(m + array.nth(sigma, base + 9) * 8))
crypto.blake2b._G(v, 1, 6, 11, 12, mem.read64(m + array.nth(sigma, base + 10) * 8), mem.read64(m + array.nth(sigma, base + 11) * 8))
crypto.blake2b._G(v, 2, 7, 8, 13, mem.read64(m + array.nth(sigma, base + 12) * 8), mem.read64(m + array.nth(sigma, base + 13) * 8))
crypto.blake2b._G(v, 3, 4, 9, 14, mem.read64(m + array.nth(sigma, base + 14) * 8), mem.read64(m + array.nth(sigma, base + 15) * 8))
for j in 0..8
mem.write64(h + j * 8, mem.read64(h + j * 8) ^ (mem.read64(v + j * 8) ^ mem.read64(v + (j + 8) * 8)))
mem.zero_and_free(v, 16 * 8)
mem.zero_and_free(m, 16 * 8)
func crypto.blake2b.hash[outlen: i64, key: ptr, keylen: i64, input: ptr, inputlen: i64] : ptr
if outlen == 0 || outlen > 64 || keylen > 64
panic("invalid length passed to crypto.blake2b.hash")
let out: ptr = mem.alloc(outlen)
// IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), where prime(i)
// is the i:th prime number ( 2, 3, 5, 7, 11, 13, 17, 19 )
// and sqrt(x) is the square root of x.
// https://www.ietf.org/rfc/rfc7693#section-2.6
let iv: Array = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179]
// https://crypto.stackexchange.com/questions/108987/rationale-for-blake2-message-schedule
let sigma: Array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3,11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4,7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8,9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13,2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9,12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11,13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10,6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5,10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3]
let h: ptr = mem.alloc(8 * 8)
let t0 = 0
let t1 = 0
let block: ptr = mem.alloc(128)
let c = 0
for i in 0..8
mem.write64(h + i * 8, array.nth(iv, i))
mem.write64(h, mem.read64(h) ^ (0x01010000 ^ ((keylen << 8) ^ outlen)))
if keylen > 0
for i in 0..keylen
block[i] = key[i]
for i in (keylen)..128
block[i] = 0
c = 128
else
c = 0
for i in 0..inputlen
if c == 128
t0 = t0 + c
if t0 < c
t1 = t1 + 1
crypto.blake2b._compress(h, block, t0, t1, 0, iv, sigma)
c = 0
block[c] = input[i]
c = c + 1
t0 = t0 + c
if t0 < c
t1 = t1 + 1
if c < 128
for i in (c)..128
block[i] = 0
crypto.blake2b._compress(h, block, t0, t1, 1, iv, sigma)
for i in 0..outlen
out[i] = (mem.read64(h + (i >> 3) * 8) >> (8 * (i & 7))) & 0xff
mem.zero_and_free(h, 8 * 8)
mem.zero_and_free(block, 128)
array.free(iv)
array.free(sigma)
return out
func crypto.chacha20._quarter_round[state: ptr, a: i64, b: i64, c: i64, d: i64] : void
let va: i64 = mem.read32(state + a * 4)
let vb: i64 = mem.read32(state + b * 4)
let vc: i64 = mem.read32(state + c * 4)
let vd: i64 = mem.read32(state + d * 4)
va = (va + vb) & 0xffffffff
vd = vd ^ va
vd = crypto.rotl32(vd, 16)
vc = (vc + vd) & 0xffffffff
vb = vb ^ vc
vb = crypto.rotl32(vb, 12)
va = (va + vb) & 0xffffffff
vd = vd ^ va
vd = crypto.rotl32(vd, 8)
vc = (vc + vd) & 0xffffffff
vb = vb ^ vc
vb = crypto.rotl32(vb, 7)
mem.write32(state + a * 4, va)
mem.write32(state + b * 4, vb)
mem.write32(state + c * 4, vc)
mem.write32(state + d * 4, vd)
func crypto.xchacha20._permute[state: ptr] : void
for i in 0..10
crypto.chacha20._quarter_round(state, 0, 4, 8, 12)
crypto.chacha20._quarter_round(state, 1, 5, 9, 13)
crypto.chacha20._quarter_round(state, 2, 6, 10, 14)
crypto.chacha20._quarter_round(state, 3, 7, 11, 15)
crypto.chacha20._quarter_round(state, 0, 5, 10, 15)
crypto.chacha20._quarter_round(state, 1, 6, 11, 12)
crypto.chacha20._quarter_round(state, 2, 7, 8, 13)
crypto.chacha20._quarter_round(state, 3, 4, 9, 14)
func crypto.xchacha20._block[key: ptr, nonce: ptr, blocknum: i64, out: ptr] : void
let sigma: str = "expand 32-byte k"
let state: ptr = mem.alloc(16 * 4)
mem.write32(state + 0, mem.read32(sigma + 0))
mem.write32(state + 4, mem.read32(sigma + 4))
mem.write32(state + 8, mem.read32(sigma + 8))
mem.write32(state + 12, mem.read32(sigma + 12))
for i in 0..8
mem.write32(state + (4 + i) * 4, mem.read32(key + i * 4))
mem.write32(state + 12 * 4, blocknum)
mem.write32(state + 13 * 4, mem.read32(nonce + 0))
mem.write32(state + 14 * 4, mem.read32(nonce + 4))
mem.write32(state + 15 * 4, mem.read32(nonce + 8))
let working: ptr = mem.alloc(16 * 4)
for i in 0..16
mem.write32(working + i * 4, mem.read32(state + i * 4))
crypto.xchacha20._permute(working)
for i in 0..16
let v: i64 = (mem.read32(working + i * 4) + mem.read32(state + i * 4)) & 0xffffffff
mem.write32(out + i * 4, v)
mem.zero_and_free(working, 16 * 4)
mem.zero_and_free(state, 16 * 4)
func crypto.xchacha20._hchacha20[key: ptr, input: ptr, out32: ptr] : void
let sigma: str = "expand 32-byte k"
let state: ptr = mem.alloc(16 * 4)
mem.write32(state + 0, mem.read32(sigma + 0))
mem.write32(state + 4, mem.read32(sigma + 4))
mem.write32(state + 8, mem.read32(sigma + 8))
mem.write32(state + 12, mem.read32(sigma + 12))
for i in 0..8
mem.write32(state + (4 + i) * 4, mem.read32(key + i * 4))
for i in 0..4
mem.write32(state + (12 + i) * 4, mem.read32(input + i * 4))
crypto.xchacha20._permute(state)
for i in 0..4
mem.write32(out32 + i * 4, mem.read32(state + i * 4))
for i in 0..4
mem.write32(out32 + 16 + i * 4, mem.read32(state + (12 + i) * 4))
mem.zero_and_free(state, 16 * 4)
func crypto.xchacha20._stream[key: ptr, nonce: ptr, out: ptr, len: i64] : void
let subkey: ptr = mem.alloc(32)
crypto.xchacha20._hchacha20(key, nonce, subkey)
let nonce12: ptr = mem.alloc(12)
mem.zero(nonce12, 12)
for i in 0..8
nonce12[i + 4] = nonce[16 + i]
let blocknum = 0
let remaining: i64 = len
let block: ptr = mem.alloc(64)
while remaining > 0
crypto.xchacha20._block(subkey, nonce12, blocknum, block)
let take = 64
if remaining < 64
take = remaining
for i in 0..take
out[len - remaining + i] = block[i]
remaining = remaining - take
blocknum = blocknum + 1
mem.zero_and_free(block, 64)
mem.zero_and_free(nonce12, 12)
mem.zero_and_free(subkey, 32)
// expects 32-byte key and 24-byte nonce
// no way to verify that until we get actual strings
func crypto.xchacha20.xor_no_auth[key: ptr, nonce: ptr, input: ptr, len: i64] : ptr
if len <= 0
let out: ptr = mem.alloc(1)
out[0] = 0
return out
let out: ptr = mem.alloc(len)
let ks: ptr = mem.alloc(len)
crypto.xchacha20._stream(key, nonce, ks, len)
for i in 0..len
out[i] = input[i] ^ ks[i]
mem.zero_and_free(ks, len)
return out
func crypto.x25519.carry[elem: ptr] : void
for i in 0..16
let carry: i64 = mem.read64(elem + i * 8) >> 16
mem.write64(elem + i * 8, mem.read64(elem + i * 8) - (carry << 16))
if i < 15
mem.write64(elem + (i + 1) * 8, mem.read64(elem + (i + 1) * 8) + carry)
else
mem.write64(elem, mem.read64(elem) + 38 * carry)
func crypto.x25519.fadd[out: ptr, a: ptr, b: ptr] : void
for i in 0..16
mem.write64(out + i * 8, mem.read64(a + i * 8) + mem.read64(b + i * 8))
func crypto.x25519.fsub[out: ptr, a: ptr, b: ptr] : void
for i in 0..16
mem.write64(out + i * 8, mem.read64(a + i * 8) - mem.read64(b + i * 8))
func crypto.x25519.fmul[out: ptr, a: ptr, b: ptr] : void
let product: ptr = mem.alloc(31 * 8)
for i in 0..31
mem.write64(product + i * 8, 0)
for i in 0..16
for j in 0..16
mem.write64(product + (i + j) * 8, mem.read64(product + (i + j) * 8) + (mem.read64(a + i * 8) * mem.read64(b + j * 8)))
for i in 0..15
mem.write64(product + i * 8, mem.read64(product + i * 8) + 38 * mem.read64(product + (i + 16) * 8))
for i in 0..16
mem.write64(out + i * 8, mem.read64(product + i * 8))
crypto.x25519.carry(out)
crypto.x25519.carry(out)
mem.zero_and_free(product, 31 * 8)
func crypto.x25519.finverse[out: ptr, input: ptr] : void
let c: ptr = mem.alloc(16 * 8)
for i in 0..16
mem.write64(c + i * 8, mem.read64(input + i * 8))
let i = 253
while i >= 0
crypto.x25519.fmul(c, c, c)
if i != 2 && i != 4
crypto.x25519.fmul(c, c, input)
i = i - 1
for i in 0..16
mem.write64(out + i * 8, mem.read64(c + i * 8))
mem.zero_and_free(c, 16 * 8)
func crypto.x25519.swap[p: ptr, q: ptr, bit: i64] : void
for i in 0..16
let t: i64 = (-bit) & (mem.read64(p + i * 8) ^ mem.read64(q + i * 8))
mem.write64(p + i * 8, mem.read64(p + i * 8) ^ t)
mem.write64(q + i * 8, mem.read64(q + i * 8) ^ t)
func crypto.x25519.unpack[out: ptr, input: ptr] : void
for i in 0..16
mem.write64(out + i * 8, input[i * 2] + (input[i * 2 + 1] << 8))
mem.write64(out + 8 * 15, mem.read64(out + 8 * 15) & 0x7fff)
func crypto.x25519.pack[out: ptr, input: ptr] : void
let t: ptr = mem.alloc(16 * 8)
for i in 0..16
mem.write64(t + i * 8, mem.read64(input + i * 8))
let m: ptr = mem.alloc(16 * 8)
crypto.x25519.carry(t)
crypto.x25519.carry(t)
crypto.x25519.carry(t)
for j in 0..2
mem.write64(m, mem.read64(t) - 0xffed)
for i in 1..15
mem.write64(m + i * 8, mem.read64(t + i * 8) - 0xffff - ((mem.read64(m + (i - 1) * 8) >> 16) & 1))
mem.write64(m + (i - 1) * 8, mem.read64(m + (i - 1) * 8) & 0xffff)
mem.write64(m + 15 * 8, mem.read64(t + 15 * 8) - 0x7fff - ((mem.read64(m + 14 * 8) >> 16) & 1))
let carry: i64 = (mem.read64(m + 15 * 8) >> 16) & 1
mem.write64(m + 14 * 8, mem.read64(m + 14 * 8) & 0xffff)
crypto.x25519.swap(t, m, 1 - carry)
for i in 0..16
let v: i64 = mem.read64(t + i * 8)
out[i * 2] = v & 0xff
out[i * 2 + 1] = (v >> 8) & 0xff
mem.zero_and_free(t, 16 * 8)
mem.zero_and_free(m, 16 * 8)
func crypto.x25519.scalarmult[scalar: ptr, point: ptr] : ptr
let clamped: ptr = mem.alloc(32)
let a: ptr = mem.alloc(16 * 8)
let b: ptr = mem.alloc(16 * 8)
let c: ptr = mem.alloc(16 * 8)
let d: ptr = mem.alloc(16 * 8)
let e: ptr = mem.alloc(16 * 8)
let f: ptr = mem.alloc(16 * 8)
let x: ptr = mem.alloc(16 * 8)
// "A" parameter in Curve25519
// 121665 = (486662 - 2)/4
let magic: ptr = mem.alloc(16 * 8)
mem.zero(magic, 16 * 8)
mem.write64(magic, 0xdb41)
mem.write64(magic + 8, 1)
// copy and clamp scalar
for i in 0..32
clamped[i] = scalar[i]
clamped[0] = clamped[0] & 0xf8
clamped[31] = (clamped[31] & 0x7f) | 0x40
// load point
crypto.x25519.unpack(x, point)
// initialize ladder state
for i in 0..16
mem.write64(a + i * 8, 0)
mem.write64(b + i * 8, mem.read64(x + i * 8))
mem.write64(c + i * 8, 0)
mem.write64(d + i * 8, 0)
mem.write64(a, 1)
mem.write64(d, 1)
let i = 254
while i >= 0
let bit: i64 = (clamped[i >> 3] >> (i & 7)) & 1
crypto.x25519.swap(a, b, bit)
crypto.x25519.swap(c, d, bit)
crypto.x25519.fadd(e, a, c)
crypto.x25519.fsub(a, a, c)
crypto.x25519.fadd(c, b, d)
crypto.x25519.fsub(b, b, d)
crypto.x25519.fmul(d, e, e)
crypto.x25519.fmul(f, a, a)
crypto.x25519.fmul(a, c, a)
crypto.x25519.fmul(c, b, e)
crypto.x25519.fadd(e, a, c)
crypto.x25519.fsub(a, a, c)
crypto.x25519.fmul(b, a, a)
crypto.x25519.fsub(c, d, f)
crypto.x25519.fmul(a, c, magic)
crypto.x25519.fadd(a, a, d)
crypto.x25519.fmul(c, c, a)
crypto.x25519.fmul(a, d, f)
crypto.x25519.fmul(d, b, x)
crypto.x25519.fmul(b, e, e)
crypto.x25519.swap(a, b, bit)
crypto.x25519.swap(c, d, bit)
i = i - 1
crypto.x25519.finverse(c, c)
crypto.x25519.fmul(a, a, c)
let out: ptr = mem.alloc(32)
crypto.x25519.pack(out, a)
mem.zero_and_free(clamped, 32)
mem.zero_and_free(a, 16 * 8)
mem.zero_and_free(b, 16 * 8)
mem.zero_and_free(c, 16 * 8)
mem.zero_and_free(d, 16 * 8)
mem.zero_and_free(e, 16 * 8)
mem.zero_and_free(f, 16 * 8)
mem.zero_and_free(x, 16 * 8)
mem.free(magic)
return out

View File

@@ -1,810 +0,0 @@
func panic[msg: str] : void
io.print("PANIC: ")
io.println(msg)
(0 / 0) // crashes program which is kinda better since you get a gdb backtrace
os.exit(1)
const MEM_BLOCK_SIZE = 24
struct mem.Block
size: i64
free: bool
next: mem.Block
func mem._align[x: i64] : i64
return (x + 7) & -8
func mem._split_block[blk: mem.Block, needed: i64] : void
if blk->size >= needed + MEM_BLOCK_SIZE + 8
let new_blk: mem.Block = blk + MEM_BLOCK_SIZE + needed
new_blk->size = blk->size - needed - MEM_BLOCK_SIZE
new_blk->free = true
new_blk->next = blk->next
blk->size = needed
blk->next = new_blk
if mem.read64(_builtin_heap_tail()) == blk
mem.write64(_builtin_heap_tail(), new_blk)
func mem._request_space[size: i64] : mem.Block
let blk: mem.Block = _builtin_syscall(SYS_brk, 0)
let new_brk: ptr = blk + MEM_BLOCK_SIZE + size
let result: ptr = _builtin_syscall(SYS_brk, new_brk)
if result != new_brk
return 0
blk->size = size
blk->free = false
blk->next = 0
let tail: mem.Block = mem.read64(_builtin_heap_tail())
if tail
tail->next = blk
mem.write64(_builtin_heap_tail(), blk)
return blk
func mem._coalesce[] : void
let cur: mem.Block = mem.read64(_builtin_heap_head())
while cur && cur->next
let next: mem.Block = cur->next
if cur->free && next->free
cur->size = cur->size + MEM_BLOCK_SIZE + next->size
cur->next = next->next
if !cur->next
mem.write64(_builtin_heap_tail(), cur)
else
cur = cur->next
func mem.alloc[size: i64] : ptr
if size == 0
return 0
size = mem._align(size)
let cur: mem.Block = mem.read64(_builtin_heap_head())
while cur
if cur->free && cur->size >= size
mem._split_block(cur, size)
cur->free = false
return cur + MEM_BLOCK_SIZE
cur = cur->next
let blk: mem.Block = mem._request_space(size)
if !blk
return 0
if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk)
return blk + MEM_BLOCK_SIZE
func mem.free[x: ptr] : void
if !x
return 0
let blk: mem.Block = x - MEM_BLOCK_SIZE
blk->free = true
mem._coalesce()
func mem.realloc[x: ptr, new_size: i64] : ptr
if !x
return mem.alloc(new_size)
if new_size == 0
mem.free(x)
return 0
new_size = mem._align(new_size)
let blk: mem.Block = x - MEM_BLOCK_SIZE
if blk->size >= new_size
mem._split_block(blk, new_size)
return x
let next: mem.Block = blk->next
if next && next->free
let combined: i64 = blk->size + MEM_BLOCK_SIZE + next->size
if combined >= new_size
blk->size = combined
blk->next = next->next
if !blk->next
mem.write64(_builtin_heap_tail(), blk)
mem._split_block(blk, new_size)
return x
let new_ptr: ptr = mem.alloc(new_size)
if !new_ptr
return 0
for i in 0..blk->size
new_ptr[i] = x[i]
mem.free(x)
return new_ptr
func mem.zero_and_free[x: ptr, size: i64] : void
mem.zero(x, size)
mem.free(x)
func mem.zero[x: ptr, size: i64] : void
for i in 0..size
x[i] = 0
func mem.read8[x: ptr] : u8
return x[0]
func mem.read16[x: ptr] : i64
return x[0] | (x[1] << 8)
func mem.read32[x: ptr] : i64
return x[0] | (x[1] << 8) | (x[2] << 16) | (x[3] << 24)
func mem.read64[x: ptr] : i64
return _builtin_read64(x)
func mem.write32[x: ptr, d: i64] : void
x[0] = d & 0xff
x[1] = (d >> 8) & 0xff
x[2] = (d >> 16) & 0xff
x[3] = (d >> 24) & 0xff
func mem.write64[x: ptr, d: i64] : void
_builtin_set64(x, d)
func io.print_sized[x: str, size: i64] : void
_builtin_syscall(SYS_write, 1, x, size)
func io.print[x: str] : void
io.print_sized(x, str.len(x))
func io.println[x: str] : void
io.print(x)
io.print("\n")
func io.print_char[x: u8] : void
io.print_sized(^x, 1)
func io.print_i64[x: i64] : void
let s: str = str.from_i64(x)
io.print(s)
mem.free(s)
func io.print_i64_hex[x: i64] : void
let s: str = str.hex_from_i64(x)
io.print(s)
mem.free(s)
func io.println_i64[x: i64] : void
let s: str = str.from_i64(x)
io.println(s)
mem.free(s)
func io.read_char[] : u8
let c: u8 = 0
_builtin_syscall(SYS_read, 0, ^c, 1)
return c
func io.read_line[]: str
let MAX_SIZE = 60000
let buffer: str = mem.alloc(MAX_SIZE + 1)
let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE)
if n < 0
mem.free(buffer)
return ""
buffer[n] = 0
return buffer
func io.read_file[path: str] : str
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd <= 0
panic("failed to open file")
let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0)
let buffer: str = mem.alloc(size + 1)
let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size)
_builtin_syscall(SYS_close, fd)
buffer[n] = 0
return buffer
struct io.Buffer
data: ptr
size: i64
func io.read_binary_file[path: str] : io.Buffer
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0
panic("failed to open file")
let buffer: io.Buffer = new io.Buffer
buffer->size = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0)
buffer->data = mem.alloc(buffer->size)
_builtin_syscall(SYS_read, fd, buffer->data, buffer->size)
_builtin_syscall(SYS_close, fd)
return buffer
func io.write_file[path: str, content: str] : void
let fd: ptr = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644)
if fd < 0
panic("failed to open file")
_builtin_syscall(SYS_write, fd, content, str.len(content))
_builtin_syscall(SYS_close, fd)
func str.len[s: str] : i64
let i = 0
while s[i]
i = i + 1
return i
func str.copy[s: str] : str
let size: i64 = str.len(s) + 1
let dup: str = mem.alloc(size)
for i in 0..size
dup[i] = s[i]
return dup
func str.equal[a: str, b: str] : bool
let i = 0
while a[i] != 0 && b[i] != 0
if a[i] != b[i]
return false
i = i + 1
return a[i] == b[i]
func str.is_whitespace[x: u8] : bool
return x == ' ' || x == 10 || x == 13 || x == 9
func str.is_digit[x: u8] : bool
return x >= '0' && x <= '9'
func str.is_hex_digit[x: u8] : bool
return (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')
func str.is_lowercase[x: u8] : bool
return x >= 'a' && x <= 'z'
func str.is_uppercase[x: u8] : bool
return x >= 'A' && x <= 'Z'
func str.is_letter[x: u8] : bool
return str.is_uppercase(x) || str.is_lowercase(x)
func str.is_alphanumeric[x: u8] : bool
return str.is_letter(x) || str.is_digit(x)
func str.concat[a: str, b: str] : str
let a_len: i64 = str.len(a)
let b_len: i64 = str.len(b)
let out: str = mem.alloc(a_len + b_len + 1)
for i in 0..a_len
out[i] = a[i]
for i in 0..b_len
out[a_len + i] = b[i]
out[a_len + b_len] = 0
return out
func str.find[haystack: str, needle: str] : i64
let haystack_len: i64 = str.len(haystack)
let needle_len: i64 = str.len(needle)
if needle_len == 0
return 0
for i in 0..(haystack_len - needle_len + 1)
let match: bool = true
for j in 0..needle_len
if haystack[i + j] != needle[j]
match = false
break
if match
return i
return -1
func str.substr[s: str, start: i64, length: i64] : str
if start < 0 || length < 0 || start + length > str.len(s)
panic("str.substr out of bounds")
let out: str = mem.alloc(length + 1)
for i in 0..length
out[i] = s[start + i]
out[length] = 0
return out
func str.trim[s: str] : str
let len: i64 = str.len(s)
if len == 0
return ""
let start = 0
let end: i64 = len - 1
while start <= end && str.is_whitespace(s[start])
start = start + 1
while end >= start && str.is_whitespace(s[end])
end = end - 1
return str.substr(s, start, end - start + 1)
func str.split[haystack: str, needle: str]: Array
let haystack_len: i64 = str.len(haystack)
let needle_len: i64 = str.len(needle)
let result: Array = []
if !needle_len
if !haystack_len
return result
else
for i in 0..haystack_len
array.push(result, str.substr(haystack, i, 1))
return result
let start = 0
let i = 0
while i < haystack_len
if i <= haystack_len - needle_len
let match: bool = true
for j in 0..needle_len
if haystack[i + j] != needle[j]
match = false
break
if match
array.push(result, str.substr(haystack, start, i - start))
start = i + needle_len
i = i + needle_len
continue
i = i + 1
array.push(result, str.substr(haystack, start, haystack_len - start))
return result
func str.reverse[s: str] : str
let len: i64 = str.len(s)
let out: str = mem.alloc(len + 1)
for i in 0..len
out[i] = s[len - i - 1]
out[len] = 0
return out
// not sure this covers all wacky edge cases
func str.from_i64[n: i64] : str
if n == 0
return str.copy("0")
let neg: bool = n < 0
if neg
n = -n
let buf: str = mem.alloc(21) // enough to fit -MAX_I64
let i = 0
while n > 0
let d: u8 = n % 10
buf[i] = '0' + d
n = n / 10
i = i + 1
if neg
buf[i] = '-'
i = i + 1
buf[i] = 0
let s: str = str.reverse(buf)
mem.free(buf)
return s
func str.hex_from_i64[n: i64] : str
let hex_chars: str = "0123456789abcdef"
if n == 0
let out: str = mem.alloc(2)
out[0] = '0'
out[1] = 0
return out
let buf: str = mem.alloc(17)
let len = 0
while n > 0
buf[len] = hex_chars[n & 15]
n = n >> 4
len = len + 1
let out: str = mem.alloc(len + 1)
let j = 0
while j < len
out[j] = buf[len - 1 - j]
j = j + 1
out[len] = 0
return out
func str.from_char[c: u8] : str
let s: str = mem.alloc(2)
s[0] = c
s[1] = 0
return s
func str.parse_i64[s: str] : i64
let len: i64 = str.len(s)
let i = 0
let sign = 1
if i < len && s[i] == '-'
sign = -1
i = i + 1
let num = 0
while i < len
let d: u8 = s[i]
if d < '0' || d > '9'
break
num = num * 10 + (d - '0')
i = i + 1
return num * sign
func str.hex_encode[s: str, s_len: i64] : str
let hex_chars: str = "0123456789abcdef"
let j = 0
let out: str = mem.alloc(s_len * 2 + 1)
for i in 0..s_len
let high: u8 = (s[i] >> 4) & 15
let low: u8 = s[i] & 15
out[j] = hex_chars[high]
out[j + 1] = hex_chars[low]
j = j + 2
out[j] = 0
return out
func str._hex_digit_to_int[d: u8] : i64
if d >= 'a' && d <= 'f'
return d - 'a' + 10
if d >= 'A' && d <= 'F'
return d - 'A' + 10
return d - '0'
func str.hex_decode[s: str] : str
let s_len: i64 = str.len(s)
let i = 0
let j = 0
let out: str = mem.alloc(s_len / 2 + 1)
while i < s_len
out[j] = str._hex_digit_to_int(s[i]) * 16 + str._hex_digit_to_int(s[i + 1])
i = i + 2
j = j + 1
out[j] = 0
return out
func math.gcd[a: i64, b: i64] : i64
while b != 0
let tmp: i64 = b
b = a % b
a = tmp
return a
func math.min[a: i64, b: i64] : i64
if a < b
return a
return b
func math.max[a: i64, b: i64] : i64
if a > b
return a
return b
func math.abs[n: i64] : i64
if n < 0
return -n
return n
func math.sign[n: i64] : i64
if n < 0
return -1
else if n > 0
return 1
else
return 0
func math.pow[b: i64, e: i64] : i64
let out = 1
for i in 0..e
out = out * b
return out
func math.lcm[a: i64, b: i64] : i64
return (a * b) / math.gcd(a, b)
func math.isqrt[n: i64] : i64
if n < 0
panic("negative number passed to math.isqrt")
if n == 0 || n == 1
return n
let guess: i64 = n
let next_guess: i64 = (guess + n / guess) / 2
while next_guess < guess
guess = next_guess
next_guess = (guess + n / guess) / 2
return guess
func math.is_prime[n: i64]: bool
if n <= 1
return false
if n == 2 || n == 3
return true
if n % 2 == 0 || n % 3 == 0
return false
let i = 5
while i * i <= n
if n % i == 0 || n % (i + 2) == 0
return false
i = i + 6
return true
struct Array
data: ptr
size: i64
capacity: i64
func array.new[] : Array
return new Array
func array.nth[xs: Array, n: i64] : i64
if n < 0 || n >= xs->size
panic("array.nth out of bounds")
return mem.read64(xs->data + n * 8)
func array.set[xs: Array, n: i64, x: i64] : void
if n < 0 || n >= xs->size
panic("array.set out of bounds")
mem.write64(xs->data + n * 8, x)
func array.push[xs: Array, x: i64] : void
if xs->size == xs->capacity
let new_capacity = 4
if xs->capacity != 0
new_capacity = xs->capacity * 2
xs->data = mem.realloc(xs->data, new_capacity * 8)
xs->capacity = new_capacity
mem.write64(xs->data + xs->size * 8, x)
xs->size = xs->size + 1
func array.free[xs: Array] : void
if xs->data != 0
mem.free(xs->data)
mem.free(xs)
func array.pop[xs: Array] : i64
if xs->size == 0
panic("array.pop on empty array")
let x: i64 = array.nth(xs, xs->size - 1)
xs->size = xs->size - 1
return x
func array.slice[xs: Array, start: i64, length: i64] : Array
if start < 0 || length < 0 || start + length > xs->size
panic("array.slice out of bounds")
let new_array: Array = []
for i in 0..length
array.push(new_array, array.nth(xs, start + i))
return new_array
func array.concat[a: Array, b: Array] : Array
let new_array: Array = []
for i in 0..a->size
array.push(new_array, array.nth(a, i))
for i in 0..b->size
array.push(new_array, array.nth(b, i))
return new_array
func alg.quicksort[arr: Array] : void
alg._do_quicksort(arr, 0, arr->size - 1)
func alg._do_quicksort[arr: Array, low: i64, high: i64] : void
if low < high
let i: i64 = alg._partition(arr, low, high)
alg._do_quicksort(arr, low, i - 1)
alg._do_quicksort(arr, i + 1, high)
func alg._partition[arr: Array, low: i64, high: i64] : i64
let pivot: i64 = array.nth(arr, high)
let i: i64 = low - 1
for j in (low)..high
if array.nth(arr, j) <= pivot
i = i + 1
let temp: i64 = array.nth(arr ,i)
array.set(arr, i, array.nth(arr, j))
array.set(arr, j, temp)
let temp: i64 = array.nth(arr, i + 1)
array.set(arr, i + 1, array.nth(arr, high))
array.set(arr, high, temp)
return i + 1
func alg.count[arr: Array, item: i64] : i64
let count = 0
for i in 0..arr->size
if array.nth(arr, i) == item
count = count + 1
return count
func alg.map[arr: Array, fn: ptr] : Array
let out: Array = []
for i in 0..arr->size
array.push(out, fn(array.nth(arr, i)))
return out
func alg.filter[arr: Array, fn: ptr] : Array
let out: Array = []
for i in 0..arr->size
if fn(array.nth(arr, i))
array.push(out, array.nth(arr, i))
return out
func alg.reduce[arr: Array, fn: ptr, acc: i64] : i64
for i in 0..arr->size
acc = fn(acc, array.nth(arr, i))
return acc
func os.exit[code: i64] : void
_builtin_syscall(SYS_exit, code)
func os.urandom[n: i64]: ptr
let buffer: ptr = mem.alloc(n)
let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0)
_builtin_syscall(SYS_read, fd, buffer, n)
_builtin_syscall(SYS_close, fd)
return buffer
func os.urandom_i64[]: i64
let buffer: ptr = os.urandom(8)
let n: i64 = mem.read64(buffer)
mem.free(buffer)
return n
struct os.Timeval
tv_sec: i64
tv_usec: i64
func os.time[] : i64
let tv: os.Timeval = new os.Timeval
_builtin_syscall(SYS_gettimeofday, tv, 0)
mem.free(tv)
return tv->tv_sec * 1000 + tv->tv_usec / 1000
// voodoo magic
func os.shell[command: str] : i64
let pid: i64 = _builtin_syscall(SYS_fork)
if pid == 0
// leaky but not sure where can i free it
let argv: Array = ["sh", "-c", command, 0]
_builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ())
_builtin_syscall(SYS_exit, 1)
else
let status = 0
let wp: i64 = _builtin_syscall(SYS_wait4, pid, ^status, 0, 0)
if wp == -1
return -1
let st: i64 = status & 0xffffffff
if (st & 0x7f) == 0
return (st >> 8) & 0xff
else
return -(st & 0x7f)
func os.listdir[path: str] : Array
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0
return []
let files: Array = []
let buf: ptr = mem.alloc(1024)
while true
let n: i64 = _builtin_syscall(SYS_getdents64, fd, buf, 1024)
if n <= 0
break
let pos = 0
while pos < n
let len: i64 = mem.read16(buf + pos + 16)
let name: str = buf + pos + 19
if name[0]
let skip: bool = false
// skip if name is exactly '.' or '..'
if name[0] == '.'
if name[1] == 0
skip = true
else if name[1] == '.'
if name[2] == 0
skip = true
if !skip
array.push(files, str.copy(name))
pos = pos + len
mem.free(buf)
_builtin_syscall(SYS_close, fd)
return files
func net.listen[packed_host: i64, port: i64] : i64
let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0)
if s < 0
return -1
// setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
let optval = 1
if _builtin_syscall(SYS_setsockopt, s, 1, 2, ^optval, 8) < 0
_builtin_syscall(SYS_close, s)
return -1
let sa: ptr = mem.alloc(16)
mem.zero(sa, 16)
sa[0] = 2
sa[1] = 0
sa[2] = (port >> 8) & 255
sa[3] = port & 255
sa[4] = (packed_host >> 24) & 255
sa[5] = (packed_host >> 16) & 255
sa[6] = (packed_host >> 8) & 255
sa[7] = packed_host & 255
if _builtin_syscall(SYS_bind, s, sa, 16) < 0
_builtin_syscall(SYS_close, s)
mem.free(sa)
return -1
mem.free(sa)
if _builtin_syscall(SYS_listen, s, 1) < 0
_builtin_syscall(SYS_close, s)
return -1
return s
func net.connect[packed_host: i64, port: i64] : i64
let s: i64 = _builtin_syscall(SYS_socket, 2, 1, 0)
if s < 0
return -1
let sa: ptr = mem.alloc(16)
mem.zero(sa, 16)
sa[0] = 2
sa[1] = 0
sa[2] = (port >> 8) & 255
sa[3] = port & 255
sa[4] = (packed_host >> 24) & 255
sa[5] = (packed_host >> 16) & 255
sa[6] = (packed_host >> 8) & 255
sa[7] = packed_host & 255
if _builtin_syscall(SYS_connect, s, sa, 16) < 0
mem.free(sa)
_builtin_syscall(SYS_close, s)
return -1
mem.free(sa)
return s
func net.accept[s: i64] : i64
return _builtin_syscall(SYS_accept, s, 0, 0)
func net.send[s: i64, data: str, size: i64] : void
_builtin_syscall(SYS_sendto, s, data, size, 0, 0, 0)
func net.read[s: i64, buffer: ptr, size: i64] : i64
return _builtin_syscall(SYS_read, s, buffer, size)
func net.close[s: i64] : void
_builtin_syscall(SYS_close, s)
func net.pack_addr[a: i64, b: i64, c: i64, d: i64] : i64
return (a << 24) | (b << 16) | (c << 8) | d

View File

@@ -1,381 +0,0 @@
// https://github.com/torvalds/linux/blob/19272b37aa4f83ca52bdf9c16d5d81bdd1354494/arch/x86/entry/syscalls/syscall_64.tbl
const SYS_read = 0
const SYS_write = 1
const SYS_open = 2
const SYS_close = 3
const SYS_stat = 4
const SYS_fstat = 5
const SYS_lstat = 6
const SYS_poll = 7
const SYS_lseek = 8
const SYS_mmap = 9
const SYS_mprotect = 10
const SYS_munmap = 11
const SYS_brk = 12
const SYS_rt_sigaction = 13
const SYS_rt_sigprocmask = 14
const SYS_rt_sigreturn = 15
const SYS_ioctl = 16
const SYS_pread64 = 17
const SYS_pwrite64 = 18
const SYS_readv = 19
const SYS_writev = 20
const SYS_access = 21
const SYS_pipe = 22
const SYS_select = 23
const SYS_sched_yield = 24
const SYS_mremap = 25
const SYS_msync = 26
const SYS_mincore = 27
const SYS_madvise = 28
const SYS_shmget = 29
const SYS_shmat = 30
const SYS_shmctl = 31
const SYS_dup = 32
const SYS_dup2 = 33
const SYS_pause = 34
const SYS_nanosleep = 35
const SYS_getitimer = 36
const SYS_alarm = 37
const SYS_setitimer = 38
const SYS_getpid = 39
const SYS_sendfile = 40
const SYS_socket = 41
const SYS_connect = 42
const SYS_accept = 43
const SYS_sendto = 44
const SYS_recvfrom = 45
const SYS_sendmsg = 46
const SYS_recvmsg = 47
const SYS_shutdown = 48
const SYS_bind = 49
const SYS_listen = 50
const SYS_getsockname = 51
const SYS_getpeername = 52
const SYS_socketpair = 53
const SYS_setsockopt = 54
const SYS_getsockopt = 55
const SYS_clone = 56
const SYS_fork = 57
const SYS_vfork = 58
const SYS_execve = 59
const SYS_exit = 60
const SYS_wait4 = 61
const SYS_kill = 62
const SYS_uname = 63
const SYS_semget = 64
const SYS_semop = 65
const SYS_semctl = 66
const SYS_shmdt = 67
const SYS_msgget = 68
const SYS_msgsnd = 69
const SYS_msgrcv = 70
const SYS_msgctl = 71
const SYS_fcntl = 72
const SYS_flock = 73
const SYS_fsync = 74
const SYS_fdatasync = 75
const SYS_truncate = 76
const SYS_ftruncate = 77
const SYS_getdents = 78
const SYS_getcwd = 79
const SYS_chdir = 80
const SYS_fchdir = 81
const SYS_rename = 82
const SYS_mkdir = 83
const SYS_rmdir = 84
const SYS_creat = 85
const SYS_link = 86
const SYS_unlink = 87
const SYS_symlink = 88
const SYS_readlink = 89
const SYS_chmod = 90
const SYS_fchmod = 91
const SYS_chown = 92
const SYS_fchown = 93
const SYS_lchown = 94
const SYS_umask = 95
const SYS_gettimeofday = 96
const SYS_getrlimit = 97
const SYS_getrusage = 98
const SYS_sysinfo = 99
const SYS_times = 100
const SYS_ptrace = 101
const SYS_getuid = 102
const SYS_syslog = 103
const SYS_getgid = 104
const SYS_setuid = 105
const SYS_setgid = 106
const SYS_geteuid = 107
const SYS_getegid = 108
const SYS_setpgid = 109
const SYS_getppid = 110
const SYS_getpgrp = 111
const SYS_setsid = 112
const SYS_setreuid = 113
const SYS_setregid = 114
const SYS_getgroups = 115
const SYS_setgroups = 116
const SYS_setresuid = 117
const SYS_getresuid = 118
const SYS_setresgid = 119
const SYS_getresgid = 120
const SYS_getpgid = 121
const SYS_setfsuid = 122
const SYS_setfsgid = 123
const SYS_getsid = 124
const SYS_capget = 125
const SYS_capset = 126
const SYS_rt_sigpending = 127
const SYS_rt_sigtimedwait = 128
const SYS_rt_sigqueueinfo = 129
const SYS_rt_sigsuspend = 130
const SYS_sigaltstack = 131
const SYS_utime = 132
const SYS_mknod = 133
const SYS_uselib = 134
const SYS_personality = 135
const SYS_ustat = 136
const SYS_statfs = 137
const SYS_fstatfs = 138
const SYS_sysfs = 139
const SYS_getpriority = 140
const SYS_setpriority = 141
const SYS_sched_setparam = 142
const SYS_sched_getparam = 143
const SYS_sched_setscheduler = 144
const SYS_sched_getscheduler = 145
const SYS_sched_get_priority_max = 146
const SYS_sched_get_priority_min = 147
const SYS_sched_rr_get_interval = 148
const SYS_mlock = 149
const SYS_munlock = 150
const SYS_mlockall = 151
const SYS_munlockall = 152
const SYS_vhangup = 153
const SYS_modify_ldt = 154
const SYS_pivot_root = 155
const SYS__sysctl = 156
const SYS_prctl = 157
const SYS_arch_prctl = 158
const SYS_adjtimex = 159
const SYS_setrlimit = 160
const SYS_chroot = 161
const SYS_sync = 162
const SYS_acct = 163
const SYS_settimeofday = 164
const SYS_mount = 165
const SYS_umount2 = 166
const SYS_swapon = 167
const SYS_swapoff = 168
const SYS_reboot = 169
const SYS_sethostname = 170
const SYS_setdomainname = 171
const SYS_iopl = 172
const SYS_ioperm = 173
const SYS_create_module = 174
const SYS_init_module = 175
const SYS_delete_module = 176
const SYS_get_kernel_syms = 177
const SYS_query_module = 178
const SYS_quotactl = 179
const SYS_nfsservctl = 180
const SYS_getpmsg = 181
const SYS_putpmsg = 182
const SYS_afs_syscall = 183
const SYS_tuxcall = 184
const SYS_security = 185
const SYS_gettid = 186
const SYS_readahead = 187
const SYS_setxattr = 188
const SYS_lsetxattr = 189
const SYS_fsetxattr = 190
const SYS_getxattr = 191
const SYS_lgetxattr = 192
const SYS_fgetxattr = 193
const SYS_listxattr = 194
const SYS_llistxattr = 195
const SYS_flistxattr = 196
const SYS_removexattr = 197
const SYS_lremovexattr = 198
const SYS_fremovexattr = 199
const SYS_tkill = 200
const SYS_time = 201
const SYS_futex = 202
const SYS_sched_setaffinity = 203
const SYS_sched_getaffinity = 204
const SYS_set_thread_area = 205
const SYS_io_setup = 206
const SYS_io_destroy = 207
const SYS_io_getevents = 208
const SYS_io_submit = 209
const SYS_io_cancel = 210
const SYS_get_thread_area = 211
const SYS_lookup_dcookie = 212
const SYS_epoll_create = 213
const SYS_epoll_ctl_old = 214
const SYS_epoll_wait_old = 215
const SYS_remap_file_pages = 216
const SYS_getdents64 = 217
const SYS_set_tid_address = 218
const SYS_restart_syscall = 219
const SYS_semtimedop = 220
const SYS_fadvise64 = 221
const SYS_timer_create = 222
const SYS_timer_settime = 223
const SYS_timer_gettime = 224
const SYS_timer_getoverrun = 225
const SYS_timer_delete = 226
const SYS_clock_settime = 227
const SYS_clock_gettime = 228
const SYS_clock_getres = 229
const SYS_clock_nanosleep = 230
const SYS_exit_group = 231
const SYS_epoll_wait = 232
const SYS_epoll_ctl = 233
const SYS_tgkill = 234
const SYS_utimes = 235
const SYS_vserver = 236
const SYS_mbind = 237
const SYS_set_mempolicy = 238
const SYS_get_mempolicy = 239
const SYS_mq_open = 240
const SYS_mq_unlink = 241
const SYS_mq_timedsend = 242
const SYS_mq_timedreceive = 243
const SYS_mq_notify = 244
const SYS_mq_getsetattr = 245
const SYS_kexec_load = 246
const SYS_waitid = 247
const SYS_add_key = 248
const SYS_request_key = 249
const SYS_keyctl = 250
const SYS_ioprio_set = 251
const SYS_ioprio_get = 252
const SYS_inotify_init = 253
const SYS_inotify_add_watch = 254
const SYS_inotify_rm_watch = 255
const SYS_migrate_pages = 256
const SYS_openat = 257
const SYS_mkdirat = 258
const SYS_mknodat = 259
const SYS_fchownat = 260
const SYS_futimesat = 261
const SYS_newfstatat = 262
const SYS_unlinkat = 263
const SYS_renameat = 264
const SYS_linkat = 265
const SYS_symlinkat = 266
const SYS_readlinkat = 267
const SYS_fchmodat = 268
const SYS_faccessat = 269
const SYS_pselect6 = 270
const SYS_ppoll = 271
const SYS_unshare = 272
const SYS_set_robust_list = 273
const SYS_get_robust_list = 274
const SYS_splice = 275
const SYS_tee = 276
const SYS_sync_file_range = 277
const SYS_vmsplice = 278
const SYS_move_pages = 279
const SYS_utimensat = 280
const SYS_epoll_pwait = 281
const SYS_signalfd = 282
const SYS_timerfd_create = 283
const SYS_eventfd = 284
const SYS_fallocate = 285
const SYS_timerfd_settime = 286
const SYS_timerfd_gettime = 287
const SYS_accept4 = 288
const SYS_signalfd4 = 289
const SYS_eventfd2 = 290
const SYS_epoll_create1 = 291
const SYS_dup3 = 292
const SYS_pipe2 = 293
const SYS_inotify_init1 = 294
const SYS_preadv = 295
const SYS_pwritev = 296
const SYS_rt_tgsigqueueinfo = 297
const SYS_perf_event_open = 298
const SYS_recvmmsg = 299
const SYS_fanotify_init = 300
const SYS_fanotify_mark = 301
const SYS_prlimit64 = 302
const SYS_name_to_handle_at = 303
const SYS_open_by_handle_at = 304
const SYS_clock_adjtime = 305
const SYS_syncfs = 306
const SYS_sendmmsg = 307
const SYS_setns = 308
const SYS_getcpu = 309
const SYS_process_vm_readv = 310
const SYS_process_vm_writev = 311
const SYS_kcmp = 312
const SYS_finit_module = 313
const SYS_sched_setattr = 314
const SYS_sched_getattr = 315
const SYS_renameat2 = 316
const SYS_seccomp = 317
const SYS_getrandom = 318
const SYS_memfd_create = 319
const SYS_kexec_file_load = 320
const SYS_bpf = 321
const SYS_execveat = 322
const SYS_userfaultfd = 323
const SYS_membarrier = 324
const SYS_mlock2 = 325
const SYS_copy_file_range = 326
const SYS_preadv2 = 327
const SYS_pwritev2 = 328
const SYS_pkey_mprotect = 329
const SYS_pkey_alloc = 330
const SYS_pkey_free = 331
const SYS_statx = 332
const SYS_io_pgetevents = 333
const SYS_rseq = 334
const SYS_uretprobe = 335
const SYS_pidfd_send_signal = 424
const SYS_io_uring_setup = 425
const SYS_io_uring_enter = 426
const SYS_io_uring_register = 427
const SYS_open_tree = 428
const SYS_move_mount = 429
const SYS_fsopen = 430
const SYS_fsconfig = 431
const SYS_fsmount = 432
const SYS_fspick = 433
const SYS_pidfd_open = 434
const SYS_clone3 = 435
const SYS_close_range = 436
const SYS_openat2 = 437
const SYS_pidfd_getfd = 438
const SYS_faccessat2 = 439
const SYS_process_madvise = 440
const SYS_epoll_pwait2 = 441
const SYS_mount_setattr = 442
const SYS_quotactl_fd = 443
const SYS_landlock_create_ruleset = 444
const SYS_landlock_add_rule = 445
const SYS_landlock_restrict_self = 446
const SYS_memfd_secret = 447
const SYS_process_mrelease = 448
const SYS_futex_waitv = 449
const SYS_set_mempolicy_home_node = 450
const SYS_cachestat = 451
const SYS_fchmodat2 = 452
const SYS_map_shadow_stack = 453
const SYS_futex_wake = 454
const SYS_futex_wait = 455
const SYS_futex_requeue = 456
const SYS_statmount = 457
const SYS_listmount = 458
const SYS_lsm_get_self_attr = 459
const SYS_lsm_set_self_attr = 460
const SYS_lsm_list_modules = 461
const SYS_mseal = 462
const SYS_setxattrat = 463
const SYS_getxattrat = 464
const SYS_listxattrat = 465
const SYS_removexattrat = 466
const SYS_open_tree_attr = 467

167
src/symbol_table.rs Normal file
View File

@@ -0,0 +1,167 @@
use std::collections::HashMap;
use crate::{
parser::{Params, Stmt},
tokenizer::{ZernError, error},
};
pub struct StructField {
pub offset: usize,
pub field_type: String,
}
#[derive(Clone)]
pub enum FnParams {
Normal(Vec<String>),
Variadic,
}
#[derive(Clone)]
pub struct FnType {
pub return_type: String,
pub params: FnParams,
}
impl FnType {
fn new(return_type: &str, params: Vec<&str>) -> FnType {
FnType {
return_type: return_type.to_string(),
params: FnParams::Normal(params.iter().map(|x| x.to_string()).collect()),
}
}
fn new_variadic(return_type: &str) -> FnType {
FnType {
return_type: return_type.to_string(),
params: FnParams::Variadic,
}
}
}
pub struct SymbolTable {
pub functions: HashMap<String, FnType>,
pub constants: HashMap<String, i64>,
pub structs: HashMap<String, HashMap<String, StructField>>,
}
impl SymbolTable {
pub fn new() -> SymbolTable {
SymbolTable {
functions: HashMap::from([
("_builtin_heap_head".into(), FnType::new("ptr", vec![])),
("_builtin_heap_tail".into(), FnType::new("ptr", vec![])),
("_builtin_read64".into(), FnType::new("i64", vec!["ptr"])),
(
"_builtin_set64".into(),
FnType::new("void", vec!["ptr", "i64"]),
),
("_builtin_cvtsi2sd".into(), FnType::new("f64", vec!["i64"])),
("_builtin_cvttsd2si".into(), FnType::new("i64", vec!["f64"])),
(
"_builtin_f64_to_f32".into(),
FnType::new("any", vec!["f64"]),
),
("_builtin_syscall".into(), FnType::new_variadic("i64")),
("_builtin_environ".into(), FnType::new("ptr", vec![])),
("_var_arg".into(), FnType::new("any", vec!["i64"])),
("_stackalloc".into(), FnType::new("ptr", vec!["i64"])),
]),
constants: HashMap::new(),
structs: HashMap::new(),
}
}
pub fn register_declaration(&mut self, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Const { name, value, neg } => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
let mut value = if value.lexeme.starts_with("0x") {
match u64::from_str_radix(&value.lexeme[2..], 16) {
Ok(v) => v,
Err(_) => return error!(value.loc, "failed to parse hex numeric constant"),
}
} else {
match value.lexeme.parse() {
Ok(v) => v,
Err(_) => return error!(value.loc, "failed to parse numeric constant"),
}
} as i64;
if *neg {
value = -value;
}
self.constants.insert(name.lexeme.clone(), value);
}
Stmt::Extern(name) => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
self.functions
.insert(name.lexeme.clone(), FnType::new_variadic("any"));
}
Stmt::Function {
name,
params,
return_types,
body: _,
exported: _,
} => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
let return_type = return_types
.iter()
.map(|t| t.lexeme.clone())
.collect::<Vec<_>>()
.join(",");
match params {
Params::Normal(params) => self.functions.insert(
name.lexeme.clone(),
FnType {
return_type,
params: FnParams::Normal(
params.iter().map(|x| x.var_type.lexeme.clone()).collect(),
),
},
),
Params::Variadic => self.functions.insert(
name.lexeme.clone(),
FnType {
return_type,
params: FnParams::Variadic,
},
),
};
}
Stmt::Struct { name, fields } => {
if self.is_name_defined(&name.lexeme) {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
}
let mut fields_map: HashMap<String, StructField> = HashMap::new();
let mut offset: usize = 0;
for field in fields {
fields_map.insert(
field.var_name.lexeme.clone(),
StructField {
offset,
field_type: field.var_type.lexeme.clone(),
},
);
offset += 8;
}
self.structs.insert(name.lexeme.clone(), fields_map);
}
_ => {}
}
Ok(())
}
fn is_name_defined(&self, s: &str) -> bool {
self.functions.contains_key(s)
|| self.constants.contains_key(s)
|| self.structs.contains_key(s)
}
}

View File

@@ -1,4 +1,9 @@
use std::{cmp::Ordering, fmt}; use std::{
cmp::Ordering,
collections::HashSet,
fmt, fs,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum TokenType { pub enum TokenType {
@@ -8,7 +13,9 @@ pub enum TokenType {
RightBracket, RightBracket,
Comma, Comma,
Plus, Plus,
PlusEqual,
Minus, Minus,
MinusEqual,
Star, Star,
Slash, Slash,
Mod, Mod,
@@ -19,7 +26,6 @@ pub enum TokenType {
BitOr, BitOr,
LogicalAnd, LogicalAnd,
LogicalOr, LogicalOr,
Pipe,
DoubleDot, DoubleDot,
ShiftLeft, ShiftLeft,
ShiftRight, ShiftRight,
@@ -34,13 +40,11 @@ pub enum TokenType {
LessEqual, LessEqual,
Identifier, Identifier,
String, StringLiteral,
Char, CharLiteral,
Number, IntLiteral,
True, FloatLiteral,
False,
KeywordLet,
KeywordConst, KeywordConst,
KeywordIf, KeywordIf,
KeywordElse, KeywordElse,
@@ -55,6 +59,9 @@ pub enum TokenType {
KeywordExport, KeywordExport,
KeywordStruct, KeywordStruct,
KeywordNew, KeywordNew,
KeywordAs,
KeywordTrue,
KeywordFalse,
Indent, Indent,
Dedent, Dedent,
@@ -106,7 +113,7 @@ pub struct Token {
pub loc: Loc, pub loc: Loc,
} }
pub struct Tokenizer { pub struct Tokenizer<'a> {
source: Vec<char>, source: Vec<char>,
tokens: Vec<Token>, tokens: Vec<Token>,
indent_stack: Vec<usize>, indent_stack: Vec<usize>,
@@ -114,10 +121,15 @@ pub struct Tokenizer {
start: usize, start: usize,
current: usize, current: usize,
loc: Loc, loc: Loc,
included_paths: &'a mut HashSet<PathBuf>,
} }
impl Tokenizer { impl<'a> Tokenizer<'a> {
pub fn new(filename: String, source: String) -> Tokenizer { pub fn new(
filename: String,
source: String,
included_paths: &'a mut HashSet<PathBuf>,
) -> Tokenizer<'a> {
Tokenizer { Tokenizer {
source: source.chars().collect(), source: source.chars().collect(),
tokens: vec![], tokens: vec![],
@@ -130,6 +142,7 @@ impl Tokenizer {
line: 1, line: 1,
column: 1, column: 1,
}, },
included_paths,
} }
} }
@@ -153,14 +166,22 @@ impl Tokenizer {
')' => self.add_token(TokenType::RightParen)?, ')' => self.add_token(TokenType::RightParen)?,
'[' => self.add_token(TokenType::LeftBracket)?, '[' => self.add_token(TokenType::LeftBracket)?,
']' => self.add_token(TokenType::RightBracket)?, ']' => self.add_token(TokenType::RightBracket)?,
'+' => self.add_token(TokenType::Plus)?, '+' => {
if self.match_char('=') {
self.add_token(TokenType::PlusEqual)?
} else {
self.add_token(TokenType::Plus)?
}
}
'*' => self.add_token(TokenType::Star)?, '*' => self.add_token(TokenType::Star)?,
',' => self.add_token(TokenType::Comma)?, ',' => self.add_token(TokenType::Comma)?,
'%' => self.add_token(TokenType::Mod)?, '%' => self.add_token(TokenType::Mod)?,
'^' => self.add_token(TokenType::Xor)?, '^' => self.add_token(TokenType::Xor)?,
':' => self.add_token(TokenType::Colon)?, ':' => self.add_token(TokenType::Colon)?,
'-' => { '-' => {
if self.match_char('>') { if self.match_char('=') {
self.add_token(TokenType::MinusEqual)?
} else if self.match_char('>') {
self.add_token(TokenType::Arrow)? self.add_token(TokenType::Arrow)?
} else { } else {
self.add_token(TokenType::Minus)? self.add_token(TokenType::Minus)?
@@ -190,9 +211,7 @@ impl Tokenizer {
} }
} }
'|' => { '|' => {
if self.match_char('>') { if self.match_char('|') {
self.add_token(TokenType::Pipe)?
} else if self.match_char('|') {
self.add_token(TokenType::LogicalOr)? self.add_token(TokenType::LogicalOr)?
} else { } else {
self.add_token(TokenType::BitOr)? self.add_token(TokenType::BitOr)?
@@ -235,11 +254,14 @@ impl Tokenizer {
return error!(self.loc, "unterminated char literal"); return error!(self.loc, "unterminated char literal");
} }
_ = self.match_char('\\'); // if its an escape sequence skip \ and read one more _ = self.match_char('\\'); // if its an escape sequence skip \ and read one more
if self.eof() {
return error!(self.loc, "unterminated char literal");
}
self.advance(); self.advance();
if !self.match_char('\'') { if !self.match_char('\'') {
return error!(self.loc, "expected ' after char literal"); return error!(self.loc, "expected ' after char literal");
} }
self.add_token(TokenType::Char)? self.add_token(TokenType::CharLiteral)?
} }
'"' => { '"' => {
let start_loc = self.loc.clone(); let start_loc = self.loc.clone();
@@ -247,11 +269,17 @@ impl Tokenizer {
while !self.eof() { while !self.eof() {
if self.peek() == '\\' { if self.peek() == '\\' {
self.advance(); self.advance();
if self.eof() {
return error!(
self.loc,
format!("unterminated string, started at {}", start_loc)
);
}
} else if self.peek() == '"' { } else if self.peek() == '"' {
break; break;
} else if self.peek() == '\n' { } else if self.peek() == '\n' {
self.loc.line += 1; self.loc.line += 1;
self.loc.column = 1; self.loc.column = 0;
} }
self.advance(); self.advance();
} }
@@ -264,7 +292,7 @@ impl Tokenizer {
} }
self.advance(); self.advance();
self.add_token(TokenType::String)? self.add_token(TokenType::StringLiteral)?
} }
' ' | '\r' => {} ' ' | '\r' => {}
'\n' => { '\n' => {
@@ -328,21 +356,39 @@ impl Tokenizer {
} }
fn scan_number(&mut self) -> Result<(), ZernError> { fn scan_number(&mut self) -> Result<(), ZernError> {
if self.match_char('x') { let mut is_float = false;
if self.source[self.current - 1] == '0' && self.match_char('x') {
if !self.peek().is_ascii_hexdigit() {
return error!(self.loc, "expected a digit after '0x'");
}
while self.peek().is_ascii_hexdigit() { while self.peek().is_ascii_hexdigit() {
self.advance(); self.advance();
} }
} else if self.match_char('o') {
while matches!(self.peek(), '0'..='7') {
self.advance();
}
} else { } else {
if self.source[self.current - 1] == '0' && self.peek().is_ascii_digit() {
return error!(self.loc, "octal literals are not allowed");
}
while self.peek().is_ascii_digit() { while self.peek().is_ascii_digit() {
self.advance(); self.advance();
} }
if self.current + 1 < self.source.len()
&& self.peek() == '.'
&& self.source[self.current + 1] != '.'
{
is_float = true;
self.advance();
while self.peek().is_ascii_digit() {
self.advance();
}
}
} }
self.add_token(TokenType::Number) if is_float {
self.add_token(TokenType::FloatLiteral)
} else {
self.add_token(TokenType::IntLiteral)
}
} }
fn scan_identifier(&mut self) -> Result<(), ZernError> { fn scan_identifier(&mut self) -> Result<(), ZernError> {
@@ -351,8 +397,12 @@ impl Tokenizer {
} }
let lexeme: String = self.source[self.start..self.current].iter().collect(); let lexeme: String = self.source[self.start..self.current].iter().collect();
if lexeme == "include" {
return self.scan_include();
}
self.add_token(match lexeme.as_str() { self.add_token(match lexeme.as_str() {
"let" => TokenType::KeywordLet,
"const" => TokenType::KeywordConst, "const" => TokenType::KeywordConst,
"if" => TokenType::KeywordIf, "if" => TokenType::KeywordIf,
"else" => TokenType::KeywordElse, "else" => TokenType::KeywordElse,
@@ -367,12 +417,78 @@ impl Tokenizer {
"export" => TokenType::KeywordExport, "export" => TokenType::KeywordExport,
"struct" => TokenType::KeywordStruct, "struct" => TokenType::KeywordStruct,
"new" => TokenType::KeywordNew, "new" => TokenType::KeywordNew,
"true" => TokenType::True, "as" => TokenType::KeywordAs,
"false" => TokenType::False, "true" => TokenType::KeywordTrue,
"false" => TokenType::KeywordFalse,
_ => TokenType::Identifier, _ => TokenType::Identifier,
}) })
} }
fn scan_include(&mut self) -> Result<(), ZernError> {
if !self.match_char(' ') {
return error!(self.loc, "expected a space after 'include'");
}
if self.peek() != '"' {
return error!(self.loc, "expected '\"' after 'include '");
}
self.advance();
let path_start = self.current;
while !self.eof() && self.peek() != '"' {
self.advance();
}
if self.eof() {
return error!(self.loc, "unterminated string after 'include '");
}
let path: String = self.source[path_start..self.current].iter().collect();
self.advance(); // consume closing quote
self.include_file(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.clone()) {
return Ok(());
}
let source = match fs::read_to_string(&canonical) {
Ok(x) => x,
Err(_) => {
return error!(self.loc, format!("failed to include {}", path));
}
};
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
Ok(())
}
fn match_char(&mut self, expected: char) -> bool { fn match_char(&mut self, expected: char) -> bool {
if self.eof() || self.peek() != expected { if self.eof() || self.peek() != expected {
false false
@@ -386,7 +502,7 @@ impl Tokenizer {
fn add_token(&mut self, token_type: TokenType) -> Result<(), ZernError> { fn add_token(&mut self, token_type: TokenType) -> Result<(), ZernError> {
let mut lexeme: String = self.source[self.start..self.current].iter().collect(); let mut lexeme: String = self.source[self.start..self.current].iter().collect();
if token_type == TokenType::Char || token_type == TokenType::String { if token_type == TokenType::CharLiteral || token_type == TokenType::StringLiteral {
lexeme = self.unescape(&lexeme)?; lexeme = self.unescape(&lexeme)?;
} }
@@ -446,3 +562,15 @@ impl Tokenizer {
self.current >= self.source.len() 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");
}

649
src/typechecker.rs Normal file
View File

@@ -0,0 +1,649 @@
use std::collections::HashMap;
use crate::{
parser::{Expr, ExprKind, Params, Stmt},
symbol_table::{FnParams, SymbolTable},
tokenizer::{TokenType, ZernError, error},
};
macro_rules! expect_type {
($expr_type:expr, $expected:expr, $loc:expr) => {{
let actual = $expr_type;
if $expected != "any" && actual != "any" && actual != $expected {
return error!(
$loc,
format!("expected type '{}', got {}", $expected, actual)
);
}
}};
}
macro_rules! expect_types {
($expr_type:expr, [$( $expected:expr ),+], $loc:expr) => {
if $expr_type != "any" && $( $expr_type != $expected )&&+ {
return error!(
$loc,
format!(
"expected one of [{}], got {}",
[$( $expected ),+].join(", "),
$expr_type
)
);
}
};
}
static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "any"];
pub struct Env {
scopes: Vec<HashMap<String, String>>,
}
impl Env {
pub fn new() -> Env {
Env {
scopes: vec![HashMap::new()],
}
}
pub fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
pub fn pop_scope(&mut self) {
assert!(!self.scopes.is_empty());
self.scopes.pop();
}
pub fn define_var(&mut self, name: String, var_type: String) {
self.scopes.last_mut().unwrap().insert(name, var_type);
}
fn get_var_type(&self, name: &str) -> Option<&String> {
for scope in self.scopes.iter().rev() {
if let Some(var) = scope.get(name) {
return Some(var);
}
}
None
}
}
pub struct TypeChecker<'a> {
symbol_table: &'a SymbolTable,
pub expr_types: HashMap<usize, String>,
current_function_return_type: String,
}
impl<'a> TypeChecker<'a> {
pub fn new(symbol_table: &'a SymbolTable) -> TypeChecker<'a> {
TypeChecker {
symbol_table,
expr_types: HashMap::new(),
current_function_return_type: String::new(),
}
}
pub fn typecheck_stmt(&mut self, env: &mut Env, stmt: &Stmt) -> Result<(), ZernError> {
match stmt {
Stmt::Expression(expr) => {
self.typecheck_expr(env, expr)?;
}
Stmt::Declare {
name,
var_type,
initializer,
} => {
let mut actual_type = self.typecheck_expr(env, initializer)?;
if actual_type.contains(',') {
return error!(
&name.loc,
"cannot assign multi-return call to a single variable"
);
}
if let Some(var_type) = var_type {
if !self.is_valid_type_name(&var_type.lexeme) {
return error!(
&name.loc,
"unrecognized type: ".to_owned() + &var_type.lexeme
);
}
expect_type!(actual_type.clone(), var_type.lexeme, var_type.loc);
if actual_type == "any" {
actual_type = var_type.lexeme.clone();
}
}
env.define_var(name.lexeme.clone(), actual_type);
}
Stmt::Assign { left, op, value } => {
let value_type = self.typecheck_expr(env, value)?;
if value_type.contains(',') {
return error!(
&op.loc,
"cannot assign multi-return call to a single variable"
);
}
match &left.kind {
ExprKind::Variable(name) => {
let existing_var_type = match env.get_var_type(&name.lexeme) {
Some(x) => x,
None => {
return error!(
name.loc,
format!("undefined variable: {}", &name.lexeme)
);
}
};
expect_type!(value_type.clone(), *existing_var_type, name.loc);
}
ExprKind::Index {
expr,
bracket,
index,
} => {
expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc);
expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc);
expect_types!(value_type.clone(), ["u8", "i64"], bracket.loc);
}
ExprKind::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.symbol_table.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(
&field.loc,
format!("unknown struct type: {}", left_type)
);
}
};
let f = match fields.get(&field.lexeme) {
Some(o) => o,
None => {
return error!(
&field.loc,
format!("unknown field: {}", &field.lexeme)
);
}
};
expect_type!(value_type.clone(), f.field_type, field.loc);
}
_ => return error!(&op.loc, "invalid assignment target"),
}
}
Stmt::Destructure { targets, op, value } => {
let value_type = self.typecheck_expr(env, value)?;
let types: Vec<&str> = value_type.split(',').collect();
if types.len() != targets.len() {
return error!(
&op.loc,
"destructure target count does not match return count"
);
}
for (target, ty) in targets.iter().zip(types.iter()) {
match env.get_var_type(&target.lexeme) {
Some(existing) => {
expect_type!(ty.to_string(), *existing, target.loc);
}
None => {
env.define_var(target.lexeme.clone(), ty.to_string());
}
}
}
}
Stmt::Const { .. } => {
// handled in SymbolTable
}
Stmt::Block(stmts) => {
env.push_scope();
for stmt in stmts {
self.typecheck_stmt(env, stmt)?;
}
env.pop_scope();
}
Stmt::If {
keyword,
condition,
then_branch,
else_branch,
} => {
expect_types!(
self.typecheck_expr(env, condition)?,
["i64", "u8", "ptr", "bool"],
keyword.loc
);
self.typecheck_stmt(env, then_branch)?;
self.typecheck_stmt(env, else_branch)?;
}
Stmt::While {
keyword,
condition,
body,
} => {
expect_types!(
self.typecheck_expr(env, condition)?,
["i64", "u8", "ptr", "bool"],
keyword.loc
);
self.typecheck_stmt(env, body)?;
}
Stmt::For {
var,
start,
end,
body,
} => {
expect_type!(self.typecheck_expr(env, start)?, "i64", var.loc);
expect_type!(self.typecheck_expr(env, end)?, "i64", var.loc);
env.push_scope();
env.define_var(var.lexeme.clone(), "i64".into());
self.typecheck_stmt(env, body)?;
env.pop_scope();
}
Stmt::Function {
name,
params,
return_types,
body,
exported: _,
} => {
let return_type = return_types
.iter()
.map(|t| t.lexeme.clone())
.collect::<Vec<_>>()
.join(",");
if name.lexeme == "main" {
if return_type != "i64" {
return error!(&name.loc, "main function must return i64");
}
match params {
Params::Normal(params) => {
if !params.is_empty() && params.len() != 2 {
return error!(
&name.loc,
"main function must accept 0 or 2 parameters"
);
}
if params.len() == 2 {
if params[0].var_type.lexeme != "i64" {
return error!(
&name.loc,
"first parameter of the main function must be an i64"
);
}
if params[1].var_type.lexeme != "ptr" {
return error!(
&name.loc,
"second parameter of the main function must be a ptr"
);
}
}
}
Params::Variadic => {
return error!(&name.loc, "main function cannot be variadic");
}
}
}
if !self.is_valid_type_name(&return_type) {
return error!(
&return_types[0].loc,
"unrecognized type: ".to_owned() + &return_type
);
}
self.current_function_return_type = return_type.clone();
env.push_scope();
match params {
Params::Normal(params) => {
for param in params {
if !self.is_valid_type_name(&param.var_type.lexeme) {
return error!(
&param.var_name.loc,
"unrecognized type: ".to_owned() + &param.var_type.lexeme
);
}
if param.var_type.lexeme == "f64" {
return error!(
&param.var_name.loc,
"f64 params not implemented yet"
);
}
env.define_var(
param.var_name.lexeme.clone(),
param.var_type.lexeme.clone(),
);
}
}
Params::Variadic => {}
}
self.typecheck_stmt(env, body)?;
env.pop_scope();
}
Stmt::Return { keyword, exprs } => {
let joined_type = if exprs.is_empty() {
"void".into()
} else {
exprs
.iter()
.map(|e| self.typecheck_expr(env, e))
.collect::<Result<Vec<String>, _>>()?
.join(",")
};
expect_type!(joined_type, self.current_function_return_type, keyword.loc);
}
Stmt::Break => {}
Stmt::Continue => {}
Stmt::Extern(_) => {
// handled in the SymbolTable
}
Stmt::Struct { name: _, fields } => {
for field in fields {
if !self.is_valid_type_name(&field.var_type.lexeme) {
return error!(
&field.var_type.loc,
format!("unknown type: {}", &field.var_type.lexeme)
);
}
}
}
}
Ok(())
}
pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result<String, ZernError> {
let expr_type = match &expr.kind {
ExprKind::Binary { left, op, right } => {
let left_type = self.typecheck_expr(env, left)?;
match op.token_type {
TokenType::Plus | TokenType::Minus => {
expect_types!(left_type, ["i64", "ptr", "u8"], op.loc);
expect_types!(
self.typecheck_expr(env, right)?,
["i64", "ptr", "u8"],
op.loc
);
Ok(left_type)
}
TokenType::Star
| TokenType::Slash
| TokenType::Mod
| TokenType::Xor
| TokenType::BitAnd
| TokenType::BitOr
| TokenType::ShiftLeft
| TokenType::ShiftRight => {
expect_types!(left_type, ["i64", "u8"], op.loc);
expect_types!(self.typecheck_expr(env, right)?, ["i64", "u8"], op.loc);
Ok(left_type)
}
TokenType::DoubleEqual
| TokenType::NotEqual
| TokenType::Greater
| TokenType::GreaterEqual
| TokenType::Less
| TokenType::LessEqual => {
expect_types!(left_type, ["i64", "ptr", "u8"], op.loc);
expect_types!(
self.typecheck_expr(env, right)?,
["i64", "ptr", "u8"],
op.loc
);
Ok("bool".into())
}
_ => unreachable!(),
}
}
ExprKind::Logical { left, op, right } => {
expect_types!(
self.typecheck_expr(env, left)?,
["bool", "i64", "ptr"],
op.loc
);
expect_types!(
self.typecheck_expr(env, right)?,
["bool", "i64", "ptr"],
op.loc
);
Ok("bool".into())
}
ExprKind::Grouping(expr) => self.typecheck_expr(env, expr),
ExprKind::Literal(token) => match token.token_type {
TokenType::IntLiteral => Ok("i64".into()),
TokenType::FloatLiteral => Ok("f64".into()),
TokenType::CharLiteral => Ok("u8".into()),
TokenType::StringLiteral => Ok("str".into()),
TokenType::KeywordTrue => Ok("bool".into()),
TokenType::KeywordFalse => Ok("bool".into()),
_ => unreachable!(),
},
ExprKind::Unary { op, right } => {
let right_type = self.typecheck_expr(env, right)?;
match op.token_type {
TokenType::Minus => {
expect_types!(right_type, ["f64", "i64"], op.loc);
Ok(right_type)
}
TokenType::Bang => {
expect_types!(right_type, ["bool", "i64", "ptr", "u8"], op.loc);
Ok("bool".into())
}
_ => unreachable!(),
}
}
ExprKind::Variable(name) => {
if self.symbol_table.constants.contains_key(&name.lexeme) {
Ok("i64".into())
} else {
match env.get_var_type(&name.lexeme) {
Some(x) => Ok(x.clone()),
None => error!(name.loc, format!("undefined variable: {}", &name.lexeme)),
}
}
}
ExprKind::Call {
callee,
paren,
args,
} => {
if let ExprKind::Variable(callee_name) = &callee.kind {
if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) {
// its a function (defined/builtin/extern)
match &fn_type.params {
FnParams::Normal(params) => {
if params.len() != args.len() {
return error!(
&paren.loc,
format!(
"expected {} arguments, got {}",
params.len(),
args.len()
)
);
}
for (i, arg) in args.iter().enumerate() {
expect_type!(
self.typecheck_expr(env, arg)?,
params[i],
paren.loc
);
}
}
FnParams::Variadic => {
// cant check arg types
for arg in args {
self.typecheck_expr(env, arg)?;
}
}
}
Ok(fn_type.return_type.clone())
} else {
// its a variable containing function address
expect_type!(self.typecheck_expr(env, callee)?, "ptr", paren.loc);
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok("any".into())
}
} else {
// its an expression that evalutes to function address
expect_type!(self.typecheck_expr(env, callee)?, "ptr", paren.loc);
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok("any".into())
}
}
ExprKind::ArrayLiteral(exprs) => {
for expr in exprs {
self.typecheck_expr(env, expr)?;
}
Ok("Array".into())
}
ExprKind::Index {
expr,
bracket,
index,
} => {
expect_types!(self.typecheck_expr(env, expr)?, ["ptr", "str"], bracket.loc);
expect_types!(self.typecheck_expr(env, index)?, ["i64", "u8"], bracket.loc);
Ok("u8".into())
}
ExprKind::AddrOf { op, expr } => match &expr.kind {
ExprKind::Variable(_) => Ok("ptr".into()),
_ => {
error!(&op.loc, "can only take address of variables and functions")
}
},
ExprKind::New {
struct_name,
use_heap: _,
} => {
if !self.symbol_table.structs.contains_key(&struct_name.lexeme) {
return error!(
&struct_name.loc,
format!("unknown struct name: {}", &struct_name.lexeme)
);
}
Ok(struct_name.lexeme.clone())
}
ExprKind::MemberAccess { left, field } => {
let left_type = self.typecheck_expr(env, left)?;
let fields = match self.symbol_table.structs.get(&left_type) {
Some(f) => f,
None => {
return error!(&field.loc, format!("unknown struct type: {}", left_type));
}
};
let field = match fields.get(&field.lexeme) {
Some(o) => o,
None => return error!(&field.loc, format!("unknown field: {}", &field.lexeme)),
};
Ok(field.field_type.clone())
}
ExprKind::Cast { expr, type_name } => {
let expr_type = self.typecheck_expr(env, expr)?;
if expr_type != "any" && type_name.lexeme == "f64" {
return error!(
&type_name.loc,
"use _builtin_cvtsi2sd and _builtin_cvttsd2si to cast between integers and f64"
);
}
if !self.is_valid_type_name(&type_name.lexeme) {
return error!(
&type_name.loc,
format!("unknown type: {}", &type_name.lexeme)
);
}
Ok(type_name.lexeme.clone())
}
ExprKind::MethodCall { expr, method, args } => {
let receiver_type = self.typecheck_expr(env, expr)?;
let func_name = format!("{}.{}", receiver_type, method.lexeme);
let func_type = match self.symbol_table.functions.get(&func_name) {
Some(f) => f,
None => {
return error!(
method.loc,
format!(
"method {} not found on on type {}",
method.lexeme, receiver_type
)
);
}
};
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,
format!(
"expected {} arguments, got {}",
params.len() - 1,
args.len()
)
);
}
for (i, arg) in args.iter().enumerate() {
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
}
Ok(func_type.return_type.clone())
}
FnParams::Variadic => {
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok(func_type.return_type.clone())
}
}
}
}?;
self.expr_types.insert(expr.id, expr_type.clone());
Ok(expr_type)
}
fn is_valid_type_name(&self, name: &str) -> bool {
if name.contains(',') {
return name.split(',').all(|part| self.is_valid_type_name(part));
}
if BUILTIN_TYPES.contains(&name) {
return true;
}
if self.symbol_table.structs.contains_key(name) {
return true;
}
false
}
}

249
std/containers.zr Normal file
View File

@@ -0,0 +1,249 @@
include "mem.zr"
struct Array
data: ptr
size: i64
capacity: i64
func Array.new_preallocated_and_zeroed[size: i64] : Array
xs := new* Array
if size > 0
xs->data = mem.alloc(size * 8)
mem.zero(xs->data, size * 8)
xs->size = size
xs->capacity = size
return xs
func Array.nth[xs: Array, n: i64] : any
if n < 0 || n >= xs->size
panic("Array.nth out of bounds")
return mem.read64(xs->data + n * 8)
func Array.set[xs: Array, n: i64, x: any] : void
if n < 0 || n >= xs->size
panic("Array.set out of bounds")
mem.write64(xs->data + n * 8, x)
func Array.push[xs: Array, x: any] : void
if xs->size == xs->capacity
new_capacity := 4
if xs->capacity != 0
new_capacity = xs->capacity * 2
xs->data = xs->data->realloc(new_capacity * 8)
xs->capacity = new_capacity
mem.write64(xs->data + xs->size * 8, x)
xs->size += 1
func Array.free[xs: Array] : void
if xs->data != 0
xs->data->free()
(xs as ptr)->free()
func Array.free_with_items[xs: Array] : void
if xs->data != 0
for i in 0..xs->size
(xs->nth(i) as ptr)->free()
xs->data->free()
(xs as ptr)->free()
func Array.pop[xs: Array] : any
if xs->size == 0
panic("Array.pop on empty array")
x : any = Array.last(xs)
xs->size = xs->size - 1
return x
func Array.last[xs: Array] : any
if xs->size == 0
panic("Array.last on empty array")
return xs->nth(xs->size - 1)
func Array.slice[xs: Array, start: i64, length: i64] : Array
if start < 0 || length < 0 || start + length > xs->size
panic("Array.slice out of bounds")
new_array := Array.new_preallocated_and_zeroed(length)
mem.copy(xs->data + start * 8, new_array->data, length * 8)
return new_array
func Array.concat[a: Array, b: Array] : Array
new_array := Array.new_preallocated_and_zeroed(a->size + b->size)
mem.copy(a->data, new_array->data, a->size * 8)
mem.copy(b->data, new_array->data + a->size * 8, b->size * 8)
return new_array
func Array.quicksort[arr: Array] : void
arr->_do_quicksort(0, arr->size - 1)
func Array._do_quicksort[arr: Array, low: i64, high: i64] : void
if low < high
i := arr->_partition(low, high)
arr->_do_quicksort(low, i - 1)
arr->_do_quicksort(i + 1, high)
func Array._partition[arr: Array, low: i64, high: i64] : i64
pivot : i64 = arr->nth(high)
i := low - 1
for j in (low)..high
if arr->nth(j) as i64 <= pivot
i += 1
temp : i64 = arr->nth(i)
arr->set(i, arr->nth(j))
arr->set(j, temp)
temp : i64 = arr->nth(i + 1)
arr->set(i + 1, arr->nth(high))
arr->set(high, temp)
return i + 1
func Array.contains_str[arr: Array, s: str] : bool
for i in 0..arr->size
if (arr->nth(i) as str)->equal(s)
return true
return false
func Array.count[arr: Array, item: any] : i64
count := 0
for i in 0..arr->size
if arr->nth(i) == item
count += 1
return count
func Array.map[arr: Array, fn: ptr] : Array
out := Array.new_preallocated_and_zeroed(arr->size)
for i in 0..arr->size
out->set(i, fn(arr->nth(i)))
return out
func Array.filter[arr: Array, fn: ptr] : Array
out := []
for i in 0..arr->size
if fn(arr->nth(i))
out->push(arr->nth(i))
return out
func Array.reduce[arr: Array, fn: ptr, acc: any] : any
for i in 0..arr->size
acc = fn(acc, arr->nth(i))
return acc
func str.split[haystack: str, needle: str]: Array
haystack_len := haystack->len()
needle_len := needle->len()
result := []
if !needle_len
if !haystack_len
return result
else
for i in 0..haystack_len
result->push(haystack->substr_n(i, 1))
return result
start := 0
i := 0
while i < haystack_len
if i <= haystack_len - needle_len
match := true
for j in 0..needle_len
if haystack[i + j] != needle[j]
match = false
break
if match
result->push(haystack->substr_n(start, i - start))
start = i + needle_len
i += needle_len
continue
i += 1
result->push(haystack->substr_n(start, haystack_len - start))
return result
const HashMap._TABLE_SIZE = 100
struct HashMap
table: Array
struct HashMap._Node
key: str
value: any
next: HashMap._Node
func HashMap.new[] : HashMap
map := new* HashMap
map->table = Array.new_preallocated_and_zeroed(HashMap._TABLE_SIZE)
return map
func HashMap.insert[map: HashMap, key: str, value: any] : void
index := HashMap._djb2(key)
current : HashMap._Node = map->table->nth(index)
while current as ptr
if current->key->equal(key)
current->value = value
return
current = current->next
new_node := new* HashMap._Node
new_node->key = key->make_copy()
new_node->value = value
new_node->next = map->table->nth(index)
map->table->set(index, new_node)
func HashMap.get[map: HashMap, key: str] : any, bool
index := HashMap._djb2(key)
current : HashMap._Node = map->table->nth(index)
while current as ptr
if current->key->equal(key)
return current->value, true
current = current->next
return 0 as any, false
func HashMap.delete[map: HashMap, key: str] : void
index := HashMap._djb2(key)
current : HashMap._Node = map->table->nth(index)
prev := 0 as HashMap._Node
while current as ptr
if current->key->equal(key)
if prev as ptr
prev->next = current->next
else
map->table->set(index, current->next)
current->key->free()
(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

152
std/io.zr Normal file
View File

@@ -0,0 +1,152 @@
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
i := 1
while s[0]
if s[0] == '%'
s += 1
if s[0] == 'd'
io.print_i64(_var_arg(i) as i64)
i += 1
else if s[0] == 'x'
io.print_i64_hex(_var_arg(i) as i64)
i += 1
else if s[0] == 's'
io.print(_var_arg(i) as str)
i += 1
else if s[0] == 'c'
io.print_char(_var_arg(i) as u8)
i += 1
else if s[0] == 'f'
io.print_f64(_var_arg(i) as i64)
i += 1
else if s[0] == '%'
io.print_char('%')
else if s[0] == 0
break
else
panic("io.printf: unrecognized format")
else
io.print_char(s[0])
s += 1
func io.print_sized[x: ptr, size: i64] : void
_builtin_syscall(SYS_write, STDOUT_FILENO, x, size)
func io.print[x: str] : void
io.print_sized(x as ptr, x->len())
func io.println[x: str] : void
io.print(x)
io.print("\n")
func io.print_char[x: u8] : void
io.print_sized(^x, 1)
func io.print_bool[x: bool] : void
if x
io.print("true")
else
io.print("false")
func io.print_i64[x: i64] : void
s := _stackalloc(21)
x->to_str_buf(s)
io.print(s as str)
func io.print_i64_hex[x: i64] : void
s := _stackalloc(17)
x->to_hex_str_buf(s)
io.print(s as str)
func io.println_i64[x: i64] : void
s := _stackalloc(21)
x->to_str_buf(s)
io.println(s as str)
// TODO: fix when we implement f64 params
func io.print_f64[x: i64] : void
s := _stackalloc(64)
f64.to_str_buf(x, s)
io.print(s as str)
func io.read_char[] : u8
c := 0 as u8
_builtin_syscall(SYS_read, STDIN_FILENO, ^c, 1)
return c
func io.read_line[] : str
b := new str.Builder
while true
c := io.read_char()
if c == '\n'
break
b->append_char(c)
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)
if fd < 0
return 0 as str, false
size := _builtin_syscall(SYS_lseek, fd, 0, SEEK_END)
_builtin_syscall(SYS_lseek, fd, 0, SEEK_SET)
buffer := mem.alloc(size + 1)
n := _builtin_syscall(SYS_read, fd, buffer, size)
_builtin_syscall(SYS_close, fd)
if n != size
buffer->free()
return 0 as str, false
buffer[n] = 0
return buffer as str, true
func io.read_binary_file[path: str] : Blob, bool
fd := _builtin_syscall(SYS_openat, AT_FDCWD, path, O_RDONLY, 0)
if fd < 0
return 0 as Blob, false
buf := new* Blob
buf->size = _builtin_syscall(SYS_lseek, fd, 0, SEEK_END)
_builtin_syscall(SYS_lseek, fd, 0, SEEK_SET)
buf->data = mem.alloc(buf->size)
n := _builtin_syscall(SYS_read, fd, buf->data, buf->size)
_builtin_syscall(SYS_close, fd)
if n != buf->size
buf->free()
return 0 as Blob, false
return buf, true
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
fd := _builtin_syscall(SYS_openat, AT_FDCWD, path, O_WRONLY|O_CREAT|O_TRUNC, math.octal_to_decimal(644))
if fd < 0
return false
n := _builtin_syscall(SYS_write, fd, content, size)
_builtin_syscall(SYS_close, fd)
if n != size
return false
return true

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)

380
std/linux_syscalls.zr Normal file
View File

@@ -0,0 +1,380 @@
const SYS_read = 0
const SYS_write = 1
const SYS_open = 2
const SYS_close = 3
const SYS_stat = 4
const SYS_fstat = 5
const SYS_lstat = 6
const SYS_poll = 7
const SYS_lseek = 8
const SYS_mmap = 9
const SYS_mprotect = 10
const SYS_munmap = 11
const SYS_brk = 12
const SYS_rt_sigaction = 13
const SYS_rt_sigprocmask = 14
const SYS_rt_sigreturn = 15
const SYS_ioctl = 16
const SYS_pread64 = 17
const SYS_pwrite64 = 18
const SYS_readv = 19
const SYS_writev = 20
const SYS_access = 21
const SYS_pipe = 22
const SYS_select = 23
const SYS_sched_yield = 24
const SYS_mremap = 25
const SYS_msync = 26
const SYS_mincore = 27
const SYS_madvise = 28
const SYS_shmget = 29
const SYS_shmat = 30
const SYS_shmctl = 31
const SYS_dup = 32
const SYS_dup2 = 33
const SYS_pause = 34
const SYS_nanosleep = 35
const SYS_getitimer = 36
const SYS_alarm = 37
const SYS_setitimer = 38
const SYS_getpid = 39
const SYS_sendfile = 40
const SYS_socket = 41
const SYS_connect = 42
const SYS_accept = 43
const SYS_sendto = 44
const SYS_recvfrom = 45
const SYS_sendmsg = 46
const SYS_recvmsg = 47
const SYS_shutdown = 48
const SYS_bind = 49
const SYS_listen = 50
const SYS_getsockname = 51
const SYS_getpeername = 52
const SYS_socketpair = 53
const SYS_setsockopt = 54
const SYS_getsockopt = 55
const SYS_clone = 56
const SYS_fork = 57
const SYS_vfork = 58
const SYS_execve = 59
const SYS_exit = 60
const SYS_wait4 = 61
const SYS_kill = 62
const SYS_uname = 63
const SYS_semget = 64
const SYS_semop = 65
const SYS_semctl = 66
const SYS_shmdt = 67
const SYS_msgget = 68
const SYS_msgsnd = 69
const SYS_msgrcv = 70
const SYS_msgctl = 71
const SYS_fcntl = 72
const SYS_flock = 73
const SYS_fsync = 74
const SYS_fdatasync = 75
const SYS_truncate = 76
const SYS_ftruncate = 77
const SYS_getdents = 78
const SYS_getcwd = 79
const SYS_chdir = 80
const SYS_fchdir = 81
const SYS_rename = 82
const SYS_mkdir = 83
const SYS_rmdir = 84
const SYS_creat = 85
const SYS_link = 86
const SYS_unlink = 87
const SYS_symlink = 88
const SYS_readlink = 89
const SYS_chmod = 90
const SYS_fchmod = 91
const SYS_chown = 92
const SYS_fchown = 93
const SYS_lchown = 94
const SYS_umask = 95
const SYS_gettimeofday = 96
const SYS_getrlimit = 97
const SYS_getrusage = 98
const SYS_sysinfo = 99
const SYS_times = 100
const SYS_ptrace = 101
const SYS_getuid = 102
const SYS_syslog = 103
const SYS_getgid = 104
const SYS_setuid = 105
const SYS_setgid = 106
const SYS_geteuid = 107
const SYS_getegid = 108
const SYS_setpgid = 109
const SYS_getppid = 110
const SYS_getpgrp = 111
const SYS_setsid = 112
const SYS_setreuid = 113
const SYS_setregid = 114
const SYS_getgroups = 115
const SYS_setgroups = 116
const SYS_setresuid = 117
const SYS_getresuid = 118
const SYS_setresgid = 119
const SYS_getresgid = 120
const SYS_getpgid = 121
const SYS_setfsuid = 122
const SYS_setfsgid = 123
const SYS_getsid = 124
const SYS_capget = 125
const SYS_capset = 126
const SYS_rt_sigpending = 127
const SYS_rt_sigtimedwait = 128
const SYS_rt_sigqueueinfo = 129
const SYS_rt_sigsuspend = 130
const SYS_sigaltstack = 131
const SYS_utime = 132
const SYS_mknod = 133
const SYS_uselib = 134
const SYS_personality = 135
const SYS_ustat = 136
const SYS_statfs = 137
const SYS_fstatfs = 138
const SYS_sysfs = 139
const SYS_getpriority = 140
const SYS_setpriority = 141
const SYS_sched_setparam = 142
const SYS_sched_getparam = 143
const SYS_sched_setscheduler = 144
const SYS_sched_getscheduler = 145
const SYS_sched_get_priority_max = 146
const SYS_sched_get_priority_min = 147
const SYS_sched_rr_get_interval = 148
const SYS_mlock = 149
const SYS_munlock = 150
const SYS_mlockall = 151
const SYS_munlockall = 152
const SYS_vhangup = 153
const SYS_modify_ldt = 154
const SYS_pivot_root = 155
const SYS__sysctl = 156
const SYS_prctl = 157
const SYS_arch_prctl = 158
const SYS_adjtimex = 159
const SYS_setrlimit = 160
const SYS_chroot = 161
const SYS_sync = 162
const SYS_acct = 163
const SYS_settimeofday = 164
const SYS_mount = 165
const SYS_umount2 = 166
const SYS_swapon = 167
const SYS_swapoff = 168
const SYS_reboot = 169
const SYS_sethostname = 170
const SYS_setdomainname = 171
const SYS_iopl = 172
const SYS_ioperm = 173
const SYS_create_module = 174
const SYS_init_module = 175
const SYS_delete_module = 176
const SYS_get_kernel_syms = 177
const SYS_query_module = 178
const SYS_quotactl = 179
const SYS_nfsservctl = 180
const SYS_getpmsg = 181
const SYS_putpmsg = 182
const SYS_afs_syscall = 183
const SYS_tuxcall = 184
const SYS_security = 185
const SYS_gettid = 186
const SYS_readahead = 187
const SYS_setxattr = 188
const SYS_lsetxattr = 189
const SYS_fsetxattr = 190
const SYS_getxattr = 191
const SYS_lgetxattr = 192
const SYS_fgetxattr = 193
const SYS_listxattr = 194
const SYS_llistxattr = 195
const SYS_flistxattr = 196
const SYS_removexattr = 197
const SYS_lremovexattr = 198
const SYS_fremovexattr = 199
const SYS_tkill = 200
const SYS_time = 201
const SYS_futex = 202
const SYS_sched_setaffinity = 203
const SYS_sched_getaffinity = 204
const SYS_set_thread_area = 205
const SYS_io_setup = 206
const SYS_io_destroy = 207
const SYS_io_getevents = 208
const SYS_io_submit = 209
const SYS_io_cancel = 210
const SYS_get_thread_area = 211
const SYS_lookup_dcookie = 212
const SYS_epoll_create = 213
const SYS_epoll_ctl_old = 214
const SYS_epoll_wait_old = 215
const SYS_remap_file_pages = 216
const SYS_getdents64 = 217
const SYS_set_tid_address = 218
const SYS_restart_syscall = 219
const SYS_semtimedop = 220
const SYS_fadvise64 = 221
const SYS_timer_create = 222
const SYS_timer_settime = 223
const SYS_timer_gettime = 224
const SYS_timer_getoverrun = 225
const SYS_timer_delete = 226
const SYS_clock_settime = 227
const SYS_clock_gettime = 228
const SYS_clock_getres = 229
const SYS_clock_nanosleep = 230
const SYS_exit_group = 231
const SYS_epoll_wait = 232
const SYS_epoll_ctl = 233
const SYS_tgkill = 234
const SYS_utimes = 235
const SYS_vserver = 236
const SYS_mbind = 237
const SYS_set_mempolicy = 238
const SYS_get_mempolicy = 239
const SYS_mq_open = 240
const SYS_mq_unlink = 241
const SYS_mq_timedsend = 242
const SYS_mq_timedreceive = 243
const SYS_mq_notify = 244
const SYS_mq_getsetattr = 245
const SYS_kexec_load = 246
const SYS_waitid = 247
const SYS_add_key = 248
const SYS_request_key = 249
const SYS_keyctl = 250
const SYS_ioprio_set = 251
const SYS_ioprio_get = 252
const SYS_inotify_init = 253
const SYS_inotify_add_watch = 254
const SYS_inotify_rm_watch = 255
const SYS_migrate_pages = 256
const SYS_openat = 257
const SYS_mkdirat = 258
const SYS_mknodat = 259
const SYS_fchownat = 260
const SYS_futimesat = 261
const SYS_newfstatat = 262
const SYS_unlinkat = 263
const SYS_renameat = 264
const SYS_linkat = 265
const SYS_symlinkat = 266
const SYS_readlinkat = 267
const SYS_fchmodat = 268
const SYS_faccessat = 269
const SYS_pselect6 = 270
const SYS_ppoll = 271
const SYS_unshare = 272
const SYS_set_robust_list = 273
const SYS_get_robust_list = 274
const SYS_splice = 275
const SYS_tee = 276
const SYS_sync_file_range = 277
const SYS_vmsplice = 278
const SYS_move_pages = 279
const SYS_utimensat = 280
const SYS_epoll_pwait = 281
const SYS_signalfd = 282
const SYS_timerfd_create = 283
const SYS_eventfd = 284
const SYS_fallocate = 285
const SYS_timerfd_settime = 286
const SYS_timerfd_gettime = 287
const SYS_accept4 = 288
const SYS_signalfd4 = 289
const SYS_eventfd2 = 290
const SYS_epoll_create1 = 291
const SYS_dup3 = 292
const SYS_pipe2 = 293
const SYS_inotify_init1 = 294
const SYS_preadv = 295
const SYS_pwritev = 296
const SYS_rt_tgsigqueueinfo = 297
const SYS_perf_event_open = 298
const SYS_recvmmsg = 299
const SYS_fanotify_init = 300
const SYS_fanotify_mark = 301
const SYS_prlimit64 = 302
const SYS_name_to_handle_at = 303
const SYS_open_by_handle_at = 304
const SYS_clock_adjtime = 305
const SYS_syncfs = 306
const SYS_sendmmsg = 307
const SYS_setns = 308
const SYS_getcpu = 309
const SYS_process_vm_readv = 310
const SYS_process_vm_writev = 311
const SYS_kcmp = 312
const SYS_finit_module = 313
const SYS_sched_setattr = 314
const SYS_sched_getattr = 315
const SYS_renameat2 = 316
const SYS_seccomp = 317
const SYS_getrandom = 318
const SYS_memfd_create = 319
const SYS_kexec_file_load = 320
const SYS_bpf = 321
const SYS_execveat = 322
const SYS_userfaultfd = 323
const SYS_membarrier = 324
const SYS_mlock2 = 325
const SYS_copy_file_range = 326
const SYS_preadv2 = 327
const SYS_pwritev2 = 328
const SYS_pkey_mprotect = 329
const SYS_pkey_alloc = 330
const SYS_pkey_free = 331
const SYS_statx = 332
const SYS_io_pgetevents = 333
const SYS_rseq = 334
const SYS_uretprobe = 335
const SYS_pidfd_send_signal = 424
const SYS_io_uring_setup = 425
const SYS_io_uring_enter = 426
const SYS_io_uring_register = 427
const SYS_open_tree = 428
const SYS_move_mount = 429
const SYS_fsopen = 430
const SYS_fsconfig = 431
const SYS_fsmount = 432
const SYS_fspick = 433
const SYS_pidfd_open = 434
const SYS_clone3 = 435
const SYS_close_range = 436
const SYS_openat2 = 437
const SYS_pidfd_getfd = 438
const SYS_faccessat2 = 439
const SYS_process_madvise = 440
const SYS_epoll_pwait2 = 441
const SYS_mount_setattr = 442
const SYS_quotactl_fd = 443
const SYS_landlock_create_ruleset = 444
const SYS_landlock_add_rule = 445
const SYS_landlock_restrict_self = 446
const SYS_memfd_secret = 447
const SYS_process_mrelease = 448
const SYS_futex_waitv = 449
const SYS_set_mempolicy_home_node = 450
const SYS_cachestat = 451
const SYS_fchmodat2 = 452
const SYS_map_shadow_stack = 453
const SYS_futex_wake = 454
const SYS_futex_wait = 455
const SYS_futex_requeue = 456
const SYS_statmount = 457
const SYS_listmount = 458
const SYS_lsm_get_self_attr = 459
const SYS_lsm_set_self_attr = 460
const SYS_lsm_list_modules = 461
const SYS_mseal = 462
const SYS_setxattrat = 463
const SYS_getxattrat = 464
const SYS_listxattrat = 465
const SYS_removexattrat = 466
const SYS_open_tree_attr = 467

237
std/mem.zr Normal file
View File

@@ -0,0 +1,237 @@
include "linux_syscalls.zr"
include "num.zr"
const MEM_BLOCK_SIZE = 32
struct mem.Block
size: i64
free: bool
next: mem.Block
prev: mem.Block
func mem.align[x: i64] : i64
return (x + 7) & -8
func mem.Block._split[blk: mem.Block, needed: i64] : void
if blk->size >= needed + MEM_BLOCK_SIZE + 8
new_blk := (blk as ptr + MEM_BLOCK_SIZE + needed) as mem.Block
new_blk->size = blk->size - needed - MEM_BLOCK_SIZE
new_blk->free = true
new_blk->next = blk->next
new_blk->prev = blk
if blk->next as ptr
blk->next->prev = new_blk
else
mem.write64(_builtin_heap_tail(), new_blk)
blk->size = needed
blk->next = new_blk
func mem._request_space[size: i64] : mem.Block
needed := size + MEM_BLOCK_SIZE
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) == MAP_FAILED
panic("mem._request_space: failed to mmap")
blk->size = alloc_size - MEM_BLOCK_SIZE
blk->free = false
blk->next = 0 as mem.Block
blk->prev = 0 as mem.Block
tail := mem.read64(_builtin_heap_tail()) as mem.Block
if tail as ptr
tail->next = blk
blk->prev = tail
mem.write64(_builtin_heap_tail(), blk)
return blk
func mem.alloc[size: i64] : ptr
if size <= 0
panic("mem.alloc: called with nonpositive size")
size = mem.align(size)
cur := mem.read64(_builtin_heap_head()) as mem.Block
while cur as ptr
if cur->free && cur->size >= size
cur->_split(size)
cur->free = false
return cur as ptr + MEM_BLOCK_SIZE
cur = cur->next
blk := mem._request_space(size)
if !mem.read64(_builtin_heap_head())
mem.write64(_builtin_heap_head(), blk)
blk->_split(size)
return blk as ptr + MEM_BLOCK_SIZE
func ptr.free[x: ptr] : void
if x == 0
return
blk := (x - MEM_BLOCK_SIZE) as mem.Block
if blk->free
return
blk->free = true
next := blk->next
if next as ptr && next->free
expected_next := (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block
if expected_next as ptr == next as ptr
blk->size += MEM_BLOCK_SIZE + next->size
blk->next = next->next
if next->next as ptr
next->next->prev = blk
if mem.read64(_builtin_heap_tail()) == next as ptr
mem.write64(_builtin_heap_tail(), blk)
prev := blk->prev
if prev as ptr && prev->free
expected_blk := (prev as ptr + MEM_BLOCK_SIZE + prev->size) as mem.Block
if expected_blk as ptr == blk as ptr
prev->size += MEM_BLOCK_SIZE + blk->size
prev->next = blk->next
if blk->next as ptr
blk->next->prev = prev
if mem.read64(_builtin_heap_tail()) == blk as ptr
mem.write64(_builtin_heap_tail(), prev)
blk = prev
block_total := blk->size + MEM_BLOCK_SIZE
if (blk as i64 & 4095) == 0 && (block_total & 4095) == 0
if blk->prev as ptr
blk->prev->next = blk->next
else
mem.write64(_builtin_heap_head(), blk->next)
if blk->next as ptr
blk->next->prev = blk->prev
else
mem.write64(_builtin_heap_tail(), blk->prev)
_builtin_syscall(SYS_munmap, blk, block_total)
func ptr.realloc[x: ptr, new_size: i64] : ptr
if !x
return mem.alloc(new_size)
if new_size < 0
panic("mem.realloc: called with negative new_size")
if new_size == 0
x->free()
return 0 as ptr
new_size = mem.align(new_size)
blk := (x - MEM_BLOCK_SIZE) as mem.Block
if blk->size >= new_size
blk->_split(new_size)
return x
next := blk->next
expected_next := (blk as ptr + MEM_BLOCK_SIZE + blk->size) as mem.Block
if next as ptr && next->free && expected_next as ptr == next as ptr
combined := blk->size + MEM_BLOCK_SIZE + next->size
if combined >= new_size
blk->size = combined
blk->next = next->next
if next->next as ptr
next->next->prev = blk
if mem.read64(_builtin_heap_tail()) == next as ptr
mem.write64(_builtin_heap_tail(), blk)
blk->_split(new_size)
return x
new_ptr := mem.alloc(new_size)
mem.copy(x, new_ptr, math.min(blk->size, new_size))
x->free()
return new_ptr
func ptr.zero_and_free[x: ptr, size: i64] : void
mem.zero(x, size)
x->free()
func mem.zero[x: ptr, size: i64] : void
i := 0
while i + 8 <= size
mem.write64(x + i, 0)
i += 8
while i < size
x[i] = 0 as u8
i += 1
func mem.copy[src: ptr, dst: ptr, n: i64] : void
if dst > src && dst < src + n
i := n
while i - 8 >= 0
i -= 8
mem.write64(dst + i, mem.read64(src + i))
while i > 0
i -= 1
dst[i] = src[i]
else
i := 0
while i + 8 <= n
mem.write64(dst + i, mem.read64(src + i))
i += 8
while i < n
dst[i] = src[i]
i += 1
func mem.read8[x: ptr] : u8
return x[0]
func mem.read16[x: ptr] : i64
return x[0] as i64 | (x[1] << 8)
func mem.read16be[x: ptr] : i64
return (x[0] << 8) as i64 | x[1]
func mem.read32[x: ptr] : i64
return x[0] as i64 | (x[1] << 8) | (x[2] << 16) | (x[3] << 24)
func mem.read32be[x: ptr] : i64
return (x[0] as i64 << 24) | (x[1] << 16) | (x[2] << 8) | x[3]
func mem.read64[x: ptr] : i64
return _builtin_read64(x)
func mem.write32[x: ptr, d: i64] : void
x[0] = d & 0xff
x[1] = (d >> 8) & 0xff
x[2] = (d >> 16) & 0xff
x[3] = (d >> 24) & 0xff
func mem.write16[x: ptr, d: i64] : void
x[0] = d & 0xff
x[1] = (d >> 8) & 0xff
func mem.write16be[x: ptr, d: i64] : void
x[0] = (d >> 8) & 0xff
x[1] = d & 0xff
func mem.write64[x: ptr, d: any] : void
_builtin_set64(x, d)
struct Blob
data: ptr
size: i64
func Blob.alloc[size: i64] : Blob
buffer := new* Blob
buffer->size = size
buffer->data = mem.alloc(size)
return buffer
func Blob.free[buf: Blob] : void
buf->data->free()
(buf as ptr)->free()

238
std/net.zr Normal file
View File

@@ -0,0 +1,238 @@
include "os.zr"
const AF_INET = 2
const SOCK_STREAM = 1
const SOCK_DGRAM = 2
const SOL_SOCKET = 1
const SO_REUSEADDR = 2
const SOMAXCONN = 128
const IP_MAXPACKET = 65535
const DNS_TYPE_A = 1
const DNS_CLASS_IN = 1
const DNS_RECURSION_DESIRED = 256
func net.listen[host: str, port: i64] : i64, bool
s := _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0)
if s < 0
return -1, false
optval := 1
if _builtin_syscall(SYS_setsockopt, s, SOL_SOCKET, SO_REUSEADDR, ^optval, 8) < 0
_builtin_syscall(SYS_close, s)
return -1, false
packed_host, ok := net.resolve(host)
if !ok
return -1, false
sa := _stackalloc(16)
net._build_sa(sa, packed_host, port)
if _builtin_syscall(SYS_bind, s, sa, 16) < 0
_builtin_syscall(SYS_close, s)
return -1, false
if _builtin_syscall(SYS_listen, s, SOMAXCONN) < 0
_builtin_syscall(SYS_close, s)
return -1, false
return s, true
func net.connect[host: str, port: i64] : i64, bool
s := _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0)
if s < 0
return -1, false
packed_host, ok := net.resolve(host)
if !ok
return -1, false
sa := _stackalloc(16)
net._build_sa(sa, packed_host, port)
if _builtin_syscall(SYS_connect, s, sa, 16) < 0
_builtin_syscall(SYS_close, s)
return -1, false
return s, true
func net.accept[s: i64, addr: ptr] : i64
addrlen := 16
return _builtin_syscall(SYS_accept, s, addr, ^addrlen)
func net.send[s: i64, data: ptr, size: i64] : void
_builtin_syscall(SYS_sendto, s, data, size, 0, 0, 0)
func net.read[s: i64, buffer: ptr, size: i64] : i64
return _builtin_syscall(SYS_read, s, buffer, size)
func net.close[s: i64] : void
_builtin_syscall(SYS_close, s)
func net.pack_addr[a: i64, b: i64, c: i64, d: i64] : i64
return (a << 24) | (b << 16) | (c << 8) | d
func net._build_sa[sa: ptr, packed_host: i64, port: i64] : void
mem.zero(sa, 16)
sa[0] = AF_INET
sa[1] = 0
sa[2] = (port >> 8) & 255
sa[3] = port & 255
sa[4] = (packed_host >> 24) & 255
sa[5] = (packed_host >> 16) & 255
sa[6] = (packed_host >> 8) & 255
sa[7] = packed_host & 255
struct net.UDPSocket
fd: i64
addr: ptr
func net.UDPSocket.receive[s: net.UDPSocket, size: i64] : net.UDPPacket
pkt := new* net.UDPPacket
pkt->data = mem.alloc(size)
pkt->source_addr = mem.alloc(16)
mem.zero(pkt->source_addr, 16)
addrlen := 16
pkt->size = _builtin_syscall(SYS_recvfrom, s->fd, pkt->data, size, 0, pkt->source_addr, ^addrlen)
return pkt
func net.UDPSocket.close_and_free[s: net.UDPSocket] : void
_builtin_syscall(SYS_close, s->fd)
s->addr->free()
(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)
if !ok
return 0 as net.UDPSocket, false
s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0)
if s->fd < 0
(s as ptr)->free()
return 0 as net.UDPSocket, false
s->addr = mem.alloc(16)
net._build_sa(s->addr, packed_host, port)
return s, true
func net.create_udp_server[host: str, port: i64] : net.UDPSocket, bool
s, ok := net.create_udp_client(host, port)
if !ok
return 0 as net.UDPSocket, false
if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0
s->close_and_free()
return 0 as net.UDPSocket, false
return s, true
func net.UDPSocket.send_to[s: net.UDPSocket, addr: ptr, data: ptr, size: i64] : void
_builtin_syscall(SYS_sendto, s->fd, data, size, 0, addr, 16)
struct net.UDPPacket
data: ptr
source_addr: ptr
size: i64
func net.UDPPacket.free[pkt: net.UDPPacket] : void
pkt->data->free()
pkt->source_addr->free()
(pkt as ptr)->free()
func net.resolve[domain: str] : i64, bool
// if domain is already an ip, skip dns resolution
parts := domain->split(".")
if parts->size == 4
valid := true
for i in 0..4
p := parts->nth(i) as str
if p->len() < 1 || p->len() > 3
valid = false
break
for j in 0..p->len()
if !p[j]->is_digit()
valid = false
break
if !valid
break
if valid
a := (parts->nth(0) as str)->parse_i64()
b := (parts->nth(1) as str)->parse_i64()
c := (parts->nth(2) as str)->parse_i64()
d := (parts->nth(3) as str)->parse_i64()
if a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255
parts->free_with_items()
return net.pack_addr(a, b, c, d), true
parts->free_with_items()
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)
if !ok
query->free()
return -1, false
s->send_to(s->addr, query->data, query->size)
query->free()
pkt := s->receive(1024)
s->close_and_free()
// TODO: do actual parsing
pos := 12 // skip header (12 bytes)
while pkt->data[pos] != 0
pos += pkt->data[pos] + 1 // skip question
pos += 5 // skip null byte, type(2), class(2)
pos += 12 // skip name(2), type(2), class(2), ttl(4), rdlength(2)
out := net.pack_addr(pkt->data[pos] as i64, pkt->data[pos+1] as i64, pkt->data[pos+2] as i64, pkt->data[pos+3] as i64)
pkt->free()
return out, true
func net.encode_dns_name[domain: str] : Blob
domain_len := domain->len()
buf := Blob.alloc(domain_len + 2)
out_pos := 0
part_start := 0
i := 0
while i <= domain_len
if i == domain_len || domain[i] == '.'
part_len := i - part_start
buf->data[out_pos] = part_len & 0xff
out_pos += 1
mem.copy(domain as ptr + part_start, buf->data + out_pos, part_len)
out_pos += part_len
part_start = i + 1
i += 1
buf->data[out_pos] = 0
return buf
func net.build_dns_query[domain_name: str, record_type: i64] : Blob
name := net.encode_dns_name(domain_name)
out := Blob.alloc(12 + name->size + 4)
// header
id := (os.urandom_i64() % 65536)->abs()
mem.write16be(out->data + 0, id)
mem.write16be(out->data + 2, DNS_RECURSION_DESIRED)
mem.write16be(out->data + 4, 1) // num_questions
mem.write16be(out->data + 6, 0) // num_answers
mem.write16be(out->data + 8, 0) // num_authorities
mem.write16be(out->data + 10, 0) // num_additionals
// question
mem.copy(name->data, out->data + 12, name->size)
mem.write16be(out->data + 12 + name->size, record_type)
mem.write16be(out->data + 12 + name->size + 2, DNS_CLASS_IN)
name->free()
return out

220
std/num.zr Normal file
View File

@@ -0,0 +1,220 @@
include "io.zr"
func i64.abs[n: i64] : i64
if n == -9223372036854775808
panic("MIN_I64 passed to math.abs")
if n < 0
return -n
return n
func i64.sign[n: i64] : i64
if n < 0
return -1
else if n > 0
return 1
else
return 0
func i64.isqrt[n: i64] : i64
if n < 0
panic("negative number passed to math.isqrt")
if n == 0 || n == 1
return n
guess := n
next_guess := (guess + n / guess) / 2
while next_guess < guess
guess = next_guess
next_guess = (guess + n / guess) / 2
return guess
func i64.is_prime[n: i64]: bool
if n <= 1
return false
if n == 2 || n == 3
return true
if n % 2 == 0 || n % 3 == 0
return false
i := 5
while i * i <= n
if n % i == 0 || n % (i + 2) == 0
return false
i += 6
return true
func i64.to_str_buf[n: i64, buf: ptr] : void
if n == 0
buf[0] = '0'
buf[1] = 0
return
neg : bool = n < 0
if neg
// MIN_I64 will fail but i so dont care
n = -n
tmp := _stackalloc(21)
end := 20
tmp[end] = 0
end -= 1
for i in 0..19
if n == 0
break
tmp[end] = '0' + (n % 10)
n = n / 10
end -= 1
if neg
tmp[end] = '-'
end -= 1
len := 19 - end
for i in 0..len
buf[i] = tmp[end + 1 + i]
buf[len] = 0
func i64.to_hex_str_buf[n: i64, buf: ptr] : void
hex_chars := "0123456789abcdef"
if n == 0
buf[0] = '0'
buf[1] = 0
return
mask := (1 << 60) - 1
tmp := _stackalloc(17)
len := 0
for i in 0..16
if n == 0
break
tmp[len] = hex_chars[n & 15]
n = (n >> 4) & mask
len += 1
for j in 0..len
buf[j] = tmp[len - 1 - j]
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
exp := (bits >> 52) & 0x7ff
mant := bits & 0x000fffffffffffff
if exp == 0x7ff
panic("invalid float")
p := 0
if sign
buf[p] = '-'
p += 1
bits = bits & 0x7fffffffffffffff
if exp == 0 && mant == 0
buf[p] = '0'
buf[p+1] = '.'
buf[p+2] = '0'
buf[p+3] = 0
return
sig := mant
if exp != 0
sig = sig | 0x0010000000000000
shift := exp - 1075
if shift >= 0
(sig << shift)->to_str_buf(buf + p)
while buf[p]
p += 1
buf[p] = '.'
buf[p+1] = '0'
buf[p+2] = 0
return
ns := -shift
if ns <= 52
int_part := sig >> ns
int_part->to_str_buf(buf + p)
while buf[p]
p += 1
else
buf[p] = '0'
p += 1
buf[p] = '.'
p += 1
mask := (1 << ns) - 1
frac := sig & mask
for i in 0..6
if frac == 0
if i > 0
break
buf[p] = '0'
p += 1
break
frac = frac * 10
digit := frac >> ns
buf[p] = '0' + digit as u8
p += 1
frac = frac & mask
buf[p] = 0
func math.gcd[a: i64, b: i64] : i64
a = a->abs()
b = b->abs()
while b != 0
tmp := b
b = a % b
a = tmp
return a
func math.min[a: i64, b: i64] : i64
if a < b
return a
return b
func math.max[a: i64, b: i64] : i64
if a > b
return a
return b
func math.pow[b: i64, e: i64] : i64
if e < 0
panic("negative exponent passed to math.pow")
out := 1
while e > 0
if e & 1
out = out * b
b = b * b
e = e >> 1
return out
func math.lcm[a: i64, b: i64] : i64
return (a / math.gcd(a, b)) * b
func math.octal_to_decimal[octal: i64] : i64
decimal := 0
base := 1
while octal > 0
digit := octal % 10
decimal += digit * base
base = base * 8
octal = octal / 10
return decimal

154
std/os.zr Normal file
View File

@@ -0,0 +1,154 @@
include "posix.zr"
include "linux_syscalls.zr"
include "str.zr"
include "containers.zr"
func os.exit[code: i64] : void
_builtin_syscall(SYS_exit, code)
func os.getpid[] : i64
return _builtin_syscall(SYS_getpid)
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
entry := mem.read64(_builtin_environ() + i * 8) as 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, true
i += 1
return 0 as str, false
func os.basename[path: str] : str
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_n(i + 1, end - i - 1)
struct os.Timespec
tv_sec: i64
tv_nsec: i64
func os.sleep[ms: i64] : void
if ms < 0
panic("negative time passed to os.sleep")
req := new os.Timespec
req->tv_sec = ms / 1000
req->tv_nsec = (ms % 1000) * 1000000
_builtin_syscall(SYS_nanosleep, req, 0)
func os.time[] : i64
ts := new os.Timespec
_builtin_syscall(SYS_clock_gettime, CLOCK_REALTIME, ts)
out := ts->tv_sec * 1000 + ts->tv_nsec / 1000000
return out
func os.urandom_buf[buf: ptr, n: i64] : void
pos := 0
while pos < n
ret := _builtin_syscall(SYS_getrandom, buf + pos, n - pos, 0)
if ret <= 0
panic("os.urandom_buf: syscall failed")
pos += ret
func os.urandom[n: i64]: ptr
buf := mem.alloc(n)
os.urandom_buf(buf, n)
return buf
func os.urandom_i64[] : i64
n := 0
os.urandom_buf(^n, 8)
return n
func os.run_shell_command[command: str] : i64, bool
pid := _builtin_syscall(SYS_fork)
if pid < 0
return -1, false
if pid == 0
argv := ["sh", "-c", command, 0] // gets freed after child process exits
_builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ())
os.exit(1)
else
status := 0
wp := _builtin_syscall(SYS_wait4, pid, ^status, 0, 0)
if wp < 0
return -1, false
st := status & 0xffffffff
if (st & 0x7f) == 0
return (st >> 8) & 0xff, true
else
return -(st & 0x7f), true
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
rc := _builtin_syscall(SYS_newfstatat, AT_FDCWD, path, st, 0)
if rc != 0
return false
return (mem.read32(st + 24) & S_IFMT) == S_IFDIR
func os.list_directory[path: str] : Array, bool
fd := _builtin_syscall(SYS_openat, AT_FDCWD, path, O_RDONLY, 0)
if fd < 0
return 0 as Array, false
files := []
buf := _stackalloc(1024)
while true
n := _builtin_syscall(SYS_getdents64, fd, buf, 1024)
if n < 0
files->free_with_items()
_builtin_syscall(SYS_close, fd)
return 0 as Array, false
else if n == 0
break
pos := 0
while pos < n
len := mem.read16(buf + pos + 16)
name : ptr = buf + pos + 19
if name[0]
skip := false
// skip if name is exactly '.' or '..'
if name[0] == '.'
if name[1] == 0
skip = true
else if name[1] == '.'
if name[2] == 0
skip = true
if !skip
files->push((name as str)->make_copy())
pos += len
_builtin_syscall(SYS_close, fd)
return files, true

32
std/posix.zr Normal file
View File

@@ -0,0 +1,32 @@
const STDIN_FILENO = 0
const STDOUT_FILENO = 1
const SIGABRT = 6
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
const O_WRONLY = 1
const O_CREAT = 64
const O_TRUNC = 512
const SEEK_SET = 0
const SEEK_END = 2
const F_OK = 0
const CLOCK_REALTIME = 0

299
std/str.zr Normal file
View File

@@ -0,0 +1,299 @@
include "num.zr"
include "mem.zr"
func u8.is_whitespace[x: u8] : bool
return x == ' ' || x == '\n' || x == '\t' || x == '\r'
func u8.is_digit[x: u8] : bool
return x >= '0' && x <= '9'
func u8.is_hex_digit[x: u8] : bool
return (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')
func u8.is_lowercase[x: u8] : bool
return x >= 'a' && x <= 'z'
func u8.is_uppercase[x: u8] : bool
return x >= 'A' && x <= 'Z'
func u8.is_letter[x: u8] : bool
return x->is_uppercase() || x->is_lowercase()
func u8.is_alphanumeric[x: u8] : bool
return x->is_letter() || x->is_digit()
func u8.to_str[c: u8] : str
s := mem.alloc(2) as str
s[0] = c
s[1] = 0
return s
func str.free[s: str] : void
(s as ptr)->free()
func str.len[s: str] : i64
i := 0
while s[i]
i += 1
return i
func str.make_copy[s: str] : str
size := s->len() + 1
dup := mem.alloc(size) as str
mem.copy(s as ptr, dup as ptr, size)
return dup
func str.equal[a: str, b: str] : bool
i := 0
while a[i] != 0 && b[i] != 0
if a[i] != b[i]
return false
i += 1
return a[i] == b[i]
func str.equal_n[a: str, b: str, n: i64] : bool
i := 0
while i < n
if a[i] == 0 || b[i] == 0
return false
if a[i] != b[i]
return false
i += 1
return true
func str.format_into[..] : i64
buf := _var_arg(0) as ptr
size := _var_arg(1) as i64
if size <= 0
return 0
s := _var_arg(2) as ptr
i := 3
n := 0
tmp := _stackalloc(64) as str
while s[0]
if s[0] == '%'
s += 1
if s[0] == 'd'
(_var_arg(i) as i64)->to_str_buf(tmp as ptr)
tmp_len := tmp->len()
remaining := size - n - 1
mem.copy(tmp as ptr, buf + n, math.min(tmp_len, remaining))
n += tmp_len
i += 1
else if s[0] == 'x'
(_var_arg(i) as i64)->to_hex_str_buf(tmp as ptr)
tmp_len := tmp->len()
remaining := size - n - 1
mem.copy(tmp as ptr, buf + n, math.min(tmp_len, remaining))
n += tmp_len
i += 1
else if s[0] == 's'
tmp_str := _var_arg(i) as str
tmp_len := tmp_str->len()
remaining := size - n - 1
mem.copy(tmp_str as ptr, buf + n, math.min(tmp_len, remaining))
n += tmp_len
i += 1
else if s[0] == 'c'
if n < size - 1
buf[n] = _var_arg(i) as u8
n += 1
i += 1
else if s[0] == 'f'
// 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
mem.copy(tmp as ptr, buf + n, math.min(tmp_len, remaining))
n += tmp_len
i += 1
else if s[0] == '%'
if n < size - 1
buf[n] = '%'
n += 1
else if s[0] == 0
break
else
panic("str.format_into: unrecognized format")
else
if n < size - 1
buf[n] = s[0]
n += 1
s += 1
if n < size
buf[n] = 0
else if size > 0
buf[size - 1] = 0
return n
func str.concat[a: str, b: str] : str
a_len := a->len()
b_len := b->len()
out := mem.alloc(a_len + b_len + 1) as str
mem.copy(a as ptr, out as ptr, a_len)
mem.copy(b as ptr, out as ptr + a_len, b_len)
out[a_len + b_len] = 0
return out
func str.contains[haystack: str, needle: str] : bool
return haystack->find(needle) != -1
func str.find[haystack: str, needle: str] : i64
haystack_len := haystack->len()
needle_len := needle->len()
if needle_len == 0
return 0
if needle_len > haystack_len
return -1
for i in 0..(haystack_len - needle_len + 1)
if haystack[i] != needle[0]
continue
if needle_len == 1
return i
match := true
for j in 1..needle_len
if haystack[i + j] != needle[j]
match = false
break
if match
return i
return -1
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
return out
func str.trim[s: str] : str
len := s->len()
if len == 0
out := mem.alloc(1) as str
out[0] = 0
return out
start := 0
end := len - 1
while start <= end && s[start]->is_whitespace()
start += 1
while end >= start && s[end]->is_whitespace()
end -= 1
return s->substr_n(start, end - start + 1)
func str.reverse[s: str] : str
len := s->len()
out := mem.alloc(len + 1) as str
for i in 0..len
out[i] = s[len - i - 1]
out[len] = 0
return out
func str.parse_i64[s: str] : i64
len := s->len()
i := 0
sign := 1
if i < len && s[i] == '-'
sign = -1
i += 1
num := 0
while i < len
d := s[i]
if !d->is_digit()
break
num = num * 10 + (d - '0')
i += 1
return num * sign
func str.hex_encode[s: str, s_len: i64] : str
hex_chars := "0123456789abcdef"
out := mem.alloc(s_len * 2 + 1) as str
for i in 0..s_len
b := s[i]
out[i * 2] = hex_chars[(b >> 4) & 15]
out[i * 2 + 1] = hex_chars[b & 15]
out[s_len * 2] = 0
return out
func str._hex_digit_to_int[d: u8] : u8
if d->is_digit()
return d - '0'
lower : u8 = d | 32
if lower >= 'a' && lower <= 'f'
return lower - 'a' + 10
panic("invalid hex digit passed to str._hex_digit_to_int")
func str.hex_decode[s: str] : str
s_len := s->len()
if s_len % 2 != 0
panic("invalid hex string passed to str.hex_decode")
out_len := s_len / 2
out := mem.alloc(out_len + 1) as str
for i in 0..out_len
high := str._hex_digit_to_int(s[i * 2])
low := str._hex_digit_to_int(s[i * 2 + 1])
out[i] = (high << 4) | low
out[out_len] = 0
return out
struct str.Builder
data: ptr
size: i64
capacity: i64
func str.Builder._grow[b: str.Builder, needed: i64] : void
if b->size + needed > b->capacity
new_capacity := 64
if b->capacity != 0
new_capacity = b->capacity * 2
while new_capacity < b->size + needed
new_capacity = new_capacity * 2
b->data = b->data->realloc(new_capacity)
b->capacity = new_capacity
func str.Builder.append_char[b: str.Builder, c: u8] : void
b->_grow(1)
b->data[b->size] = c
b->size += 1
func str.Builder.append[b: str.Builder, s: str] : void
len := s->len()
b->_grow(len)
mem.copy(s as ptr, b->data + b->size, len)
b->size += len
func str.Builder.build[b: str.Builder] : str
s := mem.alloc(b->size + 1) as str
mem.copy(b->data, s as ptr, b->size)
s[b->size] = 0
return s
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

131
test.zr
View File

@@ -1,63 +1,80 @@
func run_test[x: str] : void include "$/io.zr"
let build_blacklist: Array = ["examples/puzzles", "examples/raylib.zr", "examples/chip8.zr", "examples/sqlite_todo.zr"] include "$/os.zr"
let run_blacklist: Array = ["/aoc", "guess_number.zr", "tcp_server.zr"]
for i in 0..build_blacklist->size struct TestRunner
if str.equal(x, array.nth(build_blacklist, i)) build_blacklist: Array
io.print("Skipping ") run_blacklist: Array
io.print(x) custom_build_flags: HashMap
io.println("...") custom_run_args: HashMap
return 0
io.print("Building ") func TestRunner.run_directory[tr: TestRunner, dir: str] : void
io.print(x) files, ok := os.list_directory(dir)
io.print("... ") if !ok
panic("failed to open test directory")
let build_start_time: i64 = os.time()
if os.shell(str.concat("./target/release/zern ", x)) != 0
os.exit(1)
let build_end_time: i64 = os.time()
io.print_i64(build_end_time - build_start_time)
io.println("ms")
for i in 0..run_blacklist->size
if str.find(x, array.nth(run_blacklist, i)) != -1
io.print("Skipping ")
io.print(x)
io.println("...")
return 0
io.print("Running ")
io.print(x)
io.println("...")
let run_cmd: str = "./out"
if str.equal(x, "examples/curl.zr")
run_cmd = str.concat(run_cmd, " http://example.com")
else if str.equal(x, "examples/tokenizer.zr")
run_cmd = str.concat(run_cmd, " test.zr")
let run_start_time: i64 = os.time()
if os.shell(run_cmd) != 0
os.exit(1)
let run_end_time: i64 = os.time()
io.print("Running ")
io.print(x)
io.print(" took ")
io.print_i64(run_end_time - run_start_time)
io.println("ms")
func run_directory[dir: str] : void
let files: Array = os.listdir(dir)
for i in 0..files->size for i in 0..files->size
run_test(str.concat(dir, array.nth(files, i))) joined := dir->concat("/")->concat(files->nth(i))
if os.is_a_directory(joined)
array.free(files) tr->run_directory(joined)
else
tr->run_test(joined)
func TestRunner.run_test[tr: TestRunner, x: str] : void
name := os.basename(x)
if tr->build_blacklist->contains_str(name)
io.printf("Skipping %s...\n", x)
return
io.printf("Building %s...\n", x)
build_cmd := "./target/release/zern "->concat(x)
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)
if !ok || status != 0
os.exit(1)
build_end_time := os.time()
io.printf("%dms\n", build_end_time - build_start_time)
if tr->run_blacklist->contains_str(name)
io.printf("Skipping %s...\n", x)
return
io.printf("Running %s...\n", x)
run_cmd := "./out"
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)
if !ok || status != 0
io.println("Test failed.")
os.exit(1)
run_end_time := os.time()
io.printf("Running %s took %dms\n", x, run_end_time - run_start_time)
func main[] : i64 func main[] : i64
os.shell("cargo build --release") status, ok := os.run_shell_command("cargo build --release")
if !ok || status != 0
os.exit(1)
run_directory("examples/") tr := new TestRunner
run_directory("examples/puzzles/") tr->build_blacklist = []
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")
tr->run_directory("examples")
io.println("DONE")