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",
"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"

View file

@ -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"

View file

@ -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**
```

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::value::Value;
use log::debug;
@ -6,7 +11,7 @@ use std::collections::HashMap;
pub fn compile(tokens: Vec<Token>) -> anyhow::Result<Vec<Statement>> {
let mut compiler = AstCompiler::new(tokens);
compiler.compile(0)
compiler.compile_tokens(0)
}
#[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>>{
if !self.had_error {
let mut statements = vec![];
while !self.is_at_end() {
let statement = self.indent(expected_indent)?;
@ -49,6 +65,62 @@ impl AstCompiler {
}
}
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>> {
@ -89,50 +161,25 @@ impl AstCompiler {
fn function_declaration(&mut self) -> anyhow::Result<Statement> {
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)
}
@ -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<Expression> {
@ -369,6 +418,7 @@ impl AstCompiler {
}
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 = 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())
}

View file

@ -12,7 +12,7 @@ use crate::vm::{
use std::collections::HashMap;
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> {
@ -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)?;

View file

@ -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<u16>,
pub constants: Vec<Value>,
lines: Vec<usize>,
pub(crate) functions: Vec<Chunk>,
pub functions: HashMap<String, Chunk>,
pub(crate) functions_by_index: Vec<Chunk>,
}
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);

View file

@ -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;

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::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);
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) => {
println!("{:?}", statements);
let chunk = compile(&statements)?;
chunk.disassemble();
println!("{}",interpret(&chunk)?);
let path = path.strip_prefix("source")?.to_str().unwrap();
let path = path.replace("/web.crud", "");
paths.insert(format!("/{}", path), chunk);
}
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(())
}
#[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,
}
pub fn interpret(chunk: &Chunk) -> anyhow::Result<Value> {
pub async fn interpret(chunk: &Chunk) -> anyhow::Result<Value> {
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 {