external function calling

This commit is contained in:
Shautvast 2025-11-07 20:49:45 +01:00
parent 2e85a60d22
commit 2b3b8d4bb3
12 changed files with 263 additions and 135 deletions

View file

@ -1,22 +1,22 @@
# crud-lang # crud-lang
## Why? ## Why?
1. Existing languages are just fine, 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 >always< 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. Is it just me? -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 crappy. 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.
7. Yes, you can automatically serve json from postgres or whatever, but that is not the point. We want to build services. 7. Yes, you can automatically serve json from postgres or whatever, but that is not the point. We want to build services.
## Now what? ## Now what?
- an experimental language for CRUD applications (web api's) - En experimental language for CRUD applications (web api's)
- Enterprise as a first-class citizen - Enterprise as a first-class citizen
- built-in types for dates and uuid for example - built-in types for dates and uuid for example
- collection literals - collection literals
- ease of use for CRUD operations, like automatic mapping from sql rows to json - ease of use for CRUD operations, like automatic mapping from sql rows to json
- urls are made up of directories. - Urls are made up of directories.
- a controller sourcefile is a file named web.crud - A controller sourcefile is a file named web.crud
- likewise: - likewise:
- service.crud for services - service.crud for services
- db.crud database access code - db.crud database access code
@ -27,27 +27,36 @@
- openapi support - openapi support
### An interpreter written in Rust. ### An interpreter written in Rust.
OMG! I cherry picked things I like, mostly from rust and python.
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
- no objects, no inheritance - objects, not inheritance
- 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
- It's not written in stone. Things may change.
**types** **Numeric Types**
* u32, u64 (also in hex: 0x...)
* i32, i64 signed
* f32, f64 (including scientific notation)
**And also**
* string: "hello world"
* uuid ,
* bool: true, false
* char '.'
* object: {field: value}. An object is a map with fixed keys that are strings.
* enum
* date
**Collections**
* list: \[e1, e2, e3, ...]
* map: {key: value, key2: value2, ...}
- u32, i32
- u64, i64
- f32, f64,
- string, bool, char
- struct, enum
- date
## open questions ## open questions
- pluggability for middleware?, implement later? - pluggability for middleware?, implement later?
@ -65,25 +74,26 @@ And I cherry picked things I like, mostly 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: infancy ## Current status: toddler stage
* compiler and runtime are still limited but working * compiler and runtime are limited but working
* next big thing: control flow: branch jumps and loops
* built on a solid foundation: [axum](https://github.com/tokio-rs/axum)
* supports: * supports:
* basic types: * basic types:
* 32/64 bit integers, signed and unsigned * 32/64 bit integers, signed and unsigned
* 32/64 bit floats * 32/64 bit floats
* strings * strings, bools, chars
* bools
* chars
* lists and maps (as literals) * lists and maps (as literals)
* type checking and type inference (although it needs more testing) * still todo: dates, uuids, enums, objects
* arithmetic expressions * type checking and type inference
* arithmetic expressions (all you'd expect including bitwise ops)
* function declaration and calling * function declaration and calling
* indenting like python (for now just 1 level, but both tabs or double spaces) * indenting like python (for now just 1 level, but both tabs or double spaces)
* strict typing like in rust (no implicit numeric conversions) * strict typing like in rust (no implicit numeric conversions)
* basic set of operators, including logical and/or and bitwise operations * basic set of operators, including logical and/or and bitwise operations
* automatic injection of uri, query parameters and headers * automatic injection of uri, query parameters and headers
* if you declare them they will be available in the function body * if you declare them they will be available in the function body.
* example: For example:
```html ```html
fn get(path: string, headers: map, query: map) -> string: fn get(path: string, headers: map, query: map) -> string:
"hello" + path "hello" + path
@ -99,18 +109,14 @@ fn get(path: string, headers: map, query: map) -> string:
* ```cargo run -- --watch``` * ```cargo run -- --watch```
## What's next? ## What's next?
* guards: this will be the way to correctly deal with parameters * guards: this will be the way to deal with input
``` ```
fn get(): fn get() -> [Customer] | Customer? | ():
| path == "/" -> list: | /. -> service.get_all()
service.get_all() | /./{uuid} -> service.get(uuid)?
| path == "/{uuid}" -> Customer?: | /.?{query.firstname} -> service.get_by_firstname(fname)?
service.get(uuid)? | /.?{query.last_name} -> service.get_by_lastname(lname)?
| path == "/" && query.firstname -> Customer?: | _ -> 404
service.get_by_firstname(fname)?
| path == "/" && query.last_name -> Customer?
service.get_by_lastname(lname)?
| 404
``` ```
* this may also require ADT's... * this may also require ADT's...
* object/struct types: Work in Progress * object/struct types: Work in Progress

View file

@ -1,5 +1,2 @@
fn get(path: string) -> string: fn get(path: string) -> string:
add("hello", path) service.add("hello", path)
fn add(a: string, b: string) -> string:
a + " " + b

View file

@ -1,15 +1,15 @@
use crate::ast_compiler::Expression::Variable; use crate::ast_compiler::Expression::{FunctionCall, Literal, RemoteFunctionCall, Variable};
use crate::errors::CompilerError::{ use crate::errors::CompilerError::{
self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError, UnexpectedIndent, self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError,
UninitializedVariable, UndeclaredVariable, UnexpectedIndent, UninitializedVariable,
}; };
use crate::errors::CompilerErrorAtLine; use crate::errors::CompilerErrorAtLine;
use crate::tokens::TokenType::{ use crate::tokens::TokenType::{
Bang, Bool, Char, Colon, Date, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, Bang, Bool, Char, Colon, Date, Dot, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn,
Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess, Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace,
Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SignedInteger, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus,
SingleRightArrow, Slash, Star, StringType, True, UnsignedInteger, F32, F64, Print, RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star,
I32, I64, U32, U64, StringType, True, U32, U64, UnsignedInteger,
}; };
use crate::tokens::{Token, TokenType}; use crate::tokens::{Token, TokenType};
use crate::value::Value; use crate::value::Value;
@ -75,6 +75,7 @@ impl AstCompiler {
break; break;
} }
} }
debug!("AST {:?}", statements);
Ok(statements) Ok(statements)
} else { } else {
Err(self.raise(CompilerError::Failure)) Err(self.raise(CompilerError::Failure))
@ -296,7 +297,9 @@ impl AstCompiler {
fn expr_statement(&mut self) -> Result<Statement, CompilerErrorAtLine> { fn expr_statement(&mut self) -> Result<Statement, CompilerErrorAtLine> {
let expr = self.expression()?; let expr = self.expression()?;
self.consume(Eol, Expected("end of line after expression."))?; if !self.is_at_end() {
self.consume(Eol, Expected("end of line after expression."))?;
}
Ok(Statement::ExpressionStmt { expression: expr }) Ok(Statement::ExpressionStmt { expression: expr })
} }
@ -468,6 +471,27 @@ impl AstCompiler {
debug!("{:?}", token); debug!("{:?}", token);
if self.match_token(vec![LeftParen]) { if self.match_token(vec![LeftParen]) {
self.function_call(token.lexeme)? self.function_call(token.lexeme)?
} else if self.check(Dot) {
let mut name = "/".to_string();
name.push_str(&self.previous().lexeme);
while self.match_token(vec![Dot]) {
name.push_str("/");
name.push_str(&self.peek().lexeme);
self.advance();
}
if self.match_token(vec![LeftParen]) {
self.function_call(name)?
} else {
return if self.match_token(vec![Eol, Eof]) {
Ok(Literal {
value: Value::Void,
literaltype: Object,
line: token.line,
})
} else {
Err(self.raise(UndeclaredVariable(token.lexeme.clone())))
};
}
} else { } else {
self.variable_lookup(&token)? self.variable_lookup(&token)?
} }
@ -514,7 +538,7 @@ impl AstCompiler {
} }
fn variable_lookup(&mut self, token: &Token) -> Result<Expression, CompilerErrorAtLine> { fn variable_lookup(&mut self, token: &Token) -> Result<Expression, CompilerErrorAtLine> {
let (var_name, var_type) = self if let Some((var_name, var_type)) = self
.vars .vars
.iter() .iter()
.filter_map(|e| { .filter_map(|e| {
@ -525,46 +549,80 @@ impl AstCompiler {
} }
}) })
.find(|e| e.0 == &token.lexeme) .find(|e| e.0 == &token.lexeme)
.ok_or_else(|| return self.raise(CompilerError::UndeclaredVariable(token.clone())))?; {
Ok(Variable { Ok(Variable {
name: var_name.to_string(), name: var_name.to_string(),
var_type: var_type.clone(), var_type: var_type.clone(),
line: token.line, line: token.line,
}) })
} else {
if self.match_token(vec![Dot]) {
let right = self.primary()?;
self.binary(vec![Dot], right)
} else {
if self.is_at_end() {
Ok(Literal {
value: Value::Void,
literaltype: Object,
line: token.line,
})
} else {
Err(self.raise(UndeclaredVariable(token.lexeme.clone())))
}
}
}
} }
fn function_call(&mut self, name: String) -> Result<Expression, CompilerErrorAtLine> { fn function_call(&mut self, name: String) -> Result<Expression, CompilerErrorAtLine> {
let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); if let Some(function) = self.functions.get(&name).cloned() {
let function = self.functions.get(&function_name).unwrap().clone(); let mut arguments = vec![];
while !self.match_token(vec![RightParen]) {
let mut arguments = vec![]; if arguments.len() >= 25 {
while !self.match_token(vec![RightParen]) { return Err(self.raise(TooManyParameters));
if arguments.len() >= 25 { }
return Err(self.raise(TooManyParameters)); let arg = self.expression()?;
let arg_type = arg.infer_type();
if arg_type != function.parameters[arguments.len()].var_type {
return Err(self.raise(IncompatibleTypes(
function.parameters[arguments.len()].var_type,
arg_type,
)));
}
arguments.push(arg);
if self.peek().token_type == TokenType::Comma {
self.advance();
} else {
self.consume(RightParen, Expected("')' after arguments."))?;
break;
}
} }
let arg = self.expression()?; Ok(FunctionCall {
let arg_type = arg.infer_type(); line: self.peek().line,
if arg_type != function.parameters[arguments.len()].var_type { name,
return Err(self.raise(IncompatibleTypes( arguments,
function.parameters[arguments.len()].var_type, return_type: function.return_type,
arg_type, })
))); } else {
} let mut arguments = vec![];
arguments.push(arg); while !self.match_token(vec![RightParen]) {
if self.peek().token_type == TokenType::Comma { if arguments.len() >= 25 {
self.advance(); return Err(self.raise(TooManyParameters));
} else { }
self.consume(RightParen, Expected("')' after arguments."))?; let arg = self.expression()?;
break; arguments.push(arg);
if self.peek().token_type == TokenType::Comma {
self.advance();
} else {
self.consume(RightParen, Expected("')' after arguments."))?;
break;
}
} }
Ok(RemoteFunctionCall {
line: self.peek().line,
name,
arguments,
})
} }
let return_type = self.functions.get(&name).unwrap().return_type;
Ok(Expression::FunctionCall {
line: self.peek().line,
name,
arguments,
return_type,
})
} }
fn consume( fn consume(
@ -576,11 +634,6 @@ impl AstCompiler {
self.advance(); self.advance();
} else { } else {
self.had_error = true; self.had_error = true;
// return Err(anyhow::anyhow!(
// "{} at {:?}",
// message.to_string(),
// self.peek()
// ));
return Err(self.raise(message)); return Err(self.raise(message));
} }
Ok(self.previous().clone()) Ok(self.previous().clone())
@ -750,6 +803,12 @@ pub enum Expression {
arguments: Vec<Expression>, arguments: Vec<Expression>,
return_type: TokenType, return_type: TokenType,
}, },
// a remote function call is a function call that is not defined in the current scope
RemoteFunctionCall {
line: usize,
name: String,
arguments: Vec<Expression>,
},
} }
impl Expression { impl Expression {
@ -761,8 +820,9 @@ impl Expression {
Self::Literal { line, .. } => *line, Self::Literal { line, .. } => *line,
Self::List { line, .. } => *line, Self::List { line, .. } => *line,
Self::Map { line, .. } => *line, Self::Map { line, .. } => *line,
Self::Variable { line, .. } => *line, Variable { line, .. } => *line,
Self::FunctionCall { line, .. } => *line, FunctionCall { line, .. } => *line,
RemoteFunctionCall { line, .. } => *line,
} }
} }
@ -838,8 +898,9 @@ impl Expression {
UnsignedInteger UnsignedInteger
} }
} }
Self::Variable { var_type, .. } => var_type.clone(), Variable { var_type, .. } => var_type.clone(),
Self::FunctionCall { return_type, .. } => return_type.clone(), FunctionCall { return_type, .. } => return_type.clone(),
RemoteFunctionCall { .. } => TokenType::Unknown,
} }
} }
} }

View file

@ -1,13 +1,12 @@
use crate::ast_compiler::{Expression, Function, Statement}; use crate::ast_compiler::{Expression, Function, Statement};
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::errors::{CompilerErrorAtLine}; use crate::errors::CompilerErrorAtLine;
use crate::tokens::TokenType; use crate::tokens::TokenType;
use crate::value::Value; use crate::value::Value;
use crate::vm::{ use crate::vm::{
OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_LIST, OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_LIST,
OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL,
OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -45,7 +44,7 @@ pub(crate) fn compile_in_namespace(
let compiler = Compiler::new(name); let compiler = Compiler::new(name);
let chunk = compiler.compile(ast, registry, name)?; let chunk = compiler.compile(ast, registry, name)?;
let qname = if let Some(namespace) = namespace { let qname = if let Some(namespace) = namespace {
format!("{}.{}", namespace, "main") format!("{}/{}", namespace, "main")
} else { } else {
"main".to_string() "main".to_string()
}; };
@ -113,7 +112,7 @@ impl Compiler {
let function_name = function.name.lexeme.clone(); let function_name = function.name.lexeme.clone();
let compiled_function = compile_function(function, registry, namespace)?; let compiled_function = compile_function(function, registry, namespace)?;
registry.insert( registry.insert(
format!("{}.{}", self.chunk.name, function_name), format!("{}/{}", self.chunk.name, function_name),
compiled_function, compiled_function,
); );
} }
@ -138,7 +137,21 @@ impl Compiler {
let name_index = self let name_index = self
.chunk .chunk
.find_constant(&qname) .find_constant(&qname)
.unwrap_or_else(|| self.emit_constant(qname.into()) as usize); .unwrap_or_else(|| self.chunk.add_constant(Value::String(qname)));
for argument in arguments {
self.compile_expression(namespace, argument, registry)?;
}
self.emit_bytes(OP_CALL, name_index as u16);
self.emit_byte(arguments.len() as u16);
}
Expression::RemoteFunctionCall {
name, arguments, ..
} => {
let name_index = self
.chunk
.find_constant(&name)
.unwrap_or_else(|| self.chunk.add_constant(Value::String(name.to_string())));
for argument in arguments { for argument in arguments {
self.compile_expression(namespace, argument, registry)?; self.compile_expression(namespace, argument, registry)?;

View file

@ -181,4 +181,9 @@ m"#);
fn add_hex_ints(){ fn add_hex_ints(){
assert_eq!(run(r#"0x10 + 0x20"#), Ok(Value::U32(48))); assert_eq!(run(r#"0x10 + 0x20"#), Ok(Value::U32(48)));
} }
#[test]
fn package(){
assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));
}
} }

View file

@ -49,8 +49,8 @@ pub enum CompilerError {
IncompatibleTypes(TokenType, TokenType), IncompatibleTypes(TokenType, TokenType),
#[error("Error parsing number {0}")] #[error("Error parsing number {0}")]
ParseError(String), ParseError(String),
#[error("Undeclared variable: {0:?}")] #[error("Undeclared variable: '{0}'")]
UndeclaredVariable(Token), UndeclaredVariable(String),
#[error("Unexpected identifier")] #[error("Unexpected identifier")]
UnexpectedIdentifier, UnexpectedIdentifier,
#[error("Unterminated {0}")] #[error("Unterminated {0}")]
@ -63,6 +63,8 @@ pub enum CompilerError {
KeywordNotAllowedAsIdentifier(TokenType), KeywordNotAllowedAsIdentifier(TokenType),
#[error("Crud does not support numbers above 2^64")] #[error("Crud does not support numbers above 2^64")]
Overflow, Overflow,
#[error("Undeclared function: '{0}'")]
FunctionNotFound(String)
} }
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]

View file

@ -4,10 +4,10 @@ use crate::errors::CrudLangError::Platform;
use crate::scanner::scan; use crate::scanner::scan;
use crate::value::Value; use crate::value::Value;
use crate::vm::interpret; use crate::vm::interpret;
use arc_swap::ArcSwap;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use arc_swap::ArcSwap;
use walkdir::WalkDir; use walkdir::WalkDir;
pub mod ast_compiler; pub mod ast_compiler;
@ -15,13 +15,13 @@ pub mod bytecode_compiler;
pub mod chunk; pub mod chunk;
mod compiler_tests; mod compiler_tests;
pub mod errors; pub mod errors;
pub mod file_watch;
mod keywords; mod keywords;
pub mod repl;
pub mod scanner; pub mod scanner;
mod tokens; mod tokens;
mod value; mod value;
pub mod vm; pub mod vm;
pub mod repl;
pub mod file_watch;
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, CrudLangError> { pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, CrudLangError> {
let mut registry = HashMap::new(); let mut registry = HashMap::new();
@ -29,12 +29,12 @@ 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()) { for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path().to_str().unwrap(); let path = entry.path().to_str().unwrap();
if path.ends_with(".crud") { if path.ends_with(".crud") {
print!("-- Compiling "); print!("-- Compiling {} -- ", path);
let source = fs::read_to_string(path).map_err(map_underlying())?; let source = fs::read_to_string(path).map_err(map_underlying())?;
let tokens = scan(&source)?; let tokens = scan(&source)?;
match ast_compiler::compile(Some(&path), tokens) { match ast_compiler::compile(Some(&path), tokens) {
Ok(statements) => { Ok(statements) => {
println!("{}",path); println!("{}", path);
let path = path.strip_prefix(source_dir).unwrap().replace(".crud", ""); let path = path.strip_prefix(source_dir).unwrap().replace(".crud", "");
bytecode_compiler::compile(Some(&path), &statements, &mut registry)?; bytecode_compiler::compile(Some(&path), &statements, &mut registry)?;
} }
@ -43,9 +43,9 @@ pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, Chunk>, Cru
break; break;
} }
} }
println!();
} }
} }
println!();
Ok(registry) Ok(registry)
} }
@ -53,6 +53,13 @@ pub fn map_underlying() -> fn(std::io::Error) -> CrudLangError {
|e| Platform(e.to_string()) |e| Platform(e.to_string())
} }
pub fn recompile(src: &str, registry: &mut HashMap<String, Chunk>) -> Result<(), CrudLangError> {
let tokens = scan(src)?;
let ast = ast_compiler::compile(None, tokens)?;
bytecode_compiler::compile(None, &ast, registry)?;
Ok(())
}
pub fn compile(src: &str) -> Result<HashMap<String, Chunk>, CrudLangError> { pub fn compile(src: &str) -> Result<HashMap<String, Chunk>, CrudLangError> {
let tokens = scan(src)?; let tokens = scan(src)?;
let mut registry = HashMap::new(); let mut registry = HashMap::new();

View file

@ -12,6 +12,7 @@ use notify::Watcher;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use log::{debug, info};
/// A simple CLI tool to greet users /// A simple CLI tool to greet users
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -58,7 +59,7 @@ async fn main() -> Result<(), CrudLangError> {
); );
if args.repl { if args.repl {
std::thread::spawn(move || crudlang::repl::start(swap.load()).unwrap()); std::thread::spawn(move || crudlang::repl::start(swap.clone()).unwrap());
} }
axum::serve(listener, app).await.map_err(map_underlying())?; axum::serve(listener, app).await.map_err(map_underlying())?;
@ -92,13 +93,14 @@ async fn handle_any(
}) })
.unwrap_or_default(); .unwrap_or_default();
let component = format!("{}/web", &uri.path()); let component = format!("{}/web", &uri.path());
let function_qname = format!("{}.{}", component, method); let function_qname = format!("{}/{}", component, method);
let mut headers = HashMap::new(); let mut headers = HashMap::new();
for (k, v) in req.headers().iter() { for (k, v) in req.headers().iter() {
headers.insert(k.to_string(), v.to_str().unwrap().to_string()); headers.insert(k.to_string(), v.to_str().unwrap().to_string());
} }
let path = &req.uri().to_string(); let path = &req.uri().to_string();
info!("invoked {:?} => {}",req, function_qname);
match interpret_async( match interpret_async(
state.registry.load(), state.registry.load(),
&function_qname, &function_qname,

View file

@ -1,13 +1,15 @@
use crate::chunk::Chunk; use crate::chunk::Chunk;
use crate::errors::CrudLangError; use crate::errors::CrudLangError;
use crate::map_underlying; use crate::vm::interpret;
use crate::{map_underlying, recompile};
use arc_swap::ArcSwap;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use arc_swap::{ArcSwap, Guard};
pub fn start(registry: Guard<Arc<HashMap<String, Chunk>>>) -> Result<(), CrudLangError> { pub fn start(registry: Arc<ArcSwap<HashMap<String, Chunk>>>) -> Result<(), CrudLangError> {
println!("REPL started -- Type ctrl-c to exit (both the repl and the server)"); println!("REPL started -- Type ctrl-c to exit (both the repl and the server)");
println!(":h for help"); println!(":h for help");
loop { loop {
@ -20,9 +22,27 @@ pub fn start(registry: Guard<Arc<HashMap<String, Chunk>>>) -> Result<(), CrudLan
let input = input.trim(); let input = input.trim();
match input { match input {
":h" => help(), ":h" => help(),
":le" => list_endpoints(registry.clone()), ":le" => list_endpoints(registry.load().clone()),
":lf" => list_endpoints(registry.clone()), ":lf" => list_functions(registry.load().clone()),
_ => {} _ => {
let registry_copy = registry.load().clone();
let mut registry_copy = registry_copy.deref().clone();
match recompile(&input, &mut registry_copy){
Ok(_)=> {
registry.store(Arc::new(registry_copy));
let result = match interpret(registry.load(), "main") {
Ok(value) => value.to_string(),
Err(e) => e.to_string(),
};
println!("{}", result);
},
Err(e) => {
println!("{}", e);
}
}
}
} }
// println!("[{}]",input); // println!("[{}]",input);
// if input == ":q" { // if input == ":q" {
@ -38,16 +58,14 @@ fn list_endpoints(registry: Arc<HashMap<String, Chunk>>) {
.iter() .iter()
.filter(|(k, _)| k.contains("get")) .filter(|(k, _)| k.contains("get"))
.for_each(|(k, _)| { .for_each(|(k, _)| {
println!("{}", k);//number println!("{}", k); //number
}); });
} }
fn list_functions(registry: Arc<HashMap<String, Chunk>>) { fn list_functions(registry: Arc<HashMap<String, Chunk>>) {
registry registry.iter().for_each(|(k, _)| {
.iter() println!("{}", k); //number
.for_each(|(k, _)| { });
println!("{}", k);//number
});
} }
fn help() { fn help() {

View file

@ -83,6 +83,7 @@ pub enum TokenType {
True, True,
U32, U32,
U64, U64,
Unknown,
Void, Void,
While, While,
} }
@ -152,6 +153,7 @@ impl fmt::Display for TokenType {
TokenType::Slash => write!(f, "/"), TokenType::Slash => write!(f, "/"),
TokenType::Star => write!(f, "*"), TokenType::Star => write!(f, "*"),
TokenType::True => write!(f, "true"), TokenType::True => write!(f, "true"),
TokenType::Unknown => write!(f, "?"),
TokenType::Void => write!(f, "()"), TokenType::Void => write!(f, "()"),
TokenType::While => write!(f, "while"), TokenType::While => write!(f, "while"),
TokenType::SignedInteger => write!(f, "i32/64"), TokenType::SignedInteger => write!(f, "i32/64"),

View file

@ -229,7 +229,10 @@ impl Add<&Value> for &Value {
(Value::String(s), Value::Bool(b)) => Ok(Value::String(format!("{}{}", s, b))), (Value::String(s), Value::Bool(b)) => Ok(Value::String(format!("{}{}", s, b))),
(Value::String(s), Value::Char(c)) => Ok(Value::String(format!("{}{}", s, c))), (Value::String(s), Value::Char(c)) => Ok(Value::String(format!("{}{}", s, c))),
(Value::String(s1), Value::String(s2)) => { (Value::String(s1), Value::String(s2)) => {
Ok(Value::String(format!("{}{}", s1, s2))) let mut s = String::with_capacity(s1.len()+s2.len());
s.push_str(s1.as_str());
s.push_str(s2.as_str());
Ok(Value::String(s))
} }
(Value::String(s1), Value::Map(m)) => Ok(Value::String(format!("{}{:?}", s1, m))), (Value::String(s1), Value::Map(m)) => Ok(Value::String(format!("{}{:?}", s1, m))),
//enum? //enum?

View file

@ -7,6 +7,7 @@ use axum::http::{Uri};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use arc_swap::Guard; use arc_swap::Guard;
use reqwest::get;
use tracing::debug; use tracing::debug;
pub struct Vm { pub struct Vm {
@ -30,7 +31,7 @@ pub fn interpret(registry: Guard<Arc<HashMap<String, Chunk>>>, function: &str) -
error_occurred: false, error_occurred: false,
registry: registry.clone(), registry: registry.clone(),
}; };
vm.run(&chunk) vm.run(&get_context(function), &chunk)
} }
pub async fn interpret_async( pub async fn interpret_async(
@ -40,7 +41,6 @@ pub async fn interpret_async(
query_params: HashMap<String, String>, query_params: HashMap<String, String>,
headers: HashMap<String, String>, headers: HashMap<String, String>,
) -> Result<Value, RuntimeError> { ) -> Result<Value, RuntimeError> {
//TODO convert request to arguments
let chunk = registry.get(function); let chunk = registry.get(function);
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
let mut vm = Vm { let mut vm = Vm {
@ -48,7 +48,7 @@ pub async fn interpret_async(
stack: vec![], stack: vec![],
local_vars: HashMap::new(), local_vars: HashMap::new(),
error_occurred: false, error_occurred: false,
registry:registry.clone(), registry: registry.clone(),
}; };
vm.local_vars vm.local_vars
.insert("path".to_string(), Value::String(uri.into())); .insert("path".to_string(), Value::String(uri.into()));
@ -56,7 +56,7 @@ pub async fn interpret_async(
.insert("query".to_string(), Value::Map(value_map(query_params))); .insert("query".to_string(), Value::Map(value_map(query_params)));
vm.local_vars vm.local_vars
.insert("headers".to_string(), Value::Map(value_map(headers))); .insert("headers".to_string(), Value::Map(value_map(headers)));
vm.run(&chunk) vm.run(&get_context(function), &chunk)
} else { } else {
Err(RuntimeError::FunctionNotFound(function.to_string())) Err(RuntimeError::FunctionNotFound(function.to_string()))
} }
@ -87,10 +87,10 @@ impl Vm {
for (_, name) in chunk.vars.iter() { for (_, name) in chunk.vars.iter() {
self.local_vars.insert(name.clone(), args.remove(0)); self.local_vars.insert(name.clone(), args.remove(0));
} }
self.run(&chunk) self.run("", &chunk)
} }
fn run(&mut self, chunk: &Chunk) -> Result<Value, RuntimeError> { fn run(&mut self, context: &str, chunk: &Chunk) -> Result<Value, RuntimeError> {
loop { loop {
if self.error_occurred { if self.error_occurred {
return Err(Something); return Err(Something);
@ -189,7 +189,6 @@ impl Vm {
let (_, name_index) = chunk.vars.get(var_index).unwrap(); let (_, name_index) = chunk.vars.get(var_index).unwrap();
let value = self.local_vars.get(name_index).unwrap(); let value = self.local_vars.get(name_index).unwrap();
self.push(value.clone()); // not happy , take ownership, no clone self.push(value.clone()); // not happy , take ownership, no clone
debug!("after get {:?}", self.stack);
} }
OP_CALL => { OP_CALL => {
let function_name_index = self.read(chunk); let function_name_index = self.read(chunk);
@ -203,9 +202,14 @@ impl Vm {
args.reverse(); args.reverse();
let function_name = chunk.constants[function_name_index].to_string(); let function_name = chunk.constants[function_name_index].to_string();
let function_chunk = self.registry.get(&function_name).unwrap(); let function_chunk = self.registry.get(&function_name)
let result = interpret_function(function_chunk, args)?; .or_else(|| self.registry.get(&format!("{}{}", context, function_name)));
self.push(result); if function_chunk.is_none() {
return Err(RuntimeError::FunctionNotFound(function_name));
} else {
let result = interpret_function(function_chunk.unwrap(), args)?;
self.push(result);
}
} }
_ => {} _ => {}
} }
@ -256,6 +260,14 @@ fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> Result<Value, ValueError> + C
} }
} }
fn get_context(path: &str) -> String {
let mut parts: Vec<&str> = path.split('/').collect();
if parts.len() >= 2 {
parts.truncate(parts.len() - 2);
}
parts.join("/")
}
pub const OP_CONSTANT: u16 = 1; pub const OP_CONSTANT: u16 = 1;
pub const OP_ADD: u16 = 2; pub const OP_ADD: u16 = 2;
pub const OP_SUBTRACT: u16 = 3; pub const OP_SUBTRACT: u16 = 3;