From: Bryan English Date: Tue, 10 Feb 2026 04:09:09 +0000 (+0000) Subject: de-monorepo X-Git-Url: https://rethought.computer/gitweb//gitweb//git?a=commitdiff_plain;h=dca335d4b6e4842b4552332e49479fd1ff1b0c21;p=sorel-lang.git de-monorepo Moves all the language stuff to the top level, since this repo is now just for the Sorel language. Website commits were removed, and preserved in a separate repo. --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a41520b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target +qemu/machine +*.o +*.asm +*.out diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..47524a9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,143 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sorel-ir" +version = "0.1.0" +dependencies = [ + "serde", + "serde_derive", + "serde_yaml", +] + +[[package]] +name = "sorelc" +version = "0.1.0" +dependencies = [ + "anyhow", + "sorel-ir", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fae2a78 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +resolver = "3" +members = ["sorel-ir","sorelc"] + + +[workspace.dependencies] +sorel-ir = { path = "./sorel-ir", version = "0.1.0" } diff --git a/README.md b/README.md new file mode 100644 index 0000000..40b5544 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# sorel + +The name means "Stack-Oriented Rethought Language". + + +## TODO + +* [x] Imports +* [x] Syscalls +* [x] Loops +* [ ] Structs +* [ ] many, many more things + +## Helpful External Resources + +* https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html +* https://godbolt.org/ +* https://projectf.io/posts/riscv-cheat-sheet/ +* https://github.com/dvoytik/riscv-cheats diff --git a/docs/hacking.md b/docs/hacking.md new file mode 100644 index 0000000..fe7323d --- /dev/null +++ b/docs/hacking.md @@ -0,0 +1,74 @@ +## Developer setup + +### Nix / NixOS + +If you're using `Nix` or `NixOS`, the provided `flake.nix` should give you a good start. +You'll need a recent Rust toolchain, for which you can use [`rustup`](https://rustup.sh). +Currently that's assumed to be included on the system. +Also, the test scripts currently assume binfmt is set up for RISC-V 64 (or that you're running on RISC-V 64). +Assuming you're not on RISC-V 64 (most people aren't), you can add the following to your Nix `configuration.nix`: + +``` + + boot.binfmt.emulatedSystems = [ + "riscv64-linux" + # .... + ]; +``` + +### Ubuntu + +``` +# On non RISC-V 64 systems: +apt install -y build-essential gdb crossbuild-essential-riscv64 qemu-system qemu-user qemu-user-binfmt +``` + +## Building the Compiler + +The compiler is written in Rust, and can be compiled with + +``` +cargo build +``` + +This puts the compiler binary at `target/debug/sorelc`. +Release builds can also be created in the usual Rust/`cargo` fasion. + +Note that if you change any stdlib files, you'll need to rebuild. + +## Compiling a Sorel Program + +Currently, `sorelc` takes only one argument, and that's an entrypoint for your program. +Any words in that file outside word definitions will constitute the "main" of your program. + +This will create a file of the same name alongside it, with the extension changed to `asm`. +This is a RISC-V 64 assembly file, suitable for use with GNU Assembler. +You can assemble this as normal, and run your program. + +## Testing + +The `tests` directory contains a shell script you can invoke with `sh test.sh`. +It should run just fine given the above setup, provided you've done a `cargo build` to build the compiler. +This will compile the test binary using `sorelc` and your local `riscv64` assembler. +It will run the binary using binfmt, which instructs the kernel to run your binary using `qemu-user`. + +### Debugging + +You can use `gdb`! +Unfortunately, with `qemu-user` we can't just load the binary directly into `gdb`. +Instead, we'll need to run `qemu-user` on a RISC-V 64 binary with a debugger port open: + +``` +qemu-riscv64 -g 4567 ./test1.out +``` + +Then, you can fire up `gdb`, and once inside it's prompt, connect to the "remote" port. + +``` +target remote :4567 +``` + +Then you can use normal gdb commands! +The `tui layout asm` is highly recommended. +Note that if you'l linking `putstack.c`, that will use libc, which drastically inflates the binary and hijack's init. +This can make things quite a pain in `gdb`, so try to build without it (i.e. uncomment the LD line and comment out the CC line in `test.sh`, and remove all references to `putstack` in code). diff --git a/docs/language_overview.md b/docs/language_overview.md new file mode 100644 index 0000000..cb94446 --- /dev/null +++ b/docs/language_overview.md @@ -0,0 +1,68 @@ +# Language Overview + +**Sorel** is a concatenative stack language, similar in form to Forth. +Unlike most Forth variants, it's strictly a compiled language, with no interpreter provided at run time. +Today, it targets only 64-bit RISC-V architectures. +It has a small standard library, that does not use libc. +Instead it makes system calls directly, currently only supporting Linux. + +Like in Forth, code is written in Reverse Polish Notation. +Every "word" corresponds to a function that manipulates the stack in some way. +Unlike in Forth, strings count as a single word, and are written much like they are in C. + +## The Stack + +All Forths have a stack, and Sorel is no exception. +Since Sorel's function calls follow the C ABI, there's no need for the traditional Forth return stack. +There's only the data stack. +Much like call stacks, it grows downward. +Each stack cell is exactly 64 bits wide, regardless of type. +For smaller types, the values are padded with 0s. +Note that RISC-V systems are little-endian. + +## Type System + +All values are treated as 64-bit unsigned integers unles otherwise indicated. +Number literals can provide type information after a colon. +For example, `-37:i32` will add -37 as a signed 32-bit integer onto the stack. + +Values themselves don't have types. +Operations have types. +In other words, values are always just bits in memory, and only have a "type" when they are treated as such by a word manipulating the stack or heap memory. + +> This is, of course, very dangerous. +> A planned improvement is to add static type-checking support via type comments. + +Complex types like arrays and structs don't exist (yet), but they can be maniplulated through direct memory operations. +See `std:mem` for details. + +String literals, when pushed onto the stack, appear as pointers to null-terminated UTF-8 representations of them. +Other than that, strings are just like any other complex type, in that there's no special support (yet). + +## Module System + +Sorel supports import statements of the form `import "./relative/path/to/file.sorel"`, with the exception of standard library imports (see below). +Note that `import` is _not_ executed at run-time, and instead is evaluated as a pre-processing step. +To make that clear, note that the import specifier comes _after_ the keyword. +This indicates that `import` is not operating on a value in the stack. + +Similarly, words may be exported via `export word_name`. +Only words deliberately exported will be made available to other files via `import`. + +Word invocations _outside_ word defintions will be executed as the program's entrypoint function (i.e. "main"), but _only_ if those invocations are in the entrypoint module. +This means any module can have word invocations inside it that aren't executed when it's not the entrypoint. +This is useful for writing tests. + +## Built-in Words + + + +## Standard Library + +Built-in words are insufficient to create most programs. +A standard library is provided with exported words to enable higher level operations. +It's implemented primarily by wrapping system calls. + +The following standard library modules are provided, and can be imported by specificying their name as the import specifier: +* **`std:mem`** : This module contains words to allocate and free memory. +* **`std:out`** : This module contains words to print numbers and strings to stdout. diff --git a/examples/alloc.sorel b/examples/alloc.sorel new file mode 100644 index 0000000..ab014eb --- /dev/null +++ b/examples/alloc.sorel @@ -0,0 +1,18 @@ +\ vim: filetype=forth + +: mmap 9 sys6 ; + +: PROT_READ 1 ; +: PROT_WRITE 2 ; +: MAP_PRIVATE 2 ; +: MAP_ANONYMOUS 32 ; + +: ALLOC_PROT PROT_READ PROT_WRITE | ; +: ALLOC_MAP MAP_PRIVATE MAP_ANONYMOUS | ; + +: alloc 0 swap ALLOC_PROT ALLOC_MAP -1:i16 0 mmap ; + +1024 alloc +putn +swap +putn diff --git a/examples/fib.sorel b/examples/fib.sorel new file mode 100644 index 0000000..ef22ee9 --- /dev/null +++ b/examples/fib.sorel @@ -0,0 +1,25 @@ +\ vim: filetype=forth + +import "./put2.rel" + +: fib + dup 1 > if + dup 1 - fib + swap 2 - fib + + + endif +; + +0 fib putn +1 fib putn +2 fib putn +3 fib putn +4 fib putn +5 fib putn +6 fib putn +7 fib putn +8 fib putn +9 fib putn +10 fib putn + +5 fib 6 fib put2 diff --git a/examples/full-fib/compile.sh b/examples/full-fib/compile.sh new file mode 100644 index 0000000..6ef003e --- /dev/null +++ b/examples/full-fib/compile.sh @@ -0,0 +1,4 @@ +../target/debug/sorelc fib.sorel +riscv64-unknown-linux-gnu-as -o fib.o fib.asm +riscv64-unknown-linux-gnu-cc -O1 -no-pie -o test.out fib.o putn.c -nostartfiles +./test.out diff --git a/examples/full-fib/fib.rel b/examples/full-fib/fib.rel new file mode 100644 index 0000000..2407f15 --- /dev/null +++ b/examples/full-fib/fib.rel @@ -0,0 +1,24 @@ +\ foo bar +: fib + dup + 1 + > + if + dup + 1 - fib + swap + 2 + - + fib + + + endif +; + +0 fib putn drop +1 fib putn drop +2 fib putn drop +3 fib putn drop +4 fib putn drop +5 fib putn drop +6 fib putn drop +7 fib putn drop diff --git a/examples/full-fib/putn.c b/examples/full-fib/putn.c new file mode 100644 index 0000000..201050c --- /dev/null +++ b/examples/full-fib/putn.c @@ -0,0 +1,16 @@ +#include + +extern unsigned long data_stack_end; +register unsigned long * stack_pointer asm("s2"); + +void putn() { + unsigned long * stack_index = &data_stack_end; + printf("stack: "); + while (stack_index != stack_pointer) { + printf("%ld ", *stack_index); + stack_index -= 1; + } + printf("%ld\n", *stack_pointer); +} + + diff --git a/examples/put2.sorel b/examples/put2.sorel new file mode 100644 index 0000000..4f756c2 --- /dev/null +++ b/examples/put2.sorel @@ -0,0 +1,7 @@ +\ vim: filetype=forth + +: put2 putn putn ; + +: foobar dup dup ; + +export put2 diff --git a/examples/syscalls.sorel b/examples/syscalls.sorel new file mode 100644 index 0000000..9d7b88c --- /dev/null +++ b/examples/syscalls.sorel @@ -0,0 +1,8 @@ +\ vim: filetype=forth + +: getpid + 39 sys0 + drop +; + +getpid putn diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1384c7f --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1765425892, + "narHash": "sha256-jlQpSkg2sK6IJVzTQBDyRxQZgKADC2HKMRfGCSgNMHo=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5d6bdbddb4695a62f0d00a3620b37a15275a5093", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8aacadf --- /dev/null +++ b/flake.nix @@ -0,0 +1,23 @@ +{ + description = "uxn11"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + }; + + outputs = {nixpkgs, ...}: let + system = "x86_64-linux"; + pkgs = import nixpkgs { + inherit system; + }; + in { + devShells.${system}.default = pkgs.mkShell { + packages = [ + pkgs.pkgsCross.riscv64.buildPackages.gcc + pkgs.pkgsCross.riscv64.buildPackages.gdb + pkgs.gdb + pkgs.qemu + ]; + }; + }; +} diff --git a/qemu/run.sh b/qemu/run.sh new file mode 100644 index 0000000..a9d3a7e --- /dev/null +++ b/qemu/run.sh @@ -0,0 +1,17 @@ + +qemu-system-riscv64 \ + -machine virt \ + -cpu rv64 \ + -m 1G \ + -device virtio-blk-device,drive=hd \ + -drive file=machine/overlay.qcow2,if=none,id=hd \ + -device virtio-net-device,netdev=net \ + -netdev user,id=net,hostfwd=tcp::2222-:22 \ + -kernel machine/u-boot-qemu/uboot.elf \ + -object rng-random,filename=/dev/urandom,id=rng \ + -device virtio-rng-device,rng=rng \ + -append "root=LABEL=rootfs console=ttyS0" \ + -nographic \ + +echo "now do:" +echo " ssh debian@localhost -p 2222" diff --git a/qemu/setup.sh b/qemu/setup.sh new file mode 100644 index 0000000..b206ac6 --- /dev/null +++ b/qemu/setup.sh @@ -0,0 +1,18 @@ + +mkdir machine +cd machine +wget "https://gitlab.com/api/v4/projects/giomasce%2Fdqib/jobs/artifacts/master/download?job=convert_riscv64-virt" -O debian-rv64.zip + +unzip ./debian-rv64.zip + +# Grab the URL from https://packages.debian.org/sid/u-boot-qemu +wget "http://ftp.us.debian.org/debian/pool/main/u/u-boot/u-boot-qemu_2025.01-3.1_all.deb" -O u-boot-qemu.deb +mkdir u-boot-qemu +cd u-boot-qemu +ar -x ../u-boot-qemu.deb +tar xvf data.tar.xz +cp ./usr/lib/u-boot/qemu-riscv64_smode/uboot.elf ./uboot.elf + +cd .. +qemu-img create -o backing_file=./dqib_riscv64-virt/image.qcow2,backing_fmt=qcow2 -f qcow2 overlay.qcow2 + diff --git a/rel-lang/.gitignore b/rel-lang/.gitignore deleted file mode 100644 index a41520b..0000000 --- a/rel-lang/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -target -qemu/machine -*.o -*.asm -*.out diff --git a/rel-lang/Cargo.lock b/rel-lang/Cargo.lock deleted file mode 100644 index 47524a9..0000000 --- a/rel-lang/Cargo.lock +++ /dev/null @@ -1,143 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sorel-ir" -version = "0.1.0" -dependencies = [ - "serde", - "serde_derive", - "serde_yaml", -] - -[[package]] -name = "sorelc" -version = "0.1.0" -dependencies = [ - "anyhow", - "sorel-ir", -] - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" diff --git a/rel-lang/Cargo.toml b/rel-lang/Cargo.toml deleted file mode 100644 index fae2a78..0000000 --- a/rel-lang/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] - -resolver = "3" -members = ["sorel-ir","sorelc"] - - -[workspace.dependencies] -sorel-ir = { path = "./sorel-ir", version = "0.1.0" } diff --git a/rel-lang/README.md b/rel-lang/README.md deleted file mode 100644 index 40b5544..0000000 --- a/rel-lang/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# sorel - -The name means "Stack-Oriented Rethought Language". - - -## TODO - -* [x] Imports -* [x] Syscalls -* [x] Loops -* [ ] Structs -* [ ] many, many more things - -## Helpful External Resources - -* https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html -* https://godbolt.org/ -* https://projectf.io/posts/riscv-cheat-sheet/ -* https://github.com/dvoytik/riscv-cheats diff --git a/rel-lang/docs/hacking.md b/rel-lang/docs/hacking.md deleted file mode 100644 index fe7323d..0000000 --- a/rel-lang/docs/hacking.md +++ /dev/null @@ -1,74 +0,0 @@ -## Developer setup - -### Nix / NixOS - -If you're using `Nix` or `NixOS`, the provided `flake.nix` should give you a good start. -You'll need a recent Rust toolchain, for which you can use [`rustup`](https://rustup.sh). -Currently that's assumed to be included on the system. -Also, the test scripts currently assume binfmt is set up for RISC-V 64 (or that you're running on RISC-V 64). -Assuming you're not on RISC-V 64 (most people aren't), you can add the following to your Nix `configuration.nix`: - -``` - - boot.binfmt.emulatedSystems = [ - "riscv64-linux" - # .... - ]; -``` - -### Ubuntu - -``` -# On non RISC-V 64 systems: -apt install -y build-essential gdb crossbuild-essential-riscv64 qemu-system qemu-user qemu-user-binfmt -``` - -## Building the Compiler - -The compiler is written in Rust, and can be compiled with - -``` -cargo build -``` - -This puts the compiler binary at `target/debug/sorelc`. -Release builds can also be created in the usual Rust/`cargo` fasion. - -Note that if you change any stdlib files, you'll need to rebuild. - -## Compiling a Sorel Program - -Currently, `sorelc` takes only one argument, and that's an entrypoint for your program. -Any words in that file outside word definitions will constitute the "main" of your program. - -This will create a file of the same name alongside it, with the extension changed to `asm`. -This is a RISC-V 64 assembly file, suitable for use with GNU Assembler. -You can assemble this as normal, and run your program. - -## Testing - -The `tests` directory contains a shell script you can invoke with `sh test.sh`. -It should run just fine given the above setup, provided you've done a `cargo build` to build the compiler. -This will compile the test binary using `sorelc` and your local `riscv64` assembler. -It will run the binary using binfmt, which instructs the kernel to run your binary using `qemu-user`. - -### Debugging - -You can use `gdb`! -Unfortunately, with `qemu-user` we can't just load the binary directly into `gdb`. -Instead, we'll need to run `qemu-user` on a RISC-V 64 binary with a debugger port open: - -``` -qemu-riscv64 -g 4567 ./test1.out -``` - -Then, you can fire up `gdb`, and once inside it's prompt, connect to the "remote" port. - -``` -target remote :4567 -``` - -Then you can use normal gdb commands! -The `tui layout asm` is highly recommended. -Note that if you'l linking `putstack.c`, that will use libc, which drastically inflates the binary and hijack's init. -This can make things quite a pain in `gdb`, so try to build without it (i.e. uncomment the LD line and comment out the CC line in `test.sh`, and remove all references to `putstack` in code). diff --git a/rel-lang/docs/language_overview.md b/rel-lang/docs/language_overview.md deleted file mode 100644 index cb94446..0000000 --- a/rel-lang/docs/language_overview.md +++ /dev/null @@ -1,68 +0,0 @@ -# Language Overview - -**Sorel** is a concatenative stack language, similar in form to Forth. -Unlike most Forth variants, it's strictly a compiled language, with no interpreter provided at run time. -Today, it targets only 64-bit RISC-V architectures. -It has a small standard library, that does not use libc. -Instead it makes system calls directly, currently only supporting Linux. - -Like in Forth, code is written in Reverse Polish Notation. -Every "word" corresponds to a function that manipulates the stack in some way. -Unlike in Forth, strings count as a single word, and are written much like they are in C. - -## The Stack - -All Forths have a stack, and Sorel is no exception. -Since Sorel's function calls follow the C ABI, there's no need for the traditional Forth return stack. -There's only the data stack. -Much like call stacks, it grows downward. -Each stack cell is exactly 64 bits wide, regardless of type. -For smaller types, the values are padded with 0s. -Note that RISC-V systems are little-endian. - -## Type System - -All values are treated as 64-bit unsigned integers unles otherwise indicated. -Number literals can provide type information after a colon. -For example, `-37:i32` will add -37 as a signed 32-bit integer onto the stack. - -Values themselves don't have types. -Operations have types. -In other words, values are always just bits in memory, and only have a "type" when they are treated as such by a word manipulating the stack or heap memory. - -> This is, of course, very dangerous. -> A planned improvement is to add static type-checking support via type comments. - -Complex types like arrays and structs don't exist (yet), but they can be maniplulated through direct memory operations. -See `std:mem` for details. - -String literals, when pushed onto the stack, appear as pointers to null-terminated UTF-8 representations of them. -Other than that, strings are just like any other complex type, in that there's no special support (yet). - -## Module System - -Sorel supports import statements of the form `import "./relative/path/to/file.sorel"`, with the exception of standard library imports (see below). -Note that `import` is _not_ executed at run-time, and instead is evaluated as a pre-processing step. -To make that clear, note that the import specifier comes _after_ the keyword. -This indicates that `import` is not operating on a value in the stack. - -Similarly, words may be exported via `export word_name`. -Only words deliberately exported will be made available to other files via `import`. - -Word invocations _outside_ word defintions will be executed as the program's entrypoint function (i.e. "main"), but _only_ if those invocations are in the entrypoint module. -This means any module can have word invocations inside it that aren't executed when it's not the entrypoint. -This is useful for writing tests. - -## Built-in Words - - - -## Standard Library - -Built-in words are insufficient to create most programs. -A standard library is provided with exported words to enable higher level operations. -It's implemented primarily by wrapping system calls. - -The following standard library modules are provided, and can be imported by specificying their name as the import specifier: -* **`std:mem`** : This module contains words to allocate and free memory. -* **`std:out`** : This module contains words to print numbers and strings to stdout. diff --git a/rel-lang/examples/alloc.sorel b/rel-lang/examples/alloc.sorel deleted file mode 100644 index ab014eb..0000000 --- a/rel-lang/examples/alloc.sorel +++ /dev/null @@ -1,18 +0,0 @@ -\ vim: filetype=forth - -: mmap 9 sys6 ; - -: PROT_READ 1 ; -: PROT_WRITE 2 ; -: MAP_PRIVATE 2 ; -: MAP_ANONYMOUS 32 ; - -: ALLOC_PROT PROT_READ PROT_WRITE | ; -: ALLOC_MAP MAP_PRIVATE MAP_ANONYMOUS | ; - -: alloc 0 swap ALLOC_PROT ALLOC_MAP -1:i16 0 mmap ; - -1024 alloc -putn -swap -putn diff --git a/rel-lang/examples/fib.sorel b/rel-lang/examples/fib.sorel deleted file mode 100644 index ef22ee9..0000000 --- a/rel-lang/examples/fib.sorel +++ /dev/null @@ -1,25 +0,0 @@ -\ vim: filetype=forth - -import "./put2.rel" - -: fib - dup 1 > if - dup 1 - fib - swap 2 - fib - + - endif -; - -0 fib putn -1 fib putn -2 fib putn -3 fib putn -4 fib putn -5 fib putn -6 fib putn -7 fib putn -8 fib putn -9 fib putn -10 fib putn - -5 fib 6 fib put2 diff --git a/rel-lang/examples/full-fib/compile.sh b/rel-lang/examples/full-fib/compile.sh deleted file mode 100644 index 6ef003e..0000000 --- a/rel-lang/examples/full-fib/compile.sh +++ /dev/null @@ -1,4 +0,0 @@ -../target/debug/sorelc fib.sorel -riscv64-unknown-linux-gnu-as -o fib.o fib.asm -riscv64-unknown-linux-gnu-cc -O1 -no-pie -o test.out fib.o putn.c -nostartfiles -./test.out diff --git a/rel-lang/examples/full-fib/fib.rel b/rel-lang/examples/full-fib/fib.rel deleted file mode 100644 index 2407f15..0000000 --- a/rel-lang/examples/full-fib/fib.rel +++ /dev/null @@ -1,24 +0,0 @@ -\ foo bar -: fib - dup - 1 - > - if - dup - 1 - fib - swap - 2 - - - fib - + - endif -; - -0 fib putn drop -1 fib putn drop -2 fib putn drop -3 fib putn drop -4 fib putn drop -5 fib putn drop -6 fib putn drop -7 fib putn drop diff --git a/rel-lang/examples/full-fib/putn.c b/rel-lang/examples/full-fib/putn.c deleted file mode 100644 index 201050c..0000000 --- a/rel-lang/examples/full-fib/putn.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -extern unsigned long data_stack_end; -register unsigned long * stack_pointer asm("s2"); - -void putn() { - unsigned long * stack_index = &data_stack_end; - printf("stack: "); - while (stack_index != stack_pointer) { - printf("%ld ", *stack_index); - stack_index -= 1; - } - printf("%ld\n", *stack_pointer); -} - - diff --git a/rel-lang/examples/put2.sorel b/rel-lang/examples/put2.sorel deleted file mode 100644 index 4f756c2..0000000 --- a/rel-lang/examples/put2.sorel +++ /dev/null @@ -1,7 +0,0 @@ -\ vim: filetype=forth - -: put2 putn putn ; - -: foobar dup dup ; - -export put2 diff --git a/rel-lang/examples/syscalls.sorel b/rel-lang/examples/syscalls.sorel deleted file mode 100644 index 9d7b88c..0000000 --- a/rel-lang/examples/syscalls.sorel +++ /dev/null @@ -1,8 +0,0 @@ -\ vim: filetype=forth - -: getpid - 39 sys0 - drop -; - -getpid putn diff --git a/rel-lang/flake.lock b/rel-lang/flake.lock deleted file mode 100644 index 1384c7f..0000000 --- a/rel-lang/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1765425892, - "narHash": "sha256-jlQpSkg2sK6IJVzTQBDyRxQZgKADC2HKMRfGCSgNMHo=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "5d6bdbddb4695a62f0d00a3620b37a15275a5093", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/rel-lang/flake.nix b/rel-lang/flake.nix deleted file mode 100644 index 8aacadf..0000000 --- a/rel-lang/flake.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ - description = "uxn11"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - }; - - outputs = {nixpkgs, ...}: let - system = "x86_64-linux"; - pkgs = import nixpkgs { - inherit system; - }; - in { - devShells.${system}.default = pkgs.mkShell { - packages = [ - pkgs.pkgsCross.riscv64.buildPackages.gcc - pkgs.pkgsCross.riscv64.buildPackages.gdb - pkgs.gdb - pkgs.qemu - ]; - }; - }; -} diff --git a/rel-lang/qemu/run.sh b/rel-lang/qemu/run.sh deleted file mode 100644 index a9d3a7e..0000000 --- a/rel-lang/qemu/run.sh +++ /dev/null @@ -1,17 +0,0 @@ - -qemu-system-riscv64 \ - -machine virt \ - -cpu rv64 \ - -m 1G \ - -device virtio-blk-device,drive=hd \ - -drive file=machine/overlay.qcow2,if=none,id=hd \ - -device virtio-net-device,netdev=net \ - -netdev user,id=net,hostfwd=tcp::2222-:22 \ - -kernel machine/u-boot-qemu/uboot.elf \ - -object rng-random,filename=/dev/urandom,id=rng \ - -device virtio-rng-device,rng=rng \ - -append "root=LABEL=rootfs console=ttyS0" \ - -nographic \ - -echo "now do:" -echo " ssh debian@localhost -p 2222" diff --git a/rel-lang/qemu/setup.sh b/rel-lang/qemu/setup.sh deleted file mode 100644 index b206ac6..0000000 --- a/rel-lang/qemu/setup.sh +++ /dev/null @@ -1,18 +0,0 @@ - -mkdir machine -cd machine -wget "https://gitlab.com/api/v4/projects/giomasce%2Fdqib/jobs/artifacts/master/download?job=convert_riscv64-virt" -O debian-rv64.zip - -unzip ./debian-rv64.zip - -# Grab the URL from https://packages.debian.org/sid/u-boot-qemu -wget "http://ftp.us.debian.org/debian/pool/main/u/u-boot/u-boot-qemu_2025.01-3.1_all.deb" -O u-boot-qemu.deb -mkdir u-boot-qemu -cd u-boot-qemu -ar -x ../u-boot-qemu.deb -tar xvf data.tar.xz -cp ./usr/lib/u-boot/qemu-riscv64_smode/uboot.elf ./uboot.elf - -cd .. -qemu-img create -o backing_file=./dqib_riscv64-virt/image.qcow2,backing_fmt=qcow2 -f qcow2 overlay.qcow2 - diff --git a/rel-lang/sorel-ir/Cargo.toml b/rel-lang/sorel-ir/Cargo.toml deleted file mode 100644 index 5618a61..0000000 --- a/rel-lang/sorel-ir/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "sorel-ir" -version = "0.1.0" -edition = "2024" - -[dependencies] -serde = "1.0.228" -serde_derive = "1.0.228" -serde_yaml = "0.9.34" diff --git a/rel-lang/sorel-ir/src/lib.rs b/rel-lang/sorel-ir/src/lib.rs deleted file mode 100644 index 1a3ec27..0000000 --- a/rel-lang/sorel-ir/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde_yaml::{from_str, to_string, Error}; -use serde_derive::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum IR { - Label(String), - Call(String), - Ret, - StackPush(u64), - StackPushString(String), // refers to string label, not the string itself - StringDef(String, String), // first is string label, second is string value - - // These next ones should always be inlined, so they're in IR. - Load, // @ ( addr -- x ) -- Fetch memory contents at addr - Load8, - Load16, - Load32, - Store, // ! ( x addr -- ) -- Store x at addr - Store8, - Store16, - Store32, - - // These ones might not be inlined, but should be built-in, so a compiler might - // turn this into `Call(String)` before translating to assembly/machine-code, but - // an IR interpreter may just execute them. - AddU64, - SubtractU64, - MultiplyU64, - DivideU64, - ModU64, - Equals, - GreaterThan, - LessThan, - BitwiseOr, - Dup, - Swap, - Drop, - Over, - Rot, - StackPointer, - If, - Else, - EndIf, - Loop, - EndLoop, - - // System calls - Sys0, - Sys1, - Sys2, - Sys3, - Sys4, - Sys5, - Sys6, -} - -// This is like an .o file. -#[derive(Serialize, Deserialize, Debug)] -pub struct IRObject { - pub text: Vec, - pub data: Vec, -} - -impl IRObject { - pub fn to_s(&self) -> Result { - to_string(self) - } - - pub fn from_s(source: &str) -> Result { - from_str(source) - } -} diff --git a/rel-lang/sorelc/Cargo.toml b/rel-lang/sorelc/Cargo.toml deleted file mode 100644 index 7859ecf..0000000 --- a/rel-lang/sorelc/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "sorelc" -version = "0.1.0" -edition = "2024" - -[dependencies] -sorel-ir = { workspace = true } -anyhow = "1.0.100" diff --git a/rel-lang/sorelc/src/ir.rs b/rel-lang/sorelc/src/ir.rs deleted file mode 100644 index 61e6115..0000000 --- a/rel-lang/sorelc/src/ir.rs +++ /dev/null @@ -1,307 +0,0 @@ -use crate::parser::Module; -use crate::tokenizer::{Token, tokenize}; -use sorel_ir::*; - -use std::collections::{HashSet, HashMap}; -use std::path::PathBuf; -use std::rc::Rc; -use std::include_str; - -use anyhow::{Result, bail}; - -macro_rules! push_num { - ($num:ident) => { IR::StackPush(*$num as u64) }; - ($num:ident, $num_typ:ty) => { IR::StackPush(*$num as $num_typ as u64) }; -} - -#[derive(Debug, Default)] -struct IRModule { - data: Vec, - text: Vec, - imports: Vec>, - exports: Vec, - externs: Vec, - // TODO these next two should form an enum, not two options - source_file: Option, - std_specifier: Option, - number: usize, -} - -impl IRModule { - fn get_label_for_call(&self, name: &String) -> String { - if self.externs.contains(name) { - return name.clone(); - } - let mut found: Option = None; - for imported in &self.imports { - if imported.exports.contains(name) { - found = Some(imported.number); - // Don't break here, since the last one should win. - } - } - if let Some(found) = found { - format!("_m{}_{}", found, name) - } else { - // TODO check if it's even a word locally. If not, bail. - format!("_m{}_{}", self.number, name) - } - } - - fn get_label(&self, name: &String) -> String { - format!("_m{}_{}", self.number, name) - } -} - -#[derive(Default)] -struct ImportTree { - data: Vec, - text: Vec, - all_modules: HashMap>, - all_exports: HashSet, - entrypoint: Rc, - module_count: usize, - collapse_seen: HashSet, -} - -fn std_import(specifier: &str) -> Result<&str> { - match specifier { - "std:mem" => Ok(include_str!("../../stdlib/mem.sorel")), - "std:out" => Ok(include_str!("../../stdlib/out.sorel")), - "std:string" => Ok(include_str!("../../stdlib/string.sorel")), - "std:process" => Ok(include_str!("../../stdlib/process.sorel")), - _ => bail!("{} is not a standard library module", specifier), - } -} - -impl ImportTree { - fn import(&mut self, importer_dir: &PathBuf, specifier: &str, is_entrypoint: bool) -> Result> { - Ok(if specifier.starts_with("std:") { - if self.all_modules.contains_key(specifier) { - let module = self.all_modules.get(specifier).unwrap().clone(); - return Ok(module); - } - let contents = std_import(specifier)?; - let parsed = &Module::parse(tokenize(&contents)?, is_entrypoint)?; - let module = self.generate_internal(None, Some(specifier.to_string()), parsed); - let module = Rc::new(module); - self.all_modules.insert(specifier.to_string(), module.clone()); - module - } else { - let mut path = PathBuf::from(specifier); - if path.is_relative() { - let mut new_path = importer_dir.clone(); - new_path.push(path); - path = new_path.canonicalize()?; - } - let path_key = path.to_string_lossy().to_string(); - if self.all_modules.contains_key(&path_key) { - let module = self.all_modules.get(&path_key).unwrap().clone(); - return Ok(module); - } - - let contents = &std::fs::read_to_string(&path)?; - let tokens = tokenize(&contents)?; - let parsed = &Module::parse(tokens, is_entrypoint)?; - - let module = self.generate_internal(Some(path), None, parsed); - let module = Rc::new(module); - self.all_modules.insert(path_key, module.clone()); - if is_entrypoint { - self.entrypoint = module.clone(); - } - module - }) - - } - - fn generate_internal(&mut self, path: Option, std_specifier: Option, module: &Module) -> IRModule { - // Eventually these will end up being sections in assembly - let mut text = vec![]; - let mut data = vec![]; - - let mut imports = vec![]; - if let Some(ref path) = path { - module.imports.iter().for_each(|imported| { - if let Some(parent_path) = path.parent() { - match self.import(&parent_path.to_path_buf(), imported, false) { - Ok(module) => { - imports.push(module); - }, - Err(msg) => { - eprintln!("{}", msg); - } - } - } else { - } - }); - } else { - // We're in a stdlib module, which can only import other stdlib - // modules. - module.imports.iter().for_each(|imported| { - match self.import(&PathBuf::new(), imported, false) { - Ok(module) => { - imports.push(module); - }, - Err(msg) => { - eprintln!("{}", msg); - } - } - }); - } - - let exports: Vec<_> = module.exports.iter().map(|s| { - self.all_exports.insert(s.to_string()); - s.to_string() - }).collect(); - - let externs = module.externs.iter().map(|s| s.to_string()).collect(); - - text.push(module.words.iter().map(|def| { - let mut body = def.instructions.iter().map(|inst| { - let mapped_ir = match inst { - Token::Word(word) => { - match *word { - "@" => IR::Load, - "@:8" => IR::Load8, - "@:16" => IR::Load16, - "@:32" => IR::Load32, - "!" => IR::Store, - "!:8" => IR::Store8, - "!:16" => IR::Store16, - "!:32" => IR::Store32, - "dup" => IR::Dup, - "swap" => IR::Swap, - "drop" => IR::Drop, - "over" => IR::Over, - "rot" => IR::Rot, - "sp" => IR::StackPointer, - "if" => IR::If, - "else" => IR::Else, - "endif" => IR::EndIf, - "loop" => IR::Loop, - "endloop" => IR::EndLoop, - "=" => IR::Equals, - ">" => IR::GreaterThan, - "<" => IR::LessThan, - "+" => IR::AddU64, - "-" => IR::SubtractU64, - "*" => IR::MultiplyU64, - "/" => IR::DivideU64, - "%" => IR::ModU64, - "|" => IR::BitwiseOr, - "sys0" => IR::Sys0, - "sys1" => IR::Sys1, - "sys2" => IR::Sys2, - "sys3" => IR::Sys3, - "sys4" => IR::Sys4, - "sys5" => IR::Sys5, - "sys6" => IR::Sys6, - // TODO num type specfic math like `+:i32`, etc. - _ => IR::Call(String::from(*word)) - } - }, - Token::String(text) => { - let string_label = format!("string_{}", data.len()); - data.push(IR::StringDef(string_label.clone(), String::from(*text))); - IR::StackPushString(string_label) - }, - Token::NumU8(num) => push_num!(num), - Token::NumI8(num) => push_num!(num, u8), - Token::NumU16(num) => push_num!(num), - Token::NumI16(num) => push_num!(num, u16), - Token::NumU32(num) => push_num!(num), - Token::NumI32(num) => push_num!(num, u32), - Token::NumU64(num) => push_num!(num), - Token::NumI64(num) => push_num!(num), - Token::NumF32(num) => push_num!(num), - Token::NumF64(num) => push_num!(num), - }; - mapped_ir - }).collect::>(); - - let mut result = vec![IR::Label(def.name.to_string())]; - result.append(&mut body); - result.push(IR::Ret); - result - }).flatten().collect::>()); - - let number = self.module_count; - self.module_count += 1; - - IRModule { - text: text.into_iter().flatten().collect::>(), - data, - imports, - exports, - externs, - source_file: path, - std_specifier, - number, - } - } - - fn collapse(&mut self, module: Rc) -> Result<()> { - let seen_key = if let Some(source_file) = &module.source_file { - source_file.to_string_lossy().to_string() - } else { - module.std_specifier.clone().unwrap() - }; - if self.collapse_seen.contains(&seen_key) { - return Ok(()) - } - - for imported in module.imports.clone() { - self.collapse(imported)?; - } - - let is_entrypoint = module.source_file == self.entrypoint.source_file; - - let module_number = module.number; - - for string in &module.data { - if let IR::StringDef(name, val) = string { - let new_name = format!("{}_{}", name, module_number); - self.data.push(IR::StringDef(new_name, val.clone())); - } else { - bail!("non-string data"); - } - } - - for instruction in &module.text { - let new_instruction = match instruction { - IR::StackPushString(name) => { - let new_name = format!("{}_{}", name, module_number); - IR::StackPushString(new_name) - }, - IR::Label(name) => { - if is_entrypoint && name == "main" { - instruction.clone() - } else { - IR::Label(module.get_label(name)) - } - }, - IR::Call(name) => { - IR::Call(module.get_label_for_call(name)) - }, - _ => instruction.clone() - }; - self.text.push(new_instruction); - } - - self.collapse_seen.insert(seen_key); - - Ok(()) - } -} - -pub fn compile(path: &str) -> Result { - let dir = std::env::current_dir()?; - let mut tree: ImportTree = Default::default(); - let module = tree.import(&dir, path, true)?; - tree.collapse(module)?; - // TODO remove unused words - Ok(IRObject { - data: tree.data, - text: tree.text, - }) -} diff --git a/rel-lang/sorelc/src/main.rs b/rel-lang/sorelc/src/main.rs deleted file mode 100644 index 724df84..0000000 --- a/rel-lang/sorelc/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod tokenizer; -mod parser; -mod ir; -mod riscv_asm_codegen; - -use anyhow::Result; - -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() -> Result<()> { - let filename = std::env::args().nth(1).expect("must provide a file to compile"); - let module = ir::compile(&filename)?; - let mut generator = riscv_asm_codegen::CodeGen::new(&module, 4096); - let mut asm_path = PathBuf::from(filename); - asm_path.set_extension("asm"); - let mut output = File::create(asm_path)?; - write!(output, "{}\n", generator.assembly()?)?; - Ok(()) -} diff --git a/rel-lang/sorelc/src/parser.rs b/rel-lang/sorelc/src/parser.rs deleted file mode 100644 index 94b53e4..0000000 --- a/rel-lang/sorelc/src/parser.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::tokenizer::Token; -use anyhow::{Result, bail}; - -#[derive(Debug)] -pub struct WordDefinition<'a> { - pub name: &'a str, - pub instructions: Vec>, -} - -#[derive(Debug)] -pub struct Module<'a> { - pub words: Vec>, - pub imports: Vec<&'a str>, - pub exports: Vec<&'a str>, - pub externs: Vec<&'a str>, -} - -impl<'a> Module<'a> { - pub fn parse(input: Vec>, is_entrypoint: bool) -> Result { - let mut result = vec![]; - let mut main = vec![]; - let mut exports = vec![]; - let mut imports = vec![]; - let mut externs = vec![]; - let mut current_word: Option = None; - let mut about_to_start_word_def = false; - let mut last_was_import = false; - let mut last_was_export = false; - let mut last_was_extern = false; - - for token in input { - if about_to_start_word_def { - if let Token::Word(name) = token { - current_word = Some(WordDefinition { - name, - instructions: vec![], - }); - about_to_start_word_def = false; - continue; - } else { - bail!("{:?} is not a valid word name!", token); - } - } else if let Token::Word(word) = token { - if word == ":" { - if current_word.is_some() { - bail!("can't define words inside word definitions!"); - } - about_to_start_word_def = true; - continue; - } - if word == ";" { - let word = current_word.take(); - if let Some(word) = word { - result.push(word); - continue; - } else { - bail!("`;` must be at the end of a word definition"); - } - } - } - if let Some(ref mut current_word) = current_word { - current_word.instructions.push(token); - } else { - match token { - Token::Word(word) => { - if word == "import" { - last_was_import = true; - } else if word == "export" { - last_was_export = true; - } else if word == "extern" { - last_was_extern = true; - } else { - if last_was_export { - exports.push(word); - last_was_export = false; - } else if last_was_extern { - externs.push(word); - last_was_extern = false; - } else { - main.push(token.clone()); - } - } - }, - Token::String(string) => { - if last_was_import { - imports.push(string); - last_was_import = false; - } else { - main.push(token.clone()); - } - }, - _ => { - main.push(token.clone()); - } - }; - } - } - - if about_to_start_word_def || current_word.is_some() { - bail!("unfinished word definition!"); - } - - if is_entrypoint { - result.push(WordDefinition { - name: "main", - instructions: main, - }); - } - - Ok(Module { words: result, imports, exports, externs }) - } - - #[cfg(test)] - pub fn debug_print(&self) { - for word in &self.words { - println!("{}", word.name); - for instruction in &word.instructions { - println!(" {:?}", instruction); - } - } - } -} - - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn try_some_parsing() { - let result = Module::parse(crate::tokenizer::tokenize(" -: hello world 16 \"planet\" ; -: soup chicken 4.5 hello ; - -hello soup -").unwrap(), true).unwrap(); - result.debug_print(); - } -} diff --git a/rel-lang/sorelc/src/riscv_asm_codegen.rs b/rel-lang/sorelc/src/riscv_asm_codegen.rs deleted file mode 100644 index c382cdf..0000000 --- a/rel-lang/sorelc/src/riscv_asm_codegen.rs +++ /dev/null @@ -1,361 +0,0 @@ -use sorel_ir::*; - -use anyhow::*; - -use std::collections::{HashMap, HashSet}; -use std::fmt::Display; - -pub struct CodeGen<'a> { - module: &'a IRObject, - data_stack_size: usize, - lines: Vec, -} - - -// Some inspiration -// ================ -// -// * https://github.com/aw/fiveforths/blob/master/docs/REFERENCE.md#registers-list -// * Except using sp as a more C ABI style stack pointer, and s2 for the data stack -// - -// Implementation Choices -// ====================== -// -// Data Stack pointer: s2 -// No return stack pointer (using C ABI, so sp, sorta) -// Use t0, t1, t2 for temporary values in words -// Data stack grows down - - -macro_rules! asm_macro { - ($name:ident, $src:expr) => { - fn $name(&mut self) { - self.line($src); - } - }; - ($name:ident, $src:expr, $arg0:ty) => { - fn $name(&mut self, val0: $arg0) { - self.line(format!($src, val0)); - } - }; - ($name:ident, $src:expr, $arg0:ty, $arg1:ty) => { - fn $name(&mut self, val0: $arg0, val1: $arg1) { - self.line(format!($src, val0, val1)); - } - }; -} - -fn mangle(input: &str) -> String { - input - .replace("<", "_LT_") - .replace(">", "_GT_") - .replace("-", "___") -} - -impl<'a> CodeGen<'a> { - pub fn new(ir_mod: &'a IRObject, data_stack_size: usize) -> Self { - Self { - module: ir_mod, - data_stack_size, - lines: vec![], - } - } - - fn line(&mut self, line: S) { - self.lines.push(format!(" {}", line)); - - } - - fn label(&mut self, line: S) { - self.lines.push(line.to_string()); - } - - asm_macro!(copy_top_stack_value_to, "ld {}, 0(s2)", &str); - asm_macro!(copy_offset_stack_value_to, "ld {}, {}*8(s2)", &str, isize); - asm_macro!(copy_to_top_of_stack, "sd {}, 0(s2)", &str); - asm_macro!(move_stack_ptr_by_cells, "addi s2, s2, {}*8", isize); - - fn pop_to(&mut self, reg: &str) { - self.copy_top_stack_value_to(reg); - self.move_stack_ptr_by_cells(1); - } - - fn pop_some_to(&mut self, regs: &str) { - let mut regs = regs.trim().split(" ").collect::>(); - regs.reverse(); - let count = regs.len(); - let mut index = 0; - for reg in regs { - self.copy_offset_stack_value_to(reg, index); - index += 1; - } - self.move_stack_ptr_by_cells(count as isize); - } - - fn push_from(&mut self, reg: &str) { - self.move_stack_ptr_by_cells(-1); - self.copy_to_top_of_stack(reg); - } - - fn pop_call_push(&mut self, regs: &str, call: &str, reg: &str) { - self.pop_some_to(regs); - self.line(call); - self.push_from(reg); - } - - pub fn assembly(&mut self) -> Result{ - let mut string_table = HashMap::new(); - - // Static strings - self.label(".section .rodata\n"); - self.label(".align 3\n"); - for ir in &self.module.data { - match ir { - IR::StringDef(string_label, some_string) => { - string_table.insert(some_string.clone(), string_label); - self.label(format!("{}:", string_label)); - self.line(format!(".asciz \"{}\"", some_string)); // should this be .asciz? - self.label(""); - }, - _ => bail!("Currently only string definitions are supported in the data section.") - } - } - - // Data stack - self.label(".data\n"); - self.label(".align 3\n"); - self.label("data_stack:"); - self.line(format!(".space {}", self.data_stack_size)); - self.label(".globl data_stack_end\ndata_stack_end:\n"); - - // Code - self.label(".text\n"); - self.label(".align 3\n"); - - let mut if_block_count = 0; - let mut if_stack = vec![]; - let mut loop_count = 0; - let mut loop_stack = vec![]; - let mut seen_else = HashSet::new(); - let mut last_label = ""; - - for ir in &self.module.text { - match ir { - IR::Label(name) => { - last_label = name; - if name == "main" { - self.label(".globl _start"); // TODO is globl necessary? - self.label("_start:"); - self.line("la s2, data_stack_end # set initial data stack pointer"); - } else { - let mangled = mangle(name); - self.label(format!(".globl {}", mangled)); - self.label(format!("{}:", mangled)); - } - self.line("addi sp, sp, -16 # allocate 16 bytes on stack"); // allocate 16 bytes on stack - self.line("sd ra, 8(sp) # store return address on stack"); // store return address on stack - }, - IR::Call(name) => { - let mangled = mangle(name); - self.label(format!("# call {}", mangled)); - self.line(format!("call {}", mangled)); - }, - IR::Ret => { - if last_label == "main" { - self.label("# exit 0 syscall"); - self.line("li a7, 93"); - self.line("mv a0, x0"); - self.line("ecall"); - } else { - self.line("ld ra, 8(sp)"); // load return address from stack - self.line("addi sp, sp, 16"); // restore stack pointer - self.line("ret"); - } - }, - IR::Load8 => { - self.label("# load 8"); - self.copy_top_stack_value_to("t0"); - self.line("lbu t0, 0(t0)"); // deref pointer in t0 to t0 - self.copy_to_top_of_stack("t0"); - }, - IR::Load16 => { - self.label("# load 16"); - self.copy_top_stack_value_to("t0"); - self.line("lhu t0, 0(t0)"); // deref pointer in t0 to t0 - self.copy_to_top_of_stack("t0"); - }, - IR::Load32 => { - self.label("# load 32"); - self.copy_top_stack_value_to("t0"); - self.line("lwu t0, 0(t0)"); // deref pointer in t0 to t0 - self.copy_to_top_of_stack("t0"); - }, - IR::Load => { - self.label("# load 64"); - self.copy_top_stack_value_to("t0"); - self.line("ld t0, 0(t0)"); // deref pointer in t0 to t0 - self.copy_to_top_of_stack("t0"); - }, - IR::Store8 => { // ( x addr -- ) - self.pop_some_to("t0 t1"); - self.line("sb t0, 0(t1)"); // store x at addr - }, - IR::Store16 => { // ( x addr -- ) - self.pop_some_to("t0 t1"); - self.line("sh t0, 0(t1)"); // store x at addr - }, - IR::Store32 => { // ( x addr -- ) - self.pop_some_to("t0 t1"); - self.line("sw t0, 0(t1)"); // store x at addr - }, - IR::Store => { // ( x addr -- ) - self.pop_some_to("t0 t1"); - self.line("sd t0, 0(t1)"); // store x at addr - }, - IR::StackPush(num) => { - self.label(format!("# stackpush {}", num)); - self.line(format!("li t0, {}", num)); - self.push_from("t0"); - }, - IR::StackPushString(name) => { - self.label(format!("# stackpushstring {}", name)); - self.line(format!("la t0, {}", name)); - self.push_from("t0"); - }, - IR::AddU64 => { - self.label("# add"); - self.pop_call_push("t0 t1", "add t0, t0, t1", "t0"); - }, - IR::SubtractU64 => { - self.label("# sub"); - self.pop_call_push("t0 t1", "sub t0, t0, t1", "t0"); - }, - IR::MultiplyU64 => { - self.pop_call_push("t0 t1", "mul t0, t0, t1", "t0"); - }, - IR::DivideU64 => { - self.pop_call_push("t0 t1", "div t0, t0, t1", "t0"); - }, - IR::ModU64 => { - self.pop_call_push("t0 t1", "rem t0, t0, t1", "t0"); - }, - IR::Dup => { - self.label("# dup"); - self.copy_top_stack_value_to("t0"); - self.push_from("t0"); - }, - IR::Swap => { - self.label("# swap"); - self.pop_some_to("t1 t0"); - self.push_from("t0"); - self.push_from("t1"); - }, - IR::Rot => { - self.label("# rot"); - self.pop_some_to("t0 t1 t2"); - self.push_from("t1"); - self.push_from("t2"); - self.push_from("t0"); - }, - IR::StackPointer => { - self.label("# sp"); - self.line("addi t0, s2, 0"); - self.push_from("t0"); - }, - IR::Drop => { - self.label("# drop"); - self.move_stack_ptr_by_cells(1); - }, - IR::Equals => { - // Yes, this is the same as subtract, since we're treating 0 as true, and - // others as false. - self.pop_call_push("t0 t1", "sub t0, t0, t1", "t0"); - }, - IR::GreaterThan => { - self.label("# >"); - self.pop_some_to("t0 t1"); - self.line("sgt t0, t0, t1"); - self.line("seqz t0, t0"); // remember, 0 is true, others are false - self.push_from("t0"); - }, - IR::LessThan => { - self.label("# <"); - self.pop_some_to("t0 t1"); - self.line("slt t0, t0, t1"); - self.line("seqz t0, t0"); // remember, 0 is true, others are false - self.push_from("t0"); - }, - IR::BitwiseOr => { - self.pop_call_push("t0 t1", "or t0, t0, t1", "t0"); - }, - IR::Sys0 => { - self.pop_call_push("a7", "ecall", "a0"); - }, - IR::Sys1 => { - self.pop_call_push("a0 a7", "ecall", "a0"); - }, - IR::Sys2 => { - self.pop_call_push("a0 a1 a7", "ecall", "a0"); - }, - IR::Sys3 => { - self.pop_call_push("a0 a1 a2 a7", "ecall", "a0"); - }, - IR::Sys4 => { - self.pop_call_push("a0 a1 a2 a3 a7", "ecall", "a0"); - }, - IR::Sys5 => { - self.pop_call_push("a0 a1 a2 a3 a4 a7", "ecall", "a0"); - }, - IR::Sys6 => { - self.pop_call_push("a0 a1 a2 a3 a4 a5 a7", "ecall", "a0"); - }, - // https://cmput229.github.io/229-labs-RISCV/RISC-V-Examples_Public/03-Conditionals/03b-If_Else.html - IR::If => { - self.label("# if"); - self.pop_to("t0"); - self.line(format!("bnez t0, _else_{}", if_block_count)); - if_stack.push(if_block_count); - if_block_count += 1; - }, - IR::Else => { - self.label("# else"); - let if_counter = if_stack.last().unwrap().clone(); - self.line(format!("j _endif_{}", if_counter)); - self.label(format!("_else_{}:", if_counter)); - seen_else.insert(if_counter); - }, - IR::EndIf => { - self.label("# endif"); - let stack = &mut if_stack; - let if_counter = stack.last().unwrap().clone(); - if !seen_else.contains(&if_counter) { - self.label(format!("_else_{}:", if_counter)); - } else { - self.label(format!("_endif_{}:", if_counter)); - seen_else.remove(&if_counter); - } - stack.pop(); - }, - IR::Loop => { // keep looping until is true/0 - self.label(format!("_loop_{}:", loop_count)); - self.pop_to("t0"); - self.line(format!("beqz t0, _endloop_{}", loop_count)); - loop_stack.push(loop_count); - loop_count += 1; - }, - IR::EndLoop => { - let stack = &mut loop_stack; - let loop_counter = stack.last().unwrap().clone(); - self.line(format!("j _loop_{}", loop_counter)); - self.label(format!("_endloop_{}:", loop_counter)); - stack.pop(); - }, - _ => bail!("not implemented yet: {:?}", ir), - } - } - - Ok(self.lines.join("\n")) - } -} - diff --git a/rel-lang/sorelc/src/tokenizer.rs b/rel-lang/sorelc/src/tokenizer.rs deleted file mode 100644 index d546c6b..0000000 --- a/rel-lang/sorelc/src/tokenizer.rs +++ /dev/null @@ -1,188 +0,0 @@ -use anyhow::{Result, anyhow}; - -#[derive(Debug, Clone)] -pub enum Token<'a> { - Word(&'a str), - String(&'a str), - NumU8(u8), - NumI8(i8), - NumU16(u16), - NumI16(i16), - NumU32(u32), - NumI32(i32), - NumU64(u64), - NumI64(i64), - NumF32(f32), - NumF64(f64), -} - -impl<'a> Token<'a>{ - fn parse_word_or_num(input: &'a str) -> Result> { - if input == "-" { - return Ok(Token::Word(input)) - } - - // we're assuming any token starting with `-` with length greater than one - // is a negative number - if input.starts_with('-') || input.chars().nth(0).map(|x| x.is_numeric()).unwrap_or(false) { - if input.contains(':') { - let mut splat = input.split(':'); - let num = splat.next().ok_or(anyhow!("no number found"))?; - let typ = splat.next().ok_or(anyhow!("no number type found"))?; - match typ { - "u8" => Ok(Token::NumU8(num.parse()?)), - "i8" => Ok(Token::NumI8(num.parse()?)), - "u16" => Ok(Token::NumU16(num.parse()?)), - "i16" => Ok(Token::NumI16(num.parse()?)), - "u32" => Ok(Token::NumU32(num.parse()?)), - "i32" => Ok(Token::NumI32(num.parse()?)), - "u64" => Ok(Token::NumU64(num.parse()?)), - "i64" => Ok(Token::NumI64(num.parse()?)), - "f32" => Ok(Token::NumF32(num.parse()?)), - "f64" => Ok(Token::NumF64(num.parse()?)), - _ => panic!("unknown number type") - } - } else { - if input.contains('.') { - Ok(Token::NumF64(input.parse()?)) - } else if input.starts_with('-') { - Ok(Token::NumI64(input.parse()?)) - } else { - Ok(Token::NumU64(input.parse()?)) - } - } - } else { - Ok(Token::Word(input)) - } - } -} - -// TODO really want an iterator, not a vector -pub fn tokenize<'a>(input: &'a str) -> Result>> { - let mut result = vec![]; - let mut string_start: Option = None; - let mut word_or_num_start: Option = None; - let mut last_is_backslash = false; - let mut last_is_whitespace = true; - let mut in_doc_comment = false; - let mut in_line_comment = false; - let mut index = 0; - let mut first_char = true; - - - for char in input.chars() { - if first_char { - first_char = false; - } else { - index += 1; - } - - if in_doc_comment { - if char == ')' { - in_doc_comment = false; - last_is_whitespace = true; // not really true, but means don't need space after - } - continue; - } - - if in_line_comment { - word_or_num_start = None; - if char == '\n' { - in_line_comment = false; - last_is_whitespace = true; // not really true, but means don't need space after - } - continue; - } - - if char == '"' { - if let Some(start) = string_start { - if !last_is_backslash { - result.push(Token::String(&input[start..index])); - string_start = None; - } - } else { - string_start = Some(index + 1) - } - last_is_backslash = false; - last_is_whitespace = false; - continue; - } - - - if string_start.is_some() { - last_is_backslash = char == '\\'; - continue; - } - - if char.is_whitespace() { - if last_is_backslash { - in_line_comment = true; - } else if !last_is_whitespace && let Some(start) = word_or_num_start { - let token = &input[start..index]; - if token == "(" { - in_doc_comment = true; - } else { - result.push(Token::parse_word_or_num(&input[start..index])?); - } - word_or_num_start = None; - } - last_is_whitespace = true; - last_is_backslash = false; - continue; - } - - last_is_backslash = char == '\\'; - - if index == input.len() - 1 { - if !last_is_whitespace && let Some(start) = word_or_num_start { - result.push(Token::parse_word_or_num(&input[start..])?); - } - continue; - } - - if last_is_whitespace { // start of word or num (we already handled strings) - word_or_num_start = Some(index); - last_is_whitespace = false; - } - } - Ok(result) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn try_some_tokenizing() { - let result = tokenize(" - - \\ soup - 2 3.4 - -88 bacon \"hello\" 43:f32 2345:u32 -57:i8 soup -"); - println!("result: {:?}", result); - } - - #[test] - fn comments() { - let result = tokenize(" - ( - foo - bar - ) - : baz ( x y -- z ) - chicken - soup - ; - "); - println!("result: {:?}", result); - } - - #[test] - fn strings() { - let result = tokenize(" -dup \\ ( stuff ) -\"hello!\" -"); - println!("result: {:?}", result); - } -} diff --git a/rel-lang/stdlib/mem.sorel b/rel-lang/stdlib/mem.sorel deleted file mode 100644 index 5ed53c8..0000000 --- a/rel-lang/stdlib/mem.sorel +++ /dev/null @@ -1,74 +0,0 @@ -\ vim: filetype=forth - -: mmap 222 sys6 ; -: munmap 215 sys2 ; - -: PROT_READ 1 ; -: PROT_WRITE 2 ; -: MAP_PRIVATE 2 ; -: MAP_ANONYMOUS 32 ; - -: ALLOC_PROT PROT_READ PROT_WRITE | ; -: ALLOC_MAP MAP_PRIVATE MAP_ANONYMOUS | ; - -\ This is a raw way to allocate memory. Callers to this will need to know -\ how much memory was allocated, to call dealloc later. -: alloc_raw ( byte-count -- addr ) - 0 swap ALLOC_PROT ALLOC_MAP -1:i32 0 mmap -; - -: dealloc ( addr byte-count -- ) - munmap -; - -export alloc -: alloc ( byte-count -- addr ) - dup \ we'll need the length again later - 8 + \ add 64 bits to the front where we'll store the length - alloc_raw \ ( byte-count addr ) - dup \ ( byte-count addr addr) - rot \ ( addr addr byte-count ) - rot \ ( addr byte-count addr ) - ! \ write the length to the addr, now ( addr ) - 8 + \ here's the address we'll return, 8 bytes forward to account for the length -; - -export free -: free ( addr -- ) - 8 - \ the real memory start is 8 bytes before - dup \ ( addr addr ) - @ \ ( addr byte-length ) - 8 + \ add 8 to the byte length to refer to the whole region from alloc. - dealloc -; - -import "std:out" - -: copy_byte ( addr_from addr_to -- addr_from addr_to ) - swap \ ( addr_to addr_from ) - dup \ ( addr_to addr_from addr_from ) - @:8 \ ( addr_to addr_from byte ) - rot \ ( addr_from byte addr_to ) - dup \ ( addr_from byte addr_to addr_to) - rot \ ( addr_from addr_to addr_to byte ) - swap \ ( addr_from addr_to byte addr_to ) - !:8 \ ( addr_from addr_to ) -; - -export memcopy -: memcopy ( addr_from addr_to count -- ) - dup \ ( addr_from addr_to count count ) - loop \ ( addr_from addr_to count ) - rot \ ( addr_to count addr_from ) - rot \ ( count addr_from addr_to ) - copy_byte \ ( count addr_from addr_to ) - 1 + \ ( count addr_from addr_to+1 ) - rot \ ( addr_from addr_to+1 count ) - 1 - \ ( addr_from addr_to+1 count-1 ) - rot \ ( addr_to+1 count-1 addr_from ) - 1 + \ ( addr_to+1 count-1 addr_from+1 ) - rot rot \ ( addr_from+1 addr_to+1 count-1 ) - dup \ ( addr_from+1 addr_to+1 count-1 count-1) - endloop \ ( addr_from+count addr_to+count 0 ) - drop drop drop \ ( ) -; diff --git a/rel-lang/stdlib/out.sorel b/rel-lang/stdlib/out.sorel deleted file mode 100644 index 5704f99..0000000 --- a/rel-lang/stdlib/out.sorel +++ /dev/null @@ -1,59 +0,0 @@ -\ vim: filetype=forth - -: write ( fd ptr len -- bytes-written-or-err ) - 64 - sys3 -; - -import "std:string" - -export puts - -: puts ( addr -- addr) - dup dup \ ( addr addr addr ) - strlen \ ( addr addr len ) - 1 \ ( addr addr len 1 ) - rot \ ( addr len 1 addr ) - rot \ ( addr 1 addr len ) - write \ ( addr bytes-written-or-err ) - drop \ ( addr ) -; - -: ZERO_CHAR 48 ; -: NEWLINE_CHAR 10 ; - -export putn - -: putn ( num -- num ) - dup dup \ ( num num num ) - 10 rot rot \ ( num 10 num num ) // Mutltidigit stop point - 10 < \ ( num 10 num is<10 ) - loop \ ( num 10 num' ) - dup \ ( num 10 num' num' ) - 10 % \ ( num 10 num' digit ) - swap \ ( num 10 digit num' ) - 10 / \ ( num 10 digit num'/10 ) - dup 10 < \ ( num 10 digit num'/10 is<10 ) - endloop \ ( num 10 digitn ... digit1 ) - dup 9 > \ ( num 10 digitn ... digit1 digit1>9 ) - loop \ ( num 10 digitn ... digit1 ) - ZERO_CHAR + \ \ ( num 10 digitn ... digit1 ) - sp \ ( num 10 digitn ... digit1 ptr ) - 1 \ ( num 10 digitn ... digit1 ptr 1 ) - swap \ ( num 10 digitn ... digit1 1 ptr ) - 1 \ ( num 10 digitn ... digit1 1 ptr 1 ) - write \ ( num 10 digitn ... digit1 result ) - drop drop \ ( num 10 digitn ... digit2 ) - dup 9 > \ ( num 10 digitn ... digit2 digit2>9 ) - endloop \ ( num 10 ) - drop \ ( num ) - NEWLINE_CHAR \ ( num 10 ) - sp \ ( num 10 ptr ) - 1 \ ( num 10 ptr 1 ) - swap \ (num 10 1 ptr ) - 1 \ ( num 10 1 ptr 1 ) - write \ ( num 10 result ) - drop \ ( num 10 ) - drop \ ( num ) -; - diff --git a/rel-lang/stdlib/process.sorel b/rel-lang/stdlib/process.sorel deleted file mode 100644 index 73a7008..0000000 --- a/rel-lang/stdlib/process.sorel +++ /dev/null @@ -1,8 +0,0 @@ -\ vim: filetype=forth - -export exit - -: exit ( code -- ) - 93 - sys1 -; diff --git a/rel-lang/stdlib/string.sorel b/rel-lang/stdlib/string.sorel deleted file mode 100644 index b0c76a8..0000000 --- a/rel-lang/stdlib/string.sorel +++ /dev/null @@ -1,15 +0,0 @@ -\ vim: filetype=forth - -: strlen ( addr -- len ) - dup dup \ ( addr addr addr ) - @:8 \ ( addr addr byte ) - loop \ ( addr addr ) - 1 + \ ( addr addr+1 ) - dup \ ( addr addr+1 addr+1) - @:8 \ ( addr addr+1 byte ) - endloop \ ( addr addr+len ) - swap \ ( addr+len addr ) - - \ ( len ) -; - -export strlen diff --git a/rel-lang/tests/assert.sorel b/rel-lang/tests/assert.sorel deleted file mode 100644 index 9d84758..0000000 --- a/rel-lang/tests/assert.sorel +++ /dev/null @@ -1,13 +0,0 @@ -\ vim: filetype=forth - -import "std:process" - -export assert - -: assert ( x -- ) - if - drop - else - 1 exit - endif -; diff --git a/rel-lang/tests/putstack.c b/rel-lang/tests/putstack.c deleted file mode 100644 index a5200c4..0000000 --- a/rel-lang/tests/putstack.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -extern unsigned long data_stack_end; -register unsigned long * stack_pointer asm("s2"); - -void putstack() { - unsigned long * stack_index = &data_stack_end; - printf("stack: "); - while (stack_index != stack_pointer) { - printf("%ld ", *stack_index); - stack_index -= 1; - } - printf("%ld\n", *stack_pointer); -} - - diff --git a/rel-lang/tests/test.sh b/rel-lang/tests/test.sh deleted file mode 100644 index 279addc..0000000 --- a/rel-lang/tests/test.sh +++ /dev/null @@ -1,11 +0,0 @@ -UNAME=$(uname -m) -CMD_PREFIX=$([ "$UNAME" = "riscv64" ] && echo "" || echo "riscv64-unknown-linux-gnu-") -AS="${CMD_PREFIX}as" -LD="${CMD_PREFIX}ld" -CC="${CMD_PREFIX}cc" - -../target/debug/sorelc test1.sorel -$AS -g -o test1.o test1.asm -$CC -O1 -no-pie -o test1.out test1.o putstack.c -nostartfiles -# $LD -o test1.out test1.o -./test1.out diff --git a/rel-lang/tests/test1.sorel b/rel-lang/tests/test1.sorel deleted file mode 100644 index 3256625..0000000 --- a/rel-lang/tests/test1.sorel +++ /dev/null @@ -1,36 +0,0 @@ -\ vim: filetype=forth - -import "./assert.sorel" -import "std:out" - -"Hello, World! \n" puts -drop - -42 putn -drop - -import "std:mem" - -64 alloc -free - -0 assert - -import "std:string" -import "std:mem" -import "std:out" - -extern putstack - -16 alloc \ ( ptr ) -dup \ ( ptr ptr ) -"To be copied!" \ ( ptr ptr strptr ) -dup \ ( ptr ptr strptr strptr ) -strlen 1 + \ ( ptr ptr strptr len ) -rot \ ( ptr strptr len ptr ) -swap \ ( ptr strptr ptr len ) -memcopy \ ( ptr ) -puts \ ( ptr ) -free \ ( ) - - diff --git a/sorel-ir/Cargo.toml b/sorel-ir/Cargo.toml new file mode 100644 index 0000000..5618a61 --- /dev/null +++ b/sorel-ir/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sorel-ir" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = "1.0.228" +serde_derive = "1.0.228" +serde_yaml = "0.9.34" diff --git a/sorel-ir/src/lib.rs b/sorel-ir/src/lib.rs new file mode 100644 index 0000000..1a3ec27 --- /dev/null +++ b/sorel-ir/src/lib.rs @@ -0,0 +1,72 @@ +use serde_yaml::{from_str, to_string, Error}; +use serde_derive::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum IR { + Label(String), + Call(String), + Ret, + StackPush(u64), + StackPushString(String), // refers to string label, not the string itself + StringDef(String, String), // first is string label, second is string value + + // These next ones should always be inlined, so they're in IR. + Load, // @ ( addr -- x ) -- Fetch memory contents at addr + Load8, + Load16, + Load32, + Store, // ! ( x addr -- ) -- Store x at addr + Store8, + Store16, + Store32, + + // These ones might not be inlined, but should be built-in, so a compiler might + // turn this into `Call(String)` before translating to assembly/machine-code, but + // an IR interpreter may just execute them. + AddU64, + SubtractU64, + MultiplyU64, + DivideU64, + ModU64, + Equals, + GreaterThan, + LessThan, + BitwiseOr, + Dup, + Swap, + Drop, + Over, + Rot, + StackPointer, + If, + Else, + EndIf, + Loop, + EndLoop, + + // System calls + Sys0, + Sys1, + Sys2, + Sys3, + Sys4, + Sys5, + Sys6, +} + +// This is like an .o file. +#[derive(Serialize, Deserialize, Debug)] +pub struct IRObject { + pub text: Vec, + pub data: Vec, +} + +impl IRObject { + pub fn to_s(&self) -> Result { + to_string(self) + } + + pub fn from_s(source: &str) -> Result { + from_str(source) + } +} diff --git a/sorelc/Cargo.toml b/sorelc/Cargo.toml new file mode 100644 index 0000000..7859ecf --- /dev/null +++ b/sorelc/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sorelc" +version = "0.1.0" +edition = "2024" + +[dependencies] +sorel-ir = { workspace = true } +anyhow = "1.0.100" diff --git a/sorelc/src/ir.rs b/sorelc/src/ir.rs new file mode 100644 index 0000000..61e6115 --- /dev/null +++ b/sorelc/src/ir.rs @@ -0,0 +1,307 @@ +use crate::parser::Module; +use crate::tokenizer::{Token, tokenize}; +use sorel_ir::*; + +use std::collections::{HashSet, HashMap}; +use std::path::PathBuf; +use std::rc::Rc; +use std::include_str; + +use anyhow::{Result, bail}; + +macro_rules! push_num { + ($num:ident) => { IR::StackPush(*$num as u64) }; + ($num:ident, $num_typ:ty) => { IR::StackPush(*$num as $num_typ as u64) }; +} + +#[derive(Debug, Default)] +struct IRModule { + data: Vec, + text: Vec, + imports: Vec>, + exports: Vec, + externs: Vec, + // TODO these next two should form an enum, not two options + source_file: Option, + std_specifier: Option, + number: usize, +} + +impl IRModule { + fn get_label_for_call(&self, name: &String) -> String { + if self.externs.contains(name) { + return name.clone(); + } + let mut found: Option = None; + for imported in &self.imports { + if imported.exports.contains(name) { + found = Some(imported.number); + // Don't break here, since the last one should win. + } + } + if let Some(found) = found { + format!("_m{}_{}", found, name) + } else { + // TODO check if it's even a word locally. If not, bail. + format!("_m{}_{}", self.number, name) + } + } + + fn get_label(&self, name: &String) -> String { + format!("_m{}_{}", self.number, name) + } +} + +#[derive(Default)] +struct ImportTree { + data: Vec, + text: Vec, + all_modules: HashMap>, + all_exports: HashSet, + entrypoint: Rc, + module_count: usize, + collapse_seen: HashSet, +} + +fn std_import(specifier: &str) -> Result<&str> { + match specifier { + "std:mem" => Ok(include_str!("../../stdlib/mem.sorel")), + "std:out" => Ok(include_str!("../../stdlib/out.sorel")), + "std:string" => Ok(include_str!("../../stdlib/string.sorel")), + "std:process" => Ok(include_str!("../../stdlib/process.sorel")), + _ => bail!("{} is not a standard library module", specifier), + } +} + +impl ImportTree { + fn import(&mut self, importer_dir: &PathBuf, specifier: &str, is_entrypoint: bool) -> Result> { + Ok(if specifier.starts_with("std:") { + if self.all_modules.contains_key(specifier) { + let module = self.all_modules.get(specifier).unwrap().clone(); + return Ok(module); + } + let contents = std_import(specifier)?; + let parsed = &Module::parse(tokenize(&contents)?, is_entrypoint)?; + let module = self.generate_internal(None, Some(specifier.to_string()), parsed); + let module = Rc::new(module); + self.all_modules.insert(specifier.to_string(), module.clone()); + module + } else { + let mut path = PathBuf::from(specifier); + if path.is_relative() { + let mut new_path = importer_dir.clone(); + new_path.push(path); + path = new_path.canonicalize()?; + } + let path_key = path.to_string_lossy().to_string(); + if self.all_modules.contains_key(&path_key) { + let module = self.all_modules.get(&path_key).unwrap().clone(); + return Ok(module); + } + + let contents = &std::fs::read_to_string(&path)?; + let tokens = tokenize(&contents)?; + let parsed = &Module::parse(tokens, is_entrypoint)?; + + let module = self.generate_internal(Some(path), None, parsed); + let module = Rc::new(module); + self.all_modules.insert(path_key, module.clone()); + if is_entrypoint { + self.entrypoint = module.clone(); + } + module + }) + + } + + fn generate_internal(&mut self, path: Option, std_specifier: Option, module: &Module) -> IRModule { + // Eventually these will end up being sections in assembly + let mut text = vec![]; + let mut data = vec![]; + + let mut imports = vec![]; + if let Some(ref path) = path { + module.imports.iter().for_each(|imported| { + if let Some(parent_path) = path.parent() { + match self.import(&parent_path.to_path_buf(), imported, false) { + Ok(module) => { + imports.push(module); + }, + Err(msg) => { + eprintln!("{}", msg); + } + } + } else { + } + }); + } else { + // We're in a stdlib module, which can only import other stdlib + // modules. + module.imports.iter().for_each(|imported| { + match self.import(&PathBuf::new(), imported, false) { + Ok(module) => { + imports.push(module); + }, + Err(msg) => { + eprintln!("{}", msg); + } + } + }); + } + + let exports: Vec<_> = module.exports.iter().map(|s| { + self.all_exports.insert(s.to_string()); + s.to_string() + }).collect(); + + let externs = module.externs.iter().map(|s| s.to_string()).collect(); + + text.push(module.words.iter().map(|def| { + let mut body = def.instructions.iter().map(|inst| { + let mapped_ir = match inst { + Token::Word(word) => { + match *word { + "@" => IR::Load, + "@:8" => IR::Load8, + "@:16" => IR::Load16, + "@:32" => IR::Load32, + "!" => IR::Store, + "!:8" => IR::Store8, + "!:16" => IR::Store16, + "!:32" => IR::Store32, + "dup" => IR::Dup, + "swap" => IR::Swap, + "drop" => IR::Drop, + "over" => IR::Over, + "rot" => IR::Rot, + "sp" => IR::StackPointer, + "if" => IR::If, + "else" => IR::Else, + "endif" => IR::EndIf, + "loop" => IR::Loop, + "endloop" => IR::EndLoop, + "=" => IR::Equals, + ">" => IR::GreaterThan, + "<" => IR::LessThan, + "+" => IR::AddU64, + "-" => IR::SubtractU64, + "*" => IR::MultiplyU64, + "/" => IR::DivideU64, + "%" => IR::ModU64, + "|" => IR::BitwiseOr, + "sys0" => IR::Sys0, + "sys1" => IR::Sys1, + "sys2" => IR::Sys2, + "sys3" => IR::Sys3, + "sys4" => IR::Sys4, + "sys5" => IR::Sys5, + "sys6" => IR::Sys6, + // TODO num type specfic math like `+:i32`, etc. + _ => IR::Call(String::from(*word)) + } + }, + Token::String(text) => { + let string_label = format!("string_{}", data.len()); + data.push(IR::StringDef(string_label.clone(), String::from(*text))); + IR::StackPushString(string_label) + }, + Token::NumU8(num) => push_num!(num), + Token::NumI8(num) => push_num!(num, u8), + Token::NumU16(num) => push_num!(num), + Token::NumI16(num) => push_num!(num, u16), + Token::NumU32(num) => push_num!(num), + Token::NumI32(num) => push_num!(num, u32), + Token::NumU64(num) => push_num!(num), + Token::NumI64(num) => push_num!(num), + Token::NumF32(num) => push_num!(num), + Token::NumF64(num) => push_num!(num), + }; + mapped_ir + }).collect::>(); + + let mut result = vec![IR::Label(def.name.to_string())]; + result.append(&mut body); + result.push(IR::Ret); + result + }).flatten().collect::>()); + + let number = self.module_count; + self.module_count += 1; + + IRModule { + text: text.into_iter().flatten().collect::>(), + data, + imports, + exports, + externs, + source_file: path, + std_specifier, + number, + } + } + + fn collapse(&mut self, module: Rc) -> Result<()> { + let seen_key = if let Some(source_file) = &module.source_file { + source_file.to_string_lossy().to_string() + } else { + module.std_specifier.clone().unwrap() + }; + if self.collapse_seen.contains(&seen_key) { + return Ok(()) + } + + for imported in module.imports.clone() { + self.collapse(imported)?; + } + + let is_entrypoint = module.source_file == self.entrypoint.source_file; + + let module_number = module.number; + + for string in &module.data { + if let IR::StringDef(name, val) = string { + let new_name = format!("{}_{}", name, module_number); + self.data.push(IR::StringDef(new_name, val.clone())); + } else { + bail!("non-string data"); + } + } + + for instruction in &module.text { + let new_instruction = match instruction { + IR::StackPushString(name) => { + let new_name = format!("{}_{}", name, module_number); + IR::StackPushString(new_name) + }, + IR::Label(name) => { + if is_entrypoint && name == "main" { + instruction.clone() + } else { + IR::Label(module.get_label(name)) + } + }, + IR::Call(name) => { + IR::Call(module.get_label_for_call(name)) + }, + _ => instruction.clone() + }; + self.text.push(new_instruction); + } + + self.collapse_seen.insert(seen_key); + + Ok(()) + } +} + +pub fn compile(path: &str) -> Result { + let dir = std::env::current_dir()?; + let mut tree: ImportTree = Default::default(); + let module = tree.import(&dir, path, true)?; + tree.collapse(module)?; + // TODO remove unused words + Ok(IRObject { + data: tree.data, + text: tree.text, + }) +} diff --git a/sorelc/src/main.rs b/sorelc/src/main.rs new file mode 100644 index 0000000..724df84 --- /dev/null +++ b/sorelc/src/main.rs @@ -0,0 +1,21 @@ +mod tokenizer; +mod parser; +mod ir; +mod riscv_asm_codegen; + +use anyhow::Result; + +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() -> Result<()> { + let filename = std::env::args().nth(1).expect("must provide a file to compile"); + let module = ir::compile(&filename)?; + let mut generator = riscv_asm_codegen::CodeGen::new(&module, 4096); + let mut asm_path = PathBuf::from(filename); + asm_path.set_extension("asm"); + let mut output = File::create(asm_path)?; + write!(output, "{}\n", generator.assembly()?)?; + Ok(()) +} diff --git a/sorelc/src/parser.rs b/sorelc/src/parser.rs new file mode 100644 index 0000000..94b53e4 --- /dev/null +++ b/sorelc/src/parser.rs @@ -0,0 +1,140 @@ +use crate::tokenizer::Token; +use anyhow::{Result, bail}; + +#[derive(Debug)] +pub struct WordDefinition<'a> { + pub name: &'a str, + pub instructions: Vec>, +} + +#[derive(Debug)] +pub struct Module<'a> { + pub words: Vec>, + pub imports: Vec<&'a str>, + pub exports: Vec<&'a str>, + pub externs: Vec<&'a str>, +} + +impl<'a> Module<'a> { + pub fn parse(input: Vec>, is_entrypoint: bool) -> Result { + let mut result = vec![]; + let mut main = vec![]; + let mut exports = vec![]; + let mut imports = vec![]; + let mut externs = vec![]; + let mut current_word: Option = None; + let mut about_to_start_word_def = false; + let mut last_was_import = false; + let mut last_was_export = false; + let mut last_was_extern = false; + + for token in input { + if about_to_start_word_def { + if let Token::Word(name) = token { + current_word = Some(WordDefinition { + name, + instructions: vec![], + }); + about_to_start_word_def = false; + continue; + } else { + bail!("{:?} is not a valid word name!", token); + } + } else if let Token::Word(word) = token { + if word == ":" { + if current_word.is_some() { + bail!("can't define words inside word definitions!"); + } + about_to_start_word_def = true; + continue; + } + if word == ";" { + let word = current_word.take(); + if let Some(word) = word { + result.push(word); + continue; + } else { + bail!("`;` must be at the end of a word definition"); + } + } + } + if let Some(ref mut current_word) = current_word { + current_word.instructions.push(token); + } else { + match token { + Token::Word(word) => { + if word == "import" { + last_was_import = true; + } else if word == "export" { + last_was_export = true; + } else if word == "extern" { + last_was_extern = true; + } else { + if last_was_export { + exports.push(word); + last_was_export = false; + } else if last_was_extern { + externs.push(word); + last_was_extern = false; + } else { + main.push(token.clone()); + } + } + }, + Token::String(string) => { + if last_was_import { + imports.push(string); + last_was_import = false; + } else { + main.push(token.clone()); + } + }, + _ => { + main.push(token.clone()); + } + }; + } + } + + if about_to_start_word_def || current_word.is_some() { + bail!("unfinished word definition!"); + } + + if is_entrypoint { + result.push(WordDefinition { + name: "main", + instructions: main, + }); + } + + Ok(Module { words: result, imports, exports, externs }) + } + + #[cfg(test)] + pub fn debug_print(&self) { + for word in &self.words { + println!("{}", word.name); + for instruction in &word.instructions { + println!(" {:?}", instruction); + } + } + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn try_some_parsing() { + let result = Module::parse(crate::tokenizer::tokenize(" +: hello world 16 \"planet\" ; +: soup chicken 4.5 hello ; + +hello soup +").unwrap(), true).unwrap(); + result.debug_print(); + } +} diff --git a/sorelc/src/riscv_asm_codegen.rs b/sorelc/src/riscv_asm_codegen.rs new file mode 100644 index 0000000..c382cdf --- /dev/null +++ b/sorelc/src/riscv_asm_codegen.rs @@ -0,0 +1,361 @@ +use sorel_ir::*; + +use anyhow::*; + +use std::collections::{HashMap, HashSet}; +use std::fmt::Display; + +pub struct CodeGen<'a> { + module: &'a IRObject, + data_stack_size: usize, + lines: Vec, +} + + +// Some inspiration +// ================ +// +// * https://github.com/aw/fiveforths/blob/master/docs/REFERENCE.md#registers-list +// * Except using sp as a more C ABI style stack pointer, and s2 for the data stack +// + +// Implementation Choices +// ====================== +// +// Data Stack pointer: s2 +// No return stack pointer (using C ABI, so sp, sorta) +// Use t0, t1, t2 for temporary values in words +// Data stack grows down + + +macro_rules! asm_macro { + ($name:ident, $src:expr) => { + fn $name(&mut self) { + self.line($src); + } + }; + ($name:ident, $src:expr, $arg0:ty) => { + fn $name(&mut self, val0: $arg0) { + self.line(format!($src, val0)); + } + }; + ($name:ident, $src:expr, $arg0:ty, $arg1:ty) => { + fn $name(&mut self, val0: $arg0, val1: $arg1) { + self.line(format!($src, val0, val1)); + } + }; +} + +fn mangle(input: &str) -> String { + input + .replace("<", "_LT_") + .replace(">", "_GT_") + .replace("-", "___") +} + +impl<'a> CodeGen<'a> { + pub fn new(ir_mod: &'a IRObject, data_stack_size: usize) -> Self { + Self { + module: ir_mod, + data_stack_size, + lines: vec![], + } + } + + fn line(&mut self, line: S) { + self.lines.push(format!(" {}", line)); + + } + + fn label(&mut self, line: S) { + self.lines.push(line.to_string()); + } + + asm_macro!(copy_top_stack_value_to, "ld {}, 0(s2)", &str); + asm_macro!(copy_offset_stack_value_to, "ld {}, {}*8(s2)", &str, isize); + asm_macro!(copy_to_top_of_stack, "sd {}, 0(s2)", &str); + asm_macro!(move_stack_ptr_by_cells, "addi s2, s2, {}*8", isize); + + fn pop_to(&mut self, reg: &str) { + self.copy_top_stack_value_to(reg); + self.move_stack_ptr_by_cells(1); + } + + fn pop_some_to(&mut self, regs: &str) { + let mut regs = regs.trim().split(" ").collect::>(); + regs.reverse(); + let count = regs.len(); + let mut index = 0; + for reg in regs { + self.copy_offset_stack_value_to(reg, index); + index += 1; + } + self.move_stack_ptr_by_cells(count as isize); + } + + fn push_from(&mut self, reg: &str) { + self.move_stack_ptr_by_cells(-1); + self.copy_to_top_of_stack(reg); + } + + fn pop_call_push(&mut self, regs: &str, call: &str, reg: &str) { + self.pop_some_to(regs); + self.line(call); + self.push_from(reg); + } + + pub fn assembly(&mut self) -> Result{ + let mut string_table = HashMap::new(); + + // Static strings + self.label(".section .rodata\n"); + self.label(".align 3\n"); + for ir in &self.module.data { + match ir { + IR::StringDef(string_label, some_string) => { + string_table.insert(some_string.clone(), string_label); + self.label(format!("{}:", string_label)); + self.line(format!(".asciz \"{}\"", some_string)); // should this be .asciz? + self.label(""); + }, + _ => bail!("Currently only string definitions are supported in the data section.") + } + } + + // Data stack + self.label(".data\n"); + self.label(".align 3\n"); + self.label("data_stack:"); + self.line(format!(".space {}", self.data_stack_size)); + self.label(".globl data_stack_end\ndata_stack_end:\n"); + + // Code + self.label(".text\n"); + self.label(".align 3\n"); + + let mut if_block_count = 0; + let mut if_stack = vec![]; + let mut loop_count = 0; + let mut loop_stack = vec![]; + let mut seen_else = HashSet::new(); + let mut last_label = ""; + + for ir in &self.module.text { + match ir { + IR::Label(name) => { + last_label = name; + if name == "main" { + self.label(".globl _start"); // TODO is globl necessary? + self.label("_start:"); + self.line("la s2, data_stack_end # set initial data stack pointer"); + } else { + let mangled = mangle(name); + self.label(format!(".globl {}", mangled)); + self.label(format!("{}:", mangled)); + } + self.line("addi sp, sp, -16 # allocate 16 bytes on stack"); // allocate 16 bytes on stack + self.line("sd ra, 8(sp) # store return address on stack"); // store return address on stack + }, + IR::Call(name) => { + let mangled = mangle(name); + self.label(format!("# call {}", mangled)); + self.line(format!("call {}", mangled)); + }, + IR::Ret => { + if last_label == "main" { + self.label("# exit 0 syscall"); + self.line("li a7, 93"); + self.line("mv a0, x0"); + self.line("ecall"); + } else { + self.line("ld ra, 8(sp)"); // load return address from stack + self.line("addi sp, sp, 16"); // restore stack pointer + self.line("ret"); + } + }, + IR::Load8 => { + self.label("# load 8"); + self.copy_top_stack_value_to("t0"); + self.line("lbu t0, 0(t0)"); // deref pointer in t0 to t0 + self.copy_to_top_of_stack("t0"); + }, + IR::Load16 => { + self.label("# load 16"); + self.copy_top_stack_value_to("t0"); + self.line("lhu t0, 0(t0)"); // deref pointer in t0 to t0 + self.copy_to_top_of_stack("t0"); + }, + IR::Load32 => { + self.label("# load 32"); + self.copy_top_stack_value_to("t0"); + self.line("lwu t0, 0(t0)"); // deref pointer in t0 to t0 + self.copy_to_top_of_stack("t0"); + }, + IR::Load => { + self.label("# load 64"); + self.copy_top_stack_value_to("t0"); + self.line("ld t0, 0(t0)"); // deref pointer in t0 to t0 + self.copy_to_top_of_stack("t0"); + }, + IR::Store8 => { // ( x addr -- ) + self.pop_some_to("t0 t1"); + self.line("sb t0, 0(t1)"); // store x at addr + }, + IR::Store16 => { // ( x addr -- ) + self.pop_some_to("t0 t1"); + self.line("sh t0, 0(t1)"); // store x at addr + }, + IR::Store32 => { // ( x addr -- ) + self.pop_some_to("t0 t1"); + self.line("sw t0, 0(t1)"); // store x at addr + }, + IR::Store => { // ( x addr -- ) + self.pop_some_to("t0 t1"); + self.line("sd t0, 0(t1)"); // store x at addr + }, + IR::StackPush(num) => { + self.label(format!("# stackpush {}", num)); + self.line(format!("li t0, {}", num)); + self.push_from("t0"); + }, + IR::StackPushString(name) => { + self.label(format!("# stackpushstring {}", name)); + self.line(format!("la t0, {}", name)); + self.push_from("t0"); + }, + IR::AddU64 => { + self.label("# add"); + self.pop_call_push("t0 t1", "add t0, t0, t1", "t0"); + }, + IR::SubtractU64 => { + self.label("# sub"); + self.pop_call_push("t0 t1", "sub t0, t0, t1", "t0"); + }, + IR::MultiplyU64 => { + self.pop_call_push("t0 t1", "mul t0, t0, t1", "t0"); + }, + IR::DivideU64 => { + self.pop_call_push("t0 t1", "div t0, t0, t1", "t0"); + }, + IR::ModU64 => { + self.pop_call_push("t0 t1", "rem t0, t0, t1", "t0"); + }, + IR::Dup => { + self.label("# dup"); + self.copy_top_stack_value_to("t0"); + self.push_from("t0"); + }, + IR::Swap => { + self.label("# swap"); + self.pop_some_to("t1 t0"); + self.push_from("t0"); + self.push_from("t1"); + }, + IR::Rot => { + self.label("# rot"); + self.pop_some_to("t0 t1 t2"); + self.push_from("t1"); + self.push_from("t2"); + self.push_from("t0"); + }, + IR::StackPointer => { + self.label("# sp"); + self.line("addi t0, s2, 0"); + self.push_from("t0"); + }, + IR::Drop => { + self.label("# drop"); + self.move_stack_ptr_by_cells(1); + }, + IR::Equals => { + // Yes, this is the same as subtract, since we're treating 0 as true, and + // others as false. + self.pop_call_push("t0 t1", "sub t0, t0, t1", "t0"); + }, + IR::GreaterThan => { + self.label("# >"); + self.pop_some_to("t0 t1"); + self.line("sgt t0, t0, t1"); + self.line("seqz t0, t0"); // remember, 0 is true, others are false + self.push_from("t0"); + }, + IR::LessThan => { + self.label("# <"); + self.pop_some_to("t0 t1"); + self.line("slt t0, t0, t1"); + self.line("seqz t0, t0"); // remember, 0 is true, others are false + self.push_from("t0"); + }, + IR::BitwiseOr => { + self.pop_call_push("t0 t1", "or t0, t0, t1", "t0"); + }, + IR::Sys0 => { + self.pop_call_push("a7", "ecall", "a0"); + }, + IR::Sys1 => { + self.pop_call_push("a0 a7", "ecall", "a0"); + }, + IR::Sys2 => { + self.pop_call_push("a0 a1 a7", "ecall", "a0"); + }, + IR::Sys3 => { + self.pop_call_push("a0 a1 a2 a7", "ecall", "a0"); + }, + IR::Sys4 => { + self.pop_call_push("a0 a1 a2 a3 a7", "ecall", "a0"); + }, + IR::Sys5 => { + self.pop_call_push("a0 a1 a2 a3 a4 a7", "ecall", "a0"); + }, + IR::Sys6 => { + self.pop_call_push("a0 a1 a2 a3 a4 a5 a7", "ecall", "a0"); + }, + // https://cmput229.github.io/229-labs-RISCV/RISC-V-Examples_Public/03-Conditionals/03b-If_Else.html + IR::If => { + self.label("# if"); + self.pop_to("t0"); + self.line(format!("bnez t0, _else_{}", if_block_count)); + if_stack.push(if_block_count); + if_block_count += 1; + }, + IR::Else => { + self.label("# else"); + let if_counter = if_stack.last().unwrap().clone(); + self.line(format!("j _endif_{}", if_counter)); + self.label(format!("_else_{}:", if_counter)); + seen_else.insert(if_counter); + }, + IR::EndIf => { + self.label("# endif"); + let stack = &mut if_stack; + let if_counter = stack.last().unwrap().clone(); + if !seen_else.contains(&if_counter) { + self.label(format!("_else_{}:", if_counter)); + } else { + self.label(format!("_endif_{}:", if_counter)); + seen_else.remove(&if_counter); + } + stack.pop(); + }, + IR::Loop => { // keep looping until is true/0 + self.label(format!("_loop_{}:", loop_count)); + self.pop_to("t0"); + self.line(format!("beqz t0, _endloop_{}", loop_count)); + loop_stack.push(loop_count); + loop_count += 1; + }, + IR::EndLoop => { + let stack = &mut loop_stack; + let loop_counter = stack.last().unwrap().clone(); + self.line(format!("j _loop_{}", loop_counter)); + self.label(format!("_endloop_{}:", loop_counter)); + stack.pop(); + }, + _ => bail!("not implemented yet: {:?}", ir), + } + } + + Ok(self.lines.join("\n")) + } +} + diff --git a/sorelc/src/tokenizer.rs b/sorelc/src/tokenizer.rs new file mode 100644 index 0000000..d546c6b --- /dev/null +++ b/sorelc/src/tokenizer.rs @@ -0,0 +1,188 @@ +use anyhow::{Result, anyhow}; + +#[derive(Debug, Clone)] +pub enum Token<'a> { + Word(&'a str), + String(&'a str), + NumU8(u8), + NumI8(i8), + NumU16(u16), + NumI16(i16), + NumU32(u32), + NumI32(i32), + NumU64(u64), + NumI64(i64), + NumF32(f32), + NumF64(f64), +} + +impl<'a> Token<'a>{ + fn parse_word_or_num(input: &'a str) -> Result> { + if input == "-" { + return Ok(Token::Word(input)) + } + + // we're assuming any token starting with `-` with length greater than one + // is a negative number + if input.starts_with('-') || input.chars().nth(0).map(|x| x.is_numeric()).unwrap_or(false) { + if input.contains(':') { + let mut splat = input.split(':'); + let num = splat.next().ok_or(anyhow!("no number found"))?; + let typ = splat.next().ok_or(anyhow!("no number type found"))?; + match typ { + "u8" => Ok(Token::NumU8(num.parse()?)), + "i8" => Ok(Token::NumI8(num.parse()?)), + "u16" => Ok(Token::NumU16(num.parse()?)), + "i16" => Ok(Token::NumI16(num.parse()?)), + "u32" => Ok(Token::NumU32(num.parse()?)), + "i32" => Ok(Token::NumI32(num.parse()?)), + "u64" => Ok(Token::NumU64(num.parse()?)), + "i64" => Ok(Token::NumI64(num.parse()?)), + "f32" => Ok(Token::NumF32(num.parse()?)), + "f64" => Ok(Token::NumF64(num.parse()?)), + _ => panic!("unknown number type") + } + } else { + if input.contains('.') { + Ok(Token::NumF64(input.parse()?)) + } else if input.starts_with('-') { + Ok(Token::NumI64(input.parse()?)) + } else { + Ok(Token::NumU64(input.parse()?)) + } + } + } else { + Ok(Token::Word(input)) + } + } +} + +// TODO really want an iterator, not a vector +pub fn tokenize<'a>(input: &'a str) -> Result>> { + let mut result = vec![]; + let mut string_start: Option = None; + let mut word_or_num_start: Option = None; + let mut last_is_backslash = false; + let mut last_is_whitespace = true; + let mut in_doc_comment = false; + let mut in_line_comment = false; + let mut index = 0; + let mut first_char = true; + + + for char in input.chars() { + if first_char { + first_char = false; + } else { + index += 1; + } + + if in_doc_comment { + if char == ')' { + in_doc_comment = false; + last_is_whitespace = true; // not really true, but means don't need space after + } + continue; + } + + if in_line_comment { + word_or_num_start = None; + if char == '\n' { + in_line_comment = false; + last_is_whitespace = true; // not really true, but means don't need space after + } + continue; + } + + if char == '"' { + if let Some(start) = string_start { + if !last_is_backslash { + result.push(Token::String(&input[start..index])); + string_start = None; + } + } else { + string_start = Some(index + 1) + } + last_is_backslash = false; + last_is_whitespace = false; + continue; + } + + + if string_start.is_some() { + last_is_backslash = char == '\\'; + continue; + } + + if char.is_whitespace() { + if last_is_backslash { + in_line_comment = true; + } else if !last_is_whitespace && let Some(start) = word_or_num_start { + let token = &input[start..index]; + if token == "(" { + in_doc_comment = true; + } else { + result.push(Token::parse_word_or_num(&input[start..index])?); + } + word_or_num_start = None; + } + last_is_whitespace = true; + last_is_backslash = false; + continue; + } + + last_is_backslash = char == '\\'; + + if index == input.len() - 1 { + if !last_is_whitespace && let Some(start) = word_or_num_start { + result.push(Token::parse_word_or_num(&input[start..])?); + } + continue; + } + + if last_is_whitespace { // start of word or num (we already handled strings) + word_or_num_start = Some(index); + last_is_whitespace = false; + } + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn try_some_tokenizing() { + let result = tokenize(" + + \\ soup + 2 3.4 - -88 bacon \"hello\" 43:f32 2345:u32 -57:i8 soup +"); + println!("result: {:?}", result); + } + + #[test] + fn comments() { + let result = tokenize(" + ( + foo + bar + ) + : baz ( x y -- z ) + chicken + soup + ; + "); + println!("result: {:?}", result); + } + + #[test] + fn strings() { + let result = tokenize(" +dup \\ ( stuff ) +\"hello!\" +"); + println!("result: {:?}", result); + } +} diff --git a/stdlib/mem.sorel b/stdlib/mem.sorel new file mode 100644 index 0000000..5ed53c8 --- /dev/null +++ b/stdlib/mem.sorel @@ -0,0 +1,74 @@ +\ vim: filetype=forth + +: mmap 222 sys6 ; +: munmap 215 sys2 ; + +: PROT_READ 1 ; +: PROT_WRITE 2 ; +: MAP_PRIVATE 2 ; +: MAP_ANONYMOUS 32 ; + +: ALLOC_PROT PROT_READ PROT_WRITE | ; +: ALLOC_MAP MAP_PRIVATE MAP_ANONYMOUS | ; + +\ This is a raw way to allocate memory. Callers to this will need to know +\ how much memory was allocated, to call dealloc later. +: alloc_raw ( byte-count -- addr ) + 0 swap ALLOC_PROT ALLOC_MAP -1:i32 0 mmap +; + +: dealloc ( addr byte-count -- ) + munmap +; + +export alloc +: alloc ( byte-count -- addr ) + dup \ we'll need the length again later + 8 + \ add 64 bits to the front where we'll store the length + alloc_raw \ ( byte-count addr ) + dup \ ( byte-count addr addr) + rot \ ( addr addr byte-count ) + rot \ ( addr byte-count addr ) + ! \ write the length to the addr, now ( addr ) + 8 + \ here's the address we'll return, 8 bytes forward to account for the length +; + +export free +: free ( addr -- ) + 8 - \ the real memory start is 8 bytes before + dup \ ( addr addr ) + @ \ ( addr byte-length ) + 8 + \ add 8 to the byte length to refer to the whole region from alloc. + dealloc +; + +import "std:out" + +: copy_byte ( addr_from addr_to -- addr_from addr_to ) + swap \ ( addr_to addr_from ) + dup \ ( addr_to addr_from addr_from ) + @:8 \ ( addr_to addr_from byte ) + rot \ ( addr_from byte addr_to ) + dup \ ( addr_from byte addr_to addr_to) + rot \ ( addr_from addr_to addr_to byte ) + swap \ ( addr_from addr_to byte addr_to ) + !:8 \ ( addr_from addr_to ) +; + +export memcopy +: memcopy ( addr_from addr_to count -- ) + dup \ ( addr_from addr_to count count ) + loop \ ( addr_from addr_to count ) + rot \ ( addr_to count addr_from ) + rot \ ( count addr_from addr_to ) + copy_byte \ ( count addr_from addr_to ) + 1 + \ ( count addr_from addr_to+1 ) + rot \ ( addr_from addr_to+1 count ) + 1 - \ ( addr_from addr_to+1 count-1 ) + rot \ ( addr_to+1 count-1 addr_from ) + 1 + \ ( addr_to+1 count-1 addr_from+1 ) + rot rot \ ( addr_from+1 addr_to+1 count-1 ) + dup \ ( addr_from+1 addr_to+1 count-1 count-1) + endloop \ ( addr_from+count addr_to+count 0 ) + drop drop drop \ ( ) +; diff --git a/stdlib/out.sorel b/stdlib/out.sorel new file mode 100644 index 0000000..5704f99 --- /dev/null +++ b/stdlib/out.sorel @@ -0,0 +1,59 @@ +\ vim: filetype=forth + +: write ( fd ptr len -- bytes-written-or-err ) + 64 + sys3 +; + +import "std:string" + +export puts + +: puts ( addr -- addr) + dup dup \ ( addr addr addr ) + strlen \ ( addr addr len ) + 1 \ ( addr addr len 1 ) + rot \ ( addr len 1 addr ) + rot \ ( addr 1 addr len ) + write \ ( addr bytes-written-or-err ) + drop \ ( addr ) +; + +: ZERO_CHAR 48 ; +: NEWLINE_CHAR 10 ; + +export putn + +: putn ( num -- num ) + dup dup \ ( num num num ) + 10 rot rot \ ( num 10 num num ) // Mutltidigit stop point + 10 < \ ( num 10 num is<10 ) + loop \ ( num 10 num' ) + dup \ ( num 10 num' num' ) + 10 % \ ( num 10 num' digit ) + swap \ ( num 10 digit num' ) + 10 / \ ( num 10 digit num'/10 ) + dup 10 < \ ( num 10 digit num'/10 is<10 ) + endloop \ ( num 10 digitn ... digit1 ) + dup 9 > \ ( num 10 digitn ... digit1 digit1>9 ) + loop \ ( num 10 digitn ... digit1 ) + ZERO_CHAR + \ \ ( num 10 digitn ... digit1 ) + sp \ ( num 10 digitn ... digit1 ptr ) + 1 \ ( num 10 digitn ... digit1 ptr 1 ) + swap \ ( num 10 digitn ... digit1 1 ptr ) + 1 \ ( num 10 digitn ... digit1 1 ptr 1 ) + write \ ( num 10 digitn ... digit1 result ) + drop drop \ ( num 10 digitn ... digit2 ) + dup 9 > \ ( num 10 digitn ... digit2 digit2>9 ) + endloop \ ( num 10 ) + drop \ ( num ) + NEWLINE_CHAR \ ( num 10 ) + sp \ ( num 10 ptr ) + 1 \ ( num 10 ptr 1 ) + swap \ (num 10 1 ptr ) + 1 \ ( num 10 1 ptr 1 ) + write \ ( num 10 result ) + drop \ ( num 10 ) + drop \ ( num ) +; + diff --git a/stdlib/process.sorel b/stdlib/process.sorel new file mode 100644 index 0000000..73a7008 --- /dev/null +++ b/stdlib/process.sorel @@ -0,0 +1,8 @@ +\ vim: filetype=forth + +export exit + +: exit ( code -- ) + 93 + sys1 +; diff --git a/stdlib/string.sorel b/stdlib/string.sorel new file mode 100644 index 0000000..b0c76a8 --- /dev/null +++ b/stdlib/string.sorel @@ -0,0 +1,15 @@ +\ vim: filetype=forth + +: strlen ( addr -- len ) + dup dup \ ( addr addr addr ) + @:8 \ ( addr addr byte ) + loop \ ( addr addr ) + 1 + \ ( addr addr+1 ) + dup \ ( addr addr+1 addr+1) + @:8 \ ( addr addr+1 byte ) + endloop \ ( addr addr+len ) + swap \ ( addr+len addr ) + - \ ( len ) +; + +export strlen diff --git a/target/rust-analyzer/flycheck0/stderr b/target/rust-analyzer/flycheck0/stderr deleted file mode 100644 index f244f75..0000000 --- a/target/rust-analyzer/flycheck0/stderr +++ /dev/null @@ -1 +0,0 @@ - Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s diff --git a/target/rust-analyzer/flycheck0/stdout b/target/rust-analyzer/flycheck0/stdout deleted file mode 100644 index 8e625da..0000000 --- a/target/rust-analyzer/flycheck0/stdout +++ /dev/null @@ -1,38 +0,0 @@ -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.103/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.103/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/proc-macro2-bdf9ed00dad30299/build-script-build"],"executable":null,"fresh":true} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103","linked_libs":[],"linked_paths":[],"cfgs":["wrap_proc_macro","proc_macro_span_location","proc_macro_span_file"],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/proc-macro2-01b6d2c5fdefd348/out"} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.22","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.22/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicode_ident","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.22/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libunicode_ident-4fec18d14b15363e.rlib","/home/bengl/rethought/hylo-lang/target/debug/deps/libunicode_ident-4fec18d14b15363e.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.42/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.42/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/quote-03d3df1af8178ab4/build-script-build"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.103/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proc_macro2","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.103/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libproc_macro2-696ff2d61f065b42.rlib","/home/bengl/rethought/hylo-lang/target/debug/deps/libproc_macro2-696ff2d61f065b42.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.228/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.228/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["result","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/serde_core-31b971cf163f0f6c/build-script-build"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.228/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.228/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/serde-49a10a9683562367/build-script-build"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.16.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hashbrown","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.16.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhashbrown-3d3c587f4038bc13.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equivalent-1.0.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"equivalent","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equivalent-1.0.2/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libequivalent-9b88d636aa69c8ed.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#unsafe-libyaml@0.2.11","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unsafe-libyaml-0.2.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unsafe_libyaml","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unsafe-libyaml-0.2.11/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libunsafe_libyaml-5d3cabcbf60870d4.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.15","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/itoa-1.0.15/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itoa","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/itoa-1.0.15/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libitoa-bc1ca429a31fb99c.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.20","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ryu-1.0.20/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ryu","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ryu-1.0.20/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libryu-e3abc3dffcbd16d9.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#syscalls@0.7.0","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syscalls-0.7.0/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syscalls-0.7.0/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","serde","serde_repr","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/syscalls-fd98ea5acf3ec59b/build-script-build"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.100","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/build/anyhow-f0f8ac34947eb6de/build-script-build"],"executable":null,"fresh":true} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/quote-4fccd938ab7a0173/out"} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/serde_core-7b7cb0cfdf46fc20/out"} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228","linked_libs":[],"linked_paths":[],"cfgs":["if_docsrs_then_no_serde_core"],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/serde-0c79bc1bb5bf9eba/out"} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.12.1","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/indexmap-2.12.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"indexmap","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/indexmap-2.12.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libindexmap-274ebdc57a13914e.rmeta"],"executable":null,"fresh":true} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.100","linked_libs":[],"linked_paths":[],"cfgs":["std_backtrace"],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/anyhow-7dffd08ca73f1c01/out"} -{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#syscalls@0.7.0","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/bengl/rethought/hylo-lang/target/debug/build/syscalls-45c42af557e79235/out"} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.42","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.42/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quote","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.42/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libquote-49a93d0cff3d7298.rlib","/home/bengl/rethought/hylo-lang/target/debug/deps/libquote-49a93d0cff3d7298.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.228/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_core","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.228/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["result","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libserde_core-a2045ddd2bbb2331.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.100","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"anyhow","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libanyhow-a8e29080dfa88f6a.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.111","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-2.0.111/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"syn","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-2.0.111/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["clone-impls","default","derive","parsing","printing","proc-macro"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libsyn-d395eb58cbb9ffa6.rlib","/home/bengl/rethought/hylo-lang/target/debug/deps/libsyn-d395eb58cbb9ffa6.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_derive-1.0.228/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"serde_derive","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_derive-1.0.228/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libserde_derive-dca8d717b71cd876.so"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_repr@0.1.20","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_repr-0.1.20/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"serde_repr","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_repr-0.1.20/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libserde_repr-1a41abf411086f19.so"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.228/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.228/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libserde-a90341a4e2031dae.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_yaml@0.9.34+deprecated","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_yaml-0.9.34+deprecated/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_yaml","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_yaml-0.9.34+deprecated/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libserde_yaml-22ed54ad12da1e59.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#syscalls@0.7.0","manifest_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syscalls-0.7.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"syscalls","src_path":"/home/bengl/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syscalls-0.7.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","serde","serde_repr","std"],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libsyscalls-7a60ab2d0d4883d8.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-ir#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-ir/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_ir","src_path":"/home/bengl/rethought/hylo-lang/hylo-ir/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhylo_ir-4b38e4623b5ebbc5.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-ir#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-ir/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_ir","src_path":"/home/bengl/rethought/hylo-lang/hylo-ir/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhylo_ir-6637db13cb880a28.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-message","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-interpret#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_interpret","src_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: field `strings` is never read\n --> hylo-interpret/src/lib.rs:14:5\n |\n 8 | pub struct Interpreter<'a> {\n | ----------- field in this struct\n...\n14 | strings: Vec,\n | ^^^^^^^\n |\n = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"note","message":"`#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default","rendered":null,"spans":[]}],"level":"warning","message":"field `strings` is never read","spans":[{"byte_end":119,"byte_start":108,"column_end":23,"column_start":12,"expansion":null,"file_name":"hylo-interpret/src/lib.rs","is_primary":false,"label":"field in this struct","line_end":8,"line_start":8,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":23,"highlight_start":12,"text":"pub struct Interpreter<'a> {"}]},{"byte_end":287,"byte_start":280,"column_end":12,"column_start":5,"expansion":null,"file_name":"hylo-interpret/src/lib.rs","is_primary":true,"label":null,"line_end":14,"line_start":14,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":12,"highlight_start":5,"text":" strings: Vec,"}]}],"code":{"code":"dead_code","explanation":null}}} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-interpret#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_interpret","src_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhylo_interpret-3e4441b288f50835.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-message","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-interpret#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_interpret","src_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: field `strings` is never read\n --> hylo-interpret/src/lib.rs:14:5\n |\n 8 | pub struct Interpreter<'a> {\n | ----------- field in this struct\n...\n14 | strings: Vec,\n | ^^^^^^^\n |\n = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"note","message":"`#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default","rendered":null,"spans":[]}],"level":"warning","message":"field `strings` is never read","spans":[{"byte_end":119,"byte_start":108,"column_end":23,"column_start":12,"expansion":null,"file_name":"hylo-interpret/src/lib.rs","is_primary":false,"label":"field in this struct","line_end":8,"line_start":8,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":23,"highlight_start":12,"text":"pub struct Interpreter<'a> {"}]},{"byte_end":287,"byte_start":280,"column_end":12,"column_start":5,"expansion":null,"file_name":"hylo-interpret/src/lib.rs","is_primary":true,"label":null,"line_end":14,"line_start":14,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":12,"highlight_start":5,"text":" strings: Vec,"}]}],"code":{"code":"dead_code","explanation":null}}} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hylo-interpret#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"hylo_interpret","src_path":"/home/bengl/rethought/hylo-lang/hylo-interpret/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhylo_interpret-8c8a8fc209c55986.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hyloc#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hyloc/Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"hyloc","src_path":"/home/bengl/rethought/hylo-lang/hyloc/src/main.rs","edition":"2024","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhyloc-58f075f82bafc9dc.rmeta"],"executable":null,"fresh":true} -{"reason":"compiler-artifact","package_id":"path+file:///home/bengl/rethought/hylo-lang/hyloc#0.1.0","manifest_path":"/home/bengl/rethought/hylo-lang/hyloc/Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"hyloc","src_path":"/home/bengl/rethought/hylo-lang/hyloc/src/main.rs","edition":"2024","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":["/home/bengl/rethought/hylo-lang/target/debug/deps/libhyloc-4df3a47409d99128.rmeta"],"executable":null,"fresh":true} -{"reason":"build-finished","success":true} diff --git a/tests/assert.sorel b/tests/assert.sorel new file mode 100644 index 0000000..9d84758 --- /dev/null +++ b/tests/assert.sorel @@ -0,0 +1,13 @@ +\ vim: filetype=forth + +import "std:process" + +export assert + +: assert ( x -- ) + if + drop + else + 1 exit + endif +; diff --git a/tests/putstack.c b/tests/putstack.c new file mode 100644 index 0000000..a5200c4 --- /dev/null +++ b/tests/putstack.c @@ -0,0 +1,16 @@ +#include + +extern unsigned long data_stack_end; +register unsigned long * stack_pointer asm("s2"); + +void putstack() { + unsigned long * stack_index = &data_stack_end; + printf("stack: "); + while (stack_index != stack_pointer) { + printf("%ld ", *stack_index); + stack_index -= 1; + } + printf("%ld\n", *stack_pointer); +} + + diff --git a/tests/test.sh b/tests/test.sh new file mode 100644 index 0000000..279addc --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,11 @@ +UNAME=$(uname -m) +CMD_PREFIX=$([ "$UNAME" = "riscv64" ] && echo "" || echo "riscv64-unknown-linux-gnu-") +AS="${CMD_PREFIX}as" +LD="${CMD_PREFIX}ld" +CC="${CMD_PREFIX}cc" + +../target/debug/sorelc test1.sorel +$AS -g -o test1.o test1.asm +$CC -O1 -no-pie -o test1.out test1.o putstack.c -nostartfiles +# $LD -o test1.out test1.o +./test1.out diff --git a/tests/test1.sorel b/tests/test1.sorel new file mode 100644 index 0000000..3256625 --- /dev/null +++ b/tests/test1.sorel @@ -0,0 +1,36 @@ +\ vim: filetype=forth + +import "./assert.sorel" +import "std:out" + +"Hello, World! \n" puts +drop + +42 putn +drop + +import "std:mem" + +64 alloc +free + +0 assert + +import "std:string" +import "std:mem" +import "std:out" + +extern putstack + +16 alloc \ ( ptr ) +dup \ ( ptr ptr ) +"To be copied!" \ ( ptr ptr strptr ) +dup \ ( ptr ptr strptr strptr ) +strlen 1 + \ ( ptr ptr strptr len ) +rot \ ( ptr strptr len ptr ) +swap \ ( ptr strptr ptr len ) +memcopy \ ( ptr ) +puts \ ( ptr ) +free \ ( ) + +