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
|
# 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
|
||||||
|
|
@ -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.
|
- 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
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
19
src/lib.rs
19
src/lib.rs
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
42
src/repl.rs
42
src/repl.rs
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
|
||||||
32
src/vm.rs
32
src/vm.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue