Compare commits
10 commits
e5b03d9037
...
c4dd82d191
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4dd82d191 | ||
|
|
611d635df5 | ||
|
|
05279201c8 | ||
|
|
215dc13797 | ||
|
|
3d17182c75 | ||
|
|
a321c198ad | ||
|
|
491a8a598c | ||
|
|
f563a8048a | ||
|
|
05d241b97a | ||
|
|
8eeb09855f |
31 changed files with 550 additions and 162 deletions
81
Cargo.lock
generated
81
Cargo.lock
generated
|
|
@ -163,6 +163,29 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bb8"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "457d7ed3f888dfd2c7af56d4975cade43c622f74bdcddfed6d4352f57acc6310"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"parking_lot",
|
||||||
|
"portable-atomic",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bb8-postgres"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e570e6557cd0f88d28d32afa76644873271a70dc22656df565b2021c4036aa9c"
|
||||||
|
dependencies = [
|
||||||
|
"bb8",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
|
@ -227,6 +250,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
@ -304,9 +328,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"typenum",
|
"typenum",
|
||||||
|
|
@ -492,9 +516,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.9"
|
version = "0.14.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
|
@ -935,13 +959,13 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.10"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1207,7 +1231,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
@ -1255,6 +1279,26 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres"
|
||||||
|
version = "0.19.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postgres-protocol"
|
name = "postgres-protocol"
|
||||||
version = "0.6.9"
|
version = "0.6.9"
|
||||||
|
|
@ -1280,6 +1324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095"
|
checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
]
|
]
|
||||||
|
|
@ -1364,6 +1409,15 @@ dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.2"
|
version = "1.12.2"
|
||||||
|
|
@ -1870,12 +1924,15 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"axum",
|
"axum",
|
||||||
|
"bb8",
|
||||||
|
"bb8-postgres",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
"notify",
|
"notify",
|
||||||
|
"postgres",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -2147,18 +2204,18 @@ checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
|
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-properties"
|
name = "unicode-properties"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ log4rs = "1.4.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
tokio = { version = "1.47", features = ["full"] }
|
tokio = { version = "1.47", features = ["full"] }
|
||||||
tokio-postgres = "0.7"
|
tokio-postgres = "0.7"
|
||||||
chrono = "0.4.42"
|
chrono = { version= "0.4", features = ["serde"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
reqwest = { version = "0.12", features = ["json", "multipart"] }
|
reqwest = { version = "0.12", features = ["json", "multipart"] }
|
||||||
tower-http = { version = "0.6", features = ["fs"] }
|
tower-http = { version = "0.6", features = ["fs"] }
|
||||||
|
|
@ -26,3 +26,6 @@ clap = { version = "4.5.51", features = ["derive"] }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
regex = "1.12.2"
|
regex = "1.12.2"
|
||||||
|
postgres = { version = "0.19", features = ["with-chrono-0_4"] }
|
||||||
|
bb8 = "0.9"
|
||||||
|
bb8-postgres = "0.9"
|
||||||
12
README.md
12
README.md
|
|
@ -1,7 +1,8 @@
|
||||||
# tipi-lang
|
# tipi-lang
|
||||||
|
|
||||||

|
<img src="icon.svg" width="400" alt="tipi"/>
|
||||||
Tipi/teepee means: 'the place where they live' in Sioux/Dakota.
|
|
||||||
|
Tipi/teepee means: 'the place where they live/dwell' in Sioux/Dakota.
|
||||||
see https://sesquiotic.com/2013/02/23/teepee/
|
see https://sesquiotic.com/2013/02/23/teepee/
|
||||||
|
|
||||||
Borrowing from that: 'the place where http lives'.
|
Borrowing from that: 'the place where http lives'.
|
||||||
|
|
@ -130,8 +131,9 @@ fn get() -> [Customer] | Customer? | ():
|
||||||
* test support
|
* test support
|
||||||
|
|
||||||
## What about performance?
|
## What about performance?
|
||||||
* Clueless really! We'll see.
|
* Not optimizing for performance yet. First results look pretty bad.
|
||||||
* But it is written in rust
|
* as in: purely recursive fibonacci of 38 (the 38th fibonacci number) takes 42 seconds on my machine (in release mode) :sad-face.
|
||||||
|
* That said the idea is that a lot of processing is done within the virtual machine.
|
||||||
* And it has no GC
|
* And it has no GC
|
||||||
* So, maybe it will compete with python?
|
* So, maybe it will compete with python?
|
||||||
|
|
||||||
|
|
@ -231,3 +233,5 @@ fn add(a: string, b: string) -> string:
|
||||||
ISSUES
|
ISSUES
|
||||||
* Make everything an expression. If is a statement and so it can not be type checked
|
* Make everything an expression. If is a statement and so it can not be type checked
|
||||||
* improve indenting
|
* improve indenting
|
||||||
|
|
||||||
|
WIP guards
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ fn fib(n: u64):
|
||||||
else:
|
else:
|
||||||
fib(n-1) + fib(n-2)
|
fib(n-1) + fib(n-2)
|
||||||
|
|
||||||
println(fib(10))
|
println("fib = " + fib(38))
|
||||||
6
examples/web/README.md
Normal file
6
examples/web/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
* Example with postgres running
|
||||||
|
* run ```cargo run -- --source examples/web --watch --postgres <connect_string>```
|
||||||
|
* the connect_string is directly passed to bb8 like [here](https://github.com/djc/bb8/blob/main/postgres/examples/static_select.rs)
|
||||||
|
* see ```main.tp``` for the database queries that it does on startup
|
||||||
|
* and head to http://localhost:3000/api/customer
|
||||||
|
* optionally, adjust the database ddl, update the query in db.tp, and recheck the output
|
||||||
|
|
@ -1,5 +1,2 @@
|
||||||
fn get(id: u32) -> Customer:
|
fn get_all() -> list:
|
||||||
select id, first_name, last_name from customers where id = :id
|
sql("select id, first_name, last_name from customers")
|
||||||
|
|
||||||
fn save(c: Customer):
|
|
||||||
insert into customers values(id, first_name, last_name) values(:c.id, :c.first_name, :c.last_name)
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,2 @@
|
||||||
fn get(id: u32) -> Customer:
|
fn get_all() -> string:
|
||||||
let customer = dao.get(id)
|
db::get_all()
|
||||||
customer.date_fetched = current_date
|
|
||||||
|
|
||||||
fn add(customer: Customer):
|
|
||||||
dao.save(customer)
|
|
||||||
|
|
@ -1,17 +1,2 @@
|
||||||
fn get():
|
fn get() -> list:
|
||||||
| path == "/" -> list:
|
service::get_all()
|
||||||
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 post(customer: Customer):
|
|
||||||
service.add(customer)
|
|
||||||
|
|
||||||
|
|
||||||
fn put(customer: Customer):
|
|
||||||
service.update(customer)
|
|
||||||
3
examples/web/main.tp
Normal file
3
examples/web/main.tp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
println("Creating the customers table")
|
||||||
|
sql("create table if not exists customers(id serial primary key, first_name varchar(50), last_name varchar(50))")
|
||||||
|
sql("insert into customers (first_name,last_name) values ('first', 'last')")
|
||||||
|
|
@ -1,18 +1,41 @@
|
||||||
use std::cell::{RefMut};
|
use crate::DB_POOL;
|
||||||
use crate::builtins::{FunctionMap, Signature, add};
|
use crate::builtins::{FunctionMap, Signature, add};
|
||||||
use crate::compiler::tokens::TokenType::{DateTime, StringType, Void};
|
use crate::compiler::ast_pass::Parameter;
|
||||||
|
use crate::compiler::tokens::TokenType;
|
||||||
|
use crate::compiler::tokens::TokenType::{DateTime, ListType, StringType, Void};
|
||||||
use crate::errors::RuntimeError;
|
use crate::errors::RuntimeError;
|
||||||
use crate::value::Value;
|
use crate::value::{Value, string};
|
||||||
|
use std::cell::RefMut;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use crate::compiler::ast_pass::Parameter;
|
use tokio_postgres::types::Type;
|
||||||
|
|
||||||
pub(crate) static GLOBAL_FUNCTIONS: LazyLock<FunctionMap> = LazyLock::new(|| {
|
pub(crate) static GLOBAL_FUNCTIONS: LazyLock<FunctionMap> = LazyLock::new(|| {
|
||||||
let mut global_functions: FunctionMap = HashMap::new();
|
let mut global_functions: FunctionMap = HashMap::new();
|
||||||
let functions = &mut global_functions;
|
let functions = &mut global_functions;
|
||||||
add(functions, "now", Signature::new(vec![], DateTime, now));
|
add(functions, "now", Signature::new(vec![], DateTime, now));
|
||||||
add(functions, "print", Signature::new(vec![Parameter::new("text", StringType)], Void, print));
|
add(
|
||||||
add(functions, "println", Signature::new(vec![Parameter::new("text", StringType)], Void, println));
|
functions,
|
||||||
|
"print",
|
||||||
|
Signature::new(vec![Parameter::new("text", StringType)], Void, print),
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
functions,
|
||||||
|
"println",
|
||||||
|
Signature::new(vec![Parameter::new("text", StringType)], Void, println),
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
functions,
|
||||||
|
"sql",
|
||||||
|
Signature::new(
|
||||||
|
vec![
|
||||||
|
Parameter::new("query", StringType),
|
||||||
|
Parameter::varargs(TokenType::Any),
|
||||||
|
],
|
||||||
|
ListType,
|
||||||
|
sql,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
global_functions
|
global_functions
|
||||||
});
|
});
|
||||||
|
|
@ -31,7 +54,55 @@ fn print(_self_val: RefMut<Value>, args: Vec<Value>) -> Result<Value, RuntimeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now(_self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
fn now(_self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
Ok(Value::DateTime(Box::new(
|
Ok(Value::DateTime(Box::new(chrono::Utc::now())))
|
||||||
chrono::Utc::now(),
|
}
|
||||||
)))
|
|
||||||
|
fn sql(_self_val: RefMut<Value>, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
|
let result = tokio::task::block_in_place(|| {
|
||||||
|
tokio::runtime::Handle::current().block_on(async {
|
||||||
|
let mut conn = DB_POOL.get()
|
||||||
|
.expect(format!("Error running query '{}'. Did you add --postgres on the commandline?",args[0]).as_str())
|
||||||
|
.get().await.unwrap();
|
||||||
|
let result = conn.query(&args[0].to_string(), &[]).await.unwrap();
|
||||||
|
let mut columns = vec![];
|
||||||
|
|
||||||
|
if let Some(first_row) = result.first() {
|
||||||
|
let retrieved_columns = first_row.columns();
|
||||||
|
for column in retrieved_columns {
|
||||||
|
columns.push((column.name(), column.type_()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rows = vec![];
|
||||||
|
for result_row in &result {
|
||||||
|
let mut row: HashMap<Value, Value> = HashMap::new();
|
||||||
|
for (index, _) in result_row.columns().iter().enumerate() {
|
||||||
|
let column_name = columns.get(index).unwrap().0;
|
||||||
|
let column_type = columns.get(index).unwrap().1;
|
||||||
|
let value = match column_type {
|
||||||
|
&Type::BOOL => Value::Bool(result_row.get(index)),
|
||||||
|
&Type::INT2 | &Type::INT4 => Value::I32(result_row.get(index)),
|
||||||
|
&Type::INT8 => Value::I64(result_row.get(index)),
|
||||||
|
&Type::FLOAT4 => Value::F32(result_row.get(index)),
|
||||||
|
&Type::FLOAT8 | &Type::NUMERIC => Value::F64(result_row.get(index)),
|
||||||
|
&Type::CHAR
|
||||||
|
| &Type::VARCHAR
|
||||||
|
| &Type::TEXT
|
||||||
|
| &Type::BPCHAR
|
||||||
|
| &Type::NAME => Value::String(result_row.get(index)),
|
||||||
|
&Type::TIMESTAMP | &Type::TIMESTAMPTZ => {
|
||||||
|
Value::DateTime(Box::new(result_row.get(index)))
|
||||||
|
}
|
||||||
|
&Type::UUID => Value::Uuid(result_row.get(index)),
|
||||||
|
_ => unimplemented!("database type {:?}", column_type),
|
||||||
|
};
|
||||||
|
|
||||||
|
row.insert(string(column_name), value);
|
||||||
|
}
|
||||||
|
rows.push(Value::Map(row));
|
||||||
|
}
|
||||||
|
rows
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(Value::List(result))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ pub(crate) fn list_functions() -> FunctionMap {
|
||||||
"remove",
|
"remove",
|
||||||
Signature::new(vec![Parameter::new("index", U64)], U64, remove),
|
Signature::new(vec![Parameter::new("index", U64)], U64, remove),
|
||||||
);
|
);
|
||||||
|
add(
|
||||||
|
functions,
|
||||||
|
"sort",
|
||||||
|
Signature::new(vec![], U64, sort),
|
||||||
|
);
|
||||||
list_functions
|
list_functions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +60,19 @@ fn len(self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sort(mut self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
|
if let Value::List(list) = self_val.deref_mut() {
|
||||||
|
if list.windows(2).any(|w| w[0].sort_cmp(&w[1]).is_none()) {
|
||||||
|
return Err(RuntimeError::NotSortable);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort_by(|a, b| a.sort_cmp(b).unwrap());
|
||||||
|
Ok(Value::Void)
|
||||||
|
} else {
|
||||||
|
Err(expected_a_list())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn expected_a_list() -> RuntimeError {
|
fn expected_a_list() -> RuntimeError {
|
||||||
expected("list")
|
expected("list")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
src/builtins/map.rs
Normal file
64
src/builtins/map.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::builtins::{FunctionMap, Signature, add, expected};
|
||||||
|
use crate::compiler::ast_pass::Parameter;
|
||||||
|
use crate::compiler::tokens::TokenType;
|
||||||
|
use crate::compiler::tokens::TokenType::{U64, Void};
|
||||||
|
use crate::errors::RuntimeError;
|
||||||
|
use crate::value::{Value, u64};
|
||||||
|
use std::cell::RefMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
pub(crate) fn map_functions() -> FunctionMap {
|
||||||
|
let mut map_functions: FunctionMap = HashMap::new();
|
||||||
|
let functions = &mut map_functions;
|
||||||
|
add(functions, "len", Signature::new(vec![], U64, len));
|
||||||
|
add(
|
||||||
|
functions,
|
||||||
|
"insert",
|
||||||
|
Signature::new(
|
||||||
|
vec![
|
||||||
|
Parameter::new("key", TokenType::Any),
|
||||||
|
Parameter::new("value", TokenType::Any),
|
||||||
|
],
|
||||||
|
Void,
|
||||||
|
insert,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
functions,
|
||||||
|
"remove",
|
||||||
|
Signature::new(vec![Parameter::new("key", TokenType::Any)], Void, remove),
|
||||||
|
);
|
||||||
|
map_functions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(mut self_val: RefMut<Value>, mut args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
|
if let Value::Map(map) = self_val.deref_mut() {
|
||||||
|
let key = args.remove(0);
|
||||||
|
map.remove(&key);
|
||||||
|
Ok(Value::Void)
|
||||||
|
} else {
|
||||||
|
Err(expected_a_map())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(mut self_val: RefMut<Value>, mut args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
|
if let Value::Map(map) = self_val.deref_mut() {
|
||||||
|
map.insert(args.remove(0),args.remove(0));
|
||||||
|
Ok(Value::Void)
|
||||||
|
} else {
|
||||||
|
Err(expected_a_map())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(self_val: RefMut<Value>, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
|
if let Value::Map(map) = self_val.deref() {
|
||||||
|
Ok(u64(map.len() as u64))
|
||||||
|
} else {
|
||||||
|
Err(expected_a_map())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_a_map() -> RuntimeError {
|
||||||
|
expected("map")
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
mod string;
|
|
||||||
mod list;
|
|
||||||
pub(crate) mod globals;
|
pub(crate) mod globals;
|
||||||
|
mod list;
|
||||||
|
mod map;
|
||||||
|
mod string;
|
||||||
|
|
||||||
use std::cell::{RefCell, RefMut};
|
use crate::builtins::list::list_functions;
|
||||||
|
use crate::builtins::map::map_functions;
|
||||||
use crate::builtins::string::string_functions;
|
use crate::builtins::string::string_functions;
|
||||||
use crate::errors::{CompilerError, RuntimeError};
|
use crate::compiler::ast_pass::Parameter;
|
||||||
use crate::compiler::tokens::TokenType;
|
use crate::compiler::tokens::TokenType;
|
||||||
|
use crate::errors::{CompilerError, RuntimeError};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use crate::compiler::ast_pass::Parameter;
|
|
||||||
use crate::builtins::list::list_functions;
|
|
||||||
|
|
||||||
pub(crate) struct Signature {
|
pub(crate) struct Signature {
|
||||||
pub(crate) parameters: Vec<Parameter>,
|
pub(crate) parameters: Vec<Parameter>,
|
||||||
|
|
@ -32,8 +34,12 @@ impl Signature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn arity(&self) -> usize {
|
pub(crate) fn arity(&self) -> u8 {
|
||||||
self.parameters.len()
|
self.parameters.len() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has_varargs(&self) -> bool {
|
||||||
|
!self.parameters.is_empty() && self.parameters.last().unwrap().name.lexeme == "varargs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +53,7 @@ static FUNCTIONS: LazyLock<FunctionTable> = LazyLock::new(|| {
|
||||||
let mut table: FunctionTable = HashMap::new();
|
let mut table: FunctionTable = HashMap::new();
|
||||||
table.insert("string".to_string(), string_functions());
|
table.insert("string".to_string(), string_functions());
|
||||||
table.insert("list".to_string(), list_functions());
|
table.insert("list".to_string(), list_functions());
|
||||||
|
table.insert("map".to_string(), map_functions());
|
||||||
|
|
||||||
table
|
table
|
||||||
});
|
});
|
||||||
|
|
@ -55,7 +62,10 @@ pub(crate) fn add(m: &mut FunctionMap, name: &str, method: Signature) {
|
||||||
m.insert(name.to_string(), method);
|
m.insert(name.to_string(), method);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lookup(type_name: &str, method_name: &str) -> Result<&'static Signature, CompilerError> {
|
pub(crate) fn lookup(
|
||||||
|
type_name: &str,
|
||||||
|
method_name: &str,
|
||||||
|
) -> Result<&'static Signature, CompilerError> {
|
||||||
FUNCTIONS
|
FUNCTIONS
|
||||||
.get(type_name)
|
.get(type_name)
|
||||||
.and_then(|methods| methods.get(method_name))
|
.and_then(|methods| methods.get(method_name))
|
||||||
|
|
@ -68,7 +78,9 @@ pub(crate) fn call(
|
||||||
self_val: Rc<RefCell<Value>>,
|
self_val: Rc<RefCell<Value>>,
|
||||||
args: Vec<Value>,
|
args: Vec<Value>,
|
||||||
) -> Result<Value, RuntimeError> {
|
) -> Result<Value, RuntimeError> {
|
||||||
(lookup(type_name,method_name).map_err(|e|RuntimeError::FunctionNotFound(e.to_string()))?.function)(self_val.borrow_mut(), args)
|
(lookup(type_name, method_name)
|
||||||
|
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?
|
||||||
|
.function)(self_val.borrow_mut(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expected(expected_type: &str) -> RuntimeError {
|
pub(crate) fn expected(expected_type: &str) -> RuntimeError {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use crate::value::Value;
|
||||||
use crate::{AsmRegistry, SymbolTable};
|
use crate::{AsmRegistry, SymbolTable};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use crate::compiler::namespace::Namespace;
|
||||||
|
|
||||||
pub fn compile(
|
pub fn compile(
|
||||||
qualified_name: Option<&str>,
|
qualified_name: Option<&str>,
|
||||||
|
|
@ -30,7 +31,7 @@ pub fn compile_function(
|
||||||
function: &Function,
|
function: &Function,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
) -> Result<AsmChunk, CompilerErrorAtLine> {
|
) -> Result<AsmChunk, CompilerErrorAtLine> {
|
||||||
let fn_name = &function.name.lexeme;
|
let fn_name = &function.name.lexeme;
|
||||||
let mut compiler = AsmPass::new(fn_name);
|
let mut compiler = AsmPass::new(fn_name);
|
||||||
|
|
@ -53,7 +54,7 @@ pub fn compile_in_namespace(
|
||||||
) -> Result<(), CompilerErrorAtLine> {
|
) -> Result<(), CompilerErrorAtLine> {
|
||||||
let name = namespace.unwrap_or("main");
|
let name = namespace.unwrap_or("main");
|
||||||
let mut compiler = AsmPass::new(name);
|
let mut compiler = AsmPass::new(name);
|
||||||
let chunk = compiler.compile(ast, symbols, registry, name)?;
|
let chunk = compiler.compile(ast, symbols, registry, &Namespace::new(name))?;
|
||||||
registry.insert(name.to_string(), chunk);
|
registry.insert(name.to_string(), chunk);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +137,7 @@ impl AsmPass {
|
||||||
ast: &Vec<Statement>,
|
ast: &Vec<Statement>,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
) -> Result<AsmChunk, CompilerErrorAtLine> {
|
) -> Result<AsmChunk, CompilerErrorAtLine> {
|
||||||
self.compile_statements(ast, symbols, registry, namespace)?;
|
self.compile_statements(ast, symbols, registry, namespace)?;
|
||||||
self.emit(Return);
|
self.emit(Return);
|
||||||
|
|
@ -151,7 +152,7 @@ impl AsmPass {
|
||||||
ast: &Vec<Statement>,
|
ast: &Vec<Statement>,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
) -> Result<(), CompilerErrorAtLine> {
|
) -> Result<(), CompilerErrorAtLine> {
|
||||||
for statement in ast {
|
for statement in ast {
|
||||||
self.compile_statement(statement, symbols, registry, namespace)?;
|
self.compile_statement(statement, symbols, registry, namespace)?;
|
||||||
|
|
@ -165,7 +166,7 @@ impl AsmPass {
|
||||||
statement: &Statement,
|
statement: &Statement,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
) -> Result<(), CompilerErrorAtLine> {
|
) -> Result<(), CompilerErrorAtLine> {
|
||||||
self.current_line = statement.line();
|
self.current_line = statement.line();
|
||||||
match statement {
|
match statement {
|
||||||
|
|
@ -193,7 +194,7 @@ impl AsmPass {
|
||||||
|
|
||||||
fn compile_expression(
|
fn compile_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
expression: &Expression,
|
expression: &Expression,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
|
|
@ -258,8 +259,8 @@ impl AsmPass {
|
||||||
Expression::FunctionCall {
|
Expression::FunctionCall {
|
||||||
name, arguments, ..
|
name, arguments, ..
|
||||||
} => {
|
} => {
|
||||||
let qname = format!("{}/{}", namespace, name);
|
let qname = format!("{}/{}", namespace.pop(), name.replace("::","/"));
|
||||||
let function = symbols.get(&qname).or(symbols.get(name));
|
let function = symbols.get(&qname);
|
||||||
match function {
|
match function {
|
||||||
Some(Symbol::Function { parameters, .. }) => {
|
Some(Symbol::Function { parameters, .. }) => {
|
||||||
let name_index = self.chunk.find_constant(&qname).unwrap_or_else(|| {
|
let name_index = self.chunk.find_constant(&qname).unwrap_or_else(|| {
|
||||||
|
|
@ -270,7 +271,7 @@ impl AsmPass {
|
||||||
namespace, symbols, registry, arguments, parameters,
|
namespace, symbols, registry, arguments, parameters,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.emit(Call(name_index, arguments.len()));
|
self.emit(Call(name_index, arguments.len() as u8));
|
||||||
}
|
}
|
||||||
// constructor function
|
// constructor function
|
||||||
Some(Symbol::Object { fields, .. }) => {
|
Some(Symbol::Object { fields, .. }) => {
|
||||||
|
|
@ -280,7 +281,7 @@ impl AsmPass {
|
||||||
self.get_arguments_in_order(
|
self.get_arguments_in_order(
|
||||||
namespace, symbols, registry, arguments, fields,
|
namespace, symbols, registry, arguments, fields,
|
||||||
)?;
|
)?;
|
||||||
self.emit(Call(name_index, arguments.len()));
|
self.emit(Call(name_index, arguments.len() as u8));
|
||||||
}
|
}
|
||||||
// maybe global function
|
// maybe global function
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -295,7 +296,12 @@ impl AsmPass {
|
||||||
let name_index = self.chunk.find_constant(name).unwrap_or_else(|| {
|
let name_index = self.chunk.find_constant(name).unwrap_or_else(|| {
|
||||||
self.chunk.add_constant(Value::String(name.to_string()))
|
self.chunk.add_constant(Value::String(name.to_string()))
|
||||||
});
|
});
|
||||||
self.emit(Call(name_index, fun.arity()));
|
let arity = if fun.has_varargs() {
|
||||||
|
arguments.len() as u8
|
||||||
|
} else {
|
||||||
|
fun.arity()
|
||||||
|
};
|
||||||
|
self.emit(Call(name_index, arity));
|
||||||
} else {
|
} else {
|
||||||
return Err(self
|
return Err(self
|
||||||
.error_at_line(CompilerError::FunctionNotFound(name.to_string())));
|
.error_at_line(CompilerError::FunctionNotFound(name.to_string())));
|
||||||
|
|
@ -325,7 +331,7 @@ impl AsmPass {
|
||||||
});
|
});
|
||||||
let signature =
|
let signature =
|
||||||
lookup(&receiver_type, method_name).map_err(|e| self.error_at_line(e))?;
|
lookup(&receiver_type, method_name).map_err(|e| self.error_at_line(e))?;
|
||||||
if signature.arity() != arguments.len() {
|
if signature.arity() != arguments.len() as u8 && !signature.has_varargs() {
|
||||||
return Err(self.error_at_line(CompilerError::IllegalArgumentsException(
|
return Err(self.error_at_line(CompilerError::IllegalArgumentsException(
|
||||||
format!("{}.{}", receiver_type, method_name),
|
format!("{}.{}", receiver_type, method_name),
|
||||||
signature.parameters.len(),
|
signature.parameters.len(),
|
||||||
|
|
@ -339,7 +345,7 @@ impl AsmPass {
|
||||||
arguments,
|
arguments,
|
||||||
&signature.parameters,
|
&signature.parameters,
|
||||||
)?;
|
)?;
|
||||||
self.emit(CallBuiltin(name_index, type_index, arguments.len()));
|
self.emit(CallBuiltin(name_index, type_index, arguments.len() as u8));
|
||||||
}
|
}
|
||||||
Expression::Variable { name, .. } => {
|
Expression::Variable { name, .. } => {
|
||||||
let name_index = self.vars.get(name);
|
let name_index = self.vars.get(name);
|
||||||
|
|
@ -444,11 +450,7 @@ impl AsmPass {
|
||||||
}
|
}
|
||||||
Expression::MapGet { .. } => {}
|
Expression::MapGet { .. } => {}
|
||||||
Expression::FieldGet { .. } => {}
|
Expression::FieldGet { .. } => {}
|
||||||
Expression::Range { lower, upper, .. } => {
|
Expression::Range { lower, .. } => {}
|
||||||
// opposite order, because we have to assign last one first to the loop variable
|
|
||||||
// self.compile_expression(namespace, upper, symbols, registry)?;
|
|
||||||
// self.compile_expression(namespace, lower, symbols, registry)?;
|
|
||||||
}
|
|
||||||
Expression::ForStatement {
|
Expression::ForStatement {
|
||||||
loop_var,
|
loop_var,
|
||||||
range,
|
range,
|
||||||
|
|
@ -495,7 +497,7 @@ impl AsmPass {
|
||||||
// named parameters do not have to be passed in order, but they do need to be evaluated in the order of the called function/constructor
|
// named parameters do not have to be passed in order, but they do need to be evaluated in the order of the called function/constructor
|
||||||
fn get_arguments_in_order(
|
fn get_arguments_in_order(
|
||||||
&mut self,
|
&mut self,
|
||||||
namespace: &str,
|
namespace: &Namespace,
|
||||||
symbols: &SymbolTable,
|
symbols: &SymbolTable,
|
||||||
registry: &mut AsmRegistry,
|
registry: &mut AsmRegistry,
|
||||||
arguments: &[Expression],
|
arguments: &[Expression],
|
||||||
|
|
@ -550,7 +552,7 @@ pub enum Op {
|
||||||
Divide,
|
Divide,
|
||||||
Negate,
|
Negate,
|
||||||
Return,
|
Return,
|
||||||
Call(usize, usize),
|
Call(usize, u8),
|
||||||
And,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Not,
|
Not,
|
||||||
|
|
@ -571,7 +573,7 @@ pub enum Op {
|
||||||
DefMap(usize),
|
DefMap(usize),
|
||||||
Assign(usize),
|
Assign(usize),
|
||||||
ListGet,
|
ListGet,
|
||||||
CallBuiltin(usize, usize, usize),
|
CallBuiltin(usize, usize, u8),
|
||||||
Dup,
|
Dup,
|
||||||
GotoIf(usize),
|
GotoIf(usize),
|
||||||
GotoIfNot(usize),
|
GotoIfNot(usize),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
use crate::builtins::globals::GLOBAL_FUNCTIONS;
|
use crate::builtins::globals::GLOBAL_FUNCTIONS;
|
||||||
use crate::compiler::ast_pass::Expression::{
|
|
||||||
Assignment, FieldGet, FunctionCall, IfElseExpression, IfExpression, LetExpression, ListGet,
|
|
||||||
MapGet, MethodCall, NamedParameter, Stop, Variable,
|
|
||||||
};
|
|
||||||
use crate::compiler::tokens::TokenType::{
|
use crate::compiler::tokens::TokenType::{
|
||||||
Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For,
|
Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For,
|
||||||
Greater, GreaterEqual, GreaterGreater, Identifier, If, In, Indent, Integer, LeftBrace,
|
Greater, GreaterEqual, GreaterGreater, Identifier, If, In, Indent, Integer, LeftBrace,
|
||||||
|
|
@ -151,7 +147,7 @@ impl AstCompiler {
|
||||||
Err(self.error_at_line(Expected("-> or ?")))
|
Err(self.error_at_line(Expected("-> or ?")))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(Stop {
|
Ok(Expression::Stop {
|
||||||
line: self.peek().line,
|
line: self.peek().line,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +158,7 @@ impl AstCompiler {
|
||||||
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
|
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
|
||||||
Ok(query_params)
|
Ok(query_params)
|
||||||
} else {
|
} else {
|
||||||
Ok(Stop {
|
Ok(Expression::Stop {
|
||||||
line: self.peek().line,
|
line: self.peek().line,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +170,7 @@ impl AstCompiler {
|
||||||
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
|
self.consume(&RightBrace, Expected("'}' after guard expression."))?;
|
||||||
Ok(path_params)
|
Ok(path_params)
|
||||||
} else {
|
} else {
|
||||||
Ok(Stop {
|
Ok(Expression::Stop {
|
||||||
line: self.peek().line,
|
line: self.peek().line,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +343,7 @@ impl AstCompiler {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(LetExpression {
|
Ok(Expression::LetExpression {
|
||||||
name: name_token,
|
name: name_token,
|
||||||
var_type,
|
var_type,
|
||||||
initializer: Box::new(initializer),
|
initializer: Box::new(initializer),
|
||||||
|
|
@ -391,8 +387,8 @@ impl AstCompiler {
|
||||||
if self.match_token(&[Equal]) {
|
if self.match_token(&[Equal]) {
|
||||||
let operator = self.previous().clone();
|
let operator = self.previous().clone();
|
||||||
let right = self.comparison(symbol_table)?;
|
let right = self.comparison(symbol_table)?;
|
||||||
if let Variable { name, .. } = expr {
|
if let Expression::Variable { name, .. } = expr {
|
||||||
Ok(Assignment {
|
Ok(Expression::Assignment {
|
||||||
line: operator.line,
|
line: operator.line,
|
||||||
variable_name: name.to_string(),
|
variable_name: name.to_string(),
|
||||||
value: Box::new(right),
|
value: Box::new(right),
|
||||||
|
|
@ -499,13 +495,13 @@ impl AstCompiler {
|
||||||
|
|
||||||
self.inc_indent();
|
self.inc_indent();
|
||||||
|
|
||||||
Ok(IfElseExpression {
|
Ok(Expression::IfElseExpression {
|
||||||
condition: Box::new(condition),
|
condition: Box::new(condition),
|
||||||
then_branch,
|
then_branch,
|
||||||
else_branch: self.compile(symbol_table)?,
|
else_branch: self.compile(symbol_table)?,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(IfExpression {
|
Ok(Expression::IfExpression {
|
||||||
condition: Box::new(condition),
|
condition: Box::new(condition),
|
||||||
then_branch,
|
then_branch,
|
||||||
})
|
})
|
||||||
|
|
@ -534,22 +530,23 @@ impl AstCompiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// index into list
|
||||||
fn index(&mut self, operand: Expression, index: Expression) -> Expr {
|
fn index(&mut self, operand: Expression, index: Expression) -> Expr {
|
||||||
let get = match &operand {
|
let get = match &operand {
|
||||||
Expression::Map { .. } => MapGet {
|
Expression::Map { .. } => Expression::MapGet {
|
||||||
map: Box::new(operand),
|
map: Box::new(operand),
|
||||||
key: Box::new(index),
|
key: Box::new(index),
|
||||||
},
|
},
|
||||||
Expression::List { .. } => ListGet {
|
Expression::List { .. } => Expression::ListGet {
|
||||||
list: Box::new(operand),
|
list: Box::new(operand),
|
||||||
index: Box::new(index),
|
index: Box::new(index),
|
||||||
},
|
},
|
||||||
Variable { var_type, .. } => match var_type {
|
Expression::Variable { var_type, .. } => match var_type {
|
||||||
ListType => ListGet {
|
ListType => Expression::ListGet {
|
||||||
list: Box::new(operand),
|
list: Box::new(operand),
|
||||||
index: Box::new(index),
|
index: Box::new(index),
|
||||||
},
|
},
|
||||||
MapType => MapGet {
|
MapType => Expression::MapGet {
|
||||||
map: Box::new(operand),
|
map: Box::new(operand),
|
||||||
key: Box::new(index),
|
key: Box::new(index),
|
||||||
},
|
},
|
||||||
|
|
@ -578,7 +575,7 @@ impl AstCompiler {
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
if self.match_token(&[LeftParen]) {
|
if self.match_token(&[LeftParen]) {
|
||||||
let arguments = self.arguments(symbol_table)?;
|
let arguments = self.arguments(symbol_table)?;
|
||||||
Ok(MethodCall {
|
Ok(Expression::MethodCall {
|
||||||
receiver: Box::new(receiver.clone()),
|
receiver: Box::new(receiver.clone()),
|
||||||
method_name: op.lexeme,
|
method_name: op.lexeme,
|
||||||
arguments,
|
arguments,
|
||||||
|
|
@ -586,7 +583,7 @@ impl AstCompiler {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// no test yet
|
// no test yet
|
||||||
Ok(FieldGet {
|
Ok(Expression::FieldGet {
|
||||||
receiver: Box::new(receiver.clone()),
|
receiver: Box::new(receiver.clone()),
|
||||||
field: op.lexeme.clone(),
|
field: op.lexeme.clone(),
|
||||||
})
|
})
|
||||||
|
|
@ -698,7 +695,7 @@ impl AstCompiler {
|
||||||
fn named_parameter(&mut self, name: &Token, symbol_table: &mut SymbolTable) -> Expr {
|
fn named_parameter(&mut self, name: &Token, symbol_table: &mut SymbolTable) -> Expr {
|
||||||
let value = self.expression(symbol_table)?;
|
let value = self.expression(symbol_table)?;
|
||||||
let line = name.line;
|
let line = name.line;
|
||||||
Ok(NamedParameter {
|
Ok(Expression::NamedParameter {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
value: Box::new(value),
|
value: Box::new(value),
|
||||||
line,
|
line,
|
||||||
|
|
@ -751,7 +748,7 @@ impl AstCompiler {
|
||||||
} else {
|
} else {
|
||||||
&Unknown
|
&Unknown
|
||||||
};
|
};
|
||||||
Ok(Variable {
|
Ok(Expression::Variable {
|
||||||
name: name.lexeme.to_string(),
|
name: name.lexeme.to_string(),
|
||||||
var_type: var_type.clone(),
|
var_type: var_type.clone(),
|
||||||
line: name.line,
|
line: name.line,
|
||||||
|
|
@ -760,7 +757,7 @@ impl AstCompiler {
|
||||||
|
|
||||||
fn function_call(&mut self, name: Token, symbol_table: &mut SymbolTable) -> Expr {
|
fn function_call(&mut self, name: Token, symbol_table: &mut SymbolTable) -> Expr {
|
||||||
let arguments = self.arguments(symbol_table)?;
|
let arguments = self.arguments(symbol_table)?;
|
||||||
Ok(FunctionCall {
|
Ok(Expression::FunctionCall {
|
||||||
line: self.peek().line,
|
line: self.peek().line,
|
||||||
name: name.lexeme.to_string(),
|
name: name.lexeme.to_string(),
|
||||||
arguments,
|
arguments,
|
||||||
|
|
@ -891,13 +888,24 @@ impl Parameter {
|
||||||
pub fn new(name: impl Into<String>, value_type: TokenType) -> Self {
|
pub fn new(name: impl Into<String>, value_type: TokenType) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: Token {
|
name: Token {
|
||||||
token_type: TokenType::StringType,
|
token_type: StringType,
|
||||||
lexeme: name.into(),
|
lexeme: name.into(),
|
||||||
line: 0,
|
line: 0,
|
||||||
},
|
},
|
||||||
var_type: value_type,
|
var_type: value_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn varargs(token_type: TokenType) -> Self {
|
||||||
|
Self {
|
||||||
|
name: Token {
|
||||||
|
token_type: StringType,
|
||||||
|
lexeme: "varargs".into(),
|
||||||
|
line: 0,
|
||||||
|
},
|
||||||
|
var_type: token_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -1009,19 +1017,19 @@ impl Expression {
|
||||||
Self::Range { line, .. } => *line,
|
Self::Range { line, .. } => *line,
|
||||||
Self::List { line, .. } => *line,
|
Self::List { line, .. } => *line,
|
||||||
Self::Map { line, .. } => *line,
|
Self::Map { line, .. } => *line,
|
||||||
Variable { line, .. } => *line,
|
Self::Variable { line, .. } => *line,
|
||||||
Assignment { line, .. } => *line,
|
Self::Assignment { line, .. } => *line,
|
||||||
FunctionCall { line, .. } => *line,
|
Self::FunctionCall { line, .. } => *line,
|
||||||
MethodCall { line, .. } => *line,
|
Self::MethodCall { line, .. } => *line,
|
||||||
Stop { line } => *line,
|
Self::Stop { line } => *line,
|
||||||
NamedParameter { line, .. } => *line,
|
Self::NamedParameter { line, .. } => *line,
|
||||||
MapGet { .. } => 0,
|
Self::MapGet { .. } => 0,
|
||||||
ListGet { .. } => 0,
|
Self::ListGet { .. } => 0,
|
||||||
FieldGet { .. } => 0,
|
Self::FieldGet { .. } => 0,
|
||||||
IfExpression { condition, .. } => condition.line(),
|
Self::IfExpression { condition, .. } => condition.line(),
|
||||||
IfElseExpression { condition, .. } => condition.line(),
|
Self::IfElseExpression { condition, .. } => condition.line(),
|
||||||
LetExpression { name, .. } => name.line,
|
Self::LetExpression { name, .. } => name.line,
|
||||||
Expression::ForStatement { loop_var, .. } => loop_var.line,
|
Self::ForStatement { loop_var, .. } => loop_var.line,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::DATE_FORMAT_TIMEZONE;
|
use crate::DATE_FORMAT_TIMEZONE;
|
||||||
use crate::compiler::{compile, run};
|
use crate::compiler::{compile, run};
|
||||||
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName};
|
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName, UndeclaredVariable};
|
||||||
use crate::errors::CompilerErrorAtLine;
|
use crate::errors::CompilerErrorAtLine;
|
||||||
use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds};
|
use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds, NotSortable};
|
||||||
use crate::errors::TipiLangError::{Compiler, Runtime};
|
use crate::errors::TipiLangError::{Compiler, Runtime};
|
||||||
use crate::value::{Value, string};
|
use crate::value::{Value, i64, string};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -299,6 +299,28 @@ date"#),
|
||||||
assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3)));
|
assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_sort() {
|
||||||
|
assert_eq!(
|
||||||
|
run(r#"
|
||||||
|
let a = [3,2,1]
|
||||||
|
a.sort()
|
||||||
|
a"#),
|
||||||
|
Ok(Value::List(vec![i64(1), i64(2), i64(3)]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_sort_invalid() {
|
||||||
|
assert_eq!(
|
||||||
|
run(r#"
|
||||||
|
let a = [3,2,"a"]
|
||||||
|
a.sort()
|
||||||
|
a"#),
|
||||||
|
Err(Runtime(NotSortable))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_push() {
|
fn list_push() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -449,12 +471,14 @@ let a:i64 = if true:
|
||||||
42
|
42
|
||||||
else:
|
else:
|
||||||
0
|
0
|
||||||
a"#).unwrap();
|
a"#)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn package() {
|
||||||
|
assert_eq!(run(r#"a::b.c()"#), Err(Compiler(CompilerErrorAtLine {error: UndeclaredVariable("a::b".to_string()), line: 1 })));
|
||||||
}
|
}
|
||||||
// #[test]
|
|
||||||
// fn package() {
|
|
||||||
// assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn guards() {
|
// fn guards() {
|
||||||
|
|
@ -464,4 +488,19 @@ a"#).unwrap();
|
||||||
// Ok(Value::Void)
|
// Ok(Value::Void)
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query() {
|
||||||
|
let result =
|
||||||
|
run(r#"sql("select id, first_name, last_name from customers where id = 1")"#).unwrap();
|
||||||
|
if let Value::List(records) = result {
|
||||||
|
assert_eq!(records.len(), 1);
|
||||||
|
if let Value::Map(record) = records.get(0).unwrap() {
|
||||||
|
assert_eq!(record.get(&string("id")).unwrap(), &i64(1));
|
||||||
|
assert_eq!(record.get(&string("first_name")).unwrap(), &string("sander"));
|
||||||
|
assert_eq!(record.get(&string("last_name")).unwrap(), &string("hautvast"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use log::info;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use crate::{symbol_builder, AsmRegistry, TIPI_EXT};
|
use crate::{symbol_builder, AsmRegistry, TIPI_EXT};
|
||||||
use crate::compiler::assembly_pass::AsmChunk;
|
use crate::compiler::assembly_pass::AsmChunk;
|
||||||
|
|
@ -11,17 +12,18 @@ pub mod scan_pass;
|
||||||
pub mod ast_pass;
|
pub mod ast_pass;
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod assembly_pass;
|
pub mod assembly_pass;
|
||||||
|
pub(crate) mod namespace;
|
||||||
|
|
||||||
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, AsmChunk>, TipiLangError> {
|
pub fn compile_sourcedir(source_dir: &str) -> Result<HashMap<String, AsmChunk>, TipiLangError> {
|
||||||
let mut asm_registry = AsmRegistry::new();
|
let mut asm_registry = AsmRegistry::new();
|
||||||
|
let mut symbol_table = HashMap::new();
|
||||||
|
|
||||||
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(TIPI_EXT) {
|
if path.ends_with(TIPI_EXT) {
|
||||||
print!("-- Compiling {} -- ", path);
|
info!("-- 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_pass::scan(&source)?;
|
let tokens = scan_pass::scan(&source)?;
|
||||||
let mut symbol_table = HashMap::new();
|
|
||||||
match ast_pass::compile(Some(path), tokens, &mut symbol_table) {
|
match ast_pass::compile(Some(path), tokens, &mut symbol_table) {
|
||||||
Ok(statements) => {
|
Ok(statements) => {
|
||||||
let path = path.strip_prefix(source_dir).unwrap().replace(TIPI_EXT, "");
|
let path = path.strip_prefix(source_dir).unwrap().replace(TIPI_EXT, "");
|
||||||
|
|
@ -63,5 +65,5 @@ pub fn run(src: &str) -> Result<crate::value::Value, TipiLangError> {
|
||||||
let mut asm_registry = HashMap::new();
|
let mut asm_registry = HashMap::new();
|
||||||
assembly_pass::compile(None, &ast, &symbol_table, &mut asm_registry)?;
|
assembly_pass::compile(None, &ast, &symbol_table, &mut asm_registry)?;
|
||||||
let registry = arc_swap::ArcSwap::from(std::sync::Arc::new(asm_registry));
|
let registry = arc_swap::ArcSwap::from(std::sync::Arc::new(asm_registry));
|
||||||
crate::vm::interpret(registry.load(), "main").map_err(TipiLangError::from)
|
crate::vm::run(registry.load(), "main").map_err(TipiLangError::from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
src/compiler/namespace.rs
Normal file
27
src/compiler/namespace.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub(crate) struct Namespace {
|
||||||
|
elements: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Namespace {
|
||||||
|
pub(crate) fn new(path: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
elements: path.split('/').map(|path| path.to_string()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop(&self) -> Self{
|
||||||
|
let mut copy = self.elements.to_vec();
|
||||||
|
copy.pop();
|
||||||
|
Self{
|
||||||
|
elements: copy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Namespace {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.elements.join("/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,11 @@ impl Scanner {
|
||||||
}
|
}
|
||||||
'#' => self.add_token(TokenType::Hash),
|
'#' => self.add_token(TokenType::Hash),
|
||||||
'+' => self.add_token(TokenType::Plus),
|
'+' => self.add_token(TokenType::Plus),
|
||||||
':' => self.add_token(TokenType::Colon),
|
':' => {
|
||||||
|
if !self.match_next(':') {
|
||||||
|
self.add_token(TokenType::Colon);
|
||||||
|
}
|
||||||
|
}
|
||||||
';' => println!("Warning: Ignoring semicolon at line {}", self.line),
|
';' => println!("Warning: Ignoring semicolon at line {}", self.line),
|
||||||
'*' => self.add_token(TokenType::Star),
|
'*' => self.add_token(TokenType::Star),
|
||||||
'!' => {
|
'!' => {
|
||||||
|
|
@ -182,7 +186,13 @@ impl Scanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identifier(&mut self) {
|
fn identifier(&mut self) {
|
||||||
while is_alphanumeric(self.peek()) {
|
while is_alphanumeric(self.peek())
|
||||||
|
|| self.peek() == '_'
|
||||||
|
|| (self.peek() == ':' && self.peek_next() == ':')
|
||||||
|
{
|
||||||
|
if self.peek() == ':' && self.peek_next() == ':' {
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
let value: String = self.chars[self.start..self.current].iter().collect();
|
let value: String = self.chars[self.start..self.current].iter().collect();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub enum TokenType {
|
||||||
Bool,
|
Bool,
|
||||||
Char,
|
Char,
|
||||||
Colon,
|
Colon,
|
||||||
|
ColonColon,
|
||||||
Comma,
|
Comma,
|
||||||
DateTime,
|
DateTime,
|
||||||
Dot,
|
Dot,
|
||||||
|
|
@ -113,6 +114,7 @@ impl fmt::Display for TokenType {
|
||||||
TokenType::Pipe => write!(f, "|"),
|
TokenType::Pipe => write!(f, "|"),
|
||||||
TokenType::BitXor => write!(f, "^"),
|
TokenType::BitXor => write!(f, "^"),
|
||||||
TokenType::Colon => write!(f, ":"),
|
TokenType::Colon => write!(f, ":"),
|
||||||
|
TokenType::ColonColon => write!(f, "::"),
|
||||||
TokenType::Comma => write!(f, ","),
|
TokenType::Comma => write!(f, ","),
|
||||||
TokenType::FloatingPoint => write!(f, "float"),
|
TokenType::FloatingPoint => write!(f, "float"),
|
||||||
TokenType::MapType => write!(f, "map"),
|
TokenType::MapType => write!(f, "map"),
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ pub enum RuntimeError {
|
||||||
ExpectedType(String),
|
ExpectedType(String),
|
||||||
#[error("Index out of bounds: {0} > {1}")]
|
#[error("Index out of bounds: {0} > {1}")]
|
||||||
IndexOutOfBounds(usize, usize),
|
IndexOutOfBounds(usize, usize),
|
||||||
|
#[error("The list contains values that are not comparable.")]
|
||||||
|
NotSortable,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,13 @@ pub fn start_watch_daemon(source: &str, registry: Arc<ArcSwap<HashMap<String, As
|
||||||
let s = source.to_string();
|
let s = source.to_string();
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
println!("-- File watch started --");
|
info!("-- File watch started --");
|
||||||
let path = Path::new(&source);
|
let path = Path::new(&source);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
panic!("source directory {} does not exist", &source);
|
panic!("source directory {} does not exist", &source);
|
||||||
}
|
}
|
||||||
let mut watcher = notify::recommended_watcher(tx).expect("Failed to create watcher");
|
let mut watcher = notify::recommended_watcher(tx)
|
||||||
|
.expect("Failed to create watcher");
|
||||||
watcher
|
watcher
|
||||||
.watch(path, RecursiveMode::Recursive)
|
.watch(path, RecursiveMode::Recursive)
|
||||||
.expect("Failed to watch");
|
.expect("Failed to watch");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ use crate::compiler::ast_pass::{Expression, Statement};
|
||||||
use crate::errors::CompilerErrorAtLine;
|
use crate::errors::CompilerErrorAtLine;
|
||||||
use crate::symbol_builder::Symbol;
|
use crate::symbol_builder::Symbol;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use bb8_postgres::PostgresConnectionManager;
|
||||||
|
use tokio_postgres::NoTls;
|
||||||
|
|
||||||
mod builtins;
|
mod builtins;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
|
|
@ -11,7 +14,7 @@ pub mod file_watch;
|
||||||
mod keywords;
|
mod keywords;
|
||||||
pub mod repl;
|
pub mod repl;
|
||||||
mod symbol_builder;
|
mod symbol_builder;
|
||||||
mod value;
|
pub mod value;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
pub(crate) type SymbolTable = HashMap<String, Symbol>;
|
pub(crate) type SymbolTable = HashMap<String, Symbol>;
|
||||||
|
|
@ -21,3 +24,5 @@ pub(crate) type AsmRegistry = HashMap<String, AsmChunk>;
|
||||||
|
|
||||||
pub const TIPI_EXT: &str = ".tp";
|
pub const TIPI_EXT: &str = ".tp";
|
||||||
pub const DATE_FORMAT_TIMEZONE: &str = "%Y-%m-%d %H:%M:%S%.3f %z";
|
pub const DATE_FORMAT_TIMEZONE: &str = "%Y-%m-%d %H:%M:%S%.3f %z";
|
||||||
|
|
||||||
|
pub static DB_POOL: OnceLock<bb8::Pool<PostgresConnectionManager<NoTls>>> = OnceLock::new();
|
||||||
|
|
|
||||||
44
src/main.rs
44
src/main.rs
|
|
@ -3,15 +3,20 @@ use axum::extract::{Request, State};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::routing::any;
|
use axum::routing::any;
|
||||||
use axum::{Json, Router};
|
use axum::{Json, Router};
|
||||||
|
use bb8_postgres::PostgresConnectionManager;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::info;
|
use log::{error, info};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock, OnceLock};
|
||||||
use tipi_lang::compiler::assembly_pass::AsmChunk;
|
use tipi_lang::compiler::assembly_pass::AsmChunk;
|
||||||
use tipi_lang::compiler::{compile_sourcedir, map_underlying, run};
|
use tipi_lang::compiler::{compile_sourcedir, map_underlying, run};
|
||||||
use tipi_lang::errors::TipiLangError;
|
use tipi_lang::errors::TipiLangError;
|
||||||
use tipi_lang::vm::interpret_async;
|
use tipi_lang::value::Value;
|
||||||
|
use tipi_lang::vm::run_async;
|
||||||
|
use tipi_lang::{DB_POOL, vm};
|
||||||
|
use tokio_postgres::NoTls;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// A simple CLI tool to greet users
|
/// A simple CLI tool to greet users
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
|
@ -27,27 +32,46 @@ struct Args {
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
file: Option<String>,
|
file: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
postgres: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), TipiLangError> {
|
async fn main() -> Result<(), TipiLangError> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
if let Some(pg) = args.postgres {
|
||||||
|
let config = tokio_postgres::config::Config::from_str(&pg).map_err(|e|TipiLangError::Platform(e.to_string()))?;
|
||||||
|
let manager = PostgresConnectionManager::new(config,NoTls);
|
||||||
|
let pool = bb8::Pool::builder()
|
||||||
|
.build(manager)
|
||||||
|
.await
|
||||||
|
.expect("cannot create the the database connection pool");
|
||||||
|
let _ = DB_POOL.set(pool);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(file) = args.file {
|
if let Some(file) = args.file {
|
||||||
let source = fs::read_to_string(file).expect("Unable to read file");
|
let source = fs::read_to_string(file).expect("Unable to read file");
|
||||||
run(&source)?;
|
run(&source)?;
|
||||||
} else {
|
} else {
|
||||||
println!("-- Tipilang --");
|
info!("-- Tipilang --");
|
||||||
let source = args.source.unwrap_or("./source".to_string());
|
let source = args.source.unwrap_or("./source".to_string());
|
||||||
let registry = compile_sourcedir(&source)?;
|
let registry = compile_sourcedir(&source)?;
|
||||||
let empty = registry.is_empty();
|
let empty = registry.is_empty();
|
||||||
|
|
||||||
let swap = Arc::new(ArcSwap::from(Arc::new(registry)));
|
let swap = Arc::new(ArcSwap::from(Arc::new(registry)));
|
||||||
if !empty {
|
if !empty {
|
||||||
|
let registry = swap.clone().load();
|
||||||
|
if registry.get("/main").is_some() {
|
||||||
|
vm::run(registry, "/main")?;
|
||||||
|
}
|
||||||
|
|
||||||
if args.watch {
|
if args.watch {
|
||||||
tipi_lang::file_watch::start_watch_daemon(&source, swap.clone());
|
tipi_lang::file_watch::start_watch_daemon(&source, swap.clone());
|
||||||
}
|
}
|
||||||
println!("-- Compilation successful --");
|
info!("-- Compilation successful --");
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
registry: swap.clone(),
|
registry: swap.clone(),
|
||||||
};
|
};
|
||||||
|
|
@ -59,7 +83,7 @@ async fn main() -> Result<(), TipiLangError> {
|
||||||
.await
|
.await
|
||||||
.map_err(map_underlying())?;
|
.map_err(map_underlying())?;
|
||||||
|
|
||||||
println!(
|
info!(
|
||||||
"-- Listening on {} --\n",
|
"-- Listening on {} --\n",
|
||||||
listener.local_addr().map_err(map_underlying())?
|
listener.local_addr().map_err(map_underlying())?
|
||||||
);
|
);
|
||||||
|
|
@ -70,7 +94,7 @@ async fn main() -> Result<(), TipiLangError> {
|
||||||
|
|
||||||
axum::serve(listener, app).await.map_err(map_underlying())?;
|
axum::serve(listener, app).await.map_err(map_underlying())?;
|
||||||
} else {
|
} else {
|
||||||
println!("No source files found or compilation error");
|
error!("No source files found or compilation error");
|
||||||
if args.repl {
|
if args.repl {
|
||||||
tipi_lang::repl::start(swap.clone())?;
|
tipi_lang::repl::start(swap.clone())?;
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +111,7 @@ struct AppState {
|
||||||
async fn handle_any(
|
async fn handle_any(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
req: Request,
|
req: Request,
|
||||||
) -> Result<Json<String>, StatusCode> {
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
let method = req.method().to_string().to_ascii_lowercase();
|
let method = req.method().to_string().to_ascii_lowercase();
|
||||||
let uri = req.uri();
|
let uri = req.uri();
|
||||||
|
|
||||||
|
|
@ -109,7 +133,7 @@ async fn handle_any(
|
||||||
}
|
}
|
||||||
let path = &req.uri().to_string();
|
let path = &req.uri().to_string();
|
||||||
info!("invoked {:?} => {}", req, function_qname);
|
info!("invoked {:?} => {}", req, function_qname);
|
||||||
match interpret_async(
|
match run_async(
|
||||||
state.registry.load(),
|
state.registry.load(),
|
||||||
&function_qname,
|
&function_qname,
|
||||||
path,
|
path,
|
||||||
|
|
@ -118,7 +142,7 @@ async fn handle_any(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(value) => Ok(Json(value.to_string())),
|
Ok(value) => Ok(Json(value)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// url checks out but function for method not found
|
// url checks out but function for method not found
|
||||||
if state
|
if state
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use crate::compiler::namespace::Namespace;
|
||||||
|
|
||||||
pub fn start(registry: Arc<ArcSwap<HashMap<String, AsmChunk>>>) -> Result<(), TipiLangError> {
|
pub fn start(registry: Arc<ArcSwap<HashMap<String, AsmChunk>>>) -> Result<(), TipiLangError> {
|
||||||
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)");
|
||||||
|
|
@ -44,7 +45,7 @@ pub fn start(registry: Arc<ArcSwap<HashMap<String, AsmChunk>>>) -> Result<(), Ti
|
||||||
};
|
};
|
||||||
symbol_builder::build("", &ast, &mut symbol_table);
|
symbol_builder::build("", &ast, &mut symbol_table);
|
||||||
|
|
||||||
match asm_pass.compile(&ast, &symbol_table, &mut registry_copy, "") {
|
match asm_pass.compile(&ast, &symbol_table, &mut registry_copy, &Namespace::new("")) {
|
||||||
Ok(chunk) => {
|
Ok(chunk) => {
|
||||||
registry_copy.insert("main".to_string(), chunk);
|
registry_copy.insert("main".to_string(), chunk);
|
||||||
registry.store(Arc::new(registry_copy));
|
registry.store(Arc::new(registry_copy));
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fn make_qname(path: &str, name: &Token) -> String {
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
name.lexeme.to_string()
|
name.lexeme.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}.{}", path, name.lexeme)
|
format!("{}/{}", path, name.lexeme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
55
src/value.rs
55
src/value.rs
|
|
@ -1,19 +1,21 @@
|
||||||
use crate::DATE_FORMAT_TIMEZONE;
|
use crate::DATE_FORMAT_TIMEZONE;
|
||||||
use crate::errors::ValueError;
|
use crate::errors::ValueError;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub};
|
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub(crate) definition: String,
|
pub(crate) definition: String,
|
||||||
pub(crate) fields: Vec<(String, Value)>,
|
pub(crate) fields: Vec<(String, Value)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
U32(u32),
|
U32(u32),
|
||||||
I32(i32),
|
I32(i32),
|
||||||
|
|
@ -31,13 +33,41 @@ pub enum Value {
|
||||||
ObjectType(Box<Object>),
|
ObjectType(Box<Object>),
|
||||||
Error(String),
|
Error(String),
|
||||||
Void,
|
Void,
|
||||||
|
Uuid(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Value {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Value::String(s) => serializer.serialize_str(s),
|
||||||
|
Value::I32(n) => serializer.serialize_i32(*n),
|
||||||
|
Value::I64(n) => serializer.serialize_i64(*n),
|
||||||
|
Value::U32(n) => serializer.serialize_u32(*n),
|
||||||
|
Value::U64(n) => serializer.serialize_u64(*n),
|
||||||
|
Value::F32(f) => serializer.serialize_f32(*f),
|
||||||
|
Value::F64(f) => serializer.serialize_f64(*f),
|
||||||
|
Value::Void => serializer.serialize_unit(),
|
||||||
|
Value::Bool(b) => serializer.serialize_bool(*b),
|
||||||
|
Value::Char(c) => serializer.serialize_char(*c),
|
||||||
|
Value::DateTime(d) => serializer.serialize_str(&d.to_string()),
|
||||||
|
Value::Enum => unimplemented!("TODO implement serialize enums"),
|
||||||
|
Value::Error(s) => serializer.serialize_str(s),
|
||||||
|
Value::List(vec) => vec.serialize(serializer),
|
||||||
|
Value::Map(map) => map.serialize(serializer),
|
||||||
|
Value::ObjectType(obj) => obj.serialize(serializer),
|
||||||
|
Value::Uuid(uuid) => serializer.serialize_str(uuid),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn string(v: impl Into<String>) -> Value {
|
pub(crate) fn string(v: impl Into<String>) -> Value {
|
||||||
Value::String(v.into())
|
Value::String(v.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn _i64(v: impl Into<i64>) -> Value {
|
pub(crate) fn i64(v: impl Into<i64>) -> Value {
|
||||||
Value::I64(v.into())
|
Value::I64(v.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +139,24 @@ impl Value {
|
||||||
_ => Err(ValueError::IllegalCast),
|
_ => Err(ValueError::IllegalCast),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Comparison used for sorting lists.
|
||||||
|
/// Returns `None` when values are not comparable (different variants, unsupported types, etc.).
|
||||||
|
pub fn sort_cmp(&self, rhs: &Self) -> Option<Ordering> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Value::I32(a), Value::I32(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::I64(a), Value::I64(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::U32(a), Value::U32(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::U64(a), Value::U64(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::F32(a), Value::F32(b)) => Some(a.total_cmp(b)),
|
||||||
|
(Value::F64(a), Value::F64(b)) => Some(a.total_cmp(b)),
|
||||||
|
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::Char(a), Value::Char(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)),
|
||||||
|
(Value::DateTime(a), Value::DateTime(b)) => Some(a.cmp(b)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for Value {
|
impl From<i32> for Value {
|
||||||
|
|
@ -199,6 +247,7 @@ impl Display for Value {
|
||||||
Value::Map(map) => to_string(f, map),
|
Value::Map(map) => to_string(f, map),
|
||||||
Value::Error(v) => write!(f, "{}", v),
|
Value::Error(v) => write!(f, "{}", v),
|
||||||
Value::Void => write!(f, "()"),
|
Value::Void => write!(f, "()"),
|
||||||
|
Value::Uuid(v) => write!(f, "{}", v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub async fn interpret_async(
|
pub async fn run_async(
|
||||||
registry: Guard<Arc<HashMap<String, AsmChunk>>>,
|
registry: Guard<Arc<HashMap<String, AsmChunk>>>,
|
||||||
function: &str,
|
function: &str,
|
||||||
uri: &str,
|
uri: &str,
|
||||||
|
|
@ -38,13 +38,13 @@ pub async fn interpret_async(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret(registry: Guard<Arc<AsmRegistry>>, function: &str) -> Result<Value, RuntimeError> {
|
pub fn run(registry: Guard<Arc<AsmRegistry>>, function: &str) -> Result<Value, RuntimeError> {
|
||||||
let chunk = registry.get(function).unwrap().clone();
|
let chunk = registry.get(function).unwrap().clone();
|
||||||
let mut vm = Vm::new(®istry);
|
let mut vm = Vm::new(®istry);
|
||||||
vm.run(&get_context(function), &chunk)
|
vm.run(&get_context(function), &chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret_function(chunk: &AsmChunk, args: Vec<Value>, registry: Arc<AsmRegistry>) -> Result<Value, RuntimeError> {
|
pub fn run_function(chunk: &AsmChunk, args: Vec<Value>, registry: Arc<AsmRegistry>) -> Result<Value, RuntimeError> {
|
||||||
let mut vm = Vm::new(®istry);
|
let mut vm = Vm::new(®istry);
|
||||||
vm.run_function(chunk, args)
|
vm.run_function(chunk, args)
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +241,7 @@ impl Vm {
|
||||||
return Err(RuntimeError::FunctionNotFound(function_name));
|
return Err(RuntimeError::FunctionNotFound(function_name));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = interpret_function(function_chunk.unwrap(), args, self.registry.clone())?;
|
let result = run_function(function_chunk.unwrap(), args, self.registry.clone())?;
|
||||||
self.push(Rc::new(RefCell::new(result)));
|
self.push(Rc::new(RefCell::new(result)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"languages": [{
|
"languages": [{
|
||||||
"id": "tipi",
|
"id": "tipi",
|
||||||
"aliases": ["tipi-lang", "tipi"],
|
"aliases": ["tipi-lang", "tipi"],
|
||||||
"extensions": [".tipi"],
|
"extensions": [".tp"],
|
||||||
"configuration": "./language-configuration.json"
|
"configuration": "./language-configuration.json"
|
||||||
}],
|
}],
|
||||||
"grammars": [{
|
"grammars": [{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue