[add] modules::fs

[add] modules::fs::read_utf8
[add] modules::fs::write_utf8
[add] modules::fs::remove
TODO: modules::fs - remove_dir, mkdir, read_dir (into array)
[add] modules::math::eq
[add] modules::math::neq
[add] modules::math::not
[add] modules::math::con_eval
[add] don't import all standard library functions by default
This commit is contained in:
hkau 2024-02-22 17:38:32 -05:00
parent 5fade5e530
commit b952f61eb2
16 changed files with 459 additions and 46 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "amethystine"
version = "0.7.2"
version = "0.7.3"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -199,36 +199,19 @@ pub fn from_tree(
// import modules
functions = concat_functions(functions, crate::modules::standard::import());
functions = concat_functions(functions, crate::modules::time::import());
variables.insert(
String::from("time"),
module_to_json("time", &crate::modules::time::import()),
);
functions = concat_functions(functions, crate::modules::os::import());
variables.insert(
String::from("os"),
module_to_json("os", &crate::modules::os::import()),
);
functions = concat_functions(functions, crate::modules::state::import());
variables.insert(
String::from("state"),
module_to_json("state", &crate::modules::state::import()),
);
functions = concat_functions(functions, crate::modules::math::import());
variables.insert(
// imported by default
String::from("math"),
module_to_json("math", &crate::modules::math::import()),
);
functions = concat_functions(functions, crate::modules::regex::import());
variables.insert(
String::from("regex"),
module_to_json("regex", &crate::modules::regex::import()),
);
functions = concat_functions(functions, crate::modules::fs::import());
// instance methods
functions = concat_functions(functions, crate::modules::methods::string::import());
@ -1062,7 +1045,7 @@ fn call_function(
.into_inner();
// collect arguments and define them as variable definition blocks in rt_fun.clone()
let mut starting_varstore: VariablesStore = HashMap::new();
let mut starting_varstore: VariablesStore = variables.clone();
let expected_params = inner
.clone()
.into_iter()
@ -1075,7 +1058,6 @@ fn call_function(
for (i, arg) in expected_params.clone().enumerate() {
// get arg value
// TODO: fix starting_varstore loading
let val = args.get(i);
let param = arg.as_span().as_str().to_string();

161
src/modules/fs.rs Normal file
View File

@ -0,0 +1,161 @@
//! File system functions, `fs:` prefix
//!
//! # Import:
//! ```py
//! fs = require("#fs")
//! ```
use crate::interpret::{error_exit, FunctionsStore, ReturnValue};
use crate::parser::Rule;
use std::collections::HashMap;
/// Returns a [`string`](crate::modules::methods::table) representing the contents of a given file, returns `NULL null` on error
///
/// # Arguments:
/// * `path` - [`string`](crate::modules::methods::string) containing the path to the requested file
///
/// # Example:
/// ```py
/// fs:read_utf8("README.md")
/// ```
pub fn read_utf8(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg = args.get(0);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::string) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'string'",
)
}
// write file
let content = std::fs::read_to_string(arg.unwrap().value.clone().replace("\"", ""));
if content.is_err() {
return ReturnValue::new(Rule::null);
}
// return
ReturnValue {
rule: Rule::string,
value: content.unwrap(),
children: Option::None,
attributes: Option::None,
}
}
/// Write to a file at a [`string`](crate::modules::methods::table) path, returns `NULL null` on error and `BOOL true` on success
///
/// # Arguments:
/// * `path` - [`string`](crate::modules::methods::string) containing the path to the requested file
/// * `content` - [`string`](crate::modules::methods::string) containing the new data to commit to the file
///
/// # Example:
/// ```py
/// fs:write_utf8("README.md", "# Hello, world!")
/// ```
pub fn write_utf8(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg = args.get(0);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::string) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'string'",
)
}
let input = args.get(1);
if (input.is_none()) | (input.is_some() && input.unwrap().rule != Rule::string) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'string'",
)
}
// write file
let res = std::fs::write(
arg.unwrap().value.clone().replace("\"", ""),
input.unwrap().value.clone().replace("\"", ""),
);
if res.is_err() {
return ReturnValue::new(Rule::null);
}
// return
ReturnValue {
rule: Rule::bool,
value: String::from("true"),
children: Option::None,
attributes: Option::None,
}
}
/// Remove the file at a [`string`](crate::modules::methods::table) path, returns `NULL null` on error and `BOOL true` on success
///
/// # Arguments:
/// * `path` - [`string`](crate::modules::methods::string) containing the path to the requested file
///
/// # Example:
/// ```py
/// fs:remove("README.md")
/// ```
pub fn remove(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg = args.get(0);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::string) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'string'",
)
}
// remove file
let res = std::fs::remove_file(arg.unwrap().value.clone().replace("\"", ""));
if res.is_err() {
return ReturnValue::new(Rule::null);
}
// return
ReturnValue {
rule: Rule::bool,
value: String::from("true"),
children: Option::None,
attributes: Option::None,
}
}
// ...
/// Import module
pub fn import() -> FunctionsStore {
let mut functions: FunctionsStore = HashMap::new();
// add functions
functions.insert(
String::from("#ST_fs_read_utf8"),
Box::new(|args, _| read_utf8(args)),
);
functions.insert(
String::from("#ST_fs_write_utf8"),
Box::new(|args, _| write_utf8(args)),
);
functions.insert(
String::from("#ST_fs_remove"),
Box::new(|args, _| remove(args)),
);
// return
functions
}

View File

@ -1,6 +1,9 @@
//! Mathematical operation functions, `math:` prefix
//!
//! This module is imported by default.
use pest::iterators::Pair;
use crate::interpret::{error_exit, FunctionsStore, ReturnValue};
use crate::interpret::{error_exit, FunctionsStore, ReturnValue, VariablesStore};
use crate::parser::Rule;
use std::collections::HashMap;
@ -249,6 +252,230 @@ pub fn pow(args: Vec<ReturnValue>) -> ReturnValue {
};
}
// (compare)
/// Returns `true` is both given arguments are equal
///
/// # Arguments:
/// * `arg1` - first argument
/// * `arg2` - second second
///
/// # Example:
/// ```py
/// eq(5, 10) # false
/// ```
pub fn eq(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg1 = args.get(0);
if arg1.is_none() {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'any'",
)
}
let arg2 = args.get(1);
if arg2.is_none() {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'any'",
)
}
// return
return ReturnValue {
rule: Rule::bool,
value: (arg1 == arg2).to_string(),
children: Option::None,
attributes: Option::None,
};
}
/// Returns `true` is both given arguments are not equal
///
/// # Arguments:
/// * `arg1` - first argument
/// * `arg2` - second second
///
/// # Example:
/// ```py
/// neq(5, 10) # true
/// ```
///
/// # See:
/// * [`eq()`]
pub fn neq(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg1 = args.get(0);
if arg1.is_none() {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'any'",
)
}
let arg2 = args.get(1);
if arg2.is_none() {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'any'",
)
}
// return
return ReturnValue {
rule: Rule::bool,
value: (arg1 != arg2).to_string(),
children: Option::None,
attributes: Option::None,
};
}
/// Reverse a [`boolean`](crate::modules::methods::default)
///
/// # Arguments:
/// * `arg1` - first argument
///
/// # Example:
/// ```py
/// not(true) # false
/// not(false) # true
/// ```
pub fn not(args: Vec<ReturnValue>) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg1 = args.get(0);
if arg1.is_none() | (arg1.is_some() && arg1.unwrap().rule != Rule::bool) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'bool'",
)
}
// return
return ReturnValue {
rule: Rule::bool,
value: if arg1.unwrap().value == "true" {
String::from("false")
} else {
String::from("true")
},
children: Option::None,
attributes: Option::None,
};
}
/// Functional conditionals
///
/// Ideally you won't have to call these manually and "if", "else", and "elif" will be automatically translated into them. Ideally.
/// This is indented to replace the current conditional system (`run_conditional`).
///
/// # Arguments:
/// * `arg1` - first argument containing a [`boolean`](crate::modules::methods::default) that will be evaluated
/// * `arg2` - [`table`](crate::modules::methods::table) containing the callback function(s)
///
/// `arg2`'s `table` should contain a function named `then`, to provide an `else` statement the `table` should contain a function named `otherwise.`
/// `elif` is provided through a function named `however`.
///
/// # Example:
/// ```py
/// callbacks = table {}
///
/// def callbacks:then():
/// print(1)
///
/// def callbacks:otherwise():
/// print(2)
///
/// def callbacks:however():
/// return con_eval(eq(2, 2), callbacks) # reuse the **same** callbacks, just with a different conditional
///
/// con_eval(eq(1, 1), callbacks)
/// ```
pub fn con_eval(args: Vec<ReturnValue>, vars: VariablesStore) -> ReturnValue {
let args = args.get(0).unwrap().children.as_ref().unwrap();
// get args
let arg1 = args.get(0);
if arg1.is_none() | (arg1.is_some() && arg1.unwrap().rule != Rule::bool) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'bool'",
)
}
let arg2 = args.get(1);
if arg2.is_none() | (arg2.is_some() && arg2.unwrap().rule != Rule::table) {
error_exit(
"ARGS,TYPE",
"expected function to be called with an argument of type 'table'",
)
}
// evaluate
if arg1.unwrap().value.to_lowercase() == "true" {
// get function
let attrs = arg2.unwrap().attributes.clone().unwrap();
let func = attrs
.k_fn
.get("then")
.expect("provided callback table has no continuation");
con_eval_run(func, vars);
} else {
// otherwise
let attrs = arg2.unwrap().attributes.clone().unwrap();
let func = attrs.k_fn.get("otherwise");
if func.is_some() {
con_eval_run(func.unwrap(), vars);
}
}
// return
ReturnValue::new(Rule::null)
}
/// Private function to run callback functions in [`con_eval()`]
fn con_eval_run(func: &str, vars: VariablesStore) {
let content = crate::parser::parse(func);
// this is the same thing as crate::interpret::run_function (but with no arguments)
let inner = content
// BLOCKS
.into_inner()
.collect::<Vec<Pair<'_, Rule>>>()
.get(0)
// FNDEC
.unwrap()
.clone()
.into_inner()
.into_iter()
// FNDEC inner
.collect::<Vec<Pair<'_, Rule>>>()
.get(0)
.unwrap()
.clone()
.into_inner();
// technically we *could* return the result of the conditional here instead...
crate::interpret::from_tree(&inner, Option::Some(vars), Option::Some(HashMap::new()));
}
// ...
/// Import module
pub fn import() -> FunctionsStore {
@ -264,6 +491,15 @@ pub fn import() -> FunctionsStore {
functions.insert(String::from("#ST_math_mul"), Box::new(|args, _| mul(args)));
functions.insert(String::from("#ST_math_div"), Box::new(|args, _| div(args)));
// (compare)
functions.insert(String::from("#ST_math_eq"), Box::new(|args, _| eq(args)));
functions.insert(String::from("#ST_math_neq"), Box::new(|args, _| neq(args)));
functions.insert(String::from("#ST_math_not"), Box::new(|args, _| not(args)));
functions.insert(
String::from("#ST_math_con_eval"),
Box::new(|args, vars| con_eval(args, vars)),
);
// return
return functions;
functions
}

View File

@ -387,5 +387,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -194,5 +194,5 @@ pub fn import(typename: &str) -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -914,5 +914,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -423,5 +423,5 @@ pub fn import() -> FunctionsStore {
functions.insert(String::from("#table::iter"), Box::new(|_, vars| iter(vars)));
// return
return functions;
functions
}

View File

@ -114,5 +114,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -1,4 +1,5 @@
//! Standard Library
pub mod fs;
pub mod math;
pub mod methods;
pub mod os;

View File

@ -1,5 +1,9 @@
//! Host operating system functions, `os:` prefix
//!
//! # Import:
//! ```py
//! os = require("#os")
//! ```
use crate::interpret::{FunctionsStore, ReturnValue};
use crate::parser::Rule;
use std::collections::HashMap;
@ -63,5 +67,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -7,7 +7,11 @@
//! ## Regex Match
//! * `matches` - [`array`](crate::modules::methods::array) containing all matches in the given input
//! * `groups` - [`table`](crate::modules::methods::table) containing all groups matched in the given input
//!
//! # Import:
//! ```py
//! regex = require("#regex")
//! ```
use crate::interpret::{error_exit, FunctionsStore, KvKfnStore, ReturnValue, VariablesStore};
use crate::parser::Rule;
use std::collections::HashMap;
@ -15,7 +19,7 @@ use std::collections::HashMap;
/// Returns a [`table`](crate::modules::methods::table) object representing a group of [`regex matches`](#regex-match)
///
/// # Arguments:
/// * `regex` - [`string`](crate::modules::methods::string) containing the regular expression to execute, function returns `null` if the regex has no matches in the haystack
/// * `regex` - [`string`](crate::modules::methods::string) containing the regular expression to execute, function returns `NULL null` if the regex has no matches in the haystack
/// * `haystack` - [`string`](crate::modules::methods::string) containing the input string to be tested
///
/// # Example:
@ -161,5 +165,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -301,9 +301,31 @@ pub fn require(args: Vec<ReturnValue>) -> ReturnValue {
}
// import
let mod_name = arg.unwrap().value.clone().replace("\"", "");
// if name begins with "#" load standard library
if mod_name.starts_with("#") {
// this is only for OPTIONAL standard imports, things such as "math" are REQUIRED for function
let mod_name = mod_name.get(1..mod_name.len()).unwrap(); // remove prefix
let valid = &["state", "os", "time", "regex", "fs"];
if !valid.contains(&mod_name) {
return ReturnValue::new(Rule::null);
}
return match mod_name {
"state" => crate::interpret::module_to_json("state", &crate::modules::fs::import()),
"os" => crate::interpret::module_to_json("os", &crate::modules::os::import()),
"time" => crate::interpret::module_to_json("time", &crate::modules::time::import()),
"regex" => crate::interpret::module_to_json("regex", &crate::modules::regex::import()),
"fs" => crate::interpret::module_to_json("fs", &crate::modules::fs::import()),
_ => ReturnValue::new(Rule::null),
};
}
// read file
let file_content = crate::parser::fix_input(
std::fs::read_to_string(arg.unwrap().value.clone().replace("\"", ""))
.expect("failed to read file from import"),
std::fs::read_to_string(mod_name).expect("failed to read file from import"),
);
// parse
@ -446,5 +468,5 @@ pub fn import() -> FunctionsStore {
// TODO: "zip" (https://docs.python.org/3/library/functions.html#zip)
// return
return functions;
functions
}

View File

@ -1,5 +1,9 @@
//! Interpreter state functions, `state:` prefix
//!
//! # Import:
//! ```py
//! state = require("#state")
//! ```
use crate::interpret::{FunctionsStore, KvKfnStore, ReturnValue, VariablesStore};
use crate::parser::Rule;
use std::collections::HashMap;
@ -35,5 +39,5 @@ pub fn import() -> FunctionsStore {
);
// return
return functions;
functions
}

View File

@ -1,6 +1,10 @@
//! Interpreter time functions, `time:` prefix
//!
//! # Import:
//! ```py
//! time = require("#time")
//! ```
use std::time::{SystemTime, UNIX_EPOCH};
use crate::interpret::{FunctionsStore, ReturnValue};
use crate::parser::Rule;
use std::collections::HashMap;
@ -59,7 +63,7 @@ pub fn import() -> FunctionsStore {
functions.insert(String::from("#ST_time_now"), Box::new(|_, _| now()));
// return
return functions;
functions
}
pub fn unix_epoch_timestamp() -> u128 {

View File

@ -26,11 +26,6 @@ pub fn fix_input(mut file_contents: String) -> String {
// convert lua comments to python comments
file_contents = file_contents.replace(r"-- ", "# ");
// TODO: fix weird comma bug with arrays/tuples
// file_contents = file_contents.replace(", ", ",");
// TODO: remove \n in arrays and tuples
// add ending tags
// we'll need these to detect the end of a scoped block
let file_contents_c = file_contents.clone();