- changed the list type from [t] to [t ...]

- added more examples to the readme
This commit is contained in:
Dummi26 2023-04-13 17:40:25 +02:00
parent a6389b7ac0
commit a2a976c7f9
11 changed files with 935 additions and 106 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
script.mers
/mers/target
/mers/Cargo.lock
/mers_libs/*/target

143
README.md
View File

@ -32,24 +32,34 @@ To index this tuple, you can use `tuple.0` for the exit code and `tuple.1` and `
The compiler checks your program. It will guarantee type-safety. If a variable has type `int/float/bool`, it cannot be used in the add() function, because you can't add bools.
## Intro
## building mers
### mers cli
to use mers, clone this repo and compile it using cargo. (if you don't have rustc and cargo, get it from https://rustup.rs/)
to use mers, clone this repo and compile it using cargo. (if you don't have rustc and cargo, get it from https://rustup.rs/. the mers project is in the mers subdirectory, one level deeper than this readme.)
for simplicity, i will assume you have the executable in your path and it is named mers. Since this probably isn't the case, just know that `mers` can be replaced with `cargo run --release` in all of the following commands.
### running from a file
To run a program, just run `mers script.txt`. The file needs to be valid utf8.
To run a program, just run `mers your_file.txt`. The file needs to be valid utf8.
Alternatively, run `mers -e println("Hello, file-less world")`.
If you compiled mers in debug mode, it will print a lot of debugging information.
### interactive mode
Use `mers -i` to start interactive mode. mers will create a temporary file and open it in your default editor. Every time the file is saved, mers reloads and runs it, showing errors or the output.
If your default editor is a CLI editor, it might hide mers' output. Run `mers -i+t` to start the editor in another terminal. This requires that $TERM is a terminal emulator that works with the `TERM -e command args` syntax (alacritty, konsole, ..., but not wezterm).
### Output
Somewhere in mers' output, you will see a line with five '-' characters: ` - - - - -`. This is where your program starts running. The second time you see this line is where your program finished. After this, You will see how long your program took to run and its output.
## Intro
Welcome to mers! This section explains the basics of the language via examples.
Some basic knowledge of another programming language might be helpful,
but no advanced knowledge is required (and if it is, that just means that my examples aren't good enough and need to be improved).
### Hello, World!
Since this is likely your first time using mers, let's write a hello world program:
@ -57,7 +67,7 @@ Since this is likely your first time using mers, let's write a hello world progr
println("Hello, World!")
If you're familiar with other programming languages, this is probably what you expected. Running it prints Hello, World! between the two five-dash-lines.
The `"` character starts/ends a string literal. This creates a value of type `string` which is then passed to the `println()` function, which writes the string to the programs stdout.
The `"` character starts/ends a string literal. This creates a value of type `String` which is then passed to the `println()` function, which writes the string to the programs standard output (stdout).
### Hello, World?
@ -65,7 +75,7 @@ But what if we didn't print anything?
"Hello, World?"
Running this should show Hello, World? as the program's output. This is because the output of a code block in mers is always the output of its last statement. Since we only have one statement, its output is the entire program's output.
Running this should show `Hello, World?` as the program's output. This is because the output of a code block in mers is always the output of its last statement. Since we only have one statement, its output is the entire program's output.
### Hello, Variable!
@ -90,7 +100,8 @@ The builtin `read_line()` function reads a line from stdin. It can be used to ge
### Format
`format()` is a builtin function that takes a string (called the format string) and any number of further arguments. The pattern `{n}` anywhere in the format string will be replaced with the n-th argument, not counting the format string.
`format()` is a builtin function that is used to format text.
It takes a string (called the format string) and any number of further arguments. The pattern `{n}` anywhere in the format string will be replaced with the n-th argument, not counting the format string.
println("What is your name?")
name = read_line()
@ -98,22 +109,20 @@ The builtin `read_line()` function reads a line from stdin. It can be used to ge
### alternative syntax for the first argument
If function(a b) is valid, you can also use a.function(b). This does exactly the same thing, because a.function(args) inserts a as the first argument, moving all other args back by one.
If function(a b) is valid, you can also use a.function(b). They do exactly the same thing.
This can make reading `println(format(...))` statements a lot more enjoyable:
println("Hello, {0}! How was your day?".format(name))
However, this can also be overused:
This also works to chain multiple functions together:
"Hello, {0}! How was your day?".format(name).println()
I would consider this to be less readable because `println()`, which is the one and only thing this line was written to achieve, is now the last thing in the line. Readers need to read the entire line before realizing it's "just" a print statement. However, this is of course personal preference.
### A simple counter
Let's build a counter app: We start at 0. If the user types '+', we increment the counter by one. If they type '-', we decrement it. If they type anything else, we print the current count in a status message.
The first thing we will need for this is a loop:
The first thing we will need for this is a loop to prevent the app from stopping after the first user input:
while {
println("...")
@ -121,7 +130,15 @@ The first thing we will need for this is a loop:
Running this should spam your terminal with '...'.
Now let's add a counter variable and read user input:
Now let's add a counter variable, read user input and print the status message.
counter = 0
while {
input = read_line()
println("The counter is currently at {0}. Type + or - to change it.".format(counter))
}
We can then use `eq(a b)` to check if the input is equal to + or -, and then decide to increase or decrease counter:
counter = 0
while {
@ -135,7 +152,7 @@ Now let's add a counter variable and read user input:
}
}
mers actually doesn't have an else-if, the if statement is parsed as:
mers actually doesn't have an else-if, the if statement is simply parsed as:
if input.eq("+") {
counter = counter.add(1)
@ -153,7 +170,7 @@ In fact, `fn difference(a int/float b int/float) if a.gt(b) a.sub(b) else b.sub(
### Getting rid of the if-else-chain
Let's replace the if statement from before with a nice match statement!
Let's replace the if statement from before with a match statement!
counter = 0
while {
@ -171,6 +188,8 @@ A match arm consists of a condition statement and an action statement. `input.eq
The match statement will go through all condition statements until one matches (in this case: returns `true`), then run the action statement.
If we move the `true` match arm to the top, the other two arms will never be executed, even though they might also match.
Match statements are a lot more powerful than if-else-statements, but this will be explained in a later example.
### Breaking from loops
Loops will break if the value returned in the current iteration matches:
@ -250,7 +269,95 @@ We then print the string we read from the file (the `contents` variable).
### Advanced match statements
(todo)
Some constructs in mers use the concept of "Matching". The most obvious example of this is the `match` statement:
x = 10
match x {
x.eq(10) println("x was 10.")
true println("x was not 10.")
}
Here, we check if x is equal to 10. If it is, we print "x was 10.". If not, we print "x was not 10.".
So far, this is almost the same as an if statement.
However, match statements have a superpower: They can change the value of the variable:
x = 10
match x {
x.eq(10) println("x is now {0}".format(x))
true println("x was not 10.")
}
Instead of the expected "x is now 10", this actually prints "x is now true", because `x.eq(10)` returned `true`.
In this case, this behavior isn't very useful. However, many builtin functions are designed with matching in mind.
For example, `parse_int()` and `parse_float()` return `[]/int` and `[]/float`. `[]` will not match, but `int` and `float` will.
We can use this to parse a list of strings into numbers: First, try to parse the string to an int. If that doesn't work, try a float. If that also doesn't work, print "not a number".
Using a match statement, this is one way to implement it:
strings = ["87" "not a number" "25" "14.5" ...]
for x strings {
match x {
x.parse_int() println("int: {0} = 10 * {1} + {2}".format(x x.sub(x.mod(10)).div(10) x.mod(10)))
x.parse_float() println("float: {0} = {1} + {2}".format(x x.sub(x.mod(1)) x.mod(1)))
true println("not a number")
}
}
Because the condition statement is just a normal expression, we can make it do pretty much anything. This means we can define our own functions and use those in the match statement
to implement almost any functionality we want. All we need to know is that `[]` doesn't match and `[v]` does match with `v`, so if our function returns `["some text"]` and is used in a match statement,
the variable that is being matched on will become `"some text"`.
### Example: sum of all scores of the lines in a string
This is the task:
You are given a string. Each line in that string is either an int, one or more words (a-z only), or an empty line. Calculate the sum of all scores.
- If a line contains an int, its score is just that int: "20" has a score of 20.
- If a line contains words, its score is the product of the length of all words: "hello world" has a score of 5*5=25.
- If a line is empty, its score is 0.
A possible solution in mers looks like this:
// if there is at least one argument, treat each argument as one line of puzzle input. otherwise, use the default input.
input = if args.len().gt(0) {
input = ""
for arg args {
input = input.add(arg).add("\n")
}
input
} else {
"this is the default puzzle input\n312\n\n-50\nsome more words\n21"
// 4*2*3*7*6*5 = 5040
// 312
// 0
// -50
// 4*4*5 = 80
// => expected sum: 5403
}
// calculates the score for a line of words. returns 1 for empty lines.
fn word_line_score(line string) {
line_score = 1
// \S+ matches any number of non-whitespace characters => split at spaces
for x line.regex("\\S+").assume_no_enum("word-split regex is valid") {
line_score = line_score.mul(x.len())
}
line_score
}
sum = 0
// .+ matches all lines that aren't empty
for x input.regex(".+").assume_no_enum("line-split regex is valid") {
match x {
x.parse_int() sum = sum.add(x)
x.word_line_score() sum = sum.add(x)
}
}
sum
## Advanced Info / Docs

View File

495
mers/Cargo.lock generated
View File

@ -11,6 +11,197 @@ dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
[[package]]
name = "edit"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c562aa71f7bc691fde4c6bf5f93ae5a5298b617c2eb44c76c87832299a17fbb4"
dependencies = [
"tempfile",
"which",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "filetime"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.16",
"windows-sys 0.48.0",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "kqueue"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -21,7 +212,64 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
name = "mers"
version = "0.1.0"
dependencies = [
"edit",
"notify",
"regex",
"static_assertions",
]
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "notify"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
dependencies = [
"bitflags",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"mio",
"walkdir",
"windows-sys 0.42.0",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]]
@ -40,3 +288,250 @@ name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "rustix"
version = "0.37.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "tempfile"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.45.0",
]
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View File

@ -10,4 +10,7 @@ name = "mers"
path = "src/main.rs"
[dependencies]
edit = "0.1.4"
notify = "5.1.0"
regex = "1.7.2"
static_assertions = "1.1.0"

View File

@ -1,24 +1,172 @@
use std::time::Instant;
use std::{
fs,
sync::{Arc, Mutex},
time::Instant,
};
use notify::Watcher as FsWatcher;
pub mod libs;
pub mod parse;
pub mod script;
fn main() {
let args: Vec<_> = std::env::args().skip(1).collect();
let path = std::env::args().nth(1).unwrap();
let script = parse::parse::parse(&mut if path.trim() == "-e" {
parse::file::File::new(
std::env::args()
.skip(2)
.map(|mut v| {
v.push('\n');
v
})
.collect::<String>(),
path.into(),
)
} else {
parse::file::File::new(std::fs::read_to_string(&path).unwrap(), path.into())
let script = parse::parse::parse(&mut match args.len() {
0 => {
println!("Please provide some arguments, such as the path to a file or \"-e <code>\".");
std::process::exit(100);
}
_ => {
if args[0].trim_start().starts_with("-") {
let mut execute = false;
let mut verbose = 0;
let mut interactive = 0;
let mut interactive_use_new_terminal = 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 {
'e' => execute = true,
'v' => verbose += 1,
'i' => interactive += 1,
ch => {
eprintln!("Ignoring -{ch}. (unknown char)");
continue;
}
}
prev_char = Some(ch);
} else {
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)"),
}
}
_ => (),
}
} else {
eprintln!("Ignoring advanced args because there was no previous argument.");
}
advanced = false;
}
}
if verbose != 0 {
eprintln!("info: set verbosity level to {verbose}. this doesn't do anything yet. [TODO!]");
}
if interactive >= 0 {
let (contents, path) = match interactive {
1 => {
// basic: open file and watch for fs changes
let temp_file_edit = edit::Builder::new().suffix(".mers").tempfile().unwrap();
let temp_file = temp_file_edit.path();
eprintln!("Using temporary file at {temp_file:?}. Save the file to update the output here.");
if let Ok(_) = std::fs::write(&temp_file, []) {
if let Ok(mut watcher) = {
let temp_file = temp_file.to_path_buf();
// 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(_)) => {
if let Ok(file_contents) = fs::read_to_string(&temp_file) {
let mut file = parse::file::File::new(file_contents, temp_file.clone());
static_assertions::const_assert_eq!(parse::parse::PARSE_VERSION, 0);
let mut ginfo = script::block::to_runnable::GInfo::default();
let libs = parse::parse::parse_step_lib_paths(&mut file);
match parse::parse::parse_step_interpret(&mut file) {
Ok(func) => {
let libs = parse::parse::parse_step_libs_load(libs, &mut ginfo);
ginfo.libs = Arc::new(libs);
match parse::parse::parse_step_compile(func, &mut ginfo) {
Ok(func) => {
println!();
println!(" - - - - -");
let output = func.run(vec![]);
println!(" - - - - -");
println!("{}", output);
}
Err(e) => eprintln!("Couldn't compile:\n{e:?}"),
}
}
Err(e) =>eprintln!("Couldn't interpret:\n{e:?}"),
}
} else {
println!("can't read file at {:?}!", temp_file);
std::process::exit(105);
}
}
_ => (),
}
}
})} {
if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) {
if interactive_use_new_terminal {
if let Ok(term) = std::env::var("TERM") {
let editor = edit::get_editor().unwrap();
eprintln!("launching \"{term} -e {editor:?} {temp_file:?}...");
std::process::Command::new(term)
.arg("-e")
.arg(&editor)
.arg(temp_file)
.spawn()
.unwrap()
.wait()
.unwrap();
}
} else {
edit::edit_file(temp_file_edit.path()).unwrap();
}
temp_file_edit.close().unwrap();
std::process::exit(0);
} else {
println!("Cannot watch the file at \"{:?}\" for hot-reload.", temp_file);
std::process::exit(104);
}
} else {
println!("Cannot use filesystem watcher for hot-reload.");
// TODO: don't exit here?
std::process::exit(103);
}
} else {
println!("could not write file \"{:?}\".", temp_file);
std::process::exit(102);
}
}
_ => (String::new(), String::new()),
};
parse::file::File::new(
contents,
path.into()
)
}
else if execute {
parse::file::File::new(
args.iter().skip(1).fold(String::new(), |mut s, v| {
if !s.is_empty() {
s.push(' ');
}
s.push_str(v);
s
}),
path.into(),
)
} else {
println!("please provide either a file or -e and a script to run!");
std::process::exit(101);
}
} else {
parse::file::File::new(std::fs::read_to_string(&args[0]).unwrap(), path.into())
}
}
})
.unwrap();
println!(" - - - - -");

View File

@ -33,9 +33,31 @@ impl From<ToRunnableError> for ScriptError {
#[derive(Debug)]
pub enum ParseError {}
pub const PARSE_VERSION: u64 = 0;
/// executes the 4 parse_steps in order: lib_paths => interpret => libs_load => compile
pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
let mut ginfo = GInfo::default();
let libs = parse_step_lib_paths(file);
let func = parse_step_interpret(file)?;
ginfo.libs = Arc::new(parse_step_libs_load(libs, &mut ginfo));
eprintln!();
#[cfg(debug_assertions)]
eprintln!("Parsed: {func}");
#[cfg(debug_assertions)]
eprintln!("Parsed: {func:#?}");
let run = parse_step_compile(func, &mut ginfo)?;
#[cfg(debug_assertions)]
eprintln!("Runnable: {run:#?}");
Ok(run)
}
pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> {
let mut libs = vec![];
let mut enum_variants = GInfo::default_enum_variants();
loop {
file.skip_whitespaces();
let pos = file.get_pos().clone();
@ -49,37 +71,43 @@ pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
if let Some(parent) = path_to_executable.parent() {
cmd.current_dir(parent.clone());
}
match libs::Lib::launch(cmd, &mut enum_variants) {
Ok(lib) => {
libs.push(lib);
eprintln!("Loaded library!");
}
Err(e) => panic!(
"Unable to load library at {}: {e:?}",
path_to_executable.to_string_lossy().as_ref(),
),
}
libs.push(cmd);
} else {
file.set_pos(pos);
break;
}
}
let func = SFunction::new(
libs
}
pub fn parse_step_interpret(file: &mut File) -> Result<SFunction, ParseError> {
Ok(SFunction::new(
vec![(
"args".to_string(),
VSingleType::List(VSingleType::String.into()).into(),
)],
parse_block_advanced(file, Some(false), true, true, false)?,
);
eprintln!();
#[cfg(debug_assertions)]
eprintln!("Parsed: {func}");
#[cfg(debug_assertions)]
eprintln!("Parsed: {func:#?}");
let run = to_runnable::to_runnable(func, GInfo::new(Arc::new(libs), enum_variants))?;
#[cfg(debug_assertions)]
eprintln!("Runnable: {run:#?}");
Ok(run)
))
}
pub fn parse_step_libs_load(lib_cmds: Vec<Command>, ginfo: &mut GInfo) -> Vec<libs::Lib> {
let mut libs = vec![];
for cmd in lib_cmds {
match libs::Lib::launch(cmd, &mut ginfo.enum_variants) {
Ok(lib) => {
libs.push(lib);
}
Err(e) => eprintln!("!! Unable to load library: {e:?} !!",),
}
}
libs
}
pub fn parse_step_compile(
main_func: SFunction,
ginfo: &mut GInfo,
) -> Result<RScript, ToRunnableError> {
to_runnable::to_runnable(main_func, ginfo)
}
fn parse_block(file: &mut File) -> Result<SBlock, ParseError> {
@ -503,8 +531,17 @@ fn parse_single_type_adv(
// Tuple or Array
Some('[') => {
let mut types = vec![];
let mut list = false;
loop {
file.skip_whitespaces();
if file[file.get_char_index()..].starts_with("...]") {
list = true;
file.next();
file.next();
file.next();
file.next();
break;
}
match file.peek() {
Some(']') => {
file.next();
@ -521,7 +558,7 @@ fn parse_single_type_adv(
file.next();
}
}
if types.len() == 1 {
if list {
VSingleType::List(types.pop().unwrap())
} else {
VSingleType::Tuple(types)

View File

@ -8,7 +8,7 @@ use std::{
use crate::libs;
use self::to_runnable::{ToRunnableError, GInfo};
use self::to_runnable::{GInfo, ToRunnableError};
use super::{
builtins::BuiltinFunction,
@ -94,10 +94,14 @@ pub mod to_runnable {
sync::Arc,
};
use crate::{script::{
val_data::VDataEnum,
val_type::{VSingleType, VType}, builtins,
}, libs};
use crate::{
libs,
script::{
builtins,
val_data::VDataEnum,
val_type::{VSingleType, VType},
},
};
use super::{
BuiltinFunction, RBlock, RFunction, RScript, RStatement, RStatementEnum, SBlock, SFunction,
@ -150,13 +154,27 @@ pub mod to_runnable {
// Global, shared between all
pub struct GInfo {
vars: usize,
libs: Arc<Vec<libs::Lib>>,
lib_fns: HashMap<String, (usize, usize)>,
enum_variants: HashMap<String, usize>,
pub libs: Arc<Vec<libs::Lib>>,
pub lib_fns: HashMap<String, (usize, usize)>,
pub enum_variants: HashMap<String, usize>,
}
impl Default for GInfo {
fn default() -> Self {
Self {
vars: 0,
libs: Arc::new(vec![]),
lib_fns: HashMap::new(),
enum_variants: Self::default_enum_variants(),
}
}
}
impl GInfo {
pub fn default_enum_variants() -> HashMap<String, usize> {
builtins::EVS.iter().enumerate().map(|(i, v)| (v.to_string(), i)).collect()
builtins::EVS
.iter()
.enumerate()
.map(|(i, v)| (v.to_string(), i))
.collect()
}
pub fn new(libs: Arc<Vec<libs::Lib>>, enum_variants: HashMap<String, usize>) -> Self {
let mut lib_fns = HashMap::new();
@ -165,7 +183,12 @@ pub mod to_runnable {
lib_fns.insert(name.to_string(), (libid, fnid));
}
}
Self { vars: 0, libs, lib_fns, enum_variants }
Self {
vars: 0,
libs,
lib_fns,
enum_variants,
}
}
}
// Local, used to keep local variables separated
@ -175,24 +198,27 @@ pub mod to_runnable {
fns: HashMap<String, Arc<RFunction>>,
}
pub fn to_runnable(s: SFunction, mut ginfo: GInfo) -> Result<RScript, ToRunnableError> {
pub fn to_runnable(s: SFunction, ginfo: &mut GInfo) -> Result<RScript, ToRunnableError> {
if s.inputs.len() != 1 || s.inputs[0].0 != "args" {
return Err(ToRunnableError::MainWrongInput);
}
assert_eq!(s.inputs[0].1,VType {
types: vec![VSingleType::List(VType {
types: vec![VSingleType::String],
})],
});
assert_eq!(
s.inputs[0].1,
VType {
types: vec![VSingleType::List(VType {
types: vec![VSingleType::String],
})],
}
);
let func = function(
&s,
&mut ginfo,
ginfo,
LInfo {
vars: HashMap::new(),
fns: HashMap::new(),
},
)?;
Ok(RScript::new(func, ginfo.vars, ginfo.libs)?)
Ok(RScript::new(func, ginfo.vars, ginfo.libs.clone())?)
}
// go over every possible known-type input for the given function, returning all possible RFunctions.
@ -276,21 +302,24 @@ pub mod to_runnable {
for t in v {
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::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()
},
)
}
_ => (),
}
}
@ -533,7 +562,6 @@ pub mod to_runnable {
} else {
eprintln!("WARN: Match condition with return type {} never returns a match and will be ignored entirely. Note: this also skips type-checking for the action part of this match arm because the success type is not known.", case_condition_out);
}
}
linfo.vars.get_mut(match_on).unwrap().1 = og_type;
@ -721,7 +749,7 @@ pub enum RStatementEnum {
Switch(RStatement, Vec<(VType, RStatement)>),
Match(usize, Vec<(RStatement, RStatement)>),
IndexFixed(RStatement, usize),
EnumVariant(usize, RStatement)
EnumVariant(usize, RStatement),
}
impl RStatementEnum {
pub fn run(&self, vars: &Vec<Am<VData>>, libs: &Arc<Vec<libs::Lib>>) -> VData {
@ -758,7 +786,9 @@ impl RStatementEnum {
func.run(vars, libs)
}
Self::BuiltinFunction(v, args) => v.run(args, vars, libs),
Self::LibFunction(libid, fnid, args, _) => libs[*libid].run_fn(*fnid, &args.iter().map(|arg| arg.run(vars, libs)).collect()),
Self::LibFunction(libid, fnid, args, _) => {
libs[*libid].run_fn(*fnid, &args.iter().map(|arg| arg.run(vars, libs)).collect())
}
Self::Block(b) => b.run(vars, libs),
Self::If(c, t, e) => {
if let VDataEnum::Bool(v) = c.run(vars, libs).data {
@ -802,7 +832,10 @@ impl RStatementEnum {
}
VDataEnum::String(v) => {
for ch in v.chars() {
if let Some(v) = in_loop(VDataEnum::String(ch.to_string()).to()).data.matches() {
if let Some(v) = in_loop(VDataEnum::String(ch.to_string()).to())
.data
.matches()
{
oval = v;
break;
}
@ -836,9 +869,7 @@ impl RStatementEnum {
for (case_condition, case_action) in cases {
// [t] => Some(t), t => Some(t), [] | false => None
if let Some(v) = case_condition.run(vars, libs).data.matches() {
let og = {
std::mem::replace(&mut *vars[*match_on].lock().unwrap(), v)
};
let og = { std::mem::replace(&mut *vars[*match_on].lock().unwrap(), v) };
let res = case_action.run(vars, libs);
*vars[*match_on].lock().unwrap() = og;
break 'm res;
@ -847,9 +878,7 @@ impl RStatementEnum {
VDataEnum::Tuple(vec![]).to()
}
Self::IndexFixed(st, i) => st.run(vars, libs).get(*i).unwrap(),
Self::EnumVariant(e, v) => {
VDataEnum::EnumVariant(*e, Box::new(v.run(vars, libs))).to()
}
Self::EnumVariant(e, v) => VDataEnum::EnumVariant(*e, Box::new(v.run(vars, libs))).to(),
}
}
pub fn out(&self) -> VType {
@ -887,12 +916,8 @@ impl RStatementEnum {
a.out() | VSingleType::Tuple(vec![]).to()
}
}
Self::While(c) => {
c.out().matches().1
}
Self::For(_, _, b) => {
VSingleType::Tuple(vec![]).to() | b.out().matches().1
}
Self::While(c) => c.out().matches().1,
Self::For(_, _, b) => VSingleType::Tuple(vec![]).to() | b.out().matches().1,
Self::BuiltinFunction(f, args) => f.returns(args.iter().map(|rs| rs.out()).collect()),
Self::Switch(switch_on, cases) => {
let switch_on = switch_on.out().types;
@ -941,7 +966,11 @@ pub struct RScript {
libs: Arc<Vec<libs::Lib>>,
}
impl RScript {
fn new(main: RFunction, vars: usize, libs: Arc<Vec<libs::Lib>>) -> Result<Self, ToRunnableError> {
fn new(
main: RFunction,
vars: usize,
libs: Arc<Vec<libs::Lib>>,
) -> Result<Self, ToRunnableError> {
if main.inputs.len() != 1 {
return Err(ToRunnableError::MainWrongInput);
}
@ -1016,7 +1045,7 @@ impl Display for VSingleType {
write!(f, "]")?;
Ok(())
}
Self::List(t) => write!(f, "[{t}]"),
Self::List(t) => write!(f, "[{t} ...]"),
Self::Function(_) => write!(f, "FUNCTION"),
Self::Thread(_) => write!(f, "THREAD"),
Self::Reference(r) => write!(f, "&{r}"),

View File

@ -354,7 +354,16 @@ impl BuiltinFunction {
}
}
// TODO! finish this
_ => true,
Self::Pop | Self::Remove | Self::Get | Self::Len | Self::Substring => true,
Self::Contains | Self::StartsWith | Self::EndsWith | Self::Regex => {
input.len() == 2
&& input
.iter()
.all(|v| v.fits_in(&VSingleType::String.to()).is_empty())
}
Self::Trim => {
input.len() == 1 && input[0].fits_in(&VSingleType::String.to()).is_empty()
}
}
}
/// for invalid inputs, may panic