This commit is contained in:
Mark 2023-10-19 18:46:15 +02:00
parent 2d79e75ba2
commit b39a768099
22 changed files with 822 additions and 94 deletions

View File

@ -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
View 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
View 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
View 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

View File

@ -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)
} }
} }
} }

View File

@ -1 +0,0 @@
("a").{ (a) -> a }

View File

@ -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

View File

@ -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

View File

@ -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
} }

View File

@ -258,7 +258,9 @@ impl Type {
self.add(Arc::clone(t)); self.add(Arc::clone(t));
} }
} else { } else {
self.types.push(new); if !self.types.iter().any(|t| new.is_included_in(t.as_ref())) {
self.types.push(new);
}
} }
} }
pub fn dereference(&self) -> Option<Self> { pub fn dereference(&self) -> Option<Self> {

View File

@ -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);

View File

@ -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"),
}); });

View File

@ -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()

View File

@ -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()),

View File

@ -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)) {

View File

@ -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 {

View File

@ -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 {

View File

@ -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")
}
}),
}),
)
} }
} }

View File

@ -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 {

View 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()
}
}

View File

@ -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

View File

@ -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 {