mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
.
This commit is contained in:
parent
2d79e75ba2
commit
b39a768099
73
README.md
73
README.md
@ -7,49 +7,30 @@ It is designed to be safe (it doesn't crash at runtime) and as simple as possibl
|
|||||||
|
|
||||||
### Simplicity
|
### Simplicity
|
||||||
|
|
||||||
Mers aims to be very simple, as in, having very few "special" things.
|
Mers is simple. There are only few expressions:
|
||||||
But this means that many things you may be familiar with simply don't exist in mers,
|
|
||||||
because they aren't actually needed.
|
|
||||||
This includes:
|
|
||||||
|
|
||||||
**Exceptions**, because errors in mers are just values.
|
- Values (`1`, `"my string"`, ...)
|
||||||
A function to read a UTF-8 text file from disk could have a return type like `String/IOError/UTF8DecodeError`,
|
- Blocks (`{}`)
|
||||||
which tells you exactly what errors can happen and also forces you to handle them (see later for mers' type-system).
|
- Tuples (`()`)
|
||||||
|
- Assignments (`=`)
|
||||||
|
- Variable initializations (`:=`)
|
||||||
|
- Variables (`my_var`, `&my_var`)
|
||||||
|
- If statements (`if <condition> <then> [else <else>]`)
|
||||||
|
- Functions (`arg -> <do something>`)
|
||||||
|
- Function calls `arg.function`
|
||||||
|
|
||||||
**Loops**, because, as it turns out, a function which takes an iterable and a function can do this just fine:
|
Everything else is implemented as a function:
|
||||||
```
|
|
||||||
my_list := (1, 2, 3, "four", "five", 6.5).as_list
|
|
||||||
(my_list, val -> val.println).for_each
|
|
||||||
```
|
|
||||||
Javascript has `forEach`, Rust `for_each`, C# `Each`, and Java has `forEach`.
|
|
||||||
At first, it seemed stupid to have a function that does the same thing a `for` or `foreach` loop can already do,
|
|
||||||
but if a function can do the job, why do we even need a special language construct to do this for us?
|
|
||||||
To keep it simple, mers doesn't have any loops - just functions you can use to loop.
|
|
||||||
|
|
||||||
**Breaks** aren't necessary, since this can be achieved using iterators or by returning `(T)` in a `loop`.
|
|
||||||
It is also similar to `goto`, because it makes control flow less obvious, so it had to go.
|
|
||||||
|
|
||||||
The same control flow obfuscation issue exists for **returns**, so these also aren't a thing.
|
|
||||||
A function simply returns the value created by its expression.
|
|
||||||
|
|
||||||
**Functions** do exist, but they have one key difference: They take exactly one argument. Always.
|
|
||||||
Why? Because we don't need anything else.
|
|
||||||
A function with no arguments now takes an empty tuple `()`,
|
|
||||||
a function with two arguments now takes a two-length tuple `(a, b)`,
|
|
||||||
a function with either zero, one, or three arguments now takes a `()/(a)/(a, b, c)`,
|
|
||||||
and a function with n arguments takes a list, or a list as part of a tuple, or an optional list via `()/<the list>`.
|
|
||||||
|
|
||||||
### Types and Safety
|
### Types and Safety
|
||||||
|
|
||||||
Mers is built around a type-system where a value could be one of multiple types.
|
Mers is built around a type-system where a value could be one of multiple types.
|
||||||
This is basically what dynamic typing allows you to do:
|
Dynamic typing allows you to do:
|
||||||
```
|
```
|
||||||
x := if condition { 12 } else { "something went wrong" }
|
x := if condition { 12 } else { "something went wrong" }
|
||||||
```
|
```
|
||||||
This would be valid code.
|
In mers, the compiler tracks all the types in your program,
|
||||||
However, in mers, the compiler still tracks all the types in your program,
|
|
||||||
and it will catch every possible crash before the program even runs:
|
and it will catch every possible crash before the program even runs:
|
||||||
If we tried to use `x` as an int, the compiler would complain since it might be a string, so this does not compile:
|
If we tried to use `x` as an int, the compiler would complain since it might be a string, so this **does not compile**:
|
||||||
```
|
```
|
||||||
list := (1, 2, if true 3 else "not an int")
|
list := (1, 2, if true 3 else "not an int")
|
||||||
list.sum.println
|
list.sum.println
|
||||||
@ -72,6 +53,32 @@ But mers will catch this and show an error, because the call to `sum` inside of
|
|||||||
|
|
||||||
(note: type-checks aren't implemented for all functions yet - some are just `todo!()`s, so mers will crash while checking your program. you may need to use `--check no` to get around this and deal with runtime panics for now)
|
(note: type-checks aren't implemented for all functions yet - some are just `todo!()`s, so mers will crash while checking your program. you may need to use `--check no` to get around this and deal with runtime panics for now)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
Errors in mers are normal values.
|
||||||
|
For example, `("ls", ("/")).run_command` has the return type `({Int/()}, String, String)/RunCommandError`.
|
||||||
|
This means it either returns the result of the command (exit code, stdout, stderr) or an error (a value of type `RunCommandError`).
|
||||||
|
|
||||||
|
So, if we want to print the programs stdout, we could try
|
||||||
|
|
||||||
|
```
|
||||||
|
(s, stdout, stderr) := ("ls", ("/")).run_command
|
||||||
|
stdout.println
|
||||||
|
```
|
||||||
|
|
||||||
|
But if we encountered a `RunCommandError`, mers couldn't assign the value to `(s, stdout, stderr)`, so this doesn't compile.
|
||||||
|
Instead, we need to handle the error case, using the `try` function:
|
||||||
|
|
||||||
|
```
|
||||||
|
(
|
||||||
|
("ls", ("/")).run_command,
|
||||||
|
(
|
||||||
|
(s, stdout, stderr) -> stdout.println,
|
||||||
|
error -> error.println,
|
||||||
|
)
|
||||||
|
).try
|
||||||
|
```
|
||||||
|
|
||||||
## docs
|
## docs
|
||||||
|
|
||||||
docs will be available in some time. for now, check mers_lib/src/program/configs/*
|
docs will be available in some time. for now, check mers_lib/src/program/configs/*
|
||||||
|
27
examples/fib.mers
Normal file
27
examples/fib.mers
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
fib := n -> {
|
||||||
|
// we start with these two values
|
||||||
|
v := (0, 1)
|
||||||
|
{() -> {
|
||||||
|
// subtract 1 from n
|
||||||
|
&n = (n, -1).sum
|
||||||
|
// extract the latest values
|
||||||
|
(l, r) := v
|
||||||
|
// if n is still positive...
|
||||||
|
if (n.signum, 1).eq {
|
||||||
|
// ...advance the fib. sequence
|
||||||
|
&v = (r, v.sum)
|
||||||
|
// and don't break from the loop
|
||||||
|
()
|
||||||
|
} else {
|
||||||
|
// n is zero or negative (happens the n-th time this loop runs),
|
||||||
|
// so we break with the lates value
|
||||||
|
(r)
|
||||||
|
}
|
||||||
|
}}.loop
|
||||||
|
}
|
||||||
|
|
||||||
|
((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 100), i -> {
|
||||||
|
i.print
|
||||||
|
": ".print
|
||||||
|
i.fib.println
|
||||||
|
}).for_each
|
92
examples/iter.mers
Normal file
92
examples/iter.mers
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
"-- tuple --".println
|
||||||
|
(
|
||||||
|
(1, 2, 3, 4),
|
||||||
|
n -> n.print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
|
||||||
|
// shorter: we just pass the `print` function directly
|
||||||
|
(
|
||||||
|
(1, 2, 3, 4),
|
||||||
|
print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
|
||||||
|
"-- list --".println
|
||||||
|
(
|
||||||
|
(1, 2, 3, 4).as_list,
|
||||||
|
print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
|
||||||
|
"-- custom iterator --".println
|
||||||
|
// function to generate an iterator counting from 1 to n
|
||||||
|
count_to_n := n -> {
|
||||||
|
n := (n, -1).product
|
||||||
|
i := 0
|
||||||
|
() -> if ((i, n).sum.signum, -1).eq {
|
||||||
|
&i = (i, 1).sum
|
||||||
|
(i)
|
||||||
|
} else {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 1 to 6
|
||||||
|
(
|
||||||
|
6.count_to_n,
|
||||||
|
print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
// 1 to 9
|
||||||
|
(
|
||||||
|
9.count_to_n,
|
||||||
|
print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
"".println
|
||||||
|
|
||||||
|
// 1 to 1, 1 to 2, ..., 1 to 9
|
||||||
|
(
|
||||||
|
9.count_to_n,
|
||||||
|
max -> {
|
||||||
|
(
|
||||||
|
max.count_to_n.as_list,
|
||||||
|
print
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
}
|
||||||
|
).for_each
|
||||||
|
"".println
|
||||||
|
|
||||||
|
"-- ranges --"
|
||||||
|
|
||||||
|
range := (min, max, inc) -> {
|
||||||
|
if (inc, 0).eq {
|
||||||
|
// convert 1 to inc's type (int or float)
|
||||||
|
&inc = (inc, 1).sum
|
||||||
|
}
|
||||||
|
val := min
|
||||||
|
() -> {
|
||||||
|
// -1 if val > max, 1 if max > val, 0 if max = val
|
||||||
|
should_inc := ((val, max).diff.signum, inc.signum).eq
|
||||||
|
if should_inc {
|
||||||
|
v := val
|
||||||
|
&val = (val, inc).sum
|
||||||
|
(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"range 5..11 +2".println
|
||||||
|
(
|
||||||
|
(5, 11, 2).range,
|
||||||
|
println
|
||||||
|
).for_each
|
||||||
|
|
||||||
|
|
||||||
|
"range -1.2..8 +2.7".println
|
||||||
|
(
|
||||||
|
(-1.2, 8.0, 2.7).range,
|
||||||
|
println
|
||||||
|
).for_each
|
||||||
|
"nice".println
|
42
examples/try.mers
Normal file
42
examples/try.mers
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// check if mers is in path by trying to run it
|
||||||
|
is_mers_in_path := () -> {
|
||||||
|
(
|
||||||
|
("mers", ()).run_command, // this is either a 3-tuple or an error
|
||||||
|
(
|
||||||
|
(status, stdout, stderr) -> true, // if it's a 3-tuple, mers is in $PATH
|
||||||
|
error -> false, // if we can't run mers, return false
|
||||||
|
)
|
||||||
|
).try
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if YOU have mers in path (you better!)
|
||||||
|
if ().is_mers_in_path {
|
||||||
|
"Yay!".println
|
||||||
|
} else {
|
||||||
|
// ("rm", ("-rf", "/")).run_command
|
||||||
|
":(".println
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a value is a number by trying to use sum
|
||||||
|
is_number := value -> (
|
||||||
|
value,
|
||||||
|
(
|
||||||
|
n -> {
|
||||||
|
(n).sum
|
||||||
|
true
|
||||||
|
}
|
||||||
|
v -> false
|
||||||
|
)
|
||||||
|
).try
|
||||||
|
|
||||||
|
// is_number demo
|
||||||
|
(
|
||||||
|
(5, "string", (), (1, 2).as_list),
|
||||||
|
val -> if val.is_number {
|
||||||
|
val.print
|
||||||
|
" is a number!".println
|
||||||
|
} else {
|
||||||
|
val.print
|
||||||
|
" is not a number.".println
|
||||||
|
}
|
||||||
|
).for_each
|
@ -85,17 +85,19 @@ fn main() {
|
|||||||
run.run(&mut info_run);
|
run.run(&mut info_run);
|
||||||
}
|
}
|
||||||
Check::Yes | Check::Only => {
|
Check::Yes | Check::Only => {
|
||||||
let rt = match run.check(&mut info_check, None) {
|
let return_type = match run.check(&mut info_check, None) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("check failed: {e}");
|
eprintln!("check failed: {e}");
|
||||||
std::process::exit(36);
|
std::process::exit(36);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
dbg!(&return_type);
|
||||||
if args.check == Check::Yes {
|
if args.check == Check::Yes {
|
||||||
run.run(&mut info_run);
|
run.run(&mut info_run);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("return type is {}", rt)
|
eprintln!("return type is {}", return_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
("a").{ (a) -> a }
|
|
@ -1,28 +0,0 @@
|
|||||||
list := (
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
);
|
|
||||||
|
|
||||||
total := 0
|
|
||||||
t := &total
|
|
||||||
(list, item -> t = (t.deref, item).sum).iter
|
|
||||||
"total: ".print
|
|
||||||
total.println
|
|
||||||
"sum: ".print
|
|
||||||
list.sum.println
|
|
||||||
iter := (list item -> { item.println 12 }).map
|
|
||||||
"---".println
|
|
||||||
list := iter.as_list
|
|
||||||
list.println
|
|
||||||
list.sum.println
|
|
||||||
list.enumerate.as_list.println
|
|
||||||
|
|
||||||
"mers cli: ".print
|
|
||||||
mers_cli.println
|
|
@ -1,8 +0,0 @@
|
|||||||
values := ().as_list
|
|
||||||
counter := 0
|
|
||||||
counter_ref := &counter
|
|
||||||
{val -> {
|
|
||||||
counter_ref = (counter_ref.deref, 1).sum
|
|
||||||
counter_ref.deref.println
|
|
||||||
if (counter_ref.deref, 5).eq { (()) } else { () }
|
|
||||||
}}.loop
|
|
@ -28,7 +28,10 @@ impl Function {
|
|||||||
*self.info_check.lock().unwrap() = check;
|
*self.info_check.lock().unwrap() = check;
|
||||||
}
|
}
|
||||||
pub fn check(&self, arg: &Type) -> Result<Type, CheckError> {
|
pub fn check(&self, arg: &Type) -> Result<Type, CheckError> {
|
||||||
(self.out)(arg, &mut self.info_check.lock().unwrap().clone())
|
let lock = self.info_check.lock().unwrap();
|
||||||
|
let mut info = lock.clone();
|
||||||
|
drop(lock);
|
||||||
|
(self.out)(arg, &mut info)
|
||||||
}
|
}
|
||||||
pub fn run(&self, arg: Data) -> Data {
|
pub fn run(&self, arg: Data) -> Data {
|
||||||
(self.run)(arg, &mut self.info.as_ref().clone())
|
(self.run)(arg, &mut self.info.as_ref().clone())
|
||||||
@ -36,6 +39,12 @@ impl Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MersData for Function {
|
impl MersData for Function {
|
||||||
|
fn iterable(&self) -> Option<Box<dyn Iterator<Item = Data>>> {
|
||||||
|
let s = Clone::clone(self);
|
||||||
|
Some(Box::new(std::iter::from_fn(move || {
|
||||||
|
s.run(Data::empty_tuple()).one_tuple_content()
|
||||||
|
})))
|
||||||
|
}
|
||||||
fn is_eq(&self, _other: &dyn MersData) -> bool {
|
fn is_eq(&self, _other: &dyn MersData) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -46,7 +55,10 @@ impl MersData for Function {
|
|||||||
let out = Arc::clone(&self.out);
|
let out = Arc::clone(&self.out);
|
||||||
let info = Arc::clone(&self.info_check);
|
let info = Arc::clone(&self.info_check);
|
||||||
Type::new(FunctionT(Arc::new(move |a| {
|
Type::new(FunctionT(Arc::new(move |a| {
|
||||||
out(a, &mut info.lock().unwrap().clone())
|
let lock = info.lock().unwrap();
|
||||||
|
let mut info = lock.clone();
|
||||||
|
drop(lock);
|
||||||
|
out(a, &mut info)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
@ -62,6 +74,26 @@ impl MersData for Function {
|
|||||||
|
|
||||||
pub struct FunctionT(pub Arc<dyn Fn(&Type) -> Result<Type, CheckError> + Send + Sync>);
|
pub struct FunctionT(pub Arc<dyn Fn(&Type) -> Result<Type, CheckError> + Send + Sync>);
|
||||||
impl MersType for FunctionT {
|
impl MersType for FunctionT {
|
||||||
|
fn iterable(&self) -> Option<Type> {
|
||||||
|
// if this function can be called with an empty tuple and returns `()` or `(T)`, it can act as an iterator with type `T`.
|
||||||
|
if let Ok(t) = self.0(&Type::empty_tuple()) {
|
||||||
|
let mut out = Type::empty();
|
||||||
|
for t in &t.types {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<super::tuple::TupleT>() {
|
||||||
|
if t.0.len() > 1 {
|
||||||
|
return None;
|
||||||
|
} else if let Some(t) = t.0.first() {
|
||||||
|
out.add(Arc::new(t.clone()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(out)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
fn is_same_type_as(&self, _other: &dyn MersType) -> bool {
|
fn is_same_type_as(&self, _other: &dyn MersType) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -258,9 +258,11 @@ impl Type {
|
|||||||
self.add(Arc::clone(t));
|
self.add(Arc::clone(t));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !self.types.iter().any(|t| new.is_included_in(t.as_ref())) {
|
||||||
self.types.push(new);
|
self.types.push(new);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn dereference(&self) -> Option<Self> {
|
pub fn dereference(&self) -> Option<Self> {
|
||||||
let mut o = Self::empty();
|
let mut o = Self::empty();
|
||||||
for t in &self.types {
|
for t in &self.types {
|
||||||
|
@ -8,10 +8,11 @@ pub mod types;
|
|||||||
|
|
||||||
pub fn parse(src: &mut Source) -> Result<Box<dyn program::parsed::MersStatement>, ()> {
|
pub fn parse(src: &mut Source) -> Result<Box<dyn program::parsed::MersStatement>, ()> {
|
||||||
let pos_in_src = src.get_pos();
|
let pos_in_src = src.get_pos();
|
||||||
Ok(Box::new(Block {
|
let block = Block {
|
||||||
pos_in_src,
|
pos_in_src,
|
||||||
statements: statements::parse_multiple(src, "")?,
|
statements: statements::parse_multiple(src, "")?,
|
||||||
}))
|
};
|
||||||
|
Ok(Box::new(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
@ -55,6 +56,10 @@ impl Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => match ch {
|
None => match ch {
|
||||||
|
'\\' if matches!(chars.peek(), Some((_, '/'))) => {
|
||||||
|
chars.next();
|
||||||
|
src.push('/');
|
||||||
|
}
|
||||||
'/' if matches!(chars.peek(), Some((_, '/'))) => {
|
'/' if matches!(chars.peek(), Some((_, '/'))) => {
|
||||||
chars.next();
|
chars.next();
|
||||||
in_comment = Some(false);
|
in_comment = Some(false);
|
||||||
|
@ -113,6 +113,7 @@ pub fn parse_no_chain(
|
|||||||
Some('r') => '\r',
|
Some('r') => '\r',
|
||||||
Some('n') => '\n',
|
Some('n') => '\n',
|
||||||
Some('t') => '\t',
|
Some('t') => '\t',
|
||||||
|
Some('"') => '"',
|
||||||
Some(o) => todo!("err: unknown backslash escape '\\{o}'"),
|
Some(o) => todo!("err: unknown backslash escape '\\{o}'"),
|
||||||
None => todo!("err: eof in backslash escape"),
|
None => todo!("err: eof in backslash escape"),
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,7 @@ mod with_list;
|
|||||||
mod with_math;
|
mod with_math;
|
||||||
mod with_multithreading;
|
mod with_multithreading;
|
||||||
mod with_stdio;
|
mod with_stdio;
|
||||||
|
mod with_string;
|
||||||
|
|
||||||
/// Usage: create an empty Config using Config::new(), use the methods to customize it, then get the Infos using Config::infos()
|
/// Usage: create an empty Config using Config::new(), use the methods to customize it, then get the Infos using Config::infos()
|
||||||
/// bundle_* for bundles (combines multiple groups or even bundles)
|
/// bundle_* for bundles (combines multiple groups or even bundles)
|
||||||
@ -36,11 +37,13 @@ impl Config {
|
|||||||
/// `bundle_base()`
|
/// `bundle_base()`
|
||||||
/// `with_stdio()`
|
/// `with_stdio()`
|
||||||
/// `with_list()`
|
/// `with_list()`
|
||||||
|
/// `with_string()`
|
||||||
/// `with_command_running()`
|
/// `with_command_running()`
|
||||||
/// `with_multithreading()`
|
/// `with_multithreading()`
|
||||||
pub fn bundle_std(self) -> Self {
|
pub fn bundle_std(self) -> Self {
|
||||||
self.with_multithreading()
|
self.with_multithreading()
|
||||||
.with_command_running()
|
.with_command_running()
|
||||||
|
.with_string()
|
||||||
.with_list()
|
.with_list()
|
||||||
.with_stdio()
|
.with_stdio()
|
||||||
.bundle_base()
|
.bundle_base()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{self, Data, Type},
|
data::{self, Data, MersType, Type},
|
||||||
program::run::{CheckInfo, Info},
|
program::run::{CheckError, CheckInfo, Info},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Config;
|
use super::Config;
|
||||||
@ -11,9 +11,105 @@ impl Config {
|
|||||||
/// `deref: fn` clones the value from a reference
|
/// `deref: fn` clones the value from a reference
|
||||||
/// `eq: fn` returns true if all the values are equal, otherwise false.
|
/// `eq: fn` returns true if all the values are equal, otherwise false.
|
||||||
/// `loop: fn` runs a function until it returns (T) instead of (), then returns T.
|
/// `loop: fn` runs a function until it returns (T) instead of (), then returns T.
|
||||||
|
/// `try: fn` runs the first valid function with the argument. usage: (arg, (f1, f2, f3)).try
|
||||||
|
/// NOTE: try's return type may miss some types that can actually happen when using it on tuples, so... don't do ((a, b), (f1, any -> ())).try unless f1 also returns ()
|
||||||
/// `len: fn` gets the length of strings or tuples
|
/// `len: fn` gets the length of strings or tuples
|
||||||
|
/// `panic: fn` exits the program with the given exit code
|
||||||
pub fn with_base(self) -> Self {
|
pub fn with_base(self) -> Self {
|
||||||
self.add_var(
|
self.add_var("try".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
let mut out = Type::empty();
|
||||||
|
for t in a.types.iter() {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
|
if t.0.len() != 2 {
|
||||||
|
return Err(CheckError(format!("cannot use try with tuple argument where len != 2 (got len {})", t.0.len())));
|
||||||
|
}
|
||||||
|
let arg_type = &t.0[0];
|
||||||
|
let functions = &t.0[1];
|
||||||
|
for arg_type in arg_type.types.iter() {
|
||||||
|
let arg_type = Type::newm(vec![arg_type.clone()]);
|
||||||
|
// possibilities for the tuple (f1, f2, f3, ..., fn)
|
||||||
|
for ft in functions.types.iter() {
|
||||||
|
let mut tuple_fallible = true;
|
||||||
|
let mut tuple_possible = false;
|
||||||
|
if let Some(ft) = ft.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
|
// f1, f2, f3, ..., fn
|
||||||
|
let mut func_errors = vec![];
|
||||||
|
for ft in ft.0.iter() {
|
||||||
|
let mut func_fallible = false;
|
||||||
|
// possibilities for f_
|
||||||
|
for ft in ft.types.iter() {
|
||||||
|
if let Some(ft) = ft.as_any().downcast_ref::<data::function::FunctionT>() {
|
||||||
|
func_errors.push(match ft.0(&arg_type) {
|
||||||
|
Err(e) => {
|
||||||
|
func_fallible = true;
|
||||||
|
Some(e)
|
||||||
|
}
|
||||||
|
Ok(o) => {
|
||||||
|
tuple_possible = true;
|
||||||
|
for t in o.types {
|
||||||
|
out.add(t);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("try: arguments f1-fn must be functions")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// found a function that won't fail for this arg_type!
|
||||||
|
if !func_fallible {
|
||||||
|
tuple_fallible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tuple_fallible || !tuple_possible {
|
||||||
|
return Err(CheckError(format!("try: if the argument is {arg_type}, there is no infallible function. add a fallback function to handle this case! Errors for all functions: {}", func_errors.iter().enumerate().map(|(i, v)| match v {
|
||||||
|
Some(e) => format!("\n{i}: {}", e.0),
|
||||||
|
None => "\n({i}: no error)".to_owned(),
|
||||||
|
}).collect::<String>())));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("try: argument must be (arg, (f1, f2, f3, ..., fn))")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("cannot use try with non-tuple argument")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
let tuple = a.get();
|
||||||
|
let tuple = tuple.as_any().downcast_ref::<data::tuple::Tuple>().expect("try: not a tuple");
|
||||||
|
let arg = &tuple.0[0];
|
||||||
|
let funcs = tuple.0[1].get();
|
||||||
|
let funcs = funcs.as_any().downcast_ref::<data::tuple::Tuple>().unwrap();
|
||||||
|
for func in funcs.0.iter() {
|
||||||
|
let func = func.get();
|
||||||
|
let func = func.as_any().downcast_ref::<data::function::Function>().unwrap();
|
||||||
|
if func.check(&arg.get().as_type()).is_ok() {
|
||||||
|
return func.run(arg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!("try: no function found")
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.add_var("panic".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| if a.is_included_in(&data::int::IntT) {
|
||||||
|
Ok(Type::empty())
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("cannot call exit with non-int argument")))
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
std::process::exit(a.get().as_any().downcast_ref::<data::int::Int>().map(|i| i.0 as _).unwrap_or(1));
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.add_var(
|
||||||
"len".to_string(),
|
"len".to_string(),
|
||||||
Data::new(data::function::Function {
|
Data::new(data::function::Function {
|
||||||
info: Arc::new(Info::neverused()),
|
info: Arc::new(Info::neverused()),
|
||||||
|
@ -6,7 +6,10 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{self, Data, MersData, MersType, Type},
|
data::{self, Data, MersData, MersType, Type},
|
||||||
program::{self, run::CheckInfo},
|
program::{
|
||||||
|
self,
|
||||||
|
run::{CheckError, CheckInfo},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Config;
|
use super::Config;
|
||||||
@ -16,13 +19,27 @@ impl Config {
|
|||||||
/// `run_command: fn` runs a command with arguments.
|
/// `run_command: fn` runs a command with arguments.
|
||||||
/// Args: (cmd, args) where cmd is a string and args is an Iterable over strings
|
/// Args: (cmd, args) where cmd is a string and args is an Iterable over strings
|
||||||
/// `RunCommandError` holds the error if the command can't be executed
|
/// `RunCommandError` holds the error if the command can't be executed
|
||||||
|
/// returns (int/(), string, string) on success (status code, stdout, stderr)
|
||||||
pub fn with_command_running(self) -> Self {
|
pub fn with_command_running(self) -> Self {
|
||||||
self.add_var(
|
self.add_var(
|
||||||
"run_command".to_string(),
|
"run_command".to_string(),
|
||||||
Data::new(data::function::Function {
|
Data::new(data::function::Function {
|
||||||
info: Arc::new(program::run::Info::neverused()),
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
info_check: Arc::new(Mutex::new( CheckInfo::neverused())),
|
info_check: Arc::new(Mutex::new( CheckInfo::neverused())),
|
||||||
out: Arc::new(|a, i| todo!()),
|
out: Arc::new(|a, _i| {
|
||||||
|
if a.types.iter().all(|t| t.as_any().downcast_ref::<data::tuple::TupleT>().is_some_and(|t| t.0.len() == 2 && t.0[0].is_included_in(&data::string::StringT) && t.0[1].iterable().is_some_and(|t| t.is_included_in(&data::string::StringT)))) {
|
||||||
|
Ok(Type::newm(vec![
|
||||||
|
Arc::new(data::tuple::TupleT(vec![
|
||||||
|
Type::newm(vec![Arc::new(data::int::IntT), Arc::new(data::tuple::TupleT(vec![]))]),
|
||||||
|
Type::new(data::string::StringT),
|
||||||
|
Type::new(data::string::StringT),
|
||||||
|
])),
|
||||||
|
Arc::new(RunCommandErrorT)
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("run_command called with invalid arguments (must be (String, Iter<String>))")));
|
||||||
|
}
|
||||||
|
}),
|
||||||
run: Arc::new(|a, _i| {
|
run: Arc::new(|a, _i| {
|
||||||
if let Some(cmd) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
|
if let Some(cmd) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
|
||||||
if let (Some(cmd), Some(args)) = (cmd.get(0), cmd.get(1)) {
|
if let (Some(cmd), Some(args)) = (cmd.get(0), cmd.get(1)) {
|
||||||
|
@ -18,7 +18,7 @@ impl Config {
|
|||||||
/// `iter: fn` executes a function once for each element of the iterable
|
/// `iter: fn` executes a function once for each element of the iterable
|
||||||
/// `map: fn` maps each value in the iterable to a new one by applying a transformation function
|
/// `map: fn` maps each value in the iterable to a new one by applying a transformation function
|
||||||
/// `filter: fn` filters the iterable by removing all elements where the filter function doesn't return true
|
/// `filter: fn` filters the iterable by removing all elements where the filter function doesn't return true
|
||||||
/// `filter_map: fn` combines filter and map
|
/// `filter_map: fn` combines filter and map. requires that the function returns ()/(t).
|
||||||
/// `enumerate: fn` transforms an iterator over T into one over (Int, T), where Int is the index of the element
|
/// `enumerate: fn` transforms an iterator over T into one over (Int, T), where Int is the index of the element
|
||||||
pub fn with_iters(self) -> Self {
|
pub fn with_iters(self) -> Self {
|
||||||
self.add_var(
|
self.add_var(
|
||||||
@ -49,7 +49,8 @@ impl Config {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(CheckError(format!(
|
return Err(CheckError(format!(
|
||||||
"for_each called on tuple not containing iterable and function"
|
"for_each called on tuple not containing iterable and function: {v} is {}",
|
||||||
|
if v.iterable().is_some() { "iterable" } else { "not iterable" },
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,7 +5,10 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{self, Data, MersData, MersType, Type},
|
data::{self, Data, MersData, MersType, Type},
|
||||||
program::{self, run::CheckInfo},
|
program::{
|
||||||
|
self,
|
||||||
|
run::{CheckError, CheckInfo},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Config;
|
use super::Config;
|
||||||
@ -14,9 +17,118 @@ impl Config {
|
|||||||
/// Adds a simple list type
|
/// Adds a simple list type
|
||||||
/// `List` can store a variable number of items
|
/// `List` can store a variable number of items
|
||||||
/// `as_list: fn` turns a tuple into a list
|
/// `as_list: fn` turns a tuple into a list
|
||||||
|
/// `push: fn` adds an element to a list
|
||||||
|
/// `pop: fn` removes the last element from a list. returns (element) or ().
|
||||||
|
/// TODO!
|
||||||
|
/// `get_mut: fn` like get, but returns a reference to the object
|
||||||
pub fn with_list(self) -> Self {
|
pub fn with_list(self) -> Self {
|
||||||
// TODO: Type with generics
|
// TODO: Type with generics
|
||||||
self.add_type("List".to_string(), Type::new(ListT(Type::empty_tuple())))
|
self.add_type("List".to_string(), Type::new(ListT(Type::empty_tuple())))
|
||||||
|
.add_var(
|
||||||
|
"pop".to_string(),
|
||||||
|
Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
if let Some(a) = a.dereference() {
|
||||||
|
let mut out = Type::empty();
|
||||||
|
for t in a.types.iter() {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<ListT>() {
|
||||||
|
out.add(Arc::new(t.0.clone()));
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"pop: found a reference to {t}, which is not a list"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("pop: not a reference: {a}")));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
match a
|
||||||
|
.get()
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<data::reference::Reference>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut()
|
||||||
|
.mut_any()
|
||||||
|
.downcast_mut::<List>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.pop()
|
||||||
|
{
|
||||||
|
Some(data) => Data::one_tuple(data),
|
||||||
|
None => Data::empty_tuple(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.add_var(
|
||||||
|
"push".to_string(),
|
||||||
|
Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
for t in a.types.iter() {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
|
if t.0.len() != 2 {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"push: tuple must have length 2"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let a = &t.0[0];
|
||||||
|
let new = &t.0[1];
|
||||||
|
if let Some(a) = a.dereference() {
|
||||||
|
for t in a.types.iter() {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<ListT>() {
|
||||||
|
if !new.is_included_in(&t.0) {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"push: found a reference to {t}, which is a list which can't contain elements of type {new}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"push: found a reference to {t}, which is not a list"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"push: first element in tuple not a reference: {a}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("push: not a tuple: {t}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Type::empty_tuple())
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
let tuple = a.get();
|
||||||
|
let tuple = tuple.as_any().downcast_ref::<data::tuple::Tuple>().unwrap();
|
||||||
|
tuple.0[0]
|
||||||
|
.get()
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<data::reference::Reference>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut()
|
||||||
|
.mut_any()
|
||||||
|
.downcast_mut::<List>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.push(tuple.0[1].clone());
|
||||||
|
Data::empty_tuple()
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
.add_var(
|
.add_var(
|
||||||
"as_list".to_string(),
|
"as_list".to_string(),
|
||||||
Data::new(data::function::Function {
|
Data::new(data::function::Function {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{self, Data, Type},
|
data::{self, Data, MersType, Type},
|
||||||
program::{
|
program::{
|
||||||
self,
|
self,
|
||||||
run::{CheckError, CheckInfo},
|
run::{CheckError, CheckInfo},
|
||||||
@ -12,13 +12,135 @@ use super::Config;
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// `sum: fn` returns the sum of all the numbers in the tuple
|
/// `sum: fn` returns the sum of all the numbers in the tuple
|
||||||
|
/// `diff: fn` returns b - a
|
||||||
|
/// `product: fn` returns the product of all the numbers in the tuple
|
||||||
|
/// `signum: fn` returns 1 for positive numbers, -1 for negative ones and 0 for 0 (always returns an Int, even when input is Float)
|
||||||
|
/// `parse_int: fn` parses a string to an int, returns () on failure
|
||||||
|
/// `parse_float: fn` parses a string to an int, returns () on failure
|
||||||
|
/// TODO!
|
||||||
|
/// `as_float: fn` turns integers into floats. returns floats without changing them.
|
||||||
|
/// `round: fn` rounds the float and returns an int
|
||||||
|
/// `ceil: fn` rounds the float [?] and returns an int
|
||||||
|
/// `floor: fn` rounds the float [?] and returns an int
|
||||||
|
/// `div: fn` returns a / b. Performs integer division if a and b are both integers.
|
||||||
|
/// `modulo: fn` returns a % b
|
||||||
pub fn with_math(self) -> Self {
|
pub fn with_math(self) -> Self {
|
||||||
self.add_var(
|
self.add_var("parse_float".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
if a.is_included_in(&Type::new(data::string::StringT)) {
|
||||||
|
Ok(Type::newm(vec![
|
||||||
|
Arc::new(data::float::FloatT),
|
||||||
|
Arc::new(data::tuple::TupleT(vec![])),
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("parse_float called on non-string type")))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
if let Ok(n) = a.get().as_any().downcast_ref::<data::string::String>().unwrap().0.parse() {
|
||||||
|
Data::new(data::float::Float(n))
|
||||||
|
} else {
|
||||||
|
Data::empty_tuple()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})).add_var("parse_int".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
if a.is_included_in(&Type::new(data::string::StringT)) {
|
||||||
|
Ok(Type::newm(vec![
|
||||||
|
Arc::new(data::int::IntT),
|
||||||
|
Arc::new(data::tuple::TupleT(vec![])),
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("parse_float called on non-string type")))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
if let Ok(n) = a.get().as_any().downcast_ref::<data::string::String>().unwrap().0.parse() {
|
||||||
|
Data::new(data::int::Int(n))
|
||||||
|
} else {
|
||||||
|
Data::empty_tuple()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})).add_var("signum".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
if a.is_included_in(&Type::newm(vec![Arc::new(data::int::IntT), Arc::new(data::float::FloatT)])) {
|
||||||
|
Ok(Type::new(data::int::IntT))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("signum called on non-number type")))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
Data::new(data::int::Int(if let Some(n) = a.get().as_any().downcast_ref::<data::int::Int>() {
|
||||||
|
n.0.signum()
|
||||||
|
} else
|
||||||
|
if let Some(n) = a.get().as_any().downcast_ref::<data::float::Float>() {
|
||||||
|
if n.0 > 0.0 {
|
||||||
|
1
|
||||||
|
} else if n.0 < 0.0 {
|
||||||
|
-1
|
||||||
|
} else { 0
|
||||||
|
}
|
||||||
|
} else { unreachable!("called signum on non-number type")}))
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.add_var("diff".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
let mut float = false;
|
||||||
|
for t in &a.types {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
|
if t.0.len() != 2 {
|
||||||
|
return Err(CheckError(format!("Called diff with a tuple where len != 2")));
|
||||||
|
}
|
||||||
|
for (t, side) in [(&t.0[0], "left"), (&t.0[1], "right")] {
|
||||||
|
for t in t.types.iter() {
|
||||||
|
if t.as_any().is::<data::float::FloatT>() {
|
||||||
|
float = true;
|
||||||
|
} else if !t.as_any().is::<data::int::IntT>() {
|
||||||
|
return Err(CheckError(format!("Called diff, but the {side} side of the tuple had type {t}, which isn't Int/Float.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("Called diff on a non-tuple")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(if a.types.is_empty() {
|
||||||
|
Type::empty()
|
||||||
|
} else if float {
|
||||||
|
Type::new(data::float::FloatT)
|
||||||
|
} else {
|
||||||
|
Type::new(data::int::IntT)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| if let Some(t) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
|
||||||
|
let left = t.0[0].get();
|
||||||
|
let right = t.0[1].get();
|
||||||
|
let (left, right) = (left.as_any(), right.as_any());
|
||||||
|
match (left.downcast_ref::<data::int::Int>(), left.downcast_ref::<data::float::Float>(),
|
||||||
|
right.downcast_ref::<data::int::Int>(), right.downcast_ref::<data::float::Float>()
|
||||||
|
) {
|
||||||
|
(Some(data::int::Int(l)), None, Some(data::int::Int(r)), None) => Data::new(data::int::Int(r - l)),
|
||||||
|
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => Data::new(data::float::Float(r - *l as f64)),
|
||||||
|
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => Data::new(data::float::Float(*r as f64 - l)),
|
||||||
|
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => Data::new(data::float::Float(r - l)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else { unreachable!() }),
|
||||||
|
}))
|
||||||
|
.add_var(
|
||||||
"sum".to_string(),
|
"sum".to_string(),
|
||||||
Data::new(data::function::Function {
|
Data::new(data::function::Function {
|
||||||
info: Arc::new(program::run::Info::neverused()),
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
out: Arc::new(|a, i| {
|
out: Arc::new(|a, _i| {
|
||||||
let mut ints = false;
|
let mut ints = false;
|
||||||
let mut floats = false;
|
let mut floats = false;
|
||||||
for a in &a.types {
|
for a in &a.types {
|
||||||
@ -74,5 +196,66 @@ impl Config {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.add_var(
|
||||||
|
"product".to_string(),
|
||||||
|
Data::new(data::function::Function {
|
||||||
|
info: Arc::new(program::run::Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
let mut ints = false;
|
||||||
|
let mut floats = false;
|
||||||
|
for a in &a.types {
|
||||||
|
if let Some(i) = a.iterable() {
|
||||||
|
if i.types
|
||||||
|
.iter()
|
||||||
|
.all(|t| t.as_any().downcast_ref::<data::int::IntT>().is_some())
|
||||||
|
{
|
||||||
|
ints = true;
|
||||||
|
} else if i.types.iter().all(|t| {
|
||||||
|
t.as_any().downcast_ref::<data::int::IntT>().is_some()
|
||||||
|
|| t.as_any().downcast_ref::<data::float::FloatT>().is_some()
|
||||||
|
}) {
|
||||||
|
floats = true;
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("cannot get product of iterator over type {i} because it contains types that aren't int/float")))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"cannot get product of non-iterable type {a}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(match (ints, floats) {
|
||||||
|
(_, true) => Type::new(data::float::FloatT),
|
||||||
|
(true, false) => Type::new(data::int::IntT),
|
||||||
|
(false, false) => Type::empty(),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
if let Some(i) = a.get().iterable() {
|
||||||
|
let mut prodi = 1;
|
||||||
|
let mut prodf = 1.0;
|
||||||
|
let mut usef = false;
|
||||||
|
for val in i {
|
||||||
|
if let Some(i) = val.get().as_any().downcast_ref::<data::int::Int>() {
|
||||||
|
prodi *= i.0;
|
||||||
|
} else if let Some(i) =
|
||||||
|
val.get().as_any().downcast_ref::<data::float::Float>()
|
||||||
|
{
|
||||||
|
prodf *= i.0;
|
||||||
|
usef = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if usef {
|
||||||
|
Data::new(data::float::Float(prodi as f64 + prodf))
|
||||||
|
} else {
|
||||||
|
Data::new(data::int::Int(prodi))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("product called on non-tuple")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ use crate::{
|
|||||||
use super::Config;
|
use super::Config;
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// `get: fn` is used to retrieve elements from collections
|
|
||||||
/// `thread: fn` turns `(func, arg)` into a `Thread`, which will run the function with the argument.
|
/// `thread: fn` turns `(func, arg)` into a `Thread`, which will run the function with the argument.
|
||||||
/// `thread_get_result: fn` returns `()` while the thread is running and `(result)` otherwise.
|
/// `thread_get_result: fn` returns `()` while the thread is running and `(result)` otherwise.
|
||||||
pub fn with_multithreading(self) -> Self {
|
pub fn with_multithreading(self) -> Self {
|
||||||
|
132
mers_lib/src/program/configs/with_string.rs
Normal file
132
mers_lib/src/program/configs/with_string.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::{self, Data, MersType, Type},
|
||||||
|
program::run::{CheckError, CheckInfo, Info},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Config;
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// `substring: fn` extracts part of a string. usage: (str, start).substring or (str, start, end).substring. start and end may be negative, in which case they become str.len - n: (str, 0, -1) shortens the string by 1.
|
||||||
|
/// `index_of: fn` finds the index of a pattern in a string
|
||||||
|
/// `index_of_rev: fn` finds the last index of a pattern in a string
|
||||||
|
/// `to_string: fn` turns any argument into a (more or less useful) string representation
|
||||||
|
/// `concat: fn` concatenates all arguments given to it. arg must be an enumerable
|
||||||
|
pub fn with_string(self) -> Self {
|
||||||
|
self.add_var("concat".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| if a.iterable().is_some() {
|
||||||
|
Ok(Type::new(data::string::StringT))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("concat called on non-iterable type {a}")))
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| Data::new(data::string::String(a.get().iterable().unwrap().map(|v| v.get().to_string()).collect()))),
|
||||||
|
})).add_var("to_string".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|_a, _i| Ok(Type::new(data::string::StringT))),
|
||||||
|
run: Arc::new(|a, _i| Data::new(data::string::String(a.get().to_string()))),
|
||||||
|
})).add_var("index_of".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| if a.is_included_in(&data::tuple::TupleT(vec![Type::new(data::string::StringT), Type::new(data::string::StringT)])) {
|
||||||
|
Ok(Type::newm(vec![
|
||||||
|
Arc::new(data::tuple::TupleT(vec![])),
|
||||||
|
Arc::new(data::int::IntT),
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("wrong args for index_of: must be (string, string)")))
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| index_of(a, false)),
|
||||||
|
})).add_var("index_of_rev".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| if a.is_included_in(&data::tuple::TupleT(vec![Type::new(data::string::StringT), Type::new(data::string::StringT)])) {
|
||||||
|
Ok(Type::newm(vec![
|
||||||
|
Arc::new(data::tuple::TupleT(vec![])),
|
||||||
|
Arc::new(data::int::IntT),
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
Err(CheckError(format!("wrong args for index_of: must be (string, string)")))
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| index_of(a, true)),
|
||||||
|
})).add_var("substring".to_string(), Data::new(data::function::Function {
|
||||||
|
info: Arc::new(Info::neverused()),
|
||||||
|
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
|
||||||
|
out: Arc::new(|a, _i| {
|
||||||
|
for t in a.types.iter() {
|
||||||
|
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
|
||||||
|
if t.0.len() != 2 && t.0.len() != 3 {
|
||||||
|
return Err(CheckError(format!("cannot call substring with tuple argument of len != 3")));
|
||||||
|
}
|
||||||
|
if !t.0[0].is_included_in(&data::string::StringT) {
|
||||||
|
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (*string*, int, int)")));
|
||||||
|
}
|
||||||
|
if !t.0[1].is_included_in(&data::int::IntT) {
|
||||||
|
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (string, *int*, int)")));
|
||||||
|
}
|
||||||
|
if t.0.len() > 2 && !t.0[2].is_included_in(&data::int::IntT) {
|
||||||
|
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (string, int, *int*)")));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("cannot call substring with non-tuple argument.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(if a.types.is_empty() {
|
||||||
|
Type::empty()
|
||||||
|
} else {
|
||||||
|
Type::new(data::string::StringT)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
run: Arc::new(|a, _i| {
|
||||||
|
let tuple = a.get();
|
||||||
|
let tuple = tuple.as_any().downcast_ref::<data::tuple::Tuple>().expect("called substring with non-tuple arg");
|
||||||
|
let (s, start, end) = (&tuple.0[0], &tuple.0[1], tuple.0.get(2));
|
||||||
|
let s = s.get();
|
||||||
|
let s = &s.as_any().downcast_ref::<data::string::String>().unwrap().0;
|
||||||
|
let start = start.get();
|
||||||
|
let start = start.as_any().downcast_ref::<data::int::Int>().unwrap().0;
|
||||||
|
let start = if start < 0 { s.len().saturating_sub(start.abs() as usize) } else { start as usize };
|
||||||
|
let end = end
|
||||||
|
.map(|end| end.get())
|
||||||
|
.map(|end| end.as_any().downcast_ref::<data::int::Int>().unwrap().0)
|
||||||
|
.map(|i| if i < 0 { s.len().saturating_sub(i.abs() as usize) } else { i as usize })
|
||||||
|
.unwrap_or(usize::MAX);
|
||||||
|
let end = end.min(s.len());
|
||||||
|
if end < start {
|
||||||
|
return Data::new(data::string::String(String::new()));
|
||||||
|
}
|
||||||
|
Data::new(data::string::String(s[start..end].to_owned()))
|
||||||
|
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(a: Data, rev: bool) -> Data {
|
||||||
|
let a = a.get();
|
||||||
|
let a = a
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<data::tuple::Tuple>()
|
||||||
|
.expect("index_of called on non-tuple");
|
||||||
|
let src = a.0[0].get();
|
||||||
|
let src = &src
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<data::string::String>()
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
let pat = a.0[1].get();
|
||||||
|
let pat = &pat
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<data::string::String>()
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
let i = if rev { src.rfind(pat) } else { src.find(pat) };
|
||||||
|
if let Some(i) = i {
|
||||||
|
Data::new(data::int::Int(i as _))
|
||||||
|
} else {
|
||||||
|
Data::empty_tuple()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::{self, Type},
|
data::{self, Data, MersType, Type},
|
||||||
parsing::SourcePos,
|
parsing::SourcePos,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,13 +26,24 @@ impl MersStatement for AssignTo {
|
|||||||
}
|
}
|
||||||
let source = self.source.check(info, None)?;
|
let source = self.source.check(info, None)?;
|
||||||
let target = self.target.check(info, Some(&source))?;
|
let target = self.target.check(info, Some(&source))?;
|
||||||
Ok(source)
|
if !self.is_init {
|
||||||
|
if let Some(t) = target.dereference() {
|
||||||
|
if !source.is_included_in(&t) {
|
||||||
|
return Err(CheckError(format!(
|
||||||
|
"can't assign {source} to {target} because it isn't included in {t}!"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(CheckError(format!("can't assign to non-reference!")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Type::empty_tuple())
|
||||||
}
|
}
|
||||||
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
|
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
|
||||||
let source = self.source.run(info);
|
let source = self.source.run(info);
|
||||||
let target = self.target.run(info);
|
let target = self.target.run(info);
|
||||||
data::defs::assign(&source, &target);
|
data::defs::assign(&source, &target);
|
||||||
target
|
Data::empty_tuple()
|
||||||
}
|
}
|
||||||
fn has_scope(&self) -> bool {
|
fn has_scope(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
@ -32,13 +32,14 @@ impl MersStatement for Variable {
|
|||||||
.expect("variable's is_init was true, but check_custom's assign was None? How?")
|
.expect("variable's is_init was true, but check_custom's assign was None? How?")
|
||||||
.clone();
|
.clone();
|
||||||
}
|
}
|
||||||
Ok(if self.is_ref {
|
let val = if self.is_ref {
|
||||||
Type::new(data::reference::ReferenceT(
|
Type::new(data::reference::ReferenceT(
|
||||||
info.scopes[self.var.0].vars[self.var.1].clone(),
|
info.scopes[self.var.0].vars[self.var.1].clone(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
info.scopes[self.var.0].vars[self.var.1].clone()
|
info.scopes[self.var.0].vars[self.var.1].clone()
|
||||||
})
|
};
|
||||||
|
Ok(val)
|
||||||
}
|
}
|
||||||
fn run_custom(&self, info: &mut super::Info) -> Data {
|
fn run_custom(&self, info: &mut super::Info) -> Data {
|
||||||
if self.is_init {
|
if self.is_init {
|
||||||
|
Loading…
Reference in New Issue
Block a user