updated site + added docs/builtins.md + statements can end with ',' to differentiate '1, -2' from '1 - 2' == '-1'.

This commit is contained in:
mark 2023-05-25 00:22:03 +02:00
parent 46653666f0
commit e766e78e96
8 changed files with 203 additions and 33 deletions

View File

@ -32,4 +32,7 @@ Now, create a new text file (or choose one from the examples) and run it: `mers
## Docs ## Docs
[syntax cheat sheet](docs/syntax_cheat_sheet.md) [syntax cheat sheet](docs/syntax_cheat_sheet.md)
[intro](docs/intro.md) [intro](docs/intro.md)
[builtins](docs/builtins.md)

160
docs/builtins.md Normal file
View File

@ -0,0 +1,160 @@
# 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
returns a list of matches of the arg0 regex that were found in the string arg1

View File

@ -9,7 +9,7 @@
<section class="container"> <section class="container">
<section class="container_left2 code-border"> <section class="container_left2 code-border">
<pre><code class="mers-code-snippet"> <pre><code class="mers-code-snippet">
fn get_number_input(question string) {<br> println(question)<br> input = read_line()<br> // try to parse to an int, then a float.<br> in = match input {<br> input.parse_int() input<br> input.parse_float() input<br> }<br> // 'in' has type int/float/[] because of the match statement<br> switch! in {<br> int/float in<br> // replace [] with an appropriate error before returning<br> [] Err: "input was not a number."<br> }<br> // return type is int/float/Err(string)<br>}<br><br>answer = get_number_input("What is your favorite number?")<br><br>answer.debug() // type: int/float/Err(string)<br>// switch can be used to branch based on a variables type.<br>// switch! indicates that every possible type must be handled.<br>switch! answer {<br> int {<br> println("Entered an integer")<br> answer.debug() // type: int<br> }<br> float {<br> println("Entered a decimal number")<br> answer.debug() // type: float<br> }<br> Err(string) println("Input was not a number!")<br>}<br><br>// wait one second<br>sleep(1)<br><br><br>// function that returns an anonymous function (function object).<br>// anonymous functions can be used as iterators in for-loops.<br>fn square_numbers() {<br> i = 0<br> () {<br> i = i + 1<br> i * i<br> }<br>}<br><br>for num square_numbers() {<br> println(num.to_string())<br> // once num is greater than 60, the loop stops.<br> num.gt(50)<br>}<br></code></pre> fn get_number_input(question string) {<br> println(question)<br> input := read_line()<br> // try to parse to an int, then a float.<br> in := match input {<br> input.parse_int() input<br> input.parse_float() input<br> }<br> // 'in' has type int/float/[] because of the match statement<br> switch! in {<br> int/float in<br> // replace [] with an appropriate error before returning<br> [] Err: "input was not a number."<br> }<br> // return type is int/float/Err(string)<br>}<br><br>answer := get_number_input("What is your favorite number?")<br><br>answer.debug() // type: int/float/Err(string)<br>// switch can be used to branch based on a variables type.<br>// switch! indicates that every possible type must be handled.<br>switch! answer {<br> int {<br> println("Entered an integer")<br> answer.debug() // type: int<br> }<br> float {<br> println("Entered a decimal number")<br> answer.debug() // type: float<br> }<br> Err(string) println("Input was not a number!")<br>}<br><br>// wait one second<br>sleep(1)<br><br><br>// function that returns an anonymous function (function object).<br>// anonymous functions can be used as iterators in for-loops.<br>fn square_numbers() {<br> i := 0<br> () {<br> &amp;i = i + 1<br> i * i<br> }<br>}<br><br>for num square_numbers() {<br> println(num.to_string())<br> // once num is greater than 60, the loop stops.<br> num.gt(50)<br>}<br></code></pre>
</section> </section>
<section class="container_right"> <section class="container_right">
<image <image
@ -42,7 +42,7 @@ it is by using one of the assume*() functions (similar to unwrap()s).
<pre class="container2_left"><code> <pre class="container2_left"><code>
&lt;!DOCTYPE html&gt;<br># This document will be processed by build.mers.<br># Lines starting with hashtags are comments and will be ignored.<br># Lines starting with dollar-signs insert special text.<br># To escape this, put a space before the hashtag or dollar sign.<br>&lt;head&gt;<br> &lt;meta charset=“UTF-8”&gt;<br> &lt;link rel="stylesheet" href="external.css"&gt;<br> &lt;title&gt;Mark :: mers&lt;/title&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br> &lt;h1&gt;Mers&lt;/h1&gt;<br> &lt;section class="container"&gt;<br> &lt;section class="container_left2 code-border"&gt;<br> &lt;pre&gt;&lt;code class="mers-code-snippet"&gt;<br>$welcome_script<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;/section&gt;<br> &lt;section class="container_right"&gt;<br> &lt;image<br> alt="some picture related to mers (todo)"<br> src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////<br> width="100%" height="100%"<br> &gt;<br> &lt;h3&gt;Mers types&lt;/h3&gt;<br> &lt;div&gt;<br> Mers uses a multiple-types system.<br> It keeps track of which types a variable could have<br> and constructs a type with that information.<br> &lt;br&gt;<br> For example, &lt;code&gt;int/float&lt;/code&gt; can represent a number - int or<br> Optional types can be &lt;code&gt;[]/[t]&lt;/code&gt; - either nothing or one v<br> Mers doesn't have null, it just has the empty tuple &lt;code&gt;[]&lt;/code&gt;<br> &lt;/div&gt;<br> &lt;h3&gt;No exceptions, no crashes&lt;/h3&gt;<br> &lt;div&gt;<br> Errors in mers are passed as values.<br> Because of the type system, you are forced to handle them explicitl<br> Mers will not crash in unexpected places, because the only way to c<br> it is by using one of the assume*() functions (similar to unwrap()s<br> &lt;/div&gt;<br> &lt;/section&gt;<br> &lt;/section&gt;<br> &lt;hr&gt;<br> &lt;h3&gt;HTML preprocessor to help build this document written in mers:&lt;/h3&gt;<br> &lt;section class="container"&gt;<br> &lt;pre class="container2_left"&gt;&lt;code&gt;<br>$index.html<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;pre class="container2_right"&gt;&lt;code class="mers-code-snippet"&gt;<br>$build_script<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;/section&gt;<br>&lt;/body&gt;<br><br></code></pre> &lt;!DOCTYPE html&gt;<br># This document will be processed by build.mers.<br># Lines starting with hashtags are comments and will be ignored.<br># Lines starting with dollar-signs insert special text.<br># To escape this, put a space before the hashtag or dollar sign.<br>&lt;head&gt;<br> &lt;meta charset=“UTF-8”&gt;<br> &lt;link rel="stylesheet" href="external.css"&gt;<br> &lt;title&gt;Mark :: mers&lt;/title&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br> &lt;h1&gt;Mers&lt;/h1&gt;<br> &lt;section class="container"&gt;<br> &lt;section class="container_left2 code-border"&gt;<br> &lt;pre&gt;&lt;code class="mers-code-snippet"&gt;<br>$welcome_script<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;/section&gt;<br> &lt;section class="container_right"&gt;<br> &lt;image<br> alt="some picture related to mers (todo)"<br> src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////<br> width="100%" height="100%"<br> &gt;<br> &lt;h3&gt;Mers types&lt;/h3&gt;<br> &lt;div&gt;<br> Mers uses a multiple-types system.<br> It keeps track of which types a variable could have<br> and constructs a type with that information.<br> &lt;br&gt;<br> For example, &lt;code&gt;int/float&lt;/code&gt; can represent a number - int or<br> Optional types can be &lt;code&gt;[]/[t]&lt;/code&gt; - either nothing or one v<br> Mers doesn't have null, it just has the empty tuple &lt;code&gt;[]&lt;/code&gt;<br> &lt;/div&gt;<br> &lt;h3&gt;No exceptions, no crashes&lt;/h3&gt;<br> &lt;div&gt;<br> Errors in mers are passed as values.<br> Because of the type system, you are forced to handle them explicitl<br> Mers will not crash in unexpected places, because the only way to c<br> it is by using one of the assume*() functions (similar to unwrap()s<br> &lt;/div&gt;<br> &lt;/section&gt;<br> &lt;/section&gt;<br> &lt;hr&gt;<br> &lt;h3&gt;HTML preprocessor to help build this document written in mers:&lt;/h3&gt;<br> &lt;section class="container"&gt;<br> &lt;pre class="container2_left"&gt;&lt;code&gt;<br>$index.html<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;pre class="container2_right"&gt;&lt;code class="mers-code-snippet"&gt;<br>$build_script<br> &lt;/code&gt;&lt;/pre&gt;<br> &lt;/section&gt;<br>&lt;/body&gt;<br><br></code></pre>
<pre class="container2_right"><code class="mers-code-snippet"> <pre class="container2_right"><code class="mers-code-snippet">
#!/usr/bin/env mers<br><br>// helper functions<br><br>fn read_string(path string) {<br> bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum()<br>}<br>fn code_to_html(code string code_width_limit_chars int) {<br> out = ""<br> for line code.regex(".*").assume_no_enum() {<br> if code_width_limit_chars.gtoe(0).and(line.len().gt(code_width_limit_chars)) {<br> line = line.substring(0 code_width_limit_chars)<br> }<br> line = line<br> .replace("&amp;" "&amp;amp;")<br> .replace("&lt;" "&amp;lt;")<br> .replace("&gt;" "&amp;gt;")<br> out = out.add(line.add("&lt;br&gt;"))<br> }<br> out<br>}<br><br>// data<br><br>index = read_string("index.html")<br><br>index_html = index.code_to_html(75)<br>build_script = read_string("build.mers").code_to_html(-1)<br>welcome_script = read_string("welcome.mers").code_to_html(-1)<br><br>// process index.html<br><br>out = ""<br>for line index.regex("\\S*.*").assume_no_enum() {<br> if line.starts_with("#") {<br> // comment, ignore<br> } else if line.starts_with("$") {<br> if line == "$welcome_script" {<br> out = out + welcome_script<br> } else if line == "$build_script" {<br> out = out + build_script<br> } else if line == "$index.html" {<br> out = out + index_html<br> }<br> } else {<br> // remove spaces<br> loop {<br> if line.starts_with(" ") {<br> line = line.substring(1)<br> } else {<br> true // break<br> }<br> }<br> out = out + line + "\n"<br> }<br>}<br>fs_write("../index.html" string_to_bytes(out)).assume_no_enum()<br><br></code></pre> #!/usr/bin/env mers<br><br>// helper functions<br><br>fn read_string(path string) {<br> bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum()<br>}<br>fn code_to_html(code string code_width_limit_chars int) {<br> out := ""<br> for line code.regex(".*").assume_no_enum() {<br> if code_width_limit_chars.gtoe(0).and(line.len().gt(code_width_limit_chars)) {<br> &amp;line = line.substring(0 code_width_limit_chars)<br> }<br> &amp;line = line<br> .replace("&amp;" "&amp;amp;")<br> .replace("&lt;" "&amp;lt;")<br> .replace("&gt;" "&amp;gt;")<br> &amp;out = out.add(line.add("&lt;br&gt;"))<br> }<br> out<br>}<br><br>// data<br><br>index := read_string("index.html")<br><br>index_html := index.code_to_html(75)<br>build_script := read_string("build.mers").code_to_html(-1)<br>welcome_script := read_string("welcome.mers").code_to_html(-1)<br><br>// process index.html<br><br>out := ""<br>for line index.regex("\\S*.*").assume_no_enum() {<br> if line.starts_with("#") {<br> // comment, ignore<br> } else if line.starts_with("$") {<br> if line == "$welcome_script" {<br> &amp;out = out + welcome_script<br> } else if line == "$build_script" {<br> &amp;out = out + build_script<br> } else if line == "$index.html" {<br> &amp;out = out + index_html<br> }<br> } else {<br> // remove spaces<br> loop {<br> if line.starts_with(" ") {<br> &amp;line = line.substring(1)<br> } else {<br> true // break<br> }<br> }<br> &amp;out = out + line + "\n"<br> }<br>}<br>fs_write("../index.html" string_to_bytes(out)).assume_no_enum()<br><br></code></pre>
</section> </section>
</body> </body>

View File

@ -1603,7 +1603,7 @@ impl BuiltinFunction {
let left = if left >= 0 { let left = if left >= 0 {
left as usize left as usize
} else { } else {
(a.len() - 1).saturating_sub(left.abs() as _) a.len().saturating_sub(left.abs() as _)
}; };
if let Some(len) = len { if let Some(len) = len {
if len >= 0 { if len >= 0 {

View File

@ -280,7 +280,8 @@ impl FormatGs for SStatement {
write!(f, " -> ")?; write!(f, " -> ")?;
force_opt.fmtgs(f, info, form, file)?; force_opt.fmtgs(f, info, form, file)?;
} }
self.statement.fmtgs(f, info, form, file) self.statement.fmtgs(f, info, form, file)?;
write!(f, ",")
} }
} }
impl Display for SStatement { impl Display for SStatement {

View File

@ -583,7 +583,7 @@ pub mod implementation {
let mut start = String::new(); let mut start = String::new();
loop { loop {
fn is_delimeter(ch: char) -> bool { fn is_delimeter(ch: char) -> bool {
matches!(ch, '}' | ']' | ')' | '.') matches!(ch, '}' | ']' | ')' | '.' | ',')
} }
let nchar = match file.peek() { let nchar = match file.peek() {
Some(ch) if is_delimeter(ch) => Some(ch), Some(ch) if is_delimeter(ch) => Some(ch),
@ -596,8 +596,9 @@ pub mod implementation {
parse_statement(file)?, parse_statement(file)?,
))); )));
} }
Some(ch) if ch.is_whitespace() || is_delimeter(ch) => { Some(ch)
if start.trim().is_empty() { if (ch.is_whitespace() || is_delimeter(ch)) && start.trim().is_empty() =>
{
return Err(ParseError { return Err(ParseError {
err: ParseErrors::StatementCannotStartWith(ch), err: ParseErrors::StatementCannotStartWith(ch),
location: *file.get_pos(), location: *file.get_pos(),
@ -606,6 +607,7 @@ pub mod implementation {
info: None, info: None,
}); });
} }
Some(ch) if ch.is_whitespace() || is_delimeter(ch) => {
file.skip_whitespaces(); file.skip_whitespaces();
// parse normal statement // parse normal statement
let start = start.trim(); let start = start.trim();
@ -853,6 +855,10 @@ pub mod implementation {
// 080 . // 080 .
// most local (evaluated first) // most local (evaluated first)
out = match (chain_level, file.peek()) { out = match (chain_level, file.peek()) {
(_, Some(',')) => {
file.next();
break;
}
// 080 . // 080 .
(0..=80, Some('.')) (0..=80, Some('.'))
if !matches!( if !matches!(

View File

@ -6,52 +6,52 @@ fn read_string(path string) {
bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum() bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum()
} }
fn code_to_html(code string code_width_limit_chars int) { fn code_to_html(code string code_width_limit_chars int) {
out = "" out := ""
for line code.regex(".*").assume_no_enum() { for line code.regex(".*").assume_no_enum() {
if code_width_limit_chars.gtoe(0).and(line.len().gt(code_width_limit_chars)) { if code_width_limit_chars.gtoe(0).and(line.len().gt(code_width_limit_chars)) {
line = line.substring(0 code_width_limit_chars) &line = line.substring(0 code_width_limit_chars)
} }
line = line &line = line
.replace("&" "&amp;") .replace("&" "&amp;")
.replace("<" "&lt;") .replace("<" "&lt;")
.replace(">" "&gt;") .replace(">" "&gt;")
out = out.add(line.add("<br>")) &out = out.add(line.add("<br>"))
} }
out out
} }
// data // data
index = read_string("index.html") index := read_string("index.html")
index_html = index.code_to_html(75) index_html := index.code_to_html(75)
build_script = read_string("build.mers").code_to_html(-1) build_script := read_string("build.mers").code_to_html(-1)
welcome_script = read_string("welcome.mers").code_to_html(-1) welcome_script := read_string("welcome.mers").code_to_html(-1)
// process index.html // process index.html
out = "" out := ""
for line index.regex("\\S*.*").assume_no_enum() { for line index.regex("\\S*.*").assume_no_enum() {
if line.starts_with("#") { if line.starts_with("#") {
// comment, ignore // comment, ignore
} else if line.starts_with("$") { } else if line.starts_with("$") {
if line == "$welcome_script" { if line == "$welcome_script" {
out = out + welcome_script &out = out + welcome_script
} else if line == "$build_script" { } else if line == "$build_script" {
out = out + build_script &out = out + build_script
} else if line == "$index.html" { } else if line == "$index.html" {
out = out + index_html &out = out + index_html
} }
} else { } else {
// remove spaces // remove spaces
loop { loop {
if line.starts_with(" ") { if line.starts_with(" ") {
line = line.substring(1) &line = line.substring(1)
} else { } else {
true // break true // break
} }
} }
out = out + line + "\n" &out = out + line + "\n"
} }
} }
fs_write("../index.html" string_to_bytes(out)).assume_no_enum() fs_write("../index.html" string_to_bytes(out)).assume_no_enum()

View File

@ -1,8 +1,8 @@
fn get_number_input(question string) { fn get_number_input(question string) {
println(question) println(question)
input = read_line() input := read_line()
// try to parse to an int, then a float. // try to parse to an int, then a float.
in = match input { in := match input {
input.parse_int() input input.parse_int() input
input.parse_float() input input.parse_float() input
} }
@ -15,7 +15,7 @@ fn get_number_input(question string) {
// return type is int/float/Err(string) // return type is int/float/Err(string)
} }
answer = get_number_input("What is your favorite number?") answer := get_number_input("What is your favorite number?")
answer.debug() // type: int/float/Err(string) answer.debug() // type: int/float/Err(string)
// switch can be used to branch based on a variables type. // switch can be used to branch based on a variables type.
@ -39,9 +39,9 @@ sleep(1)
// function that returns an anonymous function (function object). // function that returns an anonymous function (function object).
// anonymous functions can be used as iterators in for-loops. // anonymous functions can be used as iterators in for-loops.
fn square_numbers() { fn square_numbers() {
i = 0 i := 0
() { () {
i = i + 1 &i = i + 1
i * i i * i
} }
} }