This commit is contained in:
Dummi26 2023-04-25 15:15:21 +02:00
parent d66a0f37f2
commit 99f8c504a4
3 changed files with 236 additions and 0 deletions

View File

@ -0,0 +1,41 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some(
"
// Functions represent certain actions.
// They are given some inputs (arguments) and output (return) something.
// Mers comes with a range of builtin functions defined in src/script/builtins.rs.
// As an example, let's look at the add() function:
// It takes two arguments as its input and adds them together, then returns the sum:
add(5 10) // 15
// Similar to this, sub() subtracts two numbers:
sub(15 5) // 10
// For some functions, there is no value they could return:
sleep(0.01) // wait 0.01 seconds, then continue.
// These will return an empty tuple [] in mers.
// However, you aren't limited to the builtin functions.
// You can easily define your own functions to do more complex tasks:
fn say_hello_world() {
println(\"Hello, world!\")
}
// to return to the menu, add two arguments to the mul() function to make it return 32*5
mul()
",
));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::Int(160) => break,
other => {
tutor.set_status(format!(" - Returned {other} instead of 160"));
tutor.update(None);
}
}
}
}

View File

@ -0,0 +1,159 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some("
// Mers uses a type system to verify your programs,
// which prevents your program from crashing.
// Mers will verify that your program is valid and will not run into issues before it is executed.
// This way, errors are found when you write the program, not when you run it. If mers runs your program
// it is almost always safe to assume that it will not crash.
// for example, this will cause an error because you cannot subtract text from numbers.
// sub(15 \"some text\")
// mers can verify this type-safety in all programs, no matter how complicated they are:
// a = 15 // mers knows: a is an int
// b = \"some text\" // mers knows: b is a string
// sub(a b) // mers knows: it can't subtract a string from an int
// Traditional statically-typed languages achieve this same type-safety:
// C / C++ / Java / C#
// int a = 10;
// int b = 5;
// int c = a - b;
// In C#, we can just use 'var' to automatically infer the types
// var a = 10;
// var b = 5;
// var c = a - b; // all of these are ints, and C# knows this
// Not specifying a type for variables is the default in Rust...
// let a = 10;
// let b = 5;
// let c = a - b;
// ... and Go
// a := 10
// b := 5
// c := a - b
// Dynamically-typed languages don't need to know the type of their variables at all:
// JavaScript
// let a = 10
// let b = 5
// let c = a - b
// Also JavaScript (c becomes NaN in this example)
// let a = \"some text\"
// let b = 5
// let c = a - b
// However, there are some things dynamic typing can let us do that static typing can't:
// JavaScript
// let x
// if (condition()) {
// x = 10
// } else {
// x = \"some string\"
// }
// console.log(x) // we can't know if x is an int or a string, but it will work either way.
// We *could* implement this in Rust:
// enum StringOrInt {
// S(String),
// I(i32),
// }
// impl std::fmt::Display for StringOrInt {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// match self {
// Self::S(s) => write!(f, \"{}\", s),
// Self::I(i) => write!(f, \"{}\", i),
// }
// }
// }
// fn main() {
// let x;
// if condition() {
// x = StringOrInt::I(10)
// } else {
// x = StringOrInt::S(format!(\"some string\"));
// }
// println!(\"{}\", x);
// }
// While this works, it's a lot of effort. But don't worry, we can do better!
// Mers (it doesn't let you declare variables without initializing them, but that doesn't really matter for the example)
// x = if condition() {
// 10
// } else {
// \"some string\"
// }
// println(\"x = {0}\".format(x))
// Okay, but how can we keep the type-safety of statically typed languages if code like this is valid in mers?
// To figure this out, let's just ask mers for the type of x using 'switch! var {}'.
// (you can always reload and check the top of the file to see mers' full error)
a = \"this is clearly a string\"
switch! a {} // string
b = 10
switch! b {} // int
// Now comment out the two switch statements, reload the file and look at the error produced by 'switch! x {}'.
x = if true {
10
} else {
\"a string\"
}
switch! x {}
// Mers doesn't know if x is an int or a string, but it knows that it has to be either of those and can't be anything else.
// And this doesn't even rely on any fancy technology, it's all just based on simple and intuitive rules:
// x is the result of an if-else statement, meaning x's type is just the return types from the two branches combined: int and string -> int/string.
// if we remove the else, x's type would be int/[]: either an int or nothing.
// (don't forget to comment out the third switch statement, too. you can't return to the menu otherwise)
// By combining multiple-types with tuples, we can express complicated data structures without needing structs or enums:
// [int/float int/float]/int/float // one or two numbers
// [int/float ...] // a list of numbers
// Mers does have enums, but they are different from what other languages have.
// Enums are just identifiers that can be used to specify what a certain type is supposed to be used for.
// Consider a function read_file() that wants to return a the file's contents as a string.
// If the file doesn't exist or can't be read, the function should return some sort of error.
// Both of these can be the type string: \"this is my file\" and \"Couldn't read file: Permission denied.\".
// To avoid this ambiguity, we can wrap the error in an enum:
// \"this is my file\" and Err: \"Couldn't read file: Permission denied.\".
// Instead of the function just returning string, it now returns string/Err(string).
// This shows programmers that your function can fail and at the same time tells mers
// that the function returns two different types that both need to be handeled:
fn read_file() {
if false {
// successfully read the file
\"this is my file\"
} else {
Err: \"Couldn't read file: I didn't even try.\"
}
}
file = read_file()
// without switching, I can't get to the file's content:
println(file) // this causes an error!
// using switch! instead of switch forces me to handle all types, including the error path.
switch! file {
string {
println(\"File content: {0}\".format(file))
}
Err(string) {
println(\"Error! {0}\".format(file.noenum()))
}
}
// To return to the menu, fix all the errors in this file.
true
"));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::Tuple(v) if v.is_empty() => {
tutor.set_status(format!(" - Returned an empty tuple."));
tutor.update(None);
}
_ => break,
}
}
}

View File

@ -0,0 +1,36 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some(
"
// A variable can be used to store values.
// Create one by assigning a value to it:
my_first_variable = 15
// Then use it instead of literal values:
five_less = sub(my_first_variable 5) // 10
// to return to the menu, create a variable my_name and assign your name to it.
/* return the name so the tutor can check it - ignore this */ my_name
",
));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::String(name) if !name.is_empty() => {
tutor.i_name = Some(name);
break;
}
VDataEnum::String(_) => {
tutor.set_status(format!(" - Almost there, you made an empty string. Put your name between the quotes to continue!"));
tutor.update(None);
}
other => {
tutor.set_status(format!(" - Returned {other} instead of a string. String literals start and end with double quotes (\")."));
tutor.update(None);
}
}
}
}