mirror of
https://github.com/Dummi26/mers.git
synced 2026-01-26 10:47:04 +01:00
move old mers to old/ and update gitignore
This commit is contained in:
302
old/README.md
Executable file
302
old/README.md
Executable file
@@ -0,0 +1,302 @@
|
||||
# mers 
|
||||
|
||||
Mers is a simple and reliable programming language.
|
||||
|
||||
While mers can already be used right now,
|
||||
many things about it will still change -
|
||||
at least until 1.0.0 is released.
|
||||
For serious projects, this probably
|
||||
isn't the language you should be using.
|
||||
|
||||
## Why mers?
|
||||
|
||||
### it doesn't crash
|
||||
|
||||
If your program starts, **it won't crash**.
|
||||
The only exceptions to this are
|
||||
the builtin `exit()` function, which can exit with a nonzero exit code
|
||||
and the builtin `assume` functions, similar to Rust's `unwrap()`.
|
||||
|
||||
This is because **errors in mers are values**,
|
||||
and the compiler forces you to handle them.
|
||||
|
||||
### type system
|
||||
|
||||
In mers, a type can consist of multiple types:
|
||||
|
||||
```
|
||||
// bool
|
||||
half := true
|
||||
// float/int
|
||||
number := if half { 0.5 } else { 1 }
|
||||
|
||||
// some math functions that work with int and float
|
||||
fn absolute_value(num int/float) {
|
||||
if num >= 0 { num } else { 0 - num }
|
||||
}
|
||||
fn difference(a int/float, b int/float) {
|
||||
absolute_value(a - b)
|
||||
}
|
||||
|
||||
debug(difference(0.2, 2)) // float: 1.8
|
||||
// although difference can work with floats, since we only give it ints, it also returns an int.
|
||||
// this is known at compile time.
|
||||
debug(difference(20, 5)) // int: 15
|
||||
```
|
||||
|
||||
With this, you can write code like you would in dynamically typed languages:
|
||||
|
||||
```
|
||||
user_input := if true { "some text input" } else { 19 }
|
||||
```
|
||||
|
||||
But you get the compile-time checks you love from statically typed ones:
|
||||
|
||||
```
|
||||
if user_input >= min_age {
|
||||
println("welcome!")
|
||||
}
|
||||
```
|
||||
|
||||
Because `user_input` could be a string, the code above won't compile.
|
||||
|
||||
### it's simple
|
||||
|
||||
Almost everything in mers is built around this type system.
|
||||
This eliminates many concepts you may be familiar with:
|
||||
|
||||
#### instead of null or exceptions, just add `[]` or the error(s) to the return type
|
||||
|
||||
```
|
||||
// returns `[]` instead of dividing by zero
|
||||
fn div_checked(a int, b int) -> int/[] {
|
||||
if b != 0 {
|
||||
a / b
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
// returns the file content as a string.
|
||||
// if the file can't be read, returns IoError and an error message.
|
||||
// if the file's contents weren't Utf8, return NotUtf8 and the file's bytes.
|
||||
fn load_file_as_string(path string) -> string/IoError(string)/NotUtf8([int ...]) {}
|
||||
```
|
||||
|
||||
#### structs are nothing more than tuples
|
||||
|
||||
```
|
||||
// define our type named Person
|
||||
type Person [int, string]
|
||||
|
||||
// function to get (but not set) the person's birth year
|
||||
fn birth_year(self Person) self.0
|
||||
|
||||
// function to get or set the person's name
|
||||
fn name(self Person/&Person) self.1
|
||||
|
||||
person := [1999, "Ryan"]
|
||||
|
||||
// Get the values
|
||||
person.name() // Ryan
|
||||
person.birth_year() // 1999
|
||||
|
||||
// Change the name
|
||||
&person.name() = "James"
|
||||
// or: name(&person) = "James"
|
||||
person.name() // James
|
||||
|
||||
// This doesn't compile:
|
||||
// &person.birth_year() = 1998
|
||||
```
|
||||
|
||||
#### enums are already part of the type system
|
||||
|
||||
```
|
||||
type DirectoryEntry File(string)/Dir(string)/Link(DirectoryEntry)
|
||||
```
|
||||
|
||||
#### return and break
|
||||
|
||||
Most people agree that `goto` statements are usually a bad idea.
|
||||
But `return` and `break` have similar issues, although less severe:
|
||||
|
||||
**they create weird control flow:**
|
||||
|
||||
Maybe you want to see how long a function takes to execute, so
|
||||
you add a `println` to the end - just to see absolutely no output.
|
||||
Now you might think the function is stuck in an infinite loop somewhere,
|
||||
although it just returned before it ever got to your println.
|
||||
This can be annoying. If we remove `return` statements,
|
||||
it becomes way easier to tell when code will or won't be executed.
|
||||
|
||||
**return is exclusive to functions, break to loops**
|
||||
|
||||
With mers, whenever possible, concepts in the language should work on *statements*.
|
||||
|
||||
An example of this are type annotations:
|
||||
|
||||
```
|
||||
fn half(num int) -> int { num / 2 }
|
||||
```
|
||||
|
||||
The `-> int` indicates that this function returns an int.
|
||||
Actually, it indicates that the statement following it returns an int.
|
||||
This obviously means the function will also return an int,
|
||||
but since this syntax isn't specifically made for functions,
|
||||
we can use it anywhere we want:
|
||||
|
||||
```
|
||||
num := -> int { 4 + 5 }
|
||||
half(-> int { double * 2})
|
||||
```
|
||||
|
||||
**So how do we return values then?**
|
||||
Simple!
|
||||
|
||||
```
|
||||
// a function is just another statement
|
||||
fn return_int() 4 + 5
|
||||
|
||||
// a block returns whatever its last statement returns,
|
||||
fn return_string() {
|
||||
a := "start"
|
||||
b := "end"
|
||||
a + " " + b
|
||||
}
|
||||
|
||||
// this returns string/[], because there is no else
|
||||
if condition() {
|
||||
"some value"
|
||||
}
|
||||
|
||||
// this returns string/int
|
||||
if condition() {
|
||||
"some value"
|
||||
} else {
|
||||
12
|
||||
}
|
||||
```
|
||||
|
||||
Most returns should be relatively intuitive,
|
||||
although some special ones (like loops)
|
||||
use matching (see docs/intro)
|
||||
|
||||
### it has references
|
||||
|
||||
To explain why this is necessary,
|
||||
let's look at examples from other languages.
|
||||
|
||||
Here is some JavaScript:
|
||||
|
||||
```js
|
||||
function doSmth(list) {
|
||||
list[0] = 0
|
||||
}
|
||||
function modify(num) {
|
||||
num = 0
|
||||
}
|
||||
list = [1]
|
||||
num = 1
|
||||
doSmth(list)
|
||||
modify(num)
|
||||
console.log(list) // [ 0 ]
|
||||
console.log(num) // 1
|
||||
```
|
||||
|
||||
The same thing in Go:
|
||||
|
||||
```go
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
func doSmth(list []int) {
|
||||
list[0] = 0
|
||||
}
|
||||
func modify(num int) {
|
||||
num = 0
|
||||
}
|
||||
func main() {
|
||||
list := []int{1}
|
||||
num := 1
|
||||
doSmth(list)
|
||||
modify(num)
|
||||
fmt.Println(list) // [0]
|
||||
fmt.Println(num) // 1
|
||||
}
|
||||
```
|
||||
|
||||
In both cases, the function was able to modify the list's contents,
|
||||
but unable to change the number to a different value.
|
||||
This is not just inconsistent, it's also not always what you wanted to do:
|
||||
|
||||
- i passed the list by value, how could the function change the inner value?
|
||||
- the function is called `modify`, why can't it modify the value?
|
||||
+ In Go, we could use references to make this work - but that just makes the list example even more annoying, since we don't need a reference to change the inner value.
|
||||
|
||||
So, let's look at mers:
|
||||
|
||||
```
|
||||
fn doSmth(list [int ...]) {
|
||||
&list.get(0).assume1() = 0
|
||||
}
|
||||
fn modify(num &int) {
|
||||
num = 0
|
||||
}
|
||||
list := [1 ...]
|
||||
num := 1
|
||||
doSmth(list)
|
||||
modify(&num)
|
||||
debug(list) // [1 ...]
|
||||
debug(num) // 0
|
||||
```
|
||||
|
||||
The list is unchanged, but the number was changed by `modify`,
|
||||
because it was explicitly passed as a reference `&int`.
|
||||
**Unless you pass a reference, the value you passed will not be changed.**
|
||||
|
||||
### compile-time execution via macros
|
||||
|
||||
```
|
||||
// at compile-time, runs primes.mers to find the first few prime numbers
|
||||
primes := !(mers "primes.mers")
|
||||
|
||||
// at compile-time, runs the code to create a string
|
||||
// containing 256 '-'s
|
||||
long_string := !(mers {
|
||||
str := "-"
|
||||
loop {
|
||||
str = str + str
|
||||
if str.len() >= 256 {
|
||||
str
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## How mers?
|
||||
|
||||
There are prebuilt binaries in `build_scripts/`.
|
||||
|
||||
Mers is written in Rust. If you have `cargo`, you can also use the build script in `build_scripts/` to build it yourself.
|
||||
|
||||
Now, create a new text file (or choose one from the examples) and run it: `mers <file>`.
|
||||
|
||||
## Known Issues (only major ones)
|
||||
|
||||
### Multithreading
|
||||
|
||||
If a function is called from two threads, all local variables of that function are shared.
|
||||
This doesn't affect builtin functions, and since functions usually don't take long to execute,
|
||||
the chance of anyone encountering this is low, but it's something to be aware of.
|
||||
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
|
||||
|
||||
## Docs
|
||||
|
||||
[intro](docs/intro.md)
|
||||
|
||||
[syntax cheat sheet](docs/syntax_cheat_sheet.md)
|
||||
|
||||
[builtins](docs/builtins.md)
|
||||
|
||||
[statements](docs/statements.md)
|
||||
4
old/build_scripts/build.sh
Executable file
4
old/build_scripts/build.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
cd ../mers
|
||||
cargo build --release
|
||||
cp target/release/mers ../build_scripts
|
||||
5
old/build_scripts/build_cross.sh
Executable file
5
old/build_scripts/build_cross.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
./build_musl.sh
|
||||
cd ../mers
|
||||
cargo build --release --target x86_64-pc-windows-gnu
|
||||
cp target/x86_64-pc-windows-gnu/release/mers.exe ../build_scripts
|
||||
4
old/build_scripts/build_musl.sh
Executable file
4
old/build_scripts/build_musl.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
cd ../mers
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
cp target/x86_64-unknown-linux-musl/release/mers ../build_scripts
|
||||
5
old/build_scripts/install_nu_plugin.sh
Executable file
5
old/build_scripts/install_nu_plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
cd ../mers
|
||||
cargo build --features nushell_plugin --profile nushellplugin
|
||||
cp target/nushellplugin/mers ../build_scripts/nu_plugin_mers
|
||||
echo "done. now run 'register nu_plugin_mers' in nu. You should then be able to use mers-nu."
|
||||
4
old/build_scripts/install_nu_plugin_auto.sh
Executable file
4
old/build_scripts/install_nu_plugin_auto.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env nu
|
||||
./install_nu_plugin.sh
|
||||
register nu_plugin_mers
|
||||
echo "registered plugin automatically."
|
||||
BIN
old/build_scripts/mers
Executable file
BIN
old/build_scripts/mers
Executable file
Binary file not shown.
BIN
old/build_scripts/mers.exe
Executable file
BIN
old/build_scripts/mers.exe
Executable file
Binary file not shown.
175
old/docs/builtins.md
Executable file
175
old/docs/builtins.md
Executable file
@@ -0,0 +1,175 @@
|
||||
# mers builtins
|
||||
|
||||
## functions
|
||||
|
||||
### assume1
|
||||
|
||||
if the input is `[v]`, returns `v`. if the input is `[]`, panics and crashes the program.
|
||||
useful for prototyping when error-handling is not a priority or for use with the `get` function with an index that is always in range.
|
||||
|
||||
### assume_no_enum
|
||||
|
||||
if the input is any enum variant, panics and crashes the program.
|
||||
just like `assume1`, this is useful to quickly ignore `Err(_)` types.
|
||||
|
||||
### noenum
|
||||
|
||||
if the input is any enum variant, returns the inner value. mostly used in `switch` statements when you want to use the inner value from an enum value (like the `string` from an `Err(string)`).
|
||||
|
||||
### matches
|
||||
|
||||
returns `[]` for values that don't match and `[v]` for ones that do, where `v` is the matched value.
|
||||
useful to avoid the (sometimes ambiguous) values that technically match but aren't `[v]`.
|
||||
|
||||
### clone
|
||||
|
||||
NOTE: may be replaced with dereference syntax?
|
||||
|
||||
### print
|
||||
|
||||
writes a string to stdout
|
||||
|
||||
### println
|
||||
|
||||
like print, but adds a newline character
|
||||
|
||||
### debug
|
||||
|
||||
prints a values compile-time type, runtime type and value
|
||||
|
||||
### read_line
|
||||
|
||||
reads one line from stdin and returns it.
|
||||
blocks until input was received.
|
||||
|
||||
### to_string
|
||||
|
||||
converts any value to a string so that the string, if parsed by the mers parser, would perfectly represent the valu.
|
||||
Exceptions are strings (because of escape sequences), functions, thread values and maybe more. this list should get shorter with time.
|
||||
|
||||
### format
|
||||
|
||||
given a format string (first argument) and any additional number of strings, replaces "{n}" in the format string with the {n}th additional argument,
|
||||
so that {0} represents the first extra argument: `"val: {0}".format(value)`
|
||||
|
||||
### parse_int
|
||||
|
||||
tries to parse a string to an int
|
||||
|
||||
### parse_float
|
||||
|
||||
tries to parse a string to a float
|
||||
|
||||
### run
|
||||
|
||||
runs an anonymous function. further arguments are passed to the anonymous function.
|
||||
|
||||
### thread
|
||||
|
||||
like run, but spawns a new thread to do the work.
|
||||
|
||||
### await
|
||||
|
||||
takes the value returned by thread, waits for the thread to finish and then returns whatever the anonymous function used to spawn the thread returned.
|
||||
|
||||
### sleep
|
||||
|
||||
sleeps for a number of seconds (int/float)
|
||||
|
||||
### exit
|
||||
|
||||
exits the process, optionally with a specific exit code
|
||||
|
||||
### fs_list, fs_read, fs_write
|
||||
|
||||
file system operations - will likely be reworked at some point
|
||||
|
||||
### bytes_to_string, string_to_bytes
|
||||
|
||||
converts UTF-8 bytes to a string (can error) and back (can't error)
|
||||
|
||||
### run_command, run_command_get_bytes
|
||||
|
||||
runs a command (executable in PATH) with a set of arguments (list of string), returning `[exit_code stdout stderr]` on success
|
||||
|
||||
### not
|
||||
|
||||
turns `true` to `false` and `false` to `true`
|
||||
|
||||
### and, or, add, sub, mul, div, mod, pow, eq, ne, lt, gt, ltoe, gtoe
|
||||
|
||||
functions for operators like `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `>`, `<=`, ...
|
||||
|
||||
### min, max
|
||||
|
||||
returns the max/min of two numbers
|
||||
|
||||
### push
|
||||
|
||||
given a reference to a list and some value, appends that value to the end of the list.
|
||||
|
||||
### insert
|
||||
|
||||
same as push, but the index where the value should be inserted can be specified
|
||||
|
||||
### pop
|
||||
|
||||
given a reference to a list, <removes and returns the last element from a list<
|
||||
|
||||
### remove
|
||||
|
||||
same as pop, but the index of the value to remove can be specified
|
||||
|
||||
### get
|
||||
|
||||
given a list and an index, returns the value at that index wrapped in a 1-length tuple or `[]`.
|
||||
if the first argument is a refernce to a list, this will return a reference to the value at that index (which can be modified):
|
||||
|
||||
`&list.get(2).assume1() = "new_value"`
|
||||
|
||||
### len
|
||||
|
||||
returns the length of the string/list/tuple/...
|
||||
|
||||
### contains, starts_with, ends_with
|
||||
|
||||
check for certain substring in a string
|
||||
|
||||
### index_or
|
||||
|
||||
find first index of a certain substring in a string, or return `[]` otherwise
|
||||
|
||||
### trim
|
||||
|
||||
remove leading and trailing whitespaces from the string
|
||||
|
||||
### substring
|
||||
|
||||
returns a sustring of the original string.
|
||||
|
||||
first argmuent is the start index. -1 is the last character in the string, -2 the second to last and so on.
|
||||
|
||||
second argument, if provided, is the end index (exclusive). if it is negative, it limits the string's length: `"1234".substring(1, -2)` returns `"23"`.
|
||||
|
||||
### replace
|
||||
|
||||
replaces occurences of arg1 in arg0 with arg2
|
||||
|
||||
### regex
|
||||
|
||||
given a regex (in string form), this function returns either `Err(string)` or a function which, when called with another string, returns a list of matches found in that string:
|
||||
|
||||
lines_regex := regex(".*").assume_no_enum()
|
||||
fn lines(s string) {
|
||||
lines_regex.run(s)
|
||||
}
|
||||
debug("a string\nwith multiple\nlines!".lines())
|
||||
|
||||
This is done because compiling regex is somewhat slow, so if multiple strings have to be searched by the regex,
|
||||
it would be inefficient to recompile the regex every time. (btw: credit goes to the Regex crate, which is used to implement this)
|
||||
|
||||
### split
|
||||
|
||||
given two strings, splits the first one at the pattern specified by the second one:
|
||||
|
||||
word_count := "a string containing five words".split(" ").len()
|
||||
230
old/docs/intro.md
Executable file
230
old/docs/intro.md
Executable file
@@ -0,0 +1,230 @@
|
||||
# Hello, world!
|
||||
|
||||
Welcome to mers! Start by creating a file for your mers code:
|
||||
|
||||
println("Hello, world!")
|
||||
|
||||
now run it: `mers <file>` (replace mers with the location of the compiled executable if it's not in your PATH)
|
||||
|
||||
# The basics
|
||||
|
||||
To write a comment, type `//`. From here until the end of the line, whatever you write will be ignored completely.
|
||||
To write a comment that spans multiple lines or ends before the end of the line, put your text between `/*` and `*/`.
|
||||
|
||||
if 10 != 15 /* pretend this actually meant something... */ {
|
||||
// does something
|
||||
} else {
|
||||
// do something else
|
||||
}
|
||||
|
||||
To declare a new variable, use `:=`.
|
||||
|
||||
println("Hello! Please enter your name.")
|
||||
username := read_line()
|
||||
println("Hello, " + username)
|
||||
|
||||
To change an existing variable, prefix it with `&` and use `=` instead of `:=`.
|
||||
|
||||
username := "<unknown>"
|
||||
println("Hello! Please enter your name.")
|
||||
&username = read_line()
|
||||
println("Hello, " + username)
|
||||
|
||||
To call a function, write `function_name(argument1, argument2, argument3, ...)` with as many arguments as you need:
|
||||
|
||||
add(10, 25)
|
||||
|
||||
To avoid too many nested function calls, you can also put the first argument in front of the function and have a dot behind it:
|
||||
|
||||
10.add(25)
|
||||
|
||||
The following two lines are identical:
|
||||
|
||||
println(to_string(add(10, 25)))
|
||||
10.add(25).to_string().println()
|
||||
|
||||
# Conditions - If/Else
|
||||
|
||||
An `if`-statement is used to only do something if a condition is met.
|
||||
In mers, no brackets are necessary to do this.
|
||||
You can just write `if <any condition> <what to do>`.
|
||||
|
||||
println("Hello! Please enter your name.")
|
||||
username := read_line()
|
||||
if username.len() > 0 {
|
||||
println("Hello, " + username)
|
||||
}
|
||||
|
||||
You may have noticed that the `{` and `}` shown in the example weren't mentioned before.
|
||||
That's because they are totally optional! If you only want to do one thing, like the `println` in our case, you can leave them out. However, this can get messy very quickly.
|
||||
|
||||
println("Hello! Please enter your name.")
|
||||
username := read_line()
|
||||
if username.len() > 0
|
||||
println("Hello, " + username)
|
||||
|
||||
The indentation here also isn't required - mers doesn't care about indentation. A single space is as good as any number of spaces, tabs, or newline characters.
|
||||
|
||||
By adding `else <what to do>` after an if statement, we can define what should happen if the condition isn't met:
|
||||
|
||||
println("Hello! Please enter your name.")
|
||||
username := read_line()
|
||||
if username.len() > 0 {
|
||||
println("Hello, " + username)
|
||||
} else {
|
||||
println("Hello!")
|
||||
}
|
||||
|
||||
# Loops - Loop
|
||||
|
||||
Let's force the user to give us their name.
|
||||
|
||||
For this, we'll do what any not-insane person would do: Ask them repeatedly until they answer.
|
||||
|
||||
username := ""
|
||||
loop {
|
||||
println("Hello! Please enter your name.")
|
||||
&username = read_line()
|
||||
if username.len() > 0 {
|
||||
true
|
||||
}
|
||||
}
|
||||
println("Hello, " + username)
|
||||
|
||||
The most obvious thing that stands out here is `loop`. This just repeatedly runs its statement.
|
||||
Since mers doesn't have `break`s or `return`s, we need a different way to exit the loop.
|
||||
This is done by returning `true` from the block, which is exactly what the if statement is doing.
|
||||
|
||||
We can simplify this by just removing the `if` entirely - if `username.len() > 0` returns `true`, we break from the loop, if it returns `false` we don't.
|
||||
|
||||
username := ""
|
||||
loop {
|
||||
println("Hello! Please enter your name.")
|
||||
&username = read_line()
|
||||
username.len() > 0
|
||||
}
|
||||
println("Hello, " + username)
|
||||
|
||||
There is one more thing here that I would like to change: We assign a default value to `username`, in this case an empty string `""`.
|
||||
Default values like this one can lead to all kinds of unexpected behavior and, in my opinion, should be avoided whenever possible.
|
||||
Luckily, mers' `loop` can actually return something - specifically, it returns the `match` of the inner statement, if there is one.
|
||||
Sounds quite abstract, but is very simple once you understand matching.
|
||||
|
||||
# Matching
|
||||
|
||||
In mers, some values match and some don't.
|
||||
This is often used for advanced conditions, but we also need it to break from a loop.
|
||||
|
||||
- Values that don't match are an empty tuple `[]`, `false`, and enums.
|
||||
- Tuples of length 1 match with the value contained in them: `[4]` becomes `4`.
|
||||
- Other values match and don't change: `true` becomes `true`, `"some text"` becomes `"some text"`.
|
||||
|
||||
# Loops... again
|
||||
|
||||
The actual reason the loop stops once we return `true` is because `true` is a value that matches.
|
||||
We didn't do anything with it, but the loop actually returned `true` in the previous example.
|
||||
Since the value we want to get out of the loop is the `username`, not just `true`, we have to return the username from the loop:
|
||||
|
||||
username := loop {
|
||||
println("Hello! Please enter your name.")
|
||||
input := read_line()
|
||||
if input.len() > 0 {
|
||||
input
|
||||
}
|
||||
}
|
||||
println("Hello, " + username)
|
||||
|
||||
If the input isn't empty, we return it from the loop since a value of type `string` will match. The value is then assigned to `username` and printed.
|
||||
|
||||
If the input was still empty, `input.len() > 0` will return `false`, causing the `if` statement to return `[]`, which doesn't match, so the loop will continue.
|
||||
|
||||
# Match statements
|
||||
|
||||
Match statements let you define multiple conditions in a row.
|
||||
Their superpower is that they use matching, while `if` statements just use `bool`s (`true` and `false`).
|
||||
One of my favorite examples for mers' strength is this:
|
||||
|
||||
input := read_line()
|
||||
number := match {
|
||||
input.parse_int() n n
|
||||
input.parse_float() n n
|
||||
[true] [] Err: "couldn't parse"
|
||||
}
|
||||
number.debug()
|
||||
|
||||
Unfortunately, this needs quite a lengthy explanation.
|
||||
|
||||
First, `parse_int()` and `parse_float()`. These are functions that take a string as their argument and return `[]/int` or `[]/float`.
|
||||
They try to read a number from the text and return it. If this fails, they return `[]`.
|
||||
|
||||
Conveniently, `[]` doesn't match while `int` and `float` values do.
|
||||
|
||||
This is where the magic happens: the `match` statement.
|
||||
Between the `{` and the `}`, you can put as many "match arms" as you want.
|
||||
Each of these arms consists of three statements: the condition, something to assign the value to, and an action.
|
||||
|
||||
Let's look at the match arm `input.parse_int() n n`.
|
||||
The three statements here are `input.parse_int()` (condition), `n` (assign_to), and `n` (action).
|
||||
|
||||
If the input isn't a number, `input.parse_int()` will return `[]`. Since this doesn't match, the second match arm (`input.parse_float()`) will try to parse it to a float instead.
|
||||
|
||||
If the input is a number, `input.parse_int()` will return an `int`. Since this matches, the match arm will be executed.
|
||||
First, the matched value - the `int` - will be assigned to `n`. the assign_to part behaves like the left side of a `:=` expression (it supports destructuring and will declare new variables).
|
||||
Finally, the action statement uses our new variable `n` which contains the number we have parsed and returns it from the match statement.
|
||||
|
||||
Since the two arms in the match statement return `int` and `float`, the match statement will return `int/float/[]`.
|
||||
To get rid of the `[]`, we need to add a third arm: `[true] [] Err: "couldn't parse"`. `[true]` is a value that the compiler knows will always match - a tuple of length 1. Assigning something to an empty tuple `[]` just gets rid of the value.
|
||||
The return type is now `int/float/Err(string)`.
|
||||
|
||||
Finally, we `debug()` the variable. Debug is a builtin function that prints the expected type (statically determined at compile-time), the actual type, and the value.
|
||||
If we input `12.3`, it outputs `int/float/Err(string) :: float :: 12.3`.
|
||||
If we input `9`, it outputs `int/float/Err(string) :: int :: 9`.
|
||||
|
||||
# Switch statements
|
||||
|
||||
As demonstrated in the previous example, variables (and all values) in mers can have a type that actually represents a list of possible types.
|
||||
If you wish to filter the types, you can use the `switch` statement.
|
||||
|
||||
For example, here we only print "Number: _" if x is actually a number.
|
||||
|
||||
x := if true 10 else "text"
|
||||
switch x {
|
||||
int num println("Number: " + num.to_string())
|
||||
}
|
||||
|
||||
In most cases, you should use `switch!` instead of `switch`.
|
||||
This simply forces you to handle all possible types `x` could have:
|
||||
|
||||
x := if true 10 else "text"
|
||||
switch! x {
|
||||
int num println("Number: " + num.to_string())
|
||||
}
|
||||
|
||||
After adding the `!`, mers will refuse to compile:
|
||||
|
||||
> Switch! statement, but not all types covered. Types to cover: string
|
||||
|
||||
To fix this, we have to cover the `string` type:
|
||||
|
||||
|
||||
x := if true 10 else "text"
|
||||
switch! x {
|
||||
int num println("Number: " + num.to_string())
|
||||
string s println("String: " + s)
|
||||
}
|
||||
|
||||
We have now covered every possible type `x` can have, and mers happily accepts the `!`.
|
||||
By adding the `!`, mers will not add `[]` to the switch statement's output type, since one of the arms will always be executed and provide some value that eliminates the need for a `[]` fallback.
|
||||
|
||||
# Loops - For
|
||||
|
||||
Mers also has a for loop. The syntax for it is `for <assign_to> <iterator> <what_to_do>`, for example `for number [1, 2, 4, 8, 16, ...] { println("Number: " + number.to_string()) }`.
|
||||
The `...]` indicates that this is a list instead of a tuple. In this case, it doesn't make a difference, but lists are more common in for loops which is why this is what the example uses.
|
||||
|
||||
Just like normal `loop`s, the for loop will exit if `<what_to_do>` returns a value that matches.
|
||||
|
||||
If you want custom iterators, all you need is a function that takes no arguments and returns any value. If the returned value matches, it is assigned and the loop will run. If it doesn't match, the loop will exit.
|
||||
|
||||
# END
|
||||
|
||||
The best way to learn about mers is to use it. If you get stuck, a look at the examples or the syntax cheat sheet may help. Good luck, have fun!
|
||||
155
old/docs/statements.md
Executable file
155
old/docs/statements.md
Executable file
@@ -0,0 +1,155 @@
|
||||
# Mers statements overview
|
||||
|
||||
This is the documentation for mers statements.
|
||||
In code, statements are represented by `SStatement`, `SStatementEnum`, `RStatement`, and `RStatementEnum`.
|
||||
|
||||
## Statement prefixes
|
||||
|
||||
A statement can be prefixed with any number of stars `*`. This is called dereferencing and turns a reference to a value into the value itself. Modifying the value after a dereference leaves the value the reference was pointing to untouched (the data will be cloned to achieve this).
|
||||
|
||||
A statement can also be prefixed with an arrow followed by the type the statement will have: `-> int/float 12`.
|
||||
Although the statement will always return an `int`, mers will assume that it could also return a float.
|
||||
If the statement's output doesn't fit in the forced type (`-> int "text"`), mers will generate an error.
|
||||
In combination with functions, this is similar to rust's syntax:
|
||||
|
||||
fn my_func(a int, b int) -> int {
|
||||
a + b
|
||||
}
|
||||
|
||||
## Assignment
|
||||
|
||||
Statements can assign their value instead of returning it.
|
||||
The syntax for this is `<ref_statement> = <statement>` or `<assign_to> := <statement>`.
|
||||
|
||||
If just `=` is used, the left side must return a reference to some value which will then be changed to the value generated on the right.
|
||||
If `:=` is used, new variables can be declared on the left.
|
||||
|
||||
Destructuring is possible too: `[a, b] := [12, 15]`.
|
||||
|
||||
# Different statements
|
||||
|
||||
## Value
|
||||
|
||||
A statement that always returns a certain value: `10`, `"Hello, World"`, ...
|
||||
|
||||
## Code block
|
||||
|
||||
Code blocks are a single statement, but they can contain any number of statements.
|
||||
They have their own scope, so local variables declared in them aren't accessible from outside.
|
||||
|
||||
They return whatever the last statement they contain returns. Empty code blocks (ones without any statements: `{}`) return `[]`.
|
||||
|
||||
list := [1, 2, 3, 4 ...]
|
||||
sum := {
|
||||
// a temporary variable that only exists in this block
|
||||
counter := 0
|
||||
for elem list {
|
||||
&counter = counter + elem
|
||||
}
|
||||
// the final value will be returned from the block and then assigned to sum.
|
||||
counter
|
||||
}
|
||||
|
||||
## Tuple and list
|
||||
|
||||
These statements contain any number of statements and return the returned values in order as a tuple or list.
|
||||
|
||||
// x is a tuple and has type [int string float]
|
||||
x := [
|
||||
get_num(),
|
||||
get_text(),
|
||||
12.5
|
||||
]
|
||||
// y is a list and has type [int/string/float ...]
|
||||
x := [
|
||||
get_num(),
|
||||
get_text(),
|
||||
12.5
|
||||
...]
|
||||
|
||||
## Variable
|
||||
|
||||
These statements retrieve the value from a variable name:
|
||||
|
||||
x := "my value"
|
||||
println(
|
||||
x // <- this is a variable statement - it gets the "my value" from above so the println function can use it.
|
||||
)
|
||||
|
||||
They can also get references if the variable name is prefixed with the `&` symbol:
|
||||
|
||||
debug(&x) // type is &string
|
||||
|
||||
This is why `&` is required to assign to a variable:
|
||||
|
||||
x = "something else" // can't assign to string!
|
||||
&x = "something else" // only data behind references can be changed - we can assign to &string.
|
||||
|
||||
## Function call
|
||||
|
||||
Function calls give some data to a function and return the result from that function.
|
||||
|
||||
There are 3 kinds of function calls:
|
||||
|
||||
- calls to builtin functions
|
||||
- calls to library functions
|
||||
- calls to custom functions
|
||||
|
||||
Anonymous functions are called using the builtin `run` function.
|
||||
|
||||
## If statement
|
||||
|
||||
Allow for conditional execution of code paths:
|
||||
|
||||
if condition() {
|
||||
// do something
|
||||
}
|
||||
|
||||
if condition() {
|
||||
// do something
|
||||
} else {
|
||||
// do something else
|
||||
}
|
||||
|
||||
## Loop
|
||||
|
||||
Executes code repeatedly:
|
||||
|
||||
counter := 0
|
||||
loop {
|
||||
&counter = counter + 1
|
||||
println("c: " + counter.to_string())
|
||||
}
|
||||
|
||||
## For loop
|
||||
|
||||
## Switch
|
||||
|
||||
## Match
|
||||
|
||||
## Enum
|
||||
|
||||
Wrap a value in an enum: `EnumName: <statement>`
|
||||
|
||||
# dot-chains
|
||||
|
||||
Statements can be followed by the dot `.` character. This has different meanings depending on what comes after the dot:
|
||||
|
||||
## A function call
|
||||
|
||||
In this case, the statement before the dot is prepended to the function arguments:
|
||||
|
||||
a.func(b, c)
|
||||
func(a, b, c)
|
||||
|
||||
## An integer
|
||||
|
||||
Gets the n-th thing from a tuple (uses zero-based indexing):
|
||||
|
||||
[1, "2", 3.0].1 == "2"
|
||||
|
||||
If the value before the dot is a reference to a tuple, a reference to the thing inside the tuple will be obtained:
|
||||
|
||||
x := [1, "2", 3.0]
|
||||
// we assign to the "2", which changes it.
|
||||
&x.1 = "4"
|
||||
141
old/docs/syntax_cheat_sheet.md
Executable file
141
old/docs/syntax_cheat_sheet.md
Executable file
@@ -0,0 +1,141 @@
|
||||
# mers syntax cheat sheet
|
||||
|
||||
## Types
|
||||
|
||||
- `bool`
|
||||
- `int`
|
||||
- `float`
|
||||
- `string`
|
||||
- tuple: `[<type1> <type2> <type3> <...>]`
|
||||
- list: `[<type> ...]`
|
||||
- function: `fn(<input-output-map>)` (might change? depends on implementation of generics)
|
||||
- thread: `thread(<return_type>)`
|
||||
- reference: `&<type>` or `&(<type1>/<type2>/<...>)`
|
||||
- enum: `EnumName(<type>)`
|
||||
- one of multiple types: `<type1>/<type2>/<type3>`
|
||||
|
||||
## Values
|
||||
|
||||
- bool
|
||||
+ `true` or `false`
|
||||
- int
|
||||
+ decimal: `2`, `+2`, `-5`, `0`, ...
|
||||
- float
|
||||
+ decimal: `1.5`, `0.5`, `-5.2`, `2.0`, ... (this is recommended for compatability and clarity)
|
||||
+ whole numbers: `1.0`, `1.`, ... (may break with future updates)
|
||||
+ numbers from 0 to 1: `.5` would be ambiguous following tuples, so is not supported.
|
||||
- string
|
||||
+ `"my string"` (double quotes)
|
||||
+ `"it's called \"<insert name>\""` (escape inner double quotes with backslash)
|
||||
+ all escape sequences
|
||||
* `\"` double quote character
|
||||
* `\\` backslash character
|
||||
* `\n` newline
|
||||
* `\r` carriage return
|
||||
* `\t` tab
|
||||
* `\0` null
|
||||
- tuple
|
||||
+ `[<val1> <val2> <val3> <...>]`
|
||||
- list
|
||||
+ `[<val1> <val2> <val3> <...> ...]` (like tuple, but `...]` instead of `]`)
|
||||
- function
|
||||
+ `(<arg1> <arg2> <arg3> <...>) <statement>` where `<argn>` is `<namen> <typen>`.
|
||||
- thread
|
||||
+ returned by the builtin function `thread()`
|
||||
- reference
|
||||
+ to a variable: `&<varname>`
|
||||
+ to something else: usually using `get()` or its equivalent on a reference to a container instead of the container itself: `&list.get()` instead of `list.get()`
|
||||
- enum
|
||||
+ `<EnumName>: <statement>`
|
||||
|
||||
## Variables
|
||||
|
||||
- declaration and initialization
|
||||
+ `<var_name> := <statement>`
|
||||
+ can shadow previous variables with the same name: `x := 5 { x := 10 debug(x) } debug(x)` prints `10` and then `5`.
|
||||
- assignment
|
||||
+ `&<var_name> = <statement>`
|
||||
* modifies the value: `x := 5 { &x = 10 debug(x) } debug(x)` prints `10` and then `10`.
|
||||
+ `<statement_left> = <statement_right>`
|
||||
* assigns the value returned by `<statement_right>` to the value behind the reference returned by `<statement_left>`.
|
||||
* if `<statement_right>` returns `<type>`, `<statement_left>` has to return `&<type>`.
|
||||
* this is why `&<var_name> = <statement>` is the way it is.
|
||||
+ `***<statement_left> = <statement_right>`
|
||||
* same as before, but performs dereferences: `&&&&int` becomes `&int` (minus 3 references because 3 `*`s), so a value of type `int` can be assigned to it.
|
||||
- destructuring
|
||||
+ values can be destructured into tuples or lists (as of right now):
|
||||
+ `[a, b] := [10, "some text"]` is effectively the same as `a := 10, b := "some text"`
|
||||
|
||||
## Statements
|
||||
|
||||
- value
|
||||
+ `10`, `true`, `"text"`, `"[]"`, etc
|
||||
- tuple
|
||||
+ `[<statement1> <statement2> <...>]`
|
||||
- list
|
||||
+ `[<statement1> <statement2> <...> ...]`
|
||||
- variable
|
||||
+ `<var_name>` (if the name of the variable isn't a value or some other kind of statement)
|
||||
- function call
|
||||
+ `<fn_name>(<arg1> <arg2> <...>)`
|
||||
+ `<fn_name>(<arg1>, <arg2>, <...>)`
|
||||
+ `<arg1>.<fn_name>(<arg2>, <...>)`
|
||||
- function definition
|
||||
+ `fn <fn_name>(<arg1> <arg2> <...>) <statement>` where `<argn>` is `<namen> <typen>`
|
||||
- block
|
||||
+ `{ <statement1> <statement2> <...> }`
|
||||
- if
|
||||
+ `if <condition> <statement>` (`if true println("test")`)
|
||||
+ `if <condition> <statement> else <statement>` (`if false println("test") else println("value was false")`)
|
||||
- loop
|
||||
+ `loop <statement>`
|
||||
+ if the statement returns a value that matches, the loop will end and return the matched value
|
||||
+ the loop's return type is the match of the return type of the statement
|
||||
- for loop
|
||||
+ `for <assign_to> <iterator> <statement>`
|
||||
+ in each iteration, the variable will be initialized with a value from the iterator.
|
||||
+ iterators can be lists, tuples, or functions.
|
||||
+ for function iterators, as long as the returned value matches, the matched value will be used. if the value doesn't match, the loop ends.
|
||||
+ if the statement returns a value that matches, the loop will end and return the matched value
|
||||
+ the loop's return type is the match of the return type of the statement or `[]` (if the loop ends because the iterator ended)
|
||||
- switch
|
||||
+ `switch <value> { <arm1> <arm2> <...> }`
|
||||
* where `<armn>` is `<typen> <assign_to> <statementn>`
|
||||
* if the variable's value is of type `<typen>`, `<statementn>` will be executed with `<value>` assigned to `<assign_to>`.
|
||||
* if the variables type is included multiple times, only the first match will be executed.
|
||||
* within the statement of an arm, the variables type is that specified in `<typen>`, and not its original type (which may be too broad to work with)
|
||||
+ `switch! <var_name> { <arm1> <arm2> <...> }`
|
||||
* same as above, but all types the variable could have must be covered
|
||||
* the additional `[]` in the return type isn't added here since it is impossible not to run the statement of one of the arms.
|
||||
- match
|
||||
+ `match { <arm1> <arm2> <...> }`
|
||||
* where `<armn>` is `<(condition) statement> <assign_to> <(action) statement>`
|
||||
* each arm has a condition statement, something that the matched value will be assigned to, and an action statement.
|
||||
* if the value returned by the condition statement matches, the matched value is assigned to `<assign_to>` and the action statement is executed.
|
||||
* only the first matching arm will be executed. if no arm was executed, `[]` is returned.
|
||||
- fixed-indexing
|
||||
+ `<statement>.n` where `n` is a fixed number (not a variable, just `0`, `1`, `2`, ...)
|
||||
+ `<statement>` must return a tuple or a reference to one. `<statement>.n` then refers to the nth value in that tuple.
|
||||
+ for references to a tuple, references to the inner values are returned.
|
||||
- enum
|
||||
+ `<EnumName>: <statement>`
|
||||
- type definition
|
||||
+ `type <name> <type>` (`type Message [string string]`)
|
||||
- macros
|
||||
+ `!(<macro_type> <...>)`
|
||||
+ `!(mers { <code> })`
|
||||
* compiles and runs the code at compile-time, then returns the computed value at runtime.
|
||||
+ `!(mers <file_path>)` or `!(mers "<file path with spaces and \" double quotes>")`
|
||||
* same as above, but reads code from a file instead
|
||||
* path can be relative
|
||||
|
||||
## Matching
|
||||
|
||||
- values that don't match
|
||||
+ `[]`
|
||||
+ `false`
|
||||
- values that match
|
||||
+ `[v]` -> `v`
|
||||
+ `v` -> `v` unless otherwise specified
|
||||
- invalid
|
||||
+ `[v1 v2]` or any tuple whose length isn't `0` or `1`
|
||||
28
old/examples/amogus.mers
Executable file
28
old/examples/amogus.mers
Executable file
File diff suppressed because one or more lines are too long
19
old/examples/custom_type.mers
Executable file
19
old/examples/custom_type.mers
Executable file
@@ -0,0 +1,19 @@
|
||||
// linked list
|
||||
type elem [int []/elem]
|
||||
|
||||
fn print_linked_list(start elem) {
|
||||
loop {
|
||||
println(start.0.to_string())
|
||||
elem := start.1
|
||||
switch! elem {
|
||||
[] elem {
|
||||
println("[END]")
|
||||
true // break
|
||||
}
|
||||
elem elem &start = elem // continue
|
||||
}
|
||||
}
|
||||
[]
|
||||
}
|
||||
|
||||
[1 [2 [3 [5 [7 []]]]]].print_linked_list()
|
||||
12
old/examples/destructuring_assignment.mers
Executable file
12
old/examples/destructuring_assignment.mers
Executable file
@@ -0,0 +1,12 @@
|
||||
x := "https:\//www.google.com"
|
||||
|
||||
x.debug()
|
||||
|
||||
[a, [b, c]] := [1, ["str", 12.5]]
|
||||
|
||||
println("---")
|
||||
println(a.to_string() + " " + b + " " + c.to_string())
|
||||
println("~~~")
|
||||
|
||||
// switch! run_command("curl", [x, ...]) {
|
||||
// }
|
||||
3
old/examples/force_output_type.mers
Executable file
3
old/examples/force_output_type.mers
Executable file
@@ -0,0 +1,3 @@
|
||||
fn plus(a int, b int) -> int { a + b }
|
||||
|
||||
10.plus(20).debug()
|
||||
27
old/examples/functions_double_definitions.mers
Executable file
27
old/examples/functions_double_definitions.mers
Executable file
@@ -0,0 +1,27 @@
|
||||
type person [string int]
|
||||
fn name(p person/&person) p.0
|
||||
fn age(p person/&person) p.1
|
||||
|
||||
type village [[float float] string]
|
||||
fn name(v village/&village) v.1
|
||||
fn location(v village/&village) v.0
|
||||
fn x_coordinate(v village/&village) v.0.0
|
||||
fn y_coordinate(v village/&village) v.0.1
|
||||
|
||||
|
||||
|
||||
customer := ["Max M.", 43]
|
||||
home_town := [[12.3, 5.09], "Maxburg"]
|
||||
|
||||
debug(customer)
|
||||
debug(customer.name())
|
||||
debug(&customer.name())
|
||||
|
||||
debug(home_town)
|
||||
debug(home_town.name())
|
||||
debug(home_town.location())
|
||||
|
||||
println(
|
||||
"Hello, " + customer.name()
|
||||
+ " from " + home_town.name()
|
||||
)
|
||||
12
old/examples/get_ref.mers
Executable file
12
old/examples/get_ref.mers
Executable file
@@ -0,0 +1,12 @@
|
||||
list := [1 2 3 4 5 6 7 8 9 ...]
|
||||
|
||||
// calling get on an &list will get a reference
|
||||
&list.get(2).assume1() = 24
|
||||
// calling get on a list will get a value
|
||||
should_not_be_changeable := list.get(3).assume1()
|
||||
&should_not_be_changeable = 24
|
||||
|
||||
if list.get(2) != [24] println("[!!] list.get(2) != 24 (was {0})".format(list.get(2).to_string()))
|
||||
if list.get(3) == [24] println("[!!] list.get(3) == 24")
|
||||
|
||||
list.debug()
|
||||
39
old/examples/iterators.mers
Executable file
39
old/examples/iterators.mers
Executable file
@@ -0,0 +1,39 @@
|
||||
fn map_string_to_int(list [string ...], func fn((string) int)) {
|
||||
// return a function that can be used as an iterator
|
||||
() {
|
||||
// this function will be called by the for loop
|
||||
// whenever it wants to retrieve the next item in the collection.
|
||||
// get the element
|
||||
elem := &list.remove(0)
|
||||
switch! elem {
|
||||
// if the element was present, run the map function to convert it from a string to an int and return the result
|
||||
[string] [elem] {
|
||||
[func.run(elem)]
|
||||
}
|
||||
// if there are no more elements, return something that doesn't match.
|
||||
[] [] []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for v ["1" "2" "5" "-10" ...].map_string_to_int((s string) s.parse_int().assume1("list contained strings that can't be parsed to an int!")) {
|
||||
debug(v)
|
||||
}
|
||||
|
||||
|
||||
// returns an iterator to iteratively compute square numbers
|
||||
// using the fact that (n+1)^2 = n^2 + 2n + 1
|
||||
fn square_numbers() {
|
||||
i := 0
|
||||
val := 0
|
||||
() {
|
||||
&val = val + { 2 * i } + 1
|
||||
&i = i + 1
|
||||
[val]
|
||||
}
|
||||
}
|
||||
|
||||
for n square_numbers() {
|
||||
println(n.to_string())
|
||||
n >= 100
|
||||
}
|
||||
8
old/examples/macro.mers
Executable file
8
old/examples/macro.mers
Executable file
@@ -0,0 +1,8 @@
|
||||
val := "some string"
|
||||
val := !(mers {
|
||||
println("macro is running now!")
|
||||
"macro returned value"
|
||||
})
|
||||
println(val)
|
||||
val := !(mers "my_macro.mers")
|
||||
println(val)
|
||||
10
old/examples/modify_variable.mers
Executable file
10
old/examples/modify_variable.mers
Executable file
@@ -0,0 +1,10 @@
|
||||
// NOTE: Might change, but this is the current state of things
|
||||
x := 10
|
||||
t := thread(() {
|
||||
sleep(0.25)
|
||||
println(x.to_string())
|
||||
})
|
||||
&x = 20 // -> 20 20 because it modifies the original variable x
|
||||
// x := 20 // -> 10 20 because it shadows the original variable x
|
||||
t.await()
|
||||
println(x.to_string())
|
||||
2
old/examples/my_macro.mers
Executable file
2
old/examples/my_macro.mers
Executable file
@@ -0,0 +1,2 @@
|
||||
println("my macro running in ./my_macro.mers")
|
||||
"value returned from ./my_macro.mers :)"
|
||||
19
old/examples/struct_fields.mers
Executable file
19
old/examples/struct_fields.mers
Executable file
@@ -0,0 +1,19 @@
|
||||
// mers doesn't have structs, so instead we define a type:
|
||||
type myStruct [
|
||||
int,
|
||||
string
|
||||
]
|
||||
// to give names to the fields, we define functions:
|
||||
fn count(s myStruct) s.0
|
||||
// to allow users to change the value, add &myStruct to the valid types for s (only through references can values be changed)
|
||||
fn note(s myStruct/&myStruct) s.1
|
||||
|
||||
my_struct := [12, "test"]
|
||||
|
||||
my_struct.count().debug()
|
||||
|
||||
my_struct.note().debug()
|
||||
|
||||
&my_struct.note() = "changed"
|
||||
|
||||
my_struct.note().debug()
|
||||
19
old/examples/switch_match.mers
Executable file
19
old/examples/switch_match.mers
Executable file
@@ -0,0 +1,19 @@
|
||||
x := if true [10 "my text"] else 10
|
||||
|
||||
switch! x {
|
||||
[int string] [num text] {
|
||||
num.debug()
|
||||
text.debug()
|
||||
}
|
||||
int num {
|
||||
println("number:")
|
||||
num.debug()
|
||||
}
|
||||
}
|
||||
|
||||
text := "12.5"
|
||||
match {
|
||||
parse_int(text) num println("int: " + num.to_string())
|
||||
parse_float(text) num println("float: " + num.to_string())
|
||||
[true] [] println("not a number: " + text)
|
||||
}
|
||||
13
old/examples/the_any_type.mers
Executable file
13
old/examples/the_any_type.mers
Executable file
@@ -0,0 +1,13 @@
|
||||
fn debug_any(val any) {
|
||||
val.debug()
|
||||
}
|
||||
|
||||
"something".debug_any()
|
||||
|
||||
fn just_return(val any) {
|
||||
val
|
||||
}.debug()
|
||||
|
||||
v := "text"
|
||||
v.debug()
|
||||
v.just_return().debug()
|
||||
36
old/examples/thread.mers
Executable file
36
old/examples/thread.mers
Executable file
@@ -0,0 +1,36 @@
|
||||
// this is true by default so the example doesn't finish too quickly or too slowly depending on your hardware.
|
||||
// you can set it to false and tweak the max value for a more authentic cpu-heavy workload.
|
||||
fake_delay := true
|
||||
|
||||
// this will be shared between the two threads to report the progress in percent (0-100%).
|
||||
progress := 0
|
||||
|
||||
// an anonymous function that sums all numbers from 0 to max.
|
||||
// it captures the progress variable and uses it to report its status to the main thread, which will periodically print the current progress.
|
||||
// once done, it returns a string with the sum of all numbers.
|
||||
calculator := (max int) {
|
||||
sum := 0
|
||||
for i max {
|
||||
i := i + 1
|
||||
// println("i: {0} s: {1}".format(i.to_string() sum.to_string()))
|
||||
&sum = sum + i
|
||||
if fake_delay sleep(1)
|
||||
&progress = i * 100 / max
|
||||
}
|
||||
"the sum of all numbers from 0 to {0} is {1}!".format(max.to_string() sum.to_string())
|
||||
}
|
||||
|
||||
// start the thread. if fake_delay is true, calculate 1+2+3+4+5+6+7+8+9+10. if fake_delay is false, count up to some ridiculously large number.
|
||||
slow_calculation_thread := calculator.thread(if fake_delay 10 else 20000000)
|
||||
|
||||
// every second, print the progress. once it reaches 100%, stop
|
||||
loop {
|
||||
sleep(1)
|
||||
println("{0}%".format(progress.to_string()))
|
||||
progress == 100 // break from the loop
|
||||
}
|
||||
|
||||
// use await() to get the result from the thread. if the thread is still running, this will block until the thread finishes.
|
||||
result := slow_calculation_thread.await()
|
||||
|
||||
println("Thread finished, result: {0}".format(result))
|
||||
1629
old/mers/Cargo.lock
generated
Executable file
1629
old/mers/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
26
old/mers/Cargo.toml
Executable file
26
old/mers/Cargo.toml
Executable file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "mers"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "mers_libs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
edit = "0.1.4"
|
||||
notify = "5.1.0"
|
||||
regex = "1.7.2"
|
||||
static_assertions = "1.1.0"
|
||||
nu-plugin = { version = "0.79.0", optional = true }
|
||||
nu-protocol = { version = "0.79.0", features = ["plugin"], optional = true }
|
||||
colorize = "0.1.0"
|
||||
|
||||
[features]
|
||||
# default = ["nushell_plugin"]
|
||||
nushell_plugin = ["dep:nu-plugin", "dep:nu-protocol"]
|
||||
|
||||
[profile.nushellplugin]
|
||||
inherits = "release"
|
||||
150
old/mers/src/inlib/mod.rs
Executable file
150
old/mers/src/inlib/mod.rs
Executable file
@@ -0,0 +1,150 @@
|
||||
use std::io::{Stdin, StdinLock, Write};
|
||||
|
||||
use crate::lang::val_type::VType;
|
||||
|
||||
use super::libs::{
|
||||
comms::{self, ByteData, ByteDataA, Message, RespondableMessage},
|
||||
LibInitInfo, LibInitReq,
|
||||
};
|
||||
|
||||
pub struct MyLib {
|
||||
// name: String,
|
||||
version: (u32, u32),
|
||||
// description: String,
|
||||
// functions: Vec<(String, Vec<VType>, VType)>,
|
||||
pub callbacks: Callbacks,
|
||||
enum_variants: Vec<(String, usize)>,
|
||||
stdin: StdinLock<'static>,
|
||||
#[allow(unused)]
|
||||
stdin_no_lock: Stdin,
|
||||
}
|
||||
impl MyLib {
|
||||
pub fn new(
|
||||
name: String,
|
||||
version: (u32, u32),
|
||||
description: String,
|
||||
functions: Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
) -> Self {
|
||||
let stdout_no_lock = std::io::stdout();
|
||||
let stdin_no_lock = std::io::stdin();
|
||||
let mut stdout = stdout_no_lock.lock();
|
||||
let mut stdin = stdin_no_lock.lock();
|
||||
// comms version
|
||||
stdout
|
||||
.write_all(1u128.as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
let init_req: LibInitReq = (version.0, version.1, name, description, functions);
|
||||
stdout
|
||||
.write_all(init_req.as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
let enum_variants = LibInitInfo::from_byte_data(&mut stdin).unwrap();
|
||||
Self {
|
||||
// name: name.clone(),
|
||||
version,
|
||||
// description: description.clone(),
|
||||
// functions: functions.clone(),
|
||||
callbacks: Callbacks::empty(),
|
||||
enum_variants,
|
||||
stdin,
|
||||
stdin_no_lock,
|
||||
}
|
||||
}
|
||||
pub fn get_enums(&self) -> &Vec<(String, usize)> {
|
||||
&self.enum_variants
|
||||
}
|
||||
fn get_one_msg(&mut self) -> Result<Result<(), Message>, std::io::Error> {
|
||||
let id = u128::from_byte_data(&mut self.stdin)?;
|
||||
let message = Message::from_byte_data(&mut self.stdin)?;
|
||||
Ok(match message {
|
||||
Message::RunFunction(msg) => self
|
||||
.callbacks
|
||||
.run_function
|
||||
.run(Respondable::new(id, msg))
|
||||
.map_err(|e| Message::RunFunction(e.msg)),
|
||||
})
|
||||
}
|
||||
pub fn get_next_unhandled_message(&mut self) -> Result<(), Message> {
|
||||
loop {
|
||||
match self.get_one_msg() {
|
||||
Ok(Ok(())) => {}
|
||||
// unhandled message. return it to be handeled or included in the error
|
||||
Ok(Err(msg)) => return Err(msg),
|
||||
// i/o error, probably because mers exited. return successfully.
|
||||
Err(_e) => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Respondable<M> {
|
||||
id: u128,
|
||||
pub msg: M,
|
||||
}
|
||||
impl<M> Respondable<M> {
|
||||
fn new(id: u128, msg: M) -> Self {
|
||||
Self { id, msg }
|
||||
}
|
||||
}
|
||||
impl<M> Respondable<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub fn respond(self, with: M::With) {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout.write_all(&self.id.as_byte_data_vec()).unwrap();
|
||||
stdout
|
||||
.write_all(&self.msg.respond(with).as_byte_data_vec())
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
}
|
||||
impl<M> Respondable<M>
|
||||
where
|
||||
M: Into<Message>,
|
||||
{
|
||||
pub fn to_general(self) -> Respondable<Message> {
|
||||
Respondable::new(self.id, self.msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Callbacks {
|
||||
pub run_function: Callback<comms::run_function::Message>,
|
||||
}
|
||||
impl Callbacks {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
run_function: Callback::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct Callback<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub nonconsuming: Vec<Box<dyn FnMut(&M)>>,
|
||||
pub consuming: Option<Box<dyn FnMut(Respondable<M>)>>,
|
||||
}
|
||||
impl<M> Callback<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
nonconsuming: vec![],
|
||||
consuming: None,
|
||||
}
|
||||
}
|
||||
/// If the event was handled by a consuming function, returns Ok(r) where r is the returned value from the consuming function.
|
||||
/// If it wasn't handled (or only handled by nonconsuming functions), Err(m) is returned, giving ownership of the original message back to the caller for further handling.
|
||||
pub fn run(&mut self, msg: Respondable<M>) -> Result<(), Respondable<M>> {
|
||||
for f in self.nonconsuming.iter_mut() {
|
||||
f(&msg.msg);
|
||||
}
|
||||
if let Some(f) = self.consuming.as_mut() {
|
||||
Ok(f(msg))
|
||||
} else {
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
132
old/mers/src/interactive_mode.rs
Executable file
132
old/mers/src/interactive_mode.rs
Executable file
@@ -0,0 +1,132 @@
|
||||
/// creates a temporary file, then opens it in the user's default editor. watches the fs for changes to the file and acts upon them.
|
||||
pub mod fs_watcher {
|
||||
use notify::Watcher;
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use crate::lang::fmtgs::FormatGs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
/// on each file change, recompiles and runs the code. lets people experiment with mers without having to put a file anywhere
|
||||
pub fn playground(spawn_new_terminal_for_editor: bool) -> Result<(), Error> {
|
||||
main(
|
||||
spawn_new_terminal_for_editor,
|
||||
"// Welcome to mers! (interactive mode)
|
||||
|
||||
// put your name here, then save the file to run the script.
|
||||
your_name = \"\"
|
||||
greeting = \"Hello, {0}!\".format(your_name)
|
||||
println(greeting)
|
||||
",
|
||||
Box::new(|temp_file: &PathBuf| {
|
||||
println!();
|
||||
if let Ok(file_contents) = fs::read_to_string(&temp_file) {
|
||||
let mut file =
|
||||
crate::parsing::file::File::new(file_contents, temp_file.to_path_buf());
|
||||
match crate::parsing::parse::parse(&mut file) {
|
||||
Ok(func) => {
|
||||
println!(" - - - - -");
|
||||
let output = func.run(vec![]);
|
||||
println!(" - - - - -");
|
||||
println!("{}", output);
|
||||
}
|
||||
Err(e) => println!("{}", e.with_file(&file)),
|
||||
}
|
||||
} else {
|
||||
println!("can't read file at {:?}!", temp_file);
|
||||
std::process::exit(105);
|
||||
}
|
||||
}),
|
||||
)?
|
||||
.0
|
||||
.join()
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// when the file is changed, calls the provided closure with the file's path.
|
||||
/// returns a JoinHandle which will finish when the user closes the editor and the file that is being used.
|
||||
pub fn main(
|
||||
spawn_new_terminal_for_editor: bool,
|
||||
initial_file_contents: &str,
|
||||
mut on_file_change: Box<dyn FnMut(&PathBuf) + Send>,
|
||||
) -> Result<(JoinHandle<()>, PathBuf), Error> {
|
||||
let temp_file_edit = edit::Builder::new().suffix(".mers").tempfile().unwrap();
|
||||
let temp_file = temp_file_edit.path().to_path_buf();
|
||||
eprintln!(
|
||||
"Using temporary file at {temp_file:?}. Save the file to update the output here."
|
||||
);
|
||||
if let Ok(_) = std::fs::write(&temp_file, initial_file_contents) {
|
||||
if let Ok(mut watcher) = {
|
||||
let temp_file = temp_file.clone();
|
||||
// the file watcher
|
||||
notify::recommended_watcher(move |event: Result<notify::Event, notify::Error>| {
|
||||
if let Ok(event) = event {
|
||||
match &event.kind {
|
||||
notify::EventKind::Modify(notify::event::ModifyKind::Data(_)) => {
|
||||
on_file_change(&temp_file);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
})
|
||||
} {
|
||||
if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) {
|
||||
let out = if spawn_new_terminal_for_editor {
|
||||
if let Ok(term) = std::env::var("TERM") {
|
||||
let editor = edit::get_editor().unwrap();
|
||||
eprintln!("launching \"{term} -e {editor:?} {temp_file:?}...");
|
||||
let mut editor = std::process::Command::new(term)
|
||||
.arg("-e")
|
||||
.arg(&editor)
|
||||
.arg(&temp_file)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
(
|
||||
thread::spawn(move || {
|
||||
// wait for the editor to finish
|
||||
editor.wait().unwrap();
|
||||
// stop the watcher (this is absolutely necessary because it also moves the watcher into the closure,
|
||||
// which prevents it from being dropped too early)
|
||||
drop(watcher);
|
||||
// close and remove the temporary file
|
||||
temp_file_edit.close().unwrap();
|
||||
}),
|
||||
temp_file,
|
||||
)
|
||||
} else {
|
||||
return Err(Error(format!("TERM environment variable not set.")));
|
||||
}
|
||||
} else {
|
||||
let tf = temp_file.clone();
|
||||
(
|
||||
thread::spawn(move || {
|
||||
edit::edit_file(temp_file).unwrap();
|
||||
drop(watcher);
|
||||
temp_file_edit.close().unwrap();
|
||||
}),
|
||||
tf,
|
||||
)
|
||||
};
|
||||
Ok(out)
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Cannot watch the file at \"{:?}\" for hot-reload.",
|
||||
temp_file
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Cannot use filesystem watcher for hot-reload."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Error(format!("could not write file \"{:?}\".", temp_file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
1770
old/mers/src/lang/builtins.rs
Executable file
1770
old/mers/src/lang/builtins.rs
Executable file
File diff suppressed because it is too large
Load Diff
117
old/mers/src/lang/code_macro.rs
Executable file
117
old/mers/src/lang/code_macro.rs
Executable file
@@ -0,0 +1,117 @@
|
||||
use std::{fmt::Display, fs};
|
||||
|
||||
use crate::parsing::{
|
||||
file::File,
|
||||
parse::{self, ParseError, ScriptError},
|
||||
};
|
||||
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::{code_runnable::RScript, val_data::VData};
|
||||
|
||||
// macro format is !(macro_type [...])
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Macro {
|
||||
/// Compiles and executes the provided mers code at compile-time and inserts the value
|
||||
StaticMers(VData),
|
||||
}
|
||||
|
||||
pub fn parse_macro(file: &mut File) -> Result<Macro, MacroError> {
|
||||
file.skip_whitespaces();
|
||||
let macro_type = file.collect_to_whitespace();
|
||||
Ok(match macro_type.as_str() {
|
||||
"mers" => Macro::StaticMers({
|
||||
let code = parse_mers_code(file)?;
|
||||
let mut args = vec![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if let Some(')') = file.peek() {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
args.push(parse_string_val(file));
|
||||
}
|
||||
let val = code.run(
|
||||
args.into_iter()
|
||||
.map(|v| VDataEnum::String(v).to())
|
||||
.collect(),
|
||||
);
|
||||
if val.safe_to_share() {
|
||||
val
|
||||
} else {
|
||||
return Err(MacroError::StaticValueNotSafeToShare);
|
||||
}
|
||||
}),
|
||||
_ => return Err(MacroError::UnknownMacroType(macro_type)),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_string_val(file: &mut File) -> String {
|
||||
parse::implementation::parse_string_val(file, |ch| ch.is_whitespace() || ch == ')')
|
||||
}
|
||||
|
||||
fn parse_mers_code(file: &mut File) -> Result<RScript, MacroError> {
|
||||
file.skip_whitespaces();
|
||||
if let Some('{') = file.peek() {
|
||||
_ = file.next();
|
||||
match parse::parse(file) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(e.err.into()),
|
||||
}
|
||||
} else {
|
||||
let path = parse_string_val(file);
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("macro: mers: path: {path}");
|
||||
let path = crate::pathutil::path_from_string(path.as_str(), file.path(), false)
|
||||
.expect("can't include mers code because no file was found at that path");
|
||||
let mut file = File::new(
|
||||
fs::read_to_string(&path)
|
||||
.expect("can't include mers code because the file could not be read"),
|
||||
path.into(),
|
||||
);
|
||||
Ok(match parse::parse(&mut file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(e.err.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Macro {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::StaticMers(v) => write!(f, "mers {v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MacroError {
|
||||
MersStatementArgError(Box<ScriptError>),
|
||||
UnknownMacroType(String),
|
||||
StaticValueNotSafeToShare,
|
||||
}
|
||||
|
||||
impl Display for MacroError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MersStatementArgError(e) => write!(f, "error in mers statement argument: {e}"),
|
||||
Self::UnknownMacroType(t) => write!(
|
||||
f,
|
||||
"unknown macro type '{t}', try mers-include or mers-static."
|
||||
),
|
||||
Self::StaticValueNotSafeToShare => write!(f, "static value cannot safely be shared (cannot use value returned by mers-static in your code - maybe it was a reference, an enum, ...)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScriptError> for MacroError {
|
||||
fn from(value: ScriptError) -> Self {
|
||||
Self::MersStatementArgError(Box::new(value))
|
||||
}
|
||||
}
|
||||
impl From<ParseError> for MacroError {
|
||||
fn from(value: ParseError) -> Self {
|
||||
let value: ScriptError = value.into();
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
326
old/mers/src/lang/code_parsed.rs
Executable file
326
old/mers/src/lang/code_parsed.rs
Executable file
@@ -0,0 +1,326 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use super::{
|
||||
code_macro::Macro, fmtgs::FormatGs, global_info::GlobalScriptInfo, val_data::VData,
|
||||
val_type::VType,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SStatementEnum {
|
||||
Value(VData),
|
||||
Tuple(Vec<SStatement>),
|
||||
List(Vec<SStatement>),
|
||||
Variable(String, bool),
|
||||
FunctionCall(String, Vec<SStatement>),
|
||||
FunctionDefinition(Option<String>, SFunction),
|
||||
Block(SBlock),
|
||||
If(SStatement, SStatement, Option<SStatement>),
|
||||
Loop(SStatement),
|
||||
For(SStatement, SStatement, SStatement),
|
||||
Switch(SStatement, Vec<(VType, SStatement, SStatement)>, bool),
|
||||
Match(Vec<(SStatement, SStatement, SStatement)>),
|
||||
IndexFixed(SStatement, usize),
|
||||
EnumVariant(String, SStatement),
|
||||
TypeDefinition(String, VType),
|
||||
Macro(Macro),
|
||||
}
|
||||
impl SStatementEnum {
|
||||
pub fn to(self) -> SStatement {
|
||||
SStatement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SStatement {
|
||||
pub derefs: usize,
|
||||
/// if the statement is a Variable that doesn't exist yet, it will be initialized.
|
||||
/// if it's a variable that exists, but is_ref is false, an error may show up: cannot dereference
|
||||
/// if the third value is true, the variable will always be initialized, shadowing previous mentions of the same name.
|
||||
pub output_to: Option<(Box<SStatement>, bool)>,
|
||||
pub statement: Box<SStatementEnum>,
|
||||
pub force_output_type: Option<VType>,
|
||||
}
|
||||
|
||||
impl SStatement {
|
||||
pub fn new(statement: SStatementEnum) -> Self {
|
||||
Self {
|
||||
derefs: 0,
|
||||
output_to: None,
|
||||
statement: Box::new(statement),
|
||||
force_output_type: None,
|
||||
}
|
||||
}
|
||||
pub fn output_to(mut self, statement: SStatement) -> Self {
|
||||
self.output_to = Some((Box::new(statement), false));
|
||||
self
|
||||
}
|
||||
/// like output_to, but always initializes the variable (shadows previous variables of the same name)
|
||||
pub fn initialize_to(mut self, statement: SStatement) -> Self {
|
||||
self.output_to = Some((Box::new(statement), true));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of code is a collection of statements.
|
||||
#[derive(Debug)]
|
||||
pub struct SBlock {
|
||||
pub statements: Vec<SStatement>,
|
||||
}
|
||||
impl SBlock {
|
||||
pub fn new(statements: Vec<SStatement>) -> Self {
|
||||
Self { statements }
|
||||
}
|
||||
}
|
||||
|
||||
// A function is a block of code that starts with some local variables as inputs and returns some value as its output. The last statement in the block will be the output.
|
||||
#[derive(Debug)]
|
||||
pub struct SFunction {
|
||||
pub inputs: Vec<(String, VType)>,
|
||||
pub statement: SStatement,
|
||||
}
|
||||
impl SFunction {
|
||||
pub fn new(inputs: Vec<(String, VType)>, statement: SStatement) -> Self {
|
||||
Self { inputs, statement }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl FormatGs for SStatementEnum {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Value(v) => v.fmtgs(f, info, form, file),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "{}", form.open_bracket(info, "[".to_owned()))?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, "]".to_owned()))
|
||||
}
|
||||
Self::List(v) => {
|
||||
write!(f, "{}", form.open_bracket(info, "[".to_owned()))?;
|
||||
for (_i, v) in v.iter().enumerate() {
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, "...]".to_owned()))
|
||||
}
|
||||
Self::Variable(var, reference) => {
|
||||
if *reference {
|
||||
write!(f, "{}", form.variable_ref_symbol(info, "&".to_owned()))?;
|
||||
}
|
||||
write!(f, "{}", form.variable(info, var.to_owned()))
|
||||
}
|
||||
Self::FunctionCall(func, args) => {
|
||||
write!(
|
||||
f,
|
||||
"{}{}",
|
||||
form.fncall(info, func.to_owned()),
|
||||
form.open_bracket(info, "(".to_owned())
|
||||
)?;
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, ")".to_owned()))
|
||||
}
|
||||
Self::FunctionDefinition(name, func) => {
|
||||
if let Some(name) = name {
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
form.fndef_fn(info, "fn".to_owned()),
|
||||
form.fndef_name(info, name.to_owned())
|
||||
)?;
|
||||
}
|
||||
func.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Block(b) => b.fmtgs(f, info, form, file),
|
||||
Self::If(condition, yes, no) => {
|
||||
write!(f, "{} ", form.if_if(info, "if".to_owned()))?;
|
||||
condition.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
yes.fmtgs(f, info, form, file)?;
|
||||
if let Some(no) = no {
|
||||
write!(f, " {} ", form.if_else(info, "else".to_owned()))?;
|
||||
no.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::Loop(b) => {
|
||||
write!(f, "{} ", form.loop_loop(info, "loop".to_owned()))?;
|
||||
b.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::For(assign_to, i, b) => {
|
||||
write!(f, "{} ", form.loop_for(info, "for".to_owned()))?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
i.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
b.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Switch(var, arms, force) => {
|
||||
if *force {
|
||||
writeln!(
|
||||
f,
|
||||
"{} {var} {}",
|
||||
form.kw_switch(info, "switch!".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"{} {var} {}",
|
||||
form.kw_switch(info, "switch".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
}
|
||||
form.go_deeper();
|
||||
for (t, assign_to, action) in arms {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
action.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
Self::Match(arms) => {
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
form.kw_match(info, "match".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
form.go_deeper();
|
||||
for (condition, assign_to, action) in arms {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
condition.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
action.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
Self::IndexFixed(statement, index) => {
|
||||
statement.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".{index}")
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
write!(f, "{variant}: ")?;
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::TypeDefinition(name, t) => write!(f, "type {name} {t}"),
|
||||
Self::Macro(m) => {
|
||||
write!(f, "!({m})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for SStatementEnum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SStatement {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
// output output_to
|
||||
if let Some((opt, is_init)) = &self.output_to {
|
||||
write!(
|
||||
f,
|
||||
"{} {} ",
|
||||
opt.with(info, file),
|
||||
if *is_init { ":=" } else { "=" }
|
||||
)?;
|
||||
}
|
||||
// output self
|
||||
if let Some(force_opt) = &self.force_output_type {
|
||||
write!(f, "-> ")?;
|
||||
force_opt.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", "*".repeat(self.derefs))?;
|
||||
self.statement.fmtgs(f, info, form, file)?;
|
||||
write!(f, ",")
|
||||
}
|
||||
}
|
||||
impl Display for SStatement {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SFunction {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
write!(f, "(")?;
|
||||
for (i, (name, t)) in self.inputs.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " {name} ")?;
|
||||
} else {
|
||||
write!(f, "{name} ")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ") ")?;
|
||||
self.statement.fmtgs(f, info, form, file)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SBlock {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self.statements.len() {
|
||||
0 => write!(f, "{{}}"),
|
||||
// 1 => self.statements[0].fmtgs(f, info, form, file),
|
||||
_ => {
|
||||
writeln!(f, "{}", form.open_bracket(info, "{".to_owned()))?;
|
||||
form.go_deeper();
|
||||
for statement in self.statements.iter() {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
statement.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
459
old/mers/src/lang/code_runnable.rs
Executable file
459
old/mers/src/lang/code_runnable.rs
Executable file
@@ -0,0 +1,459 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins::BuiltinFunction,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
to_runnable::ToRunnableError,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RStatementEnum {
|
||||
Value(VData),
|
||||
Tuple(Vec<RStatement>),
|
||||
List(Vec<RStatement>),
|
||||
Variable(Arc<Mutex<(VData, VType)>>, bool),
|
||||
FunctionCall(Arc<RFunction>, Vec<RStatement>),
|
||||
BuiltinFunctionCall(BuiltinFunction, Vec<RStatement>),
|
||||
LibFunctionCall(usize, usize, Vec<RStatement>, VType),
|
||||
Block(RBlock),
|
||||
If(RStatement, RStatement, Option<RStatement>),
|
||||
Loop(RStatement),
|
||||
For(RStatement, RStatement, RStatement),
|
||||
Switch(RStatement, Vec<(VType, RStatement, RStatement)>, bool),
|
||||
Match(Vec<(RStatement, RStatement, RStatement)>),
|
||||
IndexFixed(RStatement, usize),
|
||||
EnumVariant(usize, RStatement),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RBlock {
|
||||
pub statements: Vec<RStatement>,
|
||||
}
|
||||
impl RBlock {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
let mut last = None;
|
||||
for statement in &self.statements {
|
||||
last = Some(statement.run(info));
|
||||
}
|
||||
if let Some(v) = last {
|
||||
v
|
||||
} else {
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
if let Some(last) = self.statements.last() {
|
||||
last.out(info)
|
||||
} else {
|
||||
VType {
|
||||
types: vec![VSingleType::Tuple(vec![])],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RFunction {
|
||||
pub statement: RFunctionType,
|
||||
pub out_map: Vec<(Vec<VType>, VType)>,
|
||||
}
|
||||
impl Debug for RFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.out_map)
|
||||
}
|
||||
}
|
||||
pub enum RFunctionType {
|
||||
/// ignores all args and returns some default value
|
||||
Dummy,
|
||||
Statement(Vec<Arc<Mutex<(VData, VType)>>>, RStatement, Vec<VType>),
|
||||
Func(Box<dyn Fn(&GSInfo, Vec<VData>) -> VData + Send + Sync>),
|
||||
}
|
||||
impl PartialEq for RFunction {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
impl Eq for RFunction {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
impl RFunction {
|
||||
pub fn run(&self, info: &GSInfo, args: Vec<VData>) -> VData {
|
||||
match &self.statement {
|
||||
RFunctionType::Dummy => VDataEnum::Bool(false).to(),
|
||||
RFunctionType::Statement(inputs, s, _) => {
|
||||
for (i, input) in inputs.iter().enumerate() {
|
||||
input.lock().unwrap().0.assign(args[i].clone_mut());
|
||||
}
|
||||
s.run(info)
|
||||
}
|
||||
RFunctionType::Func(f) => f(info, args),
|
||||
}
|
||||
}
|
||||
pub fn out_by_map(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
||||
let mut empty = true;
|
||||
let out =
|
||||
self.out_map
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == (input_types.len())
|
||||
&& fn_in.iter().zip(input_types.iter()).all(|(fn_in, arg)| {
|
||||
arg.types.iter().any(|t| t.fits_in_type(fn_in, info))
|
||||
})
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, info);
|
||||
}
|
||||
t
|
||||
});
|
||||
if empty {
|
||||
None
|
||||
} else {
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
pub fn out_all_by_map(&self, info: &GlobalScriptInfo) -> VType {
|
||||
// self.statement.out(info)
|
||||
self.out_map.iter().fold(VType::empty(), |mut t, (_, v)| {
|
||||
t.add_typesr(v, info);
|
||||
t
|
||||
})
|
||||
}
|
||||
pub fn out_by_statement(
|
||||
&self,
|
||||
input_types: &Vec<VType>,
|
||||
info: &GlobalScriptInfo,
|
||||
) -> Option<VType> {
|
||||
if let RFunctionType::Statement(inputs, statement, _) = &self.statement {
|
||||
let mut actual = Vec::with_capacity(inputs.len());
|
||||
// simulate these variable types
|
||||
for (fn_input, c_type) in inputs.iter().zip(input_types.iter()) {
|
||||
actual.push(std::mem::replace(
|
||||
&mut fn_input.lock().unwrap().1,
|
||||
c_type.clone(),
|
||||
));
|
||||
}
|
||||
// not get the return type if these were the actual types
|
||||
let out = statement.out(info);
|
||||
// reset
|
||||
for (fn_input, actual) in inputs.iter().zip(actual) {
|
||||
fn_input.lock().unwrap().1 = actual;
|
||||
}
|
||||
// return
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RStatement {
|
||||
// (_, derefs, is_init)
|
||||
pub derefs: usize,
|
||||
pub output_to: Option<(Box<RStatement>, bool)>,
|
||||
statement: Box<RStatementEnum>,
|
||||
pub force_output_type: Option<VType>,
|
||||
}
|
||||
impl RStatement {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
let out = self.statement.run(info);
|
||||
let mut o = if let Some((v, _is_init)) = &self.output_to {
|
||||
// // assigns a new VData to the variable's Arc<Mutex<_>>, so that threads which have captured the variable at some point
|
||||
// // won't be updated with its new value (is_init is set to true for initializations, such as in a loop - this can happen multiple times, but each should be its own variable with the same name)
|
||||
// if *is_init && *derefs == 0 {
|
||||
// Self::assign_to(out, v.run(info), info);
|
||||
// break 'init;
|
||||
// }
|
||||
let val = v.run(info);
|
||||
out.assign_to(val, info);
|
||||
// val.assign(out);
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
} else {
|
||||
out
|
||||
};
|
||||
for _ in 0..self.derefs {
|
||||
o = o.deref().expect("couldn't dereference! (run())");
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
// `a = b` evaluates to [] (don't change this - cloning is cheap but a = b should NEVER return a boolean because that will make if a = b {} errors way too likely.)
|
||||
if self.output_to.is_some() {
|
||||
return VType {
|
||||
types: vec![VSingleType::Tuple(vec![])],
|
||||
};
|
||||
}
|
||||
if let Some(t) = &self.force_output_type {
|
||||
return t.clone();
|
||||
}
|
||||
let mut o = self.statement.out(info);
|
||||
for _ in 0..self.derefs {
|
||||
o = o.dereference(info).expect("can't dereference (out())");
|
||||
}
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
impl RStatementEnum {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
match self {
|
||||
Self::Value(v) => v.clone(),
|
||||
Self::Tuple(v) => {
|
||||
let mut w = vec![];
|
||||
for v in v {
|
||||
w.push(v.run(info));
|
||||
}
|
||||
VDataEnum::Tuple(w).to()
|
||||
}
|
||||
Self::List(v) => {
|
||||
let mut w = vec![];
|
||||
let mut out = VType { types: vec![] };
|
||||
for v in v {
|
||||
let val = v.run(info);
|
||||
out.add_types(val.out(), &info);
|
||||
w.push(val);
|
||||
}
|
||||
VDataEnum::List(out, w).to()
|
||||
}
|
||||
Self::Variable(v, is_ref) => {
|
||||
if *is_ref {
|
||||
VDataEnum::Reference(v.lock().unwrap().0.clone_mut()).to()
|
||||
} else {
|
||||
v.lock().unwrap().0.clone_data()
|
||||
}
|
||||
}
|
||||
Self::FunctionCall(func, args) => {
|
||||
func.run(info, args.iter().map(|s| s.run(info)).collect())
|
||||
}
|
||||
Self::BuiltinFunctionCall(v, args) => v.run(args, info),
|
||||
Self::LibFunctionCall(libid, fnid, args, _) => {
|
||||
info.libs[*libid].run_fn(*fnid, args.iter().map(|arg| arg.run(info)).collect())
|
||||
}
|
||||
Self::Block(b) => b.run(info),
|
||||
Self::If(c, t, e) => c.run(info).operate_on_data_immut(|v| {
|
||||
if let VDataEnum::Bool(v) = v {
|
||||
if *v {
|
||||
t.run(info)
|
||||
} else {
|
||||
if let Some(e) = e {
|
||||
e.run(info)
|
||||
} else {
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}),
|
||||
Self::Loop(c) => loop {
|
||||
// loops will break if the value matches.
|
||||
if let Some(break_val) = c.run(info).matches() {
|
||||
break break_val;
|
||||
}
|
||||
},
|
||||
Self::For(v, c, b) => {
|
||||
// matching values also break with value from a for loop.
|
||||
let vv = v.run(info);
|
||||
let in_loop = |c: VData| {
|
||||
c.assign_to(vv.clone_mut(), info);
|
||||
b.run(info)
|
||||
};
|
||||
let mut iter = c.run(info);
|
||||
if let Some(v) = iter.operate_on_data_immut(|c: &VDataEnum| {
|
||||
let mut oval = VDataEnum::Tuple(vec![]).to();
|
||||
match c {
|
||||
VDataEnum::Int(v) => {
|
||||
for i in 0..*v {
|
||||
if let Some(v) = in_loop(VDataEnum::Int(i).to()).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::String(v) => {
|
||||
for ch in v.chars() {
|
||||
if let Some(v) =
|
||||
in_loop(VDataEnum::String(ch.to_string()).to()).matches()
|
||||
{
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for v in v {
|
||||
if let Some(v) = in_loop(v.clone()).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(f) => loop {
|
||||
if let Some(v) = f.run(info, vec![]).matches() {
|
||||
if let Some(v) = in_loop(v).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
VDataEnum::Reference(_r) => return None,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Some(oval)
|
||||
}) {
|
||||
v
|
||||
} else {
|
||||
// loop mutably
|
||||
iter.operate_on_data_mut(|c| match c {
|
||||
VDataEnum::Reference(r) => r.operate_on_data_mut(|c| match c {
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for v in v {
|
||||
if let Some(v) =
|
||||
in_loop(VDataEnum::Reference(v.clone_mut()).to()).matches()
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Self::Switch(switch_on, cases, _force) => {
|
||||
let switch_on = switch_on.run(info);
|
||||
let switch_on_type = switch_on.out();
|
||||
let mut out = VDataEnum::Tuple(vec![]).to();
|
||||
for (case_type, assign_to, case_action) in cases.iter() {
|
||||
if switch_on_type.fits_in(case_type, info).is_empty() {
|
||||
switch_on.assign_to(assign_to.run(info), info);
|
||||
out = case_action.run(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Match(cases) => 'm: {
|
||||
for (case_condition, assign_to, case_action) in cases {
|
||||
// [t] => Some(t), t => Some(t), [] | false => None
|
||||
if let Some(v) = case_condition.run(info).matches() {
|
||||
v.assign_to(assign_to.run(info), info);
|
||||
// let og = { std::mem::replace(&mut *match_on.lock().unwrap(), v) };
|
||||
let res = case_action.run(info);
|
||||
// *match_on.lock().unwrap() = og;
|
||||
break 'm res;
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
Self::IndexFixed(st, i) => st.run(info).get(*i).unwrap(),
|
||||
Self::EnumVariant(e, v) => VDataEnum::EnumVariant(*e, Box::new(v.run(info))).to(),
|
||||
}
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
match self {
|
||||
Self::Value(v) => v.out(),
|
||||
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out(info)).collect()).into(),
|
||||
Self::List(v) => VSingleType::List({
|
||||
let mut types = VType { types: vec![] };
|
||||
for t in v {
|
||||
types.add_types(t.out(info), info);
|
||||
}
|
||||
types
|
||||
})
|
||||
.into(),
|
||||
Self::Variable(t, is_ref) => {
|
||||
if *is_ref {
|
||||
VSingleType::Reference(t.lock().unwrap().1.clone()).to()
|
||||
} else {
|
||||
t.lock().unwrap().1.clone()
|
||||
}
|
||||
}
|
||||
Self::FunctionCall(f, args) => f
|
||||
.out_by_map(&args.iter().map(|v| v.out(info)).collect(), info)
|
||||
.expect("invalid args for function -> can't determine output type"),
|
||||
Self::LibFunctionCall(.., out) => out.clone(),
|
||||
Self::Block(b) => b.out(info),
|
||||
Self::If(_, a, b) => {
|
||||
let mut out = a.out(info);
|
||||
if let Some(b) = b {
|
||||
out.add_types(b.out(info), info);
|
||||
} else {
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Loop(c) => c.out(info).matches(info).1,
|
||||
Self::For(_, _, b) => {
|
||||
let mut out = b.out(info).matches(info).1;
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
out
|
||||
}
|
||||
Self::BuiltinFunctionCall(f, args) => {
|
||||
f.returns(args.iter().map(|rs| rs.out(info)).collect(), info)
|
||||
}
|
||||
Self::Switch(switch_on, cases, force) => {
|
||||
let switch_on = switch_on.out(info).types;
|
||||
let _might_return_empty = switch_on.is_empty();
|
||||
let mut out = if *force {
|
||||
VType::empty()
|
||||
} else {
|
||||
VSingleType::Tuple(vec![]).to()
|
||||
};
|
||||
for _switch_on in switch_on {
|
||||
for (_on_type, _assign_to, case) in cases.iter() {
|
||||
out.add_types(case.out(info), info);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Match(cases) => {
|
||||
let mut out = VType::empty();
|
||||
let mut can_fail_to_match = true;
|
||||
for (condition, _assign_to, action) in cases {
|
||||
out.add_types(action.out(info), info);
|
||||
if !condition.out(info).matches(info).0 {
|
||||
can_fail_to_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if can_fail_to_match {
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::IndexFixed(st, i) => st.out(info).get(*i, info).unwrap(),
|
||||
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out(info)).to(),
|
||||
}
|
||||
}
|
||||
pub fn to(self) -> RStatement {
|
||||
RStatement {
|
||||
derefs: 0,
|
||||
output_to: None,
|
||||
statement: Box::new(self),
|
||||
force_output_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RScript {
|
||||
main: RFunction,
|
||||
pub info: GSInfo,
|
||||
}
|
||||
impl RScript {
|
||||
pub fn new(main: RFunction, info: GSInfo) -> Result<Self, ToRunnableError> {
|
||||
Ok(Self { main, info })
|
||||
}
|
||||
pub fn run(&self, args: Vec<VData>) -> VData {
|
||||
self.main.run(&self.info, args)
|
||||
}
|
||||
}
|
||||
180
old/mers/src/lang/fmtgs.rs
Executable file
180
old/mers/src/lang/fmtgs.rs
Executable file
@@ -0,0 +1,180 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use super::global_info::{ColorFormatMode, ColorFormatter, GlobalScriptInfo};
|
||||
|
||||
use colorize::AnsiColor;
|
||||
|
||||
pub enum Color {
|
||||
// Keep,
|
||||
Grey,
|
||||
Red,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
Cyan,
|
||||
Magenta,
|
||||
}
|
||||
impl Color {
|
||||
pub fn colorize(&self, s: String) -> String {
|
||||
match self {
|
||||
// Self::Keep => s,
|
||||
Self::Grey => s.grey().to_string(),
|
||||
Self::Red => s.red().to_string(),
|
||||
Self::Yellow => s.yellow().to_string(),
|
||||
Self::Green => s.green().to_string(),
|
||||
Self::Blue => s.blue().to_string(),
|
||||
Self::Cyan => s.cyan().to_string(),
|
||||
Self::Magenta => s.magenta().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatInfo {
|
||||
pub depth: usize,
|
||||
pub brackets: usize,
|
||||
}
|
||||
impl FormatInfo {
|
||||
fn color<F>(&self, info: Option<&GlobalScriptInfo>, color: F, s: String) -> String
|
||||
where
|
||||
F: Fn(&ColorFormatter) -> &Color,
|
||||
{
|
||||
if let Some(info) = info {
|
||||
let color = color(&info.formatter);
|
||||
match info.formatter.mode {
|
||||
ColorFormatMode::Plain => s,
|
||||
ColorFormatMode::Colorize => color.colorize(s),
|
||||
}
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
pub fn open_bracket(&mut self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
let o = self.color(
|
||||
info,
|
||||
|c| &c.bracket_colors[self.brackets % c.bracket_colors.len()],
|
||||
s,
|
||||
);
|
||||
self.brackets += 1;
|
||||
o
|
||||
}
|
||||
pub fn close_bracket(&mut self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.brackets -= 1;
|
||||
self.color(
|
||||
info,
|
||||
|c| &c.bracket_colors[self.brackets % c.bracket_colors.len()],
|
||||
s,
|
||||
)
|
||||
}
|
||||
pub fn go_deeper(&mut self) {
|
||||
self.depth += 1;
|
||||
}
|
||||
pub fn go_shallower(&mut self) {
|
||||
self.depth -= 1;
|
||||
}
|
||||
pub fn variable_ref_symbol(&self, _info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
s
|
||||
}
|
||||
pub fn variable(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.variable_color, s)
|
||||
}
|
||||
pub fn if_if(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_if_color, s)
|
||||
}
|
||||
pub fn if_else(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_else_color, s)
|
||||
}
|
||||
pub fn loop_loop(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_loop_color, s)
|
||||
}
|
||||
pub fn loop_for(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_for_color, s)
|
||||
}
|
||||
pub fn kw_switch(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_switch_color, s)
|
||||
}
|
||||
pub fn kw_match(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_match_color, s)
|
||||
}
|
||||
pub fn fncall(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_call_color, s)
|
||||
}
|
||||
pub fn fndef_fn(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_def_fn_color, s)
|
||||
}
|
||||
pub fn fndef_name(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_def_name_color, s)
|
||||
}
|
||||
pub fn value_string_quotes(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.value_string_quotes_color, s)
|
||||
}
|
||||
pub fn value_string_content(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.value_string_content_color, s)
|
||||
}
|
||||
pub fn line_prefix(&self) -> String {
|
||||
" ".repeat(self.depth)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormatGs {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result;
|
||||
fn with_info<'a>(&'a self, info: &'a GlobalScriptInfo) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: Some(info),
|
||||
file: None,
|
||||
}
|
||||
}
|
||||
fn with_file<'a>(&'a self, file: &'a crate::parsing::file::File) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: None,
|
||||
file: Some(file),
|
||||
}
|
||||
}
|
||||
fn with_info_and_file<'a>(
|
||||
&'a self,
|
||||
info: &'a GlobalScriptInfo,
|
||||
file: &'a crate::parsing::file::File,
|
||||
) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: Some(info),
|
||||
file: Some(file),
|
||||
}
|
||||
}
|
||||
fn with<'a>(
|
||||
&'a self,
|
||||
info: Option<&'a GlobalScriptInfo>,
|
||||
file: Option<&'a crate::parsing::file::File>,
|
||||
) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct FormatWithGs<'a, T: ?Sized>
|
||||
where
|
||||
T: FormatGs,
|
||||
{
|
||||
format: &'a T,
|
||||
info: Option<&'a GlobalScriptInfo>,
|
||||
file: Option<&'a crate::parsing::file::File>,
|
||||
}
|
||||
impl<'a, T> Display for FormatWithGs<'a, T>
|
||||
where
|
||||
T: FormatGs,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.format
|
||||
.fmtgs(f, self.info, &mut FormatInfo::default(), self.file.clone())
|
||||
}
|
||||
}
|
||||
200
old/mers/src/lang/global_info.rs
Executable file
200
old/mers/src/lang/global_info.rs
Executable file
@@ -0,0 +1,200 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins,
|
||||
fmtgs::Color,
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
pub type GSInfo = Arc<GlobalScriptInfo>;
|
||||
|
||||
pub struct GlobalScriptInfo {
|
||||
pub libs: Vec<crate::libs::Lib>,
|
||||
|
||||
pub main_fn_args: Vec<(String, VType)>,
|
||||
|
||||
pub lib_fns: HashMap<String, (usize, usize)>,
|
||||
|
||||
pub enum_variants: HashMap<String, usize>,
|
||||
|
||||
pub custom_type_names: HashMap<String, usize>,
|
||||
pub custom_types: Vec<VType>,
|
||||
|
||||
/// if true, trying to assign to the reference of a variable that doesn't exist yet will create and initialize that variable.
|
||||
/// if false, variables will only be initialized if this is explicitly stated.
|
||||
/// settings this to true is useful for "x = 2; x = 5;" syntax in parser implementations that don't differenciate initialization and assignment syntactically.
|
||||
pub to_runnable_automatic_initialization: bool,
|
||||
|
||||
pub formatter: ColorFormatter,
|
||||
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
pub struct ColorFormatter {
|
||||
pub mode: ColorFormatMode,
|
||||
pub bracket_colors: Vec<Color>,
|
||||
pub value_string_quotes_color: Color,
|
||||
pub value_string_content_color: Color,
|
||||
pub keyword_if_color: Color,
|
||||
pub keyword_else_color: Color,
|
||||
pub keyword_loop_color: Color,
|
||||
pub keyword_for_color: Color,
|
||||
pub keyword_switch_color: Color,
|
||||
pub keyword_match_color: Color,
|
||||
pub function_call_color: Color,
|
||||
pub function_def_fn_color: Color,
|
||||
pub function_def_name_color: Color,
|
||||
pub variable_color: Color,
|
||||
}
|
||||
impl Default for ColorFormatter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: ColorFormatMode::Plain,
|
||||
bracket_colors: vec![
|
||||
Color::Red,
|
||||
Color::Yellow,
|
||||
Color::Cyan,
|
||||
Color::Blue,
|
||||
Color::Magenta,
|
||||
],
|
||||
value_string_quotes_color: Color::Grey,
|
||||
value_string_content_color: Color::Cyan,
|
||||
keyword_if_color: Color::Yellow,
|
||||
keyword_else_color: Color::Yellow,
|
||||
keyword_loop_color: Color::Yellow,
|
||||
keyword_for_color: Color::Yellow,
|
||||
keyword_switch_color: Color::Yellow,
|
||||
keyword_match_color: Color::Yellow,
|
||||
function_call_color: Color::Magenta,
|
||||
function_def_fn_color: Color::Blue,
|
||||
function_def_name_color: Color::Magenta,
|
||||
variable_color: Color::Green,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ColorFormatMode {
|
||||
/// No color.
|
||||
Plain,
|
||||
/// For terminal output
|
||||
Colorize,
|
||||
}
|
||||
|
||||
impl GlobalScriptInfo {
|
||||
pub fn to_arc(self) -> GSInfo {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GlobalScriptInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
libs: vec![],
|
||||
lib_fns: HashMap::new(),
|
||||
main_fn_args: vec![],
|
||||
enum_variants: Self::default_enum_variants(),
|
||||
custom_type_names: HashMap::new(),
|
||||
custom_types: vec![],
|
||||
to_runnable_automatic_initialization: false,
|
||||
formatter: Default::default(),
|
||||
log: Logger::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl GlobalScriptInfo {
|
||||
pub fn default_enum_variants() -> HashMap<String, usize> {
|
||||
builtins::EVS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v.to_string(), i))
|
||||
.collect()
|
||||
}
|
||||
pub fn set_main_fn_args(&mut self, args: Vec<(String, VType)>) {
|
||||
self.main_fn_args = args;
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_enum_variant(&mut self, name: String) -> usize {
|
||||
let id = self.enum_variants.len();
|
||||
self.enum_variants.insert(name, id);
|
||||
id
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_custom_type(&mut self, name: String, t: VType) -> usize {
|
||||
let id = self.custom_types.len();
|
||||
self.custom_types.push(t);
|
||||
self.custom_type_names.insert(name, id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Logger {
|
||||
logs: Arc<Mutex<Vec<LogMsg>>>,
|
||||
|
||||
pub after_parse: LogKind,
|
||||
|
||||
pub vtype_fits_in: LogKind,
|
||||
pub vsingletype_fits_in: LogKind,
|
||||
}
|
||||
impl Logger {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
logs: Arc::new(Mutex::new(vec![])),
|
||||
after_parse: Default::default(),
|
||||
vtype_fits_in: Default::default(),
|
||||
vsingletype_fits_in: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogMsg {
|
||||
AfterParse(String),
|
||||
VTypeFitsIn(VType, VType, Vec<VSingleType>),
|
||||
VSingleTypeFitsIn(VSingleType, VSingleType, bool),
|
||||
}
|
||||
impl Logger {
|
||||
pub fn log(&self, msg: LogMsg) {
|
||||
let kind = match msg {
|
||||
LogMsg::AfterParse(..) => &self.after_parse,
|
||||
LogMsg::VTypeFitsIn(..) => &self.vtype_fits_in,
|
||||
LogMsg::VSingleTypeFitsIn(..) => &self.vsingletype_fits_in,
|
||||
};
|
||||
if kind.stderr {
|
||||
eprintln!("{msg}");
|
||||
}
|
||||
if kind.log {
|
||||
if let Ok(mut logs) = self.logs.lock() {
|
||||
logs.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for LogMsg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AfterParse(code) => {
|
||||
write!(f, "AfterParse :: {code}")
|
||||
}
|
||||
Self::VTypeFitsIn(a, b, no) => write!(f, "VTypeFitsIn :: {a} in {b} ? -> {no:?}"),
|
||||
Self::VSingleTypeFitsIn(a, b, fits) => {
|
||||
write!(f, "VSingleTypeFitsIn :: {a} in {b} ? -> {fits}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LogKind {
|
||||
pub stderr: bool,
|
||||
pub log: bool,
|
||||
}
|
||||
impl LogKind {
|
||||
pub fn log(&self) -> bool {
|
||||
self.stderr || self.log
|
||||
}
|
||||
}
|
||||
9
old/mers/src/lang/mod.rs
Executable file
9
old/mers/src/lang/mod.rs
Executable file
@@ -0,0 +1,9 @@
|
||||
pub mod builtins;
|
||||
pub mod code_macro;
|
||||
pub mod code_parsed;
|
||||
pub mod code_runnable;
|
||||
pub mod fmtgs;
|
||||
pub mod global_info;
|
||||
pub mod to_runnable;
|
||||
pub mod val_data;
|
||||
pub mod val_type;
|
||||
749
old/mers/src/lang/to_runnable.rs
Executable file
749
old/mers/src/lang/to_runnable.rs
Executable file
@@ -0,0 +1,749 @@
|
||||
use core::panic;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Display},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::lang::{
|
||||
global_info::GlobalScriptInfo,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins::BuiltinFunction,
|
||||
code_macro::Macro,
|
||||
code_parsed::{SBlock, SFunction, SStatement, SStatementEnum},
|
||||
code_runnable::{RBlock, RFunction, RFunctionType, RScript, RStatement, RStatementEnum},
|
||||
fmtgs::FormatGs,
|
||||
global_info::GSInfo,
|
||||
};
|
||||
|
||||
pub enum ToRunnableError {
|
||||
UseOfUndefinedVariable(String),
|
||||
UseOfUndefinedFunction(String),
|
||||
UnknownType(String),
|
||||
CannotDereferenceTypeNTimes(VType, usize, VType),
|
||||
FunctionWrongArgs(String, Vec<Arc<RFunction>>, Vec<VType>),
|
||||
InvalidType {
|
||||
expected: VType,
|
||||
found: VType,
|
||||
problematic: VType,
|
||||
},
|
||||
CannotAssignTo(VType, VType),
|
||||
CaseForceButTypeNotCovered(VType),
|
||||
MatchConditionInvalidReturn(VType),
|
||||
NotIndexableFixed(VType, usize),
|
||||
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
|
||||
WrongArgsForLibFunction(String, Vec<VType>),
|
||||
ForLoopContainerHasNoInnerTypes,
|
||||
StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType),
|
||||
}
|
||||
impl Debug for ToRunnableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// - Don't use {} to format, use .fmtgs(f, info, form, file) instead!
|
||||
// - Show location in code where the error was found
|
||||
impl Display for ToRunnableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
impl FormatGs for ToRunnableError {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UseOfUndefinedVariable(v) => {
|
||||
write!(f, "Cannot use variable \"{v}\" as it isn't defined (yet?).")
|
||||
}
|
||||
Self::UseOfUndefinedFunction(v) => {
|
||||
write!(f, "Cannot use function \"{v}\" as it isn't defined (yet?).")
|
||||
}
|
||||
Self::UnknownType(name) => write!(f, "Unknown type \"{name}\"."),
|
||||
Self::CannotDereferenceTypeNTimes(og_type, derefs_wanted, last_valid_type) => {
|
||||
write!(f, "Cannot dereference type ")?;
|
||||
og_type.fmtgs(f, info, form, file)?;
|
||||
write!(f, " {derefs_wanted} times (stopped at ")?;
|
||||
last_valid_type.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::FunctionWrongArgs(fn_name, possible_fns, given_types) => {
|
||||
write!(f, "Wrong args for function \"{fn_name}\": Found (")?;
|
||||
for (i, t) in given_types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "), but valid are only ")?;
|
||||
for (i, func) in possible_fns.iter().enumerate() {
|
||||
if i != 0 {
|
||||
if i + 1 == possible_fns.len() {
|
||||
write!(f, ", and ")?;
|
||||
} else {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(Arc::clone(func)).fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::InvalidType {
|
||||
expected,
|
||||
found,
|
||||
problematic,
|
||||
} => {
|
||||
write!(f, "Invalid type: Expected ")?;
|
||||
expected.fmtgs(f, info, form, file)?;
|
||||
write!(f, " but found ")?;
|
||||
found.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", which includes ")?;
|
||||
problematic.fmtgs(f, info, form, file)?;
|
||||
write!(f, " which is not covered.")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::CaseForceButTypeNotCovered(v) => {
|
||||
write!(
|
||||
f,
|
||||
"Switch! statement, but not all types covered. Types to cover: "
|
||||
)?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
Ok(())
|
||||
}
|
||||
Self::MatchConditionInvalidReturn(v) => {
|
||||
write!(f, "match statement condition returned ")?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", which is not necessarily a tuple of size 0 to 1.")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::NotIndexableFixed(t, i) => {
|
||||
write!(f, "Cannot use fixed-index {i} on type ")?;
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::WrongInputsForBuiltinFunction(_builtin, builtin_name, args) => {
|
||||
write!(
|
||||
f,
|
||||
"Wrong arguments for builtin function \"{}\":",
|
||||
builtin_name
|
||||
)?;
|
||||
for arg in args {
|
||||
write!(f, " ")?;
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")
|
||||
}
|
||||
Self::WrongArgsForLibFunction(name, args) => {
|
||||
write!(f, "Wrong arguments for library function {}:", name)?;
|
||||
for arg in args {
|
||||
write!(f, " ")?;
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")
|
||||
}
|
||||
Self::CannotAssignTo(val, target) => {
|
||||
write!(f, "Cannot assign type ")?;
|
||||
val.fmtgs(f, info, form, file)?;
|
||||
write!(f, " to ")?;
|
||||
target.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::ForLoopContainerHasNoInnerTypes => {
|
||||
write!(f, "For loop: container had no inner types, cannot iterate.")
|
||||
}
|
||||
Self::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(
|
||||
required,
|
||||
real,
|
||||
problematic,
|
||||
) => {
|
||||
write!(f, "the statement requires its output type to be ")?;
|
||||
required.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", but its real output type is ")?;
|
||||
real.fmtgs(f, info, form, file)?;
|
||||
write!(
|
||||
f,
|
||||
", which doesn't fit in the required type because of the problematic types "
|
||||
)?;
|
||||
problematic.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local, used to keep local variables separated
|
||||
#[derive(Clone)]
|
||||
struct LInfo {
|
||||
vars: HashMap<String, Arc<Mutex<(VData, VType)>>>,
|
||||
fns: HashMap<String, Vec<Arc<RFunction>>>,
|
||||
}
|
||||
|
||||
pub fn to_runnable(
|
||||
s: SFunction,
|
||||
mut ginfo: GlobalScriptInfo,
|
||||
) -> Result<RScript, (ToRunnableError, GSInfo)> {
|
||||
let func = match function(
|
||||
&s,
|
||||
&mut ginfo,
|
||||
LInfo {
|
||||
vars: HashMap::new(),
|
||||
fns: HashMap::new(),
|
||||
},
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((e, ginfo.to_arc())),
|
||||
};
|
||||
let ginfo = ginfo.to_arc();
|
||||
match RScript::new(func, ginfo.clone()) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err((e, ginfo)),
|
||||
}
|
||||
}
|
||||
|
||||
fn function(
|
||||
s: &SFunction,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
mut linfo: LInfo,
|
||||
) -> Result<RFunction, ToRunnableError> {
|
||||
let mut input_vars = vec![];
|
||||
let mut input_types = vec![];
|
||||
for (iname, itype) in &s.inputs {
|
||||
let mut itype = itype.to_owned();
|
||||
stypes(&mut itype, ginfo)?;
|
||||
let var = Arc::new(Mutex::new((VData::new_placeholder(), itype.clone())));
|
||||
linfo.vars.insert(iname.clone(), Arc::clone(&var));
|
||||
input_vars.push(var);
|
||||
input_types.push(itype);
|
||||
}
|
||||
// set the types to all possible types (get_all_functions sets the types to one single type to get the return type of the block for that case)
|
||||
for (varid, vartype) in s.inputs.iter().zip(input_types.iter()) {
|
||||
linfo.vars.get(&varid.0).unwrap().lock().unwrap().1 = vartype.clone();
|
||||
}
|
||||
let mut o = RFunction {
|
||||
out_map: vec![],
|
||||
statement: RFunctionType::Statement(
|
||||
input_vars,
|
||||
statement(&s.statement, ginfo, &mut linfo.clone())?,
|
||||
input_types,
|
||||
),
|
||||
};
|
||||
o.out_map = {
|
||||
let mut map = vec![];
|
||||
let mut indices: Vec<_> = if let RFunctionType::Statement(_, _, input_types) = &o.statement
|
||||
{
|
||||
input_types.iter().map(|_| 0).collect()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
// like counting: advance first index, when we reach the end, reset to zero and advance the next index, ...
|
||||
loop {
|
||||
if let RFunctionType::Statement(_, _, input_types) = &o.statement {
|
||||
let mut current_types = Vec::with_capacity(input_types.len());
|
||||
let mut adv = true;
|
||||
let mut was_last = input_types.is_empty();
|
||||
for i in 0..input_types.len() {
|
||||
current_types.push(match input_types[i].types.get(indices[i]) {
|
||||
Some(v) => v.clone().to(),
|
||||
None => VType::empty(),
|
||||
});
|
||||
if adv {
|
||||
if indices[i] + 1 < input_types[i].types.len() {
|
||||
indices[i] += 1;
|
||||
adv = false;
|
||||
} else {
|
||||
indices[i] = 0;
|
||||
// we just reset the last index back to 0 - if we don't break
|
||||
// from the loop, we will just start all over again.
|
||||
if i + 1 == input_types.len() {
|
||||
was_last = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = o
|
||||
.out_by_statement(¤t_types, &ginfo)
|
||||
.expect("this should always be a Statement function type");
|
||||
map.push((current_types, out));
|
||||
if was_last {
|
||||
break map;
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(o)
|
||||
}
|
||||
|
||||
fn block(
|
||||
s: &SBlock,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
mut linfo: LInfo,
|
||||
) -> Result<RBlock, ToRunnableError> {
|
||||
let mut statements = Vec::new();
|
||||
for st in &s.statements {
|
||||
statements.push(statement(st, ginfo, &mut linfo)?);
|
||||
}
|
||||
Ok(RBlock { statements })
|
||||
}
|
||||
|
||||
pub fn stypes(t: &mut VType, ginfo: &mut GlobalScriptInfo) -> Result<(), ToRunnableError> {
|
||||
for t in &mut t.types {
|
||||
stype(t, ginfo)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn stype(t: &mut VSingleType, ginfo: &mut GlobalScriptInfo) -> Result<(), ToRunnableError> {
|
||||
match t {
|
||||
VSingleType::Any
|
||||
| VSingleType::Bool
|
||||
| VSingleType::Int
|
||||
| VSingleType::Float
|
||||
| VSingleType::String => (),
|
||||
VSingleType::Tuple(v) => {
|
||||
for t in v {
|
||||
stypes(t, ginfo)?;
|
||||
}
|
||||
}
|
||||
VSingleType::List(t) => stypes(t, ginfo)?,
|
||||
VSingleType::Reference(t) => stypes(t, ginfo)?,
|
||||
VSingleType::Thread(t) => stypes(t, ginfo)?,
|
||||
VSingleType::EnumVariantS(e, v) => {
|
||||
*t = VSingleType::EnumVariant(
|
||||
{
|
||||
if let Some(v) = ginfo.enum_variants.get(e) {
|
||||
*v
|
||||
} else {
|
||||
let v = ginfo.enum_variants.len();
|
||||
ginfo.enum_variants.insert(e.clone(), v);
|
||||
v
|
||||
}
|
||||
},
|
||||
{
|
||||
stypes(v, ginfo)?;
|
||||
v.clone()
|
||||
},
|
||||
)
|
||||
}
|
||||
VSingleType::Function(io_map) => {
|
||||
for io_variant in io_map {
|
||||
for i in &mut io_variant.0 {
|
||||
stypes(i, ginfo)?;
|
||||
}
|
||||
stypes(&mut io_variant.1, ginfo)?;
|
||||
}
|
||||
}
|
||||
VSingleType::EnumVariant(_, t) => stypes(t, ginfo)?,
|
||||
VSingleType::CustomTypeS(name) => {
|
||||
*t = VSingleType::CustomType(
|
||||
if let Some(v) = ginfo.custom_type_names.get(&name.to_lowercase()) {
|
||||
*v
|
||||
} else {
|
||||
return Err(ToRunnableError::UnknownType(name.to_owned()));
|
||||
},
|
||||
)
|
||||
}
|
||||
VSingleType::CustomType(_) => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn statement(
|
||||
s: &SStatement,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
linfo: &mut LInfo,
|
||||
) -> Result<RStatement, ToRunnableError> {
|
||||
statement_adv(s, ginfo, linfo, &mut None)
|
||||
}
|
||||
fn statement_adv(
|
||||
s: &SStatement,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
linfo: &mut LInfo,
|
||||
// if Some((t, is_init)), the statement creates by this function is the left side of an assignment, meaning it can create variables. t is the type that will be assigned to it.
|
||||
to_be_assigned_to: &mut Option<(VType, &mut bool)>,
|
||||
) -> Result<RStatement, ToRunnableError> {
|
||||
// eprintln!("TR : {}", s);
|
||||
// if let Some(t) = &to_be_assigned_to {
|
||||
// eprintln!(" --> {}", t.0);
|
||||
// }
|
||||
let mut state = match &*s.statement {
|
||||
SStatementEnum::Value(v) => RStatementEnum::Value(v.clone()).to(),
|
||||
SStatementEnum::Tuple(v) | SStatementEnum::List(v) => {
|
||||
let mut w = Vec::with_capacity(v.len());
|
||||
let mut prev = None;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if let Some(t) = to_be_assigned_to {
|
||||
let out_t = if let Some(p) = &prev { p } else { &t.0 };
|
||||
let inner_t = if let Some(v) = out_t.get_always(i, ginfo) {
|
||||
v
|
||||
} else {
|
||||
panic!("cannot assign: cannot get_always({i}) on type {}.", out_t);
|
||||
};
|
||||
let p = std::mem::replace(&mut t.0, inner_t);
|
||||
if prev.is_none() {
|
||||
prev = Some(p);
|
||||
}
|
||||
};
|
||||
w.push(statement_adv(v, ginfo, linfo, to_be_assigned_to)?);
|
||||
}
|
||||
if let (Some(t), Some(prev)) = (to_be_assigned_to, prev) {
|
||||
t.0 = prev;
|
||||
}
|
||||
if let SStatementEnum::List(_) = &*s.statement {
|
||||
RStatementEnum::List(w)
|
||||
} else {
|
||||
RStatementEnum::Tuple(w)
|
||||
}
|
||||
.to()
|
||||
}
|
||||
SStatementEnum::Variable(v, is_ref) => {
|
||||
let existing_var = linfo.vars.get(v);
|
||||
let is_init_force = if let Some(v) = &to_be_assigned_to {
|
||||
*v.1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// we can't assign to a variable that doesn't exist yet -> create a new one
|
||||
if is_init_force
|
||||
|| (existing_var.is_none() && ginfo.to_runnable_automatic_initialization)
|
||||
{
|
||||
// if to_be_assigned_to is some (-> this is on the left side of an assignment), create a new variable. else, return an error.
|
||||
if let Some((t, is_init)) = to_be_assigned_to {
|
||||
**is_init = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let var = VData::new_placeholder();
|
||||
#[cfg(debug_assertions)]
|
||||
let var = VData::new_placeholder_with_name(v.to_owned());
|
||||
let var_arc = Arc::new(Mutex::new((var, t.clone())));
|
||||
linfo.vars.insert(v.to_owned(), Arc::clone(&var_arc));
|
||||
RStatementEnum::Variable(var_arc, true)
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||
}
|
||||
} else if let Some(var) = existing_var {
|
||||
RStatementEnum::Variable(Arc::clone(&var), *is_ref)
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||
}
|
||||
.to()
|
||||
}
|
||||
SStatementEnum::FunctionCall(v, args) => {
|
||||
let mut rargs = Vec::with_capacity(args.len());
|
||||
for arg in args.iter() {
|
||||
rargs.push(statement(arg, ginfo, linfo)?);
|
||||
}
|
||||
let arg_types: Vec<_> = rargs.iter().map(|v| v.out(ginfo)).collect();
|
||||
fn check_fn_args(
|
||||
arg_types: &Vec<VType>,
|
||||
func: &RFunction,
|
||||
ginfo: &GlobalScriptInfo,
|
||||
) -> bool {
|
||||
func.out_by_map(arg_types, ginfo).is_some()
|
||||
// func.inputs.len() == arg_types.len()
|
||||
// && func
|
||||
// .inputs
|
||||
// .iter()
|
||||
// .zip(arg_types.iter())
|
||||
// .all(|(fn_in, arg)| arg.fits_in(&fn_in.lock().unwrap().1, ginfo).is_empty())
|
||||
}
|
||||
if let Some(funcs) = linfo.fns.get(v) {
|
||||
'find_func: {
|
||||
for func in funcs.iter().rev() {
|
||||
if check_fn_args(&arg_types, &func, ginfo) {
|
||||
break 'find_func RStatementEnum::FunctionCall(
|
||||
Arc::clone(&func),
|
||||
rargs,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Err(ToRunnableError::FunctionWrongArgs(
|
||||
v.to_owned(),
|
||||
funcs.iter().map(|v| Arc::clone(v)).collect(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if let Some(builtin) = BuiltinFunction::get(v) {
|
||||
let arg_types = rargs.iter().map(|v| v.out(ginfo)).collect();
|
||||
if builtin.can_take(&arg_types, ginfo) {
|
||||
RStatementEnum::BuiltinFunctionCall(builtin, rargs)
|
||||
} else {
|
||||
return Err(ToRunnableError::WrongInputsForBuiltinFunction(
|
||||
builtin,
|
||||
v.to_string(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// LIBRARY FUNCTION?
|
||||
if let Some((libid, fnid)) = ginfo.lib_fns.get(v) {
|
||||
let lib = &ginfo.libs[*libid];
|
||||
let libfn = &lib.registered_fns[*fnid];
|
||||
let mut empty = true;
|
||||
let fn_out =
|
||||
libfn
|
||||
.1
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == arg_types.len()
|
||||
&& fn_in.iter().zip(arg_types.iter()).all(|(fn_in, arg)| {
|
||||
arg.fits_in(fn_in, ginfo).is_empty()
|
||||
})
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, ginfo);
|
||||
}
|
||||
t
|
||||
});
|
||||
if empty {
|
||||
return Err(ToRunnableError::WrongArgsForLibFunction(
|
||||
v.to_owned(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
RStatementEnum::LibFunctionCall(*libid, *fnid, rargs, fn_out.clone())
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedFunction(v.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.to(),
|
||||
SStatementEnum::FunctionDefinition(name, f) => {
|
||||
let f = Arc::new(function(f, ginfo, linfo.clone())?);
|
||||
if let Some(name) = name {
|
||||
// named function => add to global functions
|
||||
let f = Arc::clone(&f);
|
||||
if let Some(vec) = linfo.fns.get_mut(name) {
|
||||
vec.push(f);
|
||||
} else {
|
||||
linfo.fns.insert(name.clone(), vec![f]);
|
||||
}
|
||||
}
|
||||
RStatementEnum::Value(VDataEnum::Function(f).to()).to()
|
||||
}
|
||||
SStatementEnum::Block(b) => RStatementEnum::Block(block(&b, ginfo, linfo.clone())?).to(),
|
||||
SStatementEnum::If(c, t, e) => RStatementEnum::If(
|
||||
{
|
||||
let condition = statement(&c, ginfo, linfo)?;
|
||||
let out = condition.out(ginfo).fits_in(
|
||||
&VType {
|
||||
types: vec![VSingleType::Bool],
|
||||
},
|
||||
ginfo,
|
||||
);
|
||||
if out.is_empty() {
|
||||
condition
|
||||
} else {
|
||||
return Err(ToRunnableError::InvalidType {
|
||||
expected: VSingleType::Bool.into(),
|
||||
found: condition.out(ginfo),
|
||||
problematic: VType { types: out },
|
||||
});
|
||||
}
|
||||
},
|
||||
statement(&t, ginfo, linfo)?,
|
||||
match e {
|
||||
Some(v) => Some(statement(&v, ginfo, linfo)?),
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
.to(),
|
||||
SStatementEnum::Loop(c) => RStatementEnum::Loop(statement(&c, ginfo, linfo)?).to(),
|
||||
SStatementEnum::For(v, c, b) => {
|
||||
let mut linfo = linfo.clone();
|
||||
let container = statement(&c, ginfo, &mut linfo)?;
|
||||
let inner = container.out(ginfo).inner_types_for_iters(ginfo);
|
||||
if inner.types.is_empty() {
|
||||
return Err(ToRunnableError::ForLoopContainerHasNoInnerTypes);
|
||||
}
|
||||
let assign_to = statement_adv(v, ginfo, &mut linfo, &mut Some((inner, &mut true)))?;
|
||||
let block = statement(&b, ginfo, &mut linfo)?;
|
||||
let o = RStatementEnum::For(assign_to, container, block);
|
||||
o.to()
|
||||
}
|
||||
|
||||
SStatementEnum::Switch(switch_on, cases, force) => {
|
||||
let mut ncases = Vec::with_capacity(cases.len());
|
||||
let switch_on = statement(switch_on, ginfo, linfo)?;
|
||||
let og_type = switch_on.out(ginfo);
|
||||
let mut covered_types = VType::empty();
|
||||
for (case_type, case_assign_to, case_action) in cases.iter() {
|
||||
let mut linfo = linfo.clone();
|
||||
let case_type = {
|
||||
let mut v = case_type.clone();
|
||||
stypes(&mut v, ginfo)?;
|
||||
v
|
||||
};
|
||||
covered_types.add_typesr(&case_type, ginfo);
|
||||
ncases.push((
|
||||
case_type.clone(),
|
||||
statement_adv(
|
||||
case_assign_to,
|
||||
ginfo,
|
||||
&mut linfo,
|
||||
&mut Some((case_type, &mut true)),
|
||||
)?,
|
||||
statement(case_action, ginfo, &mut linfo)?,
|
||||
));
|
||||
}
|
||||
if *force {
|
||||
let types_not_covered = og_type.fits_in(&covered_types, ginfo);
|
||||
if !types_not_covered.is_empty() {
|
||||
return Err(ToRunnableError::CaseForceButTypeNotCovered(VType {
|
||||
types: types_not_covered,
|
||||
}));
|
||||
}
|
||||
}
|
||||
RStatementEnum::Switch(switch_on, ncases, *force).to()
|
||||
}
|
||||
SStatementEnum::Match(cases) => {
|
||||
let _ncases: Vec<(RStatement, RStatement, RStatement)> =
|
||||
Vec::with_capacity(cases.len());
|
||||
let mut ncases = Vec::with_capacity(cases.len());
|
||||
let mut out_type = VType::empty();
|
||||
let may_not_match = true;
|
||||
for (condition, assign_to, action) in cases.iter() {
|
||||
let mut linfo = linfo.clone();
|
||||
let condition = statement(condition, ginfo, &mut linfo)?;
|
||||
let (can_fail, matches) = condition.out(ginfo).matches(ginfo);
|
||||
let assign_to = statement_adv(
|
||||
assign_to,
|
||||
ginfo,
|
||||
&mut linfo,
|
||||
&mut Some((matches, &mut true)),
|
||||
)?;
|
||||
let action = statement(action, ginfo, &mut linfo)?;
|
||||
ncases.push((condition, assign_to, action));
|
||||
if !can_fail {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if may_not_match {
|
||||
out_type.add_type(VSingleType::Tuple(vec![]), ginfo);
|
||||
}
|
||||
|
||||
RStatementEnum::Match(ncases).to()
|
||||
}
|
||||
|
||||
SStatementEnum::IndexFixed(st, i) => {
|
||||
let st = statement(st, ginfo, linfo)?;
|
||||
if st.out(ginfo).get_always(*i, ginfo).is_some() {
|
||||
RStatementEnum::IndexFixed(st, *i).to()
|
||||
} else {
|
||||
return Err(ToRunnableError::NotIndexableFixed(st.out(ginfo), *i));
|
||||
}
|
||||
}
|
||||
SStatementEnum::EnumVariant(variant, s) => RStatementEnum::EnumVariant(
|
||||
{
|
||||
if let Some(v) = ginfo.enum_variants.get(variant) {
|
||||
*v
|
||||
} else {
|
||||
let v = ginfo.enum_variants.len();
|
||||
ginfo.enum_variants.insert(variant.clone(), v);
|
||||
v
|
||||
}
|
||||
},
|
||||
statement(s, ginfo, linfo)?,
|
||||
)
|
||||
.to(),
|
||||
SStatementEnum::TypeDefinition(name, t) => {
|
||||
// insert to name map has to happen before stypes()
|
||||
ginfo
|
||||
.custom_type_names
|
||||
.insert(name.to_lowercase(), ginfo.custom_types.len());
|
||||
let mut t = t.to_owned();
|
||||
stypes(&mut t, ginfo)?;
|
||||
ginfo.custom_types.push(t);
|
||||
RStatementEnum::Value(VDataEnum::Tuple(vec![]).to()).to()
|
||||
}
|
||||
SStatementEnum::Macro(m) => match m {
|
||||
Macro::StaticMers(val) => RStatementEnum::Value(val.clone()).to(),
|
||||
},
|
||||
};
|
||||
state.derefs = s.derefs;
|
||||
// if force_output_type is set, verify that the real output type actually fits in the forced one.
|
||||
if let Some(force_opt) = &s.force_output_type {
|
||||
let mut force_opt = force_opt.to_owned();
|
||||
stypes(&mut force_opt, ginfo)?;
|
||||
let real_output_type = state.out(ginfo);
|
||||
let problematic_types = real_output_type.fits_in(&force_opt, ginfo);
|
||||
if problematic_types.is_empty() {
|
||||
state.force_output_type = Some(force_opt);
|
||||
} else {
|
||||
return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types }));
|
||||
}
|
||||
}
|
||||
if let Some((opt, is_init)) = &s.output_to {
|
||||
// if false, may be changed to true by statement_adv
|
||||
let mut is_init = *is_init;
|
||||
let optr = statement_adv(
|
||||
opt,
|
||||
ginfo,
|
||||
linfo,
|
||||
&mut Some((state.out(ginfo), &mut is_init)),
|
||||
)?;
|
||||
state.output_to = Some((Box::new(optr), is_init));
|
||||
//
|
||||
// if let Some((var_id, var_out)) = linfo.vars.get(opt) {
|
||||
// let out = state.out(ginfo);
|
||||
// let mut var_derefd = var_out.clone();
|
||||
// for _ in 0..*derefs {
|
||||
// var_derefd = if let Some(v) = var_derefd.dereference() {
|
||||
// v
|
||||
// } else {
|
||||
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
|
||||
// var_out.clone(),
|
||||
// *derefs,
|
||||
// var_derefd,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// let inv_types = out.fits_in(&var_derefd, ginfo);
|
||||
// if !inv_types.is_empty() {
|
||||
// eprintln!("Warn: shadowing variable {opt} because statement's output type {out} does not fit in the original variable's {var_out}. This might become an error in the future, or it might stop shadowing the variiable entirely - for stable scripts, avoid this by giving the variable a different name.");
|
||||
// if *derefs != 0 {
|
||||
// return Err(ToRunnableError::CannotDeclareVariableWithDereference(
|
||||
// opt.clone(),
|
||||
// ));
|
||||
// }
|
||||
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
|
||||
// state.output_to = Some((ginfo.vars, 0, true));
|
||||
// ginfo.vars += 1;
|
||||
// } else {
|
||||
// // mutate existing variable
|
||||
// state.output_to = Some((*var_id, *derefs, false));
|
||||
// }
|
||||
// } else {
|
||||
// let mut out = state.out(ginfo);
|
||||
// for _ in 0..*derefs {
|
||||
// out = if let Some(v) = out.dereference() {
|
||||
// v
|
||||
// } else {
|
||||
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
|
||||
// state.out(ginfo),
|
||||
// *derefs,
|
||||
// out,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
|
||||
// state.output_to = Some((ginfo.vars, *derefs, true));
|
||||
// ginfo.vars += 1;
|
||||
// }
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
586
old/mers/src/lang/val_data.rs
Executable file
586
old/mers/src/lang/val_data.rs
Executable file
@@ -0,0 +1,586 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
code_runnable::RFunction,
|
||||
fmtgs::FormatGs,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VDataEnum {
|
||||
Bool(bool),
|
||||
Int(isize),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Tuple(Vec<VData>),
|
||||
List(VType, Vec<VData>),
|
||||
Function(Arc<RFunction>),
|
||||
Thread(thread::VDataThread, VType),
|
||||
Reference(VData),
|
||||
EnumVariant(usize, Box<VData>),
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub struct VData(Arc<Mutex<VDataInner>>);
|
||||
#[cfg(debug_assertions)]
|
||||
pub struct VData(pub Arc<Mutex<VDataInner>>, pub Option<String>);
|
||||
pub enum VDataInner {
|
||||
Data(usize, Box<VDataEnum>),
|
||||
Mut(Arc<Mutex<VData>>),
|
||||
ClonedFrom(VData),
|
||||
}
|
||||
/// can be either Data, Mut or ClonedFrom.
|
||||
/// - any ClonedFrom will point to a Data variant. It can never point to anything else.
|
||||
/// it will increase the Data's clone count by one on creation and decrease it again on Drop::drop().
|
||||
/// - any Mut will eventually point to a ClonedFrom or a Data variant. It can also point to another Mut.
|
||||
impl VDataInner {
|
||||
fn to(self) -> VData {
|
||||
#[cfg(not(debug_assertions))]
|
||||
return VData(Arc::new(Mutex::new(self)));
|
||||
#[cfg(debug_assertions)]
|
||||
return VData(Arc::new(Mutex::new(self)), None);
|
||||
}
|
||||
}
|
||||
impl VDataEnum {
|
||||
pub fn to(self) -> VData {
|
||||
VDataInner::Data(0, Box::new(self)).to()
|
||||
}
|
||||
}
|
||||
|
||||
impl VData {
|
||||
pub fn new_placeholder() -> Self {
|
||||
VDataEnum::Bool(false).to()
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn new_placeholder_with_name(name: String) -> Self {
|
||||
let mut o = VDataEnum::Bool(false).to();
|
||||
o.1 = Some(name);
|
||||
o
|
||||
}
|
||||
/// clones self, retrurning a new instance of self that will always yield the value self had when this function was called.
|
||||
/// note to dev: since the actual data is stored in VDataEnum, which either clones data or calls clone() (= clone_data()) on further VData, this automatically borrows all child data as immutable too. rust's Drop::drop() implementation (probably) handles everything for us too, so this can be implemented without thinking about recursion.
|
||||
pub fn clone_data(&self) -> Self {
|
||||
// TODO! implement CopyOnWrite. For now, just always copy. This also prevents mut references not existing since in ::Dat(cloned, _), cloned will always stay 0.
|
||||
return self.operate_on_data_immut(|v| v.clone()).to();
|
||||
// match &mut *self.0.lock().unwrap() {
|
||||
// VDataInner::Data(cloned, _data) => {
|
||||
// *cloned += 1;
|
||||
// VDataInner::ClonedFrom(self.clone_arc()).to()
|
||||
// }
|
||||
// VDataInner::Mut(inner) => inner.lock().unwrap().clone_data(),
|
||||
// VDataInner::ClonedFrom(inner) => inner.clone_data(),
|
||||
// }
|
||||
}
|
||||
/// clones self, returning a new instance of self that will always yield the same data as self, so that changes done to either are shared between both.
|
||||
pub fn clone_mut(&self) -> Self {
|
||||
VDataInner::Mut(Arc::new(Mutex::new(self.clone_arc()))).to()
|
||||
}
|
||||
fn clone_arc(&self) -> Self {
|
||||
#[cfg(not(debug_assertions))]
|
||||
return Self(Arc::clone(&self.0));
|
||||
#[cfg(debug_assertions)]
|
||||
return Self(Arc::clone(&self.0), self.1.clone());
|
||||
}
|
||||
pub fn operate_on_data_immut<F, O>(&self, func: F) -> O
|
||||
where
|
||||
F: FnOnce(&VDataEnum) -> O,
|
||||
{
|
||||
match &*self.0.lock().unwrap() {
|
||||
VDataInner::Data(_, data) => func(data.as_ref()),
|
||||
VDataInner::Mut(inner) => inner.lock().unwrap().operate_on_data_immut(func),
|
||||
VDataInner::ClonedFrom(inner) => inner.operate_on_data_immut(func),
|
||||
}
|
||||
}
|
||||
/// runs func on the underlying data.
|
||||
/// attempts to get a mutable reference to the data. if this fails, it will (partially) clone the data, then point the VData to the new data,
|
||||
/// so that other VDatas pointing to the same original data aren't changed.
|
||||
pub fn operate_on_data_mut<F, O>(&mut self, func: F) -> O
|
||||
where
|
||||
F: FnOnce(&mut VDataEnum) -> O,
|
||||
{
|
||||
let (new_val, o) = {
|
||||
let mut lock = self.0.lock().unwrap();
|
||||
match &mut *lock {
|
||||
VDataInner::Data(count, data) => {
|
||||
if *count == 0 {
|
||||
(None, func(data.as_mut()))
|
||||
} else {
|
||||
let mut new_data = data.clone();
|
||||
let o = func(new_data.as_mut());
|
||||
// *self doesn't modify the ::Data, it instead points the value that wraps it to a new ::Data, leaving the old one as it was.
|
||||
// for proof: data is untouched, only the new_data is ever modified.
|
||||
let new_vdata = VDataInner::Data(0, new_data).to();
|
||||
(Some(new_vdata), o)
|
||||
}
|
||||
}
|
||||
VDataInner::Mut(inner) => (None, inner.lock().unwrap().operate_on_data_mut(func)),
|
||||
VDataInner::ClonedFrom(inner) => (None, inner.operate_on_data_mut(func)),
|
||||
}
|
||||
};
|
||||
if let Some(nv) = new_val {
|
||||
*self = nv;
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
/// Since operate_on_data_mut can clone, it may be inefficient for just assigning (where we don't care about the previous value, so it doesn't need to be cloned).
|
||||
/// This is what this function is for. (TODO: actually make it more efficient instead of using operate_on_data_mut)
|
||||
pub fn assign_data(&mut self, new_data: VDataEnum) {
|
||||
let o = self.operate_on_data_mut(|d| *d = new_data);
|
||||
o
|
||||
}
|
||||
/// Assigns the new_data to self. Affects all muts pointing to the same data, but no ClonedFroms.
|
||||
pub fn assign(&mut self, new: VData) {
|
||||
self.assign_data(new.inner_cloned())
|
||||
// !PROBLEM! If ClonedFrom always has to point to a Data, this may break things!
|
||||
// match &mut *self.0.lock().unwrap() {
|
||||
// VDataInner::Data(count, data) => {
|
||||
// // *self doesn't modify the ::Data, it instead points the value that wraps it to a new ::Data, leaving the old one as it was.
|
||||
// // for proof: data is untouched.
|
||||
// *self = new_data;
|
||||
// }
|
||||
// VDataInner::Mut(inner) => inner.lock().unwrap().assign(new_data),
|
||||
// VDataInner::ClonedFrom(inner) => inner.assign(new_data),
|
||||
// }
|
||||
}
|
||||
/// assigns the value from self to assign_to if it's a reference, performs destructuring, and panics on invalid types that cannot be assigned to.
|
||||
pub fn assign_to(self: VData, mut assign_to: VData, info: &GSInfo) {
|
||||
// eprintln!("Assigning {self} to {assign_to}");
|
||||
assign_to.operate_on_data_mut(|assign_to| match assign_to {
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
self.get(i)
|
||||
.expect(
|
||||
"tried to assign to tuple, but value didn't return Some(_) on get()",
|
||||
)
|
||||
.assign_to(v.clone_data(), info)
|
||||
}
|
||||
}
|
||||
VDataEnum::Reference(r) => r.assign(self),
|
||||
o => todo!("ERR: Cannot assign to {o}."),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Drop for VDataInner {
|
||||
fn drop(&mut self) {
|
||||
if let Self::ClonedFrom(origin) = self {
|
||||
if let Self::Data(_ref_count, _data) = &mut *origin.0.lock().unwrap() {
|
||||
// *ref_count = ref_count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VData {
|
||||
/// this will always clone! if a reference or mutable reference is enough, use operate_on_data_* instead!
|
||||
pub fn inner_cloned(&self) -> VDataEnum {
|
||||
self.operate_on_data_immut(|v| v.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// - - make VData act like VDataEnum (as if it were real data) - -
|
||||
|
||||
impl Clone for VData {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_data()
|
||||
}
|
||||
}
|
||||
impl FormatGs for VData {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
self.operate_on_data_immut(|v| v.fmtgs(f, info, form, file))
|
||||
}
|
||||
}
|
||||
impl Debug for VData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.operate_on_data_immut(|v| Debug::fmt(v, f))
|
||||
}
|
||||
}
|
||||
impl Display for VData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.operate_on_data_immut(|v| Display::fmt(v, f))
|
||||
}
|
||||
}
|
||||
impl PartialEq for VData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.operate_on_data_immut(|a| other.operate_on_data_immut(|b| a == b))
|
||||
}
|
||||
}
|
||||
impl PartialEq<VDataEnum> for VData {
|
||||
fn eq(&self, other: &VDataEnum) -> bool {
|
||||
self.operate_on_data_immut(|a| a == other)
|
||||
}
|
||||
}
|
||||
impl PartialEq<VData> for VDataEnum {
|
||||
fn eq(&self, other: &VData) -> bool {
|
||||
other.operate_on_data_immut(|b| self == b)
|
||||
}
|
||||
}
|
||||
impl VData {
|
||||
pub fn out_single(&self) -> VSingleType {
|
||||
self.operate_on_data_immut(|v| v.out_single())
|
||||
}
|
||||
pub fn out(&self) -> VType {
|
||||
self.out_single().to()
|
||||
}
|
||||
pub fn noenum(&self) -> Self {
|
||||
if let Some(v) = self.operate_on_data_immut(|v| v.noenum()) {
|
||||
v
|
||||
} else {
|
||||
self.clone_data()
|
||||
}
|
||||
}
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
self.operate_on_data_immut(|v| v.safe_to_share())
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<VData> {
|
||||
self.operate_on_data_immut(|v| v.get(i))
|
||||
}
|
||||
pub fn get_ref(&mut self, i: usize) -> Option<VData> {
|
||||
self.operate_on_data_mut(|v| v.get_ref(i))
|
||||
}
|
||||
pub fn matches(&self) -> Option<Self> {
|
||||
match self.operate_on_data_immut(|v| v.matches()) {
|
||||
Some(Some(v)) => Some(v),
|
||||
Some(None) => Some(self.clone_data()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn deref(&self) -> Option<Self> {
|
||||
self.operate_on_data_immut(|v| v.deref())
|
||||
}
|
||||
}
|
||||
|
||||
// - - VDataEnum - -
|
||||
|
||||
impl Clone for VDataEnum {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
// exception: don't clone the value AND don't use CoW,
|
||||
// because we want to share the same Arc<Mutex<_>>.
|
||||
Self::Reference(r) => Self::Reference(r.clone_mut()),
|
||||
// default impls
|
||||
Self::Bool(b) => Self::Bool(*b),
|
||||
Self::Int(i) => Self::Int(*i),
|
||||
Self::Float(f) => Self::Float(*f),
|
||||
Self::String(s) => Self::String(s.clone()),
|
||||
Self::Tuple(v) => Self::Tuple(v.clone()),
|
||||
Self::List(t, v) => Self::List(t.clone(), v.clone()),
|
||||
Self::Function(f) => Self::Function(f.clone()),
|
||||
Self::Thread(th, ty) => Self::Thread(th.clone(), ty.clone()),
|
||||
Self::EnumVariant(v, d) => Self::EnumVariant(v.clone(), d.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq for VDataEnum {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Reference(a), Self::Reference(b)) => a == b,
|
||||
(Self::Reference(a), b) => a == b,
|
||||
(a, Self::Reference(b)) => a == b,
|
||||
(Self::Bool(a), Self::Bool(b)) => *a == *b,
|
||||
(Self::Int(a), Self::Int(b)) => *a == *b,
|
||||
(Self::Float(a), Self::Float(b)) => *a == *b,
|
||||
(Self::String(a), Self::String(b)) => *a == *b,
|
||||
(Self::Tuple(a), Self::Tuple(b)) | (Self::List(_, a), Self::List(_, b)) => {
|
||||
a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a == b)
|
||||
}
|
||||
(Self::EnumVariant(a1, a2), Self::EnumVariant(b1, b2)) => *a1 == *b1 && *a2 == *b2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VDataEnum {
|
||||
pub fn deref(&self) -> Option<VData> {
|
||||
if let Self::Reference(r) = self {
|
||||
Some(r.clone_mut())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn out_single(&self) -> VSingleType {
|
||||
match self {
|
||||
Self::Bool(..) => VSingleType::Bool,
|
||||
Self::Int(..) => VSingleType::Int,
|
||||
Self::Float(..) => VSingleType::Float,
|
||||
Self::String(..) => VSingleType::String,
|
||||
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out_single().to()).collect()),
|
||||
Self::List(t, _) => VSingleType::List(t.clone()),
|
||||
Self::Function(f) => VSingleType::Function(f.out_map.clone()),
|
||||
Self::Thread(_, o) => VSingleType::Thread(o.clone()),
|
||||
Self::Reference(r) => VSingleType::Reference(r.out()),
|
||||
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out_single().to()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get()
|
||||
impl VDataEnum {
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
match self {
|
||||
Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Function(_) => {
|
||||
true
|
||||
}
|
||||
Self::Tuple(v) | Self::List(_, v) => v.iter().all(|v| v.safe_to_share()),
|
||||
Self::Thread(..) | Self::Reference(..) | Self::EnumVariant(..) => false,
|
||||
}
|
||||
}
|
||||
pub fn noenum(&self) -> Option<VData> {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) => Some(v.clone_data()),
|
||||
_v => None,
|
||||
}
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<VData> {
|
||||
match self {
|
||||
Self::Bool(..)
|
||||
| Self::Int(..)
|
||||
| Self::Float(..)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
Self::String(s) => match s.chars().nth(i) {
|
||||
// Slow!
|
||||
Some(ch) => Some(Self::String(format!("{ch}")).to()),
|
||||
None => None,
|
||||
},
|
||||
Self::Tuple(v) | Self::List(_, v) => v.get(i).cloned(),
|
||||
Self::Reference(r) => r.clone_mut().get_ref(i),
|
||||
Self::EnumVariant(_, v) => v.get(i),
|
||||
}
|
||||
}
|
||||
/// this is guaranteed to return Self::Reference(_), if it returns Some(_).
|
||||
pub fn get_ref(&mut self, i: usize) -> Option<VData> {
|
||||
Some(Self::Reference(self.get_ref_inner(i)?).to())
|
||||
}
|
||||
pub fn get_ref_inner(&mut self, i: usize) -> Option<VData> {
|
||||
match self {
|
||||
Self::Bool(..)
|
||||
| Self::Int(..)
|
||||
| Self::Float(..)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
// TODO: String
|
||||
Self::String(_s) => None,
|
||||
Self::Tuple(v) | Self::List(_, v) => v.get(i).map(|v| v.clone_mut()),
|
||||
Self::Reference(r) => r.get_ref(i),
|
||||
Self::EnumVariant(_, v) => v.get_ref(i),
|
||||
}
|
||||
}
|
||||
/// Some(None) => matches with self
|
||||
pub fn matches(&self) -> Option<Option<VData>> {
|
||||
match self {
|
||||
VDataEnum::Tuple(tuple) => tuple.get(0).cloned().map(|v| Some(v)),
|
||||
VDataEnum::Bool(v) => {
|
||||
if *v {
|
||||
Some(Some(VDataEnum::Bool(true).to()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VDataEnum::EnumVariant(..) => None,
|
||||
_other => Some(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VSingleType {
|
||||
/// returns (can_fail_to_match, matches_as)
|
||||
pub fn matches(&self) -> (bool, VType) {
|
||||
match self {
|
||||
Self::Tuple(v) => match v.first() {
|
||||
Some(v) => (false, v.clone()),
|
||||
None => (true, VType { types: vec![] }),
|
||||
},
|
||||
Self::Bool => (true, Self::Bool.to()),
|
||||
Self::EnumVariant(..) | Self::EnumVariantS(..) => (true, VType { types: vec![] }),
|
||||
v => (false, v.clone().to()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
/// returns (can_fail_to_match, matches_as)
|
||||
pub fn matches(&self, info: &GlobalScriptInfo) -> (bool, VType) {
|
||||
let mut can_fail = false;
|
||||
let mut matches_as = VType { types: vec![] };
|
||||
for t in self.types.iter() {
|
||||
let (f, t) = t.matches();
|
||||
can_fail |= f;
|
||||
matches_as.add_types(t, info);
|
||||
}
|
||||
(can_fail, matches_as)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod thread {
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
thread::JoinHandle,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{VData, VDataEnum};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VDataThread(Arc<Mutex<VDataThreadEnum>>);
|
||||
impl VDataThread {
|
||||
pub fn try_get(&self) -> Option<VData> {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(_) => None,
|
||||
VDataThreadEnum::Finished(v) => Some(v.clone()),
|
||||
}
|
||||
}
|
||||
pub fn get(&self) -> VData {
|
||||
let dur = Duration::from_millis(100);
|
||||
loop {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
while !v.is_finished() {
|
||||
std::thread::sleep(dur);
|
||||
}
|
||||
}
|
||||
VDataThreadEnum::Finished(v) => return v.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn lock(&self) -> std::sync::MutexGuard<VDataThreadEnum> {
|
||||
let mut mg = self.0.lock().unwrap();
|
||||
match &*mg {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
if v.is_finished() {
|
||||
let m = std::mem::replace(
|
||||
&mut *mg,
|
||||
VDataThreadEnum::Finished(VDataEnum::Bool(false).to()),
|
||||
);
|
||||
match m {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
*mg = VDataThreadEnum::Finished(v.join().unwrap())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
mg
|
||||
}
|
||||
}
|
||||
impl Debug for VDataThread {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(_) => write!(f, "(thread running)"),
|
||||
VDataThreadEnum::Finished(_v) => write!(f, "(thread finished)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum VDataThreadEnum {
|
||||
Running(JoinHandle<VData>),
|
||||
Finished(VData),
|
||||
}
|
||||
impl VDataThreadEnum {
|
||||
pub fn to(self) -> VDataThread {
|
||||
VDataThread(Arc::new(Mutex::new(self)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct VDataWInfo(VData, GSInfo);
|
||||
impl Display for VDataWInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmtgs(
|
||||
f,
|
||||
Some(&self.1),
|
||||
&mut super::fmtgs::FormatInfo::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl VData {
|
||||
pub fn gsi(self, info: GSInfo) -> VDataWInfo {
|
||||
VDataWInfo(self, info)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VDataEnum {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Bool(true) => write!(f, "true"),
|
||||
Self::Bool(false) => write!(f, "false"),
|
||||
Self::Int(v) => write!(f, "{v}"),
|
||||
Self::Float(v) => write!(f, "{v}"),
|
||||
Self::String(v) => write!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
form.value_string_quotes(info, "\"".to_owned()),
|
||||
form.value_string_content(info, v.to_owned()),
|
||||
form.value_string_quotes(info, "\"".to_owned())
|
||||
),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "[")?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::List(_t, v) => {
|
||||
write!(f, "[")?;
|
||||
for (_i, v) in v.iter().enumerate() {
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "...]")
|
||||
}
|
||||
Self::Function(func) => {
|
||||
VSingleType::Function(func.out_map.clone()).fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Thread(..) => write!(f, "[TODO] THREAD"),
|
||||
Self::Reference(inner) => {
|
||||
write!(f, "&")?;
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
if let Some(name) = if let Some(info) = info {
|
||||
info.enum_variants.iter().find_map(|(name, id)| {
|
||||
if id == variant {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
write!(f, "{name}: ")?;
|
||||
} else {
|
||||
write!(f, "{variant}: ")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for VDataEnum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
593
old/mers/src/lang/val_type.rs
Executable file
593
old/mers/src/lang/val_type.rs
Executable file
@@ -0,0 +1,593 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
};
|
||||
|
||||
use super::{
|
||||
code_runnable::{RFunction, RFunctionType},
|
||||
fmtgs::FormatGs,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
};
|
||||
|
||||
use super::global_info::LogMsg;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VType {
|
||||
pub types: Vec<VSingleType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VSingleType {
|
||||
Any,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Tuple(Vec<VType>),
|
||||
List(VType),
|
||||
Function(Vec<(Vec<VType>, VType)>),
|
||||
Thread(VType),
|
||||
Reference(VType),
|
||||
EnumVariant(usize, VType),
|
||||
EnumVariantS(String, VType),
|
||||
CustomType(usize),
|
||||
CustomTypeS(String),
|
||||
}
|
||||
|
||||
impl VType {
|
||||
pub fn empty() -> Self {
|
||||
Self { types: vec![] }
|
||||
}
|
||||
pub fn get(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get(i, info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
pub fn get_always(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get_always(i, info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
/// returns Some(true) or Some(false) if all types are references or not references. If it is mixed or types is empty, returns None.
|
||||
pub fn is_reference(&self) -> Option<bool> {
|
||||
let mut noref = false;
|
||||
let mut reference = false;
|
||||
for t in &self.types {
|
||||
if t.is_reference() {
|
||||
reference = true;
|
||||
} else {
|
||||
noref = true;
|
||||
}
|
||||
}
|
||||
if noref != reference {
|
||||
Some(reference)
|
||||
} else {
|
||||
// either empty (false == false) or mixed (true == true)
|
||||
None
|
||||
}
|
||||
}
|
||||
/// returns Some(t) where t is the type you get from dereferencing self or None if self contains even a single type that cannot be dereferenced.
|
||||
pub fn dereference(&self, info: &GlobalScriptInfo) -> Option<Self> {
|
||||
let mut out = Self::empty();
|
||||
for t in self.types.iter() {
|
||||
out.add_types(t.deref()?, info);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
pub fn reference(&self) -> Self {
|
||||
VSingleType::Reference(self.clone()).to()
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
/// None => Cannot get, Some(t) => getting can return t or nothing
|
||||
pub fn get(&self, i: usize, gsinfo: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..)
|
||||
| Self::EnumVariant(..)
|
||||
| Self::EnumVariantS(..) => None,
|
||||
Self::String => Some(VSingleType::String.into()),
|
||||
Self::Tuple(t) => t.get(i).cloned(),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => Some(r.get(i, gsinfo)?.reference()),
|
||||
Self::CustomType(t) => gsinfo.custom_types[*t].get(i, gsinfo),
|
||||
&Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomTypeS instead of CustomType, compiler bug? [get]")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_any(&self, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
Self::String => Some(VSingleType::String.into()),
|
||||
Self::Tuple(t) => Some(t.iter().fold(VType::empty(), |mut a, b| {
|
||||
a.add_typesr(b, info);
|
||||
a
|
||||
})),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => Some(VType {
|
||||
// this is &a/&b/..., NOT &(a/b/...)!
|
||||
types: r
|
||||
.get_any(info)?
|
||||
.types
|
||||
.iter()
|
||||
.map(|v| VSingleType::Reference(v.clone().to()))
|
||||
.collect(),
|
||||
}),
|
||||
Self::EnumVariant(_, t) => t.get_any(info),
|
||||
Self::EnumVariantS(..) => unreachable!(),
|
||||
Self::CustomType(t) => info.custom_types[*t].get_any(info),
|
||||
Self::CustomTypeS(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
/// None => might not always return t, Some(t) => can only return t
|
||||
pub fn get_always(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::String
|
||||
| Self::List(_)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..)
|
||||
| Self::EnumVariant(..)
|
||||
| Self::EnumVariantS(..) => None,
|
||||
Self::Tuple(t) => t.get(i).cloned(),
|
||||
Self::Reference(r) => Some(VSingleType::Reference(r.get_any(info)?).to()),
|
||||
Self::CustomType(t) => info.custom_types[*t].get_always(i, info),
|
||||
Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomTypeS instead of CustomType, compiler bug? [get_always]")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn is_reference(&self) -> bool {
|
||||
match self {
|
||||
Self::Reference(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn deref(&self) -> Option<VType> {
|
||||
if let Self::Reference(v) = self {
|
||||
Some(v.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn get_any(&self, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get_any(info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl VType {
|
||||
/// Returns a vec with all types in self that aren't covered by rhs. If the returned vec is empty, self fits in rhs.
|
||||
pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> Vec<VSingleType> {
|
||||
let mut no = vec![];
|
||||
for t in &self.types {
|
||||
// if t doesnt fit in any of rhs's types
|
||||
if !t.fits_in_type(rhs, info) {
|
||||
no.push(t.clone())
|
||||
}
|
||||
}
|
||||
if info.log.vtype_fits_in.log() {
|
||||
info.log
|
||||
.log(LogMsg::VTypeFitsIn(self.clone(), rhs.clone(), no.clone()))
|
||||
}
|
||||
no
|
||||
}
|
||||
pub fn inner_types_for_iters(&self, info: &GlobalScriptInfo) -> VType {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.inner_types_for_iters(info), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
pub fn enum_variants(&mut self, enum_variants: &mut HashMap<String, usize>) {
|
||||
for t in &mut self.types {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
pub fn contains(&self, t: &VSingleType, info: &GlobalScriptInfo) -> bool {
|
||||
t.fits_in_type(self, info)
|
||||
}
|
||||
pub fn noenum(self, info: &GlobalScriptInfo) -> Self {
|
||||
let mut o = Self { types: vec![] };
|
||||
for t in self.types {
|
||||
o.add_types(t.noenum(), info);
|
||||
}
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
impl VType {
|
||||
pub fn eq(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
self.fits_in(rhs, info).is_empty() && rhs.fits_in(self, info).is_empty()
|
||||
}
|
||||
}
|
||||
impl VSingleType {
|
||||
pub fn eq(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
self.fits_in(rhs, info) && rhs.fits_in(self, info)
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn add_types(&mut self, new: Self, info: &GlobalScriptInfo) {
|
||||
for t in new.types {
|
||||
self.add_type(t, info)
|
||||
}
|
||||
}
|
||||
pub fn add_type(&mut self, new: VSingleType, info: &GlobalScriptInfo) {
|
||||
if !self.contains(&new, info) {
|
||||
self.types.push(new)
|
||||
}
|
||||
}
|
||||
pub fn add_typesr(&mut self, new: &Self, info: &GlobalScriptInfo) {
|
||||
for t in &new.types {
|
||||
self.add_typer(t, info)
|
||||
}
|
||||
}
|
||||
pub fn add_typer(&mut self, new: &VSingleType, info: &GlobalScriptInfo) {
|
||||
if !self.contains(new, info) {
|
||||
self.types.push(new.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
pub fn to(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
pub fn inner_types_for_iters(&self, info: &GlobalScriptInfo) -> VType {
|
||||
match self {
|
||||
Self::Tuple(v) => {
|
||||
let mut out = VType::empty();
|
||||
for it in v {
|
||||
out.add_typesr(it, info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::List(v) => v.clone(),
|
||||
// NOTE: to make ints work in for loops
|
||||
Self::Int => Self::Int.to(),
|
||||
// for iterators in for loops: the match of the function's returned value make up the inner type
|
||||
Self::Function(f) => {
|
||||
// function that takes no inputs
|
||||
if let Some(out) = f.iter().find_map(|(args, out)| {
|
||||
if args.is_empty() {
|
||||
Some(out.clone().matches(info).1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
out
|
||||
} else {
|
||||
VType::empty()
|
||||
}
|
||||
}
|
||||
Self::Reference(r) => r.inner_types_for_iters(info).reference(),
|
||||
_ => VType::empty(),
|
||||
}
|
||||
}
|
||||
pub fn noenum(self) -> VType {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) | Self::EnumVariantS(_, v) => v,
|
||||
v => v.to(),
|
||||
}
|
||||
}
|
||||
/// converts all Self::EnumVariantS to Self::EnumVariant
|
||||
pub fn enum_variants(&mut self, enum_variants: &mut HashMap<String, usize>) {
|
||||
match self {
|
||||
Self::Any | Self::Bool | Self::Int | Self::Float | Self::String => (),
|
||||
Self::Tuple(v) => {
|
||||
for t in v {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
Self::List(t) => t.enum_variants(enum_variants),
|
||||
Self::Function(f) => {
|
||||
for f in f {
|
||||
for t in &mut f.0 {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
f.1.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
Self::Thread(v) => v.enum_variants(enum_variants),
|
||||
Self::Reference(v) => v.enum_variants(enum_variants),
|
||||
Self::EnumVariant(_e, v) => v.enum_variants(enum_variants),
|
||||
Self::EnumVariantS(e, v) => {
|
||||
let e = if let Some(e) = enum_variants.get(e) {
|
||||
*e
|
||||
} else {
|
||||
let v = enum_variants.len();
|
||||
enum_variants.insert(e.clone(), v);
|
||||
v
|
||||
};
|
||||
v.enum_variants(enum_variants);
|
||||
*self = Self::EnumVariant(e, v.clone());
|
||||
}
|
||||
Self::CustomType(_) | Self::CustomTypeS(_) => (),
|
||||
}
|
||||
}
|
||||
pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
let o = match (self, rhs) {
|
||||
(_, Self::Any) => true,
|
||||
(Self::Any, _) => false,
|
||||
// references have to be eq, not fits_in; otherwise whoever gets our reference could write invalid data to it!
|
||||
(Self::Reference(a), Self::Reference(b)) => a.eq(b, info),
|
||||
(Self::Reference(_), _) | (_, Self::Reference(_)) => false,
|
||||
(Self::EnumVariant(v1, t1), Self::EnumVariant(v2, t2)) => {
|
||||
*v1 == *v2 && t1.fits_in(&t2, info).is_empty()
|
||||
}
|
||||
(Self::CustomType(a), Self::CustomType(b)) => *a == *b, /* || info.custom_types[*a].fits_in(&info.custom_types[*b], info).is_empty() */
|
||||
(Self::CustomType(a), b) => info.custom_types[*a]
|
||||
.fits_in(&b.clone().to(), info)
|
||||
.is_empty(),
|
||||
(a, Self::CustomType(b)) => a
|
||||
.clone()
|
||||
.to()
|
||||
.fits_in(&info.custom_types[*b], info)
|
||||
.is_empty(),
|
||||
(Self::CustomTypeS(_), _) | (_, Self::CustomTypeS(_)) => {
|
||||
unreachable!("CustomTypeS instead of CustomType - compiler bug?")
|
||||
}
|
||||
(Self::EnumVariant(..), _) | (_, Self::EnumVariant(..)) => false,
|
||||
(Self::EnumVariantS(..), _) | (_, Self::EnumVariantS(..)) => {
|
||||
unreachable!("EnumVariantS instead of EnumVariant - compiler bug?")
|
||||
}
|
||||
(Self::Bool, Self::Bool)
|
||||
| (Self::Int, Self::Int)
|
||||
| (Self::Float, Self::Float)
|
||||
| (Self::String, Self::String) => true,
|
||||
(Self::Bool | Self::Int | Self::Float | Self::String, _) => false,
|
||||
(Self::Tuple(a), Self::Tuple(b)) => {
|
||||
if a.len() == b.len() {
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.all(|(a, b)| a.fits_in(b, info).is_empty())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Self::Tuple(_), _) => false,
|
||||
(Self::List(a), Self::List(b)) => a.fits_in(b, info).is_empty(),
|
||||
(Self::List(_), _) => false,
|
||||
(Self::Function(a), Self::Function(b)) => 'fnt: {
|
||||
// since RFunction.out only uses out_map, we can create a dummy RFunction here.
|
||||
let af = RFunction {
|
||||
statement: RFunctionType::Dummy,
|
||||
out_map: a.clone(),
|
||||
};
|
||||
for (ins, out) in b {
|
||||
// try everything that would be valid for b
|
||||
if let Some(v) = af.out_by_map(ins, info) {
|
||||
if !v.fits_in(out, info).is_empty() {
|
||||
// found something that's valid for both, but a returns something that doesn't fit in what b would have returned -> a doesn't fit.
|
||||
break 'fnt false;
|
||||
}
|
||||
} else {
|
||||
// found something that's valid for b but not for a -> a doesn't fit.
|
||||
break 'fnt false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
(Self::Function(..), _) => false,
|
||||
(Self::Thread(a), Self::Thread(b)) => a.fits_in(b, info).is_empty(),
|
||||
(Self::Thread(..), _) => false,
|
||||
};
|
||||
if info.log.vsingletype_fits_in.log() {
|
||||
info.log
|
||||
.log(LogMsg::VSingleTypeFitsIn(self.clone(), rhs.clone(), o));
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn fits_in_type(&self, rhs: &VType, info: &GlobalScriptInfo) -> bool {
|
||||
match self {
|
||||
Self::CustomType(t) => {
|
||||
rhs.types.iter().any(|rhs| {
|
||||
if let Self::CustomType(rhs) = rhs {
|
||||
*t == *rhs
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) || info.custom_types[*t].fits_in(rhs, info).is_empty()
|
||||
}
|
||||
_ => rhs.types.iter().any(|b| self.fits_in(b, info)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<VType> for VSingleType {
|
||||
fn into(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct VTypeWInfo(VType, GSInfo);
|
||||
impl Display for VTypeWInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmtgs(
|
||||
f,
|
||||
Some(&self.1),
|
||||
&mut super::fmtgs::FormatInfo::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn gsi(self, info: GSInfo) -> VTypeWInfo {
|
||||
VTypeWInfo(self, info)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VSingleType {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
Self::Bool => write!(f, "bool"),
|
||||
Self::Int => write!(f, "int"),
|
||||
Self::Float => write!(f, "float"),
|
||||
Self::String => write!(f, "string"),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "[")?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::List(v) => {
|
||||
write!(f, "[")?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ...]")
|
||||
}
|
||||
Self::Function(func) => {
|
||||
write!(f, "fn(")?;
|
||||
for (inputs, output) in func {
|
||||
write!(f, "(")?;
|
||||
for i in inputs {
|
||||
i.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
output.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::Thread(out) => {
|
||||
write!(f, "thread(")?;
|
||||
out.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::Reference(inner) => {
|
||||
if inner.types.len() != 1 {
|
||||
write!(f, "&(")?;
|
||||
} else {
|
||||
write!(f, "&")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
if inner.types.len() != 1 {
|
||||
write!(f, ")")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
if let Some(name) = if let Some(info) = info {
|
||||
info.enum_variants.iter().find_map(|(name, id)| {
|
||||
if id == variant {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
write!(f, "{name}(")?;
|
||||
} else {
|
||||
write!(f, "{variant}(")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::EnumVariantS(name, inner) => {
|
||||
write!(f, "{name}(")?;
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::CustomType(t) => {
|
||||
if let Some(info) = info {
|
||||
#[cfg(not(debug_assertions))]
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
info.custom_type_names
|
||||
.iter()
|
||||
.find_map(|(name, id)| if *t == *id {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
)?;
|
||||
#[cfg(debug_assertions)]
|
||||
write!(
|
||||
f,
|
||||
"{}/*{}*/",
|
||||
info.custom_type_names
|
||||
.iter()
|
||||
.find_map(|(name, id)| if *t == *id {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap(),
|
||||
&info.custom_types[*t]
|
||||
)?;
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "[custom type #{t}]")
|
||||
}
|
||||
}
|
||||
Self::CustomTypeS(t) => write!(f, "{t}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for VSingleType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VType {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
for (i, t) in self.types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, "/")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ",")
|
||||
}
|
||||
}
|
||||
impl Display for VType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
20
old/mers/src/lib.rs
Executable file
20
old/mers/src/lib.rs
Executable file
@@ -0,0 +1,20 @@
|
||||
mod inlib;
|
||||
mod lang;
|
||||
mod libs;
|
||||
mod parsing;
|
||||
mod pathutil;
|
||||
|
||||
pub use inlib::MyLib;
|
||||
pub use lang::{fmtgs, global_info::GlobalScriptInfo, val_data::*, val_type::*};
|
||||
pub use libs::comms::{ByteData, ByteDataA, Message, RespondableMessage};
|
||||
pub use parsing::{parse::*, *};
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
lang::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
},
|
||||
MyLib, RespondableMessage,
|
||||
};
|
||||
}
|
||||
540
old/mers/src/libs/comms.rs
Executable file
540
old/mers/src/libs/comms.rs
Executable file
@@ -0,0 +1,540 @@
|
||||
use crate::lang::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
/// any message implements this trait. reponding to the message with A generates the response of type B.
|
||||
pub trait RespondableMessage: MessageResponse {
|
||||
type With;
|
||||
type Response: MessageResponse;
|
||||
fn respond(self, with: Self::With) -> Self::Response;
|
||||
}
|
||||
|
||||
/// any message or response implements this trait
|
||||
pub trait MessageResponse: ByteData + ByteDataA {
|
||||
fn messagetype_id() -> u32;
|
||||
fn msgtype_id(&self) -> u32 {
|
||||
Self::messagetype_id()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ByteData: Sized {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read;
|
||||
}
|
||||
/// for things like &str, which can't be created easily (String exists for that purpose), but can still be converted to bytes.
|
||||
pub trait ByteDataA {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>);
|
||||
fn as_byte_data_vec(&self) -> Vec<u8> {
|
||||
let mut vec = Vec::new();
|
||||
self.as_byte_data(&mut vec);
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
RunFunction(run_function::Message),
|
||||
}
|
||||
impl ByteData for Message {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let type_id = u32::from_byte_data(data)?;
|
||||
Ok(match type_id {
|
||||
0 => Self::RunFunction(ByteData::from_byte_data(data)?),
|
||||
_other => unreachable!("read unknown type_id byte for message!"),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<run_function::Message> for Message {
|
||||
fn from(value: run_function::Message) -> Self {
|
||||
Self::RunFunction(value)
|
||||
}
|
||||
}
|
||||
|
||||
// implementations for the message/response pairs
|
||||
|
||||
pub mod run_function {
|
||||
use crate::lang::val_data::VData;
|
||||
|
||||
use super::{ByteData, ByteDataA, MessageResponse, RespondableMessage};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
pub function_id: u64,
|
||||
pub args: Vec<VData>,
|
||||
}
|
||||
pub struct Response {
|
||||
pub result: VData,
|
||||
}
|
||||
impl RespondableMessage for Message {
|
||||
type With = VData;
|
||||
type Response = Response;
|
||||
fn respond(self, with: Self::With) -> Self::Response {
|
||||
Response { result: with }
|
||||
}
|
||||
}
|
||||
impl MessageResponse for Message {
|
||||
fn messagetype_id() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
impl MessageResponse for Response {
|
||||
fn messagetype_id() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
impl ByteDataA for Message {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.function_id.as_byte_data(vec);
|
||||
self.args.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl ByteData for Message {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
function_id: ByteData::from_byte_data(data)?,
|
||||
args: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for Response {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.result.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl ByteData for Response {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
result: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implementations of ByteData for other data
|
||||
|
||||
type UsizeConstLen = u64;
|
||||
type IsizeConstLen = i64;
|
||||
|
||||
impl ByteDataA for usize {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self as UsizeConstLen).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for usize {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(UsizeConstLen::from_byte_data(data)? as _)
|
||||
}
|
||||
}
|
||||
impl ByteDataA for isize {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self as IsizeConstLen).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for isize {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(IsizeConstLen::from_byte_data(data)? as _)
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i32 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i32 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 4];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u32 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u32 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 4];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u128 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u128 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 16];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i128 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i128 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 16];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for f64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes());
|
||||
}
|
||||
}
|
||||
impl ByteData for f64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for String {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.len().as_byte_data(vec);
|
||||
vec.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
impl ByteData for String {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let len = ByteData::from_byte_data(data)?;
|
||||
let mut buf = vec![0; len];
|
||||
data.read_exact(buf.as_mut_slice())?;
|
||||
let str = String::from_utf8(buf).unwrap();
|
||||
Ok(str)
|
||||
}
|
||||
}
|
||||
impl<T> ByteDataA for &T
|
||||
where
|
||||
T: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl<T> ByteDataA for Vec<T>
|
||||
where
|
||||
T: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.len().as_byte_data(vec);
|
||||
for elem in self {
|
||||
elem.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> ByteData for Vec<T>
|
||||
where
|
||||
T: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let len = usize::from_byte_data(data)?;
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vec.push(T::from_byte_data(data)?);
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
impl<A, B> ByteDataA for (A, B)
|
||||
where
|
||||
A: ByteDataA,
|
||||
B: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.0.as_byte_data(vec);
|
||||
self.1.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl<A, B> ByteData for (A, B)
|
||||
where
|
||||
A: ByteData,
|
||||
B: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok((
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl<A, B, C, D, E> ByteDataA for (A, B, C, D, E)
|
||||
where
|
||||
A: ByteDataA,
|
||||
B: ByteDataA,
|
||||
C: ByteDataA,
|
||||
D: ByteDataA,
|
||||
E: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.0.as_byte_data(vec);
|
||||
self.1.as_byte_data(vec);
|
||||
self.2.as_byte_data(vec);
|
||||
self.3.as_byte_data(vec);
|
||||
self.4.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl<A, B, C, D, E> ByteData for (A, B, C, D, E)
|
||||
where
|
||||
A: ByteData,
|
||||
B: ByteData,
|
||||
C: ByteData,
|
||||
D: ByteData,
|
||||
E: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok((
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VType {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.types.as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for VType {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
types: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VSingleType {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
match self {
|
||||
Self::Any => vec.push(b'a'),
|
||||
Self::Bool => vec.push(b'b'),
|
||||
Self::Int => vec.push(b'i'),
|
||||
Self::Float => vec.push(b'f'),
|
||||
Self::String => vec.push(b'"'),
|
||||
Self::Tuple(v) => {
|
||||
vec.push(b't');
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::List(v) => {
|
||||
vec.push(b'l');
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::Function(f) => {
|
||||
vec.push(b'F');
|
||||
f.as_byte_data(vec);
|
||||
}
|
||||
Self::Thread(r) => {
|
||||
vec.push(b'T');
|
||||
r.as_byte_data(vec);
|
||||
}
|
||||
Self::Reference(r) => {
|
||||
vec.push(b'R');
|
||||
r.as_byte_data(vec);
|
||||
}
|
||||
Self::EnumVariant(e, v) => {
|
||||
vec.push(b'e');
|
||||
e.as_byte_data(vec);
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::EnumVariantS(e, v) => {
|
||||
vec.push(b'E');
|
||||
e.as_byte_data(vec);
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::CustomType(_) | Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomType and CustomTypeS cannot be used in libraries [yet?].")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ByteData for VSingleType {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut switch_byte = [0u8];
|
||||
data.read_exact(&mut switch_byte)?;
|
||||
Ok(match switch_byte[0] {
|
||||
b'a' => Self::Any,
|
||||
b'b' => Self::Bool,
|
||||
b'i' => Self::Int,
|
||||
b'f' => Self::Float,
|
||||
b'"' => Self::String,
|
||||
b't' => Self::Tuple(ByteData::from_byte_data(data)?),
|
||||
b'l' => Self::List(ByteData::from_byte_data(data)?),
|
||||
b'F' => Self::Function(ByteData::from_byte_data(data)?),
|
||||
b'T' => Self::Thread(ByteData::from_byte_data(data)?),
|
||||
b'R' => Self::Reference(ByteData::from_byte_data(data)?),
|
||||
b'e' => Self::EnumVariant(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
b'E' => Self::EnumVariantS(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
_ => unreachable!("unexpected byte while reading single type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VData {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.operate_on_data_immut(|v| v.as_byte_data(vec))
|
||||
}
|
||||
}
|
||||
impl ByteData for VData {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(VDataEnum::from_byte_data(data)?.to())
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VDataEnum {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
match self {
|
||||
Self::Bool(false) => vec.push(b'b'),
|
||||
Self::Bool(true) => vec.push(b'B'),
|
||||
Self::Int(num) => {
|
||||
vec.push(b'i');
|
||||
num.as_byte_data(vec);
|
||||
}
|
||||
Self::Float(num) => {
|
||||
vec.push(b'f');
|
||||
num.as_byte_data(vec);
|
||||
}
|
||||
Self::String(s) => {
|
||||
vec.push(b'"');
|
||||
s.as_byte_data(vec);
|
||||
}
|
||||
Self::Tuple(c) => {
|
||||
vec.push(b't');
|
||||
c.as_byte_data(vec);
|
||||
}
|
||||
Self::List(t, data) => {
|
||||
vec.push(b'l');
|
||||
t.as_byte_data(vec);
|
||||
data.as_byte_data(vec);
|
||||
}
|
||||
// TODO?
|
||||
Self::Function(_) => vec.push(b'F'),
|
||||
Self::Thread(..) => vec.push(b'T'),
|
||||
Self::Reference(_r) => vec.push(b'R'),
|
||||
Self::EnumVariant(enum_id, inner) => {
|
||||
vec.push(b'E');
|
||||
enum_id.as_byte_data(vec);
|
||||
inner.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ByteData for VDataEnum {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut switch_byte = [0u8];
|
||||
data.read_exact(&mut switch_byte)?;
|
||||
Ok(match switch_byte[0] {
|
||||
b'b' => Self::Bool(false),
|
||||
b'B' => Self::Bool(true),
|
||||
b'i' => Self::Int(ByteData::from_byte_data(data)?),
|
||||
b'f' => Self::Float(ByteData::from_byte_data(data)?),
|
||||
b'"' => Self::String(ByteData::from_byte_data(data)?),
|
||||
b't' => Self::Tuple(ByteData::from_byte_data(data)?),
|
||||
b'l' => Self::List(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
b'E' => Self::EnumVariant(
|
||||
ByteData::from_byte_data(data)?,
|
||||
Box::new(ByteData::from_byte_data(data)?),
|
||||
),
|
||||
_ => unreachable!("read invalid byte"),
|
||||
})
|
||||
}
|
||||
}
|
||||
227
old/mers/src/libs/mod.rs
Executable file
227
old/mers/src/libs/mod.rs
Executable file
@@ -0,0 +1,227 @@
|
||||
pub mod comms;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, BufReader, Write},
|
||||
process::{Child, ChildStdin, ChildStdout, Command, Stdio},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::lang::{
|
||||
global_info::GlobalScriptInfo, to_runnable::ToRunnableError, val_data::VData, val_type::VType,
|
||||
};
|
||||
|
||||
use self::comms::{ByteData, ByteDataA, RespondableMessage};
|
||||
|
||||
// Libraries are processes that communicate via stdout/stdin.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Lib {
|
||||
name: String,
|
||||
process: Child,
|
||||
current_id: Arc<Mutex<u128>>,
|
||||
stdin: Arc<Mutex<ChildStdin>>,
|
||||
// stdout: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
task_sender: Arc<
|
||||
Mutex<std::sync::mpsc::Sender<(u128, Box<dyn FnOnce(&mut BufReader<ChildStdout>) + Send>)>>,
|
||||
>,
|
||||
pub registered_fns: Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
}
|
||||
impl Drop for Lib {
|
||||
fn drop(&mut self) {
|
||||
if self.process.try_wait().is_err() {
|
||||
if let Err(e) = self.process.kill() {
|
||||
eprint!(
|
||||
"Warn: tried to kill lib process for library \"{}\", but failed: {e:?}",
|
||||
self.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Sent by the library to request initialization
|
||||
/// ([ver_major], [ver_minor], [name], [desc], [registered_functions])
|
||||
pub type LibInitReq<'a> = (
|
||||
u32,
|
||||
u32,
|
||||
String,
|
||||
String,
|
||||
Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
);
|
||||
/// Sent by mers to finish initializing a library.
|
||||
/// [enum variants]
|
||||
// used by crate::inlib
|
||||
#[allow(unused)]
|
||||
pub type LibInitInfo = Vec<(String, usize)>;
|
||||
pub type LibInitInfoRef<'a> = Vec<(&'a String, &'a usize)>;
|
||||
|
||||
impl Lib {
|
||||
pub fn launch(
|
||||
mut exec: Command,
|
||||
enum_variants: &mut HashMap<String, usize>,
|
||||
) -> Result<Self, LaunchError> {
|
||||
let mut handle = match exec
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(LaunchError::CouldNotSpawnProcess(e)),
|
||||
};
|
||||
if let (Some(mut stdin), Some(stdout)) = (handle.stdin.take(), handle.stdout.take()) {
|
||||
let mut stdout = BufReader::new(stdout);
|
||||
let comms_version: u128 = ByteData::from_byte_data(&mut stdout).unwrap();
|
||||
assert_eq!(comms_version, 1);
|
||||
let (ver_major, ver_minor, name, description, mut registered_fns) =
|
||||
LibInitReq::from_byte_data(&mut stdout).unwrap();
|
||||
eprintln!("- <<< ADDING LIB: {name} v{ver_major}.{ver_minor} >>> -");
|
||||
for line in description.lines() {
|
||||
eprintln!(" | {line}");
|
||||
}
|
||||
for (name, _) in registered_fns.iter() {
|
||||
eprintln!(" fn {name}");
|
||||
}
|
||||
let mut ginfo = GlobalScriptInfo::default();
|
||||
for (_name, func) in registered_fns.iter_mut() {
|
||||
for (args, out) in func.iter_mut() {
|
||||
for t in args.iter_mut() {
|
||||
if let Err(e) = crate::lang::to_runnable::stypes(t, &mut ginfo) {
|
||||
return Err(LaunchError::ErrorAddingEnumsOrTypes(e));
|
||||
}
|
||||
}
|
||||
if let Err(e) = crate::lang::to_runnable::stypes(out, &mut ginfo) {
|
||||
return Err(LaunchError::ErrorAddingEnumsOrTypes(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name, id) in ginfo.enum_variants {
|
||||
if !enum_variants.contains_key(&name) {
|
||||
enum_variants.insert(name, id);
|
||||
}
|
||||
}
|
||||
let si: LibInitInfoRef = enum_variants.iter().collect();
|
||||
if let Err(e) = stdin.write_all(si.as_byte_data_vec().as_slice()) {
|
||||
return Err(LaunchError::StdioError(e));
|
||||
};
|
||||
if let Err(e) = stdin.flush() {
|
||||
return Err(LaunchError::StdioError(e));
|
||||
};
|
||||
let (task_sender, recv) = std::sync::mpsc::channel::<(
|
||||
u128,
|
||||
Box<dyn FnOnce(&mut BufReader<ChildStdout>) + Send>,
|
||||
)>();
|
||||
let _stdout_reader = std::thread::spawn(move || {
|
||||
let dur = std::time::Duration::from_millis(20);
|
||||
let mut pending = HashMap::new();
|
||||
loop {
|
||||
// read id from stdout
|
||||
if let Ok(id) = u128::from_byte_data(&mut stdout) {
|
||||
// update pending
|
||||
loop {
|
||||
if let Ok((id, sender)) = recv.try_recv() {
|
||||
pending.insert(id, sender);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// find task with that id
|
||||
if let Some(sender) = pending.remove(&id) {
|
||||
// call the callback function, which will handle the rest
|
||||
sender(&mut stdout)
|
||||
} else {
|
||||
eprintln!("ID {id} not found! possible decode/encode error?");
|
||||
}
|
||||
std::thread::sleep(dur);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Library has exited, tasks pending: {}",
|
||||
pending.iter().enumerate().fold(
|
||||
String::new(),
|
||||
|mut s, (i, (id, _))| if i == 0 {
|
||||
format!("{id}")
|
||||
} else {
|
||||
s.push_str(format!(", {id}").as_str());
|
||||
s
|
||||
}
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(Self {
|
||||
name,
|
||||
process: handle,
|
||||
stdin: Arc::new(Mutex::new(stdin)),
|
||||
// stdout: Arc::new(Mutex::new(stdout)),
|
||||
task_sender: Arc::new(Mutex::new(task_sender)),
|
||||
current_id: Arc::new(Mutex::new(0)),
|
||||
registered_fns,
|
||||
})
|
||||
} else {
|
||||
return Err(LaunchError::NoStdio);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_fn(&self, fnid: usize, args: Vec<VData>) -> VData {
|
||||
self.get_response(comms::run_function::Message {
|
||||
function_id: fnid as _,
|
||||
args,
|
||||
})
|
||||
.result
|
||||
}
|
||||
fn get_response<M>(&self, msg: M) -> M::Response
|
||||
where
|
||||
M: RespondableMessage,
|
||||
<M as comms::RespondableMessage>::Response: Send + 'static,
|
||||
{
|
||||
let recv = {
|
||||
let mut id = self.current_id.lock().unwrap();
|
||||
let mut stdin = self.stdin.lock().unwrap();
|
||||
let (sender, recv) = std::sync::mpsc::sync_channel(2);
|
||||
self.task_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send((
|
||||
*id,
|
||||
Box::new(move |stdout| {
|
||||
sender
|
||||
.send(ByteData::from_byte_data(stdout).unwrap())
|
||||
.unwrap();
|
||||
}),
|
||||
))
|
||||
.unwrap();
|
||||
// id - type_id - message
|
||||
stdin.write_all(id.as_byte_data_vec().as_slice()).unwrap();
|
||||
stdin
|
||||
.write_all(msg.msgtype_id().as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
stdin.write_all(msg.as_byte_data_vec().as_slice()).unwrap();
|
||||
stdin.flush().unwrap();
|
||||
*id = id.wrapping_add(1);
|
||||
recv
|
||||
};
|
||||
recv.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LaunchError {
|
||||
NoStdio,
|
||||
CouldNotSpawnProcess(io::Error),
|
||||
StdioError(io::Error),
|
||||
ErrorAddingEnumsOrTypes(ToRunnableError),
|
||||
}
|
||||
impl std::fmt::Display for LaunchError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NoStdio => write!(f, "couldn't get stdio (stdin/stdout) from child process."),
|
||||
Self::CouldNotSpawnProcess(e) => write!(f, "couldn't spawn child process: {e}."),
|
||||
Self::StdioError(e) => write!(f, "error from stdio: {e}"),
|
||||
Self::ErrorAddingEnumsOrTypes(e) => {
|
||||
write!(f, "error adding enums or types from library: {e}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
245
old/mers/src/main.rs
Executable file
245
old/mers/src/main.rs
Executable file
@@ -0,0 +1,245 @@
|
||||
use lang::global_info::ColorFormatMode;
|
||||
use lang::global_info::GlobalScriptInfo;
|
||||
use lang::global_info::LogKind;
|
||||
use lang::val_data::VDataEnum;
|
||||
use lang::val_type::VSingleType;
|
||||
|
||||
use crate::lang::fmtgs::FormatGs;
|
||||
|
||||
mod interactive_mode;
|
||||
mod lang;
|
||||
mod libs;
|
||||
#[cfg(feature = "nushell_plugin")]
|
||||
mod nushell_plugin;
|
||||
mod parsing;
|
||||
mod pathutil;
|
||||
mod tutor;
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(feature = "nushell_plugin"))]
|
||||
normal_main();
|
||||
#[cfg(feature = "nushell_plugin")]
|
||||
nushell_plugin::main();
|
||||
}
|
||||
|
||||
fn normal_main() {
|
||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
||||
let mut info = GlobalScriptInfo::default();
|
||||
let mut run = true;
|
||||
let mut args_to_skip = 2;
|
||||
let mut file = match args.len() {
|
||||
0 => {
|
||||
println!("no arguments, use -h for help");
|
||||
std::process::exit(100);
|
||||
}
|
||||
_ => {
|
||||
if args[0].trim_start().starts_with("-") {
|
||||
let mut execute = false;
|
||||
let mut print_version = false;
|
||||
let mut verbose = false;
|
||||
let mut verbose_args = String::new();
|
||||
let mut interactive = 0;
|
||||
let mut interactive_use_new_terminal = false;
|
||||
let mut teachme = false;
|
||||
let mut prev_char = None;
|
||||
let mut advanced = false;
|
||||
for ch in args[0][1..].chars() {
|
||||
if !advanced {
|
||||
if ch == '+' {
|
||||
advanced = true;
|
||||
continue;
|
||||
}
|
||||
match ch {
|
||||
'h' => {
|
||||
eprintln!("~~~~ mers help ~~~~");
|
||||
eprintln!();
|
||||
eprintln!(" ~~ cli ~~");
|
||||
eprintln!("Mers has the following cli options:");
|
||||
eprintln!("-h shows this Help message");
|
||||
eprintln!("-e - mers will treat the run argument as code to be Executed rather than a file path");
|
||||
eprintln!(" mers -e 'println(\"Hello, World!\")'");
|
||||
eprintln!(
|
||||
"-c - mers will Check the code for errors, but won't run it"
|
||||
);
|
||||
eprintln!("-f - mers will Format the code and print it. useful if you suspect the parser might be misinterpreting your code");
|
||||
eprintln!(
|
||||
"+c - use Colors in the output to better visualize things"
|
||||
);
|
||||
eprintln!("+C - don't use colors (opposite of +c, redundant since this is the default)");
|
||||
eprintln!("-v - mers will be more Verbose");
|
||||
eprintln!("+???+ - customize what mers is verbose about and how - bad syntax, barely useful, don't use it until it gets improved (TODO!)");
|
||||
eprintln!("-i - launches an Interactive session to play around with (opens your editor and runs code on each file save)");
|
||||
eprintln!("+t - spawns a new terminal for the editor (if you use a terminal editors, add +t)");
|
||||
eprintln!(" mers -i+t");
|
||||
eprintln!("-t - launches the Tutor, which will attempt to Teach you the basics of the language");
|
||||
eprintln!();
|
||||
eprintln!(" ~~ getting started ~~");
|
||||
eprintln!("mers doesn't need a specific structure for directories, just create a UTF-8 text file, write code, and run it:");
|
||||
eprintln!(" echo 'println(\"Hello, World!\")' > hello.mers");
|
||||
eprintln!(" mers hello.mers");
|
||||
return;
|
||||
}
|
||||
'e' => execute = true,
|
||||
'v' => verbose = true,
|
||||
'c' => run = false,
|
||||
'f' => {
|
||||
run = false;
|
||||
info.log.after_parse.stderr = true;
|
||||
}
|
||||
'V' => print_version = true,
|
||||
'i' => interactive += 1,
|
||||
't' => teachme = true,
|
||||
ch => {
|
||||
eprintln!("Ignoring -{ch}. (unknown char)");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev_char = Some(ch);
|
||||
} else {
|
||||
advanced = false;
|
||||
if let Some(prev_char) = prev_char {
|
||||
match prev_char {
|
||||
'i' => match ch {
|
||||
't' => interactive_use_new_terminal = true,
|
||||
_ => eprintln!("Ignoring i+{ch}. (unknown adv char)"),
|
||||
},
|
||||
'v' => {
|
||||
if ch != '+' {
|
||||
advanced = true;
|
||||
verbose_args.push(ch);
|
||||
}
|
||||
}
|
||||
'f' => match ch {
|
||||
'c' => info.formatter.mode = ColorFormatMode::Colorize,
|
||||
'C' => info.formatter.mode = ColorFormatMode::Plain,
|
||||
_ => eprintln!("Ignoring f+{ch}. (unknown adv char)"),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"Ignoring advanced args because there was no previous argument."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if print_version {
|
||||
println!(
|
||||
"mers {}",
|
||||
option_env!("CARGO_PKG_VERSION")
|
||||
.unwrap_or("[[ version unknown: no CARGO_PKG_VERSION ]]")
|
||||
);
|
||||
return;
|
||||
}
|
||||
if teachme {
|
||||
tutor::start(false);
|
||||
return;
|
||||
}
|
||||
if verbose {
|
||||
if verbose_args.is_empty() {
|
||||
fn f() -> LogKind {
|
||||
LogKind {
|
||||
stderr: true,
|
||||
log: true,
|
||||
}
|
||||
}
|
||||
info.log.vtype_fits_in = f();
|
||||
info.log.vsingletype_fits_in = f();
|
||||
} else {
|
||||
fn kind(val: Option<&str>) -> LogKind {
|
||||
match val {
|
||||
Some("stderr") => LogKind {
|
||||
stderr: true,
|
||||
..Default::default()
|
||||
},
|
||||
Some("log") => LogKind {
|
||||
log: true,
|
||||
..Default::default()
|
||||
},
|
||||
Some("log+stderr" | "stderr+log") => LogKind {
|
||||
stderr: true,
|
||||
log: true,
|
||||
..Default::default()
|
||||
},
|
||||
_ => LogKind {
|
||||
stderr: true,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
for verbose_arg in verbose_args.split(',') {
|
||||
let (arg, val) = match verbose_arg.split_once('=') {
|
||||
Some((left, right)) => (left, Some(right)),
|
||||
None => (verbose_arg, None),
|
||||
};
|
||||
match arg {
|
||||
"vtype_fits_in" => info.log.vtype_fits_in = kind(val),
|
||||
"vsingletype_fits_in" => info.log.vsingletype_fits_in = kind(val),
|
||||
_ => eprintln!("Warn: -v+ unknown arg '{arg}'."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if interactive > 0 {
|
||||
match interactive {
|
||||
_ => {
|
||||
// basic: open file and watch for fs changes
|
||||
interactive_mode::fs_watcher::playground(interactive_use_new_terminal)
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
return;
|
||||
} else if execute {
|
||||
parsing::file::File::new(
|
||||
args.iter().skip(1).fold(String::new(), |mut s, v| {
|
||||
if !s.is_empty() {
|
||||
s.push(' ');
|
||||
}
|
||||
s.push_str(v);
|
||||
s
|
||||
}),
|
||||
std::path::PathBuf::new(),
|
||||
)
|
||||
} else {
|
||||
args_to_skip += 1;
|
||||
if let Some(file) = args.get(1) {
|
||||
parsing::file::File::new(
|
||||
std::fs::read_to_string(file).unwrap(),
|
||||
file.into(),
|
||||
)
|
||||
} else {
|
||||
println!("nothing to do - missing arguments?");
|
||||
std::process::exit(101);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parsing::file::File::new(
|
||||
std::fs::read_to_string(&args[0]).unwrap(),
|
||||
args[0].as_str().into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
info.main_fn_args = vec![(
|
||||
"args".to_string(),
|
||||
VSingleType::List(VSingleType::String.into()).to(),
|
||||
)];
|
||||
match parsing::parse::parse_custom_info(&mut file, info) {
|
||||
Ok(script) => {
|
||||
if run {
|
||||
script.run(vec![VDataEnum::List(
|
||||
VSingleType::String.to(),
|
||||
std::env::args()
|
||||
.skip(args_to_skip)
|
||||
.map(|v| VDataEnum::String(v).to())
|
||||
.collect(),
|
||||
)
|
||||
.to()]);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Couldn't compile:\n{}", e.with_file(&file));
|
||||
std::process::exit(99);
|
||||
}
|
||||
}
|
||||
}
|
||||
174
old/mers/src/nushell_plugin.rs
Executable file
174
old/mers/src/nushell_plugin.rs
Executable file
@@ -0,0 +1,174 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin};
|
||||
use nu_protocol::{PluginSignature, ShellError, Span, SyntaxShape, Value};
|
||||
|
||||
use crate::{
|
||||
lang::{
|
||||
fmtgs::FormatGs,
|
||||
global_info::GlobalScriptInfo,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::VType,
|
||||
},
|
||||
parsing,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
serve_plugin(&mut MersNuPlugin(), MsgPackSerializer {});
|
||||
}
|
||||
|
||||
struct MersNuPlugin();
|
||||
|
||||
impl Plugin for MersNuPlugin {
|
||||
fn signature(&self) -> Vec<nu_protocol::PluginSignature> {
|
||||
vec![PluginSignature::build("mers-nu")
|
||||
.required(
|
||||
"mers",
|
||||
SyntaxShape::String,
|
||||
"the path to the .mers file to run or the mers source code if -e is set",
|
||||
)
|
||||
.optional(
|
||||
"args",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
]))),
|
||||
]))),
|
||||
]))),
|
||||
"the arguments passed to the mers program. defaults to an empty list.",
|
||||
)
|
||||
.switch(
|
||||
"execute",
|
||||
"instead of reading from a file, interpret the 'mers' input as source code",
|
||||
Some('e'),
|
||||
)]
|
||||
}
|
||||
fn run(
|
||||
&mut self,
|
||||
_name: &str,
|
||||
call: &nu_plugin::EvaluatedCall,
|
||||
_input: &nu_protocol::Value,
|
||||
) -> Result<nu_protocol::Value, nu_plugin::LabeledError> {
|
||||
// no need to 'match name {...}' because we only register mers-nu and nothing else.
|
||||
let source: String = call.req(0)?;
|
||||
let source_span = Span::unknown(); // source.span;
|
||||
// let source = source.item;
|
||||
let mut file = if call.has_flag("execute") {
|
||||
parsing::file::File::new(source, PathBuf::new())
|
||||
} else {
|
||||
parsing::file::File::new(
|
||||
match fs::read_to_string(&source) {
|
||||
Ok(v) => v,
|
||||
Err(_e) => {
|
||||
return Ok(Value::Error {
|
||||
error: Box::new(ShellError::FileNotFound(source_span)),
|
||||
})
|
||||
}
|
||||
},
|
||||
source.into(),
|
||||
)
|
||||
};
|
||||
Ok(match parsing::parse::parse(&mut file) {
|
||||
Ok(code) => {
|
||||
let args = match call.opt(1)? {
|
||||
Some(v) => {
|
||||
fn to_mers_val(v: Vec<Value>, info: &GlobalScriptInfo) -> Vec<VData> {
|
||||
v.into_iter()
|
||||
.map(|v| {
|
||||
match v {
|
||||
Value::Bool { val, .. } => VDataEnum::Bool(val),
|
||||
Value::Int { val, .. } => VDataEnum::Int(val as _),
|
||||
Value::Float { val, .. } => VDataEnum::Float(val),
|
||||
Value::String { val, .. } => VDataEnum::String(val),
|
||||
Value::List { vals, .. } => {
|
||||
let mut t = VType::empty();
|
||||
let mut vs = Vec::with_capacity(vals.len());
|
||||
for v in to_mers_val(vals, info) {
|
||||
t.add_types(v.out(), info);
|
||||
vs.push(v);
|
||||
}
|
||||
VDataEnum::List(t, vs)
|
||||
}
|
||||
_ => unreachable!("invalid arg type"),
|
||||
}
|
||||
.to()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
if let Value::List { vals, .. } = v {
|
||||
to_mers_val(vals, &code.info)
|
||||
} else {
|
||||
unreachable!("args not a list")
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
fn to_nu_val(val: &VData, info: &GlobalScriptInfo) -> Value {
|
||||
let span = Span::unknown();
|
||||
val.operate_on_data_immut(|val| match val {
|
||||
VDataEnum::Bool(val) => Value::Bool { val: *val, span },
|
||||
VDataEnum::Int(val) => Value::Int {
|
||||
val: *val as _,
|
||||
span,
|
||||
},
|
||||
VDataEnum::Float(val) => Value::Float { val: *val, span },
|
||||
VDataEnum::String(val) => Value::String {
|
||||
val: val.to_owned(),
|
||||
span,
|
||||
},
|
||||
VDataEnum::Tuple(vals) | VDataEnum::List(_, vals) => Value::List {
|
||||
vals: vals.iter().map(|v| to_nu_val(v, info)).collect(),
|
||||
span,
|
||||
},
|
||||
VDataEnum::Reference(r) => to_nu_val(r, info),
|
||||
VDataEnum::EnumVariant(variant, val) => {
|
||||
let name = info
|
||||
.enum_variants
|
||||
.iter()
|
||||
.find_map(|(name, id)| {
|
||||
if *id == *variant {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Value::Record {
|
||||
cols: vec![format!("Enum"), format!("Value")],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: name,
|
||||
span: span,
|
||||
},
|
||||
to_nu_val(val, info),
|
||||
],
|
||||
span,
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(_func) => Value::Nothing { span },
|
||||
VDataEnum::Thread(t, _) => to_nu_val(&t.get(), info),
|
||||
})
|
||||
}
|
||||
to_nu_val(&code.run(args), &code.info)
|
||||
}
|
||||
Err(e) => Value::Error {
|
||||
error: Box::new(ShellError::IncorrectValue {
|
||||
msg: format!("Couldn't compile mers, error: {}", e.with_file(&file)),
|
||||
span: source_span,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
266
old/mers/src/parsing/file.rs
Executable file
266
old/mers/src/parsing/file.rs
Executable file
@@ -0,0 +1,266 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Index, Range, RangeFrom, RangeTo},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub struct File {
|
||||
path: PathBuf,
|
||||
data: String,
|
||||
chars: Vec<(usize, char)>,
|
||||
// contains the byte indices of all newline characters
|
||||
newlines: Vec<usize>,
|
||||
pos: FilePosition,
|
||||
ppos: FilePosition,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePosition {
|
||||
pub current_char_index: usize,
|
||||
pub current_line: usize,
|
||||
pub current_column: usize,
|
||||
}
|
||||
impl Display for FilePosition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"line {}, col. {}",
|
||||
self.current_line + 1,
|
||||
self.current_column + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// creates a file from its contents and its path. Path can be PathBuf::new(), but this disables relative MersLibs.
|
||||
pub fn new(data: String, path: PathBuf) -> Self {
|
||||
let data = if data.starts_with("#!") {
|
||||
&data[data.lines().next().unwrap().len()..].trim_start()
|
||||
} else {
|
||||
data.trim_start()
|
||||
};
|
||||
let mut chs = data.chars();
|
||||
let mut data = String::with_capacity(data.len());
|
||||
loop {
|
||||
match chs.next() {
|
||||
Some('\\') => match chs.next() {
|
||||
// backslash can escape these characters:
|
||||
Some('\n') => data.push('\\'),
|
||||
// backshash invalidates comments, so \// will just be //.
|
||||
Some('/') => data.push('/'),
|
||||
// backslash does nothing otherwise.
|
||||
Some(ch) => {
|
||||
data.push('\\');
|
||||
data.push(ch);
|
||||
}
|
||||
None => data.push('\\'),
|
||||
},
|
||||
Some('/') => match chs.next() {
|
||||
Some('/') => loop {
|
||||
match chs.next() {
|
||||
Some('\n') => {
|
||||
data.push('\n');
|
||||
break;
|
||||
}
|
||||
None => break,
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Some('*') => loop {
|
||||
match chs.next() {
|
||||
Some('*') => {
|
||||
if let Some('/') = chs.next() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Some(ch) => {
|
||||
data.push('/');
|
||||
data.push(ch);
|
||||
}
|
||||
None => {
|
||||
data.push('/');
|
||||
break;
|
||||
}
|
||||
},
|
||||
Some(ch) => data.push(ch),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
if !data.ends_with('\n') {
|
||||
data.push('\n');
|
||||
}
|
||||
let chars: Vec<_> = data.char_indices().collect();
|
||||
let newlines: Vec<_> = chars
|
||||
.iter()
|
||||
.filter_map(|v| if v.1 == '\n' { Some(v.0) } else { None })
|
||||
.collect();
|
||||
let pos = FilePosition {
|
||||
current_char_index: 0,
|
||||
current_line: 0,
|
||||
current_column: 0,
|
||||
};
|
||||
Self {
|
||||
path,
|
||||
data,
|
||||
chars,
|
||||
newlines,
|
||||
pos,
|
||||
ppos: pos,
|
||||
}
|
||||
}
|
||||
pub fn skip_whitespaces(&mut self) {
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(ch) if ch.is_whitespace() => _ = self.next(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn collect_to_whitespace(&mut self) -> String {
|
||||
let mut o = String::new();
|
||||
loop {
|
||||
if let Some(ch) = self.next() {
|
||||
if ch.is_whitespace() {
|
||||
self.set_pos(*self.get_ppos());
|
||||
break;
|
||||
}
|
||||
o.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
pub fn get_pos(&self) -> &FilePosition {
|
||||
&self.pos
|
||||
}
|
||||
pub fn get_ppos(&self) -> &FilePosition {
|
||||
&self.ppos
|
||||
}
|
||||
pub fn set_pos(&mut self, pos: FilePosition) {
|
||||
self.pos = pos;
|
||||
}
|
||||
pub fn get_char(&self, index: usize) -> Option<char> {
|
||||
match self.chars.get(index) {
|
||||
Some(v) => Some(v.1),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn get_line(&self, line_nr: usize) -> Option<&str> {
|
||||
if self.newlines.len() > line_nr {
|
||||
Some(if line_nr == 0 {
|
||||
&self.data[0..self.newlines[0]]
|
||||
} else {
|
||||
&self.data[self.newlines[line_nr - 1] + 1..self.newlines[line_nr]]
|
||||
})
|
||||
} else if self.newlines.len() == line_nr {
|
||||
Some(if line_nr == 0 {
|
||||
self.data.as_str()
|
||||
} else {
|
||||
&self.data[self.newlines[line_nr - 1] + 1..]
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// returns the lines. both from and to are inclusive.
|
||||
pub fn get_lines(&self, from: usize, to: usize) -> Option<&str> {
|
||||
let start_index = if from == 0 {
|
||||
0
|
||||
} else if from <= self.newlines.len() {
|
||||
self.newlines[from - 1] + 1
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let end_index = if to == self.newlines.len() {
|
||||
self.data.len()
|
||||
} else if to < self.newlines.len() {
|
||||
self.newlines[to]
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(&self.data[start_index..end_index])
|
||||
}
|
||||
pub fn next_line(&mut self) -> String {
|
||||
let mut o = String::new();
|
||||
for ch in self {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
} else {
|
||||
o.push(ch);
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn peek(&self) -> Option<char> {
|
||||
match self.chars.get(self.pos.current_char_index) {
|
||||
Some((_, c)) => Some(*c),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for File {
|
||||
type Item = char;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.ppos = self.pos;
|
||||
let o = self.chars.get(self.pos.current_char_index);
|
||||
self.pos.current_char_index += 1;
|
||||
match o {
|
||||
Some((_, ch)) => {
|
||||
match *ch {
|
||||
'\n' => {
|
||||
self.pos.current_line += 1;
|
||||
self.pos.current_column = 0;
|
||||
}
|
||||
_ => self.pos.current_column += 1,
|
||||
}
|
||||
// #[cfg(debug_assertions)]
|
||||
// eprint!("{ch}");
|
||||
Some(*ch)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Range<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: Range<usize>) -> &Self::Output {
|
||||
if let Some((start, _)) = self.chars.get(index.start) {
|
||||
if let Some((end, _)) = self.chars.get(index.end) {
|
||||
&self.data[*start..*end]
|
||||
} else {
|
||||
&self.data[*start..]
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Index<RangeFrom<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
|
||||
if let Some((start, _)) = self.chars.get(index.start) {
|
||||
&self.data[*start..]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Index<RangeTo<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
|
||||
if let Some((end, _)) = self.chars.get(index.end) {
|
||||
&self.data[..*end]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
2
old/mers/src/parsing/mod.rs
Executable file
2
old/mers/src/parsing/mod.rs
Executable file
@@ -0,0 +1,2 @@
|
||||
pub mod file;
|
||||
pub mod parse;
|
||||
1362
old/mers/src/parsing/parse.rs
Executable file
1362
old/mers/src/parsing/parse.rs
Executable file
File diff suppressed because it is too large
Load Diff
35
old/mers/src/pathutil.rs
Executable file
35
old/mers/src/pathutil.rs
Executable file
@@ -0,0 +1,35 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn path_from_string(
|
||||
path: &str,
|
||||
script_path: &PathBuf,
|
||||
fallback_to_lib_dir: bool,
|
||||
) -> Option<PathBuf> {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_absolute() {
|
||||
return Some(path);
|
||||
}
|
||||
if let Some(p) = script_path
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| script_path.clone())
|
||||
.parent()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: parent path: {p:?}");
|
||||
let p = p.join(&path);
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: joined: {p:?}");
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
if fallback_to_lib_dir {
|
||||
if let Ok(mers_lib_dir) = std::env::var("MERS_LIB_DIR") {
|
||||
let p = PathBuf::from(mers_lib_dir).join(&path);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
25
old/mers/src/tutor/base_comments.rs
Executable file
25
old/mers/src/tutor/base_comments.rs
Executable file
@@ -0,0 +1,25 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Comments in mers start at // and end at the end of the line.
|
||||
// They also work within strings, which can be unexpected in some cases (like \"http://www...\").
|
||||
/* also works to start a comment.
|
||||
This comment can even span multiple lines! */
|
||||
// To return to the menu, uncomment the next line:
|
||||
// true
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Bool(true) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {} instead of true.", other));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
old/mers/src/tutor/base_functions.rs
Executable file
46
old/mers/src/tutor/base_functions.rs
Executable file
@@ -0,0 +1,46 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Functions represent certain actions.
|
||||
// They are given some inputs (arguments) and output (return) something.
|
||||
// Mers comes with a range of builtin functions defined in src/script/builtins.rs.
|
||||
|
||||
// As an example, let's look at the add() function:
|
||||
// It takes two arguments as its input and adds them together, then returns the sum:
|
||||
add(5 10) // 15
|
||||
// Similar to this, sub() subtracts two numbers:
|
||||
sub(15 5) // 10
|
||||
|
||||
// For some functions, there is no value they could return:
|
||||
sleep(0.01) // wait 0.01 seconds, then continue.
|
||||
// These will return an empty tuple [] in mers.
|
||||
|
||||
// However, you aren't limited to the builtin functions.
|
||||
// You can easily define your own functions to do more complex tasks:
|
||||
fn say_hello_world() {
|
||||
println(\"Hello, world!\")
|
||||
}
|
||||
|
||||
// Since the Subject.Verb(Object) syntax is more natural to many people, a.function(b c d) is an alternative way of writing function(a b c d):
|
||||
my_var = 15
|
||||
format(\"my variable had the value {0}!\" my_var) // normal
|
||||
\"my variable had the value {0}!\".format(my_var) // alternative (does the same thing)
|
||||
|
||||
// to return to the menu, add two arguments to the mul() function to make it return 32*5
|
||||
mul()
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(160) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of 160"));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
old/mers/src/tutor/base_return.rs
Executable file
38
old/mers/src/tutor/base_return.rs
Executable file
@@ -0,0 +1,38 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Mers doesn't have a return statement.
|
||||
// Instead, the value of the last statement is implicitly returned.
|
||||
|
||||
// This applies to blocks:
|
||||
b = {
|
||||
a = 10
|
||||
a = a.add(15)
|
||||
a
|
||||
}
|
||||
// b = 25
|
||||
|
||||
// To functions:
|
||||
fn compute_sum(a int b int) {
|
||||
a.add(b)
|
||||
}
|
||||
// returns a+b
|
||||
|
||||
// and to the program itself!
|
||||
// to return to the menu, make the program return 15.
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(15) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {} instead of 15.", other));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
old/mers/src/tutor/base_types.rs
Executable file
65
old/mers/src/tutor/base_types.rs
Executable file
@@ -0,0 +1,65 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Mers uses a type system to verify your programs,
|
||||
// which prevents your program from crashing.
|
||||
// Mers will verify that your program is valid and will not run into issues before it is executed.
|
||||
// This way, errors are found when you write the program, not when you run it. If mers runs your program
|
||||
// it is almost always safe to assume that it will not crash.
|
||||
|
||||
// for example, this will cause an error because you cannot subtract text from numbers.
|
||||
// sub(15 \"some text\")
|
||||
|
||||
// mers can verify this type-safety in all programs, no matter how complicated they are:
|
||||
// a = 15 // mers knows: a is an int
|
||||
// b = \"some text\" // mers knows: b is a string
|
||||
// sub(a b) // mers knows: it can't subtract a string from an int
|
||||
|
||||
// Just like other statically-typed languages, mers achieves this safety by assigning a certain type to each variable (technically to each statement).
|
||||
// However, mers' type-system has one quirk that sets it apart from most others:
|
||||
|
||||
a = if true {
|
||||
\"some string\"
|
||||
} else {
|
||||
12
|
||||
}
|
||||
switch! a {}
|
||||
|
||||
// A type in mers can consist of multiple single types: The type of a is 'string/int', because it could be either a string or an int.
|
||||
// You can see this type mentioned in the error at the top of the file, which shows up because 'switch!' wants us to handle all possible types,
|
||||
// yet we don't handle any ('{}').
|
||||
|
||||
// By combining tuples ('[a b c]') with the idea of multiple-types, you can create complex datastructures.
|
||||
// You effectively have all the power of Rust enums (enums containing values) and structs combined:
|
||||
// Rust's Option<T>: t/[] or [t]/[] if t can be [] itself
|
||||
// Rust's Result<T, E>: T/Err(E)
|
||||
|
||||
// The Err(E) is mers' version of an enum. An enum in mers is an identifier ('Err') wrapping a type ('E').
|
||||
// They don't need to be declared anywhere. You can just return 'PossiblyWrongValue: 0.33' from your function and mers will handle the rest.
|
||||
// To access the inner value, you can use the noenum() function:
|
||||
// result = SomeValueInAnEnum: \"a string\"
|
||||
// println(result) // error - result is not a string
|
||||
// println(result.noenum()) // works because result is an enum containing a string
|
||||
|
||||
// the \\S+ regex matches anything but whitespaces
|
||||
words_in_string = \"some string\".regex(\"\\\\S+\")
|
||||
switch! words_in_string {}
|
||||
// Types to cover: [string ...]/Err(string) - If the regex is invalid, regex() will return an error.
|
||||
|
||||
// To return to the menu, fix all compiler errors (comment out all switch! statements).
|
||||
|
||||
true
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Tuple(v) if v.is_empty() => {
|
||||
tutor.set_status(format!(" - Returned an empty tuple."));
|
||||
tutor.update(None);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
35
old/mers/src/tutor/base_values.rs
Executable file
35
old/mers/src/tutor/base_values.rs
Executable file
@@ -0,0 +1,35 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Mers has the following values:
|
||||
// bool: Either true or false
|
||||
// int: An integer. Written 0, 1, 2, 3, -5, and so on
|
||||
// float: A floating point number. Written 0.5, -12.378, and so on
|
||||
// string: A piece of text. Surround something with \" and it will be a string: \"Hello, world!\"
|
||||
// tuples: Multiple values in one place. A fixed-length collection. Surround types or statements with [] to create a tuple: [12 \"a tuple of ints and a string\" -5 -12]
|
||||
// The empty tuple [] is often used to indicate nothing, while a 1-long tuple [v] indicates the opposite - something.
|
||||
// list: Similar to tuples, but the closing ] is prefixed with 3 dots: [ ...]
|
||||
// Unlike tuples, all elements in a list have the same type. Lists are resizable and can grow dynamically, while tuples cannot change their size after being created.
|
||||
// function: A piece of code in data-form.
|
||||
// value: anonymous_sum_function = (a int/float b int/float) a.add(b)
|
||||
// type: fn((int int int)(int float float)(float int float)(float float float))
|
||||
// the reason why the type syntax is so expressive is because the function doesn't return the same type for any inputs - add will return an int if it added two ints, but will return a float when at least one argument was a float.
|
||||
// add will NOT return int/float, because if you know the exact input types, you also know the output type: either int and not float or float and not int.
|
||||
// thread: Represents a different thread. The thread's return value can be retrieved by using .await(). Thread values are returned by the builtin thread() function.
|
||||
// reference: A mutable reference to some data. Used by things like push() and remove() to avoid having to clone the entire list just to make a small change.
|
||||
// enums: An enum can wrap any type. Enums are identified by their names and can be created using EnumName: inner_value. The type is written EnumName(InnerType).
|
||||
// return any enum to return to the menu.
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::EnumVariant(..) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of an enum."));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
old/mers/src/tutor/base_variables.rs
Executable file
36
old/mers/src/tutor/base_variables.rs
Executable file
@@ -0,0 +1,36 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// A variable can be used to store values.
|
||||
// Create one by assigning a value to it:
|
||||
my_first_variable = 15
|
||||
// Then use it instead of literal values:
|
||||
five_less = sub(my_first_variable 5) // 10
|
||||
|
||||
// to return to the menu, create a variable my_name and assign your name to it.
|
||||
|
||||
|
||||
/* return the name so the tutor can check it - ignore this */ my_name
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::String(name) if !name.is_empty() => {
|
||||
tutor.i_name = Some(name.to_owned());
|
||||
break;
|
||||
}
|
||||
VDataEnum::String(_) => {
|
||||
tutor.set_status(format!(" - Almost there, you made an empty string. Put your name between the quotes to continue!"));
|
||||
tutor.update(None);
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of a string. String literals start and end with double quotes (\")."));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
old/mers/src/tutor/error_handling.rs
Executable file
59
old/mers/src/tutor/error_handling.rs
Executable file
@@ -0,0 +1,59 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Error handling in mers is inspired by Rust. Errors aren't a language feature,
|
||||
// they are just a value like any other. Usually, errors have the type Err(string) or Err(SomeOtherEnum(string)),
|
||||
// but this is still just a value in an enum.
|
||||
// Because of the type system in mers, errors can just be used and don't need any special language features.
|
||||
// This part of the mers-tutor isn't as much about error handling as it is about dealing with multiple types when you only want some of them, not all,
|
||||
// but since error handling is the main use-case for this, it felt like the better title for this section.
|
||||
|
||||
// 1. [t]/[]
|
||||
// This acts like null/nil in most languages or Option<T> in rust.
|
||||
// This type indicates either '[]', a tuple of length 0 and therefore no data (null/nil/None)
|
||||
// or '[t]', a tuple of length 1 - one value. This has to be [t] and not just t because t might be [], which would otherwise cause ambiguity.
|
||||
|
||||
// The type [t]/[] is returned by get(), a function that retrieves an item from a list and returns [] if the index was less than 0 or too big for the given list:
|
||||
list = [1 2 3 4 5 ...]
|
||||
first = list.get(0) // = [1]
|
||||
second = list.get(1) // = [2]
|
||||
does_not_exist = list.get(9) // = []
|
||||
|
||||
// To handle the result from get(), we can switch on the type:
|
||||
switch! first {
|
||||
[int] \"First element in the list: {0}\".format(first.0.to_string())
|
||||
[] \"List was empty!\"
|
||||
}
|
||||
|
||||
// If we already know that the list isn't empty, we can use assume1(). This function takes a [t]/[] and returns t. If it gets called with [], it will crash your program.
|
||||
\"First element in the list: {0}\".format(first.assume1().to_string())
|
||||
|
||||
// 2. t/Err(e)
|
||||
// This acts like Rust's Result<T, E> and is used in error-handling.
|
||||
// This is mainly used by functions that do I/O (fs_* and run_command) and can also be handeled using switch or switch! statements.
|
||||
// Use switch! or .debug() to see the types returned by these functions in detail.
|
||||
// If switching is too much effort for you and you would like to just crash the program on any error,
|
||||
// you can use assume_no_enum() to ignore all enum types:
|
||||
// - t/Err(e) becomes t
|
||||
// - int/float/string/Err(e)/Err(a) becomes int/float/string
|
||||
|
||||
// To return to the menu, change the index in list.get() so that it returns a value of type [int] instead of [].
|
||||
list.get(8)
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Tuple(v) if !v.is_empty() => {
|
||||
break;
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(
|
||||
" - Returned {other} instead of a value of type [int]."
|
||||
));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
old/mers/src/tutor/menu.rs
Executable file
52
old/mers/src/tutor/menu.rs
Executable file
@@ -0,0 +1,52 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub const MAX_POS: usize = 7;
|
||||
|
||||
pub fn run(mut tutor: Tutor) {
|
||||
loop {
|
||||
tutor.current_pos = 0;
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Welcome to the mers tutor!
|
||||
// This is the main menu. Change the number to navigate to a specific part.
|
||||
fn go_to() 0
|
||||
// 1 Comments
|
||||
// 2 Functions
|
||||
// 3 Values
|
||||
// 4 Variables
|
||||
// 5 Returns
|
||||
// 6 Types
|
||||
// 7 Error handling
|
||||
|
||||
go_to()
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(pos) if pos != 0 => {
|
||||
tutor.current_pos = (pos.max(0) as usize).min(MAX_POS);
|
||||
match tutor.current_pos {
|
||||
0 => continue,
|
||||
1 => super::base_comments::run(&mut tutor),
|
||||
2 => super::base_functions::run(&mut tutor),
|
||||
3 => super::base_values::run(&mut tutor),
|
||||
4 => super::base_variables::run(&mut tutor),
|
||||
5 => super::base_return::run(&mut tutor),
|
||||
6 => super::base_types::run(&mut tutor),
|
||||
7 => super::error_handling::run(&mut tutor),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(
|
||||
" - Returned {} instead of a nonzero integer",
|
||||
other
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
old/mers/src/tutor/mod.rs
Executable file
122
old/mers/src/tutor/mod.rs
Executable file
@@ -0,0 +1,122 @@
|
||||
use std::{path::PathBuf, thread::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
lang::{code_runnable::RScript, fmtgs::FormatGs, val_data::VDataEnum},
|
||||
parsing::{self, file::File},
|
||||
};
|
||||
|
||||
mod base_comments;
|
||||
mod base_functions;
|
||||
mod base_return;
|
||||
mod base_types;
|
||||
mod base_values;
|
||||
mod base_variables;
|
||||
mod error_handling;
|
||||
mod menu;
|
||||
|
||||
pub fn start(spawn_new_terminal_for_editor: bool) {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let (editor_join_handle, file_path) = crate::interactive_mode::fs_watcher::main(
|
||||
spawn_new_terminal_for_editor,
|
||||
"// Welcome to the mers tutor!
|
||||
|
||||
// This is an interactive experience. After making a change to this file,
|
||||
// save and then reload it to see the tutor's updates.
|
||||
// DO NOT save the file twice without reloading because you might overwrite changes made by the tutor,
|
||||
// which can completely ruin the file's formatting until the next full update (page change)!
|
||||
// To begin, change the following value from false to true:
|
||||
|
||||
false
|
||||
",
|
||||
Box::new(move |file| {
|
||||
let mut file =
|
||||
parsing::file::File::new(std::fs::read_to_string(file).unwrap(), PathBuf::new());
|
||||
sender.send((parsing::parse::parse(&mut file), file)).unwrap();
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let mut tutor = Tutor {
|
||||
current_pos: 0,
|
||||
current_status: String::new(),
|
||||
written_status_byte_len: 0,
|
||||
editor_join_handle,
|
||||
file_path,
|
||||
receiver,
|
||||
i_name: None,
|
||||
};
|
||||
loop {
|
||||
if let VDataEnum::Bool(true) = tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
menu::run(tutor);
|
||||
}
|
||||
|
||||
use menu::MAX_POS;
|
||||
|
||||
pub struct Tutor {
|
||||
current_pos: usize,
|
||||
current_status: String,
|
||||
written_status_byte_len: usize,
|
||||
editor_join_handle: JoinHandle<()>,
|
||||
file_path: PathBuf,
|
||||
receiver: std::sync::mpsc::Receiver<(Result<RScript, parsing::parse::Error>, File)>,
|
||||
// i_ are inputs from the user
|
||||
pub i_name: Option<String>,
|
||||
}
|
||||
impl Tutor {
|
||||
/// only returns after a successful compile. before returning, does not call self.update() - you have to do that manually.
|
||||
pub fn let_user_make_change(&mut self) -> RScript {
|
||||
// eprintln!(" - - - - - - - - - - - - - - - - - - - - - - - - -");
|
||||
let script = loop {
|
||||
match self.receiver.recv().unwrap() {
|
||||
(Err(e), file) => {
|
||||
self.current_status = format!(
|
||||
" - Error during build{}",
|
||||
e.with_file(&file)
|
||||
.to_string()
|
||||
.lines()
|
||||
.map(|v| format!("\n// {v}"))
|
||||
.collect::<String>()
|
||||
)
|
||||
}
|
||||
(Ok(script), _) => {
|
||||
break script;
|
||||
}
|
||||
}
|
||||
self.update(None);
|
||||
};
|
||||
self.current_status = format!(" - OK");
|
||||
script
|
||||
}
|
||||
pub fn set_status(&mut self, new_status: String) {
|
||||
self.current_status = new_status;
|
||||
}
|
||||
pub fn update(&mut self, overwrite_contents_with: Option<&str>) {
|
||||
if self.editor_join_handle.is_finished() {
|
||||
eprintln!("Error has closed!");
|
||||
std::process::exit(0);
|
||||
}
|
||||
let string = std::fs::read_to_string(self.file_path()).unwrap();
|
||||
let status = format!(
|
||||
"// Tutor: {}/{MAX_POS}{}\n",
|
||||
self.current_pos, self.current_status,
|
||||
);
|
||||
let status_len = status.len();
|
||||
std::fs::write(
|
||||
self.file_path(),
|
||||
if let Some(new_content) = overwrite_contents_with {
|
||||
status + new_content
|
||||
} else {
|
||||
status + &string[self.written_status_byte_len..]
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
self.written_status_byte_len = status_len;
|
||||
// ignore this update to the file
|
||||
_ = self.receiver.recv().unwrap();
|
||||
}
|
||||
pub fn file_path(&self) -> &PathBuf {
|
||||
&self.file_path
|
||||
}
|
||||
}
|
||||
3
old/mers/tests/destructuring_assignment.mers
Executable file
3
old/mers/tests/destructuring_assignment.mers
Executable file
@@ -0,0 +1,3 @@
|
||||
[a, [b, c]] := [1, ["str", 12.5]]
|
||||
|
||||
a == 1 && b == "str" && c == 12.5
|
||||
3
old/mers/tests/force_output_type.mers
Executable file
3
old/mers/tests/force_output_type.mers
Executable file
@@ -0,0 +1,3 @@
|
||||
fn plus(a int, b int) -> int { a + b }
|
||||
|
||||
10.plus(20) == 30
|
||||
18
old/mers/tests/functions_double_definitions.mers
Executable file
18
old/mers/tests/functions_double_definitions.mers
Executable file
@@ -0,0 +1,18 @@
|
||||
type person [string int]
|
||||
fn name(p person/&person) p.0
|
||||
fn age(p person/&person) p.1
|
||||
|
||||
type village [[float float] string]
|
||||
fn name(v village/&village) v.1
|
||||
fn location(v village/&village) v.0
|
||||
fn x_coordinate(v village/&village) v.0.0
|
||||
fn y_coordinate(v village/&village) v.0.1
|
||||
|
||||
|
||||
|
||||
customer := ["Max M.", 43]
|
||||
home_town := [[12.3, 5.09], "Maxburg"]
|
||||
|
||||
customer.name() == "Max M."
|
||||
&& home_town.name() == "Maxburg"
|
||||
&& home_town.location() == [12.3, 5.09]
|
||||
10
old/mers/tests/get_ref.mers
Executable file
10
old/mers/tests/get_ref.mers
Executable file
@@ -0,0 +1,10 @@
|
||||
list := [1 2 3 4 5 6 7 8 9 ...]
|
||||
|
||||
// calling get on an &list will get a reference
|
||||
&list.get(2).assume1() = 24
|
||||
// calling get on a list will get a value
|
||||
should_not_be_changeable := list.get(3).assume1()
|
||||
&should_not_be_changeable = 24
|
||||
|
||||
list.get(2) == [24]
|
||||
&& list.get(3) != [24]
|
||||
36
old/mers/tests/lib_comms.rs
Executable file
36
old/mers/tests/lib_comms.rs
Executable file
@@ -0,0 +1,36 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use mers_libs::{prelude::*, GlobalScriptInfo};
|
||||
use mers_libs::{ByteData, ByteDataA};
|
||||
|
||||
#[test]
|
||||
fn list_type() {
|
||||
let a: Vec<i32> = vec![14, 26];
|
||||
let bytes = a.as_byte_data_vec();
|
||||
println!("{bytes:?}");
|
||||
assert_eq!(
|
||||
Vec::<i32>::from_byte_data(&mut Cursor::new(bytes)).unwrap(),
|
||||
a
|
||||
);
|
||||
|
||||
let a = VSingleType::List(VSingleType::Int.to()).to();
|
||||
assert!(
|
||||
VType::from_byte_data(&mut Cursor::new(a.as_byte_data_vec()))
|
||||
.unwrap()
|
||||
.eq(&a, &GlobalScriptInfo::default()),
|
||||
);
|
||||
|
||||
let a = VSingleType::Tuple(vec![
|
||||
VType {
|
||||
types: vec![VSingleType::Tuple(vec![]), VSingleType::Int],
|
||||
},
|
||||
VSingleType::String.to(),
|
||||
VSingleType::EnumVariant(12, VSingleType::Float.to()).to(),
|
||||
])
|
||||
.to();
|
||||
assert!(
|
||||
VType::from_byte_data(&mut Cursor::new(a.as_byte_data_vec()))
|
||||
.unwrap()
|
||||
.eq(&a, &GlobalScriptInfo::default())
|
||||
);
|
||||
}
|
||||
7
old/mers/tests/macro.mers
Executable file
7
old/mers/tests/macro.mers
Executable file
@@ -0,0 +1,7 @@
|
||||
val := !(mers {
|
||||
"macro returned value"
|
||||
})
|
||||
|
||||
val := !(mers "my_macro.mers")
|
||||
|
||||
true
|
||||
9
old/mers/tests/modify_variable.mers
Executable file
9
old/mers/tests/modify_variable.mers
Executable file
@@ -0,0 +1,9 @@
|
||||
// NOTE: Might change, but this is the current state of things
|
||||
x := 10
|
||||
t := thread(() {
|
||||
sleep(0.25)
|
||||
x
|
||||
})
|
||||
&x = 20 // -> 20 20 because it modifies the original variable x
|
||||
// x := 20 // -> 10 20 because it shadows the original variable x
|
||||
t.await() == x
|
||||
1
old/mers/tests/my_macro.mers
Executable file
1
old/mers/tests/my_macro.mers
Executable file
@@ -0,0 +1 @@
|
||||
true
|
||||
1
old/mers/tests/return_true.mers
Executable file
1
old/mers/tests/return_true.mers
Executable file
@@ -0,0 +1 @@
|
||||
true
|
||||
27
old/mers/tests/test_in_mers.rs
Executable file
27
old/mers/tests/test_in_mers.rs
Executable file
@@ -0,0 +1,27 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use mers_libs::file::File;
|
||||
use mers_libs::{parse, VDataEnum};
|
||||
|
||||
#[test]
|
||||
fn run_all() {
|
||||
for file in fs::read_dir(Path::new(file!()).parent().unwrap())
|
||||
.unwrap()
|
||||
.filter_map(|v| v.ok())
|
||||
{
|
||||
if let Some(file_name) = file.file_name().to_str() {
|
||||
if file_name.ends_with(".mers") {
|
||||
eprintln!("Checking {}", file_name);
|
||||
let mut file = File::new(fs::read_to_string(file.path()).unwrap(), file.path());
|
||||
// has to return true, otherwise the test will fail
|
||||
assert!(
|
||||
matches!(
|
||||
parse::parse(&mut file).unwrap().run(vec![]).inner_cloned(),
|
||||
VDataEnum::Bool(true)
|
||||
),
|
||||
"{file_name} didn't return true!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
old/mers/tests/thread_NOCHECK_ONLY_COMPILE.mers
Executable file
38
old/mers/tests/thread_NOCHECK_ONLY_COMPILE.mers
Executable file
@@ -0,0 +1,38 @@
|
||||
// this is true by default so the example doesn't finish too quickly or too slowly depending on your hardware.
|
||||
// you can set it to false and tweak the max value for a more authentic cpu-heavy workload.
|
||||
fake_delay := true
|
||||
|
||||
// this will be shared between the two threads to report the progress in percent (0-100%).
|
||||
progress := 0
|
||||
|
||||
// an anonymous function that sums all numbers from 0 to max.
|
||||
// it captures the progress variable and uses it to report its status to the main thread, which will periodically print the current progress.
|
||||
// once done, it returns a string with the sum of all numbers.
|
||||
calculator := (max int) {
|
||||
sum := 0
|
||||
for i max {
|
||||
i := i + 1
|
||||
// println("i: {0} s: {1}".format(i.to_string() sum.to_string()))
|
||||
&sum = sum + i
|
||||
// if fake_delay sleep(1)
|
||||
&progress = i * 100 / max
|
||||
}
|
||||
"the sum of all numbers from 0 to {0} is {1}!".format(max.to_string() sum.to_string())
|
||||
}
|
||||
|
||||
// start the thread. if fake_delay is true, calculate 1+2+3+4+5+6+7+8+9+10. if fake_delay is false, count up to some ridiculously large number.
|
||||
slow_calculation_thread := calculator.thread(if fake_delay 10 else 20000000)
|
||||
|
||||
// every second, print the progress. once it reaches 100%, stop
|
||||
loop {
|
||||
sleep(1)
|
||||
println("{0}%".format(progress.to_string()))
|
||||
progress == 100 // break from the loop
|
||||
}
|
||||
|
||||
// use await() to get the result from the thread. if the thread is still running, this will block until the thread finishes.
|
||||
result := slow_calculation_thread.await()
|
||||
|
||||
println("Thread finished, result: {0}".format(result))
|
||||
|
||||
true
|
||||
3
old/mers_libs/gui
Executable file
3
old/mers_libs/gui
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd ./gui_v1
|
||||
cargo run --release
|
||||
10
old/mers_libs/gui_v1/Cargo.toml
Executable file
10
old/mers_libs/gui_v1/Cargo.toml
Executable file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "gui_v1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.8.0", features = ["smol"] }
|
||||
mers = { path = "../../mers/" }
|
||||
444
old/mers_libs/gui_v1/src/main.rs
Executable file
444
old/mers_libs/gui_v1/src/main.rs
Executable file
@@ -0,0 +1,444 @@
|
||||
use std::{
|
||||
io::{self, Read},
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use iced::{
|
||||
executor, time,
|
||||
widget::{button, column, row, text},
|
||||
Application, Command, Element, Renderer, Settings, Subscription, Theme,
|
||||
};
|
||||
|
||||
use mers_libs::{MyLib, MyLibTask, VData, VDataEnum, VSingleType, VType};
|
||||
|
||||
/*
|
||||
|
||||
Path: Vec<usize>
|
||||
|
||||
*/
|
||||
|
||||
fn single_gui_element() -> VType {
|
||||
VType {
|
||||
types: vec![
|
||||
VSingleType::EnumVariantS("Row".to_string(), VSingleType::Tuple(vec![]).to()),
|
||||
VSingleType::EnumVariantS("Column".to_string(), VSingleType::Tuple(vec![]).to()),
|
||||
VSingleType::EnumVariantS("Text".to_string(), VSingleType::String.to()),
|
||||
VSingleType::EnumVariantS(
|
||||
"Button".to_string(),
|
||||
VType {
|
||||
types: vec![VSingleType::Tuple(vec![]), VSingleType::String],
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
fn single_gui_update() -> VType {
|
||||
VType {
|
||||
types: vec![VSingleType::EnumVariantS(
|
||||
"ButtonPressed".to_string(),
|
||||
VSingleType::List(VSingleType::Int.to()).to(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (sender, recv) = mpsc::channel();
|
||||
let (sender2, recv2) = mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let recv = recv2;
|
||||
let (mut my_lib, mut run) = MyLib::new(
|
||||
"GUI-Iced".to_string(),
|
||||
(0, 0),
|
||||
"A basic GUI library for mers.".to_string(),
|
||||
vec![
|
||||
(
|
||||
"gui_init".to_string(),
|
||||
vec![],
|
||||
VSingleType::List(VSingleType::Int.to()).to(),
|
||||
),
|
||||
(
|
||||
"gui_updates".to_string(),
|
||||
vec![],
|
||||
VSingleType::List(single_gui_update()).to(),
|
||||
),
|
||||
(
|
||||
"set_title".to_string(),
|
||||
vec![VSingleType::String.to()],
|
||||
VSingleType::Tuple(vec![]).to(),
|
||||
),
|
||||
(
|
||||
"gui_add".to_string(),
|
||||
vec![
|
||||
VSingleType::List(VSingleType::Int.to()).to(),
|
||||
single_gui_element(),
|
||||
],
|
||||
VSingleType::List(VSingleType::Int.to()).to(),
|
||||
),
|
||||
(
|
||||
"gui_remove".to_string(),
|
||||
vec![VSingleType::List(VSingleType::Int.to()).to()],
|
||||
VSingleType::Tuple(vec![]).to(),
|
||||
),
|
||||
],
|
||||
);
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
let mut layout = Layout::Row(vec![]);
|
||||
loop {
|
||||
run = match my_lib.run(run, &mut stdin, &mut stdout) {
|
||||
MyLibTask::None(v) | MyLibTask::FinishedInit(v) => v,
|
||||
MyLibTask::RunFunction(mut f) => {
|
||||
let return_value = match f.function {
|
||||
0 => VDataEnum::List(VSingleType::Int.to(), vec![]).to(),
|
||||
1 => {
|
||||
let mut v = vec![];
|
||||
while let Ok(recv) = recv.try_recv() {
|
||||
match recv {
|
||||
MessageAdv::ButtonPressed(path) => v.push(
|
||||
VDataEnum::EnumVariant(
|
||||
my_lib.get_enum("ButtonPressed").unwrap(),
|
||||
Box::new(
|
||||
VDataEnum::List(VSingleType::Int.to(), path).to(),
|
||||
),
|
||||
)
|
||||
.to(),
|
||||
),
|
||||
}
|
||||
}
|
||||
VDataEnum::List(single_gui_update(), v).to()
|
||||
}
|
||||
2 => {
|
||||
// set_title
|
||||
if let VDataEnum::String(new_title) = f.args.remove(0).data {
|
||||
sender.send(Task::SetTitle(new_title)).unwrap();
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
// gui_add
|
||||
if let (layout_data, VDataEnum::List(_, path)) =
|
||||
(f.args.remove(1).data, f.args.remove(0).data)
|
||||
{
|
||||
let path: Vec<usize> = path
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
if let VDataEnum::Int(v) = v.data {
|
||||
v as _
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let lo = layout_from_vdata(&my_lib, layout_data);
|
||||
let layout_inner = layout.get_mut(&path, 0);
|
||||
let new_path: Vec<_> = path
|
||||
.iter()
|
||||
.map(|v| VDataEnum::Int(*v as _).to())
|
||||
.chain(
|
||||
[VDataEnum::Int(layout_inner.len() as _).to()].into_iter(),
|
||||
)
|
||||
.collect();
|
||||
layout_inner.add(lo.clone());
|
||||
sender.send(Task::LAdd(path, lo)).unwrap();
|
||||
VDataEnum::List(VSingleType::Int.to(), new_path).to()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
// gui_remove
|
||||
if let VDataEnum::List(_, path) = f.args.remove(0).data {
|
||||
let mut path: Vec<usize> = path
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
if let VDataEnum::Int(v) = v.data {
|
||||
v as _
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if let Some(remove_index) = path.pop() {
|
||||
let layout_inner = layout.get_mut(&path, 0);
|
||||
layout_inner.remove(remove_index);
|
||||
path.push(remove_index);
|
||||
sender.send(Task::LRemove(path)).unwrap();
|
||||
}
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
f.done(&mut stdout, return_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
App::run(Settings::with_flags((recv, sender2))).unwrap();
|
||||
}
|
||||
|
||||
fn layout_from_vdata(my_lib: &MyLib, d: VDataEnum) -> Layout {
|
||||
let row = my_lib.get_enum("Row").unwrap();
|
||||
let col = my_lib.get_enum("Column").unwrap();
|
||||
let text = my_lib.get_enum("Text").unwrap();
|
||||
let button = my_lib.get_enum("Button").unwrap();
|
||||
if let VDataEnum::EnumVariant(variant, inner_data) = d {
|
||||
if variant == row {
|
||||
Layout::Row(vec![])
|
||||
} else if variant == col {
|
||||
Layout::Column(vec![])
|
||||
} else if variant == text {
|
||||
Layout::Text(if let VDataEnum::String(s) = inner_data.data {
|
||||
s
|
||||
} else {
|
||||
String::new()
|
||||
})
|
||||
} else if variant == button {
|
||||
Layout::Button(Box::new(Layout::Text(
|
||||
if let VDataEnum::String(s) = inner_data.data {
|
||||
s
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
)))
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
enum Task {
|
||||
SetTitle(String),
|
||||
LAdd(Vec<usize>, Layout),
|
||||
LSet(Vec<usize>, Layout),
|
||||
LRemove(Vec<usize>),
|
||||
}
|
||||
|
||||
struct App {
|
||||
title: String,
|
||||
recv: mpsc::Receiver<Task>,
|
||||
sender: mpsc::Sender<MessageAdv>,
|
||||
buttons: Vec<Vec<usize>>,
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
Tick,
|
||||
ButtonPressed(usize),
|
||||
}
|
||||
enum MessageAdv {
|
||||
ButtonPressed(Vec<VData>),
|
||||
}
|
||||
|
||||
impl Application for App {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = (mpsc::Receiver<Task>, mpsc::Sender<MessageAdv>);
|
||||
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
(
|
||||
Self {
|
||||
title: format!("mers gui (using iced)..."),
|
||||
recv: flags.0,
|
||||
sender: flags.1,
|
||||
buttons: vec![],
|
||||
layout: Layout::Column(vec![]),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
fn title(&self) -> String {
|
||||
format!("{}", self.title)
|
||||
}
|
||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
match message {
|
||||
Message::Tick => {
|
||||
let mut changed_layout = false;
|
||||
while let Ok(task) = self.recv.try_recv() {
|
||||
match task {
|
||||
Task::SetTitle(t) => {
|
||||
self.title = t;
|
||||
}
|
||||
Task::LAdd(path, add) => {
|
||||
changed_layout = true;
|
||||
self.layout.get_mut(&path, 0).add(add);
|
||||
}
|
||||
Task::LSet(path, add) => {
|
||||
changed_layout = true;
|
||||
*self.layout.get_mut(&path, 0) = add;
|
||||
}
|
||||
Task::LRemove(mut path) => {
|
||||
if let Some(last) = path.pop() {
|
||||
changed_layout = true;
|
||||
self.layout.get_mut(&path, 0).remove(last);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed_layout {
|
||||
self.calc_layout_stats();
|
||||
}
|
||||
}
|
||||
Message::ButtonPressed(bid) => self
|
||||
.sender
|
||||
.send(MessageAdv::ButtonPressed(
|
||||
self.buttons[bid]
|
||||
.iter()
|
||||
.map(|v| VDataEnum::Int(*v as _).to())
|
||||
.collect(),
|
||||
))
|
||||
.unwrap(),
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
time::every(Duration::from_millis(10)).map(|_| Message::Tick)
|
||||
}
|
||||
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
|
||||
self.viewl(&mut vec![], &self.layout, &mut 0)
|
||||
}
|
||||
}
|
||||
impl App {
|
||||
fn viewl(
|
||||
&self,
|
||||
p: &mut Vec<usize>,
|
||||
l: &Layout,
|
||||
current_button: &mut usize,
|
||||
) -> Element<'_, <Self as Application>::Message, Renderer<<Self as Application>::Theme>> {
|
||||
match l {
|
||||
Layout::Row(v) => row(v
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
p.push(i);
|
||||
let o = self.viewl(p, v, current_button);
|
||||
p.pop();
|
||||
o
|
||||
})
|
||||
.collect())
|
||||
.into(),
|
||||
Layout::Column(v) => column(
|
||||
v.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
p.push(i);
|
||||
let o = self.viewl(p, v, current_button);
|
||||
p.pop();
|
||||
o
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.into(),
|
||||
Layout::Text(txt) => text(txt).into(),
|
||||
Layout::Button(content) => button({
|
||||
p.push(0);
|
||||
let o = self.viewl(p, content, current_button);
|
||||
p.pop();
|
||||
o
|
||||
})
|
||||
.on_press(Message::ButtonPressed({
|
||||
let o = *current_button;
|
||||
*current_button = *current_button + 1;
|
||||
o
|
||||
}))
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
fn calc_layout_stats(&mut self) {
|
||||
self.buttons.clear();
|
||||
Self::calc_layout_stats_rec(&self.layout, &mut vec![], &mut self.buttons)
|
||||
}
|
||||
fn calc_layout_stats_rec(
|
||||
layout: &Layout,
|
||||
path: &mut Vec<usize>,
|
||||
buttons: &mut Vec<Vec<usize>>,
|
||||
) {
|
||||
match layout {
|
||||
Layout::Row(v) | Layout::Column(v) => {
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
path.push(i);
|
||||
Self::calc_layout_stats_rec(v, path, buttons);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
Layout::Button(c) => {
|
||||
buttons.push(path.clone());
|
||||
path.push(0);
|
||||
Self::calc_layout_stats_rec(c, path, buttons);
|
||||
path.pop();
|
||||
}
|
||||
Layout::Text(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Layout {
|
||||
Row(Vec<Self>),
|
||||
Column(Vec<Self>),
|
||||
Text(String),
|
||||
Button(Box<Self>),
|
||||
}
|
||||
impl Layout {
|
||||
pub fn get_mut(&mut self, path: &Vec<usize>, index: usize) -> &mut Self {
|
||||
if index >= path.len() {
|
||||
return self;
|
||||
}
|
||||
match self {
|
||||
Self::Row(v) => v[path[index]].get_mut(path, index + 1),
|
||||
Self::Column(v) => v[path[index]].get_mut(path, index + 1),
|
||||
Self::Button(c) => c.as_mut().get_mut(path, index + 1),
|
||||
Self::Text(_) => {
|
||||
panic!("cannot index this layout type! ({:?})", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, add: Layout) {
|
||||
match self {
|
||||
Self::Row(v) | Self::Column(v) => v.push(add),
|
||||
_ => panic!("cannot add to this layout type! ({:?})", self),
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, remove: usize) {
|
||||
match self {
|
||||
Self::Row(v) | Self::Column(v) => {
|
||||
if remove < v.len() {
|
||||
v.remove(remove);
|
||||
}
|
||||
}
|
||||
_ => panic!("cannot add to this layout type! ({:?})", self),
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Row(v) | Self::Column(v) => v.len(),
|
||||
_ => panic!("cannot get len of this layout type! ({:?})", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait DirectRead {
|
||||
fn nbyte(&mut self) -> Result<u8, io::Error>;
|
||||
fn nchar(&mut self) -> Result<char, io::Error>;
|
||||
}
|
||||
impl<T> DirectRead for T
|
||||
where
|
||||
T: Read,
|
||||
{
|
||||
fn nbyte(&mut self) -> Result<u8, io::Error> {
|
||||
let mut b = [0];
|
||||
self.read(&mut b)?;
|
||||
Ok(b[0])
|
||||
}
|
||||
fn nchar(&mut self) -> Result<char, io::Error> {
|
||||
Ok(self.nbyte()?.into())
|
||||
}
|
||||
}
|
||||
3
old/mers_libs/http_requests
Executable file
3
old/mers_libs/http_requests
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd ./http_requests_v1
|
||||
cargo run --release
|
||||
10
old/mers_libs/http_requests_v1/Cargo.toml
Executable file
10
old/mers_libs/http_requests_v1/Cargo.toml
Executable file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "http_requests_v1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
mers = { path = "../../mers/" }
|
||||
reqwest = { version = "0.11.16", features = ["blocking"] }
|
||||
139
old/mers_libs/http_requests_v1/src/main.rs
Executable file
139
old/mers_libs/http_requests_v1/src/main.rs
Executable file
@@ -0,0 +1,139 @@
|
||||
use mers_libs::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut my_lib = MyLib::new(
|
||||
"http".to_string(), // "HTTP requests for MERS".to_string(),
|
||||
(0, 0),
|
||||
"desc".to_string(), // "basic HTTP functionality for mers. warning: this is fully single-threaded.".to_string(),
|
||||
vec![
|
||||
// http_get
|
||||
(
|
||||
"http_get".to_string(),
|
||||
vec![
|
||||
// (String) -> String/Err(ErrBuildingRequest(String)/ErrGettingResponseText(String))
|
||||
(
|
||||
vec![VSingleType::String.to()],
|
||||
VType {
|
||||
types: vec![
|
||||
VSingleType::String,
|
||||
VSingleType::EnumVariantS(
|
||||
format!("Err"),
|
||||
VType {
|
||||
types: vec![
|
||||
VSingleType::EnumVariantS(
|
||||
format!("ErrBuildingRequest"),
|
||||
VSingleType::String.to(),
|
||||
),
|
||||
VSingleType::EnumVariantS(
|
||||
format!("ErrGettingResponseText"),
|
||||
VSingleType::String.to(),
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
let err_general = my_lib
|
||||
.get_enums()
|
||||
.iter()
|
||||
.find(|(name, _)| name.as_str() == "Err")
|
||||
.unwrap()
|
||||
.1;
|
||||
let err_building_request = my_lib
|
||||
.get_enums()
|
||||
.iter()
|
||||
.find(|(name, _)| name.as_str() == "ErrBuildingRequest")
|
||||
.unwrap()
|
||||
.1;
|
||||
let err_getting_response_text = my_lib
|
||||
.get_enums()
|
||||
.iter()
|
||||
.find(|(name, _)| name.as_str() == "ErrGettingResponseText")
|
||||
.unwrap()
|
||||
.1;
|
||||
my_lib.callbacks.run_function.consuming = Some(Box::new(move |msg| {
|
||||
let url = if let VDataEnum::String(url) = msg.msg.args[0].inner_cloned() {
|
||||
url
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
std::thread::spawn(move || {
|
||||
let r = match reqwest::blocking::get(url) {
|
||||
Ok(response) => match response.text() {
|
||||
Ok(text) => VDataEnum::String(text).to(),
|
||||
Err(e) => VDataEnum::EnumVariant(
|
||||
err_general,
|
||||
Box::new(
|
||||
VDataEnum::EnumVariant(
|
||||
err_getting_response_text,
|
||||
Box::new(VDataEnum::String(e.to_string()).to()),
|
||||
)
|
||||
.to(),
|
||||
),
|
||||
)
|
||||
.to(),
|
||||
},
|
||||
Err(e) => VDataEnum::EnumVariant(
|
||||
err_general,
|
||||
Box::new(
|
||||
VDataEnum::EnumVariant(
|
||||
err_building_request,
|
||||
Box::new(VDataEnum::String(e.to_string()).to()),
|
||||
)
|
||||
.to(),
|
||||
),
|
||||
)
|
||||
.to(),
|
||||
};
|
||||
msg.respond(r)
|
||||
});
|
||||
}));
|
||||
// because we handle all callbacks, this never returns Err(unhandeled message).
|
||||
// it returns Ok(()) if mers exits (i/o error in stdin/stdout), so we also exit if that happens.
|
||||
my_lib.get_next_unhandled_message().unwrap();
|
||||
}
|
||||
// fn run_function(f: ()) {
|
||||
// let return_value = match f.function {
|
||||
// 0 => {
|
||||
// // http_get
|
||||
// if let VDataEnum::String(url) = &f.args[0].data {
|
||||
// match reqwest::blocking::get(url) {
|
||||
// Ok(response) => match response.text() {
|
||||
// Ok(text) => VDataEnum::String(text).to(),
|
||||
// Err(e) => VDataEnum::EnumVariant(
|
||||
// err_general,
|
||||
// Box::new(
|
||||
// VDataEnum::EnumVariant(
|
||||
// err_getting_response_text,
|
||||
// Box::new(VDataEnum::String(e.to_string()).to()),
|
||||
// )
|
||||
// .to(),
|
||||
// ),
|
||||
// )
|
||||
// .to(),
|
||||
// },
|
||||
// Err(e) => VDataEnum::EnumVariant(
|
||||
// err_general,
|
||||
// Box::new(
|
||||
// VDataEnum::EnumVariant(
|
||||
// err_building_request,
|
||||
// Box::new(VDataEnum::String(e.to_string()).to()),
|
||||
// )
|
||||
// .to(),
|
||||
// ),
|
||||
// )
|
||||
// .to(),
|
||||
// }
|
||||
// } else {
|
||||
// unreachable!()
|
||||
// }
|
||||
// }
|
||||
// _ => unreachable!(),
|
||||
// };
|
||||
// f.done(&mut stdout, return_value)
|
||||
// }
|
||||
Reference in New Issue
Block a user