external function calling
This commit is contained in:
parent
2e85a60d22
commit
2b3b8d4bb3
12 changed files with 263 additions and 135 deletions
82
README.md
82
README.md
|
|
@ -1,22 +1,22 @@
|
|||
# crud-lang
|
||||
|
||||
## 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.
|
||||
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).
|
||||
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.
|
||||
7. Yes, you can automatically serve json from postgres or whatever, but that is not the point. We want to build services.
|
||||
|
||||
## 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
|
||||
- built-in types for dates and uuid for example
|
||||
- collection literals
|
||||
- ease of use for CRUD operations, like automatic mapping from sql rows to json
|
||||
- urls are made up of directories.
|
||||
- a controller sourcefile is a file named web.crud
|
||||
- Urls are made up of directories.
|
||||
- A controller sourcefile is a file named web.crud
|
||||
- likewise:
|
||||
- service.crud for services
|
||||
- db.crud database access code
|
||||
|
|
@ -26,28 +26,37 @@
|
|||
- Therefore, services cannot call other services, because that is the recipe for spaghetti. Refactor your logic, abstract and put lower level code in utilities.
|
||||
- openapi support
|
||||
|
||||
### An interpreter written in Rust.
|
||||
OMG!
|
||||
And I cherry picked things I like, mostly from rust and python.
|
||||
### An interpreter written in Rust.
|
||||
I cherry picked things I like, mostly from rust and python.
|
||||
- strictly typed
|
||||
- [] is a list
|
||||
- {} is a map
|
||||
- no objects, no inheritance
|
||||
- structs and duck typing
|
||||
- objects, not inheritance
|
||||
- everything is an expression
|
||||
- nice iterators.
|
||||
- First-class functions? Maybe...
|
||||
- automatic mapping from database to object to json
|
||||
- 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
|
||||
- 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)
|
||||
* uses a stack-based virtual machine
|
||||
|
||||
## Current status: infancy
|
||||
* compiler and runtime are still limited but working
|
||||
## Current status: toddler stage
|
||||
* 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:
|
||||
* basic types:
|
||||
* 32/64 bit integers, signed and unsigned
|
||||
* 32/64 bit floats
|
||||
* strings
|
||||
* bools
|
||||
* chars
|
||||
* strings, bools, chars
|
||||
* lists and maps (as literals)
|
||||
* type checking and type inference (although it needs more testing)
|
||||
* arithmetic expressions
|
||||
* still todo: dates, uuids, enums, objects
|
||||
* type checking and type inference
|
||||
* arithmetic expressions (all you'd expect including bitwise ops)
|
||||
* function declaration and calling
|
||||
* indenting like python (for now just 1 level, but both tabs or double spaces)
|
||||
* strict typing like in rust (no implicit numeric conversions)
|
||||
* basic set of operators, including logical and/or and bitwise operations
|
||||
* automatic injection of uri, query parameters and headers
|
||||
* if you declare them they will be available in the function body
|
||||
* example:
|
||||
* if you declare them they will be available in the function body.
|
||||
For example:
|
||||
```html
|
||||
fn get(path: string, headers: map, query: map) -> string:
|
||||
"hello" + path
|
||||
|
|
@ -99,18 +109,14 @@ fn get(path: string, headers: map, query: map) -> string:
|
|||
* ```cargo run -- --watch```
|
||||
|
||||
## 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():
|
||||
| path == "/" -> list:
|
||||
service.get_all()
|
||||
| path == "/{uuid}" -> Customer?:
|
||||
service.get(uuid)?
|
||||
| path == "/" && query.firstname -> Customer?:
|
||||
service.get_by_firstname(fname)?
|
||||
| path == "/" && query.last_name -> Customer?
|
||||
service.get_by_lastname(lname)?
|
||||
| 404
|
||||
fn get() -> [Customer] | Customer? | ():
|
||||
| /. -> service.get_all()
|
||||
| /./{uuid} -> service.get(uuid)?
|
||||
| /.?{query.firstname} -> service.get_by_firstname(fname)?
|
||||
| /.?{query.last_name} -> service.get_by_lastname(lname)?
|
||||
| _ -> 404
|
||||
```
|
||||
* this may also require ADT's...
|
||||
* object/struct types: Work in Progress
|
||||
|
|
|
|||
|
|
@ -1,5 +1,2 @@
|
|||
fn get(path: string) -> string:
|
||||
add("hello", path)
|
||||
|
||||
fn add(a: string, b: string) -> string:
|
||||
a + " " + b
|
||||
service.add("hello", path)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use crate::ast_compiler::Expression::Variable;
|
||||
use crate::ast_compiler::Expression::{FunctionCall, Literal, RemoteFunctionCall, Variable};
|
||||
use crate::errors::CompilerError::{
|
||||
self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError, UnexpectedIndent,
|
||||
UninitializedVariable,
|
||||
self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError,
|
||||
UndeclaredVariable, UnexpectedIndent, UninitializedVariable,
|
||||
};
|
||||
use crate::errors::CompilerErrorAtLine;
|
||||
use crate::tokens::TokenType::{
|
||||
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,
|
||||
Bang, Bool, Char, Colon, Date, Dot, 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,
|
||||
};
|
||||
use crate::tokens::{Token, TokenType};
|
||||
use crate::value::Value;
|
||||
|
|
@ -75,6 +75,7 @@ impl AstCompiler {
|
|||
break;
|
||||
}
|
||||
}
|
||||
debug!("AST {:?}", statements);
|
||||
Ok(statements)
|
||||
} else {
|
||||
Err(self.raise(CompilerError::Failure))
|
||||
|
|
@ -296,7 +297,9 @@ impl AstCompiler {
|
|||
|
||||
fn expr_statement(&mut self) -> Result<Statement, CompilerErrorAtLine> {
|
||||
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 })
|
||||
}
|
||||
|
||||
|
|
@ -468,6 +471,27 @@ impl AstCompiler {
|
|||
debug!("{:?}", token);
|
||||
if self.match_token(vec![LeftParen]) {
|
||||
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 {
|
||||
self.variable_lookup(&token)?
|
||||
}
|
||||
|
|
@ -514,7 +538,7 @@ impl AstCompiler {
|
|||
}
|
||||
|
||||
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
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
|
|
@ -525,46 +549,80 @@ impl AstCompiler {
|
|||
}
|
||||
})
|
||||
.find(|e| e.0 == &token.lexeme)
|
||||
.ok_or_else(|| return self.raise(CompilerError::UndeclaredVariable(token.clone())))?;
|
||||
Ok(Variable {
|
||||
name: var_name.to_string(),
|
||||
var_type: var_type.clone(),
|
||||
line: token.line,
|
||||
})
|
||||
{
|
||||
Ok(Variable {
|
||||
name: var_name.to_string(),
|
||||
var_type: var_type.clone(),
|
||||
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> {
|
||||
let function_name = self.functions.get(&name).unwrap().name.lexeme.clone();
|
||||
let function = self.functions.get(&function_name).unwrap().clone();
|
||||
|
||||
let mut arguments = vec![];
|
||||
while !self.match_token(vec![RightParen]) {
|
||||
if arguments.len() >= 25 {
|
||||
return Err(self.raise(TooManyParameters));
|
||||
if let Some(function) = self.functions.get(&name).cloned() {
|
||||
let mut arguments = vec![];
|
||||
while !self.match_token(vec![RightParen]) {
|
||||
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()?;
|
||||
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;
|
||||
Ok(FunctionCall {
|
||||
line: self.peek().line,
|
||||
name,
|
||||
arguments,
|
||||
return_type: function.return_type,
|
||||
})
|
||||
} else {
|
||||
let mut arguments = vec![];
|
||||
while !self.match_token(vec![RightParen]) {
|
||||
if arguments.len() >= 25 {
|
||||
return Err(self.raise(TooManyParameters));
|
||||
}
|
||||
let arg = self.expression()?;
|
||||
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(
|
||||
|
|
@ -576,11 +634,6 @@ impl AstCompiler {
|
|||
self.advance();
|
||||
} else {
|
||||
self.had_error = true;
|
||||
// return Err(anyhow::anyhow!(
|
||||
// "{} at {:?}",
|
||||
// message.to_string(),
|
||||
// self.peek()
|
||||
// ));
|
||||
return Err(self.raise(message));
|
||||
}
|
||||
Ok(self.previous().clone())
|
||||
|
|
@ -750,6 +803,12 @@ pub enum Expression {
|
|||
arguments: Vec<Expression>,
|
||||
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 {
|
||||
|
|
@ -761,8 +820,9 @@ impl Expression {
|
|||
Self::Literal { line, .. } => *line,
|
||||
Self::List { line, .. } => *line,
|
||||
Self::Map { line, .. } => *line,
|
||||
Self::Variable { line, .. } => *line,
|
||||
Self::FunctionCall { line, .. } => *line,
|
||||
Variable { line, .. } => *line,
|
||||
FunctionCall { line, .. } => *line,
|
||||
RemoteFunctionCall { line, .. } => *line,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -838,8 +898,9 @@ impl Expression {
|
|||
UnsignedInteger
|
||||
}
|
||||
}
|
||||
Self::Variable { var_type, .. } => var_type.clone(),
|
||||
Self::FunctionCall { return_type, .. } => return_type.clone(),
|
||||
Variable { var_type, .. } => var_type.clone(),
|
||||
FunctionCall { return_type, .. } => return_type.clone(),
|
||||
RemoteFunctionCall { .. } => TokenType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use crate::ast_compiler::{Expression, Function, Statement};
|
||||
use crate::chunk::Chunk;
|
||||
use crate::errors::{CompilerErrorAtLine};
|
||||
use crate::errors::CompilerErrorAtLine;
|
||||
use crate::tokens::TokenType;
|
||||
use crate::value::Value;
|
||||
use crate::vm::{
|
||||
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_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR,
|
||||
OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
|
||||
OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL,
|
||||
OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ pub(crate) fn compile_in_namespace(
|
|||
let compiler = Compiler::new(name);
|
||||
let chunk = compiler.compile(ast, registry, name)?;
|
||||
let qname = if let Some(namespace) = namespace {
|
||||
format!("{}.{}", namespace, "main")
|
||||
format!("{}/{}", namespace, "main")
|
||||
} else {
|
||||
"main".to_string()
|
||||
};
|
||||
|
|
@ -113,7 +112,7 @@ impl Compiler {
|
|||
let function_name = function.name.lexeme.clone();
|
||||
let compiled_function = compile_function(function, registry, namespace)?;
|
||||
registry.insert(
|
||||
format!("{}.{}", self.chunk.name, function_name),
|
||||
format!("{}/{}", self.chunk.name, function_name),
|
||||
compiled_function,
|
||||
);
|
||||
}
|
||||
|
|
@ -138,7 +137,21 @@ impl Compiler {
|
|||
let name_index = self
|
||||
.chunk
|
||||
.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 {
|
||||
self.compile_expression(namespace, argument, registry)?;
|
||||
|
|
|
|||
|
|
@ -181,4 +181,9 @@ m"#);
|
|||
fn add_hex_ints(){
|
||||
assert_eq!(run(r#"0x10 + 0x20"#), Ok(Value::U32(48)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package(){
|
||||
assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ pub enum CompilerError {
|
|||
IncompatibleTypes(TokenType, TokenType),
|
||||
#[error("Error parsing number {0}")]
|
||||
ParseError(String),
|
||||
#[error("Undeclared variable: {0:?}")]
|
||||
UndeclaredVariable(Token),
|
||||
#[error("Undeclared variable: '{0}'")]
|
||||
UndeclaredVariable(String),
|
||||
#[error("Unexpected identifier")]
|
||||
UnexpectedIdentifier,
|
||||
#[error("Unterminated {0}")]
|
||||
|
|
@ -63,6 +63,8 @@ pub enum CompilerError {
|
|||
KeywordNotAllowedAsIdentifier(TokenType),
|
||||
#[error("Crud does not support numbers above 2^64")]
|
||||
Overflow,
|
||||
#[error("Undeclared function: '{0}'")]
|
||||
FunctionNotFound(String)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
|
|
|
|||
19
src/lib.rs
19
src/lib.rs
|
|
@ -4,10 +4,10 @@ use crate::errors::CrudLangError::Platform;
|
|||
use crate::scanner::scan;
|
||||
use crate::value::Value;
|
||||
use crate::vm::interpret;
|
||||
use arc_swap::ArcSwap;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use arc_swap::ArcSwap;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub mod ast_compiler;
|
||||
|
|
@ -15,13 +15,13 @@ pub mod bytecode_compiler;
|
|||
pub mod chunk;
|
||||
mod compiler_tests;
|
||||
pub mod errors;
|
||||
pub mod file_watch;
|
||||
mod keywords;
|
||||
pub mod repl;
|
||||
pub mod scanner;
|
||||
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();
|
||||
|
|
@ -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()) {
|
||||
let path = entry.path().to_str().unwrap();
|
||||
if path.ends_with(".crud") {
|
||||
print!("-- Compiling ");
|
||||
print!("-- Compiling {} -- ", path);
|
||||
let source = fs::read_to_string(path).map_err(map_underlying())?;
|
||||
let tokens = scan(&source)?;
|
||||
match ast_compiler::compile(Some(&path), tokens) {
|
||||
Ok(statements) => {
|
||||
println!("{}",path);
|
||||
println!("{}", path);
|
||||
let path = path.strip_prefix(source_dir).unwrap().replace(".crud", "");
|
||||
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;
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
println!();
|
||||
Ok(registry)
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +53,13 @@ pub fn map_underlying() -> fn(std::io::Error) -> CrudLangError {
|
|||
|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> {
|
||||
let tokens = scan(src)?;
|
||||
let mut registry = HashMap::new();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use notify::Watcher;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use arc_swap::ArcSwap;
|
||||
use log::{debug, info};
|
||||
|
||||
/// A simple CLI tool to greet users
|
||||
#[derive(Parser, Debug)]
|
||||
|
|
@ -58,7 +59,7 @@ async fn main() -> Result<(), CrudLangError> {
|
|||
);
|
||||
|
||||
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())?;
|
||||
|
|
@ -92,13 +93,14 @@ async fn handle_any(
|
|||
})
|
||||
.unwrap_or_default();
|
||||
let component = format!("{}/web", &uri.path());
|
||||
let function_qname = format!("{}.{}", component, method);
|
||||
let function_qname = format!("{}/{}", component, method);
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
for (k, v) in req.headers().iter() {
|
||||
headers.insert(k.to_string(), v.to_str().unwrap().to_string());
|
||||
}
|
||||
let path = &req.uri().to_string();
|
||||
info!("invoked {:?} => {}",req, function_qname);
|
||||
match interpret_async(
|
||||
state.registry.load(),
|
||||
&function_qname,
|
||||
|
|
|
|||
42
src/repl.rs
42
src/repl.rs
|
|
@ -1,13 +1,15 @@
|
|||
use crate::chunk::Chunk;
|
||||
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::io;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
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!(":h for help");
|
||||
loop {
|
||||
|
|
@ -20,9 +22,27 @@ pub fn start(registry: Guard<Arc<HashMap<String, Chunk>>>) -> Result<(), CrudLan
|
|||
let input = input.trim();
|
||||
match input {
|
||||
":h" => help(),
|
||||
":le" => list_endpoints(registry.clone()),
|
||||
":lf" => list_endpoints(registry.clone()),
|
||||
_ => {}
|
||||
":le" => list_endpoints(registry.load().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);
|
||||
// if input == ":q" {
|
||||
|
|
@ -38,16 +58,14 @@ fn list_endpoints(registry: Arc<HashMap<String, Chunk>>) {
|
|||
.iter()
|
||||
.filter(|(k, _)| k.contains("get"))
|
||||
.for_each(|(k, _)| {
|
||||
println!("{}", k);//number
|
||||
println!("{}", k); //number
|
||||
});
|
||||
}
|
||||
|
||||
fn list_functions(registry: Arc<HashMap<String, Chunk>>) {
|
||||
registry
|
||||
.iter()
|
||||
.for_each(|(k, _)| {
|
||||
println!("{}", k);//number
|
||||
});
|
||||
registry.iter().for_each(|(k, _)| {
|
||||
println!("{}", k); //number
|
||||
});
|
||||
}
|
||||
|
||||
fn help() {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ pub enum TokenType {
|
|||
True,
|
||||
U32,
|
||||
U64,
|
||||
Unknown,
|
||||
Void,
|
||||
While,
|
||||
}
|
||||
|
|
@ -152,6 +153,7 @@ impl fmt::Display for TokenType {
|
|||
TokenType::Slash => write!(f, "/"),
|
||||
TokenType::Star => write!(f, "*"),
|
||||
TokenType::True => write!(f, "true"),
|
||||
TokenType::Unknown => write!(f, "?"),
|
||||
TokenType::Void => write!(f, "()"),
|
||||
TokenType::While => write!(f, "while"),
|
||||
TokenType::SignedInteger => write!(f, "i32/64"),
|
||||
|
|
|
|||
|
|
@ -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::Char(c)) => Ok(Value::String(format!("{}{}", s, c))),
|
||||
(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))),
|
||||
//enum?
|
||||
|
|
|
|||
32
src/vm.rs
32
src/vm.rs
|
|
@ -7,6 +7,7 @@ use axum::http::{Uri};
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use arc_swap::Guard;
|
||||
use reqwest::get;
|
||||
use tracing::debug;
|
||||
|
||||
pub struct Vm {
|
||||
|
|
@ -30,7 +31,7 @@ pub fn interpret(registry: Guard<Arc<HashMap<String, Chunk>>>, function: &str) -
|
|||
error_occurred: false,
|
||||
registry: registry.clone(),
|
||||
};
|
||||
vm.run(&chunk)
|
||||
vm.run(&get_context(function), &chunk)
|
||||
}
|
||||
|
||||
pub async fn interpret_async(
|
||||
|
|
@ -40,7 +41,6 @@ pub async fn interpret_async(
|
|||
query_params: HashMap<String, String>,
|
||||
headers: HashMap<String, String>,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
//TODO convert request to arguments
|
||||
let chunk = registry.get(function);
|
||||
if let Some(chunk) = chunk {
|
||||
let mut vm = Vm {
|
||||
|
|
@ -48,7 +48,7 @@ pub async fn interpret_async(
|
|||
stack: vec![],
|
||||
local_vars: HashMap::new(),
|
||||
error_occurred: false,
|
||||
registry:registry.clone(),
|
||||
registry: registry.clone(),
|
||||
};
|
||||
vm.local_vars
|
||||
.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)));
|
||||
vm.local_vars
|
||||
.insert("headers".to_string(), Value::Map(value_map(headers)));
|
||||
vm.run(&chunk)
|
||||
vm.run(&get_context(function), &chunk)
|
||||
} else {
|
||||
Err(RuntimeError::FunctionNotFound(function.to_string()))
|
||||
}
|
||||
|
|
@ -87,10 +87,10 @@ impl Vm {
|
|||
for (_, name) in chunk.vars.iter() {
|
||||
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 {
|
||||
if self.error_occurred {
|
||||
return Err(Something);
|
||||
|
|
@ -189,7 +189,6 @@ impl Vm {
|
|||
let (_, name_index) = chunk.vars.get(var_index).unwrap();
|
||||
let value = self.local_vars.get(name_index).unwrap();
|
||||
self.push(value.clone()); // not happy , take ownership, no clone
|
||||
debug!("after get {:?}", self.stack);
|
||||
}
|
||||
OP_CALL => {
|
||||
let function_name_index = self.read(chunk);
|
||||
|
|
@ -203,9 +202,14 @@ impl Vm {
|
|||
args.reverse();
|
||||
|
||||
let function_name = chunk.constants[function_name_index].to_string();
|
||||
let function_chunk = self.registry.get(&function_name).unwrap();
|
||||
let result = interpret_function(function_chunk, args)?;
|
||||
self.push(result);
|
||||
let function_chunk = self.registry.get(&function_name)
|
||||
.or_else(|| self.registry.get(&format!("{}{}", context, function_name)));
|
||||
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_ADD: u16 = 2;
|
||||
pub const OP_SUBTRACT: u16 = 3;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue