before better function hierarchy

This commit is contained in:
Shautvast 2025-10-29 17:09:02 +01:00
parent dd5debe93e
commit d856452c76
10 changed files with 229 additions and 81 deletions

29
Cargo.lock generated
View file

@ -214,6 +214,7 @@ dependencies = [
"tower-livereload", "tower-livereload",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"walkdir",
] ]
[[package]] [[package]]
@ -1300,6 +1301,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 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]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.28" version = "0.1.28"
@ -1980,6 +1990,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 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]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@ -2128,6 +2148,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 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]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -21,3 +21,4 @@ anyhow = "1.0"
tower-livereload = "0.9.6" tower-livereload = "0.9.6"
log = "0.4.28" log = "0.4.28"
bumpalo = { version = "3.19", features = ["collections", "boxed", "serde"] } bumpalo = { version = "3.19", features = ["collections", "boxed", "serde"] }
walkdir = "2.5.0"

View file

@ -1,9 +1,9 @@
# crud-lang # crud-lang
## Why? ## 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. 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). 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. 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. 6. Json is ubiquitous. Convention over configuration: A controller returns json by default.
@ -33,7 +33,7 @@
### An interpreter written in Rust. ### An interpreter written in Rust.
OMG! OMG!
And it has everything I like in other languages And I cherry picked things I like, mostly from rust and python.
- strictly typed - strictly typed
- [] is a list - [] is a list
- {} is a map - {} is a map
@ -41,7 +41,7 @@ And it has everything I like in other languages
- structs and duck typing - structs and duck typing
- everything is an expression - everything is an expression
- nice iterators. - nice iterators.
- First class functions? Maybe... - First-class functions? Maybe...
- automatic mapping from database to object to json - automatic mapping from database to object to json
- indenting like python - 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 - a very simple api that listens to GET /api/customers{:id} and returns a customer from the database
## Design ## Design
* heavily inspired by Crafting Interpreters * heavily inspired by Crafting Interpreters.
* language influences from rust and python
* compiler first creates an AST and then compiles to bytecode (no file format yet) * compiler first creates an AST and then compiles to bytecode (no file format yet)
* uses a stack-based virtual machine * uses a stack-based virtual machine
## Current status: ## Current status: infancy
* compiler and runtime are still limited but working * compiler and runtime are still limited but working
* supports: * supports:
* basic types: * basic types:
@ -91,6 +90,12 @@ And it has everything I like in other languages
- control flow - control flow
- tests - 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 ## A quick taste
**variables** **variables**
``` ```

5
source/hello/web.crud Normal file
View file

@ -0,0 +1,5 @@
fn get() -> string:
add("hello", "world")
fn add(a: string, b: string) -> string:
a + b

View file

@ -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::tokens::{Token, TokenType};
use crate::value::Value; use crate::value::Value;
use log::debug; use log::debug;
@ -6,7 +11,7 @@ use std::collections::HashMap;
pub fn compile(tokens: Vec<Token>) -> anyhow::Result<Vec<Statement>> { pub fn compile(tokens: Vec<Token>) -> anyhow::Result<Vec<Statement>> {
let mut compiler = AstCompiler::new(tokens); let mut compiler = AstCompiler::new(tokens);
compiler.compile(0) compiler.compile_tokens(0)
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -38,7 +43,18 @@ impl AstCompiler {
} }
} }
fn reset(&mut self) {
self.current = 0;
}
fn compile_tokens(&mut self, expected_indent: usize) -> anyhow::Result<Vec<Statement>> {
self.collect_functions()?;
self.reset();
self.compile(expected_indent)
}
fn compile(&mut self, expected_indent: usize) -> anyhow::Result<Vec<Statement>>{ fn compile(&mut self, expected_indent: usize) -> anyhow::Result<Vec<Statement>>{
if !self.had_error {
let mut statements = vec![]; let mut statements = vec![];
while !self.is_at_end() { while !self.is_at_end() {
let statement = self.indent(expected_indent)?; let statement = self.indent(expected_indent)?;
@ -49,6 +65,62 @@ impl AstCompiler {
} }
} }
Ok(statements) Ok(statements)
} else {
Err(anyhow::anyhow!("Compilation failed."))
}
}
fn collect_functions(&mut self) -> anyhow::Result<()> {
while !self.is_at_end() {
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 {
self.advance();
}
}
Ok(())
} }
fn indent(&mut self, expected_indent: usize) -> anyhow::Result<Option<Statement>> { fn indent(&mut self, expected_indent: usize) -> anyhow::Result<Option<Statement>> {
@ -89,50 +161,25 @@ impl AstCompiler {
fn function_declaration(&mut self) -> anyhow::Result<Statement> { fn function_declaration(&mut self) -> anyhow::Result<Statement> {
let name_token = self.consume(Identifier, "Expect function name.")?; let name_token = self.consume(Identifier, "Expect function name.")?;
self.consume(LeftParen, "Expect '(' after function name.")?; self.consume(LeftParen, "Expect '(' after function name.")?;
let mut parameters = vec![];
while !self.check(RightParen) { 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(); self.advance();
parameters.push(Parameter {
name: parm_name,
var_type,
});
} }
self.consume(RightParen, "Expect ')' after parameters.")?; self.consume(RightParen, "Expect ')' after parameters.")?;
let return_type = if self.check(SingleRightArrow) { while !self.check(Colon) {
self.consume(SingleRightArrow, "")?; self.advance();
self.advance().token_type }
} else { self.consume(Colon, "2Expect colon (:) after function declaration.")?;
TokenType::Void
};
self.consume(Colon, "Expect colon (:) after function declaration.")?;
self.consume(Eol, "Expect end of line.")?; self.consume(Eol, "Expect end of line.")?;
let current_indent = self.indent.last().unwrap(); let current_indent = self.indent.last().unwrap();
let body = self.compile(current_indent + 1)?; let body = self.compile(current_indent + 1)?;
let function = Function { self.functions.get_mut(&name_token.lexeme).unwrap().body = body;
name: name_token.clone(),
parameters,
return_type,
body,
};
let function_stmt = Statement::FunctionStmt { 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) Ok(function_stmt)
} }
@ -344,8 +391,10 @@ impl AstCompiler {
} }
} }
Ok(Expression::List { 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<Expression> { fn variable_lookup(&mut self, token: &Token) -> anyhow::Result<Expression> {
@ -369,6 +418,7 @@ impl AstCompiler {
} }
fn function_call(&mut self, name: String) -> anyhow::Result<Expression> { fn function_call(&mut self, name: String) -> anyhow::Result<Expression> {
println!("function call {}", name);
let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); let function_name = self.functions.get(&name).unwrap().name.lexeme.clone();
let function = self.functions.get(&function_name).unwrap().clone(); let function = self.functions.get(&function_name).unwrap().clone();
@ -408,7 +458,11 @@ impl AstCompiler {
self.advance(); self.advance();
} else { } else {
self.had_error = true; 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()) Ok(self.previous().clone())
} }

View file

@ -12,7 +12,7 @@ use crate::vm::{
use std::collections::HashMap; use std::collections::HashMap;
pub fn compile(ast: &Vec<Statement>) -> anyhow::Result<Chunk> { pub fn compile(ast: &Vec<Statement>) -> anyhow::Result<Chunk> {
compile_name(ast, "_") compile_name(ast, "/")
} }
pub(crate) fn compile_function(function: &Function) -> anyhow::Result<Chunk> { pub(crate) fn compile_function(function: &Function) -> anyhow::Result<Chunk> {
@ -98,6 +98,7 @@ impl Compiler {
Expression::FunctionCall { Expression::FunctionCall {
name, arguments, .. name, arguments, ..
} => { } => {
println!("call {}",name);
let function_index = *self.functions.get(name).unwrap(); let function_index = *self.functions.get(name).unwrap();
for argument in arguments { for argument in arguments {
self.compile_expression(argument)?; self.compile_expression(argument)?;

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use crate::value::Value; use crate::value::Value;
use crate::vm::{ use crate::vm::{
OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_BOOL, OP_DEF_F32, 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, OP_NOT, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
}; };
#[derive(Debug, Clone)]
pub struct Chunk { pub struct Chunk {
name: String, name: String,
pub code: Vec<u16>, pub code: Vec<u16>,
pub constants: Vec<Value>, pub constants: Vec<Value>,
lines: Vec<usize>, lines: Vec<usize>,
pub(crate) functions: Vec<Chunk>, pub functions: HashMap<String, Chunk>,
pub(crate) functions_by_index: Vec<Chunk>,
} }
impl Chunk { impl Chunk {
@ -21,7 +24,8 @@ impl Chunk {
code: Vec::new(), code: Vec::new(),
constants: vec![], constants: vec![],
lines: 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 { 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 self.functions.len() - 1
} }
pub fn disassemble(&self) { pub fn disassemble(&self) {
for f in &self.functions { for f in self.functions.values() {
f.disassemble(); f.disassemble();
} }
println!("== {} ==", self.name); println!("== {} ==", self.name);

View file

@ -2,7 +2,7 @@ pub mod scanner;
pub mod ast_compiler; pub mod ast_compiler;
pub mod bytecode_compiler; pub mod bytecode_compiler;
pub mod vm; pub mod vm;
mod chunk; pub mod chunk;
mod keywords; mod keywords;
mod tokens; mod tokens;
mod value; mod value;

View file

@ -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::ast_compiler;
use crudlang::bytecode_compiler::compile; use crudlang::bytecode_compiler::compile;
use crudlang::chunk::Chunk;
use crudlang::scanner::scan; use crudlang::scanner::scan;
use crudlang::vm::interpret; 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(); tracing_subscriber::fmt::init();
let tokens = scan( let mut paths = HashMap::new();
r#" for entry in WalkDir::new("source").into_iter().filter_map(|e| e.ok()) {
fn main(a: list) -> list: let path = entry.path();
a + 42 if path.is_file() && path.ends_with("web.crud") {
print!("compiling {:?}: ", path);
let text:list = ["hello "] let source = fs::read_to_string(path)?;
main(text)"#, let tokens = scan(&source);
);
println!("{:?}", tokens);
match ast_compiler::compile(tokens) { match ast_compiler::compile(tokens) {
Ok(statements) => { Ok(statements) => {
println!("{:?}", statements);
let chunk = compile(&statements)?; let chunk = compile(&statements)?;
chunk.disassemble(); let path = path.strip_prefix("source")?.to_str().unwrap();
println!("{}",interpret(&chunk)?); let path = path.replace("/web.crud", "");
paths.insert(format!("/{}", path), chunk);
} }
Err(e) => { Err(e) => {
println!("{}", 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(()) Ok(())
} }
#[derive(Clone)]
struct AppState {
code: Chunk,
}
async fn handle_get(State(state): State<Arc<AppState>>) -> Result<Json<String>, 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)
// }
// }

View file

@ -28,7 +28,7 @@ pub struct Vm {
arena: Bump, arena: Bump,
} }
pub fn interpret(chunk: &Chunk) -> anyhow::Result<Value> { pub async fn interpret(chunk: &Chunk) -> anyhow::Result<Value> {
let mut vm = Vm { let mut vm = Vm {
ip: 0, ip: 0,
stack: vec![], stack: vec![],
@ -145,7 +145,7 @@ impl Vm {
} }
OP_CALL => { OP_CALL => {
let function_index = self.read(chunk); 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 mut args = vec![];
let num_args = self.read(chunk); let num_args = self.read(chunk);
for _ in 0..num_args { for _ in 0..num_args {