move old mers to old/ and update gitignore

This commit is contained in:
Mark
2023-07-28 15:24:38 +02:00
parent 07745488b3
commit e549b1a5be
76 changed files with 1631 additions and 9 deletions

302
old/README.md Executable file
View File

@@ -0,0 +1,302 @@
# mers ![build status](https://github.com/Dummi26/mers/actions/workflows/rust.yml/badge.svg)
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
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
cd ../mers
cargo build --release
cp target/release/mers ../build_scripts

View 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

View 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

View 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."

View 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

Binary file not shown.

BIN
old/build_scripts/mers.exe Executable file

Binary file not shown.

175
old/docs/builtins.md Executable file
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

19
old/examples/custom_type.mers Executable file
View 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()

View 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, ...]) {
// }

View File

@@ -0,0 +1,3 @@
fn plus(a int, b int) -> int { a + b }
10.plus(20).debug()

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

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

File diff suppressed because it is too large Load Diff

26
old/mers/Cargo.toml Executable file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

117
old/mers/src/lang/code_macro.rs Executable file
View 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
View 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()))
}
}
}
}

View 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
View 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
View 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
View 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
View 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(&current_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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
pub mod file;
pub mod parse;

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

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

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

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

View 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,
}
}
}

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

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

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

View File

@@ -0,0 +1,3 @@
[a, [b, c]] := [1, ["str", 12.5]]
a == 1 && b == "str" && c == 12.5

View File

@@ -0,0 +1,3 @@
fn plus(a int, b int) -> int { a + b }
10.plus(20) == 30

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

@@ -0,0 +1,7 @@
val := !(mers {
"macro returned value"
})
val := !(mers "my_macro.mers")
true

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

@@ -0,0 +1 @@
true

View File

@@ -0,0 +1 @@
true

27
old/mers/tests/test_in_mers.rs Executable file
View 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!"
);
}
}
}
}

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

@@ -0,0 +1,3 @@
#!/bin/bash
cd ./gui_v1
cargo run --release

10
old/mers_libs/gui_v1/Cargo.toml Executable file
View 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
View 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
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cd ./http_requests_v1
cargo run --release

View 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"] }

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