mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
updated readme
This commit is contained in:
parent
3dea550565
commit
fd32fd2ba7
344
README.md
344
README.md
@ -1,146 +1,278 @@
|
||||
# mers
|
||||
Mark's error-script -- A script language with explicit error handling and type-checks that run before the script even starts.
|
||||
|
||||
**This is highly experimental and a lot of things might change. It lacks very basic builtins (who needs to add integers together anyways) and has a lot of bugs.**
|
||||
Mers is an experimental programming language inspired by high-level and scripting languages, but with error handling inspired by rust.
|
||||
|
||||
## checks run before the script does
|
||||
## Features
|
||||
|
||||
One of the worst things about script languages is writing a script that runs for more than 5 minutes just to crash right before printing the output because of some type-error.
|
||||
With mers, this won't happen - mers runs checks and parses the script into a special data structure before running it. This ensures that types are always what they should be and all errors can be caught before the script even starts.
|
||||
(mers was born out of the need for "a script language that handles errors almost as well as Rust, but with less typing")
|
||||
### Multiple Types
|
||||
|
||||
## Type-Checked
|
||||
mers' approach to types aims to provide the simplicity of dynamic typing with the safety of static typing.
|
||||
|
||||
The main feature of this language is the type-checking. Here are some common approaches that you might be familiar with:
|
||||
- Asssign a type or a Trait/Interface/Base Class/... to each variable (most statically typed languages - Java, C#, and Rust)
|
||||
- Just don't - what could go wrong? (please stop doing this - JavaScript and most Scripts/Shells)
|
||||
To achieve this, each 'type' is actually a list of single types that are all valid in the given context. All types must be handeled or the compiler will show an error.
|
||||
To avoid having to type so much, types are inferred almost everywhere. It is enough to say `x = if condition "now it's a string" else []`. The compiler will give x the type `string/[]` automatically.
|
||||
|
||||
While the OOP approach is safe and won't cause weird crashes, it requires classes, interfaces or enums, which can be a lot to type for a simple script.
|
||||
`int/float` is a type that can represent integers and floats. Personally, I just call this a number.
|
||||
|
||||
The JavaScript approach requires way less typing, but is prone to crashing
|
||||
Since there are no classes or structs, tuples are also widely used, for examples, `[[]/int string string]` is returned by the builtin run_command() on success. It represents the exit code (if it exists), stdout and stderr.
|
||||
|
||||
In mers, each variable has a type. However, this type is actually a list of all types the variable can have. This means
|
||||
- v = "this is a string"
|
||||
+ s will always be a string. The type-checker knows this.
|
||||
- v = if some_condition { "Hello, user!" } else { -1 }
|
||||
+ v could be a string or an integer. The type checker knows this, too, showing its type as [String, Int].
|
||||
- v = thread( () "This is an anonymous function returning a string" )
|
||||
+ Here, we use thread to run an anonymous function in the background. v will have the type Thread(String). If we do r = v.await(), r will be a String. The type-checker knows this.
|
||||
To index this tuple, you can use `tuple.0` for the exit code and `tuple.1` and `tuple.2` for stdout and stderr.
|
||||
|
||||
To ensure that variables with more than one possible type won't cause issues, **every possible type has to be handeled**. This can, for example, be achieved using to_string(), which accepts arguments of types String, Int, Float, Tuple, or List.
|
||||
If a variable could be a String, Int or Float and should be multiplied with another Float, the type-checker will complain that the String case isn't handeled because the mul() function doesn't accept String arguments.
|
||||
To distinguish between different types, a *switch* statement has to be used. For pattern-matching, *match* statements can be used.
|
||||
### Compile-Checked
|
||||
|
||||
## Error handling
|
||||
The compiler checks your program. It will guarantee type-safety. If a variable has type `int/float/bool`, it cannot be used in the add() function, because you can't add bools.
|
||||
|
||||
Using Rust has dropped the number of runtime errors/exceptions in my projects significantly. The same thing should be true for mers. Just like Rust, there are no exceptions. If a function can fail, that will be visible in the return type.
|
||||
In Rust, loading a file might show Result<String, io::Error> as the functions return type. In Mers, it would be String/[], where [] is an empty Tuple similar to () in Rust. The Type-Checker will refuse to assume that the function returned String, because it might have returned nothing.
|
||||
We need to handle the String case explicitly, for example using *switch*. If we don't, the type-checker wont even let the script start, preventing runtime errors.
|
||||
## Intro
|
||||
|
||||
## Multithreading
|
||||
### mers cli
|
||||
|
||||
"Function" is a valid type for variables in mers.
|
||||
Functions have any number of arguments and can capture their environment.
|
||||
They can be executed synchronously using run(f, args..) or on a different thread using thread(f, args..).
|
||||
run() will return what the function returns while thread() will return a Thread value. await(thread) will wait for the thread to finish and return its value.
|
||||
to use mers, clone this repo and compile it using cargo. (if you don't have rustc and cargo, get it from https://rustup.rs/)
|
||||
|
||||
## Examples
|
||||
for simplicity, i will assume you have the executable in your path and it is named mers. Since this probably isn't the case, just know that `mers` can be replaced with `cargo run --release` in all of the following commands.
|
||||
|
||||
### Switching on an unknown type.
|
||||
|
||||
string = false
|
||||
var = if string "test string" else -1
|
||||
|
||||
print_int = (num int) num.to_string().print()
|
||||
|
||||
debug( var )
|
||||
|
||||
switch! var {
|
||||
string {
|
||||
print("String:")
|
||||
print( var )
|
||||
}
|
||||
int {
|
||||
print("integer:")
|
||||
print_int.run( var )
|
||||
|
||||
To run a program, just run `mers script.txt`. The file needs to be valid utf8.
|
||||
If you compiled mers in debug mode, it will print a lot of debugging information.
|
||||
|
||||
|
||||
|
||||
Somewhere in mers' output, you will see a line with five '-' characters: ` - - - - -`. This is where your program starts running. The second time you see this line is where your program finished. After this, You will see how long your program took to run and its output.
|
||||
|
||||
### Hello, World!
|
||||
|
||||
Since this is likely your first time using mers, let's write a hello world program:
|
||||
|
||||
println("Hello, World!")
|
||||
|
||||
If you're familiar with other programming languages, this is probably what you expected. Running it prints Hello, World! between the two five-dash-lines.
|
||||
The `"` character starts/ends a string literal. This creates a value of type `string` which is then passed to the `println()` function, which writes the string to the programs stdout.
|
||||
|
||||
### Hello, World?
|
||||
|
||||
But what if we didn't print anything?
|
||||
|
||||
"Hello, World?"
|
||||
|
||||
Running this should show Hello, World? as the program's output. This is because the output of a code block in mers is always the output of its last statement. Since we only have one statement, its output is the entire program's output.
|
||||
|
||||
### Hello, Variable!
|
||||
|
||||
Variables in mers don't need to be declared explicitly, you can just assign a value to any variable:
|
||||
|
||||
x = "Hello, Variable!"
|
||||
println(x)
|
||||
x
|
||||
|
||||
Now we have our text between the five-dash-lines AND in the program output. Amazing!
|
||||
|
||||
### User Input
|
||||
|
||||
The builtin `read_line()` function reads a line from stdin. It can be used to get input from the person running your program:
|
||||
|
||||
println("What is your name?")
|
||||
name = read_line()
|
||||
print("Hello, ")
|
||||
println(name)
|
||||
|
||||
`print()` prints the given string to stdout, but doesn't insert the linebreak `\n` character. This way, "Hello, " and the user's name will stay on the same line.
|
||||
|
||||
### Format
|
||||
|
||||
`format()` is a builtin function that takes a string (called the format string) and any number of further arguments. The pattern `{n}` anywhere in the format string will be replaced with the n-th argument, not counting the format string.
|
||||
|
||||
println("What is your name?")
|
||||
name = read_line()
|
||||
println(format("Hello, {0}! How was your day?" name))
|
||||
|
||||
### alternative syntax for the first argument
|
||||
|
||||
If function(a b) is valid, you can also use a.function(b). This does exactly the same thing, because a.function(args) inserts a as the first argument, moving all other args back by one.
|
||||
This can make reading `println(format(...))` statements a lot more enjoyable:
|
||||
|
||||
println("Hello, {0}! How was your day?".format(name))
|
||||
|
||||
However, this can also be overused:
|
||||
|
||||
"Hello, {0}! How was your day?".format(name).println()
|
||||
|
||||
I would consider this to be less readable because `println()`, which is the one and only thing this line was written to achieve, is now the last thing in the line. Readers need to read the entire line before realizing it's "just" a print statement. However, this is of course personal preference.
|
||||
|
||||
### A simple counter
|
||||
|
||||
Let's build a counter app: We start at 0. If the user types '+', we increment the counter by one. If they type '-', we decrement it. If they type anything else, we print the current count in a status message.
|
||||
|
||||
The first thing we will need for this is a loop:
|
||||
|
||||
while {
|
||||
println("...")
|
||||
}
|
||||
|
||||
Running this should spam your terminal with '...'.
|
||||
|
||||
Now let's add a counter variable and read user input:
|
||||
|
||||
counter = 0
|
||||
while {
|
||||
input = read_line()
|
||||
if input.eq("+") {
|
||||
counter = counter.add(1)
|
||||
} else if input.eq("-") {
|
||||
counter = counter.sub(1)
|
||||
} else {
|
||||
println("The counter is currently at {0}. Type + or - to change it.".format(counter))
|
||||
}
|
||||
}
|
||||
|
||||
using switch! forces you to cover all possible types. Try removing the string or int case and see what happens!
|
||||
mers actually doesn't have an else-if, the if statement is parsed as:
|
||||
|
||||
### Matching on user input
|
||||
|
||||
In this script, we ask the user to enter some text. We match on the entered text:
|
||||
|
||||
- If it can be parsed as an int, we save it as an int.
|
||||
- If it can't be parsed as an int, but it can be parsed as a float, we save it as a float
|
||||
- If it can't be parsed as an int or a float, we return an error (of type string)
|
||||
|
||||
We then debug-print num (try entering '12' vs '12.5' - nums type will change!)
|
||||
|
||||
println("Enter some text, I will try to parse it!")
|
||||
text = read_line()
|
||||
|
||||
num = match text {
|
||||
// if possible, returns an int, otherwise a float, if that's also not possible, returns an error message in form of a string
|
||||
// parse_int and parse_float return either [] (which won't match) or int/float (which will match), so they just work with the match statement.
|
||||
parse_int(text) text
|
||||
parse_float(text) text
|
||||
// eq returns a bool, which will match if it was *true*, but not if it was *false*
|
||||
text.eq("some text") "haha, very funny..."
|
||||
// default value (true will always match)
|
||||
true "number wasn't int/float!"
|
||||
if input.eq("+") {
|
||||
counter = counter.add(1)
|
||||
} else {
|
||||
if input.eq("-") {
|
||||
counter = counter.sub(1)
|
||||
} else {
|
||||
println("The counter is currently at {0}. Type + or - to change it.".format(counter))
|
||||
}
|
||||
}
|
||||
|
||||
"Input: \"{0}\"".format(text).println()
|
||||
num.debug()
|
||||
This works because most {}s are optional in mers. The parser just parses a "statement", which *can* be a block.
|
||||
A block starts at {, can contain any number of statements, and ends with a }. This is why we can use {} in if statements, function bodies, and many other locations. But if we only need to do one thing, we don't need to put it in a block.
|
||||
In fact, `fn difference(a int/float b int/float) if a.gt(b) a.sub(b) else b.sub(a)` is completely valid in mers (gt = "greater than").
|
||||
|
||||
A match arm consists of two consecutive statements: The condition statement followed by the action statement.
|
||||
### Getting rid of the if-else-chain
|
||||
|
||||
If the condition statement matches, the action statement will be executed. The matched value from the condition statement can be found in the variable we originally matched on, so it can be used in the action statement.
|
||||
Let's replace the if statement from before with a nice match statement!
|
||||
|
||||
**These are the rules for matching:**
|
||||
- The condition statement *does not match* if it returns **false**, **[]** or an error.
|
||||
- If the condition statement returns a length 1 tuple [v], it will match and the matched value will be v. The condition statement can't return any tuple except [] and [v] to avoid confusion. To return a tuple, wrap it in a length-1 tuple: [[val_1, val_2, val_3]].
|
||||
- Otherwise, the condition statement will match and the matched value will be whatever it returned.
|
||||
counter = 0
|
||||
while {
|
||||
input = read_line()
|
||||
match input {
|
||||
input.eq("+") counter = counter.add(1)
|
||||
input.eq("-") counter = counter.sub(1)
|
||||
true println("The counter is currently at {0}. Type + or - to change it.".format(counter))
|
||||
}
|
||||
}
|
||||
|
||||
### Reading /tmp/ and filtering for files/directories
|
||||
the syntax for a match statement is always `match <variable> { <match arms> }`.
|
||||
|
||||
for file fs_list("/tmp/") {
|
||||
list = file.fs_list()
|
||||
switch! list {
|
||||
[] {
|
||||
"\"{0}\" is a file".format(file).println()
|
||||
}
|
||||
[string] {
|
||||
"\"{0}\" is a directory".format(file).println()
|
||||
A match arm consists of a condition statement and an action statement. `input.eq("+")`, `input-eq("-")`, and `true` are condition statements.
|
||||
The match statement will go through all condition statements until one matches (in this case: returns `true`), then run the action statement.
|
||||
If we move the `true` match arm to the top, the other two arms will never be executed, even though they might also match.
|
||||
|
||||
### Breaking from loops
|
||||
|
||||
Loops will break if the value returned in the current iteration matches:
|
||||
|
||||
i = 0
|
||||
res = while {
|
||||
i = i.add(1)
|
||||
i.gt(50)
|
||||
}
|
||||
println("res: {0}".format(res))
|
||||
|
||||
This will increment i until it reaches 51.
|
||||
Because `51.gt(50)` returns `true`, `res` will be set to `true`.
|
||||
|
||||
i = 0
|
||||
res = while {
|
||||
i = i.add(1)
|
||||
if i.gt(50) i else []
|
||||
}
|
||||
println("res: {0}".format(res))
|
||||
|
||||
Because a value of type int matches, we now break with "res: 51". For more complicated examples, using `[i]` instead of just `i` is recommended because `[i]` matches even if `i` doesn't.
|
||||
|
||||
A while loop's return type will be the matches of the inner return type.
|
||||
|
||||
For for loops, which can also end without a value matching, the return type is the same plus the empty tuple `[]`:
|
||||
|
||||
res = for i 100 {
|
||||
if 50.sub(i).eq(5) {
|
||||
[i]
|
||||
}
|
||||
}
|
||||
switch! res {}
|
||||
|
||||
You have to cover `[]/int` because the condition in the loop might not match for any value from 0 to 99.
|
||||
|
||||
### Let's read a file!
|
||||
|
||||
file = fs_read(&args.get(0).assume1("please provided a text file to read!"))
|
||||
switch! {}
|
||||
|
||||
Since `get()` can fail, it returns `[]/[t]` where t is the type of elements in the list. To avoid handling the `[]` case, the `assume1()` builtin takes a `[]/[t]` and returns `t`. If the value is `[]`, it will cause a crash.
|
||||
|
||||
We get the first argument passed to our program and read it from disk using fs_read. To run this: `mers script.txt test_file.txt`.
|
||||
|
||||
`switch` is used to distinguish between multiple possible types. `switch!` will cause a compiler error unless *all* types are covered.
|
||||
This is useful to see what type the compiler thinks a variable has: In this case `[int]/Err(string)`.
|
||||
|
||||
Err(string) is a string value in an Err enum. Builtin functions use the Err enum to indicate errors because there is no concrete Error type.
|
||||
|
||||
After handling all errors, this is the code I came up with:
|
||||
|
||||
file = fs_read(&args.get(0).assume1("please provided a text file to read!"))
|
||||
switch! file {
|
||||
[int] {
|
||||
file_as_string = bytes_to_string(file)
|
||||
contents = switch! file_as_string {
|
||||
string file_as_string
|
||||
Err([string string] ) {
|
||||
println("File wasn't valid UTF8, using lossy conversion!")
|
||||
file_as_string.noenum().0
|
||||
}
|
||||
}
|
||||
println(contents)
|
||||
}
|
||||
Err(string) println("Couldn't read the file: {0}".format(file.noenum()))
|
||||
}
|
||||
|
||||
### Running a thread and awaiting it, passing arguments to the thread when starting it and sharing a variable because the thread's function captured it (useful for reporting progress, i.e. changing a float from 0.0 to 100.0 while downloading and using the main thread to animate a progress bar, then using .await() only when the float is set to 100 to avoid blocking)
|
||||
If fs_read returns an error, the file couldn't be read.
|
||||
|
||||
print( "Starting" )
|
||||
If bytes_to_string returns a string, we just return it directly.
|
||||
|
||||
captured = "Unchanged"
|
||||
If bytes_to_string returns an error, the file wasn't valid UTF-8. We print a warning and use the first field on the error type,
|
||||
which is a string that has been lossily converted from the bytes - any invalid sequences have been replaced with the replacement character.
|
||||
|
||||
calc_function = (prog_message string) {
|
||||
sleep( 1 )
|
||||
print( prog_message )
|
||||
sleep( 3 )
|
||||
captured = "changed from a different thread"
|
||||
"this is my output"
|
||||
}
|
||||
We then print the string we read from the file (the `contents` variable).
|
||||
|
||||
calc_thread = thread(calc_function "progress message!")
|
||||
### Advanced match statements
|
||||
|
||||
sleep( 2 )
|
||||
print( "Done." )
|
||||
print( captured )
|
||||
(todo)
|
||||
|
||||
calc_thread.await().print()
|
||||
print( captured )
|
||||
## Advanced Info / Docs
|
||||
|
||||
## Quirks
|
||||
### Matching
|
||||
|
||||
currently, f(a b c) is the same as a.f(b c). var.function(args) will use var as the function's first argument, moving all other arguments back. This removes the need for struct/class syntax. Simply declare a function scream(str string) { str.to_upper().print() } and you can now use var.scream() on all strings.
|
||||
- An empty tuple `[]`, `false`, and any enum member `any_enum_here(any_value)` will not match.
|
||||
- A one-length tuple `[v]` will match with `v`, `true` will match with `true`.
|
||||
- A tuple with len >= 2 is considered invalid: It cannot be used for matching because it might lead to accidental matches and could cause confusion. If you try to use this, you will get an error from the compiler.
|
||||
- Anything else will match with itself.
|
||||
|
||||
### Single Types
|
||||
|
||||
Mers has the following builtin types:
|
||||
|
||||
- bool
|
||||
- int
|
||||
- float
|
||||
- string
|
||||
- tuple
|
||||
+ the tuple type is written as any number of types separated by whitespace(s), enclosed in square brackets: [int string].
|
||||
+ tuple values are created by putting any number of statements in square brackets: ["hello" "world" 12 -0.2 false].
|
||||
- list
|
||||
+ list types are written as a single type enclosed in square brackets: [string]. TODO! this will likely change to [string ...] or something similar to allow 1-long tuples in function args.
|
||||
+ list values are created by putting any number of statements in square brackets, prefixing the closing bracket with ...: ["hello" "mers" "world" ...].
|
||||
- functions
|
||||
+ function types are written as fn(args) out_type (TODO! implement this)
|
||||
+ function values are created using the (<first_arg_name> <first_arg_type> <second_arg_name> <second_arg_type>) <statement> syntax: anonymous_power_function = (a int b int) a.pow(b).
|
||||
+ to run anonymous functions, use the run() builtin: anonymous_power_function.run(4 2) evaluates to 16.
|
||||
+ note: functions are defined using the fn <name>(<args>) <statement> syntax and are different from anonymous functions because they aren't values and can be run directory: fn power_function(a int b int) a.pow(b) => power_function(4 2) => 16
|
||||
- thread
|
||||
+ a special type returned by the thread builtin. It is similar to JavaScript promises and can be awaited to get the value once it has finished computing.
|
||||
- reference
|
||||
+ a mutable reference to a value. &<type> for the type and &<statement> for a reference value.
|
||||
- enum
|
||||
+ wraps any other value with a certain identifier.
|
||||
+ the type is written as <enum>(<type>): many builtins use Err(String) to report errors.
|
||||
+ to get a value, use <enum>: <statement>.
|
||||
|
@ -1,9 +0,0 @@
|
||||
x = if true enum_b: enum_a: "hello" else "world"
|
||||
x.debug()
|
||||
switch! x {
|
||||
string {
|
||||
println("no enum")
|
||||
x.println()
|
||||
}
|
||||
enum_b(enum_a(string)) x.noenum().noenum().println()
|
||||
}
|
13
regex.txt
13
regex.txt
@ -1,13 +0,0 @@
|
||||
pot_dates = "2014-01-01 201-02-05 2022-09-05"
|
||||
|
||||
for v pot_dates.regex("\\d{4}-\\d{2}-\\d{2}").0.assume1() v.println()
|
||||
|
||||
reg = pot_dates.regex("\\t\\h\\is is not a regex")
|
||||
|
||||
match reg {
|
||||
reg.0 {
|
||||
"regex matches:".println()
|
||||
for reg reg reg.println()
|
||||
}
|
||||
true "not a valid regex!".println()
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
println("Which directory do you want to scan?")
|
||||
dir = read_line()
|
||||
println("How deep should I search?")
|
||||
depth = while {
|
||||
depth = read_line().parse_int()
|
||||
switch! depth {
|
||||
[] {
|
||||
println("depth wasn't an integer!")
|
||||
[]
|
||||
}
|
||||
int if depth.gtoe(0) {
|
||||
// break from loop with value depth
|
||||
[depth]
|
||||
} else {
|
||||
// depth < 0
|
||||
println("depth was negative!")
|
||||
[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Searching in {0} with a maximum depth of {1}...".format(dir depth).println()
|
||||
|
||||
fn find_all_files(dir string max_depth int) {
|
||||
files = ["" ...]
|
||||
&files.pop()
|
||||
entries = dir.fs_list()
|
||||
depth = 0
|
||||
while {
|
||||
new = ["" ...] // set type string
|
||||
&new.pop()
|
||||
for entry entries {
|
||||
local_entries = entry.fs_list()
|
||||
switch! local_entries {
|
||||
[] &files.push(entry)
|
||||
[string] for entry entry.fs_list() {
|
||||
&new.push(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
entries = new
|
||||
depth = depth.add(1)
|
||||
if depth.gt(max_depth) [files] else []
|
||||
}
|
||||
}
|
||||
|
||||
all_files = find_all_files(dir depth)
|
||||
for file all_files file.println()
|
@ -11,7 +11,7 @@ fn main() {
|
||||
.unwrap();
|
||||
println!(" - - - - -");
|
||||
let start = Instant::now();
|
||||
let out = script.run(std::env::args().collect());
|
||||
let out = script.run(std::env::args().skip(2).collect());
|
||||
let elapsed = start.elapsed();
|
||||
println!(" - - - - -");
|
||||
println!("Output ({}s)\n{out}", elapsed.as_secs_f64());
|
||||
|
@ -415,28 +415,9 @@ pub mod to_runnable {
|
||||
None => None,
|
||||
},
|
||||
),
|
||||
SStatementEnum::While(c) => RStatementEnum::While({
|
||||
let condition = statement(&c, ginfo, linfo)?;
|
||||
let out = condition.out();
|
||||
let out1 = out.fits_in(&VSingleType::Bool.into());
|
||||
if out1.is_empty() {
|
||||
condition
|
||||
} else {
|
||||
if out.types.is_empty() {
|
||||
return Err(ToRunnableError::InvalidTypeForWhileLoop(out));
|
||||
}
|
||||
for t in out.types.iter() {
|
||||
if let VSingleType::Tuple(v) = t {
|
||||
if v.len() > 1 {
|
||||
return Err(ToRunnableError::InvalidTypeForWhileLoop(out));
|
||||
}
|
||||
} else {
|
||||
return Err(ToRunnableError::InvalidTypeForWhileLoop(out));
|
||||
}
|
||||
}
|
||||
condition
|
||||
}
|
||||
}),
|
||||
SStatementEnum::While(c) => RStatementEnum::While(
|
||||
statement(&c, ginfo, linfo)?
|
||||
),
|
||||
SStatementEnum::For(v, c, b) => {
|
||||
let mut linfo = linfo.clone();
|
||||
let container = statement(&c, ginfo, &mut linfo)?;
|
||||
@ -908,7 +889,7 @@ impl RStatementEnum {
|
||||
if let Some(b) = b {
|
||||
a.out() | b.out()
|
||||
} else {
|
||||
a.out()
|
||||
a.out() | VSingleType::Tuple(vec![]).to()
|
||||
}
|
||||
}
|
||||
Self::While(c) => {
|
||||
|
@ -278,8 +278,17 @@ impl BuiltinFunction {
|
||||
false
|
||||
}
|
||||
}
|
||||
Self::Add
|
||||
| Self::Sub
|
||||
Self::Add => {
|
||||
input.len() == 2 && {
|
||||
let num = VType {
|
||||
types: vec![VSingleType::Int, VSingleType::Float],
|
||||
};
|
||||
let st = VSingleType::String.to();
|
||||
(input[0].fits_in(&num).is_empty() && input[1].fits_in(&num).is_empty())
|
||||
|| (input[0].fits_in(&st).is_empty() && input[1].fits_in(&st).is_empty())
|
||||
}
|
||||
}
|
||||
Self::Sub
|
||||
| Self::Mul
|
||||
| Self::Div
|
||||
| Self::Mod
|
||||
@ -485,7 +494,18 @@ impl BuiltinFunction {
|
||||
| Self::Min
|
||||
| Self::Max => {
|
||||
if input.len() == 2 {
|
||||
match (
|
||||
let mut might_be_string = false;
|
||||
if let Self::Add = self {
|
||||
match (
|
||||
input[0].contains(&VSingleType::String),
|
||||
input[1].contains(&VSingleType::String),
|
||||
) {
|
||||
(true, true) => might_be_string = true,
|
||||
(true, false) | (false, true) => unreachable!(),
|
||||
(false, false) => (),
|
||||
}
|
||||
}
|
||||
let o = match (
|
||||
(
|
||||
input[0].contains(&VSingleType::Int),
|
||||
input[0].contains(&VSingleType::Float),
|
||||
@ -499,7 +519,13 @@ impl BuiltinFunction {
|
||||
((true, _), (true, _)) => VType {
|
||||
types: vec![VSingleType::Int, VSingleType::Float],
|
||||
},
|
||||
((false, false), (false, false)) => VType { types: vec![] },
|
||||
_ => VSingleType::Float.to(),
|
||||
};
|
||||
if might_be_string {
|
||||
o | VSingleType::String.to()
|
||||
} else {
|
||||
o
|
||||
}
|
||||
} else {
|
||||
unreachable!("called add/sub/mul/div/mod/pow with args != 2")
|
||||
@ -919,6 +945,10 @@ impl BuiltinFunction {
|
||||
Self::Add => {
|
||||
if args.len() == 2 {
|
||||
match (args[0].run(vars, libs).data, args[1].run(vars, libs).data) {
|
||||
(VDataEnum::String(mut a), VDataEnum::String(b)) => {
|
||||
a.push_str(b.as_str());
|
||||
VDataEnum::String(a).to()
|
||||
}
|
||||
(VDataEnum::Int(a), VDataEnum::Int(b)) => VDataEnum::Int(a + b).to(),
|
||||
(VDataEnum::Int(a), VDataEnum::Float(b)) => {
|
||||
VDataEnum::Float(a as f64 + b).to()
|
||||
@ -927,7 +957,7 @@ impl BuiltinFunction {
|
||||
VDataEnum::Float(a + b as f64).to()
|
||||
}
|
||||
(VDataEnum::Float(a), VDataEnum::Float(b)) => VDataEnum::Float(a + b).to(),
|
||||
_ => unreachable!("add: not a number"),
|
||||
_ => unreachable!("add: not a number/string"),
|
||||
}
|
||||
} else {
|
||||
unreachable!("add: not 2 args")
|
||||
@ -1364,27 +1394,17 @@ impl BuiltinFunction {
|
||||
(args[0].run(vars, libs).data, args[1].run(vars, libs).data)
|
||||
{
|
||||
match regex::Regex::new(regex.as_str()) {
|
||||
Ok(regex) => {
|
||||
VDataEnum::Tuple(vec![VDataEnum::Tuple(vec![VDataEnum::List(
|
||||
VSingleType::String.to(),
|
||||
regex
|
||||
.find_iter(a.as_str())
|
||||
.map(|v| VDataEnum::String(v.as_str().to_string()).to())
|
||||
.collect(),
|
||||
)
|
||||
.to()])
|
||||
.to()])
|
||||
.to()
|
||||
}
|
||||
Ok(regex) => VDataEnum::List(
|
||||
VSingleType::String.to(),
|
||||
regex
|
||||
.find_iter(a.as_str())
|
||||
.map(|v| VDataEnum::String(v.as_str().to_string()).to())
|
||||
.collect(),
|
||||
)
|
||||
.to(),
|
||||
Err(e) => VDataEnum::EnumVariant(
|
||||
EV_ERR,
|
||||
Box::new(
|
||||
VDataEnum::Tuple(vec![
|
||||
VDataEnum::Tuple(vec![]).to(), // no results
|
||||
VDataEnum::String(e.to_string()).to(),
|
||||
])
|
||||
.to(),
|
||||
),
|
||||
Box::new(VDataEnum::String(e.to_string()).to()),
|
||||
)
|
||||
.to(),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user