finish engram-lang: protocols, decorators, imports, Result, closures, stdlib, integration tests
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "el-stdlib"
|
||||
description = "Engram language standard library — built-in function signatures and implementations"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
el-types = { workspace = true }
|
||||
el-parser = { workspace = true }
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Array operations: map, filter, reduce, find, any, all, length, push, pop,
|
||||
//! sort, reverse, zip, enumerate.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
let arr_int = Type::Array(Box::new(Type::Int));
|
||||
let arr_str = Type::Array(Box::new(Type::String));
|
||||
let arr_unk = Type::Array(Box::new(Type::Unknown));
|
||||
|
||||
// array_length([T]) -> Int
|
||||
env.register_fn("array_length", fn_type(vec![arr_unk.clone()], Type::Int));
|
||||
// array_push([T], T) -> [T]
|
||||
env.register_fn("array_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone()));
|
||||
// array_pop([T]) -> T?
|
||||
env.register_fn("array_pop", fn_type(vec![arr_unk.clone()], Type::Optional(Box::new(Type::Unknown))));
|
||||
// array_map([T], fn(T) -> U) -> [U]
|
||||
let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) };
|
||||
env.register_fn("array_map", fn_type(vec![arr_unk.clone(), mapper.clone()], arr_unk.clone()));
|
||||
// array_filter([T], fn(T) -> Bool) -> [T]
|
||||
let predicate = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Bool) };
|
||||
env.register_fn("array_filter", fn_type(vec![arr_unk.clone(), predicate.clone()], arr_unk.clone()));
|
||||
// array_reduce([T], U, fn(U, T) -> U) -> U
|
||||
let reducer = Type::Fn { params: vec![Type::Unknown, Type::Unknown], return_type: Box::new(Type::Unknown) };
|
||||
env.register_fn("array_reduce", fn_type(vec![arr_unk.clone(), Type::Unknown, reducer], Type::Unknown));
|
||||
// array_find([T], fn(T) -> Bool) -> T?
|
||||
env.register_fn("array_find", fn_type(vec![arr_unk.clone(), predicate.clone()], Type::Optional(Box::new(Type::Unknown))));
|
||||
// array_any([T], fn(T) -> Bool) -> Bool
|
||||
env.register_fn("array_any", fn_type(vec![arr_unk.clone(), predicate.clone()], Type::Bool));
|
||||
// array_all([T], fn(T) -> Bool) -> Bool
|
||||
env.register_fn("array_all", fn_type(vec![arr_unk.clone(), predicate], Type::Bool));
|
||||
// array_sort([Int]) -> [Int]
|
||||
env.register_fn("array_sort", fn_type(vec![arr_int.clone()], arr_int.clone()));
|
||||
// array_reverse([T]) -> [T]
|
||||
env.register_fn("array_reverse", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
// array_zip([T], [U]) -> [[T]] (simplified: returns array of unknown)
|
||||
env.register_fn("array_zip", fn_type(vec![arr_unk.clone(), arr_unk.clone()], arr_unk.clone()));
|
||||
// array_enumerate([T]) -> [[T]] (returns pairs as arrays)
|
||||
env.register_fn("array_enumerate", fn_type(vec![arr_unk.clone()], arr_unk.clone()));
|
||||
// array_join([String], String) -> String
|
||||
env.register_fn("array_join", fn_type(vec![arr_str.clone(), Type::String], Type::String));
|
||||
// array_concat([T], [T]) -> [T]
|
||||
env.register_fn("array_concat", fn_type(vec![arr_unk.clone(), arr_unk.clone()], arr_unk.clone()));
|
||||
// array_slice([T], Int, Int) -> [T]
|
||||
env.register_fn("array_slice", fn_type(vec![arr_unk.clone(), Type::Int, Type::Int], arr_unk));
|
||||
// array_first([T]) -> T?
|
||||
env.register_fn("array_first", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int))));
|
||||
// array_last([T]) -> T?
|
||||
env.register_fn("array_last", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int))));
|
||||
// array_contains([String], String) -> Bool
|
||||
env.register_fn("array_contains", fn_type(vec![arr_str, Type::String], Type::Bool));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_length_registered() {
|
||||
assert!(env().lookup_fn("array_length").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_map_is_fn_type() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("array_map").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_filter_registered() {
|
||||
assert!(env().lookup_fn("array_filter").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_push_registered() {
|
||||
assert!(env().lookup_fn("array_push").is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Engram graph operations: activate, relate, forget, edge_between, neighbors.
|
||||
//!
|
||||
//! These are thin wrappers over the Engram HTTP API. They are registered as
|
||||
//! built-in functions in the type environment so el programs can call them
|
||||
//! directly without an import.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
let arr_unk = Type::Array(Box::new(Type::Unknown));
|
||||
|
||||
// engram_activate(type_name: String, query: String) -> [T]
|
||||
env.register_fn("engram_activate", fn_type(vec![Type::String, Type::String], arr_unk.clone()));
|
||||
|
||||
// engram_relate(from_id: Uuid, to_id: Uuid, relation: String, weight: Float) -> Void
|
||||
env.register_fn("engram_relate", fn_type(
|
||||
vec![Type::Uuid, Type::Uuid, Type::String, Type::Float],
|
||||
Type::Void,
|
||||
));
|
||||
|
||||
// engram_forget(node_id: Uuid) -> Void
|
||||
env.register_fn("engram_forget", fn_type(vec![Type::Uuid], Type::Void));
|
||||
|
||||
// engram_edge_between(from_id: Uuid, to_id: Uuid) -> Bool
|
||||
env.register_fn("engram_edge_between", fn_type(vec![Type::Uuid, Type::Uuid], Type::Bool));
|
||||
|
||||
// engram_neighbors(node_id: Uuid) -> [T]
|
||||
env.register_fn("engram_neighbors", fn_type(vec![Type::Uuid], arr_unk.clone()));
|
||||
|
||||
// engram_node_count() -> Int
|
||||
env.register_fn("engram_node_count", fn_type(vec![], Type::Int));
|
||||
|
||||
// engram_search(query: String, limit: Int) -> [T]
|
||||
env.register_fn("engram_search", fn_type(vec![Type::String, Type::Int], arr_unk));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_engram_activate_registered() {
|
||||
assert!(env().lookup_fn("engram_activate").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_engram_relate_registered() {
|
||||
assert!(env().lookup_fn("engram_relate").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_engram_neighbors_registered() {
|
||||
assert!(env().lookup_fn("engram_neighbors").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_engram_forget_returns_void() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("engram_forget").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Void)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//! Engram language standard library.
|
||||
//!
|
||||
//! This crate defines function signatures for the built-in standard library
|
||||
//! modules. Each module registers its functions into a [`TypeEnv`] so the
|
||||
//! type checker can resolve calls to stdlib functions without an explicit
|
||||
//! import.
|
||||
//!
|
||||
//! # Auto-imported modules
|
||||
//! - `std::array` — array operations
|
||||
//! - `std::string` — string operations
|
||||
//! - `std::result` — Result<T, E> operations
|
||||
//! - `std::optional`— T? operations
|
||||
//! - `std::math` — numeric operations
|
||||
//! - `std::map` — Map<K, V> operations
|
||||
//! - `std::engram` — Engram graph operations
|
||||
|
||||
pub mod array;
|
||||
pub mod engram;
|
||||
pub mod map;
|
||||
pub mod math;
|
||||
pub mod optional;
|
||||
pub mod result;
|
||||
pub mod string;
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
|
||||
/// Register all automatically-imported stdlib modules into the given environment.
|
||||
///
|
||||
/// Call this from `TypeEnv::with_builtins()` or at the start of type checking.
|
||||
pub fn register_builtins(env: &mut TypeEnv) {
|
||||
array::register(env);
|
||||
string::register(env);
|
||||
result::register(env);
|
||||
optional::register(env);
|
||||
math::register(env);
|
||||
map::register(env);
|
||||
engram::register(env);
|
||||
}
|
||||
|
||||
/// Helper: build a simple function type.
|
||||
pub(crate) fn fn_type(params: Vec<Type>, ret: Type) -> Type {
|
||||
Type::Fn { params, return_type: Box::new(ret) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use el_types::TypeEnv;
|
||||
|
||||
fn stdlib_env() -> TypeEnv {
|
||||
let mut env = TypeEnv::with_builtins();
|
||||
register_builtins(&mut env);
|
||||
env
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("array_map").is_some(), "array_map should be registered");
|
||||
assert!(env.lookup_fn("array_filter").is_some());
|
||||
assert!(env.lookup_fn("array_length").is_some());
|
||||
assert!(env.lookup_fn("array_push").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("string_len").is_some());
|
||||
assert!(env.lookup_fn("string_trim").is_some());
|
||||
assert!(env.lookup_fn("string_split").is_some());
|
||||
assert!(env.lookup_fn("string_contains").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("math_abs").is_some());
|
||||
assert!(env.lookup_fn("math_max").is_some());
|
||||
assert!(env.lookup_fn("math_min").is_some());
|
||||
assert!(env.lookup_fn("math_sqrt").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("result_unwrap_or").is_some());
|
||||
assert!(env.lookup_fn("result_ok").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("optional_unwrap_or").is_some());
|
||||
assert!(env.lookup_fn("optional_is_some").is_some());
|
||||
assert!(env.lookup_fn("optional_is_none").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("map_get").is_some());
|
||||
assert!(env.lookup_fn("map_set").is_some());
|
||||
assert!(env.lookup_fn("map_remove").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_engram_functions_registered() {
|
||||
let env = stdlib_env();
|
||||
assert!(env.lookup_fn("engram_activate").is_some());
|
||||
assert!(env.lookup_fn("engram_relate").is_some());
|
||||
assert!(env.lookup_fn("engram_neighbors").is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Map<K, V> operations: get, set, remove, contains_key, keys, values, entries, merge.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
let map_unk = Type::Map { key: Box::new(Type::Unknown), value: Box::new(Type::Unknown) };
|
||||
let arr_unk = Type::Array(Box::new(Type::Unknown));
|
||||
|
||||
env.register_fn("map_get", fn_type(vec![map_unk.clone(), Type::Unknown], Type::Optional(Box::new(Type::Unknown))));
|
||||
env.register_fn("map_set", fn_type(vec![map_unk.clone(), Type::Unknown, Type::Unknown], map_unk.clone()));
|
||||
env.register_fn("map_remove", fn_type(vec![map_unk.clone(), Type::Unknown], map_unk.clone()));
|
||||
env.register_fn("map_contains_key", fn_type(vec![map_unk.clone(), Type::Unknown], Type::Bool));
|
||||
env.register_fn("map_keys", fn_type(vec![map_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("map_values", fn_type(vec![map_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("map_entries", fn_type(vec![map_unk.clone()], arr_unk.clone()));
|
||||
env.register_fn("map_merge", fn_type(vec![map_unk.clone(), map_unk.clone()], map_unk.clone()));
|
||||
env.register_fn("map_size", fn_type(vec![map_unk.clone()], Type::Int));
|
||||
env.register_fn("map_is_empty", fn_type(vec![map_unk.clone()], Type::Bool));
|
||||
env.register_fn("map_new", fn_type(vec![], map_unk));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_get_registered() {
|
||||
assert!(env().lookup_fn("map_get").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_get_returns_optional() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("map_get").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Optional(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_contains_key_returns_bool() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("map_contains_key").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Bool)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_merge_registered() {
|
||||
assert!(env().lookup_fn("map_merge").is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//! Math operations: abs, max, min, floor, ceil, pow, sqrt, clamp.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
env.register_fn("math_abs", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_max", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_min", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_floor", fn_type(vec![Type::Float], Type::Int));
|
||||
env.register_fn("math_ceil", fn_type(vec![Type::Float], Type::Int));
|
||||
env.register_fn("math_round", fn_type(vec![Type::Float], Type::Int));
|
||||
env.register_fn("math_pow", fn_type(vec![Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_sqrt", fn_type(vec![Type::Float], Type::Float));
|
||||
env.register_fn("math_clamp", fn_type(vec![Type::Float, Type::Float, Type::Float], Type::Float));
|
||||
env.register_fn("math_abs_int", fn_type(vec![Type::Int], Type::Int));
|
||||
env.register_fn("math_max_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
env.register_fn("math_min_int", fn_type(vec![Type::Int, Type::Int], Type::Int));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_abs_registered() {
|
||||
assert!(env().lookup_fn("math_abs").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_sqrt_returns_float() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("math_sqrt").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Float)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_floor_returns_int() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("math_floor").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Int)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//! T? operations: unwrap_or, map, flat_map, is_some, is_none.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
let opt_unk = Type::Optional(Box::new(Type::Unknown));
|
||||
let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) };
|
||||
|
||||
env.register_fn("optional_unwrap_or", fn_type(vec![opt_unk.clone(), Type::Unknown], Type::Unknown));
|
||||
env.register_fn("optional_unwrap_or_else", fn_type(
|
||||
vec![opt_unk.clone(), Type::Fn { params: vec![], return_type: Box::new(Type::Unknown) }],
|
||||
Type::Unknown,
|
||||
));
|
||||
env.register_fn("optional_map", fn_type(vec![opt_unk.clone(), mapper.clone()], opt_unk.clone()));
|
||||
env.register_fn("optional_flat_map", fn_type(vec![opt_unk.clone(), mapper.clone()], opt_unk.clone()));
|
||||
env.register_fn("optional_is_some", fn_type(vec![opt_unk.clone()], Type::Bool));
|
||||
env.register_fn("optional_is_none", fn_type(vec![opt_unk.clone()], Type::Bool));
|
||||
env.register_fn("optional_filter", fn_type(
|
||||
vec![opt_unk.clone(), Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Bool) }],
|
||||
opt_unk.clone(),
|
||||
));
|
||||
// some(T) -> T?
|
||||
env.register_fn("some", fn_type(vec![Type::Unknown], opt_unk));
|
||||
// none() -> T?
|
||||
env.register_fn("none", fn_type(vec![], Type::Optional(Box::new(Type::Unknown))));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_is_some_registered() {
|
||||
assert!(env().lookup_fn("optional_is_some").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_is_none_registered() {
|
||||
assert!(env().lookup_fn("optional_is_none").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optional_unwrap_or_registered() {
|
||||
assert!(env().lookup_fn("optional_unwrap_or").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_some_and_none_registered() {
|
||||
let e = env();
|
||||
assert!(e.lookup_fn("some").is_some());
|
||||
assert!(e.lookup_fn("none").is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Result<T, E> operations: map, map_err, unwrap_or, unwrap_or_else, and_then, ok.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
// result_unwrap_or(Result<T, E>, T) -> T
|
||||
let result_unk = Type::Result {
|
||||
ok: Box::new(Type::Unknown),
|
||||
err: Box::new(Type::Unknown),
|
||||
};
|
||||
env.register_fn("result_unwrap_or", fn_type(vec![result_unk.clone(), Type::Unknown], Type::Unknown));
|
||||
env.register_fn("result_unwrap_or_else", fn_type(
|
||||
vec![result_unk.clone(), Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) }],
|
||||
Type::Unknown,
|
||||
));
|
||||
// result_ok(Result<T, E>) -> T?
|
||||
env.register_fn("result_ok", fn_type(vec![result_unk.clone()], Type::Optional(Box::new(Type::Unknown))));
|
||||
// result_err(Result<T, E>) -> E?
|
||||
env.register_fn("result_err", fn_type(vec![result_unk.clone()], Type::Optional(Box::new(Type::Unknown))));
|
||||
// result_is_ok(Result<T, E>) -> Bool
|
||||
env.register_fn("result_is_ok", fn_type(vec![result_unk.clone()], Type::Bool));
|
||||
// result_is_err(Result<T, E>) -> Bool
|
||||
env.register_fn("result_is_err", fn_type(vec![result_unk.clone()], Type::Bool));
|
||||
// result_map(Result<T, E>, fn(T) -> U) -> Result<U, E>
|
||||
let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) };
|
||||
env.register_fn("result_map", fn_type(vec![result_unk.clone(), mapper.clone()], result_unk.clone()));
|
||||
// result_and_then(Result<T, E>, fn(T) -> Result<U, E>) -> Result<U, E>
|
||||
let chain_fn = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(result_unk.clone()) };
|
||||
env.register_fn("result_and_then", fn_type(vec![result_unk.clone(), chain_fn], result_unk.clone()));
|
||||
// result_ok_val(T) -> Result<T, E> — wrap a value in Ok
|
||||
env.register_fn("ok", fn_type(vec![Type::Unknown], result_unk.clone()));
|
||||
// result_err_val(E) -> Result<T, E> — wrap an error in Err
|
||||
env.register_fn("err", fn_type(vec![Type::Unknown], result_unk));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_unwrap_or_registered() {
|
||||
assert!(env().lookup_fn("result_unwrap_or").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_ok_returns_optional() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("result_ok").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Optional(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ok_and_err_registered() {
|
||||
let e = env();
|
||||
assert!(e.lookup_fn("ok").is_some());
|
||||
assert!(e.lookup_fn("err").is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! String operations: trim, split, join, contains, starts_with, ends_with,
|
||||
//! to_upper, to_lower, replace, len, chars.
|
||||
|
||||
use el_types::{Type, TypeEnv};
|
||||
use super::fn_type;
|
||||
|
||||
pub fn register(env: &mut TypeEnv) {
|
||||
let arr_str = Type::Array(Box::new(Type::String));
|
||||
|
||||
env.register_fn("string_len", fn_type(vec![Type::String], Type::Int));
|
||||
env.register_fn("string_trim", fn_type(vec![Type::String], Type::String));
|
||||
env.register_fn("string_split", fn_type(vec![Type::String, Type::String], arr_str.clone()));
|
||||
env.register_fn("string_join", fn_type(vec![arr_str.clone(), Type::String], Type::String));
|
||||
env.register_fn("string_contains", fn_type(vec![Type::String, Type::String], Type::Bool));
|
||||
env.register_fn("string_starts_with", fn_type(vec![Type::String, Type::String], Type::Bool));
|
||||
env.register_fn("string_ends_with", fn_type(vec![Type::String, Type::String], Type::Bool));
|
||||
env.register_fn("string_to_upper", fn_type(vec![Type::String], Type::String));
|
||||
env.register_fn("string_to_lower", fn_type(vec![Type::String], Type::String));
|
||||
env.register_fn("string_replace", fn_type(vec![Type::String, Type::String, Type::String], Type::String));
|
||||
env.register_fn("string_chars", fn_type(vec![Type::String], arr_str.clone()));
|
||||
env.register_fn("string_slice", fn_type(vec![Type::String, Type::Int, Type::Int], Type::String));
|
||||
env.register_fn("string_repeat", fn_type(vec![Type::String, Type::Int], Type::String));
|
||||
env.register_fn("string_reverse", fn_type(vec![Type::String], Type::String));
|
||||
env.register_fn("string_parse_int", fn_type(vec![Type::String], Type::Optional(Box::new(Type::Int))));
|
||||
env.register_fn("string_parse_float", fn_type(vec![Type::String], Type::Optional(Box::new(Type::Float))));
|
||||
env.register_fn("string_from_int", fn_type(vec![Type::Int], Type::String));
|
||||
env.register_fn("string_from_float", fn_type(vec![Type::Float], Type::String));
|
||||
env.register_fn("string_is_empty", fn_type(vec![Type::String], Type::Bool));
|
||||
env.register_fn("string_concat", fn_type(vec![Type::String, Type::String], Type::String));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn env() -> TypeEnv {
|
||||
let mut e = TypeEnv::with_builtins();
|
||||
register(&mut e);
|
||||
e
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_len_returns_int() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("string_len").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Int)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_split_returns_array() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("string_split").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Array(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_contains_returns_bool() {
|
||||
let e = env();
|
||||
let ty = e.lookup_fn("string_contains").unwrap();
|
||||
assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Bool)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user