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

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`