Skip to main content

ksl/
lib.rs

1//! # KSL core library
2//!
3//! [![github]](https://github.com/kands-code/rswk)
4//! [![crates-io]](https://crates.io/crates/ksl)
5//! [![docs-rs]](https://docs.rs/ksl)
6//!
7//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
8//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
9//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
10//!
11//! # Description
12//!
13//! This library provides a comprehensive set of functions
14//! for tokenizing and executing KSL code.
15
16mod builtin;
17pub mod eval;
18pub mod token;
19pub mod value;
20
21/// List of built-in functions.
22pub use builtin::BUILTIN_FUNCTIONS;
23/// Literal value corresponding to the error tag in Atom.
24pub use builtin::ERR_SYMBOL;
25/// Literal value corresponding to the boolean false in Atom.
26pub use builtin::FALSE_SYMBOL;
27/// Module name in environment as key's literal value.
28pub use builtin::MODULE_NAME_ENV;
29/// Imported module path list in environment as key's literal value.
30pub use builtin::MODULE_PATH_ENV;
31/// Literal value corresponding to the ok tag in Atom.
32pub use builtin::OK_SYMBOL;
33/// Literal value corresponding to the boolean true in Atom.
34pub use builtin::TRUE_SYMBOL;
35
36use crate::{builtin::ENV_ARGS_ENV, value::Value};
37
38/// Dict type in KSL
39///
40/// Currently using `FxHashMap` as the underlying implementation.
41pub type Dict = rustc_hash::FxHashMap<std::sync::Arc<str>, std::sync::Arc<Value>>;
42
43/// Scope represents a single level in the environment chain.
44#[derive(Debug)]
45pub struct Scope {
46    /// The variable store for this scope.
47    ///
48    /// Mutable and thread-safe.
49    pub store: parking_lot::RwLock<Dict>,
50    /// The parent scope.
51    ///
52    /// Immutable reference.
53    pub parent: Option<Environment>,
54}
55
56/// Environment type in KSL
57pub type Environment = std::sync::Arc<Scope>;
58
59/// Initial thread stack size.
60pub const INIT_STACK_SIZE: usize = 8 * 1024 * 1024;
61
62/// Initial Environment in KSL
63///
64/// Includes built-in functions and various constants in KSL.
65///
66/// # Example
67///
68/// ```rust
69/// let _ = ksl::init_environment(None); 
70/// ```
71pub fn init_environment(env_args: Option<Value>) -> Environment {
72    let mut store = Dict::default();
73    // bifs
74    for &func in BUILTIN_FUNCTIONS.iter() {
75        store.insert(
76            std::sync::Arc::from(func),
77            std::sync::Arc::new(Value::Builtin(func)),
78        );
79    }
80    // module loading paths
81    store.insert(
82        MODULE_PATH_ENV.clone(),
83        std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::default())),
84    );
85    // environment variables
86    if let Some(val) = env_args
87        && let Value::List(_) = val
88    {
89        store.insert(ENV_ARGS_ENV.clone(), std::sync::Arc::new(val));
90    }
91    // construct environment
92    std::sync::Arc::new(Scope {
93        store: parking_lot::RwLock::new(store),
94        parent: None,
95    })
96}
97
98/// Find a variable in the environment chain.
99pub fn read_from_environment(env: &Environment, key: &str) -> Option<std::sync::Arc<Value>> {
100    let mut current = env;
101    loop {
102        // try currnet scope
103        if let Some(val) = current.store.upgradable_read().get(key) {
104            return Some(val.clone());
105        }
106        // find in parent scope
107        match &current.parent {
108            Some(parent) => {
109                current = parent;
110            }
111            None => {
112                // otherwise
113                return None;
114            }
115        }
116    }
117}
118
119/// Find the environment where the variable is defined.
120pub fn find_define_environment(env: &Environment, key: &str) -> Option<Environment> {
121    let mut current = env;
122    loop {
123        // try currnet scope
124        if current.store.upgradable_read().contains_key(key) {
125            return Some(current.clone());
126        }
127        // find in parent scope
128        match current.parent.as_ref() {
129            Some(parent) => {
130                current = parent;
131            }
132            None => {
133                // otherwise
134                return None;
135            }
136        }
137    }
138}
139
140/// Check if two numbers are equal.
141pub fn is_number_eq(n1: f64, n2: f64) -> bool { (n1 == n2) || (n1 - n2).abs() <= n1.abs().max(n2.abs()) * f64::EPSILON }
142
143/// Expand the tilde at the start of a path.
144pub fn expand_tilde(path: std::sync::Arc<str>) -> Result<std::path::PathBuf, std::sync::Arc<str>> {
145    match path.strip_prefix("~/") {
146        Some(rest) => std::env::home_dir()
147            .ok_or_else(|| {
148                std::sync::Arc::from(
149                    concat!(
150                        "Error[ksl::expand_tilde]: ",
151                        "Failed to get user home directory path."
152                    )
153                    .to_string(),
154                )
155            })
156            .map(|p| p.join(rest)),
157        None => Ok(std::path::PathBuf::from(path.as_ref())),
158    }
159}