Compare commits

..

No commits in common. "54a7f8597864d45ae2133e97c2bdf661c4f97202" and "13f63c77000f54e7ac13009e4e9148363db62ebc" have entirely different histories.

21 changed files with 1033 additions and 1972 deletions

1
.gitignore vendored
View file

@ -2,4 +2,3 @@
/hbbytecode/src/instrs.rs
/.rgignore
rustc-ice-*
db.sqlite

846
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,6 @@
[workspace]
resolver = "2"
members = [
"hbbytecode",
"hbvm",
"hbxrt",
"xtask",
"hblang",
"hbjit",
"depell",
"depell/wasm-hbfmt"
]
members = ["hbbytecode", "hbvm", "hbxrt", "xtask", "hblang", "hbjit", "depell"]
[profile.release]
lto = true

View file

@ -4,12 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
axum = "0.7.7"
aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "rand_core"] }
ed25519-dalek = { version = "2.1.1", default-features = false, features = ["rand_core"] }
getrandom = "0.2.15"
htmlm = "0.3.0"
log = "0.4.22"
rusqlite = "0.32.1"
serde = { version = "1.0.210", features = ["derive"] }
time = "0.3.36"
tokio = { version = "1.40.0", features = ["rt"] }
rand_core = { version = "0.6.4", features = ["getrandom"] }
x25519-dalek = { version = "2.0.1", default-features = false }

View file

@ -1,100 +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);
}
}
div.preview {
div.info {
display: flex;
gap: var(--small-gap);
span[apply=timestamp] {
color: var(--timestamp);
}
}
div.stats {
display: flex;
gap: var(--small-gap);
}
}
form {
display: flex;
flex-direction: column;
gap: var(--small-gap);
::placeholder {
color: var(--placeholder);
}
.error {
color: var(--error);
text-align: center;
}
}
pre,
textarea {
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
padding-top: calc(var(--small-gap) * 1.5);
margin: var(--small-gap) 0px;
font-family: var(--monospace);
resize: none;
tab-size: 4;
}
input {
font-size: inherit;
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
}
input:is(:hover, :focus) {
background: white;
}
button {
border: none;
outline: none;
font-size: inherit;
background: var(--secondary);
}
button:hover:not(:active) {
background: white;
}

View file

@ -1,181 +0,0 @@
/// @ts-check
/** @return {never} */
function never() { throw new Error() }
/**@type{WebAssembly.Instance}*/ let instance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let instaceFuture;
/** @param {string} code @param {"fmt" | "minify"} action
* @returns {Promise<string | undefined> | string | undefined} */
function modifyCode(code, action) {
if (!instance) {
instaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
return (async () => {
instance = (await instaceFuture).instance;
return modifyCodeSync(instance, code, action);
})();
} else {
return modifyCodeSync(instance, code, action);
}
}
/** @param {WebAssembly.Instance} instance @param {string} code @param {"fmt" | "minify"} action @returns {string | undefined} */
function modifyCodeSync(instance, code, action) {
let {
INPUT, INPUT_LEN,
OUTPUT, OUTPUT_LEN,
PANIC_MESSAGE, PANIC_MESSAGE_LEN,
memory, fmt, minify
} = instance.exports;
if (!(true
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& OUTPUT instanceof WebAssembly.Global
&& OUTPUT_LEN instanceof WebAssembly.Global
&& memory instanceof WebAssembly.Memory
&& typeof fmt === "function"
&& typeof minify === "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));
try {
if (action === "fmt") fmt(); else minify();
let result = new TextDecoder()
.decode(new Uint8Array(memory.buffer, OUTPUT.value,
dw.getUint32(OUTPUT_LEN.value, true)));
return result;
} catch (e) {
if (PANIC_MESSAGE instanceof WebAssembly.Global
&& PANIC_MESSAGE_LEN instanceof WebAssembly.Global) {
let message = new TextDecoder()
.decode(new Uint8Array(memory.buffer, PANIC_MESSAGE.value,
dw.getUint32(PANIC_MESSAGE_LEN.value, true)));
console.error(message, e);
} else {
console.error(e);
}
return undefined;
}
}
/** @param {HTMLElement} target */
function wireUp(target) {
execApply(target);
cacheInputs(target);
bindTextareaAutoResize(target);
}
/** @type {{ [key: string]: (content: string) => Promise<string> | string }} */
const applyFns = {
timestamp: (content) => new Date(parseInt(content) * 1000).toLocaleString(),
fmt: (content) => {
let res = modifyCode(content, "fmt");
return res instanceof Promise ? res.then(c => c ?? content) : res ?? content;
},
};
/** @param {HTMLElement} target */
function execApply(target) {
for (const elem of target.querySelectorAll('[apply]')) {
if (!(elem instanceof HTMLElement)) continue;
const funcname = elem.getAttribute('apply') ?? never();
let res = applyFns[funcname](elem.textContent ?? "");
if (res instanceof Promise) res.then(c => elem.textContent = c);
else elem.textContent = res;
}
}
/** @param {HTMLElement} target */
function bindTextareaAutoResize(target) {
for (const textarea of target.querySelectorAll("textarea")) {
if (!(textarea instanceof HTMLTextAreaElement)) never();
textarea.style.height = textarea.scrollHeight + "px";
textarea.style.overflowY = "hidden";
textarea.addEventListener("input", function() {
textarea.style.height = "auto";
textarea.style.height = textarea.scrollHeight + "px";
});
textarea.onkeydown = (ev) => {
const selecting = textarea.selectionStart !== textarea.selectionEnd;
if (ev.key === "Tab") {
ev.preventDefault();
const prevPos = textarea.selectionStart;
textarea.value = textarea.value.slice(0, textarea.selectionStart) +
' ' + textarea.value.slice(textarea.selectionEnd);
textarea.selectionStart = textarea.selectionEnd = prevPos + 4;
}
if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) {
let i = textarea.selectionStart, looped = false;
while (textarea.value.charCodeAt(--i) === ' '.charCodeAt(0)) looped = true;
if (textarea.value.charCodeAt(i) === '\n'.charCodeAt(0) && looped) {
ev.preventDefault();
let toDelete = (textarea.selectionStart - (i + 1)) % 4;
if (toDelete === 0) toDelete = 4;
const prevPos = textarea.selectionStart;
textarea.value = textarea.value.slice(0, textarea.selectionStart - toDelete) +
textarea.value.slice(textarea.selectionEnd);
textarea.selectionStart = textarea.selectionEnd = prevPos - toDelete;
}
}
}
}
}
/** @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);
}
}
}
}
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 testCodeChange() {
const code = "main:=fn():void{return}";
const fmtd = await modifyCode(code, "fmt") ?? never();
const prev = await modifyCode(fmtd, "minify") ?? never();
if (code != prev) console.error(code, prev);
})()
}
document.body.addEventListener('htmx:afterSwap', (ev) => {
if (!(ev.target instanceof HTMLElement)) never();
wireUp(ev.target);
});
wireUp(document.body);

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
<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,51 +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 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 = { version = "0.1.0", path = "../../hblang", default-features = false }
log = { version = "0.4.22", features = ["max_level_off"] }

View file

@ -1,133 +0,0 @@
#![no_std]
#![feature(slice_take)]
#![feature(str_from_raw_parts)]
#![feature(alloc_error_handler)]
use {
core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
},
hblang::parser::ParserCtx,
};
const ARENA_SIZE: usize = 128 * 1024;
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
const MAX_INPUT_SIZE: usize = 1024 * 4;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
//unsafe {
// use core::fmt::Write;
// let mut f = Write(&mut PANIC_MESSAGE[..]);
// _ = writeln!(f, "{}", info);
// PANIC_MESSAGE_LEN = 1024 - f.0.len();
//}
core::arch::wasm32::unreachable();
}
#[global_allocator]
static ALLOCATOR: ArenaAllocator = ArenaAllocator::new();
#[cfg(target_arch = "wasm32")]
#[alloc_error_handler]
fn alloc_error(_: core::alloc::Layout) -> ! {
core::arch::wasm32::unreachable()
}
#[repr(C, align(32))]
struct ArenaAllocator {
arena: UnsafeCell<[u8; ARENA_SIZE]>,
head: UnsafeCell<*mut u8>,
}
impl ArenaAllocator {
const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; ARENA_SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(ARENA_SIZE);
}
}
unsafe impl Sync for ArenaAllocator {}
unsafe impl GlobalAlloc for ArenaAllocator {
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 & !(1 << (align - 1))) as *mut u8;
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 */
}
}
struct Write<'a>(&'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)
}
}
}
//#[no_mangle]
//static mut PANIC_MESSAGE: [u8; 1024] = unsafe { core::mem::zeroed() };
//#[no_mangle]
//static mut PANIC_MESSAGE_LEN: usize = 0;
#[no_mangle]
static mut OUTPUT: [u8; MAX_OUTPUT_SIZE] = unsafe { core::mem::zeroed() };
#[no_mangle]
static mut OUTPUT_LEN: usize = 0;
#[no_mangle]
static MAX_INPUT: usize = MAX_INPUT_SIZE;
#[no_mangle]
static mut INPUT: [u8; MAX_INPUT_SIZE] = unsafe { core::mem::zeroed() };
#[no_mangle]
static mut INPUT_LEN: usize = 0;
#[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 = hblang::parser::Arena::default();
let mut ctx = ParserCtx::default();
let exprs = hblang::parser::Parser::parse(&mut ctx, code, "source.hb", &|_, _| Ok(0), &arena);
let mut f = Write(&mut OUTPUT[..]);
hblang::fmt::fmt_file(exprs, code, &mut f).unwrap();
OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len();
}
#[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 = hblang::fmt::minify(code);
}

View file

@ -12,13 +12,8 @@ hashbrown = { version = "0.15.0", default-features = false, features = ["raw-ent
hbbytecode = { version = "0.1.0", path = "../hbbytecode" }
hbvm = { path = "../hbvm", features = ["nightly"] }
log = { version = "0.4.22", features = ["release_max_level_error"] }
[dependencies.regalloc2]
git = "https://github.com/jakubDoka/regalloc2"
branch = "reuse-allocations"
optional = true
regalloc2 = { git = "https://github.com/jakubDoka/regalloc2", branch = "reuse-allocations", features = [] }
[features]
default = ["std", "opts"]
default = ["std"]
std = []
opts = ["regalloc2"]

View file

@ -839,8 +839,8 @@ impl Codegen {
let index_val = self.expr(index)?;
_ = self.assert_ty(index.pos(), index_val.ty, ty::Id::INT, "subsctipt");
if let Some(ty) = self.tys.base_of(base_val.ty) {
base_val.ty = ty;
if let ty::Kind::Ptr(ty) = base_val.ty.expand() {
base_val.ty = self.tys.ins.ptrs[ty as usize].base;
base_val.loc = base_val.loc.into_derefed();
}
@ -1070,7 +1070,7 @@ impl Codegen {
Some(Value { ty, loc })
}
E::String { pos, mut literal } => {
literal = &literal[1..literal.len() - 1];
literal = literal.trim_matches('"');
if !literal.ends_with("\\0") {
self.report(pos, "string literal must end with null byte (for now)");
@ -1206,8 +1206,8 @@ impl Codegen {
let checkpoint = self.ci.snap();
let mut tal = self.expr(target)?;
if let Some(ty) = self.tys.base_of(tal.ty) {
tal.ty = ty;
if let ty::Kind::Ptr(ty) = tal.ty.expand() {
tal.ty = self.tys.ins.ptrs[ty as usize].base;
tal.loc = tal.loc.into_derefed();
}
@ -1306,9 +1306,9 @@ impl Codegen {
}
E::UnOp { op: T::Mul, val, pos } => {
let val = self.expr(val)?;
match self.tys.base_of(val.ty) {
Some(ty) => Some(Value {
ty,
match val.ty.expand() {
ty::Kind::Ptr(ty) => Some(Value {
ty: self.tys.ins.ptrs[ty as usize].base,
loc: Loc::reg(self.loc_to_reg(val.loc, self.tys.size_of(val.ty)))
.into_derefed(),
}),
@ -1640,9 +1640,10 @@ impl Codegen {
imm = u64::from_ne_bytes(dst);
}
if matches!(op, T::Add | T::Sub)
&& let Some(ty) = self.tys.base_of(ty)
&& let ty::Kind::Ptr(ty) = ty::Kind::from_ty(ty)
{
imm *= self.tys.size_of(ty) as u64;
let size = self.tys.size_of(self.tys.ins.ptrs[ty as usize].base);
imm *= size as u64;
}
self.ci.emit(oper(dst.get(), lhs.get(), imm));
@ -1675,8 +1676,9 @@ impl Codegen {
(lhs.get(), right.ty)
};
let ty = self.tys.base_of(ty).unwrap();
let size = self.tys.size_of(ty);
let ty::Kind::Ptr(ty) = ty.expand() else { unreachable!() };
let size = self.tys.size_of(self.tys.ins.ptrs[ty as usize].base);
self.ci.emit(muli64(offset, offset, size as _));
}
}

View file

@ -3,10 +3,11 @@ use {
lexer::{self, TokenKind},
parser::{self, CommentOr, CtorField, Expr, Poser, Radix, StructField},
},
alloc::string::String,
core::fmt,
};
pub fn minify(source: &mut str) -> usize {
pub fn minify(source: &mut str) -> Option<&str> {
fn needs_space(c: u8) -> bool {
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | 127..)
}
@ -14,7 +15,6 @@ pub fn minify(source: &mut str) -> usize {
let mut writer = source.as_mut_ptr();
let mut reader = &source[..];
let mut prev_needs_whitecpace = false;
let mut prev_needs_newline = false;
loop {
let mut token = lexer::Lexer::new(reader).next();
match token.kind {
@ -23,59 +23,46 @@ pub fn minify(source: &mut str) -> usize {
_ => {}
}
let cpy_len = token.range().len();
let mut suffix = 0;
if token.kind == TokenKind::Comment && reader.as_bytes()[token.end as usize - 1] != b'/' {
token.end = token.start + reader[token.range()].trim_end().len() as u32;
suffix = b'\n';
}
let mut prefix = 0;
if prev_needs_whitecpace && needs_space(reader.as_bytes()[token.start as usize]) {
prefix = b' ';
debug_assert!(token.start != 0, "{reader}");
}
prev_needs_whitecpace = needs_space(reader.as_bytes()[token.end as usize - 1]);
let inbetween_new_lines =
reader[..token.start as usize].bytes().filter(|&b| b == b'\n').count()
+ token.kind.precedence().is_some() as usize;
let extra_prefix_new_lines = if inbetween_new_lines > 1 {
1 + token.kind.precedence().is_none() as usize
} else {
prev_needs_newline as usize
};
if token.kind == TokenKind::Comment && reader.as_bytes()[token.end as usize - 1] != b'/' {
prev_needs_newline = true;
prev_needs_whitecpace = false;
} else {
prev_needs_newline = false;
}
let sstr = reader[token.start as usize..].as_ptr();
reader = &reader[token.end as usize..];
unsafe {
if extra_prefix_new_lines != 0 {
for _ in 0..extra_prefix_new_lines {
writer.write(b'\n');
writer = writer.add(1);
}
} else if prefix != 0 {
if prefix != 0 {
writer.write(prefix);
writer = writer.add(1);
}
writer.copy_from(sstr, cpy_len);
writer = writer.add(cpy_len);
writer.copy_from(sstr, token.range().len());
writer = writer.add(token.range().len());
if suffix != 0 {
writer.write(suffix);
writer = writer.add(1);
}
}
}
unsafe { writer.sub_ptr(source.as_mut_ptr()) }
None
}
pub struct Formatter<'a> {
source: &'a str,
depth: usize,
disp_buff: String,
}
impl<'a> Formatter<'a> {
pub fn new(source: &'a str) -> Self {
Self { source, depth: 0 }
Self { source, depth: 0, disp_buff: Default::default() }
}
fn fmt_list<T: Poser, F: core::fmt::Write>(
@ -185,7 +172,7 @@ impl<'a> Formatter<'a> {
self.fmt(value, f)
}
Expr::String { literal, .. } => write!(f, "{literal}"),
Expr::Comment { literal, .. } => write!(f, "{literal}"),
Expr::Comment { literal, .. } => write!(f, "{}", literal.trim_end()),
Expr::Mod { path, .. } => write!(f, "@use(\"{path}\")"),
Expr::Field { target, name: field, .. } => {
self.fmt_paren(target, f, postfix)?;
@ -207,7 +194,7 @@ impl<'a> Formatter<'a> {
write!(f, "{name}: ")?;
s.fmt(ty, f)?
}
CommentOr::Comment { literal, .. } => writeln!(f, "{literal}")?,
CommentOr::Comment { literal, .. } => write!(f, "{literal}")?,
}
Ok(field.or().is_some())
})
@ -307,42 +294,30 @@ impl<'a> Formatter<'a> {
write!(f, "{{")?;
self.fmt_list(f, true, "}", "", stmts, Self::fmt)
}
Expr::Number { value, radix, .. } => {
fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
fn conv_radix(d: u8) -> u8 {
match d {
0..=9 => d + b'0',
_ => d - 10 + b'A',
}
}
for (i, b) in buf.iter_mut().enumerate().rev() {
let d = (value % radix as u64) as u8;
value /= radix as u64;
*b = conv_radix(d);
if value == 0 {
return unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
}
}
unreachable!()
}
let mut buf = [0u8; 64];
let value = display_radix(radix, value as u64, &mut buf);
match radix {
Radix::Decimal => write!(f, "{value}"),
Radix::Hex => write!(f, "0x{value}"),
Radix::Octal => write!(f, "0o{value}"),
Radix::Binary => write!(f, "0b{value}"),
}
}
Expr::Number { value, radix, .. } => match radix {
Radix::Decimal => write!(f, "{value}"),
Radix::Hex => write!(f, "{value:#X}"),
Radix::Octal => write!(f, "{value:#o}"),
Radix::Binary => write!(f, "{value:#b}"),
},
Expr::Bool { value, .. } => write!(f, "{value}"),
Expr::Idk { .. } => write!(f, "idk"),
Expr::BinOp {
left,
op: TokenKind::Assign,
right: &Expr::BinOp { left: lleft, op, right },
} if left.pos() == lleft.pos() => {
right: Expr::BinOp { left: lleft, op, right },
} if {
let mut b = core::mem::take(&mut self.disp_buff);
self.fmt(lleft, &mut b)?;
let len = b.len();
self.fmt(left, &mut b)?;
let (lleft, left) = b.split_at(len);
let res = lleft == left;
b.clear();
self.disp_buff = b;
res
} =>
{
self.fmt(left, f)?;
write!(f, " {op}= ")?;
self.fmt(right, f)
@ -380,7 +355,7 @@ impl<'a> Formatter<'a> {
}
pub fn preserve_newlines(source: &str) -> usize {
source[source.trim_end().len()..].bytes().filter(|&c| c == b'\n').count()
source[source.trim_end().len()..].chars().filter(|&c| c == '\n').count()
}
pub fn insert_needed_semicolon(source: &str) -> bool {
@ -390,46 +365,39 @@ pub fn insert_needed_semicolon(source: &str) -> bool {
impl core::fmt::Display for parser::Ast {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_file(self.exprs(), &self.file, f)
}
}
for (i, expr) in self.exprs().iter().enumerate() {
Formatter::new(&self.file).fmt(expr, f)?;
if let Some(expr) = self.exprs().get(i + 1)
&& let Some(rest) = self.file.get(expr.pos() as usize..)
{
if insert_needed_semicolon(rest) {
write!(f, ";")?;
}
pub fn fmt_file(exprs: &[Expr], file: &str, f: &mut impl fmt::Write) -> fmt::Result {
for (i, expr) in exprs.iter().enumerate() {
Formatter::new(file).fmt(expr, f)?;
if let Some(expr) = exprs.get(i + 1)
&& let Some(rest) = file.get(expr.pos() as usize..)
{
if insert_needed_semicolon(rest) {
write!(f, ";")?;
if preserve_newlines(&self.file[..expr.pos() as usize]) > 1 {
writeln!(f)?;
}
}
if preserve_newlines(&file[..expr.pos() as usize]) > 1 {
if i + 1 != self.exprs().len() {
writeln!(f)?;
}
}
if i + 1 != exprs.len() {
writeln!(f)?;
}
Ok(())
}
Ok(())
}
#[cfg(test)]
pub mod test {
use {
crate::parser::{self, ParserCtx},
crate::parser::{self, StackAlloc},
alloc::borrow::ToOwned,
std::{fmt::Write, string::String},
};
pub fn format(ident: &str, input: &str) {
let mut minned = input.to_owned();
let len = crate::fmt::minify(&mut minned);
minned.truncate(len);
let ast = parser::Ast::new(ident, minned, &mut ParserCtx::default(), &|_, _| Ok(0));
let ast =
parser::Ast::new(ident, input.to_owned(), &mut StackAlloc::default(), &|_, _| Ok(0));
let mut output = String::new();
write!(output, "{ast}").unwrap();

View file

@ -1,7 +1,7 @@
use {
crate::{
codegen,
parser::{self, Ast, ParserCtx},
parser::{self, Ast, StackAlloc},
},
alloc::{string::String, vec::Vec},
core::{fmt::Write, num::NonZeroUsize},
@ -263,22 +263,22 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Vec<Ast>> {
Ok(id)
};
let execute_task = |ctx: &mut _, (_, path): Task| {
let execute_task = |stack: &mut _, (_, path): Task| {
let path = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("path contains invalid characters: {}", display_rel_path(&path)),
)
})?;
Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &|path, from| {
Ok(Ast::new(path, std::fs::read_to_string(path)?, stack, &|path, from| {
loader(path, from).map_err(|e| e.to_string())
}))
};
let thread = || {
let mut ctx = ParserCtx::default();
let mut stack = StackAlloc::default();
while let Some(task @ (indx, ..)) = tasks.pop() {
let res = execute_task(&mut ctx, task);
let res = execute_task(&mut stack, task);
let mut ast = ast.lock().unwrap();
let len = ast.len().max(indx as usize + 1);
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));

View file

@ -1,4 +1,4 @@
use crate::EncodedInstr;
use crate::{instrs, EncodedInstr};
const fn ascii_mask(chars: &[u8]) -> u128 {
let mut eq = 0;
@ -83,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'!',
@ -170,16 +170,9 @@ 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)> {
use crate::instrs;
Some((
match self {
Self::Le if signed => instrs::jgts,
@ -199,7 +192,7 @@ impl TokenKind {
}
pub fn binop(self, signed: bool, size: u32) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
use crate::instrs::*;
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)),*]}; }
@ -226,7 +219,7 @@ impl TokenKind {
#[allow(clippy::type_complexity)]
pub fn imm_binop(self, signed: bool, size: u32) -> Option<fn(u8, u8, u64) -> EncodedInstr> {
use crate::instrs::*;
use instrs::*;
macro_rules! def_op {
($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => {
macro_rules! $name {
@ -293,7 +286,7 @@ impl TokenKind {
pub fn unop(&self) -> Option<fn(u8, u8) -> EncodedInstr> {
Some(match self {
Self::Sub => crate::instrs::neg,
Self::Sub => instrs::neg,
_ => return None,
})
}
@ -369,7 +362,7 @@ gen_token_kind! {
pub struct Lexer<'a> {
pos: u32,
source: &'a [u8],
bytes: &'a [u8],
}
impl<'a> Lexer<'a> {
@ -378,22 +371,22 @@ impl<'a> Lexer<'a> {
}
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 { core::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]) }
unsafe { core::str::from_utf8_unchecked(&self.bytes[tok]) }
}
fn peek(&self) -> Option<u8> {
if core::intrinsics::unlikely(self.pos >= self.source.len() as u32) {
if core::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) })
}
}
@ -460,7 +453,7 @@ impl<'a> Lexer<'a> {
}
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 {
@ -472,18 +465,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;

View file

@ -8,6 +8,7 @@
never_type,
unwrap_infallible,
slice_partition_dedup,
hash_raw_entry,
portable_simd,
iter_collect_into,
new_uninit,
@ -18,8 +19,6 @@
extract_if,
ptr_internals,
iter_intersperse,
str_from_raw_parts,
ptr_sub_ptr,
slice_from_ptr_range
)]
#![warn(clippy::dbg_macro)]
@ -33,6 +32,7 @@ use {
ident::Ident,
lexer::TokenKind,
parser::{CommentOr, Expr, ExprRef, FileId, Pos},
son::reg,
ty::ArrayLen,
},
alloc::{collections::BTreeMap, string::String, vec::Vec},
@ -65,22 +65,11 @@ pub mod fmt;
#[cfg(any(feature = "std", test))]
pub mod fs;
pub mod parser;
#[cfg(feature = "opts")]
pub mod son;
mod lexer;
#[cfg(feature = "opts")]
mod vc;
pub mod reg {
pub const STACK_PTR: Reg = 254;
pub const ZERO: Reg = 0;
pub const RET: Reg = 1;
pub const RET_ADDR: Reg = 31;
pub type Reg = u8;
}
mod ctx_map {
use core::hash::BuildHasher;
@ -150,12 +139,10 @@ mod ctx_map {
.map(|(k, _)| &k.value)
}
#[cfg_attr(not(feature = "opts"), expect(dead_code))]
pub fn clear(&mut self) {
self.inner.clear();
}
#[cfg_attr(not(feature = "opts"), expect(dead_code))]
pub fn remove(&mut self, value: &T, ctx: &T::Ctx) -> Option<T> {
let (entry, _) = self.entry(value.key(ctx), ctx);
match entry {
@ -206,7 +193,6 @@ mod task {
unpack(offset).is_ok()
}
#[cfg_attr(not(feature = "opts"), expect(dead_code))]
pub fn id(index: usize) -> Offset {
1 << 31 | index as u32
}
@ -411,14 +397,8 @@ mod ty {
mod __lc_names {
use super::*;
$(pub const $name: &str = unsafe {
const LCL: &[u8] = unsafe {
&array_to_lower_case(
*(stringify!($name).as_ptr() as *const [u8; stringify!($name).len()])
)
};
core::str::from_utf8_unchecked(LCL)
};)*
$(pub const $name: &[u8] = &array_to_lower_case(unsafe {
*(stringify!($name).as_ptr() as *const [u8; stringify!($name).len()]) });)*
}
#[allow(dead_code)]
@ -427,7 +407,7 @@ mod ty {
}
pub fn from_str(name: &str) -> Option<Builtin> {
match name {
match name.as_bytes() {
$(__lc_names::$name => Some($name),)*
_ => None,
}
@ -435,7 +415,7 @@ mod ty {
pub fn to_str(ty: Builtin) -> &'static str {
match ty {
$($name => __lc_names::$name,)*
$($name => unsafe { core::str::from_utf8_unchecked(__lc_names::$name) },)*
v => unreachable!("invalid type: {}", v),
}
}
@ -571,7 +551,6 @@ mod ty {
}
}
#[cfg_attr(not(feature = "opts"), expect(dead_code))]
pub fn bin_ret(ty: Id, op: TokenKind) -> Id {
use TokenKind as T;
match op {
@ -1162,7 +1141,6 @@ impl Types {
}
}
#[cfg_attr(not(feature = "opts"), expect(dead_code))]
fn find_struct_field(&self, s: ty::Struct, name: &str) -> Option<usize> {
let name = self.names.project(name)?;
self.struct_fields(s).iter().position(|f| f.name == name)
@ -1210,8 +1188,8 @@ impl OffsetIter {
}
}
#[cfg(any(feature = "opts", feature = "std"))]
type HashMap<K, V> = hashbrown::HashMap<K, V, FnvBuildHasher>;
type _HashSet<K> = hashbrown::HashSet<K, FnvBuildHasher>;
type FnvBuildHasher = core::hash::BuildHasherDefault<FnvHasher>;
struct FnvHasher(u64);
@ -1356,10 +1334,10 @@ fn test_parse_files(ident: &'static str, input: &'static str) -> Vec<parser::Ast
.ok_or("Not Found".to_string())
};
let mut ctx = parser::ParserCtx::default();
let mut stack = parser::StackAlloc::default();
module_map
.iter()
.map(|&(path, content)| parser::Ast::new(path, content.to_owned(), &mut ctx, &loader))
.map(|&(path, content)| parser::Ast::new(path, content.to_owned(), &mut stack, &loader))
.collect()
}

View file

@ -63,60 +63,63 @@ pub struct Parser<'a, 'b> {
path: &'b str,
loader: Loader<'b>,
lexer: Lexer<'a>,
arena: &'a Arena,
ctx: &'b mut ParserCtx,
arena: &'b Arena<'a>,
token: Token,
symbols: &'b mut Symbols,
stack: &'b mut StackAlloc,
ns_bound: usize,
trailing_sep: bool,
packed: bool,
idents: Vec<ScopeIdent>,
captured: Vec<Ident>,
}
impl<'a, 'b> Parser<'a, 'b> {
pub fn parse(
ctx: &'b mut ParserCtx,
input: &'a str,
path: &'b str,
pub fn new(
arena: &'b Arena<'a>,
symbols: &'b mut Symbols,
stack: &'b mut StackAlloc,
loader: Loader<'b>,
arena: &'a Arena,
) -> &'a [Expr<'a>] {
let mut lexer = Lexer::new(input);
) -> Self {
let mut lexer = Lexer::new("");
Self {
loader,
token: lexer.next(),
lexer,
path,
ctx,
path: "",
arena,
symbols,
stack,
ns_bound: 0,
trailing_sep: false,
packed: false,
idents: Vec::new(),
captured: Vec::new(),
}
.file()
}
fn file(&mut self) -> &'a [Expr<'a>] {
pub fn file(&mut self, input: &'a str, path: &'b str) -> &'a [Expr<'a>] {
self.path = path;
self.lexer = Lexer::new(input);
self.token = self.lexer.next();
let f = self.collect_list(TokenKind::Semi, TokenKind::Eof, |s| s.expr_low(true));
self.pop_scope(0);
let mut errors = String::new();
for id in self.idents.drain(..) {
report_to(
self.lexer.source(),
self.path,
ident::pos(id.ident),
format_args!("undeclared identifier: {}", self.lexer.slice(ident::range(id.ident))),
&mut errors,
);
}
if !self.ctx.idents.is_empty() {
if !errors.is_empty() {
// TODO: we need error recovery
log::error!("{}", {
let mut errors = String::new();
for id in self.ctx.idents.drain(..) {
report_to(
self.lexer.source(),
self.path,
ident::pos(id.ident),
format_args!(
"undeclared identifier: {}",
self.lexer.slice(ident::range(id.ident))
),
&mut errors,
);
}
errors
});
log::error!("{errors}");
unreachable!();
}
@ -150,20 +153,36 @@ impl<'a, 'b> Parser<'a, 'b> {
break;
}
let checkpoint = self.token.start;
let op = self.next().kind;
if op == TokenKind::Decl {
self.declare_rec(&fold, top_level);
}
let op_ass = op.ass_op().map(|op| {
// this abomination reparses the left side, so that the desubaring adheres to the
// parser invariants.
let source = self.lexer.slice(0..checkpoint as usize);
let prev_lexer =
core::mem::replace(&mut self.lexer, Lexer::restore(source, fold.pos()));
let prev_token = core::mem::replace(&mut self.token, self.lexer.next());
let clone = self.expr();
self.lexer = prev_lexer;
self.token = prev_token;
(op, clone)
});
let right = self.unit_expr();
let right = self.bin_expr(right, prec, false);
let right = self.arena.alloc(right);
let left = self.arena.alloc(fold);
if let Some(op) = op.ass_op() {
if let Some((op, clone)) = op_ass {
self.flag_idents(*left, idfl::MUTABLE);
let right = Expr::BinOp { left: self.arena.alloc(fold), op, right };
let right = Expr::BinOp { left: self.arena.alloc(clone), op, right };
fold = Expr::BinOp { left, op: TokenKind::Assign, right: self.arena.alloc(right) };
} else {
fold = Expr::BinOp { left, right, op };
@ -201,15 +220,15 @@ impl<'a, 'b> Parser<'a, 'b> {
);
}
let index = self.ctx.idents.binary_search_by_key(&id, |s| s.ident).expect("fck up");
if core::mem::replace(&mut self.ctx.idents[index].declared, true) {
let index = self.idents.binary_search_by_key(&id, |s| s.ident).expect("fck up");
if core::mem::replace(&mut self.idents[index].declared, true) {
self.report(
pos,
format_args!("redeclaration of identifier: {}", self.lexer.slice(ident::range(id))),
)
}
self.ctx.idents[index].ordered = ordered;
self.idents[index].ordered = ordered;
}
fn resolve_ident(&mut self, token: Token) -> (Ident, bool) {
@ -221,7 +240,6 @@ impl<'a, 'b> Parser<'a, 'b> {
}
let (i, id, bl) = match self
.ctx
.idents
.iter_mut()
.enumerate()
@ -230,20 +248,20 @@ impl<'a, 'b> Parser<'a, 'b> {
Some((i, elem)) => (i, elem, false),
None => {
let id = ident::new(token.start, name.len() as _);
self.ctx.idents.push(ScopeIdent {
self.idents.push(ScopeIdent {
ident: id,
declared: false,
ordered: false,
flags: 0,
});
(self.ctx.idents.len() - 1, self.ctx.idents.last_mut().unwrap(), true)
(self.idents.len() - 1, self.idents.last_mut().unwrap(), true)
}
};
id.flags |= idfl::COMPTIME * is_ct as u32;
if id.declared && id.ordered && self.ns_bound > i {
id.flags |= idfl::COMPTIME;
self.ctx.captured.push(id.ident);
self.captured.push(id.ident);
}
(id.ident, bl)
@ -255,22 +273,21 @@ impl<'a, 'b> Parser<'a, 'b> {
fn unit_expr(&mut self) -> Expr<'a> {
use {Expr as E, TokenKind as T};
let frame = self.ctx.idents.len();
let frame = self.idents.len();
let token @ Token { start: pos, .. } = self.next();
let prev_boundary = self.ns_bound;
let prev_captured = self.ctx.captured.len();
let prev_captured = self.captured.len();
let mut expr = match token.kind {
T::Ct => E::Ct { pos, value: self.ptr_expr() },
T::Directive if self.lexer.slice(token.range()) == "use" => {
self.expect_advance(TokenKind::LParen);
let str = self.expect_advance(TokenKind::DQuote);
self.expect_advance(TokenKind::RParen);
let path = self.lexer.slice(str.range());
let path = &path[1..path.len() - 1];
let path = self.lexer.slice(str.range()).trim_matches('"');
E::Mod {
pos,
path,
path: self.arena.alloc_str(path),
id: match (self.loader)(path, self.path) {
Ok(id) => id,
Err(e) => {
@ -306,7 +323,7 @@ impl<'a, 'b> Parser<'a, 'b> {
T::Struct => E::Struct {
packed: core::mem::take(&mut self.packed),
fields: {
self.ns_bound = self.ctx.idents.len();
self.ns_bound = self.idents.len();
self.expect_advance(T::LBrace);
self.collect_list(T::Comma, T::RBrace, |s| {
let tok = s.token;
@ -325,23 +342,15 @@ impl<'a, 'b> Parser<'a, 'b> {
},
captured: {
self.ns_bound = prev_boundary;
let mut captured = &mut self.ctx.captured[prev_captured..];
while let Some(it) = captured.take_first_mut() {
for ot in &mut *captured {
if it > ot {
core::mem::swap(it, ot);
}
}
}
debug_assert!(captured.is_sorted());
let preserved = self.ctx.captured[prev_captured..].partition_dedup().0.len();
self.ctx.captured.truncate(prev_captured + preserved);
self.arena.alloc_slice(&self.ctx.captured[prev_captured..])
self.captured[prev_captured..].sort_unstable();
let preserved = self.captured[prev_captured..].partition_dedup().0.len();
self.captured.truncate(prev_captured + preserved);
self.arena.alloc_slice(&self.captured[prev_captured..])
},
pos: {
if self.ns_bound == 0 {
// we might save some memory
self.ctx.captured.clear();
self.captured.clear();
}
pos
},
@ -418,9 +427,9 @@ impl<'a, 'b> Parser<'a, 'b> {
T::Number => {
let slice = self.lexer.slice(token.range());
let (slice, radix) = match &slice.get(0..2) {
Some("0x") => (&slice[2..], Radix::Hex),
Some("0b") => (&slice[2..], Radix::Binary),
Some("0o") => (&slice[2..], Radix::Octal),
Some("0x") => (slice.trim_start_matches("0x"), Radix::Hex),
Some("0b") => (slice.trim_start_matches("0b"), Radix::Binary),
Some("0o") => (slice.trim_start_matches("0o"), Radix::Octal),
_ => (slice, Radix::Decimal),
};
E::Number {
@ -438,7 +447,7 @@ impl<'a, 'b> Parser<'a, 'b> {
expr
}
T::Comment => Expr::Comment { pos, literal: self.tok_str(token) },
tok => self.report(token.start, format_args!("unexpected token: {tok}")),
tok => self.report(token.start, format_args!("unexpected token: {tok:?}")),
};
loop {
@ -519,25 +528,24 @@ impl<'a, 'b> Parser<'a, 'b> {
} else {
self.report(
self.token.start,
format_args!("expected identifier, found {}", self.token.kind),
format_args!("expected identifier, found {:?}", self.token.kind),
)
}
}
fn pop_scope(&mut self, frame: usize) {
let mut undeclared_count = frame;
for i in frame..self.ctx.idents.len() {
if !&self.ctx.idents[i].declared {
self.ctx.idents.swap(i, undeclared_count);
for i in frame..self.idents.len() {
if !&self.idents[i].declared {
self.idents.swap(i, undeclared_count);
undeclared_count += 1;
}
}
self.ctx
.idents
self.idents
.drain(undeclared_count..)
.map(|ident| Symbol { name: ident.ident, flags: ident.flags })
.collect_into(&mut self.ctx.symbols);
.collect_into(self.symbols);
}
fn ptr_unit_expr(&mut self) -> &'a Expr<'a> {
@ -550,13 +558,13 @@ impl<'a, 'b> Parser<'a, 'b> {
end: TokenKind,
mut f: impl FnMut(&mut Self) -> T,
) -> &'a [T] {
let mut view = self.ctx.stack.view();
let mut view = self.stack.view();
while !self.advance_if(end) {
let val = f(self);
self.trailing_sep = self.advance_if(delim);
unsafe { self.ctx.stack.push(&mut view, val) };
unsafe { self.stack.push(&mut view, val) };
}
self.arena.alloc_slice(unsafe { self.ctx.stack.finalize(view) })
self.arena.alloc_slice(unsafe { self.stack.finalize(view) })
}
fn advance_if(&mut self, kind: TokenKind) -> bool {
@ -572,7 +580,7 @@ impl<'a, 'b> Parser<'a, 'b> {
if self.token.kind != kind {
self.report(
self.token.start,
format_args!("expected {}, found {}", kind, self.token.kind),
format_args!("expected {:?}, found {:?}", kind, self.token.kind),
);
}
self.next()
@ -580,17 +588,15 @@ impl<'a, 'b> Parser<'a, 'b> {
#[track_caller]
fn report(&self, pos: Pos, msg: impl fmt::Display) -> ! {
log::error!("{}", {
let mut str = String::new();
report_to(self.lexer.source(), self.path, pos, msg, &mut str);
str
});
let mut str = String::new();
report_to(self.lexer.source(), self.path, pos, msg, &mut str);
log::error!("{str}");
unreachable!();
}
fn flag_idents(&mut self, e: Expr<'a>, flags: IdentFlags) {
match e {
Expr::Ident { id, .. } => find_ident(&mut self.ctx.idents, id).flags |= flags,
Expr::Ident { id, .. } => find_ident(&mut self.idents, id).flags |= flags,
Expr::Field { target, .. } => self.flag_idents(*target, flags),
_ => {}
}
@ -628,7 +634,7 @@ macro_rules! generate_expr {
$($field:ident: $ty:ty,)*
},
)*}) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
$vis enum $name<$lt> {$(
$(#[$field_meta])*
$variant {
@ -643,6 +649,17 @@ macro_rules! generate_expr {
$(Self::$variant { $($field),* } => generate_expr!(@first $(($field),)*).posi(),)*
}
}
pub fn used_bytes(&self) -> usize {
match self {$(
Self::$variant { $($field,)* } => {
#[allow(clippy::size_of_ref)]
let fields = [$(($field as *const _ as usize - self as *const _ as usize, core::mem::size_of_val($field)),)*];
let (last, size) = fields.iter().copied().max().unwrap();
last + size
},
)*}
}
}
};
@ -789,7 +806,6 @@ generate_expr! {
/// `Expr '.' Ident`
Field {
target: &'a Self,
// we put it second place because its the pos of '.'
pos: Pos,
name: &'a str,
},
@ -804,7 +820,7 @@ generate_expr! {
},
/// `'@' Ident List('(', ',', ')', Expr)`
Directive {
pos: Pos,
pos: u32,
name: &'a str,
args: &'a [Self],
},
@ -943,14 +959,6 @@ impl core::fmt::Display for Display<'_> {
}
}
#[derive(Default)]
pub struct ParserCtx {
symbols: Symbols,
stack: StackAlloc,
idents: Vec<ScopeIdent>,
captured: Vec<Ident>,
}
#[repr(C)]
pub struct AstInner<T: ?Sized> {
ref_count: AtomicUsize,
@ -970,18 +978,21 @@ impl AstInner<[Symbol]> {
.0
}
fn new(file: Box<str>, path: &str, ctx: &mut ParserCtx, loader: Loader) -> NonNull<Self> {
fn new(file: Box<str>, path: &str, stack: &mut StackAlloc, loader: Loader) -> NonNull<Self> {
let arena = Arena::default();
let mut syms = Vec::new();
let mut parser = Parser::new(&arena, &mut syms, stack, loader);
let exprs =
unsafe { core::mem::transmute(Parser::parse(ctx, &file, path, loader, &arena)) };
parser.file(unsafe { &*(&*file as *const _) }, path) as *const [Expr<'static>];
drop(parser);
ctx.symbols.sort_unstable_by_key(|s| s.name);
syms.sort_unstable_by_key(|s| s.name);
let layout = Self::layout(ctx.symbols.len());
let layout = Self::layout(syms.len());
unsafe {
let ptr = alloc::alloc::alloc(layout);
let inner: *mut Self = core::ptr::from_raw_parts_mut(ptr as *mut _, ctx.symbols.len());
let inner: *mut Self = core::ptr::from_raw_parts_mut(ptr as *mut _, syms.len());
core::ptr::write(inner as *mut AstInner<()>, AstInner {
ref_count: AtomicUsize::new(1),
@ -993,7 +1004,7 @@ impl AstInner<[Symbol]> {
});
core::ptr::addr_of_mut!((*inner).symbols)
.as_mut_ptr()
.copy_from_nonoverlapping(ctx.symbols.as_ptr(), ctx.symbols.len());
.copy_from_nonoverlapping(syms.as_ptr(), syms.len());
NonNull::new_unchecked(inner)
}
@ -1030,8 +1041,8 @@ pub fn report_to(
pub struct Ast(NonNull<AstInner<[Symbol]>>);
impl Ast {
pub fn new(path: &str, content: String, ctx: &mut ParserCtx, loader: Loader) -> Self {
Self(AstInner::new(content.into(), path, ctx, loader))
pub fn new(path: &str, content: String, stack: &mut StackAlloc, loader: Loader) -> Self {
Self(AstInner::new(content.into(), path, stack, loader))
}
pub fn exprs(&self) -> &[Expr] {
@ -1056,7 +1067,7 @@ impl Ast {
impl Default for Ast {
fn default() -> Self {
Self(AstInner::new("".into(), "", &mut ParserCtx::default(), &no_loader))
Self(AstInner::new("".into(), "", &mut StackAlloc::default(), &no_loader))
}
}
@ -1121,13 +1132,13 @@ impl Deref for Ast {
}
}
struct StackAllocView<T> {
pub struct StackAllocView<T> {
prev: usize,
base: usize,
_ph: PhantomData<T>,
}
struct StackAlloc {
pub struct StackAlloc {
data: *mut u8,
len: usize,
cap: usize,
@ -1192,22 +1203,29 @@ impl Drop for StackAlloc {
}
#[derive(Default)]
pub struct Arena {
pub struct Arena<'a> {
chunk: UnsafeCell<ArenaChunk>,
ph: core::marker::PhantomData<&'a ()>,
}
impl Arena {
pub fn alloc<'a>(&'a self, expr: Expr<'a>) -> &'a Expr<'a> {
let layout = core::alloc::Layout::new::<Expr<'a>>();
impl<'a> Arena<'a> {
pub fn alloc_str(&self, token: &str) -> &'a str {
let ptr = self.alloc_slice(token.as_bytes());
unsafe { core::str::from_utf8_unchecked(ptr) }
}
pub fn alloc(&self, expr: Expr<'a>) -> &'a Expr<'a> {
let align = core::mem::align_of::<Expr<'a>>();
let size = expr.used_bytes();
let layout = unsafe { core::alloc::Layout::from_size_align_unchecked(size, align) };
let ptr = self.alloc_low(layout);
unsafe {
ptr.cast::<u64>()
.copy_from_nonoverlapping(NonNull::from(&expr).cast(), layout.size() / 8)
ptr.cast::<u64>().copy_from_nonoverlapping(NonNull::from(&expr).cast(), size / 8)
};
unsafe { ptr.cast::<Expr<'a>>().as_ref() }
}
pub fn alloc_slice<'a, T: Copy>(&'a self, slice: &[T]) -> &'a [T] {
pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &'a [T] {
if slice.is_empty() || core::mem::size_of::<T>() == 0 {
return &mut [];
}
@ -1248,7 +1266,7 @@ impl Default for ArenaChunk {
}
impl ArenaChunk {
const ALIGN: usize = 16;
const ALIGN: usize = core::mem::align_of::<Self>();
const CHUNK_SIZE: usize = 1 << 16;
const LAYOUT: core::alloc::Layout =
unsafe { core::alloc::Layout::from_size_align_unchecked(Self::CHUNK_SIZE, Self::ALIGN) };

View file

@ -9,7 +9,7 @@ use {
idfl::{self},
Expr, ExprRef, FileId, Pos,
},
reg, task,
task,
ty::{self},
vc::{BitSet, Vc},
Func, HashMap, Offset, OffsetIter, Reloc, Sig, Size, SymKey, TypedReloc, Types,
@ -34,6 +34,15 @@ const MEM: Nid = 3;
type Nid = u16;
pub mod reg {
pub const STACK_PTR: Reg = 254;
pub const ZERO: Reg = 0;
pub const RET: Reg = 1;
pub const RET_ADDR: Reg = 31;
pub type Reg = u8;
}
type Lookup = crate::ctx_map::CtxMap<Nid>;
impl crate::ctx_map::CtxEntry for Nid {