[add] interpret: in-place reassignment

[add] modules::methods::string - #STRING::isalpha
[add] modules::methods::string - #STRING::isalnum
[add] modules::methods::array
[add] modules::methods::array - #ARRAY::__getitem__
[add] modules::methods::array - #ARRAY::__setitem__
[fix] grammar: whitespace in ARRAY and TUPLE
[add] interpret: "children" property in ReturnValue struct
[add] interpret: call_method
[fix] interpret: clean reused code
[add] support (some) lua syntax
[chore] bump version (v0.5.4 -> v0.6.0)
This commit is contained in:
hkau 2024-02-09 21:20:17 -05:00
parent 6ccab7c5e8
commit a248d83f54
11 changed files with 557 additions and 174 deletions

1
.gitignore vendored
View File

@ -3,5 +3,6 @@ Cargo.lock
# test
*.py
*.lua
*.txt
ast.json

View File

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

View File

@ -6,6 +6,7 @@ Amethystine (named after the "Amethystine Python") is a lightweight Python inter
```bash
amethystine input.py (flags)
amethystine input.lua (flags) # experimental
```
### Flags
@ -21,7 +22,7 @@ Amethystine currently supports the following Python language features:
- Variable declaration
- Function declaration
- All blocks that are expected to contain body content consisting of other blocks are transformed to contain an ending block during parsing. This block can be manually inserted by adding `<end>` after your function/conditional
- All blocks that are expected to contain body content consisting of other blocks are transformed to contain an ending block during parsing. This block can be manually inserted by adding `end` after your function/conditional
- Basic standard functions
- The `type` function will return types defined in the grammar file, not Python types (example: `STRING`, `FLOAT`, `NULL`)
- (`QUICK_CONDITIONAL_IF`) Mathematical operations
@ -40,3 +41,60 @@ Amethystine supports the following datatypes:
- `NULL`
- `ARRAY` (soon)
- `TUPLE` (soon)
Amethystine also supports using **Lua syntax** (the standard library is still based on Python with some [additions](#standard-library)):
```py
# python input
x = "Hello, world!"
def log(y):
print(y)
log(x.upper())
# read as
x = "Hello, world!"
def log(y):
print(y)
dump()
<end>
log(STRING::upper(x))
```
```lua
-- lua input
local x = "Hello, world!"
print(x.upper())
function log(y):
print(y)
end
log(x.upper())
-- read as
x = "Hello, world!"
function log(y):
print(y)
dump()
<end>
log(STRING::upper(x))
```
### Standard Library
Amethystine aims to support *most* standard functions from the Python standard library. It also implements some of its own:
- `bits`: `(input: FLOAT) -> FLOAT`, represent a `FLOAT` number in bits
- `id`: `(input: ANY) -> STRING`, return a pointer (string representation) to the `input` argument
- This method uses an unsafe Rust function: [`std::slice::from_raw_parts`](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html)
- **PLANNED** `STRING::match`: `(pattern: STRING) -> ARRAY`, match a regex `STRING` against another `STRING`
- **PLANNED** `STRING::chars`: `() -> ARRAY`, return an `ARRAY` containg a separate string for every character in a `STRING`
- Equivalent to `STRING::split(..., "")`

View File

@ -5,12 +5,12 @@ FILE = { SOI ~ BLOCKS* ~ EOI }
BLOCKS = { VARDEC | FNDEC | RETURN | FNCALL | METHODCALL | CONDITIONAL_IF | QUICK_CONDITIONAL_IF | QUICK_CONDITIONAL_BLOCK | FORLOOP | VALUE }
KEY = @{ IDENTIFIER | STRING }
VARDEC = { KEY ~ "=" ~ BLOCKS }
VARDEC = { ("local" | "let" | "$:")? ~ KEY ~ "=" ~ BLOCKS }
FNCALL = { IDENTIFIER ~ "(" ~ (BLOCKS)* ~ ")" }
METHODCALL = { VALUE ~ ("." ~ FNCALL)+ }
FNCALL = { IDENTIFIER ~ "(" ~ (BLOCKS)* ~ ")" }
METHODCALL = { VALUE ~ ("." ~ FNCALL)+ }
RETURN = { "return" ~ VALUE }
FNDEC = { "def" ~ IDENTIFIER ~ "(" ~ VALUE* ~ "):" ~ BLOCKS* ~ "<end>" }
FNDEC = { ("def" | "fn" | "function" | "@:") ~ IDENTIFIER ~ "(" ~ VALUE* ~ "):" ~ BLOCKS* ~ "<end>" }
CONDITIONAL_IF = { "if" ~ (VALUE ~ OPERATOR ~ VALUE)* ~ ":" ~ BLOCKS* ~ "<end>" ~ (CONDITIONAL_ELSE | CONDITIONAL_ELIF)? }
CONDITIONAL_ELSE = { "else:" ~ BLOCKS* ~ "<end>" }
@ -25,7 +25,7 @@ WHITESPACE = _{ " " | "\t" | "\r" | "\n" | "," }
COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* }
OPERATOR = @{ "+" | "-" | "*" | "/" | ">" | "<" | "==" | "!=" | ">=" | "<=" }
IDENTIFIER = @{ (ASCII_ALPHANUMERIC | "_" | "-" | ".")+ }
IDENTIFIER = @{ (ASCII_ALPHANUMERIC | "_" | "-" | ":")+ }
VALUE = @{ NULL | BOOL | INT | FLOAT | STRING | IDENTIFIER | ARRAY | TUPLE }
NULL = @{ "null" | "&0" }
BOOL = @{ "True" | "False" }
@ -35,7 +35,7 @@ FLOAT = @{ ("+" | "-")? ~ INT ~ ("." ~ DIGITS ~ EXP? | EXP)? }
EXP = @{ ("E" | "e") ~ ("+" | "-")? ~ INT }
STRING = @{ ("\"" | "'") ~ INNER ~ ("\"" | "'") }
INNER = @{ (!("\"" | "\\" | "\u{0000}" | "\u{001F}") ~ ANY)* ~ (ESCAPE ~ INNER)? }
ARRAY = @{ "[" ~ VALUE ~ ("," ~ VALUE)* ~ ","? ~ "]" | "[" ~ "]" }
TUPLE = @{ "(" ~ VALUE ~ ("," ~ VALUE)* ~ ","? ~ ")" | "(" ~ ")" }
ARRAY = @{ "[" ~ VALUE ~ ("," ~ WHITESPACE? ~ VALUE)* ~ ","? ~ "]" | "[" ~ "]" }
TUPLE = @{ "(" ~ VALUE ~ ("," ~ WHITESPACE? ~ VALUE)* ~ ","? ~ ")" | "(" ~ ")" }
ESCAPE = @{ "\\" ~ ("b" | "t" | "n" | "f" | "r" | "\"" | "\\" | NEWLINE)? }

View File

@ -12,21 +12,23 @@ pub type VariablesStore = HashMap<String, ReturnValue>;
pub struct ReturnValue {
pub rule: Rule,
pub value: String,
pub children: Option<Vec<ReturnValue>>, // only really useful for ARRAY and TUPLE
}
impl ReturnValue {
fn new(rule: Rule) -> ReturnValue {
pub fn new(rule: Rule) -> ReturnValue {
return ReturnValue {
rule,
value: String::new(),
children: Option::None,
};
}
fn set_value(&mut self, val: String) {
pub fn set_value(&mut self, val: String) {
self.value = val;
}
fn set_rule(&mut self, rule: Rule) {
pub fn set_rule(&mut self, rule: Rule) {
self.rule = rule;
}
@ -57,6 +59,9 @@ impl ReturnValue {
Rule::FNCALL
} else if (input == String::from("True")) | (input == String::from("False")) {
Rule::BOOL
} else if input.starts_with("@: ") {
// this will be used to tell when a method is returning an updated version of the object
Rule::VARDEC
} else {
Rule::IDENTIFIER
};
@ -111,6 +116,7 @@ pub fn from_tree(
return ReturnValue {
rule: Rule::NULL,
value: String::new(),
children: Option::None,
};
}),
);
@ -121,6 +127,7 @@ pub fn from_tree(
// instance methods
functions = concat_functions(functions, crate::modules::methods::string::import());
functions = concat_functions(functions, crate::modules::methods::array::import());
// ...
for block in tree.clone().into_iter() {
@ -150,6 +157,24 @@ pub fn from_tree(
&variables,
);
}
// method call
else if block.as_rule() == Rule::METHODCALL {
let res = call_method(
block,
true,
Option::None,
&functions,
&rt_functions,
&variables,
);
// remove dynamic attribute
let mut new_value = res.children.unwrap().get(0).unwrap().to_owned();
new_value.value = new_value.value.replace("@: ", "");
// set variable
variables.insert(res.value, new_value);
}
// variable declaration
else if btype == Rule::VARDEC {
// get values
@ -343,6 +368,7 @@ pub fn from_tree(
ReturnValue {
rule: Rule::FLOAT,
value: _arg.to_string(),
children: Option::None,
},
);
@ -425,66 +451,77 @@ fn run_conditional(
return ReturnValue {
rule: Rule::FLOAT,
value: (c + cw).to_string(),
children: Option::None,
}
}
"-" => {
return ReturnValue {
rule: Rule::FLOAT,
value: (c - cw).to_string(),
children: Option::None,
}
}
"*" => {
return ReturnValue {
rule: Rule::FLOAT,
value: (c * cw).to_string(),
children: Option::None,
}
}
"/" => {
return ReturnValue {
rule: Rule::FLOAT,
value: (c / cw).to_string(),
children: Option::None,
}
}
">" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c > cw).to_string(),
children: Option::None,
}
}
"<" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c < cw).to_string(),
children: Option::None,
}
}
"==" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c == cw).to_string(),
children: Option::None,
}
}
"!=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c != cw).to_string(),
children: Option::None,
}
}
">=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c >= cw).to_string(),
children: Option::None,
}
}
"<=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c <= cw).to_string(),
children: Option::None,
}
}
_ => {
return ReturnValue {
rule: Rule::BOOL,
value: String::from("false"),
children: Option::None,
}
}
}
@ -498,50 +535,58 @@ fn run_conditional(
match op.value.as_str() {
"+" => {
return ReturnValue {
rule: Rule::FLOAT,
rule: Rule::STRING,
value: c + &cw,
children: Option::None,
}
}
">" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c > cw).to_string(),
children: Option::None,
}
}
"<" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c < cw).to_string(),
children: Option::None,
}
}
"==" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c == cw).to_string(),
children: Option::None,
}
}
"!=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c != cw).to_string(),
children: Option::None,
}
}
">=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c >= cw).to_string(),
children: Option::None,
}
}
"<=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c <= cw).to_string(),
children: Option::None,
}
}
_ => {
return ReturnValue {
rule: Rule::BOOL,
value: String::from("false"),
children: Option::None,
}
}
}
@ -557,18 +602,21 @@ fn run_conditional(
return ReturnValue {
rule: Rule::BOOL,
value: (c == cw).to_string(),
children: Option::None,
}
}
"!=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c != cw).to_string(),
children: Option::None,
}
}
_ => {
return ReturnValue {
rule: Rule::BOOL,
value: String::from("false"),
children: Option::None,
}
}
}
@ -584,18 +632,21 @@ fn run_conditional(
return ReturnValue {
rule: Rule::BOOL,
value: (c == cw).to_string(),
children: Option::None,
}
}
"!=" => {
return ReturnValue {
rule: Rule::BOOL,
value: (c != cw).to_string(),
children: Option::None,
}
}
_ => {
return ReturnValue {
rule: Rule::BOOL,
value: String::from("false"),
children: Option::None,
}
}
}
@ -605,6 +656,7 @@ fn run_conditional(
return ReturnValue {
rule: Rule::BOOL,
value: String::from("false"),
children: Option::None,
};
}
@ -681,9 +733,90 @@ fn call_function(
return ReturnValue {
rule: Rule::NULL,
value: String::new(),
children: Option::None,
};
}
fn call_method(
block: Pair<'_, Rule>,
panic_on_invalid: bool,
transform_fn: Option<String>,
// ...
functions: &FunctionsStore,
rt_functions: &RuntimeFunctionsStore,
variables: &VariablesStore,
) -> ReturnValue {
// method call
let inner = block.into_inner().collect::<Vec<Pair<'_, Rule>>>();
let instance = inner.get(0).unwrap();
let fncalls = inner.iter().filter(|b| b.as_rule() == Rule::FNCALL);
let mut res: Option<ReturnValue> = Option::None;
for fncall in fncalls {
// get the value we're calling on
// it's going to be the existing "res" value IF IT EXISTS
// this makes method chaining possible!
let instance_val = if res.is_none() {
get_block_value(
instance.to_owned(),
panic_on_invalid,
transform_fn.clone(),
functions,
rt_functions,
variables,
)
} else {
res.unwrap()
};
// ...
let mut fn_inner = fncall.clone().into_inner();
let fn_name = fn_inner.find(|b| b.as_rule() == Rule::IDENTIFIER).unwrap();
// clone variables and set 'self'
let mut variables_c = variables.clone();
variables_c.insert(String::from("self"), instance_val.clone());
// get real function name
let real_fn_name = format!(
"{:?}::{}",
instance_val.get_type(),
fn_name.as_span().as_str()
);
// get result of fn_call and push to res
res = Option::Some(get_block_value(
fncall.to_owned(),
panic_on_invalid,
Option::Some(real_fn_name),
functions,
rt_functions,
&variables_c,
));
}
// handle VARDEC
let unwrap = res.unwrap();
if unwrap.value.starts_with("@: ") {
// return NULL by default
let mut children = Vec::new();
children.push(unwrap);
return ReturnValue {
// we're going to return VARDEC with the value set to the name of the identifier we're editing,
// and the "children" vec containing the updated value
rule: Rule::VARDEC,
value: instance.as_span().as_str().to_string(),
children: Option::Some(children),
};
}
// ...
return unwrap;
}
fn get_variable(
block: Pair<'_, Rule>,
variables: &VariablesStore,
@ -706,7 +839,7 @@ fn get_variable(
}
fn get_block_value(
block: Pair<'_, Rule>,
mut block: Pair<'_, Rule>,
panic_on_invalid: bool,
transform_fn: Option<String>,
// ...
@ -717,6 +850,11 @@ fn get_block_value(
// parse block inner
let inner: String = block.as_span().as_str().to_string();
// escalate out of BLOCKS
if block.as_rule() == Rule::BLOCKS {
block = block.into_inner().nth(0).unwrap();
}
// ideally we'll use a function to detect the type here
let btype: Rule = ReturnValue::get_type_from_value(inner.clone());
@ -724,67 +862,28 @@ fn get_block_value(
return ReturnValue {
rule: Rule::OPERATOR,
value: inner,
children: Option::None,
};
} else if block.as_rule() == Rule::BLOCKS {
let block = block.into_inner().nth(0).unwrap();
} else if block.as_rule() == Rule::QUICK_CONDITIONAL_IF {
let inner = block.into_inner().collect::<Vec<Pair<'_, Rule>>>();
if block.as_rule() == Rule::QUICK_CONDITIONAL_IF {
let inner = block.into_inner().collect::<Vec<Pair<'_, Rule>>>();
// get compared, operator, and compare_with
// these are basic state variables that will be updated as we come across them
let mut compared: Option<ReturnValue> = Option::None;
let mut op: Option<ReturnValue> = Option::None;
let mut compare_with: Option<ReturnValue> = Option::None;
// get compared, operator, and compare_with
// these are basic state variables that will be updated as we come across them
let mut compared: Option<ReturnValue> = Option::None;
let mut op: Option<ReturnValue> = Option::None;
let mut compare_with: Option<ReturnValue> = Option::None;
let mut out: ReturnValue = ReturnValue {
rule: Rule::FLOAT,
value: String::from("0"),
children: Option::None,
};
let mut out: ReturnValue = ReturnValue {
rule: Rule::FLOAT,
value: String::from("0"),
};
for con_block in inner {
if con_block.as_rule() == Rule::VALUE {
// if we don't have anything to compare with, update compared
if compared.is_none() {
compared = Option::Some(get_block_value(
con_block,
true,
Option::None,
&functions,
&rt_functions,
&variables,
));
} else if compare_with.is_none() {
compare_with = Option::Some(get_block_value(
con_block,
true,
Option::None,
&functions,
&rt_functions,
&variables,
));
// run conditional
if compared.is_some() && op.is_some() && compare_with.is_some() {
// get values
// update value with conditional result
let res = run_conditional(
compared.unwrap(),
op.unwrap(),
compare_with.clone().unwrap(),
);
out.set_value(res.clone().value); // set out value to result
// clear values
compared = Option::Some(res); // set compared to result so our next operation is based on this return
op = Option::None;
// compare_with = Option::None;
}
}
} else if con_block.as_rule() == Rule::OPERATOR {
op = Option::Some(get_block_value(
for con_block in inner {
if con_block.as_rule() == Rule::VALUE {
// if we don't have anything to compare with, update compared
if compared.is_none() {
compared = Option::Some(get_block_value(
con_block,
true,
Option::None,
@ -792,107 +891,51 @@ fn get_block_value(
&rt_functions,
&variables,
));
} else if compare_with.is_none() {
compare_with = Option::Some(get_block_value(
con_block,
true,
Option::None,
&functions,
&rt_functions,
&variables,
));
// run conditional
if compared.is_some() && op.is_some() && compare_with.is_some() {
// get values
// update value with conditional result
let res = run_conditional(
compared.unwrap(),
op.unwrap(),
compare_with.clone().unwrap(),
);
out.set_value(res.clone().value); // set out value to result
// clear values
compared = Option::Some(res); // set compared to result so our next operation is based on this return
op = Option::None;
// compare_with = Option::None;
}
}
}
return out;
}
// function call FROM WITHIN "BLOCKS" TYPE
else if block.as_rule() == Rule::FNCALL {
// call function
let mut inner = block.into_inner();
let fn_name = inner.find(|b| b.as_rule() == Rule::IDENTIFIER);
let fn_name_str = if transform_fn.is_none() {
fn_name.unwrap().as_span().as_str().to_string()
} else {
transform_fn.unwrap()
};
let res = call_function(fn_name_str, inner, &functions, &rt_functions, &variables);
// attempt to get function return
return res; // push result NOT function call!
}
// method call
else if block.as_rule() == Rule::METHODCALL {
let inner = block.into_inner().collect::<Vec<Pair<'_, Rule>>>();
let instance = inner.get(0).unwrap();
let fncalls = inner.iter().filter(|b| b.as_rule() == Rule::FNCALL);
let mut res: Option<ReturnValue> = Option::None;
for fncall in fncalls {
// get the value we're calling on
// it's going to be the existing "res" value IF IT EXISTS
// this makes method chaining possible!
let instance_val = if res.is_none() {
get_block_value(
instance.to_owned(),
panic_on_invalid,
transform_fn.clone(),
functions,
rt_functions,
variables,
)
} else {
res.unwrap()
};
// ...
let mut fn_inner = fncall.clone().into_inner();
let fn_name = fn_inner.find(|b| b.as_rule() == Rule::IDENTIFIER).unwrap();
// clone variables but and set 'self'
let mut variables_c = variables.clone();
variables_c.insert(String::from("self"), instance_val.clone());
// get real function name
let real_fn_name = format!(
"{:?}.{}",
instance_val.get_type(),
fn_name.as_span().as_str()
);
// get result of fn_call and push to res
res = Option::Some(get_block_value(
fncall.to_owned(),
panic_on_invalid,
Option::Some(real_fn_name),
functions,
rt_functions,
&variables_c,
} else if con_block.as_rule() == Rule::OPERATOR {
op = Option::Some(get_block_value(
con_block,
true,
Option::None,
&functions,
&rt_functions,
&variables,
));
}
}
return res.unwrap();
}
// convert IDENTIFIER values to their variable values
else if btype == Rule::IDENTIFIER {
let varblock = get_variable(block, &variables, panic_on_invalid);
return ReturnValue {
rule: Rule::VALUE, // ideally we'll use a function to detect the type here
value: varblock.value,
};
}
// everything else
else {
return ReturnValue {
rule: btype,
value: inner,
};
}
return out;
}
// convert IDENTIFIER values to their variable values
else if btype == Rule::IDENTIFIER {
let varblock = get_variable(block, &variables, panic_on_invalid);
return ReturnValue {
rule: Rule::VALUE, // ideally we'll use a function to detect the type here
value: varblock.value,
};
}
// run functions FROM OUTSIDE "BLOCKS" TYPE
else if btype == Rule::FNCALL {
// function call FROM WITHIN "BLOCKS" TYPE
else if block.as_rule() == Rule::FNCALL {
// call function
let mut inner = block.into_inner();
@ -908,11 +951,118 @@ fn get_block_value(
// attempt to get function return
return res; // push result NOT function call!
}
// method call
else if block.as_rule() == Rule::METHODCALL {
let inner = block.into_inner().collect::<Vec<Pair<'_, Rule>>>();
let instance = inner.get(0).unwrap();
let fncalls = inner.iter().filter(|b| b.as_rule() == Rule::FNCALL);
let mut res: Option<ReturnValue> = Option::None;
for fncall in fncalls {
// get the value we're calling on
// it's going to be the existing "res" value IF IT EXISTS
// this makes method chaining possible!
let instance_val = if res.is_none() {
get_block_value(
instance.to_owned(),
panic_on_invalid,
transform_fn.clone(),
functions,
rt_functions,
variables,
)
} else {
res.unwrap()
};
// ...
let mut fn_inner = fncall.clone().into_inner();
let fn_name = fn_inner.find(|b| b.as_rule() == Rule::IDENTIFIER).unwrap();
// clone variables and set 'self'
let mut variables_c = variables.clone();
variables_c.insert(String::from("self"), instance_val.clone());
// get real function name
let real_fn_name = format!(
"{:?}::{}",
instance_val.get_type(),
fn_name.as_span().as_str()
);
// get result of fn_call and push to res
res = Option::Some(get_block_value(
fncall.to_owned(),
panic_on_invalid,
Option::Some(real_fn_name),
functions,
rt_functions,
&variables_c,
));
}
// handle VARDEC
let unwrap = res.unwrap();
if unwrap.rule == Rule::VARDEC && instance.as_rule() == Rule::IDENTIFIER {
dbg!(&instance);
// return NULL by default
return ReturnValue::new(Rule::NULL);
}
// ...
return unwrap;
}
// convert IDENTIFIER values to their variable values
else if btype == Rule::IDENTIFIER {
return get_variable(block, &variables, panic_on_invalid);
}
// array
else if btype == Rule::ARRAY {
// parse
let span = block.as_span().as_str();
let inner: Pairs<'_, Rule> = crate::parser::parse(
// we MUST remove "[" and "]" from the array or we risk a stack overflow
span.get(1..span.len() - 1)
.expect("failed to remove array init from ARRAY"),
)
.into_inner();
// build children
let mut children: Vec<ReturnValue> = Vec::new();
for block in inner {
let rv = get_block_value(
block,
panic_on_invalid,
Option::None,
functions,
rt_functions,
variables,
);
if rv.rule == Rule::NULL {
continue;
}
children.push(rv)
}
// return
return ReturnValue {
rule: Rule::ARRAY,
value: block.as_span().as_str().to_string(),
children: Option::Some(children),
};
}
// everything else
else {
return ReturnValue {
rule: btype,
value: inner,
children: Option::None,
};
}
}

View File

@ -39,6 +39,9 @@ fn main() {
// this makes it where we can end with a function/conditional
file_contents += "\ndump()";
// 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(", ", ",");
@ -60,7 +63,14 @@ fn main() {
}
// if next line does not start with the same whitespace, add closing tag
if !next.unwrap().starts_with(&whitespace) {
let unwrap = next.unwrap();
if !unwrap.starts_with(&whitespace) {
if unwrap.trim() == "end" {
// transform "end" to "<end>"
file_contents = file_contents.replace(&unwrap, "\ndump()\n<end>");
continue;
}
file_contents = file_contents.replace(line, &format!("{}\ndump()\n<end>", line));
}
}

View File

@ -0,0 +1,89 @@
use crate::interpret::{FunctionsStore, ReturnValue};
use crate::parser::Rule;
use std::collections::HashMap;
pub fn import() -> FunctionsStore {
let mut functions: FunctionsStore = HashMap::new();
// add functions
functions.insert(
String::from("#ARRAY::__getitem__"),
Box::new(|args, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
if (fn_self.is_none()) | (fn_self.is_some() && fn_self.unwrap().rule != Rule::ARRAY) {
panic!("expected method to be called on an object with type ARRAY")
}
// get arguments
let arg = args.get(0);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::FLOAT) {
panic!("expected function to be called with a second object of type FLOAT")
}
// get
let rv = fn_self.unwrap().children.as_ref().unwrap().get(
arg.unwrap()
.value
.parse::<usize>()
.expect("cannot cast input 'FLOAT' to 'usize'"),
);
if rv.is_none() {
return ReturnValue::new(Rule::NULL);
}
// ...
return rv.unwrap().to_owned();
}),
);
functions.insert(
String::from("#ARRAY::__setitem__"),
Box::new(|args, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
if (fn_self.is_none()) | (fn_self.is_some() && fn_self.unwrap().rule != Rule::ARRAY) {
panic!("expected method to be called on an object with type ARRAY")
}
// get arguments
let arg = args.get(0);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::FLOAT) {
panic!("expected function to be called with a second object of type FLOAT")
}
let arg1 = args.get(1);
if arg1.is_none() {
panic!("expected function to be called with a third object of type ANY")
}
// update current
let mut children = fn_self.unwrap().children.as_ref().unwrap().clone();
children.insert(
// index
arg.unwrap()
.value
.parse::<usize>()
.expect("cannot cast input 'FLOAT' to 'usize'"),
// new value
arg1.unwrap().clone(),
);
// ...
return ReturnValue {
rule: Rule::ARRAY,
value: String::from("@: dynamic"),
children: Option::Some(children),
};
}),
);
// return
return functions;
}

View File

@ -1 +1,2 @@
pub mod array;
pub mod string;

View File

@ -7,7 +7,7 @@ pub fn import() -> FunctionsStore {
// add functions
functions.insert(
String::from("#STRING.startswith"),
String::from("#STRING::startswith"),
Box::new(|args, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
@ -38,12 +38,13 @@ pub fn import() -> FunctionsStore {
.replace("\"", "")
.starts_with(&arg.value.replace("\"", "")))
.to_string(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING.endswith"),
String::from("#STRING::endswith"),
Box::new(|args, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
@ -74,12 +75,13 @@ pub fn import() -> FunctionsStore {
.replace("\"", "")
.ends_with(&arg.value.replace("\"", "")))
.to_string(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING.lower"),
String::from("#STRING::lower"),
Box::new(|_, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
@ -92,12 +94,13 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: fn_self.unwrap().value.replace("\"", "").to_lowercase(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING.upper"),
String::from("#STRING::upper"),
Box::new(|_, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
@ -110,12 +113,13 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: fn_self.unwrap().value.replace("\"", "").to_uppercase(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING.strip"),
String::from("#STRING::strip"),
Box::new(|_, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
@ -128,6 +132,61 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: fn_self.unwrap().value.replace("\"", "").trim().to_string(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING::isalpha"),
Box::new(|_, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
if (fn_self.is_none()) | (fn_self.is_some() && fn_self.unwrap().rule != Rule::STRING) {
panic!("expected method to be called on an object with type STRING")
}
// ...
let regex = regex::RegexBuilder::new("^([A-Z][a-z]*?)$")
.multi_line(true)
.build()
.unwrap();
// ...
return ReturnValue {
rule: Rule::STRING,
value: regex
.is_match(&fn_self.unwrap().value.replace("\"", ""))
.to_string(),
children: Option::None,
};
}),
);
functions.insert(
String::from("#STRING::isalnum"),
Box::new(|_, vars| {
// get the object this method is being called on
let fn_self = vars.get("self"); // 'self'
if (fn_self.is_none()) | (fn_self.is_some() && fn_self.unwrap().rule != Rule::STRING) {
panic!("expected method to be called on an object with type STRING")
}
// ...
let regex = regex::RegexBuilder::new("^(\\w*?)$")
.multi_line(true)
.build()
.unwrap();
// ...
return ReturnValue {
rule: Rule::STRING,
value: regex
.is_match(&fn_self.unwrap().value.replace("\"", ""))
.to_string(),
children: Option::None,
};
}),
);

View File

@ -24,6 +24,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::FLOAT,
value: arg.unwrap().value.parse::<f32>().unwrap().abs().to_string(),
children: Option::None,
};
}),
);
@ -56,6 +57,7 @@ pub fn import() -> FunctionsStore {
.unwrap()
.to_bits()
.to_string(),
children: Option::None,
};
}),
);
@ -111,6 +113,7 @@ pub fn import() -> FunctionsStore {
value: unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(p, 5)).to_owned()
},
children: Option::None,
};
}),
);
@ -147,6 +150,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: format!("\"{}\"", string),
children: Option::None,
};
}),
);
@ -174,12 +178,17 @@ pub fn import() -> FunctionsStore {
ReturnValue::as_printable(ReturnValue {
rule: btype,
value: val,
children: Option::None,
})
.len()
.to_string()
} else if (btype == Rule::ARRAY) | (btype == Rule::TUPLE) {
// array, tuple
input.children.as_ref().unwrap().len().to_string()
} else {
val.len().to_string()
},
children: Option::None,
};
}),
);
@ -212,7 +221,7 @@ pub fn import() -> FunctionsStore {
let arg1 = args.get(1);
if (arg.is_none()) | (arg.is_some() && arg.unwrap().rule != Rule::FLOAT) {
if (arg1.is_none()) | (arg1.is_some() && arg1.unwrap().rule != Rule::FLOAT) {
panic!("expected function to be called with a second object of type FLOAT")
}
@ -226,6 +235,7 @@ pub fn import() -> FunctionsStore {
.unwrap()
.powf(arg1.unwrap().value.parse::<f32>().unwrap())
.to_string(),
children: Option::None,
};
}),
);
@ -245,6 +255,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::NULL,
value: String::from("null"),
children: Option::None,
};
}),
);
@ -259,6 +270,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: format!("\"{}\"", printable),
children: Option::None,
};
}),
);
@ -283,6 +295,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::FLOAT,
value: arg.unwrap().value.clone(),
children: Option::None,
};
}),
);
@ -301,6 +314,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::STRING,
value: format!("{:?}", input.to_owned().get_type()),
children: Option::None,
};
}),
);

View File

@ -28,6 +28,7 @@ pub fn import() -> FunctionsStore {
return ReturnValue {
rule: Rule::NULL,
value: String::from("null"),
children: Option::None,
};
}),
);