Compare commits

..

8 commits
trunk ... hbcb

Author SHA1 Message Date
mlokr 69e7ad0642
removing hbcb 2024-09-20 07:59:40 +02:00
mlokr 7f3984ad9a
removing some useless stuff 2024-09-19 19:22:54 +02:00
mlokr bfc92431ea
brah 2024-09-19 19:18:22 +02:00
mlokr ede18f86f8
moving codegen to instrs alongside disasm 2024-09-19 18:27:25 +02:00
mlokr 31c501c643
merge 2024-09-19 17:15:03 +02:00
mlokr 0d118c17b2
reducing dependencies 2024-09-19 17:13:51 +02:00
mlokr 0ae0cae825
blah 2024-09-17 15:14:24 +02:00
mlokr da69b705f1
initiating cranelift backend 2024-09-17 10:39:56 +02:00
210 changed files with 11406 additions and 20753 deletions

View file

@ -1,4 +1,2 @@
[alias]
xtask = "r -p xtask --"
wasm-build = "b --target wasm32-unknown-unknown --profile=small -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size,panic_immediate_abort -p"
wasm-build-debug = "b --target wasm32-unknown-unknown --profile=small-dev -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size -p"

13
.gitignore vendored
View file

@ -1,13 +1,4 @@
# garbage
/target
/hbbytecode/src/instrs.rs
/.rgignore
rustc-ice-*
# sqlite
db.sqlite
db.sqlite-journal
# assets
/depell/src/*.gz
/depell/src/*.wasm
#**/*-sv.rs
/bytecode/src/instrs.rs

1626
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,50 +1,17 @@
cargo-features = ["profile-rustflags"]
[workspace]
resolver = "2"
members = [
"bytecode",
"vm",
"xrt",
"xtask",
"lang",
"depell",
"depell/wasm-fmt",
"depell/wasm-hbc",
"depell/wasm-rt",
]
[workspace.dependencies]
hbbytecode = { path = "bytecode", default-features = false }
hbvm = { path = "vm", default-features = false }
hbxrt = { path = "xrt" }
hblang = { path = "lang", default-features = false }
hbjit = { path = "jit" }
members = ["hbbytecode", "hbvm", "hbxrt", "xtask", "hblang", "hbjit"]
[profile.release]
lto = true
#debug = true
strip = true
lto = true
codegen-units = 1
panic = "abort"
[profile.small]
rustflags = ["-Zfmt-debug=none", "-Zlocation-detail=none"]
inherits = "release"
opt-level = "z"
strip = "debuginfo"
strip = true
lto = true
codegen-units = 1
panic = "abort"
[profile.small-dev]
inherits = "dev"
opt-level = "z"
strip = "debuginfo"
panic = "abort"
[profile.fuzz]
inherits = "dev"
debug = true
opt-level = 3
panic = "abort"

View file

@ -1,23 +0,0 @@
[package]
name = "depell"
version = "0.1.0"
edition = "2021"
[dependencies]
argon2 = "0.5.3"
axum = "0.7.7"
axum-server = { version = "0.7.1", optional = true, features = ["rustls", "tls-rustls"] }
const_format = "0.2.33"
getrandom = "0.2.15"
hblang.workspace = true
htmlm = "0.5.0"
log = "0.4.22"
rand_core = { version = "0.6.4", features = ["getrandom"] }
rusqlite = { version = "0.32.1", features = ["bundled"] }
serde = { version = "1.0.210", features = ["derive"] }
time = "0.3.36"
tokio = { version = "1.40.0", features = ["rt"] }
[features]
#default = ["tls"]
tls = ["dep:axum-server"]

View file

@ -1,14 +0,0 @@
# Depell
Depell is a website that allows users to import/post/run hblang code and create huge dependency graphs. Its currently hosted at https://depell.mlokis.tech.
## Local Development
Prerequirements:
- rust nigthly toolchain: install rust from [here](https://www.rust-lang.org/tools/install)
```bash
rustup default nightly
cargo xtask watch-depell-debug
# browser http://localhost:8080
```

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="18px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View file

@ -1,180 +0,0 @@
* {
font-family: var(--font);
}
body {
--primary: white;
--secondary: #EFEFEF;
--timestamp: #777777;
--error: #ff3333;
--placeholder: #333333;
}
body {
--small-gap: 5px;
--font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--monospace: 'Courier New', Courier, monospace;
nav {
display: flex;
justify-content: space-between;
align-items: center;
section:last-child {
display: flex;
gap: var(--small-gap);
}
}
main {
margin-top: var(--small-gap);
display: flex;
flex-direction: column;
gap: var(--small-gap);
}
}
div.preview {
div.info {
display: flex;
gap: var(--small-gap);
span[apply=timestamp] {
color: var(--timestamp);
}
}
div.stat {
display: flex;
}
margin: var(--small-gap) 0px;
}
form {
display: flex;
flex-direction: column;
gap: var(--small-gap);
.error {
color: var(--error);
text-align: center;
}
}
textarea {
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
padding-top: calc(var(--small-gap) * 1.5);
font-family: var(--monospace);
resize: none;
tab-size: 4;
}
pre {
background: var(--secondary);
padding: var(--small-gap);
padding-top: calc(var(--small-gap) * 1.5);
margin: 0px;
font-family: var(--monospace);
tab-size: 4;
overflow-x: auto;
}
input {
font-size: inherit;
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
}
input:is(:hover, :focus) {
background: var(--primary);
}
button {
border: none;
outline: none;
font-size: inherit;
background: var(--secondary);
}
button:hover:not(:active) {
background: var(--primary);
}
div#code-editor {
display: flex;
position: relative;
textarea {
flex: 1;
}
span#code-size {
position: absolute;
right: 2px;
font-size: 12px;
}
}
div#dep-list {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--small-gap);
section {
width: 100%;
display: flex;
flex-direction: column;
text-align: center;
gap: var(--small-gap);
div {
text-align: left;
}
}
}
.syn {
font-family: var(--monospace);
&.Comment {
color: #939f91;
}
&.Keyword {
color: #f85552;
}
&.Identifier,
&.Directive {
color: #3a94c5;
}
/* &.Number {} */
&.String {
color: #8da101;
}
&.Op,
&.Assign {
color: #f57d26;
}
&.Paren,
&.Bracket,
&.Comma,
&.Dor,
&.Ctor,
&.Colon {
color: #5c6a72;
}
}

View file

@ -1,507 +0,0 @@
/// @ts-check
/** @return {never} */
function never() { throw new Error() }
/**@type{WebAssembly.Instance}*/ let hbcInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let hbcInstaceFuture;
async function getHbcInstance() {
hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {});
return hbcInstance ??= (await hbcInstaceFuture).instance;
}
const stack_pointer_offset = 1 << 20;
/** @param {WebAssembly.Instance} instance @param {Post[]} packages @param {number} fuel
* @returns {string} */
function compileCode(instance, packages, fuel) {
let {
INPUT, INPUT_LEN,
LOG_MESSAGES, LOG_MESSAGES_LEN,
memory, compile_and_run,
} = instance.exports;
if (!(true
&& memory instanceof WebAssembly.Memory
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& LOG_MESSAGES instanceof WebAssembly.Global
&& LOG_MESSAGES_LEN instanceof WebAssembly.Global
&& typeof compile_and_run === "function"
)) never();
const codeLength = packPosts(packages, new DataView(memory.buffer, INPUT.value));
new DataView(memory.buffer).setUint32(INPUT_LEN.value, codeLength, true);
runWasmFunction(instance, compile_and_run, fuel);
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN);
}
/**@type{WebAssembly.Instance}*/ let fmtInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let fmtInstaceFuture;
async function getFmtInstance() {
fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
return fmtInstance ??= (await fmtInstaceFuture).instance;
}
/** @param {WebAssembly.Instance} instance @param {string} code @param {"tok" | "fmt" | "minify"} action
* @returns {string | Uint8Array | undefined} */
function modifyCode(instance, code, action) {
let {
INPUT, INPUT_LEN,
OUTPUT, OUTPUT_LEN,
memory, fmt, tok, minify
} = instance.exports;
let funs = { fmt, tok, minify };
let fun = funs[action];
if (!(true
&& memory instanceof WebAssembly.Memory
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& OUTPUT instanceof WebAssembly.Global
&& OUTPUT_LEN instanceof WebAssembly.Global
&& funs.hasOwnProperty(action)
&& typeof fun === "function"
)) never();
if (action !== "fmt") {
INPUT = OUTPUT;
INPUT_LEN = OUTPUT_LEN;
}
let dw = new DataView(memory.buffer);
dw.setUint32(INPUT_LEN.value, code.length, true);
new Uint8Array(memory.buffer, INPUT.value).set(new TextEncoder().encode(code));
if (!runWasmFunction(instance, fun)) {
return undefined;
}
if (action === "tok") {
return bufSlice(memory, OUTPUT, OUTPUT_LEN);
} else {
return bufToString(memory, OUTPUT, OUTPUT_LEN);
}
}
/** @param {WebAssembly.Instance} instance @param {CallableFunction} func @param {any[]} args
* @returns {boolean} */
function runWasmFunction(instance, func, ...args) {
const { PANIC_MESSAGE, PANIC_MESSAGE_LEN, memory, stack_pointer } = instance.exports;
if (!(true
&& memory instanceof WebAssembly.Memory
&& stack_pointer instanceof WebAssembly.Global
)) never();
const ptr = stack_pointer.value;
try {
func(...args);
return true;
} catch (error) {
if (error instanceof WebAssembly.RuntimeError
&& error.message == "unreachable"
&& PANIC_MESSAGE instanceof WebAssembly.Global
&& PANIC_MESSAGE_LEN instanceof WebAssembly.Global) {
console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN), error);
} else {
console.error(error);
}
stack_pointer.value = ptr;
return false;
}
}
/** @typedef {Object} Post
* @property {string} path
* @property {string} code */
/** @param {Post[]} posts @param {DataView} view @returns {number} */
function packPosts(posts, view) {
const enc = new TextEncoder(), buf = new Uint8Array(view.buffer, view.byteOffset);
let len = 0; for (const post of posts) {
view.setUint16(len, post.path.length, true); len += 2;
buf.set(enc.encode(post.path), len); len += post.path.length;
view.setUint16(len, post.code.length, true); len += 2;
buf.set(enc.encode(post.code), len); len += post.code.length;
}
return len;
}
/** @param {WebAssembly.Memory} mem
* @param {WebAssembly.Global} ptr
* @param {WebAssembly.Global} len
* @return {Uint8Array} */
function bufSlice(mem, ptr, len) {
return new Uint8Array(mem.buffer, ptr.value,
new DataView(mem.buffer).getUint32(len.value, true));
}
/** @param {WebAssembly.Memory} mem
* @param {WebAssembly.Global} ptr
* @param {WebAssembly.Global} len
* @return {string} */
function bufToString(mem, ptr, len) {
const res = new TextDecoder()
.decode(new Uint8Array(mem.buffer, ptr.value,
new DataView(mem.buffer).getUint32(len.value, true)));
new DataView(mem.buffer).setUint32(len.value, 0, true);
return res;
}
/** @param {HTMLElement} target */
function wireUp(target) {
execApply(target);
cacheInputs(target);
bindCodeEdit(target);
bindTextareaAutoResize(target);
}
const importRe = /@use\s*\(\s*"(([^"]|\\")+)"\s*\)/g;
/** @param {string} code
* @param {string[]} roots
* @param {Post[]} buf
* @param {Set<string>} prevRoots
* @returns {void} */
function loadCachedPackages(code, roots, buf, prevRoots) {
buf[0].code = code;
roots.length = 0;
let changed = false;
for (const match of code.matchAll(importRe)) {
changed ||= !prevRoots.has(match[1]);
roots.push(match[1]);
}
if (!changed) return;
buf.length = 1;
prevRoots.clear();
for (let imp = roots.pop(); imp !== undefined; imp = roots.pop()) {
if (prevRoots.has(imp)) continue; prevRoots.add(imp);
buf.push({ path: imp, code: localStorage.getItem("package-" + imp) ?? never() });
for (const match of buf[buf.length - 1].code.matchAll(importRe)) {
roots.push(match[1]);
}
}
}
/**@type{Set<string>}*/ const prevRoots = new Set();
/** @param {HTMLElement} target */
async function bindCodeEdit(target) {
const edit = target.querySelector("#code-edit");
if (!(edit instanceof HTMLTextAreaElement)) return;
const codeSize = target.querySelector("#code-size");
const errors = target.querySelector("#compiler-output");
if (!(true
&& codeSize instanceof HTMLSpanElement
&& errors instanceof HTMLPreElement
)) never();
const MAX_CODE_SIZE = parseInt(codeSize.innerHTML);
if (Number.isNaN(MAX_CODE_SIZE)) never();
const hbc = await getHbcInstance(), fmt = await getFmtInstance();
let importDiff = new Set();
const keyBuf = [];
/**@type{Post[]}*/
const packages = [{ path: "local.hb", code: "" }];
const debounce = 100;
/**@type{AbortController|undefined}*/
let cancelation = undefined;
let timeout = 0;
prevRoots.clear();
const onInput = () => {
importDiff.clear();
for (const match of edit.value.matchAll(importRe)) {
if (localStorage["package-" + match[1]]) continue;
importDiff.add(match[1]);
}
if (importDiff.size !== 0) {
if (cancelation) cancelation.abort();
cancelation = new AbortController();
keyBuf.length = 0;
keyBuf.push(...importDiff.keys());
errors.textContent = "fetching: " + keyBuf.join(", ");
fetch(`/code`, {
method: "POST",
signal: cancelation.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(keyBuf),
}).then(async e => {
try {
const json = await e.json();
if (e.status == 200) {
for (const [key, value] of Object.entries(json)) {
localStorage["package-" + key] = value;
}
const missing = keyBuf.filter(i => json[i] === undefined);
if (missing.length !== 0) {
errors.textContent = "deps not found: " + missing.join(", ");
} else {
cancelation = undefined;
edit.dispatchEvent(new InputEvent("input"));
}
}
} catch (er) {
errors.textContent = "completely failed to fetch ("
+ e.status + "): " + keyBuf.join(", ");
console.error(e, er);
}
});
}
if (cancelation && importDiff.size !== 0) {
return;
}
loadCachedPackages(edit.value, keyBuf, packages, prevRoots);
errors.textContent = compileCode(hbc, packages, 1);
const minified_size = modifyCode(fmt, edit.value, "minify")?.length;
if (minified_size) {
codeSize.textContent = (MAX_CODE_SIZE - minified_size) + "";
const perc = Math.min(100, Math.floor(100 * (minified_size / MAX_CODE_SIZE)));
codeSize.style.color = `color-mix(in srgb, white, var(--error) ${perc}%)`;
}
timeout = 0;
};
edit.addEventListener("input", () => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(onInput, debounce)
});
edit.dispatchEvent(new InputEvent("input"));
}
/**
* @type {Array<string>}
* to be synched with `enum TokenGroup` in bytecode/src/fmt.rs */
const TOK_CLASSES = [
'Blank',
'Comment',
'Keyword',
'Identifier',
'Directive',
'Number',
'String',
'Op',
'Assign',
'Paren',
'Bracket',
'Colon',
'Comma',
'Dot',
'Ctor',
];
/** @type {{ [key: string]: (el: HTMLElement) => void | Promise<void> }} */
const applyFns = {
timestamp: (el) => {
const timestamp = el.innerText;
const date = new Date(parseInt(timestamp) * 1000);
el.innerText = date.toLocaleString();
},
fmt,
};
/**
* @param {HTMLElement} target */
async function fmt(target) {
const code = target.innerText;
const instance = await getFmtInstance();
const decoder = new TextDecoder('utf-8');
const fmt = modifyCode(instance, code, 'fmt');
if (typeof fmt !== "string") never()
const codeBytes = new TextEncoder().encode(fmt);
const tok = modifyCode(instance, fmt, 'tok');
if (!(tok instanceof Uint8Array)) never();
target.innerHTML = '';
let start = 0;
let kind = tok[0];
for (let ii = 1; ii <= tok.length; ii += 1) {
// split over same tokens and buffer end
if (tok[ii] === kind && ii < tok.length) {
continue;
}
const text = decoder.decode(codeBytes.subarray(start, ii));
const textNode = document.createTextNode(text);;
if (kind === 0) {
target.appendChild(textNode);
} else {
const el = document.createElement('span');
el.classList.add('syn');
el.classList.add(TOK_CLASSES[kind]);
el.appendChild(textNode);
target.appendChild(el);
}
if (ii == tok.length) {
break;
}
start = ii;
kind = tok[ii];
}
}
/** @param {HTMLElement} target */
async function execApply(target) {
for (const elem of target.querySelectorAll('[apply]')) {
if (!(elem instanceof HTMLElement)) continue;
const funcname = elem.getAttribute('apply') ?? never();
applyFns[funcname](elem);
}
}
/** @param {HTMLElement} target */
function bindTextareaAutoResize(target) {
for (const textarea of target.querySelectorAll("textarea")) {
if (!(textarea instanceof HTMLTextAreaElement)) never();
const taCssMap = window.getComputedStyle(textarea);
const padding = parseInt(taCssMap.getPropertyValue('padding-top') ?? "0")
+ parseInt(taCssMap.getPropertyValue('padding-top') ?? "0");
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight - padding) + "px";
textarea.style.overflowY = "hidden";
textarea.addEventListener("input", function() {
let top = window.scrollY;
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight - padding) + "px";
window.scrollTo({ top });
});
textarea.onkeydown = (ev) => {
if (ev.key === "Tab") {
ev.preventDefault();
document.execCommand('insertText', false, "\t");
}
}
}
}
/** @param {HTMLElement} target */
function cacheInputs(target) {
/**@type {HTMLFormElement}*/ let form;
for (form of target.querySelectorAll('form')) {
const path = form.getAttribute('hx-post') || form.getAttribute('hx-delete');
if (!path) {
console.warn('form does not have a hx-post or hx-delete attribute', form);
continue;
}
for (const input of form.elements) {
if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
if ('password submit button'.includes(input.type)) continue;
const key = path + input.name;
input.value = localStorage.getItem(key) ?? '';
input.addEventListener("input", () => localStorage.setItem(key, input.value));
} else {
console.warn("unhandled form element: ", input);
}
}
}
}
/** @param {string} [path] */
function updaetTab(path) {
for (const elem of document.querySelectorAll("button[hx-push-url]")) {
if (elem instanceof HTMLButtonElement)
elem.disabled = elem.getAttribute("hx-push-url") === (path ?? window.location.pathname);
}
}
if (window.location.hostname === 'localhost') {
let id; setInterval(async () => {
let new_id = await fetch('/hot-reload').then(reps => reps.text());
id ??= new_id;
if (id !== new_id) window.location.reload();
}, 300);
(async function test() {
{
const code = "main:=fn():void{return}";
const inst = await getFmtInstance()
const fmtd = modifyCode(inst, code, "fmt") ?? never();
if (typeof fmtd !== "string") never();
const prev = modifyCode(inst, fmtd, "minify") ?? never();
if (code != prev) console.error(code, prev);
}
{
const posts = [{
path: "foo.hb",
code: "main:=fn():int{return 42}",
}];
const res = compileCode(await getHbcInstance(), posts, 1) ?? never();
const expected = "exit code: 42\n";
if (expected != res) console.error(expected, res);
}
})()
}
document.body.addEventListener('htmx:afterSwap', (ev) => {
if (!(ev.target instanceof HTMLElement)) never();
wireUp(ev.target);
if (ev.target.tagName == "MAIN" || ev.target.tagName == "BODY")
updaetTab(ev['detail'].pathInfo.finalRequestPath);
console.log(ev);
});
getFmtInstance().then(inst => {
document.body.addEventListener('htmx:configRequest', (ev) => {
const details = ev['detail'];
if (details.path === "/post" && details.verb === "post") {
details.parameters['code'] = modifyCode(inst, details.parameters['code'], "minify");
}
});
/** @param {string} query @param {string} target @returns {number} */
function fuzzyCost(query, target) {
let qi = 0, bi = 0, cost = 0, matched = false;
while (qi < query.length) {
if (query.charAt(qi) === target.charAt(bi++)) {
matched = true;
qi++;
} else {
cost++;
}
if (bi === target.length) (bi = 0, qi++);
}
return cost + (matched ? 0 : 100 * target.length);
}
let deps = undefined;
/** @param {HTMLInputElement} input @returns {void} */
function filterCodeDeps(input) {
deps ??= document.getElementById("deps");
if (!(deps instanceof HTMLElement)) never();
if (input.value === "") {
deps.textContent = "results show here...";
return;
}
deps.innerHTML = "";
for (const root of [...prevRoots.keys()]
.sort((a, b) => fuzzyCost(input.value, a) - fuzzyCost(input.value, b))) {
const pane = document.createElement("div");
const code = modifyCode(inst, localStorage["package-" + root], "fmt");
pane.innerHTML = `<div>${root}</div><pre>${code}</pre>`;
deps.appendChild(pane);
}
if (deps.innerHTML === "") {
deps.textContent = "no results";
}
}
Object.assign(window, { filterCodeDeps });
});
updaetTab();
wireUp(document.body);

View file

@ -1,909 +0,0 @@
#![feature(iter_collect_into)]
use {
argon2::{password_hash::SaltString, PasswordVerifier},
axum::{
body::Bytes,
extract::{DefaultBodyLimit, Path},
http::{header::COOKIE, request::Parts},
response::{AppendHeaders, Html},
},
const_format::formatcp,
core::fmt,
htmlm::{html, write_html},
rand_core::OsRng,
serde::{Deserialize, Serialize},
std::{
collections::{HashMap, HashSet},
fmt::{Display, Write},
net::Ipv4Addr,
},
};
const MAX_NAME_LENGTH: usize = 32;
const MAX_POSTNAME_LENGTH: usize = 64;
const MAX_CODE_LENGTH: usize = 1024 * 4;
const SESSION_DURATION_SECS: u64 = 60 * 60;
const MAX_FEED_SIZE: usize = 8 * 1024;
type Redirect<const COUNT: usize = 1> = AppendHeaders<[(&'static str, &'static str); COUNT]>;
macro_rules! static_asset {
($mime:literal, $body:literal) => {
get(|| async {
axum::http::Response::builder()
.header("content-type", $mime)
.header("content-encoding", "gzip")
.body(axum::body::Body::from(Bytes::from_static(include_bytes!(concat!(
$body, ".gz"
)))))
.unwrap()
})
};
}
async fn amain() {
use axum::routing::{delete, get, post};
let debug = cfg!(debug_assertions);
log::set_logger(&Logger).unwrap();
log::set_max_level(if debug { log::LevelFilter::Warn } else { log::LevelFilter::Error });
db::init();
let router = axum::Router::new()
.route("/", get(Index::page))
.route("/index.css", static_asset!("text/css", "index.css"))
.route("/index.js", static_asset!("text/javascript", "index.js"))
.route("/hbfmt.wasm", static_asset!("application/wasm", "hbfmt.wasm"))
.route("/hbc.wasm", static_asset!("application/wasm", "hbc.wasm"))
.route("/index-view", get(Index::get))
.route("/feed", get(Feed::page))
.route("/feed-view", get(Feed::get))
.route("/feed-more", post(Feed::more))
.route("/profile", get(Profile::page))
.route("/profile-view", get(Profile::get))
.route("/profile/:name", get(Profile::get_other_page))
.route("/profile/password", post(PasswordChange::post))
.route("/profile-view/:name", get(Profile::get_other))
.route("/post", get(Post::page))
.route("/post-view", get(Post::get))
.route("/post", post(Post::post))
.route("/code", post(fetch_code))
.route("/login", get(Login::page))
.route("/login-view", get(Login::get))
.route("/login", post(Login::post))
.route("/login", delete(Login::delete))
.route("/signup", get(Signup::page))
.route("/signup-view", get(Signup::get))
.route("/signup", post(Signup::post))
.route(
"/hot-reload",
get({
let id = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
move || async move { id.to_string() }
}),
)
.layer(DefaultBodyLimit::max(16 * 1024));
#[cfg(feature = "tls")]
{
let addr =
(Ipv4Addr::UNSPECIFIED, std::env::var("DEPELL_PORT").unwrap().parse::<u16>().unwrap());
let config = axum_server::tls_rustls::RustlsConfig::from_pem_file(
std::env::var("DEPELL_CERT_PATH").unwrap(),
std::env::var("DEPELL_KEY_PATH").unwrap(),
)
.await
.unwrap();
axum_server::bind_rustls(addr.into(), config)
.serve(router.into_make_service())
.await
.unwrap();
}
#[cfg(not(feature = "tls"))]
{
let addr = (Ipv4Addr::UNSPECIFIED, 8080);
let socket = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(socket, router).await.unwrap();
}
}
async fn fetch_code(
axum::Json(paths): axum::Json<Vec<String>>,
) -> axum::Json<HashMap<String, String>> {
let mut deps = HashMap::<String, String>::new();
db::with(|db| {
for path in &paths {
let Some((author, name)) = path.split_once('/') else { continue };
db.fetch_deps
.query_map((name, author), |r| {
Ok((
r.get::<_, String>(1)? + "/" + r.get_ref(0)?.as_str()?,
r.get::<_, String>(2)?,
))
})
.log("fetch deps query")
.into_iter()
.flatten()
.filter_map(|r| r.log("deps row"))
.collect_into(&mut deps);
}
});
axum::Json(deps)
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Feed {
Before { before_timestamp: u64 },
}
#[derive(Deserialize)]
struct Before {
before_timestamp: u64,
}
impl Feed {
async fn more(session: Session, axum::Form(data): axum::Form<Before>) -> Html<String> {
Self::Before { before_timestamp: data.before_timestamp }.render(&session)
}
}
impl Default for Feed {
fn default() -> Self {
Self::Before { before_timestamp: now() + 3600 }
}
}
impl Page for Feed {
fn render_to_buf(self, _: &Session, buf: &mut String) {
db::with(|db| {
let cursor = match self {
Feed::Before { before_timestamp } => db
.get_pots_before
.query_map((before_timestamp,), Post::from_row)
.log("fetch before posts query")
.into_iter()
.flatten()
.filter_map(|r| r.log("fetch before posts row")),
};
let base_len = buf.len();
let mut last_timestamp = None;
for post in cursor {
write!(buf, "{}", post).unwrap();
if buf.len() - base_len > MAX_FEED_SIZE {
last_timestamp = Some(post.timestamp);
break;
}
}
write_html!((*buf)
if let Some(last_timestamp) = last_timestamp {
<div "hx-post"="/feed-more"
"hx-trigger"="intersect once"
"hx-swap"="outerHTML"
"hx-vals"={format_args!("{{\"before_timestamp\":{last_timestamp}}}")}
>"there might be more"</div>
} else {
"no more stuff"
}
);
});
}
}
#[derive(Default)]
struct Index;
impl PublicPage for Index {
fn render_to_buf(self, buf: &mut String) {
buf.push_str(include_str!("welcome-page.html"));
}
}
#[derive(Deserialize, Default)]
struct Post {
author: String,
name: String,
#[serde(skip)]
timestamp: u64,
#[serde(skip)]
imports: usize,
#[serde(skip)]
runs: usize,
#[serde(skip)]
dependencies: usize,
code: String,
#[serde(skip)]
error: Option<&'static str>,
}
impl Page for Post {
fn render_to_buf(self, session: &Session, buf: &mut String) {
let Self { name, code, error, .. } = self;
write_html! { (buf)
<form id="postForm" "hx-post"="/post" "hx-swap"="outerHTML">
if let Some(e) = error { <div class="error">e</div> }
<input name="author" type="text" value={session.name} hidden>
<input name="name" type="text" placeholder="name" value=name
required maxlength=MAX_POSTNAME_LENGTH>
<div id="code-editor">
<textarea id="code-edit" name="code" placeholder="code" rows=1
required>code</textarea>
<span id="code-size">MAX_CODE_LENGTH</span>
</div>
<input type="submit" value="submit">
<pre id="compiler-output"></pre>
</form>
!{include_str!("post-page.html")}
}
}
}
impl Post {
pub fn from_row(r: &rusqlite::Row) -> rusqlite::Result<Self> {
Ok(Post {
author: r.get(0)?,
name: r.get(1)?,
timestamp: r.get(2)?,
code: r.get(3)?,
imports: r.get(4)?,
..Default::default()
})
}
async fn post(
session: Session,
axum::Form(mut data): axum::Form<Self>,
) -> Result<Redirect, Html<String>> {
if data.name.len() > MAX_POSTNAME_LENGTH {
data.error = Some(formatcp!("name too long, max length is {MAX_POSTNAME_LENGTH}"));
return Err(data.render(&session));
}
if data.code.len() > MAX_CODE_LENGTH {
data.error = Some(formatcp!("code too long, max length is {MAX_CODE_LENGTH}"));
return Err(data.render(&session));
}
db::with(|db| {
if let Err(e) = db.create_post.insert((&data.name, &session.name, now(), &data.code)) {
if let rusqlite::Error::SqliteFailure(e, _) = e {
if e.code == rusqlite::ErrorCode::ConstraintViolation {
data.error = Some("this name is already used");
}
}
data.error = data.error.or_else(|| {
log::error!("create post error: {e}");
Some("internal server error")
});
return;
}
for (author, name) in hblang::lexer::Lexer::uses(&data.code)
.filter_map(|v| v.split_once('/'))
.collect::<HashSet<_>>()
{
if db
.create_import
.insert((author, name, &session.name, &data.name))
.log("create import query")
.is_none()
{
data.error = Some("internal server error");
return;
};
}
});
if data.error.is_some() {
Err(data.render(&session))
} else {
Ok(redirect("/profile"))
}
}
}
impl fmt::Display for Post {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { author, name, timestamp, imports, runs, dependencies, code, .. } = self;
write_html! { f <div class="preview">
<div class="info">
<span>
<a "hx-get"={format_args!("/profile-view/{author}")} href="" "hx-target"="main"
"hx-push-url"={format_args!("/profile/{author}")}
"hx-swam"="innerHTML">author</a>
"/"
name
</span>
<span apply="timestamp">timestamp</span>
for (name, count) in [include_str!("icons/download.svg"), "runs", "deps"]
.iter()
.zip([imports, runs, dependencies])
.filter(|(_, &c)| c != 0)
{
<div class="stat">!name count</div>
}
</div>
<pre apply="fmt">code</pre>
if *timestamp == 0 {
<button "hx-get"="/post" "hx-swap"="outerHTML"
"hx-target"="[preview]">"edit"</button>
}
</div> }
Ok(())
}
}
#[derive(Deserialize, Default)]
struct PasswordChange {
old_password: String,
new_password: String,
#[serde(skip)]
error: Option<&'static str>,
}
impl PasswordChange {
async fn post(
session: Session,
axum::Form(mut change): axum::Form<PasswordChange>,
) -> Html<String> {
db::with(|que| {
match que.authenticate.query_row((&session.name,), |r| r.get::<_, String>(1)) {
Ok(hash) if verify_password(&hash, &change.old_password).is_err() => {
change.error = Some("invalid credentials");
}
Ok(_) => {
let new_hashed = hash_password(&change.new_password);
match que
.change_passowrd
.execute((new_hashed, &session.name))
.log("execute update")
{
None => change.error = Some("intenal server error"),
Some(0) => change.error = Some("password is incorrect"),
Some(_) => {}
}
}
Err(rusqlite::Error::QueryReturnedNoRows) => {
change.error = Some("invalid credentials");
}
Err(e) => {
log::error!("login queri failed: {e}");
change.error = Some("internal server error");
}
}
});
if change.error.is_some() {
change.render(&session)
} else {
PasswordChange::default().render(&session)
}
}
}
impl Page for PasswordChange {
fn render_to_buf(self, _: &Session, buf: &mut String) {
let Self { old_password, new_password, error } = self;
write_html! { (buf)
<form "hx-post"="/profile/password" "hx-swap"="outerHTML">
if let Some(e) = error { <div class="error">e</div> }
<input name="old_password" type="password" autocomplete="old-password"
placeholder="old password" value=old_password>
<input name="new_password" type="password" autocomplete="new-password" placeholder="new password"
value=new_password>
<input type="submit" value="submit">
</form>
}
}
}
#[derive(Default)]
struct Profile {
other: Option<String>,
}
impl Profile {
async fn get_other(session: Session, Path(name): Path<String>) -> Html<String> {
Profile { other: Some(name) }.render(&session)
}
async fn get_other_page(session: Session, Path(name): Path<String>) -> Html<String> {
base(|b| Profile { other: Some(name) }.render_to_buf(&session, b), Some(&session))
}
}
impl Page for Profile {
fn render_to_buf(self, session: &Session, buf: &mut String) {
db::with(|db| {
let name = self.other.as_ref().unwrap_or(&session.name);
let iter = db
.get_user_posts
.query_map((name,), Post::from_row)
.log("get user posts query")
.into_iter()
.flatten()
.filter_map(|p| p.log("user post row"));
write_html! { (*buf)
if name == &session.name {
|b|{PasswordChange::default().render_to_buf(session, b)}
}
for post in iter {
!{post}
} else {
"no posts"
}
!{include_str!("profile-page.html")}
}
})
}
}
fn hash_password(password: &str) -> String {
use argon2::PasswordHasher;
argon2::Argon2::default()
.hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng))
.unwrap()
.to_string()
}
fn verify_password(hash: &str, password: &str) -> Result<(), argon2::password_hash::Error> {
argon2::Argon2::default()
.verify_password(password.as_bytes(), &argon2::PasswordHash::new(hash)?)
}
#[derive(Serialize, Deserialize, Default, Debug)]
struct Login {
name: String,
password: String,
#[serde(skip)]
error: Option<&'static str>,
}
impl PublicPage for Login {
fn render_to_buf(self, buf: &mut String) {
let Self { name, password, error } = self;
write_html! { (buf)
<form "hx-post"="/login" "hx-swap"="outerHTML">
if let Some(e) = error { <div class="error">e</div> }
<input name="name" type="text" autocomplete="name" placeholder="name" value=name
required maxlength=MAX_NAME_LENGTH>
<input name="password" type="password" autocomplete="current-password" placeholder="password"
value=password>
<input type="submit" value="submit">
</form>
}
}
}
impl Login {
async fn post(
axum::Form(mut data): axum::Form<Self>,
) -> Result<AppendHeaders<[(&'static str, String); 2]>, Html<String>> {
// TODO: hash password
let mut id = [0u8; 32];
db::with(|db| match db.authenticate.query_row((&data.name,), |r| r.get::<_, String>(1)) {
Ok(hash) => {
if verify_password(&hash, &data.password).is_err() {
data.error = Some("invalid credentials");
} else {
getrandom::getrandom(&mut id).unwrap();
if db
.login
.insert((id, &data.name, now() + SESSION_DURATION_SECS))
.log("create session query")
.is_none()
{
data.error = Some("internal server error");
}
}
}
Err(rusqlite::Error::QueryReturnedNoRows) => {
data.error = Some("invalid credentials");
}
Err(e) => {
log::error!("login queri failed: {e}");
data.error = Some("internal server error");
}
});
if data.error.is_some() {
Err(data.render())
} else {
Ok(AppendHeaders([
("hx-location", "/feed".into()),
(
"set-cookie",
format!(
"id={}; SameSite=Strict; Secure; Max-Age={SESSION_DURATION_SECS}",
to_hex(&id)
),
),
]))
}
}
async fn delete(session: Session) -> Redirect {
_ = db::with(|q| q.logout.execute((session.id,)).log("delete session query"));
redirect("/login")
}
}
#[derive(Serialize, Deserialize, Default)]
struct Signup {
name: String,
new_password: String,
confirm_password: String,
#[serde(default)]
confirm_no_password: bool,
#[serde(skip)]
error: Option<&'static str>,
}
impl PublicPage for Signup {
fn render_to_buf(self, buf: &mut String) {
let Signup { name, new_password, confirm_password, confirm_no_password, error } = self;
let vals = if confirm_no_password { "{\"confirm_no_password\":true}" } else { "{}" };
write_html! { (buf)
<form "hx-post"="/signup" "hx-swap"="outerHTML" "hx-vals"=vals>
if let Some(e) = error { <div class="error">e</div> }
<input name="name" type="text" autocomplete="name" placeholder="name" value=name
maxlength=MAX_NAME_LENGTH required>
<input name="new_password" type="password" autocomplete="new-password" placeholder="new password"
value=new_password>
<input name="confirm_password" type="password" autocomplete="confirm-password"
placeholder="confirm password" value=confirm_password>
<input type="submit" value="submit">
</form>
}
}
}
impl Signup {
async fn post(axum::Form(mut data): axum::Form<Self>) -> Result<Redirect, Html<String>> {
if data.name.len() > MAX_NAME_LENGTH {
data.error = Some(formatcp!("name too long, max length is {MAX_NAME_LENGTH}"));
return Err(data.render());
}
if !data.confirm_no_password && data.new_password.is_empty() {
data.confirm_no_password = true;
data.error = Some("Are you sure you don't want to use a password? (then submit again)");
return Err(data.render());
}
db::with(|db| {
// TODO: hash passwords
match db.register.insert((&data.name, hash_password(&data.new_password))) {
Ok(_) => {}
Err(rusqlite::Error::SqliteFailure(e, _))
if e.code == rusqlite::ErrorCode::ConstraintViolation =>
{
data.error = Some("username already taken");
}
Err(e) => {
log::error!("create user query: {e}");
data.error = Some("internal server error");
}
};
});
if data.error.is_some() {
Err(data.render())
} else {
Ok(redirect("/login"))
}
}
}
fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html<String> {
let username = session.map(|s| &s.name);
let nav_button = |f: &mut String, name: &str| {
write_html! {(f)
<button "hx-push-url"={format_args!("/{name}")}
"hx-get"={format_args!("/{name}-view")}
"hx-target"="main"
"hx-swap"="innerHTML">name</button>
}
};
Html(html! {
"<!DOCTYPE html>"
<html lang="en">
<head>
<meta name="charset" content="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/index.css">
</head>
<body>
<nav>
<button "hx-push-url"="/" "hx-get"="/index-view" "hx-target"="main" "hx-swap"="innerHTML">"depell"</button>
<section>
if let Some(username) = username {
<button "hx-push-url"={format_args!("/profile/{username}")} "hx-get"="/profile-view" "hx-target"="main"
"hx-swap"="innerHTML">username</button>
|f|{nav_button(f, "feed"); nav_button(f, "post")}
<button "hx-delete"="/login">"logout"</button>
} else {
|f|{nav_button(f, "login"); nav_button(f, "signup")}
}
</section>
</nav>
<section id="post-form"></section>
<main>|f|{body(f)}</main>
</body>
<script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
<script type="module" src="/index.js"></script>
</html>
})
}
struct Session {
name: String,
id: [u8; 32],
}
#[axum::async_trait]
impl<S> axum::extract::FromRequestParts<S> for Session {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.
type Rejection = Redirect;
/// Perform the extraction.
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
let err = redirect("/login");
let value = parts
.headers
.get_all(COOKIE)
.into_iter()
.find_map(|c| c.to_str().ok()?.trim().strip_prefix("id="))
.map(|c| c.split_once(';').unwrap_or((c, "")).0)
.ok_or(err)?;
let mut id = [0u8; 32];
parse_hex(value, &mut id).ok_or(err)?;
let (name, expiration) = db::with(|db| {
db.get_session
.query_row((id,), |r| Ok((r.get::<_, String>(0)?, r.get::<_, u64>(1)?)))
.log("fetching session")
.ok_or(err)
})?;
if expiration < now() {
return Err(err);
}
Ok(Self { name, id })
}
}
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn parse_hex(hex: &str, dst: &mut [u8]) -> Option<()> {
fn hex_to_nibble(b: u8) -> Option<u8> {
Some(match b {
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
b'0'..=b'9' => b - b'0',
_ => return None,
})
}
if hex.len() != dst.len() * 2 {
return None;
}
for (d, p) in dst.iter_mut().zip(hex.as_bytes().chunks_exact(2)) {
*d = (hex_to_nibble(p[0])? << 4) | hex_to_nibble(p[1])?;
}
Some(())
}
fn to_hex(src: &[u8]) -> String {
use std::fmt::Write;
let mut buf = String::new();
for &b in src {
write!(buf, "{b:02x}").unwrap()
}
buf
}
fn main() {
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(amain());
}
mod db {
use std::cell::RefCell;
macro_rules! gen_queries {
($vis:vis struct $name:ident {
$($qname:ident: $code:expr,)*
}) => {
$vis struct $name<'a> {
$($vis $qname: rusqlite::Statement<'a>,)*
}
impl<'a> $name<'a> {
fn new(db: &'a rusqlite::Connection) -> Self {
Self {
$($qname: db.prepare($code).unwrap(),)*
}
}
}
};
}
gen_queries! {
pub struct Queries {
register: "INSERT INTO user (name, password_hash) VALUES(?, ?)",
change_passowrd: "UPDATE user SET password_hash = ? WHERE name = ?",
authenticate: "SELECT name, password_hash FROM user WHERE name = ?",
login: "INSERT OR REPLACE INTO session (id, username, expiration) VALUES(?, ?, ?)",
logout: "DELETE FROM session WHERE id = ?",
get_session: "SELECT username, expiration FROM session WHERE id = ?",
get_user_posts: "SELECT author, name, timestamp, code FROM post WHERE author = ?
ORDER BY timestamp DESC",
// TODO: we might want to cache the recursive queries
get_pots_before: "SELECT author, name, timestamp, code, (
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
UNION
SELECT post.name, post.author, post.code FROM
post JOIN import ON post.name = import.from_name
AND post.author = import.from_author
JOIN roots ON import.to_name = roots.name
AND import.to_author = roots.author
) SELECT (count(*) - 1) FROM roots
) AS imports FROM post AS outher WHERE timestamp < ?",
create_post: "INSERT INTO post (name, author, timestamp, code) VALUES(?, ?, ?, ?)",
fetch_deps: "
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = ? AND author = ?
UNION
SELECT post.name, post.author, post.code FROM
post JOIN import ON post.name = import.to_name
AND post.author = import.to_author
JOIN roots ON import.from_name = roots.name
AND import.from_author = roots.author
) SELECT * FROM roots;
",
create_import: "INSERT INTO import(to_author, to_name, from_author, from_name)
VALUES(?, ?, ?, ?)",
}
}
struct Db {
queries: Queries<'static>,
_db: Box<rusqlite::Connection>,
}
impl Db {
fn new() -> Self {
let db = Box::new(rusqlite::Connection::open("db.sqlite").unwrap());
Self {
queries: Queries::new(unsafe {
std::mem::transmute::<&rusqlite::Connection, &rusqlite::Connection>(&db)
}),
_db: db,
}
}
}
pub fn with<T>(with: impl FnOnce(&mut Queries) -> T) -> T {
thread_local! { static DB_CONN: RefCell<Db> = RefCell::new(Db::new()); }
DB_CONN.with_borrow_mut(|q| with(&mut q.queries))
}
pub fn init() {
const SCHEMA_VERSION: usize = 0;
const MIGRATIONS: &[&str] = &[include_str!("migrations/1.sql")];
let db = rusqlite::Connection::open("db.sqlite").unwrap();
db.execute_batch(include_str!("schema.sql")).unwrap();
let schema_version =
db.pragma_query_value(None, "user_version", |v| v.get::<_, usize>(0)).unwrap();
if schema_version != SCHEMA_VERSION {
for &mig in &MIGRATIONS[schema_version..] {
db.execute_batch(mig).expect(mig);
}
db.pragma_update(None, "user_version", SCHEMA_VERSION).unwrap();
}
Queries::new(&db);
}
}
fn redirect(to: &'static str) -> Redirect {
AppendHeaders([("hx-location", to)])
}
trait PublicPage: Default {
fn render_to_buf(self, buf: &mut String);
fn render(self) -> Html<String> {
let mut str = String::new();
self.render_to_buf(&mut str);
Html(str)
}
async fn get() -> Html<String> {
Self::default().render()
}
async fn page(session: Option<Session>) -> Html<String> {
base(|s| Self::default().render_to_buf(s), session.as_ref())
}
}
trait Page: Default {
fn render_to_buf(self, session: &Session, buf: &mut String);
fn render(self, session: &Session) -> Html<String> {
let mut str = String::new();
self.render_to_buf(session, &mut str);
Html(str)
}
async fn get(session: Session) -> Html<String> {
Self::default().render(&session)
}
async fn page(session: Option<Session>) -> Result<Html<String>, axum::response::Redirect> {
match session {
Some(session) => {
Ok(base(|f| Self::default().render_to_buf(&session, f), Some(&session)))
}
None => Err(axum::response::Redirect::permanent("/login")),
}
}
}
trait ResultExt<O, E> {
fn log(self, prefix: impl Display) -> Option<O>;
}
impl<O, E: Display> ResultExt<O, E> for Result<O, E> {
fn log(self, prefix: impl Display) -> Option<O> {
match self {
Ok(v) => Some(v),
Err(e) => {
log::error!("{prefix}: {e}");
None
}
}
}
}
struct Logger;
impl log::Log for Logger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
eprintln!("{} - {}", record.module_path().unwrap_or("=="), record.args());
}
}
fn flush(&self) {}
}

View file

@ -1 +0,0 @@

View file

@ -1,21 +0,0 @@
<div id="dep-list">
<input placeholder="search impoted deps.." oninput="filterCodeDeps(this, event)">
<section id="deps">
results show here...
</section>
</div>
<div>
<h3>About posting code</h3>
<p>
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
<strong>hblang/README.md</strong> or
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
</p>
<h3>Extra textarea features</h3>
<ul>
<li>proper tab behaviour</li>
<li>snap to previous tab boundary on "empty" lines</li>
</ul>

View file

@ -1,55 +0,0 @@
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS user(
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
PRIMARY KEY (name)
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS session(
id BLOB NOT NULL,
username TEXT NOT NULL,
expiration INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES user (name)
PRIMARY KEY (username)
) WITHOUT ROWID;
CREATE UNIQUE INDEX IF NOT EXISTS
session_id ON session (id);
CREATE TABLE IF NOT EXISTS post(
name TEXT NOT NULL,
author TEXT,
timestamp INTEGER,
code TEXT NOT NULL,
FOREIGN KEY (author) REFERENCES user(name) ON DELETE SET NULL,
PRIMARY KEY (author, name)
);
CREATE INDEX IF NOT EXISTS
post_timestamp ON post(timestamp DESC);
CREATE TABLE IF NOT EXISTS import(
from_name TEXT NOT NULL,
from_author TEXT,
to_name TEXT NOT NULL,
to_author TEXT,
FOREIGN KEY (from_name, from_author) REFERENCES post(name, author),
FOREIGN KEY (to_name, to_author) REFERENCES post(name, author)
);
CREATE INDEX IF NOT EXISTS
dependencies ON import(from_name, from_author);
CREATE INDEX IF NOT EXISTS
dependants ON import(to_name, to_author);
CREATE TABLE IF NOT EXISTS run(
code_name TEXT NOT NULL,
code_author TEXT NOT NULL,
runner TEXT NOT NULL,
FOREIGN KEY (code_name, code_author) REFERENCES post(name, author),
FOREIGN KEY (runner) REFERENCES user(name),
PRIMARY KEY (code_name, code_author, runner)
);

View file

@ -1,17 +0,0 @@
<h1>Welcome to depell</h1>
<p>
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
</p>
<p>
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
</p>
<h2>Security?</h2>
<p>
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
arbitrary JS code, WASM can act as a container inside the JS.
</p>

View file

@ -1,11 +0,0 @@
[package]
name = "wasm-hbfmt"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
hblang = { workspace = true, features = ["no_log"] }
wasm-rt = { version = "0.1.0", path = "../wasm-rt" }

View file

@ -1,42 +0,0 @@
#![no_std]
#![feature(str_from_raw_parts)]
#![feature(alloc_error_handler)]
use hblang::{fmt, parser};
wasm_rt::decl_runtime!(128 * 1024, 1024 * 4);
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
wasm_rt::decl_buffer!(MAX_OUTPUT_SIZE, MAX_OUTPUT, OUTPUT, OUTPUT_LEN);
const MAX_INPUT_SIZE: usize = 1024 * 4;
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
unsafe extern "C" fn fmt() {
ALLOCATOR.reset();
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR);
let mut ctx = parser::Ctx::default();
let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut parser::no_loader, &arena);
let mut f = wasm_rt::Write(&mut OUTPUT[..]);
fmt::fmt_file(exprs, code, &mut f).unwrap();
OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len();
}
#[no_mangle]
unsafe extern "C" fn tok() {
let code = core::slice::from_raw_parts_mut(
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = fmt::get_token_kinds(code);
}
#[no_mangle]
unsafe extern "C" fn minify() {
let code = core::str::from_raw_parts_mut(
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = fmt::minify(code);
}

View file

@ -1,14 +0,0 @@
[package]
name = "wasm-hbc"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
hblang = { workspace = true, features = [] }
hbvm.workspace = true
log = { version = "0.4.22", features = ["release_max_level_error"] }
wasm-rt = { version = "0.1.0", path = "../wasm-rt", features = ["log"] }

View file

@ -1,119 +0,0 @@
#![feature(alloc_error_handler)]
#![feature(slice_take)]
#![no_std]
use {
alloc::{string::String, vec::Vec},
hblang::{
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
ty::Module,
Ent,
},
};
extern crate alloc;
const ARENA_CAP: usize = 128 * 16 * 1024;
wasm_rt::decl_runtime!(ARENA_CAP, 1024 * 4);
const MAX_INPUT_SIZE: usize = 32 * 4 * 1024;
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
unsafe fn compile_and_run(mut fuel: usize) {
ALLOCATOR.reset();
_ = log::set_logger(&wasm_rt::Logger);
log::set_max_level(log::LevelFilter::Error);
struct File<'a> {
path: &'a str,
code: &'a mut str,
}
let mut root = 0;
let files = {
let mut input_bytes =
core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(INPUT).cast::<u8>(), INPUT_LEN);
let mut files = Vec::with_capacity(32);
while let Some((&mut path_len, rest)) = input_bytes.split_first_chunk_mut() {
let (path, rest) = rest.split_at_mut(u16::from_le_bytes(path_len) as usize);
let (&mut code_len, rest) = rest.split_first_chunk_mut().unwrap();
let (code, rest) = rest.split_at_mut(u16::from_le_bytes(code_len) as usize);
files.push(File {
path: core::str::from_utf8_unchecked(path),
code: core::str::from_utf8_unchecked_mut(code),
});
input_bytes = rest;
}
let root_path = files[root].path;
hblang::quad_sort(&mut files, |a, b| a.path.cmp(b.path));
root = files.binary_search_by_key(&root_path, |p| p.path).unwrap();
files
};
let mut ctx = CodegenCtx::default();
let files = {
let paths = files.iter().map(|f| f.path).collect::<Vec<_>>();
let mut loader = |path: &str, _: &str, kind| match kind {
hblang::parser::FileKind::Module => Ok(paths.binary_search(&path).unwrap()),
hblang::parser::FileKind::Embed => Err("embeds are not supported".into()),
};
files
.into_iter()
.map(|f| {
hblang::parser::Ast::new(
f.path,
// since 'free' does nothing this is fine
String::from_raw_parts(f.code.as_mut_ptr(), f.code.len(), f.code.len()),
&mut ctx.parser,
&mut loader,
)
})
.collect::<Vec<_>>()
};
let mut ct = {
let mut backend = HbvmBackend::default();
Codegen::new(&mut backend, &files, &mut ctx).generate(Module::new(root));
if !ctx.parser.errors.borrow().is_empty() {
log::error!("{}", ctx.parser.errors.borrow());
return;
}
let mut c = Codegen::new(&mut backend, &files, &mut ctx);
c.assemble_comptime()
};
while fuel != 0 {
match ct.vm.run() {
Ok(hbvm::VmRunOk::End) => {
log::error!("exit code: {}", ct.vm.read_reg(1).0 as i64);
break;
}
Ok(hbvm::VmRunOk::Ecall) => {
let unknown = ct.vm.read_reg(2).0;
log::error!("unknown ecall: {unknown}")
}
Ok(hbvm::VmRunOk::Timer) => {
fuel -= 1;
if fuel == 0 {
log::error!("program timed out");
}
}
Ok(hbvm::VmRunOk::Breakpoint) => todo!(),
Err(e) => {
log::error!("vm error: {e}");
break;
}
}
}
//log::error!("memory consumption: {}b / {}b", ALLOCATOR.used(), ARENA_CAP);
}

View file

@ -1,7 +0,0 @@
[package]
name = "wasm-rt"
version = "0.1.0"
edition = "2021"
[dependencies]
log = { version = "0.4.22", optional = true }

View file

@ -1,162 +0,0 @@
#![feature(alloc_error_handler)]
#![feature(pointer_is_aligned_to)]
#![feature(slice_take)]
#![no_std]
use core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
};
extern crate alloc;
#[macro_export]
macro_rules! decl_buffer {
($cap:expr, $export_cap:ident, $export_base:ident, $export_len:ident) => {
#[no_mangle]
static $export_cap: usize = $cap;
#[no_mangle]
static mut $export_base: [u8; $cap] = [0; $cap];
#[no_mangle]
static mut $export_len: usize = 0;
};
}
#[macro_export]
macro_rules! decl_runtime {
($memory_size:expr, $max_panic_size:expr) => {
#[cfg(debug_assertions)]
#[no_mangle]
static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
#[cfg(debug_assertions)]
#[no_mangle]
static mut PANIC_MESSAGE_LEN: usize = 0;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
#[cfg(debug_assertions)]
{
unsafe {
use core::fmt::Write;
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "{}", _info);
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
}
}
core::arch::wasm32::unreachable();
}
#[global_allocator]
static ALLOCATOR: $crate::ArenaAllocator<{ $memory_size }> = $crate::ArenaAllocator::new();
#[cfg(target_arch = "wasm32")]
#[alloc_error_handler]
fn alloc_error(_: core::alloc::Layout) -> ! {
#[cfg(debug_assertions)]
{
unsafe {
use core::fmt::Write;
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "out of memory");
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
}
}
core::arch::wasm32::unreachable()
}
};
}
#[cfg(feature = "log")]
pub struct Logger;
#[cfg(feature = "log")]
impl log::Log for Logger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
const MAX_LOG_MESSAGE: usize = 1024 * 8;
#[no_mangle]
static mut LOG_MESSAGES: [u8; MAX_LOG_MESSAGE] = [0; MAX_LOG_MESSAGE];
#[no_mangle]
static mut LOG_MESSAGES_LEN: usize = 0;
unsafe {
use core::fmt::Write;
let mut f = Write(&mut LOG_MESSAGES[LOG_MESSAGES_LEN..]);
_ = writeln!(f, "{}", record.args());
LOG_MESSAGES_LEN = MAX_LOG_MESSAGE - f.0.len();
}
}
}
fn flush(&self) {}
}
pub struct ArenaAllocator<const SIZE: usize> {
arena: UnsafeCell<[u8; SIZE]>,
head: UnsafeCell<*mut u8>,
}
impl<const SIZE: usize> ArenaAllocator<SIZE> {
#[expect(clippy::new_without_default)]
pub const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
#[expect(clippy::missing_safety_doc)]
pub unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(SIZE);
}
pub fn used(&self) -> usize {
unsafe { self.arena.get() as usize + SIZE - (*self.head.get()) as usize }
}
}
unsafe impl<const SIZE: usize> Sync for ArenaAllocator<SIZE> {}
unsafe impl<const SIZE: usize> GlobalAlloc for ArenaAllocator<SIZE> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();
let until = self.arena.get() as *mut u8;
let new_head = (*self.head.get()).sub(size);
let aligned_head = (new_head as usize & !(align - 1)) as *mut u8;
debug_assert!(aligned_head.is_aligned_to(align));
if until > aligned_head {
return core::ptr::null_mut();
}
*self.head.get() = aligned_head;
aligned_head
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
/* lol */
}
}
pub struct Write<'a>(pub &'a mut [u8]);
impl core::fmt::Write for Write<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if let Some(m) = self.0.take_mut(..s.len()) {
m.copy_from_slice(s.as_bytes());
Ok(())
} else {
Err(core::fmt::Error)
}
}
}

View file

@ -7,4 +7,3 @@ edition = "2018"
default = ["disasm"]
std = []
disasm = ["std"]

View file

@ -14,7 +14,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>> {
writeln!(generated, "#![expect(dead_code)]")?;
writeln!(generated, "#![allow(dead_code)] #![allow(clippy::upper_case_acronyms)]")?;
writeln!(generated, "use crate::*;")?;
'_opcode_structs: {
@ -85,7 +85,11 @@ fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>>
}
'_name_list: {
writeln!(generated, "pub const COUNT: u8 = {};", instructions().count())?;
writeln!(generated, "pub const NAMES: [&str; {}] = [", instructions().count())?;
for [_, name, _, _] in instructions() {
writeln!(generated, " \"{}\",", name.to_lowercase())?;
}
writeln!(generated, "];")?;
}
let instr = "Instr";
@ -119,7 +123,7 @@ fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>>
"/// This assumes the instruction byte is still at the beginning of the buffer"
)?;
writeln!(generated, "#[cfg(feature = \"disasm\")]")?;
writeln!(generated, "pub fn parse_args(bytes: &mut &[u8], kind: {instr}, buf: &mut alloc::vec::Vec<{oper}>) -> Option<()> {{")?;
writeln!(generated, "pub fn parse_args(bytes: &mut &[u8], kind: {instr}, buf: &mut std::vec::Vec<{oper}>) -> Option<()> {{")?;
writeln!(generated, " match kind {{")?;
let mut instrs = instructions().collect::<Vec<_>>();
instrs.sort_unstable_by_key(|&[.., ty, _]| ty);
@ -159,7 +163,7 @@ fn comma_sep(items: impl Iterator<Item = String>) -> String {
}
fn instructions() -> impl Iterator<Item = [&'static str; 4]> {
include_str!("instructions.in")
include_str!("../hbbytecode/instructions.in")
.lines()
.filter_map(|line| line.strip_suffix(';'))
.map(|line| line.splitn(4, ',').map(str::trim).next_chunk().unwrap())

View file

@ -1,7 +1,7 @@
#![no_std]
#[cfg(feature = "disasm")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub use crate::instrs::*;
use core::convert::TryFrom;
@ -34,8 +34,8 @@ impl TryFrom<u8> for Instr {
Err(value)
}
if value < COUNT {
unsafe { Ok(core::mem::transmute::<u8, Instr>(value)) }
if value < NAMES.len() as u8 {
unsafe { Ok(std::mem::transmute::<u8, Instr>(value)) }
} else {
failed(value)
}
@ -50,9 +50,8 @@ unsafe fn encode<T>(instr: T) -> (usize, [u8; instrs::MAX_SIZE]) {
}
#[inline]
#[cfg(feature = "disasm")]
fn decode<T>(binary: &mut &[u8]) -> Option<T> {
let (front, rest) = core::mem::take(binary).split_at_checked(core::mem::size_of::<T>())?;
let (front, rest) = std::mem::take(binary).split_at_checked(core::mem::size_of::<T>())?;
*binary = rest;
unsafe { Some(core::ptr::read(front.as_ptr() as *const T)) }
}
@ -83,74 +82,41 @@ pub enum DisasmItem {
}
#[cfg(feature = "disasm")]
#[derive(Debug)]
pub enum DisasmError<'a> {
InvalidInstruction(u8),
InstructionOutOfBounds(&'a str),
FmtFailed(core::fmt::Error),
HasOutOfBoundsJumps,
}
#[cfg(feature = "disasm")]
impl From<core::fmt::Error> for DisasmError<'_> {
fn from(value: core::fmt::Error) -> Self {
Self::FmtFailed(value)
}
}
#[cfg(feature = "disasm")]
impl core::fmt::Display for DisasmError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
DisasmError::InvalidInstruction(b) => write!(f, "invalid instruction opcode: {b}"),
DisasmError::InstructionOutOfBounds(name) => {
write!(f, "instruction would go out of bounds of {name} symbol")
}
DisasmError::FmtFailed(error) => write!(f, "fmt failed: {error}"),
DisasmError::HasOutOfBoundsJumps => write!(
f,
"the code contained jumps that dont got neither to a \
valid symbol or local insturction"
),
}
}
}
#[cfg(feature = "disasm")]
impl core::error::Error for DisasmError<'_> {}
#[cfg(feature = "disasm")]
pub fn disasm<'a>(
pub fn disasm(
binary: &mut &[u8],
functions: &alloc::collections::BTreeMap<u32, (&'a str, u32, DisasmItem)>,
out: &mut alloc::string::String,
functions: &std::collections::BTreeMap<u32, (&str, u32, DisasmItem)>,
out: &mut impl std::io::Write,
mut eca_handler: impl FnMut(&mut &[u8]),
) -> Result<(), DisasmError<'a>> {
) -> std::io::Result<()> {
use {
self::instrs::Instr,
alloc::{
collections::btree_map::{BTreeMap, Entry},
std::{
collections::{hash_map::Entry, HashMap},
convert::TryInto,
vec::Vec,
},
core::{convert::TryInto, fmt::Write},
};
fn instr_from_byte(b: u8) -> Result<Instr, DisasmError<'static>> {
b.try_into().map_err(DisasmError::InvalidInstruction)
fn instr_from_byte(b: u8) -> std::io::Result<Instr> {
if b as usize >= instrs::NAMES.len() {
return Err(std::io::ErrorKind::InvalidData.into());
}
Ok(unsafe { std::mem::transmute::<u8, Instr>(b) })
}
let mut labels = BTreeMap::<u32, u32>::default();
let mut labels = HashMap::<u32, u32>::default();
let mut buf = Vec::<instrs::Oper>::new();
let mut has_cycle = false;
let mut has_oob = false;
'_offset_pass: for (&off, &(name, len, kind)) in functions.iter() {
'_offset_pass: for (&off, &(_name, len, kind)) in functions.iter() {
if matches!(kind, DisasmItem::Global) {
continue;
}
let prev = *binary;
*binary = &binary[off as usize..];
*binary = &binary[..off as usize];
let mut label_count = 0;
while let Some(&byte) = binary.first() {
@ -159,8 +125,7 @@ pub fn disasm<'a>(
break;
}
let Ok(inst) = instr_from_byte(byte) else { break };
instrs::parse_args(binary, inst, &mut buf)
.ok_or(DisasmError::InstructionOutOfBounds(name))?;
instrs::parse_args(binary, inst, &mut buf).ok_or(std::io::ErrorKind::OutOfMemory)?;
for op in buf.drain(..) {
let rel = match op {
@ -169,6 +134,8 @@ pub fn disasm<'a>(
_ => continue,
};
has_cycle |= rel == 0;
let global_offset: u32 = (offset + rel).try_into().unwrap();
if functions.get(&global_offset).is_some() {
continue;
@ -201,7 +168,7 @@ pub fn disasm<'a>(
writeln!(out, "{name}:")?;
*binary = &binary[off as usize..];
*binary = &binary[..off as usize];
while let Some(&byte) = binary.first() {
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
if offset as u32 == off + len {
@ -252,9 +219,7 @@ pub fn disasm<'a>(
} else {
let local_has_oob = global_offset < off
|| global_offset > off + len
|| prev
.get(global_offset as usize)
.map_or(true, |&b| instr_from_byte(b).is_err())
|| instr_from_byte(prev[global_offset as usize]).is_err()
|| prev[global_offset as usize] == 0;
has_oob |= local_has_oob;
let label = labels.get(&global_offset).unwrap();
@ -277,7 +242,11 @@ pub fn disasm<'a>(
}
if has_oob {
return Err(DisasmError::HasOutOfBoundsJumps);
return Err(std::io::ErrorKind::InvalidInput.into());
}
if has_cycle {
return Err(std::io::ErrorKind::TimedOut.into());
}
Ok(())

6
hbjit/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "hbjit"
version = "0.1.0"
edition = "2021"
[dependencies]

3
hbjit/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

13
hblang/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "hblang"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "hbc"
path = "src/main.rs"
[dependencies]
hbbytecode = { version = "0.1.0", path = "../hbbytecode" }
hbvm = { path = "../hbvm", features = ["nightly"] }
regalloc2 = { git = "https://github.com/jakubDoka/regalloc2" }

945
hblang/README.md Normal file
View file

@ -0,0 +1,945 @@
# HERE SHALL THE DOCUMENTATION RESIDE
## Enforced Political Views
- worse is better
- less is more
- embrace `unsafe {}`
- adhere `macro_rules!`
- pessimization == death (put in `std::pin::Pin` and left with hungry crabs)
- importing external dependencies == death (`fn(dependencies) -> ExecutionStrategy`)
- above sell not be disputed, discussed, or questioned
## What hblang is
Holey-Bytes-Language (hblang for short) (*.hb) is the only true language targeting hbvm byte code. hblang is low level, manually managed, and procedural. Its rumored to be better then writing hbasm and you should probably use it for complex applications.
## What hblang isnt't
hblang knows what it isn't, because it knows what it is, hblang computes this by sub...
## Examples
Examples are also used in tests. To add an example that runs during testing add:
<pre>
#### &lt;name&gt
```hb
&lt;example&gt
```
</pre>
and also:
```rs
<name> => README;
```
to the `run_tests` macro at the bottom of the `src/codegen.rs`.
### Tour Examples
Following examples incrementally introduce language features and syntax.
#### main_fn
```hb
main := fn(): int {
return 1;
}
```
#### arithmetic
```hb
main := fn(): int {
return 10 - 20 / 2 + 4 * (2 + 2) - 4 * 4 + (1 << 0) + -1
}
```
#### functions
```hb
main := fn(): int {
return add_one(10) + add_two(20)
}
add_two := fn(x: int): int {
return x + 2
}
add_one := fn(x: int): int {
return x + 1
}
```
#### comments
```hb
// commant is an item
main := fn(): int {
// comment is a statement
foo(/* comment is an exprression /* if you are crazy */ */)
return 0
}
foo := fn(comment: void): void return /* comment evaluates to void */
// comments might be formatted in the future
```
#### if_statements
```hb
main := fn(): int {
return fib(10)
}
fib := fn(x: int): int {
if x <= 2 {
return 1
} else {
return fib(x - 1) + fib(x - 2)
}
}
```
#### variables
```hb
main := fn(): int {
ඞ := 1
b := 2
ඞ += 1
return ඞ - b
}
```
#### loops
```hb
main := fn(): int {
return fib(10)
}
fib := fn(n: int): int {
a := 0
b := 1
loop if n == 0 break else {
c := a + b
a = b
b = c
n -= 1
}
return a
}
```
#### pointers
```hb
main := fn(): int {
a := 1
b := &a
modify(b)
drop(a)
stack_reclamation_edge_case := 0
return *b - 2
}
modify := fn(a: ^int): void {
*a = 2
return
}
drop := fn(a: int): void {
return
}
```
#### structs
```hb
Ty := struct {
// comment
a: int,
b: int,
}
Ty2 := struct {
ty: Ty,
c: int,
}
main := fn(): int {
finst := Ty2.{ty: Ty.{a: 4, b: 1}, c: 3}
inst := odher_pass(finst)
if inst.c == 3 {
return pass(&inst.ty)
}
return 0
}
pass := fn(t: ^Ty): int {
.{a, b} := *t
return a - b
}
odher_pass := fn(t: Ty2): Ty2 {
return t
}
```
#### struct_operators
```hb
Point := struct {
x: int,
y: int,
}
Rect := struct {
a: Point,
b: Point,
}
main := fn(): int {
a := Point.(1, 2)
b := Point.(3, 4)
d := Rect.(a + b, b - a)
d2 := Rect.(Point.(0, 0) - b, a)
d2 += d
c := d2.a + d2.b
return c.x + c.y
}
```
#### global_variables
```hb
global_var := 10
complex_global_var := fib(global_var) - 5
fib := fn(n: int): int {
if 2 > n {
return n
}
return fib(n - 1) + fib(n - 2)
}
main := fn(): int {
complex_global_var += 5
return complex_global_var
}
```
note: values of global variables are evaluated at compile time
#### directives
```hb
foo := @use("foo.hb")
main := fn(): int {
byte := @as(u8, 10)
same_type_as_byte := @as(@TypeOf(byte), 30)
wide_uint := @as(u32, 40)
truncated_uint := @as(u8, @intcast(wide_uint))
size_of_Type_in_bytes := @sizeof(foo.Type)
align_of_Type_in_bytes := @alignof(foo.Type)
hardcoded_pointer := @as(^u8, @bitcast(10))
ecall_that_returns_int := @as(int, @eca(1, foo.Type.(10, 20), 5, 6))
return @inline(foo.foo)
}
// in module: foo.hb
Type := struct {
brah: int,
blah: int,
}
foo := fn(): int return 0
```
- `@use(<string>)`: imports a module based of string, the string is passed to a loader that can be customized, default loader uses following syntax:
- `((rel:|)(<path>)|git:<git-addr>:<path>)`: `rel:` and `''` prefixes both mean module is located at `path` relavive to the current file, `git:` takes a git url without `https://` passed as `git-addr`, `path` then refers to file within the repository
- `@TypeOf(<expr>)`: results into literal type of whatever the type of `<expr>` is, `<expr>` is not included in final binary
- `@as(<ty>, <expr>)`: hint to the compiler that `@TypeOf(<expr>) == <ty>`
- `@intcast(<expr>)`: needs to be used when conversion of `@TypeOf(<expr>)` would loose precision (widening of integers is implicit)
- `@sizeof(<ty>), @alignof(<ty>)`: I think explaining this would insult your intelligence
- `@bitcast(<expr>)`: tell compiler to assume `@TypeOf(<expr>)` is whatever is inferred, so long as size and alignment did not change
- `@eca(<ty>, ...<expr>)`: invoke `eca` instruction, where `<ty>` is the type this will return and `<expr>...` are arguments passed to the call
- `@inline(<func>, ...<args>)`: equivalent to `<func>(...<args>)` but function is guaranteed to inline, compiler will otherwise never inline
#### c_strings
```hb
str_len := fn(str: ^u8): int {
len := 0
loop if *str == 0 break else {
len += 1
str += 1
}
return len
}
main := fn(): int {
// when string ends with '\0' its a C string and thus type is '^u8'
some_str := "abඞ\n\r\t\{35}\{36373839}\0"
len := str_len(some_str)
some_other_str := "fff\0"
lep := str_len(some_other_str)
return lep + len
}
```
#### struct_patterns
```hb
.{fib, fib_iter, Fiber} := @use("fibs.hb")
main := fn(): int {
.{a, b} := Fiber.{a: 10, b: 10}
return fib(a) - fib_iter(b)
}
// in module: fibs.hb
Fiber := struct {a: u8, b: u8}
fib := fn(n: int): int if n < 2 {
return n
} else {
return fib(n - 1) + fib(n - 2)
}
fib_iter := fn(n: int): int {
a := 0
b := 1
loop if n == 0 break else {
c := a + b
a = b
b = c
n -= 1
}
return a
}
```
#### arrays
```hb
main := fn(): int {
addr := @as(u16, 0x1FF)
msg := [u8].(0, 0, @as(u8, addr & 0xFF), @as(u8, addr >> 8 & 0xFF))
_force_stack := &msg
arr := [int].(1, 2, 4)
return pass(&arr) + msg[3]
}
pass := fn(arr: ^[int; 3]): int {
return arr[0] + arr[1] + arr[arr[1]]
}
```
#### inline
```hb
main := fn(): int {
return @inline(foo, 1, 2, 3) - 6
}
foo := fn(a: int, b: int, c: int): int {
return a + b + c
}
```
#### idk
```hb
_edge_case := @as(int, idk)
main := fn(): int {
big_array := @as([u8; 128], idk)
i := 0
loop if i >= 128 break else {
big_array[i] = 69
i += 1
}
return big_array[42]
}
```
#### wide_ret
```hb
OemIdent := struct {
dos_version: [u8; 8],
dos_version_name: [u8; 8],
}
Stru := struct {
a: u16,
b: u16,
}
small_struct := fn(): Stru {
return .{a: 0, b: 0}
}
main := fn(major: int, minor: int): OemIdent {
small_struct()
ver := [u8].(0, 0, 0, 0, 0, 0, 0, 0)
return OemIdent.(ver, ver)
}
```
### Incomplete Examples
#### comptime_pointers
```hb
main := fn(): int {
$integer := 7
modify(&integer)
return integer
}
modify := fn($num: ^int): void {
$: *num = 0
}
```
#### generic_types
```hb
MALLOC_SYS_CALL := 69
FREE_SYS_CALL := 96
malloc := fn(size: uint, align: uint): ^void return @eca(MALLOC_SYS_CALL, size, align)
free := fn(ptr: ^void, size: uint, align: uint): void return @eca(FREE_SYS_CALL, ptr, size, align)
Vec := fn($Elem: type): type {
return struct {
data: ^Elem,
len: uint,
cap: uint,
}
}
new := fn($Elem: type): Vec(Elem) return Vec(Elem).{data: @bitcast(0), len: 0, cap: 0}
deinit := fn($Elem: type, vec: ^Vec(Elem)): void {
free(@bitcast(vec.data), vec.cap * @sizeof(Elem), @alignof(Elem));
*vec = new(Elem)
return
}
push := fn($Elem: type, vec: ^Vec(Elem), value: Elem): ^Elem {
if vec.len == vec.cap {
if vec.cap == 0 {
vec.cap = 1
} else {
vec.cap *= 2
}
new_alloc := @as(^Elem, @bitcast(malloc(vec.cap * @sizeof(Elem), @alignof(Elem))))
if new_alloc == 0 return 0
src_cursor := vec.data
dst_cursor := new_alloc
end := vec.data + vec.len
loop if src_cursor == end break else {
*dst_cursor = *src_cursor
src_cursor += 1
dst_cursor += 1
}
if vec.len != 0 {
free(@bitcast(vec.data), vec.len * @sizeof(Elem), @alignof(Elem))
}
vec.data = new_alloc
}
slot := vec.data + vec.len;
*slot = value
vec.len += 1
return slot
}
main := fn(): int {
vec := new(int)
push(int, &vec, 69)
res := *vec.data
deinit(int, &vec)
return res
}
```
#### generic_functions
```hb
add := fn($T: type, a: T, b: T): T return a + b
main := fn(): int {
return add(u32, 2, 2) - add(int, 1, 3)
}
```
#### fb_driver
```hb
arm_fb_ptr := fn(): int return 100
x86_fb_ptr := fn(): int return 100
check_platform := fn(): int {
return x86_fb_ptr()
}
set_pixel := fn(x: int, y: int, width: int): int {
return y * width + x
}
main := fn(): int {
fb_ptr := check_platform()
width := 100
height := 30
x := 0
y := 0
//t := 0
i := 0
loop {
if x < height {
//t += set_pixel(x, y, height)
x += 1
i += 1
} else {
x = 0
y += 1
if set_pixel(x, y, height) != i return 0
if y == width break
}
}
return i
}
```
### Purely Testing Examples
#### comptime_min_reg_leak
```hb
a := @use("math.hb").min(100, 50)
main := fn(): int {
return a
}
// in module: math.hb
SIZEOF_INT := 32
SHIFT := SIZEOF_INT - 1
min := fn(a: int, b: int): int {
c := a - b
return b + (c & c >> SHIFT)
}
```
#### different_types
```hb
Color := struct {
r: u8,
g: u8,
b: u8,
a: u8,
}
Point := struct {
x: u32,
y: u32,
}
Pixel := struct {
color: Color,
point: Point,
}
main := fn(): int {
pixel := Pixel.{
color: Color.{
r: 255,
g: 0,
b: 0,
a: 255,
},
point: Point.{
x: 0,
y: 2,
},
}
soupan := 1
if *(&pixel.point.x + soupan) != 2 {
return 0
}
if *(&pixel.point.y - 1) != 0 {
return 64
}
return pixel.point.x + pixel.point.y + pixel.color.r
+ pixel.color.g + pixel.color.b + pixel.color.a
}
```
#### struct_return_from_module_function
```hb
bar := @use("bar.hb")
main := fn(): int {
return 7 - bar.foo().x - bar.foo().y - bar.foo().z
}
// in module: bar.hb
foo := fn(): Foo {
return .{x: 3, y: 2, z: 2}
}
Foo := struct {x: int, y: u32, z: u32}
```
#### sort_something_viredly
```hb
main := fn(): int {
foo := sqrt
return 0
}
sqrt := fn(x: int): int {
temp := 0
g := 0
b := 32768
bshift := 15
loop if b == 0 break else {
bshift -= 1
temp = b + (g << 1)
temp <<= bshift
if x >= temp {
g += b
x -= temp
}
b >>= 1
}
return g
}
```
#### hex_octal_binary_literals
```hb
main := fn(): int {
hex := 0xFF
decimal := 255
octal := 0o377
binary := 0b11111111
if hex == decimal & octal == decimal & binary == decimal {
return 0
}
return 1
}
```
#### structs_in_registers
```hb
ColorBGRA := struct {b: u8, g: u8, r: u8, a: u8}
MAGENTA := ColorBGRA.{b: 205, g: 0, r: 205, a: 255}
main := fn(): int {
color := MAGENTA
return color.r
}
```
#### comptime_function_from_another_file
```hb
stn := @use("stn.hb")
CONST_A := 100
CONST_B := 50
a := stn.math.min(CONST_A, CONST_B)
main := fn(): int {
return a
}
// in module: stn.hb
math := @use("math.hb")
// in module: math.hb
SIZEOF_INT := 32
SHIFT := SIZEOF_INT - 1
min := fn(a: int, b: int): int {
c := a - b
return b + (c & c >> SHIFT)
}
```
### Just Testing Optimizations
#### const_folding_with_arg
```hb
main := fn(arg: int): int {
// reduces to 0
return arg + 0 - arg * 1 + arg + 1 + arg + 2 + arg + 3 - arg * 3 - 6
}
```
#### branch_assignments
```hb
main := fn(arg: int): int {
if arg == 1 {
arg = 1
} else if arg == 0 {
arg = 2
} else {
arg = 3
}
return arg
}
```
#### inline_test
```hb
Point := struct {x: int, y: int}
Buffer := struct {}
Transform := Point
ColorBGRA := Point
line := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA, thickness: int): void {
if true {
if p0.x > p1.x {
@inline(line_low, buffer, p1, p0, color)
} else {
@inline(line_low, buffer, p0, p1, color)
}
} else {
if p0.y > p1.y {
// blah, test leading new line on directives
@inline(line_high, buffer, p1, p0, color)
} else {
@inline(line_high, buffer, p0, p1, color)
}
}
return
}
line_low := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA): void {
return
}
line_high := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA): void {
return
}
screenidx := @use("screen.hb").screenidx
rect_line := fn(buffer: Buffer, pos: Point, tr: Transform, color: ColorBGRA, thickness: int): void {
t := 0
y := 0
x := 0
loop if t == thickness break else {
y = pos.y
x = pos.x
loop if y == pos.y + tr.x break else {
a := 1 + @inline(screenidx, 10)
a = 1 + @inline(screenidx, 2)
y += 1
}
t += 1
}
return
}
random := @use("random.hb")
example := fn(): void {
loop {
random_x := @inline(random.integer, 0, 1024)
random_y := random.integer(0, 768)
a := @inline(screenidx, random_x)
break
}
return
}
main := fn(): int {
line(.(), .(0, 0), .(0, 0), .(0, 0), 10)
rect_line(.(), .(0, 0), .(0, 0), .(0, 0), 10)
example()
return 0
}
// in module: screen.hb
screenidx := fn(orange: int): int {
return orange
}
// in module: random.hb
integer := fn(min: int, max: int): int {
rng := @as(int, @eca(3, 4))
if min != 0 | max != 0 {
return rng % (max - min + 1) + min
}
return rng
}
```
#### some_generic_code
```hb
some_func := fn($Elem: type): void {
return
}
main := fn(): void {
some_func(u8)
return
}
```
#### integer_inference_issues
```hb
.{integer_range} := @use("random.hb")
main := fn(): void {
a := integer_range(0, 1000)
return
}
// in module: random.hb
integer_range := fn(min: uint, max: int): uint {
return @eca(3, 4) % (@bitcast(max) - min + 1) + min
}
```
#### exhaustive_loop_testing
```hb
main := fn(): int {
if multiple_breaks(0) != 3 {
return 1
}
if multiple_breaks(4) != 10 {
return 2
}
if state_change_in_break(0) != 0 {
return 3
}
if state_change_in_break(4) != 10 {
return 4
}
if continue_and_state_change(10) != 10 {
return 5
}
if continue_and_state_change(3) != 0 {
return 6
}
return 0
}
multiple_breaks := fn(arg: int): int {
loop if arg < 10 {
arg += 1
if arg == 3 break
} else break
return arg
}
state_change_in_break := fn(arg: int): int {
loop if arg < 10 {
if arg == 3 {
arg = 0
break
}
arg += 1
} else break
return arg
}
continue_and_state_change := fn(arg: int): int {
loop if arg < 10 {
if arg == 2 {
arg = 4
continue
}
if arg == 3 {
arg = 0
break
}
arg += 1
} else break
return arg
}
```
#### writing_into_string
```hb
outl := fn(): void {
msg := "whahaha\0"
@as(u8, 0)
return
}
inl := fn(): void {
msg := "luhahah\0"
return
}
main := fn(): void {
outl()
inl()
return
}
```
#### request_page
```hb
request_page := fn(page_count: u8): ^u8 {
msg := "\{00}\{01}xxxxxxxx\0"
msg_page_count := msg + 1;
*msg_page_count = page_count
return @eca(3, 2, msg, 12)
}
create_back_buffer := fn(total_pages: int): ^u32 {
if total_pages <= 0xFF {
return @bitcast(request_page(total_pages))
}
ptr := request_page(255)
remaining := total_pages - 0xFF
loop if remaining <= 0 break else {
if remaining < 0xFF {
request_page(remaining)
} else {
request_page(0xFF)
}
remaining -= 0xFF
}
return @bitcast(ptr)
}
main := fn(): void {
create_back_buffer(400)
return
}
```
#### tests_ptr_to_ptr_copy
```hb
main := fn(): int {
back_buffer := @as([u8; 1024 * 10], idk)
n := 0
loop if n >= 1024 break else {
back_buffer[n] = 64
n += 1
}
n = 1
loop if n >= 10 break else {
*(@as(^[u8; 1024], @bitcast(&back_buffer)) + n) = *@as(^[u8; 1024], @bitcast(&back_buffer))
n += 1
}
return back_buffer[1024 * 2]
}
```

4
hblang/command-help.txt Normal file
View file

@ -0,0 +1,4 @@
--fmt - format all source files
--fmt-current - format mentioned file
--fmt-stdout - dont write the formatted file but print it
--threads <1...> - number of threads compiler can use [default: 1]

2786
hblang/src/codegen.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
use crate::{instrs, EncodedInstr};
const fn ascii_mask(chars: &[u8]) -> u128 {
let mut eq = 0;
let mut i = 0;
@ -17,7 +19,7 @@ pub struct Token {
}
impl Token {
pub fn range(&self) -> core::ops::Range<usize> {
pub fn range(&self) -> std::ops::Range<usize> {
self.start as usize..self.end as usize
}
}
@ -42,15 +44,13 @@ macro_rules! gen_token_kind {
)*
}
) => {
impl core::fmt::Display for $name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl $name {
pub const OPS: &[Self] = &[$($(Self::$op),*),*];
pub fn name(&self) -> &str {
let sf = unsafe { &*(self as *const _ as *const u8) } ;
match *self {
@ -59,7 +59,7 @@ macro_rules! gen_token_kind {
$( Self::$punkt => stringify!($punkt_lit), )*
$($( Self::$op => $op_lit,
$(Self::$assign => concat!($op_lit, "="),)?)*)*
_ => unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&sf)) },
_ => unsafe { std::str::from_utf8_unchecked(std::slice::from_ref(&sf)) },
}
}
@ -72,6 +72,7 @@ macro_rules! gen_token_kind {
} + 1)
}
#[inline(always)]
fn from_ident(ident: &[u8]) -> Self {
match ident {
$($keyword_lit => Self::$keyword,)*
@ -82,7 +83,7 @@ macro_rules! gen_token_kind {
};
}
#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[repr(u8)]
pub enum TokenKind {
Not = b'!',
@ -116,14 +117,24 @@ pub enum TokenKind {
Ident,
Number,
Float,
Eof,
Ct,
Return,
If,
Else,
Loop,
Break,
Continue,
Fn,
Struct,
True,
False,
Idk,
Ctor,
Tupl,
TArrow,
Or,
And,
@ -133,26 +144,7 @@ pub enum TokenKind {
BSlash = b'\\',
RBrack = b']',
Xor = b'^',
Under = b'_',
Tick = b'`',
Return,
If,
Match,
Else,
Loop,
Break,
Continue,
Fn,
Struct,
Packed,
Enum,
True,
False,
Null,
Idk,
Die,
// Unused = a-z
LBrace = b'{',
Bor = b'|',
@ -177,19 +169,93 @@ pub enum TokenKind {
ShlAss = b'<' - 5 + 128,
}
impl core::fmt::Debug for TokenKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl TokenKind {
#[allow(clippy::type_complexity)]
pub fn cond_op(self, signed: bool) -> Option<(fn(u8, u8, i16) -> EncodedInstr, bool)> {
Some((
match self {
Self::Le if signed => instrs::jgts,
Self::Le => instrs::jgtu,
Self::Lt if signed => instrs::jlts,
Self::Lt => instrs::jltu,
Self::Ge if signed => instrs::jlts,
Self::Ge => instrs::jltu,
Self::Gt if signed => instrs::jgts,
Self::Gt => instrs::jgtu,
Self::Eq => instrs::jne,
Self::Ne => instrs::jeq,
_ => return None,
},
matches!(self, Self::Lt | TokenKind::Gt),
))
}
pub fn binop(self, signed: bool, size: u32) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
use instrs::*;
macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, 0, b, c)),*]}; }
macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(0, a, b, c)),*]}; }
let ops = match self {
Self::Add => [add8, add16, add32, add64],
Self::Sub => [sub8, sub16, sub32, sub64],
Self::Mul => [mul8, mul16, mul32, mul64],
Self::Div if signed => div!(dirs8, dirs16, dirs32, dirs64),
Self::Div => div!(diru8, diru16, diru32, diru64),
Self::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64),
Self::Mod => rem!(diru8, diru16, diru32, diru64),
Self::Band => return Some(and),
Self::Bor => return Some(or),
Self::Xor => return Some(xor),
Self::Shl => [slu8, slu16, slu32, slu64],
Self::Shr if signed => [srs8, srs16, srs32, srs64],
Self::Shr => [sru8, sru16, sru32, sru64],
_ => return None,
};
Some(ops[size.ilog2() as usize])
}
#[allow(clippy::type_complexity)]
pub fn imm_binop(self, signed: bool, size: u32) -> Option<fn(u8, u8, u64) -> EncodedInstr> {
use instrs::*;
macro_rules! def_op {
($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => {
macro_rules! $name {
($$($$op:ident),*) => {
[$$(
|$a, $b, $c: u64| $$op($($tt)*),
)*]
}
}
};
}
def_op!(basic_op | a, b, c | a, b, c as _);
def_op!(sub_op | a, b, c | a, b, c.wrapping_neg() as _);
let ops = match self {
Self::Add => basic_op!(addi8, addi16, addi32, addi64),
Self::Sub => sub_op!(addi8, addi16, addi32, addi64),
Self::Mul => basic_op!(muli8, muli16, muli32, muli64),
Self::Band => return Some(andi),
Self::Bor => return Some(ori),
Self::Xor => return Some(xori),
Self::Shr if signed => basic_op!(srui8, srui16, srui32, srui64),
Self::Shr => basic_op!(srui8, srui16, srui32, srui64),
Self::Shl => basic_op!(slui8, slui16, slui32, slui64),
_ => return None,
};
Some(ops[size.ilog2() as usize])
}
pub fn ass_op(self) -> Option<Self> {
let id = (self as u8).saturating_sub(128);
if ascii_mask(b"|+-*/%^&79") & (1u128 << id) == 0 {
return None;
}
Some(unsafe { core::mem::transmute::<u8, Self>(id) })
Some(unsafe { std::mem::transmute::<u8, Self>(id) })
}
pub fn is_comutative(self) -> bool {
@ -197,66 +263,15 @@ impl TokenKind {
matches!(self, S::Eq | S::Ne | S::Bor | S::Xor | S::Band | S::Add | S::Mul)
}
pub fn is_compatison(self) -> bool {
matches!(self, Self::Lt | Self::Gt | Self::Ge | Self::Le | Self::Ne | Self::Eq)
}
pub fn is_supported_float_op(self) -> bool {
matches!(
self,
Self::Add
| Self::Sub
| Self::Mul
| Self::Div
| Self::Eq
| Self::Ne
| Self::Le
| Self::Ge
| Self::Lt
| Self::Gt
)
}
pub fn apply_binop(self, a: i64, b: i64, float: bool) -> i64 {
if float {
debug_assert!(self.is_supported_float_op());
let [a, b] = [f64::from_bits(a as _), f64::from_bits(b as _)];
let res = match self {
Self::Add => a + b,
Self::Sub => a - b,
Self::Mul => a * b,
Self::Div => a / b,
Self::Eq => return (a == b) as i64,
Self::Ne => return (a != b) as i64,
Self::Lt => return (a < b) as i64,
Self::Gt => return (a > b) as i64,
Self::Le => return (a >= b) as i64,
Self::Ge => return (a <= b) as i64,
_ => todo!("floating point op: {self}"),
};
return res.to_bits() as _;
}
pub fn apply_binop(self, a: i64, b: i64) -> i64 {
match self {
Self::Add => a.wrapping_add(b),
Self::Sub => a.wrapping_sub(b),
Self::Mul => a.wrapping_mul(b),
Self::Div if b == 0 => 0,
Self::Div => a.wrapping_div(b),
Self::Shl => a.wrapping_shl(b as _),
Self::Eq => (a == b) as i64,
Self::Ne => (a != b) as i64,
Self::Lt => (a < b) as i64,
Self::Gt => (a > b) as i64,
Self::Le => (a >= b) as i64,
Self::Ge => (a <= b) as i64,
Self::Band => a & b,
Self::Bor => a | b,
Self::Xor => a ^ b,
Self::Mod if b == 0 => 0,
Self::Mod => a.wrapping_rem(b),
Self::Shr => a.wrapping_shr(b as _),
s => todo!("{s}"),
}
}
@ -267,29 +282,19 @@ impl TokenKind {
&& self.precedence() != Self::Eof.precedence()
}
pub fn apply_unop(&self, value: i64, float: bool) -> i64 {
match self {
Self::Sub if float => (-f64::from_bits(value as _)).to_bits() as _,
Self::Sub => value.wrapping_neg(),
Self::Not => (value == 0) as _,
Self::Float if float => value,
Self::Float => (value as f64).to_bits() as _,
Self::Number if float => f64::from_bits(value as _) as _,
Self::Number => value,
s => todo!("{s}"),
}
}
pub fn closing(&self) -> Option<TokenKind> {
pub fn unop(&self) -> Option<fn(u8, u8) -> EncodedInstr> {
Some(match self {
Self::Ctor => Self::RBrace,
Self::Tupl => Self::RParen,
Self::LParen => Self::RParen,
Self::LBrack => Self::RBrack,
Self::LBrace => Self::RBrace,
Self::Sub => instrs::neg,
_ => return None,
})
}
pub fn apply_unop(&self, value: i64) -> i64 {
match self {
Self::Sub => value.wrapping_neg(),
s => todo!("{s}"),
}
}
}
gen_token_kind! {
@ -298,31 +303,23 @@ gen_token_kind! {
CtIdent,
Ident,
Number,
Float,
Eof,
Directive,
#[keywords]
Return = b"return",
If = b"if",
Match = b"match",
Else = b"else",
Loop = b"loop",
Break = b"break",
Continue = b"continue",
Fn = b"fn",
Struct = b"struct",
Packed = b"packed",
Enum = b"enum",
True = b"true",
False = b"false",
Null = b"null",
Idk = b"idk",
Die = b"die",
Under = b"_",
Idk = b"idk",
#[punkt]
Ctor = ".{",
Tupl = ".(",
TArrow = "=>",
// #define OP: each `#[prec]` delimeters a level of precedence from lowest to highest
#[ops]
#[prec]
@ -362,7 +359,7 @@ gen_token_kind! {
pub struct Lexer<'a> {
pos: u32,
source: &'a [u8],
bytes: &'a [u8],
}
impl<'a> Lexer<'a> {
@ -370,42 +367,23 @@ impl<'a> Lexer<'a> {
Self::restore(input, 0)
}
pub fn uses(input: &'a str) -> impl Iterator<Item = &'a str> {
let mut s = Self::new(input);
core::iter::from_fn(move || loop {
let t = s.eat();
if t.kind == TokenKind::Eof {
return None;
}
if t.kind == TokenKind::Directive
&& s.slice(t.range()) == "use"
&& s.eat().kind == TokenKind::LParen
{
let t = s.eat();
if t.kind == TokenKind::DQuote {
return Some(&s.slice(t.range())[1..t.range().len() - 1]);
}
}
})
}
pub fn restore(input: &'a str, pos: u32) -> Self {
Self { pos, source: input.as_bytes() }
Self { pos, bytes: input.as_bytes() }
}
pub fn source(&self) -> &'a str {
unsafe { core::str::from_utf8_unchecked(self.source) }
unsafe { std::str::from_utf8_unchecked(self.bytes) }
}
pub fn slice(&self, tok: core::ops::Range<usize>) -> &'a str {
unsafe { core::str::from_utf8_unchecked(&self.source[tok]) }
pub fn slice(&self, tok: std::ops::Range<usize>) -> &'a str {
unsafe { std::str::from_utf8_unchecked(&self.bytes[tok]) }
}
fn peek(&self) -> Option<u8> {
if core::intrinsics::unlikely(self.pos >= self.source.len() as u32) {
if std::intrinsics::unlikely(self.pos >= self.bytes.len() as u32) {
None
} else {
Some(unsafe { *self.source.get_unchecked(self.pos as usize) })
Some(unsafe { *self.bytes.get_unchecked(self.pos as usize) })
}
}
@ -416,9 +394,9 @@ impl<'a> Lexer<'a> {
}
pub fn last(&mut self) -> Token {
let mut token = self.eat();
let mut token = self.next();
loop {
let next = self.eat();
let next = self.next();
if next.kind == TokenKind::Eof {
break;
}
@ -427,7 +405,7 @@ impl<'a> Lexer<'a> {
token
}
pub fn eat(&mut self) -> Token {
pub fn next(&mut self) -> Token {
use TokenKind as T;
loop {
let mut start = self.pos;
@ -442,7 +420,7 @@ impl<'a> Lexer<'a> {
}
};
let identity = |s: u8| unsafe { core::mem::transmute::<u8, T>(s) };
let identity = |s: u8| unsafe { std::mem::transmute::<u8, T>(s) };
let kind = match c {
..=b' ' => continue,
@ -468,19 +446,11 @@ impl<'a> Lexer<'a> {
while let Some(b'0'..=b'9') = self.peek() {
self.advance();
}
if self.advance_if(b'.') {
while let Some(b'0'..=b'9') = self.peek() {
self.advance();
}
T::Float
} else {
T::Number
}
T::Number
}
b'a'..=b'z' | b'A'..=b'Z' | b'_' | 127.. => {
advance_ident(self);
let ident = &self.source[start as usize..self.pos as usize];
let ident = &self.bytes[start as usize..self.pos as usize];
T::from_ident(ident)
}
b'"' | b'\'' => loop {
@ -492,18 +462,10 @@ impl<'a> Lexer<'a> {
}
},
b'/' if self.advance_if(b'/') => {
while let Some(l) = self.peek()
while let Some(l) = self.advance()
&& l != b'\n'
{
self.pos += 1;
}
let end = self.source[..self.pos as usize]
.iter()
.rposition(|&b| !b.is_ascii_whitespace())
.map_or(self.pos, |i| i as u32 + 1);
return Token { kind: T::Comment, start, end };
{}
T::Comment
}
b'/' if self.advance_if(b'*') => {
let mut depth = 1;
@ -521,7 +483,6 @@ impl<'a> Lexer<'a> {
}
b'.' if self.advance_if(b'{') => T::Ctor,
b'.' if self.advance_if(b'(') => T::Tupl,
b'=' if self.advance_if(b'>') => T::TArrow,
b'&' if self.advance_if(b'&') => T::And,
b'|' if self.advance_if(b'|') => T::Or,
b'$' if self.advance_if(b':') => T::Ct,

1419
hblang/src/lib.rs Normal file

File diff suppressed because it is too large Load diff

28
hblang/src/main.rs Normal file
View file

@ -0,0 +1,28 @@
use std::num::NonZeroUsize;
fn main() -> std::io::Result<()> {
let args = std::env::args().collect::<Vec<_>>();
let args = args.iter().map(String::as_str).collect::<Vec<_>>();
if args.contains(&"--help") || args.contains(&"-h") {
println!("Usage: hbc [OPTIONS...] <FILE>");
println!(include_str!("../command-help.txt"));
return Err(std::io::ErrorKind::Other.into());
}
hblang::run_compiler(
args.get(1).copied().unwrap_or("main.hb"),
hblang::Options {
fmt: args.contains(&"--fmt"),
fmt_current: args.contains(&"--fmt-current"),
dump_asm: args.contains(&"--dump-asm"),
extra_threads: args
.iter()
.position(|&a| a == "--threads")
.map(|i| args[i + 1].parse::<NonZeroUsize>().expect("--threads expects integer"))
.map_or(1, NonZeroUsize::get)
- 1,
},
&mut std::io::stdout(),
)
}

1533
hblang/src/parser.rs Normal file

File diff suppressed because it is too large Load diff

2775
hblang/src/son.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LI64 r32, 10d
LI64 r33, 20d
LI64 r34, 2d
DIRS64 r33, r0, r33, r34
SUB64 r32, r32, r33
LI64 r33, 4d
LI64 r34, 2d
ADDI64 r34, r34, 2d
MUL64 r33, r33, r34
ADD64 r32, r32, r33
LI64 r33, 4d
MULI64 r33, r33, 4d
SUB64 r32, r32, r33
LI64 r33, 1d
SLUI64 r33, r33, 0b
ADD64 r32, r32, r33
LI64 r33, 1d
NEG r33, r33
ADD64 r1, r32, r33
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 205
ret: 0
status: Ok(())

View file

@ -0,0 +1,68 @@
main:
ADDI64 r254, r254, -68d
ST r31, r254, 28a, 40h
LI64 r32, 511d
LI64 r33, 0d
ST r33, r254, 24a, 1h
LI64 r33, 0d
ST r33, r254, 25a, 1h
CP r33, r32
ANDI r33, r33, 255d
ST r33, r254, 26a, 1h
SRUI16 r32, r32, 8b
ANDI r32, r32, 255d
ST r32, r254, 27a, 1h
CP r32, r0
LD r32, r254, 24a, 4h
ST r32, r254, 0a, 4h
ADDI64 r32, r254, 0d
LI64 r33, 1d
ST r33, r254, 4a, 8h
LI64 r33, 2d
ST r33, r254, 12a, 8h
LI64 r33, 4d
ST r33, r254, 20a, 8h
ADDI64 r33, r254, 4d
CP r2, r33
JAL r31, r0, :pass
CP r33, r1
ADDI64 r34, r254, 0d
LI64 r35, 3d
ADD64 r34, r34, r35
CP r35, r0
LD r35, r34, 0a, 1h
SXT8 r35, r35
ADD64 r1, r33, r35
LD r31, r254, 28a, 40h
ADDI64 r254, r254, 68d
JALA r0, r31, 0a
pass:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
CP r33, r32
LI64 r34, 0d
MULI64 r34, r34, 8d
ADD64 r33, r33, r34
LD r34, r33, 0a, 8h
CP r33, r32
LI64 r35, 1d
MULI64 r35, r35, 8d
ADD64 r33, r33, r35
LD r35, r33, 0a, 8h
ADD64 r34, r34, r35
CP r33, r32
LI64 r35, 1d
MULI64 r35, r35, 8d
ADD64 r32, r32, r35
LD r35, r32, 0a, 8h
MULI64 r35, r35, 8d
ADD64 r33, r33, r35
LD r35, r33, 0a, 8h
ADD64 r1, r34, r35
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
code size: 580
ret: 8
status: Ok(())

View file

@ -9,24 +9,28 @@ main:
CP r2, r33
JAL r31, r0, :str_len
CP r33, r1
ADD64 r32, r33, r32
CP r1, r32
ADD64 r1, r33, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
str_len:
CP r15, r2
CP r14, r0
CP r13, r14
2: LD r16, r15, 0a, 1h
ANDI r16, r16, 255d
JNE r16, r14, :0
CP r1, r13
ADDI64 r254, r254, -48d
ST r31, r254, 0a, 48h
CP r32, r2
LI64 r33, 0d
2: CP r34, r32
CP r35, r0
LD r35, r34, 0a, 1h
LI64 r36, 0d
JNE r35, r36, :0
JMP :1
0: ADDI64 r15, r15, 1d
ADDI64 r13, r13, 1d
0: ADDI64 r33, r33, 1d
ADDI64 r32, r32, 1d
JMP :2
1: JALA r0, r31, 0a
code size: 216
1: CP r1, r33
LD r31, r254, 0a, 48h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
code size: 270
ret: 16
status: Ok(())

View file

@ -0,0 +1,17 @@
foo:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :foo
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 143
ret: 0
status: Ok(())

View file

@ -0,0 +1,11 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LRA r32, r0, :a
LD r1, r32, 0a, 8h
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 95
ret: 50
status: Ok(())

View file

@ -0,0 +1,11 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LRA r32, r0, :a
LD r1, r32, 0a, 8h
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 218
ret: 50
status: Ok(())

View file

@ -0,0 +1,57 @@
main:
ADDI64 r254, r254, -44d
ST r31, r254, 12a, 32h
LI64 r32, 255d
ST r32, r254, 0a, 1h
LI64 r32, 0d
ST r32, r254, 1a, 1h
LI64 r32, 0d
ST r32, r254, 2a, 1h
LI64 r32, 255d
ST r32, r254, 3a, 1h
LI64 r32, 0d
ST r32, r254, 4a, 4h
LI64 r32, 2d
ST r32, r254, 8a, 4h
LI64 r32, 1d
ADDI64 r33, r254, 4d
CP r34, r32
MULI64 r34, r34, 4d
ADD64 r33, r33, r34
CP r32, r0
LD r32, r33, 0a, 4h
LI64 r34, 2d
JEQ r32, r34, :0
LI64 r1, 0d
JMP :1
0: ADDI64 r34, r254, 8d
ADDI64 r34, r34, -4d
CP r32, r0
LD r32, r34, 0a, 4h
LI64 r33, 0d
JEQ r32, r33, :2
LI64 r1, 64d
JMP :1
2: CP r33, r0
LD r33, r254, 4a, 4h
CP r32, r0
LD r32, r254, 8a, 4h
ADD32 r33, r33, r32
CP r32, r0
LD r32, r254, 0a, 1h
ADD32 r33, r33, r32
CP r32, r0
LD r32, r254, 1a, 1h
ADD32 r33, r33, r32
CP r32, r0
LD r32, r254, 2a, 1h
ADD32 r33, r33, r32
CP r32, r0
LD r32, r254, 3a, 1h
ADD32 r1, r33, r32
1: LD r31, r254, 12a, 32h
ADDI64 r254, r254, 44d
JALA r0, r31, 0a
code size: 474
ret: 512
status: Ok(())

View file

@ -0,0 +1,27 @@
main:
ADDI64 r254, r254, -80d
ST r31, r254, 16a, 64h
LI64 r32, 10d
LI64 r33, 30d
LI64 r34, 40d
LI64 r35, 16d
LI64 r36, 8d
LI64 r37, 10d
LI64 r2, 1d
LI64 r38, 10d
ST r38, r254, 0a, 8h
LI64 r38, 20d
ST r38, r254, 8a, 8h
LD r3, r254, 0a, 16h
LI64 r5, 5d
LI64 r6, 6d
ECA
CP r38, r1
LI64 r1, 0d
LD r31, r254, 16a, 64h
ADDI64 r254, r254, 80d
JALA r0, r31, 0a
ev: Ecall
code size: 230
ret: 0
status: Ok(())

View file

@ -0,0 +1,74 @@
main:
ADDI64 r254, r254, -80d
ST r31, r254, 0a, 80h
JAL r31, r0, :check_platform
CP r32, r1
LI64 r33, 100d
LI64 r34, 30d
LI64 r35, 0d
LI64 r36, 0d
4: CP r37, r35
CP r38, r34
ADDI64 r38, r38, 1d
CMPS r37, r37, r38
CMPUI r37, r37, 1d
JEQ r37, r0, :0
CP r2, r35
CP r3, r36
CP r4, r33
JAL r31, r0, :set_pixel
CP r37, r1
CP r38, r35
ADDI64 r38, r38, 1d
CP r35, r38
JMP :1
0: CP r2, r35
CP r3, r36
CP r4, r33
JAL r31, r0, :set_pixel
CP r38, r1
LI64 r35, 0d
CP r39, r36
ADDI64 r39, r39, 1d
CP r36, r39
1: CP r39, r36
CP r40, r33
CMPS r39, r39, r40
CMPUI r39, r39, 0d
NOT r39, r39
JEQ r39, r0, :2
JMP :3
2: JMP :4
3: LI64 r1, 0d
LD r31, r254, 0a, 80h
ADDI64 r254, r254, 80d
JALA r0, r31, 0a
set_pixel:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
CP r33, r3
CP r34, r4
MUL64 r33, r33, r34
ADD64 r33, r33, r32
LI64 r1, 0d
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
check_platform:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :x86_fb_ptr
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
x86_fb_ptr:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r1, 100d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 511
ret: 0
status: Ok(())

View file

@ -0,0 +1,32 @@
add_one:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
CP r32, r2
ADDI64 r1, r32, 1d
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
add_two:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
CP r32, r2
ADDI64 r1, r32, 2d
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r2, 10d
JAL r31, r0, :add_one
CP r32, r1
LI64 r2, 20d
JAL r31, r0, :add_two
CP r33, r1
ADD64 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 257
ret: 33
status: Ok(())

View file

@ -0,0 +1,38 @@
add:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
CP r33, r3
ADD64 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
add:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
CP r33, r3
ADD32 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LI64 r2, 2d
LI64 r3, 2d
JAL r31, r0, :add
CP r32, r1
LI64 r2, 1d
LI64 r3, 3d
JAL r31, r0, :add
CP r33, r1
CP r34, r32
SXT32 r34, r34
SUB64 r1, r34, r33
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 275
ret: 0
status: Ok(())

View file

@ -0,0 +1,142 @@
deinit:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
LD r2, r32, 0a, 8h
LD r33, r32, 16a, 8h
MULI64 r33, r33, 8d
CP r3, r33
LI64 r4, 8d
JAL r31, r0, :free
CP r1, r32
JAL r31, r0, :new
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
free:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
CP r33, r3
CP r34, r4
LRA r35, r0, :FREE_SYS_CALL
LD r2, r35, 0a, 8h
CP r3, r32
CP r4, r33
CP r5, r34
ECA
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -48d
ST r31, r254, 24a, 24h
ADDI64 r1, r254, 0d
JAL r31, r0, :new
ADDI64 r32, r254, 0d
CP r2, r32
LI64 r3, 69d
JAL r31, r0, :push
CP r32, r1
LD r32, r254, 0a, 8h
LD r33, r32, 0a, 8h
ADDI64 r32, r254, 0d
CP r2, r32
JAL r31, r0, :deinit
CP r1, r33
LD r31, r254, 24a, 24h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
malloc:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
CP r33, r3
LRA r34, r0, :MALLOC_SYS_CALL
LD r2, r34, 0a, 8h
CP r3, r32
CP r4, r33
ECA
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
new:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r1
LI64 r33, 0d
ST r33, r32, 0a, 8h
LI64 r33, 0d
ST r33, r32, 8a, 8h
LI64 r33, 0d
ST r33, r32, 16a, 8h
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
push:
ADDI64 r254, r254, -72d
ST r31, r254, 0a, 72h
CP r32, r2
CP r33, r3
LD r34, r32, 8a, 8h
LD r35, r32, 16a, 8h
JNE r34, r35, :0
LD r35, r32, 16a, 8h
LI64 r34, 0d
JNE r35, r34, :1
LI64 r34, 1d
ST r34, r32, 16a, 8h
JMP :2
1: LD r34, r32, 16a, 8h
MULI64 r34, r34, 2d
ST r34, r32, 16a, 8h
2: LD r34, r32, 16a, 8h
MULI64 r34, r34, 8d
CP r2, r34
LI64 r3, 8d
JAL r31, r0, :malloc
CP r34, r1
LI64 r35, 0d
JNE r34, r35, :3
LI64 r1, 0d
JMP :4
3: LD r35, r32, 0a, 8h
CP r36, r34
LD r37, r32, 0a, 8h
LD r38, r32, 8a, 8h
MULI64 r38, r38, 8d
ADD64 r37, r37, r38
7: JNE r35, r37, :5
JMP :6
5: CP r38, r36
CP r39, r35
BMC r39, r38, 8h
ADDI64 r35, r35, 8d
ADDI64 r36, r36, 8d
JMP :7
6: LD r38, r32, 8a, 8h
LI64 r39, 0d
JEQ r38, r39, :8
LD r2, r32, 0a, 8h
LD r39, r32, 8a, 8h
MULI64 r39, r39, 8d
CP r3, r39
LI64 r4, 8d
JAL r31, r0, :free
8: ST r34, r32, 0a, 8h
0: LD r34, r32, 0a, 8h
LD r39, r32, 8a, 8h
MULI64 r39, r39, 8d
ADD64 r34, r34, r39
CP r39, r34
ST r33, r39, 0a, 8h
LD r39, r32, 8a, 8h
ADDI64 r39, r39, 1d
ST r39, r32, 8a, 8h
CP r1, r34
4: LD r31, r254, 0a, 72h
ADDI64 r254, r254, 72d
JALA r0, r31, 0a
code size: 1201
ret: 69
status: Ok(())

View file

@ -0,0 +1,16 @@
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LRA r32, r0, :complex_global_var
LRA r33, r0, :complex_global_var
LD r34, r33, 0a, 8h
ADDI64 r34, r34, 5d
ST r34, r32, 0a, 8h
LRA r32, r0, :complex_global_var
LD r1, r32, 0a, 8h
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 146
ret: 55
status: Ok(())

View file

@ -0,0 +1,28 @@
main:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
LI64 r32, 255d
LI64 r33, 255d
LI64 r34, 255d
LI64 r35, 255d
CMPS r32, r32, r33
CMPUI r32, r32, 0d
NOT r32, r32
CMPS r34, r34, r33
CMPUI r34, r34, 0d
NOT r34, r34
AND r32, r32, r34
CMPS r35, r35, r33
CMPUI r35, r35, 0d
NOT r35, r35
AND r32, r32, r35
JEQ r32, r0, :0
LI64 r1, 0d
JMP :1
0: LI64 r1, 1d
1: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
code size: 199
ret: 0
status: Ok(())

View file

@ -0,0 +1,25 @@
main:
ADDI64 r254, r254, -160d
ST r31, r254, 128a, 32h
LI64 r32, 0d
2: LI64 r33, 128d
JLTS r32, r33, :0
JMP :1
0: ADDI64 r33, r254, 0d
CP r34, r32
ADD64 r33, r33, r34
LI64 r34, 69d
ST r34, r33, 0a, 1h
ADDI64 r32, r32, 1d
JMP :2
1: ADDI64 r33, r254, 0d
LI64 r34, 42d
ADD64 r33, r33, r34
CP r1, r0
LD r1, r33, 0a, 1h
LD r31, r254, 128a, 32h
ADDI64 r254, r254, 160d
JALA r0, r31, 0a
code size: 195
ret: 69
status: Ok(())

View file

@ -0,0 +1,32 @@
fib:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
LI64 r33, 2d
JGTS r32, r33, :0
LI64 r1, 1d
JMP :1
0: CP r33, r32
ADDI64 r33, r33, -1d
CP r2, r33
JAL r31, r0, :fib
CP r33, r1
ADDI64 r32, r32, -2d
CP r2, r32
JAL r31, r0, :fib
CP r32, r1
ADD64 r1, r33, r32
1: LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 231
ret: 55
status: Ok(())

View file

@ -0,0 +1,13 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r33, 1d
ADDI64 r33, r33, 2d
ADDI64 r32, r33, 3d
ADDI64 r1, r32, -6d
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 110
ret: 0
status: Ok(())

View file

@ -0,0 +1,161 @@
example:
ADDI64 r254, r254, -48d
ST r31, r254, 0a, 48h
LI64 r2, 3d
LI64 r3, 4d
ECA
CP r33, r1
LI64 r34, 0d
LI64 r35, 0d
CMPS r34, r34, r35
CMPUI r34, r34, 0d
LI64 r35, 1024d
LI64 r36, 0d
CMPS r35, r35, r36
CMPUI r35, r35, 0d
OR r34, r34, r35
JEQ r34, r0, :0
CP r34, r33
LI64 r35, 1024d
ADDI64 r35, r35, 0d
ADDI64 r35, r35, 1d
DIRS64 r0, r34, r34, r35
ADDI64 r32, r34, 0d
JMP :1
0: CP r32, r33
1: LI64 r2, 0d
LI64 r3, 768d
JAL r31, r0, :integer
CP r33, r1
CP r34, r32
JMP :2
2: LD r31, r254, 0a, 48h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
integer:
ADDI64 r254, r254, -56d
ST r31, r254, 0a, 56h
CP r32, r2
CP r33, r3
LI64 r2, 3d
LI64 r3, 4d
ECA
CP r34, r1
CP r35, r32
LI64 r36, 0d
CMPS r35, r35, r36
CMPUI r35, r35, 0d
CP r36, r33
LI64 r37, 0d
CMPS r36, r36, r37
CMPUI r36, r36, 0d
OR r35, r35, r36
JEQ r35, r0, :0
CP r35, r34
SUB64 r33, r33, r32
ADDI64 r33, r33, 1d
DIRS64 r0, r35, r35, r33
ADD64 r1, r35, r32
JMP :1
0: CP r1, r34
1: LD r31, r254, 0a, 56h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
line:
ADDI64 r254, r254, -80d
ST r31, r254, 48a, 32h
ST r2, r254, 16a, 16h
ST r4, r254, 0a, 16h
ST r6, r254, 32a, 16h
CP r32, r8
LI64 r33, 1d
JEQ r33, r0, :0
LD r33, r254, 16a, 8h
LD r34, r254, 0a, 8h
JGTS r33, r34, :1
JMP :1
1: JMP :2
0: LD r34, r254, 24a, 8h
LD r33, r254, 8a, 8h
JGTS r34, r33, :2
JMP :2
2: LD r31, r254, 48a, 32h
ADDI64 r254, r254, 80d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -64d
ST r31, r254, 48a, 16h
LI64 r32, 0d
ST r32, r254, 0a, 8h
LI64 r32, 0d
ST r32, r254, 8a, 8h
LD r2, r254, 0a, 16h
LI64 r32, 0d
ST r32, r254, 16a, 8h
LI64 r32, 0d
ST r32, r254, 24a, 8h
LD r4, r254, 16a, 16h
LI64 r32, 0d
ST r32, r254, 32a, 8h
LI64 r32, 0d
ST r32, r254, 40a, 8h
LD r6, r254, 32a, 16h
LI64 r8, 10d
JAL r31, r0, :line
LI64 r32, 0d
ST r32, r254, 0a, 8h
LI64 r32, 0d
ST r32, r254, 8a, 8h
LD r2, r254, 0a, 16h
LI64 r32, 0d
ST r32, r254, 16a, 8h
LI64 r32, 0d
ST r32, r254, 24a, 8h
LD r4, r254, 16a, 16h
LI64 r32, 0d
ST r32, r254, 32a, 8h
LI64 r32, 0d
ST r32, r254, 40a, 8h
LD r6, r254, 32a, 16h
LI64 r8, 10d
JAL r31, r0, :rect_line
JAL r31, r0, :example
LI64 r1, 0d
LD r31, r254, 48a, 16h
ADDI64 r254, r254, 64d
JALA r0, r31, 0a
rect_line:
ADDI64 r254, r254, -112d
ST r31, r254, 48a, 64h
ST r2, r254, 0a, 16h
ST r4, r254, 16a, 16h
ST r6, r254, 32a, 16h
CP r32, r8
LI64 r33, 0d
LI64 r34, 0d
LI64 r35, 0d
5: JNE r33, r32, :0
JMP :1
0: LD r34, r254, 8a, 8h
LD r35, r254, 0a, 8h
4: LD r36, r254, 8a, 8h
LD r37, r254, 16a, 8h
ADD64 r36, r36, r37
JNE r34, r36, :2
JMP :3
2: LI64 r36, 1d
LI64 r37, 10d
ADD64 r36, r36, r37
LI64 r37, 1d
LI64 r38, 2d
ADD64 r36, r37, r38
ADDI64 r34, r34, 1d
JMP :4
3: ADDI64 r33, r33, 1d
JMP :5
1: LD r31, r254, 48a, 64h
ADDI64 r254, r254, 112d
JALA r0, r31, 0a
code size: 1400
ret: 0
status: Ok(())

View file

@ -0,0 +1,29 @@
integer_range:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
CP r33, r3
LI64 r2, 3d
LI64 r3, 4d
ECA
CP r34, r1
SUB64 r33, r33, r32
ADDI64 r33, r33, 1d
DIRU64 r0, r34, r34, r33
ADD64 r1, r34, r32
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r2, 0d
LI64 r3, 1000d
JAL r31, r0, :integer_range
CP r32, r1
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 210
ret: 42
status: Ok(())

View file

@ -0,0 +1,30 @@
fib:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LI64 r33, 0d
LI64 r34, 1d
2: LI64 r35, 0d
JNE r32, r35, :0
JMP :1
0: CP r35, r33
ADD64 r35, r35, r34
CP r33, r34
CP r34, r35
ADDI64 r32, r32, -1d
JMP :2
1: CP r1, r33
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 218
ret: 55
status: Ok(())

View file

@ -0,0 +1,35 @@
drop:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
CP r32, r2
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -40d
ST r31, r254, 8a, 32h
LI64 r32, 1d
ST r32, r254, 0a, 8h
ADDI64 r32, r254, 0d
CP r2, r32
JAL r31, r0, :modify
LD r2, r254, 0a, 8h
JAL r31, r0, :drop
LI64 r33, 0d
LD r34, r32, 0a, 8h
ADDI64 r1, r34, -2d
LD r31, r254, 8a, 32h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
modify:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
LI64 r33, 2d
ST r33, r32, 0a, 8h
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 312
ret: 0
status: Ok(())

View file

@ -0,0 +1,59 @@
create_back_buffer:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
LI64 r33, 255d
JGTS r32, r33, :0
CP r2, r32
JAL r31, r0, :request_page
JMP :1
0: LI64 r2, 255d
JAL r31, r0, :request_page
CP r33, r1
ADDI64 r32, r32, -255d
6: LI64 r34, 0d
JGTS r32, r34, :2
JMP :3
2: LI64 r34, 255d
JLTS r32, r34, :4
LI64 r2, 255d
JAL r31, r0, :request_page
CP r34, r1
JMP :5
4: CP r2, r32
JAL r31, r0, :request_page
CP r34, r1
5: ADDI64 r32, r32, -255d
JMP :6
3: CP r1, r33
1: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r2, 400d
JAL r31, r0, :create_back_buffer
CP r32, r1
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
request_page:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
LRA r33, r0, :"\0\u{1}xxxxxxxx\0"
CP r34, r33
ADDI64 r34, r34, 1d
ST r32, r34, 0a, 1h
LI64 r2, 3d
LI64 r3, 2d
CP r4, r33
LI64 r5, 12d
ECA
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 443
ret: 42
status: Ok(())

View file

@ -0,0 +1,16 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :some_func
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
some_func:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 133
ret: 0
status: Ok(())

View file

@ -0,0 +1,11 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r32, 1610612737d
LI64 r1, 0d
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 87
ret: 0
status: Ok(())

View file

@ -0,0 +1,75 @@
main:
ADDI64 r254, r254, -136d
ST r31, r254, 112a, 24h
LI64 r32, 1d
ST r32, r254, 32a, 8h
LI64 r32, 2d
ST r32, r254, 40a, 8h
LI64 r32, 3d
ST r32, r254, 16a, 8h
LI64 r32, 4d
ST r32, r254, 24a, 8h
LD r32, r254, 32a, 8h
LD r33, r254, 16a, 8h
ADD64 r32, r32, r33
ST r32, r254, 48a, 8h
LD r32, r254, 40a, 8h
LD r33, r254, 24a, 8h
ADD64 r32, r32, r33
ST r32, r254, 56a, 8h
LD r32, r254, 16a, 8h
LD r33, r254, 32a, 8h
SUB64 r32, r32, r33
ST r32, r254, 64a, 8h
LD r32, r254, 24a, 8h
LD r33, r254, 40a, 8h
SUB64 r32, r32, r33
ST r32, r254, 72a, 8h
LI64 r32, 0d
ST r32, r254, 0a, 8h
LI64 r32, 0d
ST r32, r254, 8a, 8h
LD r32, r254, 0a, 8h
LD r33, r254, 16a, 8h
SUB64 r32, r32, r33
ST r32, r254, 80a, 8h
LD r32, r254, 8a, 8h
LD r33, r254, 24a, 8h
SUB64 r32, r32, r33
ST r32, r254, 88a, 8h
ADDI64 r32, r254, 32d
ADDI64 r33, r254, 96d
BMC r32, r33, 16h
LD r33, r254, 80a, 8h
LD r32, r254, 48a, 8h
ADD64 r33, r33, r32
ST r33, r254, 80a, 8h
LD r33, r254, 88a, 8h
LD r32, r254, 56a, 8h
ADD64 r33, r33, r32
ST r33, r254, 88a, 8h
LD r33, r254, 96a, 8h
LD r32, r254, 64a, 8h
ADD64 r33, r33, r32
ST r33, r254, 96a, 8h
LD r33, r254, 104a, 8h
LD r32, r254, 72a, 8h
ADD64 r33, r33, r32
ST r33, r254, 104a, 8h
LD r33, r254, 80a, 8h
LD r32, r254, 96a, 8h
ADD64 r33, r33, r32
ST r33, r254, 96a, 8h
LD r33, r254, 88a, 8h
LD r32, r254, 104a, 8h
ADD64 r33, r33, r32
ST r33, r254, 104a, 8h
LD r33, r254, 96a, 8h
LD r32, r254, 104a, 8h
ADD64 r1, r33, r32
LD r31, r254, 112a, 24h
ADDI64 r254, r254, 136d
JALA r0, r31, 0a
code size: 778
ret: 10
status: Ok(())

View file

@ -0,0 +1,65 @@
fib:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
LI64 r33, 2d
JLTS r32, r33, :0
CP r33, r32
ADDI64 r33, r33, -1d
CP r2, r33
JAL r31, r0, :fib
CP r33, r1
CP r34, r32
ADDI64 r34, r34, -2d
CP r2, r34
JAL r31, r0, :fib
CP r34, r1
ADD64 r1, r33, r34
JMP :1
0: CP r1, r32
1: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
fib_iter:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LI64 r33, 0d
LI64 r34, 1d
2: LI64 r35, 0d
JNE r32, r35, :0
JMP :1
0: CP r35, r33
ADD64 r35, r35, r34
CP r33, r34
CP r34, r35
ADDI64 r32, r32, -1d
JMP :2
1: CP r1, r33
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -26d
ST r31, r254, 2a, 24h
LI64 r32, 10d
ST r32, r254, 0a, 1h
LI64 r32, 10d
ST r32, r254, 1a, 1h
CP r32, r0
LD r32, r254, 0a, 1h
CP r33, r0
LD r33, r254, 1a, 1h
CP r2, r32
JAL r31, r0, :fib
CP r32, r1
CP r2, r33
JAL r31, r0, :fib_iter
CP r33, r1
SUB64 r1, r32, r33
LD r31, r254, 2a, 24h
ADDI64 r254, r254, 26d
JALA r0, r31, 0a
code size: 452
ret: 0
status: Ok(())

View file

@ -0,0 +1,39 @@
foo:
ADDI64 r254, r254, -32d
ST r31, r254, 16a, 16h
LI64 r32, 3d
ST r32, r254, 0a, 8h
LI64 r32, 2d
ST r32, r254, 8a, 4h
LI64 r32, 2d
ST r32, r254, 12a, 4h
LD r1, r254, 0a, 16h
LD r31, r254, 16a, 16h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -40d
ST r31, r254, 16a, 24h
LI64 r32, 7d
JAL r31, r0, :foo
ST r1, r254, 0a, 16h
LD r33, r254, 0a, 8h
SUB64 r32, r32, r33
JAL r31, r0, :foo
ST r1, r254, 0a, 16h
CP r33, r0
LD r33, r254, 8a, 4h
SXT32 r33, r33
SUB64 r32, r32, r33
JAL r31, r0, :foo
ST r1, r254, 0a, 16h
CP r33, r0
LD r33, r254, 12a, 4h
SXT32 r33, r33
SUB64 r1, r32, r33
LD r31, r254, 16a, 24h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
code size: 341
ret: 0
status: Ok(())

View file

@ -0,0 +1,46 @@
main:
ADDI64 r254, r254, -48d
ST r31, r254, 24a, 24h
LI64 r32, 4d
ST r32, r254, 0a, 8h
LI64 r32, 1d
ST r32, r254, 8a, 8h
LI64 r32, 3d
ST r32, r254, 16a, 8h
ADDI64 r2, r254, 0d
ADDI64 r1, r254, 0d
JAL r31, r0, :odher_pass
LD r32, r254, 16a, 8h
LI64 r33, 3d
JNE r32, r33, :0
ADDI64 r33, r254, 0d
CP r2, r33
JAL r31, r0, :pass
JMP :1
0: LI64 r1, 0d
1: LD r31, r254, 24a, 24h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
odher_pass:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
CP r33, r1
CP r34, r33
BMC r32, r34, 24h
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
pass:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
LD r33, r32, 0a, 8h
LD r34, r32, 8a, 8h
SUB64 r1, r33, r34
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 394
ret: 3
status: Ok(())

View file

@ -0,0 +1,38 @@
main:
ADDI64 r254, r254, -10272d
ST r31, r254, 10240a, 32h
LI64 r32, 0d
2: LI64 r33, 1024d
JLTS r32, r33, :0
JMP :1
0: ADDI64 r33, r254, 0d
CP r34, r32
ADD64 r33, r33, r34
LI64 r34, 64d
ST r34, r33, 0a, 1h
ADDI64 r32, r32, 1d
JMP :2
1: LI64 r32, 1d
5: LI64 r33, 10d
JLTS r32, r33, :3
JMP :4
3: ADDI64 r33, r254, 0d
CP r34, r32
MULI64 r34, r34, 1024d
ADD64 r33, r33, r34
ADDI64 r34, r254, 0d
BMC r34, r33, 1024h
ADDI64 r32, r32, 1d
JMP :5
4: LI64 r33, 1024d
MULI64 r33, r33, 2d
ADDI64 r34, r254, 0d
ADD64 r34, r34, r33
CP r1, r0
LD r1, r34, 0a, 1h
LD r31, r254, 10240a, 32h
ADDI64 r254, r254, 10272d
JALA r0, r31, 0a
code size: 297
ret: 64
status: Ok(())

View file

@ -0,0 +1,13 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r32, 1d
LI64 r33, 2d
ADDI64 r32, r32, 1d
SUB64 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 102
ret: 0
status: Ok(())

View file

@ -0,0 +1,45 @@
main:
ADDI64 r254, r254, -48d
ST r31, r254, 16a, 32h
CP r32, r3
CP r33, r4
JAL r31, r0, :small_struct
CP r34, r1
LI64 r34, 0d
ST r34, r254, 8a, 1h
LI64 r34, 0d
ST r34, r254, 9a, 1h
LI64 r34, 0d
ST r34, r254, 10a, 1h
LI64 r34, 0d
ST r34, r254, 11a, 1h
LI64 r34, 0d
ST r34, r254, 12a, 1h
LI64 r34, 0d
ST r34, r254, 13a, 1h
LI64 r34, 0d
ST r34, r254, 14a, 1h
LI64 r34, 0d
ST r34, r254, 15a, 1h
LD r34, r254, 8a, 8h
ST r34, r254, 0a, 8h
ST r34, r254, 8a, 8h
LD r1, r254, 0a, 16h
LD r31, r254, 16a, 32h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
small_struct:
ADDI64 r254, r254, -20d
ST r31, r254, 4a, 16h
LI64 r32, 0d
ST r32, r254, 0a, 2h
LI64 r32, 0d
ST r32, r254, 2a, 2h
CP r1, r0
LD r1, r254, 0a, 4h
LD r31, r254, 4a, 16h
ADDI64 r254, r254, 20d
JALA r0, r31, 0a
code size: 440
ret: 0
status: Ok(())

View file

@ -0,0 +1,25 @@
inl:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LRA r32, r0, :"luhahah\0"
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :outl
JAL r31, r0, :inl
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
outl:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LRA r32, r0, :"whahaha\0"
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 229
ret: 0
status: Ok(())

View file

@ -1,12 +1,10 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :some_func
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
some_func:
JALA r0, r31, 0a
code size: 85
code size: 77
ret: 0
status: Ok(())

View file

@ -0,0 +1,17 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r1, 1d
JNE r2, r1, :0
JMP :1
0: LI64 r32, 0d
JNE r2, r32, :2
LI64 r1, 2d
JMP :1
2: LI64 r1, 3d
1: LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 127
ret: 2
status: Ok(())

View file

@ -0,0 +1,18 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :foo
CP r32, r1
LI64 r1, 0d
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
foo:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 146
ret: 0
status: Ok(())

View file

@ -1,13 +1,10 @@
foo:
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :foo
CP r1, r0
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 88
code size: 77
ret: 0
status: Ok(())

View file

@ -0,0 +1,103 @@
main:
ADDI64 r254, r254, -88d
ST r31, r254, 0a, 88h
LI64 r32, 0d
CP r2, r32
JAL r31, r0, :multiple_breaks
CP r33, r1
LI64 r34, 3d
JEQ r33, r34, :0
LI64 r1, 1d
JMP :1
0: LI64 r35, 4d
CP r2, r35
JAL r31, r0, :multiple_breaks
CP r36, r1
LI64 r37, 10d
JEQ r36, r37, :2
LI64 r1, 2d
JMP :1
2: CP r2, r32
JAL r31, r0, :state_change_in_break
CP r38, r1
JEQ r38, r32, :3
CP r1, r34
JMP :1
3: CP r2, r35
JAL r31, r0, :state_change_in_break
CP r39, r1
JEQ r39, r37, :4
CP r1, r35
JMP :1
4: CP r2, r37
JAL r31, r0, :continue_and_state_change
CP r40, r1
JEQ r40, r37, :5
LI64 r1, 5d
JMP :1
5: CP r2, r34
JAL r31, r0, :continue_and_state_change
CP r41, r1
JEQ r41, r32, :6
LI64 r1, 6d
JMP :1
6: CP r1, r32
1: LD r31, r254, 0a, 88h
ADDI64 r254, r254, 88d
JALA r0, r31, 0a
continue_and_state_change:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r1, r2
LI64 r32, 3d
LI64 r33, 4d
LI64 r34, 2d
LI64 r35, 10d
6: JLTU r1, r35, :0
JMP :1
0: JNE r1, r34, :2
CP r1, r33
JMP :3
2: JNE r1, r32, :4
LI64 r1, 0d
1: JMP :5
4: ADDI64 r33, r1, 1d
CP r1, r33
3: JMP :6
5: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
state_change_in_break:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r1, r2
LI64 r32, 3d
LI64 r33, 10d
4: JLTU r1, r33, :0
JMP :1
0: JNE r1, r32, :2
LI64 r1, 0d
1: JMP :3
2: ADDI64 r1, r1, 1d
JMP :4
3: LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
multiple_breaks:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r1, r2
LI64 r32, 3d
LI64 r33, 10d
4: JLTU r1, r33, :0
JMP :1
0: ADDI64 r1, r1, 1d
JNE r1, r32, :2
1: JMP :3
2: JMP :4
3: LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 704
ret: 0
status: Ok(())

View file

@ -0,0 +1,60 @@
main:
ADDI64 r254, r254, -88d
ST r31, r254, 0a, 88h
JAL r31, r0, :check_platform
CP r32, r1
LI64 r33, 0d
LI64 r34, 30d
LI64 r35, 100d
CP r36, r33
CP r37, r33
CP r38, r33
5: JLTU r37, r34, :0
ADDI64 r36, r36, 1d
CP r2, r33
CP r3, r36
CP r4, r34
JAL r31, r0, :set_pixel
CP r39, r1
JEQ r39, r38, :1
CP r1, r33
JMP :2
1: JNE r36, r35, :3
CP r1, r38
JMP :2
3: CP r37, r33
CP r40, r38
JMP :4
0: ADDI64 r40, r38, 1d
ADDI64 r41, r37, 1d
CP r37, r41
4: CP r38, r40
JMP :5
2: LD r31, r254, 0a, 88h
ADDI64 r254, r254, 88d
JALA r0, r31, 0a
set_pixel:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
MUL64 r32, r4, r3
ADD64 r1, r32, r2
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
check_platform:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :x86_fb_ptr
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
x86_fb_ptr:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r1, 100d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 423
ret: 3000
status: Ok(())

View file

@ -0,0 +1,30 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r2, 10d
JAL r31, r0, :add_one
CP r32, r1
LI64 r2, 20d
JAL r31, r0, :add_two
CP r33, r1
ADD64 r1, r33, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
add_two:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
ADDI64 r1, r2, 2d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
add_one:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
ADDI64 r1, r2, 1d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 251
ret: 33
status: Ok(())

View file

@ -1,15 +1,10 @@
inl:
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :outl
JAL r31, r0, :inl
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
outl:
JALA r0, r31, 0a
code size: 103
code size: 77
ret: 0
status: Ok(())

View file

@ -0,0 +1,29 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
fib:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LI64 r1, 1d
LI64 r33, 2d
JGTU r32, r33, :0
JMP :1
0: SUB64 r2, r32, r1
JAL r31, r0, :fib
CP r34, r1
SUB64 r2, r32, r33
JAL r31, r0, :fib
CP r35, r1
ADD64 r1, r35, r34
1: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
code size: 208
ret: 55
status: Ok(())

View file

@ -0,0 +1,28 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
fib:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
LI64 r32, 1d
LI64 r33, 0d
CP r1, r33
CP r34, r32
2: JNE r2, r33, :0
JMP :1
0: ADD64 r35, r34, r1
SUB64 r2, r2, r32
CP r1, r34
CP r34, r35
JMP :2
1: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
code size: 198
ret: 55
status: Ok(())

View file

@ -0,0 +1,10 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 77
ret: 0
status: Ok(())

11
hblang/text-prj/main.hb Normal file
View file

@ -0,0 +1,11 @@
foo := 0;
.{global, fib, Structa, create_window, WindowID} := @use("pkg.hb")
main := fn(a: int): int {
g := global
win := create_window()
return fib(g + Structa.(0, 0).foo)
}

19
hblang/text-prj/pkg.hb Normal file
View file

@ -0,0 +1,19 @@
global := 10
Structa := struct {
foo: int,
goo: int,
}
create_window := fn(): WindowID {
return WindowID.(1, 2)
}
WindowID := struct {
host_id: int,
window_id: int,
}
fib := fn(n: int): int {
return n + 1
}

View file

@ -9,4 +9,4 @@ alloc = []
nightly = []
[dependencies]
hbbytecode = { workspace = true }
hbbytecode = { path = "../hbbytecode", default-features = false }

View file

@ -126,33 +126,11 @@ pub enum VmRunError {
/// Invalid operand
InvalidOperand,
}
impl core::fmt::Display for VmRunError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VmRunError::InvalidOpcode(op) => {
f.write_str("invalid op code: ").and_then(|_| op.fmt(f))
}
VmRunError::LoadAccessEx(address) => {
f.write_str("falied to load at ").and_then(|_| address.fmt(f))
}
VmRunError::ProgramFetchLoadEx(address) => {
f.write_str("falied to load instruction at ").and_then(|_| address.fmt(f))
}
VmRunError::StoreAccessEx(address) => {
f.write_str("falied to store at ").and_then(|_| address.fmt(f))
}
VmRunError::RegOutOfBounds => f.write_str("reg out of bounds"),
VmRunError::AddrOutOfBounds => f.write_str("addr out-of-bounds"),
VmRunError::Unreachable => f.write_str("unreachable"),
VmRunError::InvalidOperand => f.write_str("invalud operand"),
}
}
/// Unimplemented feature
Unimplemented,
}
impl core::error::Error for VmRunError {}
/// Virtual machine halt ok
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VmRunOk {

Some files were not shown because too many files have changed in this diff Show more