diff --git a/README.md b/README.md index 6029938..024ca57 100755 --- a/README.md +++ b/README.md @@ -7,49 +7,30 @@ It is designed to be safe (it doesn't crash at runtime) and as simple as possibl ### Simplicity -Mers aims to be very simple, as in, having very few "special" things. -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: +Mers is simple. There are only few expressions: -**Exceptions**, because errors in mers are just values. -A function to read a UTF-8 text file from disk could have a return type like `String/IOError/UTF8DecodeError`, -which tells you exactly what errors can happen and also forces you to handle them (see later for mers' type-system). +- Values (`1`, `"my string"`, ...) +- Blocks (`{}`) +- Tuples (`()`) +- Assignments (`=`) +- Variable initializations (`:=`) +- Variables (`my_var`, `&my_var`) +- If statements (`if [else ]`) +- Functions (`arg -> `) +- Function calls `arg.function` -**Loops**, because, as it turns out, a function which takes an iterable and a function can do this just fine: -``` -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 `()/`. +Everything else is implemented as a function: ### Types and Safety 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" } ``` -This would be valid code. -However, in mers, the compiler still tracks all the types in your program, +In mers, the compiler tracks all the types in your program, 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.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) +### 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 will be available in some time. for now, check mers_lib/src/program/configs/* diff --git a/examples/fib.mers b/examples/fib.mers new file mode 100644 index 0000000..1041198 --- /dev/null +++ b/examples/fib.mers @@ -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 diff --git a/examples/iter.mers b/examples/iter.mers new file mode 100644 index 0000000..4d7e3eb --- /dev/null +++ b/examples/iter.mers @@ -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 diff --git a/examples/try.mers b/examples/try.mers new file mode 100644 index 0000000..81e2c49 --- /dev/null +++ b/examples/try.mers @@ -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 diff --git a/mers/src/main.rs b/mers/src/main.rs index a62d3ea..ef4c46f 100755 --- a/mers/src/main.rs +++ b/mers/src/main.rs @@ -85,17 +85,19 @@ fn main() { run.run(&mut info_run); } 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, Err(e) => { eprintln!("check failed: {e}"); std::process::exit(36); } }; + #[cfg(debug_assertions)] + dbg!(&return_type); if args.check == Check::Yes { run.run(&mut info_run); } else { - eprintln!("return type is {}", rt) + eprintln!("return type is {}", return_type) } } } diff --git a/mers/test.mers b/mers/test.mers deleted file mode 100755 index 8aa1d62..0000000 --- a/mers/test.mers +++ /dev/null @@ -1 +0,0 @@ -("a").{ (a) -> a } diff --git a/mers/test2.mers b/mers/test2.mers deleted file mode 100755 index e554a66..0000000 --- a/mers/test2.mers +++ /dev/null @@ -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 diff --git a/mers/test3.mers b/mers/test3.mers deleted file mode 100755 index d153572..0000000 --- a/mers/test3.mers +++ /dev/null @@ -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 diff --git a/mers_lib/src/data/function.rs b/mers_lib/src/data/function.rs index df08911..6d1343c 100755 --- a/mers_lib/src/data/function.rs +++ b/mers_lib/src/data/function.rs @@ -28,7 +28,10 @@ impl Function { *self.info_check.lock().unwrap() = check; } pub fn check(&self, arg: &Type) -> Result { - (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 { (self.run)(arg, &mut self.info.as_ref().clone()) @@ -36,6 +39,12 @@ impl Function { } impl MersData for Function { + fn iterable(&self) -> Option>> { + 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 { false } @@ -46,7 +55,10 @@ impl MersData for Function { let out = Arc::clone(&self.out); let info = Arc::clone(&self.info_check); 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 { @@ -62,6 +74,26 @@ impl MersData for Function { pub struct FunctionT(pub Arc Result + Send + Sync>); impl MersType for FunctionT { + fn iterable(&self) -> Option { + // 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::() { + 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 { false } diff --git a/mers_lib/src/data/mod.rs b/mers_lib/src/data/mod.rs index 6b80568..1e3c14c 100755 --- a/mers_lib/src/data/mod.rs +++ b/mers_lib/src/data/mod.rs @@ -258,7 +258,9 @@ impl Type { self.add(Arc::clone(t)); } } 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 { diff --git a/mers_lib/src/parsing/mod.rs b/mers_lib/src/parsing/mod.rs index 623d474..703247e 100755 --- a/mers_lib/src/parsing/mod.rs +++ b/mers_lib/src/parsing/mod.rs @@ -8,10 +8,11 @@ pub mod types; pub fn parse(src: &mut Source) -> Result, ()> { let pos_in_src = src.get_pos(); - Ok(Box::new(Block { + let block = Block { pos_in_src, statements: statements::parse_multiple(src, "")?, - })) + }; + Ok(Box::new(block)) } pub struct Source { @@ -55,6 +56,10 @@ impl Source { } } None => match ch { + '\\' if matches!(chars.peek(), Some((_, '/'))) => { + chars.next(); + src.push('/'); + } '/' if matches!(chars.peek(), Some((_, '/'))) => { chars.next(); in_comment = Some(false); diff --git a/mers_lib/src/parsing/statements.rs b/mers_lib/src/parsing/statements.rs index e2443fc..4c98868 100755 --- a/mers_lib/src/parsing/statements.rs +++ b/mers_lib/src/parsing/statements.rs @@ -113,6 +113,7 @@ pub fn parse_no_chain( Some('r') => '\r', Some('n') => '\n', Some('t') => '\t', + Some('"') => '"', Some(o) => todo!("err: unknown backslash escape '\\{o}'"), None => todo!("err: eof in backslash escape"), }); diff --git a/mers_lib/src/program/configs/mod.rs b/mers_lib/src/program/configs/mod.rs index 00ebd47..a0b1f40 100755 --- a/mers_lib/src/program/configs/mod.rs +++ b/mers_lib/src/program/configs/mod.rs @@ -13,6 +13,7 @@ mod with_list; mod with_math; mod with_multithreading; 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() /// bundle_* for bundles (combines multiple groups or even bundles) @@ -36,11 +37,13 @@ impl Config { /// `bundle_base()` /// `with_stdio()` /// `with_list()` + /// `with_string()` /// `with_command_running()` /// `with_multithreading()` pub fn bundle_std(self) -> Self { self.with_multithreading() .with_command_running() + .with_string() .with_list() .with_stdio() .bundle_base() diff --git a/mers_lib/src/program/configs/with_base.rs b/mers_lib/src/program/configs/with_base.rs index a3b7fbb..f1e0e0f 100755 --- a/mers_lib/src/program/configs/with_base.rs +++ b/mers_lib/src/program/configs/with_base.rs @@ -1,8 +1,8 @@ use std::sync::{Arc, Mutex}; use crate::{ - data::{self, Data, Type}, - program::run::{CheckInfo, Info}, + data::{self, Data, MersType, Type}, + program::run::{CheckError, CheckInfo, Info}, }; use super::Config; @@ -11,9 +11,105 @@ impl Config { /// `deref: fn` clones the value from a reference /// `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. + /// `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 + /// `panic: fn` exits the program with the given exit code 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::() { + 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::() { + // 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::() { + 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::()))); + } + } 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::().expect("try: not a tuple"); + let arg = &tuple.0[0]; + let funcs = tuple.0[1].get(); + let funcs = funcs.as_any().downcast_ref::().unwrap(); + for func in funcs.0.iter() { + let func = func.get(); + let func = func.as_any().downcast_ref::().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::().map(|i| i.0 as _).unwrap_or(1)); + }) + })) + .add_var( "len".to_string(), Data::new(data::function::Function { info: Arc::new(Info::neverused()), diff --git a/mers_lib/src/program/configs/with_command_running.rs b/mers_lib/src/program/configs/with_command_running.rs index 574fe9b..d5555e3 100755 --- a/mers_lib/src/program/configs/with_command_running.rs +++ b/mers_lib/src/program/configs/with_command_running.rs @@ -6,7 +6,10 @@ use std::{ use crate::{ data::{self, Data, MersData, MersType, Type}, - program::{self, run::CheckInfo}, + program::{ + self, + run::{CheckError, CheckInfo}, + }, }; use super::Config; @@ -16,13 +19,27 @@ impl Config { /// `run_command: fn` runs a command with arguments. /// 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 + /// returns (int/(), string, string) on success (status code, stdout, stderr) pub fn with_command_running(self) -> Self { self.add_var( "run_command".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| todo!()), + out: Arc::new(|a, _i| { + if a.types.iter().all(|t| t.as_any().downcast_ref::().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))"))); + } + }), run: Arc::new(|a, _i| { if let Some(cmd) = a.get().as_any().downcast_ref::() { if let (Some(cmd), Some(args)) = (cmd.get(0), cmd.get(1)) { diff --git a/mers_lib/src/program/configs/with_iters.rs b/mers_lib/src/program/configs/with_iters.rs index 6ed6d98..1c2bf65 100755 --- a/mers_lib/src/program/configs/with_iters.rs +++ b/mers_lib/src/program/configs/with_iters.rs @@ -18,7 +18,7 @@ impl Config { /// `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 /// `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 pub fn with_iters(self) -> Self { self.add_var( @@ -49,7 +49,8 @@ impl Config { } } else { 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 { diff --git a/mers_lib/src/program/configs/with_list.rs b/mers_lib/src/program/configs/with_list.rs index 5e10fc7..155efa4 100755 --- a/mers_lib/src/program/configs/with_list.rs +++ b/mers_lib/src/program/configs/with_list.rs @@ -5,7 +5,10 @@ use std::{ use crate::{ data::{self, Data, MersData, MersType, Type}, - program::{self, run::CheckInfo}, + program::{ + self, + run::{CheckError, CheckInfo}, + }, }; use super::Config; @@ -14,9 +17,118 @@ impl Config { /// Adds a simple list type /// `List` can store a variable number of items /// `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 { // TODO: Type with generics 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::() { + 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::() + .unwrap() + .0 + .lock() + .unwrap() + .get_mut() + .mut_any() + .downcast_mut::() + .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::() { + 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::() { + 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::().unwrap(); + tuple.0[0] + .get() + .as_any() + .downcast_ref::() + .unwrap() + .0 + .lock() + .unwrap() + .get_mut() + .mut_any() + .downcast_mut::() + .unwrap() + .0 + .push(tuple.0[1].clone()); + Data::empty_tuple() + }), + }), + ) .add_var( "as_list".to_string(), Data::new(data::function::Function { diff --git a/mers_lib/src/program/configs/with_math.rs b/mers_lib/src/program/configs/with_math.rs index b66054d..d86fb33 100755 --- a/mers_lib/src/program/configs/with_math.rs +++ b/mers_lib/src/program/configs/with_math.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use crate::{ - data::{self, Data, Type}, + data::{self, Data, MersType, Type}, program::{ self, run::{CheckError, CheckInfo}, @@ -12,13 +12,135 @@ use super::Config; impl Config { /// `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 { - 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::().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::().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::() { + n.0.signum() + } else + if let Some(n) = a.get().as_any().downcast_ref::() { + 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::() { + 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::() { + float = true; + } else if !t.as_any().is::() { + 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::() { + 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::(), left.downcast_ref::(), + right.downcast_ref::(), right.downcast_ref::() + ) { + (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(), 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| { + out: Arc::new(|a, _i| { let mut ints = false; let mut floats = false; 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::().is_some()) + { + ints = true; + } else if i.types.iter().all(|t| { + t.as_any().downcast_ref::().is_some() + || t.as_any().downcast_ref::().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::() { + prodi *= i.0; + } else if let Some(i) = + val.get().as_any().downcast_ref::() + { + 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") + } + }), + }), + ) } } diff --git a/mers_lib/src/program/configs/with_multithreading.rs b/mers_lib/src/program/configs/with_multithreading.rs index cd0b223..584ecb3 100755 --- a/mers_lib/src/program/configs/with_multithreading.rs +++ b/mers_lib/src/program/configs/with_multithreading.rs @@ -12,7 +12,6 @@ use crate::{ use super::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_get_result: fn` returns `()` while the thread is running and `(result)` otherwise. pub fn with_multithreading(self) -> Self { diff --git a/mers_lib/src/program/configs/with_string.rs b/mers_lib/src/program/configs/with_string.rs new file mode 100644 index 0000000..0c4c18e --- /dev/null +++ b/mers_lib/src/program/configs/with_string.rs @@ -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::() { + 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::().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::().unwrap().0; + let start = start.get(); + let start = start.as_any().downcast_ref::().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::().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::() + .expect("index_of called on non-tuple"); + let src = a.0[0].get(); + let src = &src + .as_any() + .downcast_ref::() + .unwrap() + .0; + let pat = a.0[1].get(); + let pat = &pat + .as_any() + .downcast_ref::() + .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() + } +} diff --git a/mers_lib/src/program/run/assign_to.rs b/mers_lib/src/program/run/assign_to.rs index 152d146..dd01605 100755 --- a/mers_lib/src/program/run/assign_to.rs +++ b/mers_lib/src/program/run/assign_to.rs @@ -1,5 +1,5 @@ use crate::{ - data::{self, Type}, + data::{self, Data, MersType, Type}, parsing::SourcePos, }; @@ -26,13 +26,24 @@ impl MersStatement for AssignTo { } let source = self.source.check(info, None)?; 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 { let source = self.source.run(info); let target = self.target.run(info); data::defs::assign(&source, &target); - target + Data::empty_tuple() } fn has_scope(&self) -> bool { false diff --git a/mers_lib/src/program/run/variable.rs b/mers_lib/src/program/run/variable.rs index 6d69cc2..1bed306 100755 --- a/mers_lib/src/program/run/variable.rs +++ b/mers_lib/src/program/run/variable.rs @@ -32,13 +32,14 @@ impl MersStatement for Variable { .expect("variable's is_init was true, but check_custom's assign was None? How?") .clone(); } - Ok(if self.is_ref { + let val = if self.is_ref { Type::new(data::reference::ReferenceT( info.scopes[self.var.0].vars[self.var.1].clone(), )) } else { info.scopes[self.var.0].vars[self.var.1].clone() - }) + }; + Ok(val) } fn run_custom(&self, info: &mut super::Info) -> Data { if self.is_init {