From d856452c763c5f46ecf2720ea9b2ca1fd1727880 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Wed, 29 Oct 2025 17:09:02 +0100 Subject: [PATCH] before better function hierarchy --- Cargo.lock | 29 ++++++++ Cargo.toml | 1 + README.md | 19 +++-- source/hello/web.crud | 5 ++ src/ast_compiler.rs | 146 +++++++++++++++++++++++++++------------ src/bytecode_compiler.rs | 3 +- src/chunk.rs | 13 ++-- src/lib.rs | 2 +- src/main.rs | 88 +++++++++++++++++------ src/vm.rs | 4 +- 10 files changed, 229 insertions(+), 81 deletions(-) create mode 100644 source/hello/web.crud diff --git a/Cargo.lock b/Cargo.lock index 480d129..85a9fa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,7 @@ dependencies = [ "tower-livereload", "tracing", "tracing-subscriber", + "walkdir", ] [[package]] @@ -1300,6 +1301,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.28" @@ -1980,6 +1990,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2128,6 +2148,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 67360ba..7aca3cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ anyhow = "1.0" tower-livereload = "0.9.6" log = "0.4.28" bumpalo = { version = "3.19", features = ["collections", "boxed", "serde"] } +walkdir = "2.5.0" diff --git a/README.md b/README.md index 10c8f65..ba901df 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # crud-lang ## Why? -1. Existing languages are great, but building web services is bolted on, instead of supported out-of-the-box. +1. Existing languages are just fine, but building web services is bolted on, instead of supported out-of-the-box. 2. Whereas every company needs an API these days. -3. I always have trouble mapping urls the code that handles them. +3. Is it just me? -I always have trouble mapping urls the code that handles them. 4. There is no language (AFAIK) that supports layering. (controllers, services, database access, etc). This pattern is ubiquitous (at least where I live). 5. ORM's are awful. Mapping from sql rows to objects is a pain. This should be easy. 6. Json is ubiquitous. Convention over configuration: A controller returns json by default. @@ -33,7 +33,7 @@ ### An interpreter written in Rust. OMG! -And it has everything I like in other languages +And I cherry picked things I like, mostly from rust and python. - strictly typed - [] is a list - {} is a map @@ -41,7 +41,7 @@ And it has everything I like in other languages - structs and duck typing - everything is an expression - nice iterators. - - First class functions? Maybe... + - First-class functions? Maybe... - automatic mapping from database to object to json - indenting like python @@ -63,12 +63,11 @@ And it has everything I like in other languages - a very simple api that listens to GET /api/customers{:id} and returns a customer from the database ## Design -* heavily inspired by Crafting Interpreters -* language influences from rust and python +* heavily inspired by Crafting Interpreters. * compiler first creates an AST and then compiles to bytecode (no file format yet) * uses a stack-based virtual machine -## Current status: +## Current status: infancy * compiler and runtime are still limited but working * supports: * basic types: @@ -91,6 +90,12 @@ And it has everything I like in other languages - control flow - tests +## What about performance? +* Clueless really! We'll see. +* But it is written in rust +* And it has no GC +* So, maybe it will compete with python? + ## A quick taste **variables** ``` diff --git a/source/hello/web.crud b/source/hello/web.crud new file mode 100644 index 0000000..9677652 --- /dev/null +++ b/source/hello/web.crud @@ -0,0 +1,5 @@ +fn get() -> string: + add("hello", "world") + +fn add(a: string, b: string) -> string: + a + b diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 20a255b..b277030 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -1,4 +1,9 @@ -use crate::tokens::TokenType::{Bang, Bool, Char, Colon, Date, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, If, Indent, Integer, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, RightBracket}; +use crate::tokens::TokenType::{ + Bang, Bool, Char, Colon, Date, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater, + GreaterEqual, GreaterGreater, I32, I64, Identifier, If, Indent, Integer, LeftBracket, + LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, + RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, +}; use crate::tokens::{Token, TokenType}; use crate::value::Value; use log::debug; @@ -6,7 +11,7 @@ use std::collections::HashMap; pub fn compile(tokens: Vec) -> anyhow::Result> { let mut compiler = AstCompiler::new(tokens); - compiler.compile(0) + compiler.compile_tokens(0) } #[derive(Debug, Clone)] @@ -38,17 +43,84 @@ impl AstCompiler { } } - fn compile(&mut self, expected_indent: usize) -> anyhow::Result> { - let mut statements = vec![]; + fn reset(&mut self) { + self.current = 0; + } + + fn compile_tokens(&mut self, expected_indent: usize) -> anyhow::Result> { + self.collect_functions()?; + self.reset(); + self.compile(expected_indent) + } + + fn compile(&mut self, expected_indent: usize) -> anyhow::Result>{ + if !self.had_error { + let mut statements = vec![]; + while !self.is_at_end() { + let statement = self.indent(expected_indent)?; + if let Some(statement) = statement { + statements.push(statement); + } else { + break; + } + } + Ok(statements) + } else { + Err(anyhow::anyhow!("Compilation failed.")) + } + } + + fn collect_functions(&mut self) -> anyhow::Result<()> { while !self.is_at_end() { - let statement = self.indent(expected_indent)?; - if let Some(statement) = statement { - statements.push(statement); + if self.match_token(vec![Fn]) { + let name_token = self.consume(Identifier, "Expect function name.")?; + self.consume(LeftParen, "Expect '(' after function name.")?; + let mut parameters = vec![]; + while !self.check(RightParen) { + if parameters.len() >= 25 { + return Err(anyhow::anyhow!("Too many parameters.")); + } + let parm_name = self.consume(Identifier, "Expect parameter name.")?; + + self.consume(Colon, "Expect : after parameter name")?; + let var_type = self.peek().token_type; + self.vars.push(Expression::Variable { + name: parm_name.lexeme.to_string(), + var_type, + line: parm_name.line, + }); + self.advance(); + parameters.push(Parameter { + name: parm_name, + var_type, + }); + if self.peek().token_type == TokenType::Comma { + self.advance(); + } + } + self.consume(RightParen, "Expect ')' after parameters.")?; + let return_type = if self.check(SingleRightArrow) { + self.consume(SingleRightArrow, "")?; + self.advance().token_type + } else { + TokenType::Void + }; + self.consume(Colon, "Expect colon (:) after function declaration.")?; + self.consume(Eol, "Expect end of line.")?; + + let function = Function { + name: name_token.clone(), + parameters, + return_type, + body: vec![], + }; + + self.functions.insert(name_token.lexeme, function); } else { - break; + self.advance(); } } - Ok(statements) + Ok(()) } fn indent(&mut self, expected_indent: usize) -> anyhow::Result> { @@ -89,50 +161,25 @@ impl AstCompiler { fn function_declaration(&mut self) -> anyhow::Result { let name_token = self.consume(Identifier, "Expect function name.")?; self.consume(LeftParen, "Expect '(' after function name.")?; - let mut parameters = vec![]; while !self.check(RightParen) { - if parameters.len() >= 25 { - return Err(anyhow::anyhow!("Too many parameters.")); - } - let parm_name = self.consume(Identifier, "Expect parameter name.")?; - - self.consume(Colon, "Expect : after parameter name")?; - let var_type = self.peek().token_type; - self.vars.push(Expression::Variable { - name: parm_name.lexeme.to_string(), - var_type, - line: parm_name.line, - }); self.advance(); - parameters.push(Parameter { - name: parm_name, - var_type, - }); } + self.consume(RightParen, "Expect ')' after parameters.")?; - let return_type = if self.check(SingleRightArrow) { - self.consume(SingleRightArrow, "")?; - self.advance().token_type - } else { - TokenType::Void - }; - self.consume(Colon, "Expect colon (:) after function declaration.")?; + while !self.check(Colon) { + self.advance(); + } + self.consume(Colon, "2Expect colon (:) after function declaration.")?; self.consume(Eol, "Expect end of line.")?; let current_indent = self.indent.last().unwrap(); let body = self.compile(current_indent + 1)?; - let function = Function { - name: name_token.clone(), - parameters, - return_type, - body, - }; + self.functions.get_mut(&name_token.lexeme).unwrap().body = body; let function_stmt = Statement::FunctionStmt { - function: function.clone(), + function: self.functions.get(&name_token.lexeme).unwrap().clone(), }; - self.functions.insert(name_token.lexeme, function.clone()); Ok(function_stmt) } @@ -334,7 +381,7 @@ impl AstCompiler { fn list(&mut self) -> anyhow::Result { let mut list = vec![]; - while !self.match_token(vec![RightBracket]){ + while !self.match_token(vec![RightBracket]) { list.push(self.expression()?); if self.peek().token_type == TokenType::Comma { self.advance(); @@ -344,8 +391,10 @@ impl AstCompiler { } } Ok(Expression::List { - values: list, literaltype: ListType, line: self.peek().line}, - ) + values: list, + literaltype: ListType, + line: self.peek().line, + }) } fn variable_lookup(&mut self, token: &Token) -> anyhow::Result { @@ -369,6 +418,7 @@ impl AstCompiler { } fn function_call(&mut self, name: String) -> anyhow::Result { + println!("function call {}", name); let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); let function = self.functions.get(&function_name).unwrap().clone(); @@ -408,7 +458,11 @@ impl AstCompiler { self.advance(); } else { self.had_error = true; - return Err(anyhow::anyhow!(message.to_string())); + return Err(anyhow::anyhow!( + "{} at {:?}", + message.to_string(), + self.peek() + )); } Ok(self.previous().clone()) } @@ -640,7 +694,7 @@ impl Expression { } } } - Self::Grouping { expression,.. } => expression.infer_type(), + Self::Grouping { expression, .. } => expression.infer_type(), Self::Literal { literaltype, .. } => literaltype.clone(), Self::List { literaltype, .. } => literaltype.clone(), Self::Unary { right, .. } => right.infer_type(), diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index eba569c..88a82e0 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -12,7 +12,7 @@ use crate::vm::{ use std::collections::HashMap; pub fn compile(ast: &Vec) -> anyhow::Result { - compile_name(ast, "_") + compile_name(ast, "/") } pub(crate) fn compile_function(function: &Function) -> anyhow::Result { @@ -98,6 +98,7 @@ impl Compiler { Expression::FunctionCall { name, arguments, .. } => { + println!("call {}",name); let function_index = *self.functions.get(name).unwrap(); for argument in arguments { self.compile_expression(argument)?; diff --git a/src/chunk.rs b/src/chunk.rs index a8dd006..27528fe 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use crate::value::Value; use crate::vm::{ OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_BOOL, OP_DEF_F32, @@ -6,12 +7,14 @@ use crate::vm::{ OP_NOT, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, }; +#[derive(Debug, Clone)] pub struct Chunk { name: String, pub code: Vec, pub constants: Vec, lines: Vec, - pub(crate) functions: Vec, + pub functions: HashMap, + pub(crate) functions_by_index: Vec, } impl Chunk { @@ -21,7 +24,8 @@ impl Chunk { code: Vec::new(), constants: vec![], lines: vec![], - functions: Vec::new(), + functions: HashMap::new(), + functions_by_index: Vec::new(), } } @@ -36,12 +40,13 @@ impl Chunk { } pub fn add_function(&mut self, function: Chunk) -> usize { - self.functions.push(function); + self.functions_by_index.push(function.clone()); + self.functions.insert(function.name.to_string(), function); self.functions.len() - 1 } pub fn disassemble(&self) { - for f in &self.functions { + for f in self.functions.values() { f.disassemble(); } println!("== {} ==", self.name); diff --git a/src/lib.rs b/src/lib.rs index df89024..fd2c69b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod scanner; pub mod ast_compiler; pub mod bytecode_compiler; pub mod vm; -mod chunk; +pub mod chunk; mod keywords; mod tokens; mod value; diff --git a/src/main.rs b/src/main.rs index bbd5408..6673dca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,79 @@ +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::routing::get; +use axum::{Json, Router}; use crudlang::ast_compiler; use crudlang::bytecode_compiler::compile; +use crudlang::chunk::Chunk; use crudlang::scanner::scan; use crudlang::vm::interpret; +use std::collections::HashMap; +use std::fs; +use std::sync::Arc; +use walkdir::WalkDir; -fn main() -> anyhow::Result<()> { +#[tokio::main] +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); - let tokens = scan( - r#" -fn main(a: list) -> list: - a + 42 - -let text:list = ["hello "] -main(text)"#, - ); - println!("{:?}", tokens); - match ast_compiler::compile(tokens) { - Ok(statements) => { - println!("{:?}", statements); - let chunk = compile(&statements)?; - chunk.disassemble(); - println!("{}",interpret(&chunk)?); - } - Err(e) => { - println!("{}", e) + let mut paths = HashMap::new(); + for entry in WalkDir::new("source").into_iter().filter_map(|e| e.ok()) { + let path = entry.path(); + if path.is_file() && path.ends_with("web.crud") { + print!("compiling {:?}: ", path); + let source = fs::read_to_string(path)?; + let tokens = scan(&source); + match ast_compiler::compile(tokens) { + Ok(statements) => { + let chunk = compile(&statements)?; + let path = path.strip_prefix("source")?.to_str().unwrap(); + let path = path.replace("/web.crud", ""); + paths.insert(format!("/{}", path), chunk); + } + Err(e) => { + println!("{}", e); + break; + } + } + println!(); } } - + if !paths.is_empty() { + let mut app = Router::new(); + for (path, code) in paths.iter() { + let code = code.functions.get("get").unwrap(); + let state = Arc::new(AppState { code: code.clone() }); + println!("adding {}", path); + app = app.route(path, get(handle_get).with_state(state.clone())); + // .with_state(state); + } + // run our app with hyper, listening globally on port 3000 + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + println!("listening on {}", listener.local_addr()?); + axum::serve(listener, app).await?; + } Ok(()) } + +#[derive(Clone)] +struct AppState { + code: Chunk, +} + +async fn handle_get(State(state): State>) -> Result, StatusCode> { + Ok(Json(interpret(&state.code).await.unwrap().to_string())) +} + +// let tokens = scan(""); +// +// match ast_compiler::compile(tokens) { +// Ok(statements) => { +// println!("{:?}", statements); +// let chunk = compile(&statements)?; +// chunk.disassemble(); +// println!("{}",interpret(&chunk)?); +// } +// Err(e) => { +// println!("{}", e) +// } +// } diff --git a/src/vm.rs b/src/vm.rs index 9982cc9..82bfbab 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -28,7 +28,7 @@ pub struct Vm { arena: Bump, } -pub fn interpret(chunk: &Chunk) -> anyhow::Result { +pub async fn interpret(chunk: &Chunk) -> anyhow::Result { let mut vm = Vm { ip: 0, stack: vec![], @@ -145,7 +145,7 @@ impl Vm { } OP_CALL => { let function_index = self.read(chunk); - let function = chunk.functions.get(function_index).unwrap(); + let function = chunk.functions_by_index.get(function_index).unwrap(); let mut args = vec![]; let num_args = self.read(chunk); for _ in 0..num_args {