diff --git a/README.md b/README.md index 9c9d2a4..9063153 100755 --- a/README.md +++ b/README.md @@ -32,4 +32,7 @@ Now, create a new text file (or choose one from the examples) and run it: `mers ## Docs [syntax cheat sheet](docs/syntax_cheat_sheet.md) + [intro](docs/intro.md) + +[builtins](docs/builtins.md) diff --git a/docs/builtins.md b/docs/builtins.md new file mode 100644 index 0000000..6b0b58e --- /dev/null +++ b/docs/builtins.md @@ -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,

-fn get_number_input(question string) {
println(question)
input = read_line()
// try to parse to an int, then a float.
in = match input {
input.parse_int() input
input.parse_float() input
}
// 'in' has type int/float/[] because of the match statement
switch! in {
int/float in
// replace [] with an appropriate error before returning
[] Err: "input was not a number."
}
// return type is int/float/Err(string)
}

answer = get_number_input("What is your favorite number?")

answer.debug() // type: int/float/Err(string)
// switch can be used to branch based on a variables type.
// switch! indicates that every possible type must be handled.
switch! answer {
int {
println("Entered an integer")
answer.debug() // type: int
}
float {
println("Entered a decimal number")
answer.debug() // type: float
}
Err(string) println("Input was not a number!")
}

// wait one second
sleep(1)


// function that returns an anonymous function (function object).
// anonymous functions can be used as iterators in for-loops.
fn square_numbers() {
i = 0
() {
i = i + 1
i * i
}
}

for num square_numbers() {
println(num.to_string())
// once num is greater than 60, the loop stops.
num.gt(50)
}
+fn get_number_input(question string) {
println(question)
input := read_line()
// try to parse to an int, then a float.
in := match input {
input.parse_int() input
input.parse_float() input
}
// 'in' has type int/float/[] because of the match statement
switch! in {
int/float in
// replace [] with an appropriate error before returning
[] Err: "input was not a number."
}
// return type is int/float/Err(string)
}

answer := get_number_input("What is your favorite number?")

answer.debug() // type: int/float/Err(string)
// switch can be used to branch based on a variables type.
// switch! indicates that every possible type must be handled.
switch! answer {
int {
println("Entered an integer")
answer.debug() // type: int
}
float {
println("Entered a decimal number")
answer.debug() // type: float
}
Err(string) println("Input was not a number!")
}

// wait one second
sleep(1)


// function that returns an anonymous function (function object).
// anonymous functions can be used as iterators in for-loops.
fn square_numbers() {
i := 0
() {
&i = i + 1
i * i
}
}

for num square_numbers() {
println(num.to_string())
// once num is greater than 60, the loop stops.
num.gt(50)
}
<!DOCTYPE html>
# This document will be processed by build.mers.
# Lines starting with hashtags are comments and will be ignored.
# Lines starting with dollar-signs insert special text.
# To escape this, put a space before the hashtag or dollar sign.
<head>
<meta charset=“UTF-8”>
<link rel="stylesheet" href="external.css">
<title>Mark :: mers</title>
</head>
<body>
<h1>Mers</h1>
<section class="container">
<section class="container_left2 code-border">
<pre><code class="mers-code-snippet">
$welcome_script
</code></pre>
</section>
<section class="container_right">
<image
alt="some picture related to mers (todo)"
src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////
width="100%" height="100%"
>
<h3>Mers types</h3>
<div>
Mers uses a multiple-types system.
It keeps track of which types a variable could have
and constructs a type with that information.
<br>
For example, <code>int/float</code> can represent a number - int or
Optional types can be <code>[]/[t]</code> - either nothing or one v
Mers doesn't have null, it just has the empty tuple <code>[]</code>
</div>
<h3>No exceptions, no crashes</h3>
<div>
Errors in mers are passed as values.
Because of the type system, you are forced to handle them explicitl
Mers will not crash in unexpected places, because the only way to c
it is by using one of the assume*() functions (similar to unwrap()s
</div>
</section>
</section>
<hr>
<h3>HTML preprocessor to help build this document written in mers:</h3>
<section class="container">
<pre class="container2_left"><code>
$index.html
</code></pre>
<pre class="container2_right"><code class="mers-code-snippet">
$build_script
</code></pre>
</section>
</body>


-#!/usr/bin/env mers

// helper functions

fn read_string(path string) {
bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum()
}
fn code_to_html(code string code_width_limit_chars int) {
out = ""
for line code.regex(".*").assume_no_enum() {
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
.replace("&" "&amp;")
.replace("<" "&lt;")
.replace(">" "&gt;")
out = out.add(line.add("<br>"))
}
out
}

// data

index = read_string("index.html")

index_html = index.code_to_html(75)
build_script = read_string("build.mers").code_to_html(-1)
welcome_script = read_string("welcome.mers").code_to_html(-1)

// process index.html

out = ""
for line index.regex("\\S*.*").assume_no_enum() {
if line.starts_with("#") {
// comment, ignore
} else if line.starts_with("$") {
if line == "$welcome_script" {
out = out + welcome_script
} else if line == "$build_script" {
out = out + build_script
} else if line == "$index.html" {
out = out + index_html
}
} else {
// remove spaces
loop {
if line.starts_with(" ") {
line = line.substring(1)
} else {
true // break
}
}
out = out + line + "\n"
}
}
fs_write("../index.html" string_to_bytes(out)).assume_no_enum()

+#!/usr/bin/env mers

// helper functions

fn read_string(path string) {
bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum()
}
fn code_to_html(code string code_width_limit_chars int) {
out := ""
for line code.regex(".*").assume_no_enum() {
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
.replace("&" "&amp;")
.replace("<" "&lt;")
.replace(">" "&gt;")
&out = out.add(line.add("<br>"))
}
out
}

// data

index := read_string("index.html")

index_html := index.code_to_html(75)
build_script := read_string("build.mers").code_to_html(-1)
welcome_script := read_string("welcome.mers").code_to_html(-1)

// process index.html

out := ""
for line index.regex("\\S*.*").assume_no_enum() {
if line.starts_with("#") {
// comment, ignore
} else if line.starts_with("$") {
if line == "$welcome_script" {
&out = out + welcome_script
} else if line == "$build_script" {
&out = out + build_script
} else if line == "$index.html" {
&out = out + index_html
}
} else {
// remove spaces
loop {
if line.starts_with(" ") {
&line = line.substring(1)
} else {
true // break
}
}
&out = out + line + "\n"
}
}
fs_write("../index.html" string_to_bytes(out)).assume_no_enum()

diff --git a/mers/src/lang/builtins.rs b/mers/src/lang/builtins.rs index ae9fdd5..9392405 100755 --- a/mers/src/lang/builtins.rs +++ b/mers/src/lang/builtins.rs @@ -1603,7 +1603,7 @@ impl BuiltinFunction { let left = if left >= 0 { left as usize } else { - (a.len() - 1).saturating_sub(left.abs() as _) + a.len().saturating_sub(left.abs() as _) }; if let Some(len) = len { if len >= 0 { diff --git a/mers/src/lang/code_parsed.rs b/mers/src/lang/code_parsed.rs index b74ae0d..2b2cd4a 100755 --- a/mers/src/lang/code_parsed.rs +++ b/mers/src/lang/code_parsed.rs @@ -280,7 +280,8 @@ impl FormatGs for SStatement { write!(f, " -> ")?; 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 { diff --git a/mers/src/parsing/parse.rs b/mers/src/parsing/parse.rs index a574668..7284861 100755 --- a/mers/src/parsing/parse.rs +++ b/mers/src/parsing/parse.rs @@ -583,7 +583,7 @@ pub mod implementation { let mut start = String::new(); loop { fn is_delimeter(ch: char) -> bool { - matches!(ch, '}' | ']' | ')' | '.') + matches!(ch, '}' | ']' | ')' | '.' | ',') } let nchar = match file.peek() { Some(ch) if is_delimeter(ch) => Some(ch), @@ -596,16 +596,18 @@ pub mod implementation { parse_statement(file)?, ))); } + Some(ch) + if (ch.is_whitespace() || is_delimeter(ch)) && start.trim().is_empty() => + { + return Err(ParseError { + err: ParseErrors::StatementCannotStartWith(ch), + location: *file.get_pos(), + location_end: None, + context: vec![], + info: None, + }); + } Some(ch) if ch.is_whitespace() || is_delimeter(ch) => { - if start.trim().is_empty() { - return Err(ParseError { - err: ParseErrors::StatementCannotStartWith(ch), - location: *file.get_pos(), - location_end: None, - context: vec![], - info: None, - }); - } file.skip_whitespaces(); // parse normal statement let start = start.trim(); @@ -853,6 +855,10 @@ pub mod implementation { // 080 . // most local (evaluated first) out = match (chain_level, file.peek()) { + (_, Some(',')) => { + file.next(); + break; + } // 080 . (0..=80, Some('.')) if !matches!( diff --git a/site/build.mers b/site/build.mers index 458db5c..5cae040 100755 --- a/site/build.mers +++ b/site/build.mers @@ -6,52 +6,52 @@ fn read_string(path string) { bytes_to_string(fs_read(path).assume_no_enum()).assume_no_enum() } fn code_to_html(code string code_width_limit_chars int) { - out = "" + out := "" for line code.regex(".*").assume_no_enum() { 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("&" "&") .replace("<" "<") .replace(">" ">") - out = out.add(line.add("
")) + &out = out.add(line.add("
")) } out } // data -index = read_string("index.html") +index := read_string("index.html") -index_html = index.code_to_html(75) -build_script = read_string("build.mers").code_to_html(-1) -welcome_script = read_string("welcome.mers").code_to_html(-1) +index_html := index.code_to_html(75) +build_script := read_string("build.mers").code_to_html(-1) +welcome_script := read_string("welcome.mers").code_to_html(-1) // process index.html -out = "" +out := "" for line index.regex("\\S*.*").assume_no_enum() { if line.starts_with("#") { // comment, ignore } else if line.starts_with("$") { if line == "$welcome_script" { - out = out + welcome_script + &out = out + welcome_script } else if line == "$build_script" { - out = out + build_script + &out = out + build_script } else if line == "$index.html" { - out = out + index_html + &out = out + index_html } } else { // remove spaces loop { if line.starts_with(" ") { - line = line.substring(1) + &line = line.substring(1) } else { true // break } } - out = out + line + "\n" + &out = out + line + "\n" } } fs_write("../index.html" string_to_bytes(out)).assume_no_enum() diff --git a/site/welcome.mers b/site/welcome.mers index 3a282a2..bbceae4 100644 --- a/site/welcome.mers +++ b/site/welcome.mers @@ -1,8 +1,8 @@ fn get_number_input(question string) { println(question) - input = read_line() + input := read_line() // try to parse to an int, then a float. - in = match input { + in := match input { input.parse_int() input input.parse_float() input } @@ -15,7 +15,7 @@ fn get_number_input(question 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) // 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). // anonymous functions can be used as iterators in for-loops. fn square_numbers() { - i = 0 + i := 0 () { - i = i + 1 + &i = i + 1 i * i } }