instantly refreshable code. untested, but it compiles

This commit is contained in:
Shautvast 2025-11-06 20:52:09 +01:00
parent f1f32af113
commit 24a852f125
9 changed files with 193 additions and 40 deletions

96
Cargo.lock generated
View file

@ -154,6 +154,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.4"
@ -291,12 +297,14 @@ dependencies = [
name = "crudlang"
version = "0.1.0"
dependencies = [
"arc-swap",
"axum",
"chrono",
"clap",
"dotenv",
"log",
"log4rs",
"notify",
"reqwest",
"serde",
"thiserror",
@ -449,6 +457,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@ -843,6 +860,26 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inotify"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.4",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@ -881,6 +918,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -899,7 +956,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"libc",
"redox_syscall",
]
@ -1014,6 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@ -1041,6 +1099,30 @@ dependencies = [
"tempfile",
]
[[package]]
name = "notify"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.4",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.60.2",
]
[[package]]
name = "notify-types"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@ -1077,7 +1159,7 @@ version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"cfg-if",
"foreign-types",
"libc",
@ -1296,7 +1378,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.9.4",
]
[[package]]
@ -1361,7 +1443,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"errno",
"libc",
"linux-raw-sys",
@ -1443,7 +1525,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"core-foundation",
"core-foundation-sys",
"libc",
@ -1678,7 +1760,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"core-foundation",
"system-configuration-sys",
]
@ -1879,7 +1961,7 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bitflags 2.9.4",
"bytes",
"futures-core",
"futures-util",

View file

@ -23,3 +23,5 @@ walkdir = "2.5.0"
thiserror = "2.0.17"
url = "2.5.7"
clap = { version = "4.5.51", features = ["derive"] }
notify = "8.2.0"
arc-swap = "1.7.1"

View file

@ -2,4 +2,4 @@ fn get(path: string) -> string:
add("hello", path)
fn add(a: string, b: string) -> string:
a + " " + b
a + " " + b

View file

@ -5,11 +5,11 @@ use crate::errors::CompilerError::{
};
use crate::errors::CompilerErrorAtLine;
use crate::tokens::TokenType::{
Bang, Bool, Char, Colon, Date, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater,
GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, LeftBracket,
LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print,
RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, StringType,
True, U32, U64, UnsignedInteger,
Bang, Bool, Char, Colon, Date, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater,
Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess,
Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SignedInteger,
SingleRightArrow, Slash, Star, StringType, True, UnsignedInteger, F32, F64,
I32, I64, U32, U64,
};
use crate::tokens::{Token, TokenType};
use crate::value::Value;

54
src/file_watch.rs Normal file
View file

@ -0,0 +1,54 @@
use crate::chunk::Chunk;
use crate::compile_sourcedir;
use log4rs::append::Append;
use notify::{RecursiveMode, Watcher};
use std::collections::HashMap;
use std::hash::Hash;
use std::path::Path;
use std::sync::Arc;
use std::sync::mpsc::channel;
use std::thread;
use std::time::{Duration, SystemTime};
use arc_swap::ArcSwap;
const ONE_SEC: Duration = Duration::from_secs(1);
pub fn start_watch_daemon(source: &str, registry: Arc<ArcSwap<HashMap<String, Chunk>>>) {
let source = source.to_string();
let s = source.to_string();
let (tx, rx) = channel();
thread::spawn(move || {
println!("-- File watch started --");
let path = Path::new(&source);
if !path.exists() {
panic!("source directory {} does not exist", &source);
}
let mut watcher = notify::recommended_watcher(tx).expect("Failed to create watcher");
watcher
.watch(path, RecursiveMode::Recursive)
.expect("Failed to watch");
loop {
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let mut file_changed = false;
loop {
let start = SystemTime::now();
loop {
thread::sleep(Duration::from_millis(50));
if let Ok(_) = rx.recv() {
file_changed = true;
}
if file_changed && SystemTime::now().duration_since(start).unwrap() > ONE_SEC {
break;
}
}
println!("refresh"); // TODO implement refresh source
let new_registry = Arc::new(compile_sourcedir(&s).unwrap());
registry.store(new_registry.clone());
file_changed = false;
}
});
}

View file

@ -6,6 +6,8 @@ use crate::value::Value;
use crate::vm::interpret;
use std::collections::HashMap;
use std::fs;
use std::sync::Arc;
use arc_swap::ArcSwap;
use walkdir::WalkDir;
pub mod ast_compiler;
@ -19,6 +21,7 @@ mod tokens;
mod value;
pub mod vm;
pub mod repl;
pub mod file_watch;
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, CrudLangError> {
let mut registry = HashMap::new();
@ -26,12 +29,13 @@ pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, Cru
for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path().to_str().unwrap();
if path.ends_with(".crud") {
print!("compiling {:?}: ", path);
print!("-- Compiling ");
let source = fs::read_to_string(path).map_err(map_underlying())?;
let tokens = scan(&source)?;
match ast_compiler::compile(Some(&path), tokens) {
Ok(statements) => {
let path = path.strip_prefix("source/").unwrap().replace(".crud", "");
println!("{}",path);
let path = path.strip_prefix(source_dir).unwrap().replace(".crud", "");
bytecode_compiler::compile(Some(&path), &statements, &mut registry)?;
}
Err(e) => {
@ -62,5 +66,6 @@ fn run(src: &str) -> Result<Value, CrudLangError> {
let mut registry = HashMap::new();
let ast = ast_compiler::compile(None, tokens)?;
bytecode_compiler::compile(None, &ast, &mut registry)?;
interpret(&registry, "main").map_err(CrudLangError::from)
let registry = ArcSwap::from(Arc::new(registry));
interpret(registry.load(), "main").map_err(CrudLangError::from)
}

View file

@ -8,8 +8,10 @@ use crudlang::errors::CrudLangError;
use crudlang::errors::CrudLangError::Platform;
use crudlang::vm::interpret_async;
use crudlang::{compile_sourcedir, map_underlying};
use notify::Watcher;
use std::collections::HashMap;
use std::sync::Arc;
use arc_swap::ArcSwap;
/// A simple CLI tool to greet users
#[derive(Parser, Debug)]
@ -19,6 +21,9 @@ struct Args {
#[arg(short, long)]
source: Option<String>,
#[arg(short, long)]
watch: bool,
}
#[tokio::main]
@ -26,17 +31,19 @@ async fn main() -> Result<(), CrudLangError> {
println!("-- Crudlang --");
tracing_subscriber::fmt::init();
let args = Args::parse();
let source = args.source.unwrap_or("source".to_string());
let source = args.source.unwrap_or("./source".to_string());
let registry = compile_sourcedir(&source)?;
let registry = Arc::new(registry);
if !registry.is_empty() {
println!("-- Compilation successful -- Starting server --");
let state = Arc::new(AppState {
registry: registry.clone(),
});
let swap = Arc::new(ArcSwap::from(Arc::new(registry)));
if args.watch {
crudlang::file_watch::start_watch_daemon(&source, swap.clone());
}
println!("-- Compilation successful --");
let state =AppState {
registry: swap.clone(),
};
let app = Router::new()
.route("/", any(handle_any).with_state(state.clone()))
.route("/{*path}", any(handle_any).with_state(state.clone()));
@ -46,12 +53,12 @@ async fn main() -> Result<(), CrudLangError> {
.map_err(map_underlying())?;
println!(
"listening on {}\n",
"-- Listening on {} --\n",
listener.local_addr().map_err(map_underlying())?
);
if args.repl {
std::thread::spawn(move || crudlang::repl::start(registry).unwrap());
std::thread::spawn(move || crudlang::repl::start(swap.load()).unwrap());
}
axum::serve(listener, app).await.map_err(map_underlying())?;
@ -65,11 +72,11 @@ async fn main() -> Result<(), CrudLangError> {
#[derive(Clone)]
struct AppState {
registry: Arc<HashMap<String, Chunk>>,
registry: Arc<ArcSwap<HashMap<String, Chunk>>>,
}
async fn handle_any(
State(state): State<Arc<AppState>>,
State(state): State<AppState>,
req: Request,
) -> Result<Json<String>, StatusCode> {
let method = req.method().to_string().to_ascii_lowercase();
@ -84,7 +91,7 @@ async fn handle_any(
.collect()
})
.unwrap_or_default();
let component = format!("{}/web", &uri.path()[1..]);
let component = format!("{}/web", &uri.path());
let function_qname = format!("{}.{}", component, method);
let mut headers = HashMap::new();
@ -93,7 +100,7 @@ async fn handle_any(
}
let path = &req.uri().to_string();
match interpret_async(
&state.registry,
state.registry.load(),
&function_qname,
path,
query_params,
@ -104,7 +111,7 @@ async fn handle_any(
Ok(value) => Ok(Json(value.to_string())),
Err(e) => {
// url checks out but function for method not found
if state.registry.get(&format!("{}.main", component)).is_some() {
if state.registry.load().get(&format!("{}.main", component)).is_some() {
Err(StatusCode::METHOD_NOT_ALLOWED)
} else {
Err(StatusCode::NOT_FOUND)

View file

@ -5,8 +5,9 @@ use std::collections::HashMap;
use std::io;
use std::io::Write;
use std::sync::Arc;
use arc_swap::{ArcSwap, Guard};
pub fn start(registry: Arc<HashMap<String, Chunk>>) -> Result<(), CrudLangError> {
pub fn start(registry: Guard<Arc<HashMap<String, Chunk>>>) -> Result<(), CrudLangError> {
println!("REPL started -- Type ctrl-c to exit (both the repl and the server)");
println!(":h for help");
loop {
@ -20,7 +21,7 @@ pub fn start(registry: Arc<HashMap<String, Chunk>>) -> Result<(), CrudLangError>
match input {
":h" => help(),
":le" => list_endpoints(registry.clone()),
":lf" => list_functions(registry.clone()),
":lf" => list_endpoints(registry.clone()),
_ => {}
}
// println!("[{}]",input);

View file

@ -5,17 +5,19 @@ use crate::tokens::TokenType;
use crate::value::Value;
use axum::http::{Uri};
use std::collections::HashMap;
use std::sync::Arc;
use arc_swap::Guard;
use tracing::debug;
pub struct Vm<'a> {
pub struct Vm {
ip: usize,
stack: Vec<Value>,
local_vars: HashMap<String, Value>,
error_occurred: bool,
registry: &'a HashMap<String, Chunk>,
registry: Arc<HashMap<String, Chunk>>,
}
pub fn interpret(registry: &HashMap<String, Chunk>, function: &str) -> Result<Value, RuntimeError> {
pub fn interpret(registry: Guard<Arc<HashMap<String, Chunk>>>, function: &str) -> Result<Value, RuntimeError> {
let chunk = registry.get(function).unwrap().clone();
// for (key,value) in registry.iter() {
// println!("{}", key);
@ -26,13 +28,13 @@ pub fn interpret(registry: &HashMap<String, Chunk>, function: &str) -> Result<Va
stack: vec![],
local_vars: HashMap::new(),
error_occurred: false,
registry,
registry: registry.clone(),
};
vm.run(&chunk)
}
pub async fn interpret_async(
registry: &HashMap<String, Chunk>,
registry: Guard<Arc<HashMap<String, Chunk>>>,
function: &str,
uri: &str,
query_params: HashMap<String, String>,
@ -46,7 +48,7 @@ pub async fn interpret_async(
stack: vec![],
local_vars: HashMap::new(),
error_occurred: false,
registry,
registry:registry.clone(),
};
vm.local_vars
.insert("path".to_string(), Value::String(uri.into()));
@ -73,13 +75,13 @@ pub fn interpret_function(chunk: &Chunk, args: Vec<Value>) -> Result<Value, Runt
stack: vec![],
local_vars: HashMap::new(),
error_occurred: false,
registry: &HashMap::new(),
registry: Arc::new(HashMap::new()),
};
vm.run_function(chunk, args)
}
impl<'a> Vm<'a> {
impl Vm {
fn run_function(&mut self, chunk: &Chunk, mut args: Vec<Value>) -> Result<Value, RuntimeError> {
// arguments -> locals
for (_, name) in chunk.vars.iter() {