changed "var type = value" syntax to "var::type = value" and added the tutor (mers -t)

This commit is contained in:
Dummi26 2023-04-25 02:55:16 +02:00
parent 7baa1c2aba
commit ca1dbf2722
16 changed files with 561 additions and 216 deletions

View File

@ -44,6 +44,10 @@ 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")`. Alternatively, run `mers -e println("Hello, file-less world")`.
If you compiled mers in debug mode, it will print a lot of debugging information. If you compiled mers in debug mode, it will print a lot of debugging information.
### tutor
Use `mers -t` to start the tutor, which will give you an interactive tour of the language.
### interactive mode ### 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. 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.

View File

@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib] [lib]
name = "mers" name = "mers_libs"
path = "src/main.rs" path = "src/lib.rs"
[dependencies] [dependencies]
edit = "0.1.4" edit = "0.1.4"

View File

@ -0,0 +1,130 @@
/// creates a temporary file, then opens it in the user's default editor. watches the fs for changes to the file and acts upon them.
pub mod fs_watcher {
use notify::Watcher;
use std::{
fs,
path::PathBuf,
thread::{self, JoinHandle},
};
#[derive(Debug)]
pub struct Error(String);
/// on each file change, recompiles and runs the code. lets people experiment with mers without having to put a file anywhere
pub fn playground(spawn_new_terminal_for_editor: bool) -> Result<(), Error> {
main(
spawn_new_terminal_for_editor,
"// Welcome to mers! (interactive mode)
// put your name here, then save the file to run the script.
your_name = \"\"
greeting = \"Hello, {0}!\".format(your_name)
println(greeting)
",
Box::new(|temp_file: &PathBuf| {
println!();
if let Ok(file_contents) = fs::read_to_string(&temp_file) {
let mut file =
crate::parse::file::File::new(file_contents, temp_file.to_path_buf());
match crate::parse::parse::parse(&mut file) {
Ok(func) => {
println!(" - - - - -");
let output = func.run(vec![]);
println!(" - - - - -");
println!("{}", output);
}
Err(e) => println!("{}", e.with_file(&file)),
}
} else {
println!("can't read file at {:?}!", temp_file);
std::process::exit(105);
}
}),
)?
.0
.join()
.unwrap();
Ok(())
}
/// when the file is changed, calls the provided closure with the file's path.
/// returns a JoinHandle which will finish when the user closes the editor and the file that is being used.
pub fn main(
spawn_new_terminal_for_editor: bool,
initial_file_contents: &str,
mut on_file_change: Box<dyn FnMut(&PathBuf) + Send>,
) -> Result<(JoinHandle<()>, PathBuf), Error> {
let temp_file_edit = edit::Builder::new().suffix(".mers").tempfile().unwrap();
let temp_file = temp_file_edit.path().to_path_buf();
eprintln!(
"Using temporary file at {temp_file:?}. Save the file to update the output here."
);
if let Ok(_) = std::fs::write(&temp_file, initial_file_contents) {
if let Ok(mut watcher) = {
let temp_file = temp_file.clone();
// 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(_)) => {
on_file_change(&temp_file);
}
_ => (),
}
}
})
} {
if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) {
let out = if spawn_new_terminal_for_editor {
if let Ok(term) = std::env::var("TERM") {
let editor = edit::get_editor().unwrap();
eprintln!("launching \"{term} -e {editor:?} {temp_file:?}...");
let mut editor = std::process::Command::new(term)
.arg("-e")
.arg(&editor)
.arg(&temp_file)
.spawn()
.unwrap();
(
thread::spawn(move || {
// wait for the editor to finish
editor.wait().unwrap();
// stop the watcher (this is absolutely necessary because it also moves the watcher into the closure,
// which prevents it from being dropped too early)
drop(watcher);
// close and remove the temporary file
temp_file_edit.close().unwrap();
}),
temp_file,
)
} else {
return Err(Error(format!("TERM environment variable not set.")));
}
} else {
let tf = temp_file.clone();
(
thread::spawn(move || {
edit::edit_file(temp_file).unwrap();
drop(watcher);
temp_file_edit.close().unwrap();
}),
tf,
)
};
Ok(out)
} else {
return Err(Error(format!(
"Cannot watch the file at \"{:?}\" for hot-reload.",
temp_file
)));
}
} else {
return Err(Error(format!(
"Cannot use filesystem watcher for hot-reload."
)));
}
} else {
return Err(Error(format!("could not write file \"{:?}\".", temp_file)));
}
}
}

10
mers/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#![allow(unused)]
#![allow(dead_code)]
mod libs;
mod parse;
mod script;
pub use libs::inlib::*;
pub use parse::*;
pub use script::{val_data::*, val_type::*};

View File

@ -1,13 +1,16 @@
#![allow(unused)]
#![allow(dead_code)]
use std::{fs, time::Instant}; use std::{fs, time::Instant};
use notify::Watcher as FsWatcher; use notify::Watcher as FsWatcher;
pub mod libs; mod interactive_mode;
pub mod parse; mod libs;
pub mod script; mod parse;
mod script;
mod tutor;
// necessary because the lib target in Cargo.toml also points here. TODO: update Cargo.toml to have a lib target that is separate from the bin one (=> doesn't point to main)
#[allow(unused)]
fn main() { fn main() {
let args: Vec<_> = std::env::args().skip(1).collect(); let args: Vec<_> = std::env::args().skip(1).collect();
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -27,9 +30,11 @@ fn main() {
_ => { _ => {
if args[0].trim_start().starts_with("-") { if args[0].trim_start().starts_with("-") {
let mut execute = false; let mut execute = false;
let mut print_version = false;
let mut verbose = 0; let mut verbose = 0;
let mut interactive = 0; let mut interactive = 0;
let mut interactive_use_new_terminal = false; let mut interactive_use_new_terminal = false;
let mut teachme = false;
let mut prev_char = None; let mut prev_char = None;
let mut advanced = false; let mut advanced = false;
for ch in args[0][1..].chars() { for ch in args[0][1..].chars() {
@ -41,7 +46,9 @@ fn main() {
match ch { match ch {
'e' => execute = true, 'e' => execute = true,
'v' => verbose += 1, 'v' => verbose += 1,
'V' => print_version = true,
'i' => interactive += 1, 'i' => interactive += 1,
't' => teachme = true,
ch => { ch => {
eprintln!("Ignoring -{ch}. (unknown char)"); eprintln!("Ignoring -{ch}. (unknown char)");
continue; continue;
@ -65,103 +72,29 @@ fn main() {
advanced = false; advanced = false;
} }
} }
if print_version {
println!(
"mers {}",
option_env!("CARGO_PKG_VERSION")
.unwrap_or("[[ version unknown: no CARGO_PKG_VERSION ]]")
);
return;
}
if teachme {
tutor::start(false);
return;
}
if verbose != 0 { if verbose != 0 {
eprintln!("info: set verbosity level to {verbose}. this doesn't do anything yet. [TODO!]"); eprintln!("info: set verbosity level to {verbose}. this doesn't do anything yet. [TODO!]");
} }
if interactive >= 0 { if interactive >= 0 {
let (contents, path) = match interactive { match interactive {
1 => { _ => {
// basic: open file and watch for fs changes // basic: open file and watch for fs changes
let temp_file_edit = interactive_mode::fs_watcher::playground(interactive_use_new_terminal)
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(_),
) => {
println!();
if let Ok(file_contents) =
fs::read_to_string(&temp_file)
{
let mut file = parse::file::File::new(
file_contents,
temp_file.clone(),
);
match parse::parse::parse(&mut file) {
Ok(func) => {
println!(" - - - - -");
let output = func.run(vec![]);
println!(" - - - - -");
println!("{}", output);
} }
Err(e) => println!(
"{}",
e.with_file(&file)
),
}
} 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()) return;
} else if execute { } else if execute {
parse::file::File::new( parse::file::File::new(
args.iter().skip(1).fold(String::new(), |mut s, v| { args.iter().skip(1).fold(String::new(), |mut s, v| {

View File

@ -455,7 +455,7 @@ fn parse_statement_adv(
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),
@ -463,6 +463,40 @@ fn parse_statement_adv(
}; };
match nchar { match nchar {
Some(':') => { Some(':') => {
if let Some(':') = file.peek() {
_ = file.next();
let file_pos_before_pot_type = *file.get_pos();
let parsed_type = parse_type(file);
file.skip_whitespaces();
if let Some('=') = file.next() {
let err_equals_sign = *file.get_pos();
let start = start.trim();
let derefs = start.chars().take_while(|c| *c == '*').count();
match parse_statement(file) {
Ok(v) => break v
.output_to(start[derefs..].to_owned(), derefs)
.force_output_type(Some(match parsed_type {
Ok(v) => v,
Err(mut e) => {
e.context.push((
format!("interpreted this as an assignment to a variable with the format <var>::<var_type> = <statement>"), Some((err_start_of_statement, Some(err_equals_sign)))
));
return Err(e);
}
})),
Err(mut e) => {
e.context.push((
format!(
"statement was supposed to be assigned to variable {start}"
),
Some((err_start_of_statement, Some(err_equals_sign))),
));
return Err(e);
}
}
}
file.set_pos(file_pos_before_pot_type);
}
return Ok(SStatement::new(SStatementEnum::EnumVariant( return Ok(SStatement::new(SStatementEnum::EnumVariant(
start, start,
parse_statement(file)?, parse_statement(file)?,
@ -484,8 +518,8 @@ fn parse_statement_adv(
let err_equals_sign = *file.get_pos(); let err_equals_sign = *file.get_pos();
let start = start.trim(); let start = start.trim();
let derefs = start.chars().take_while(|c| *c == '*').count(); let derefs = start.chars().take_while(|c| *c == '*').count();
break match parse_statement(file) { match parse_statement(file) {
Ok(v) => v, Ok(v) => break v.output_to(start[derefs..].to_owned(), derefs),
Err(mut e) => { Err(mut e) => {
e.context.push(( e.context.push((
format!( format!(
@ -495,43 +529,8 @@ fn parse_statement_adv(
)); ));
return Err(e); return Err(e);
} }
};
} }
.output_to(start[derefs..].to_owned(), derefs);
}
// var type = statement
let file_pos_before_pot_type = *file.get_pos();
let parsed_type = parse_type(file);
file.skip_whitespaces();
if let Some('=') = file.peek() {
file.next();
let err_equals_sign = *file.get_pos();
let start = start.trim();
let derefs = start.chars().take_while(|c| *c == '*').count();
break match parse_statement(file) {
Ok(v) => v,
Err(mut e) => {
e.context.push((
format!(
"statement was supposed to be assigned to variable {start}"
),
Some((err_start_of_statement, Some(err_equals_sign))),
));
return Err(e);
}
}
.output_to(start[derefs..].to_owned(), derefs)
.force_output_type(Some(match parsed_type {
Ok(v) => v,
Err(mut e) => {
e.context.push((
format!("interpreted this as an assignment to a variable with the format <var> <var_type> = <statement>"), Some((err_start_of_statement, Some(err_equals_sign)))
));
return Err(e);
}
}));
}
// nevermind, not var type = statement
file.set_pos(file_pos_before_pot_type);
// parse normal statement // parse normal statement
let start = start.trim(); let start = start.trim();
match start { match start {

View File

@ -1,5 +1,6 @@
// A code block is any section of code. It contains its own local variables and functions, as well as a list of statements. // A code block is any section of code. It contains its own local variables and functions, as well as a list of statements.
// Types starting with S are directly parsed from Strings and unchecked. Types starting with T are type-checked templates for R-types. Types starting with R are runnable. S are converted to T after parsing is done, and T are converted to R whenever they need to run. // Types starting with S are directly parsed from Strings and unchecked. Types starting with T are type-checked templates for R-types. Types starting with R are runnable. S are converted to T after parsing is done, and T are converted to R whenever they need to run.
// There currently are no T-Types, but we might need them in the future.
use std::{ use std::{
fmt::Display, fmt::Display,
@ -133,7 +134,11 @@ pub mod to_runnable {
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>), WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
WrongArgsForLibFunction(String, Vec<VType>), WrongArgsForLibFunction(String, Vec<VType>),
ForLoopContainerHasNoInnerTypes, ForLoopContainerHasNoInnerTypes,
StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType), StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(
VType,
VType,
VType,
),
} }
impl Debug for ToRunnableError { impl Debug for ToRunnableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -658,7 +663,7 @@ pub mod to_runnable {
if problematic_types.is_empty() { if problematic_types.is_empty() {
statement.force_output_type = Some(force_opt.clone()); statement.force_output_type = Some(force_opt.clone());
} else { } else {
return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types })) return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types }));
} }
} }
if let Some((opt, derefs)) = &s.output_to { if let Some((opt, derefs)) = &s.output_to {
@ -669,7 +674,11 @@ pub mod to_runnable {
var_derefd = if let Some(v) = var_derefd.dereference() { var_derefd = if let Some(v) = var_derefd.dereference() {
v v
} else { } else {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(var_out.clone(), *derefs, var_derefd)); return Err(ToRunnableError::CannotDereferenceTypeNTimes(
var_out.clone(),
*derefs,
var_derefd,
));
} }
} }
let inv_types = out.fits_in(&var_derefd); let inv_types = out.fits_in(&var_derefd);
@ -693,16 +702,19 @@ pub mod to_runnable {
out = if let Some(v) = out.dereference() { out = if let Some(v) = out.dereference() {
v v
} else { } else {
return Err(ToRunnableError::CannotDereferenceTypeNTimes(statement.out(), *derefs, out)); return Err(ToRunnableError::CannotDereferenceTypeNTimes(
statement.out(),
*derefs,
out,
));
} }
} }
linfo linfo.vars.insert(opt.clone(), (ginfo.vars, out));
.vars
.insert(opt.clone(), (ginfo.vars, out));
statement.output_to = Some((ginfo.vars, *derefs)); statement.output_to = Some((ginfo.vars, *derefs));
ginfo.vars += 1; ginfo.vars += 1;
} }
} Ok(statement) }
Ok(statement)
} }
} }

View File

@ -8,7 +8,7 @@ use crate::libs;
use super::{ use super::{
block::RStatement, block::RStatement,
val_data::{VData, VDataEnum, VDataThreadEnum}, val_data::{thread::VDataThreadEnum, VData, VDataEnum},
val_type::{VSingleType, VType}, val_type::{VSingleType, VType},
}; };
@ -236,10 +236,21 @@ impl BuiltinFunction {
Self::Run | Self::Thread => { Self::Run | Self::Thread => {
if input.len() >= 1 { if input.len() >= 1 {
input[0].types.iter().all(|v| { input[0].types.iter().all(|v| {
// all possible types of the input function must be function types
if let VSingleType::Function(v) = v { if let VSingleType::Function(v) = v {
if v.iter().any(|(i, _)| i.len() == input.len() - 1) { // and all those functions must take as many inputs as were supplied to run() or thread() minus one (the function itself).
if v.iter()
.all(|(fn_in, _fn_out)| fn_in.len() == input.len() - 1)
{
eprintln!("Warn: Function inputs aren't type checked yet!)"); eprintln!("Warn: Function inputs aren't type checked yet!)");
// TODO! // all functions have the correct length, now check their types:
// this is more difficult than it seems, because if a function covers all input types on the first and second argument, that doesn't necessarily mean that it covers all possible cases:
// say out function is of type fn((int string []) (string int) []).
// this covers int/string for the first two arguments, but the function actually can't handle two ints or two strings as arguments, it requires exactly one int and one string.
// the most obvious implementation here would be a recursive function that goes over each type in the first argument, then calls itself recursively to check the second element and so on,
// but this would likely become slower than it should for complex functions.
// because of this, we just trust the programmer not to provide wrong arguments to run() and thread() for now,
// until a better solution is found.
true true
} else { } else {
false false

View File

@ -1,8 +1,6 @@
use std::{ use std::{
fmt::Debug, fmt::Debug,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread::JoinHandle,
time::Duration,
}; };
use super::{ use super::{
@ -25,7 +23,7 @@ pub enum VDataEnum {
Tuple(Vec<VData>), Tuple(Vec<VData>),
List(VType, Vec<VData>), List(VType, Vec<VData>),
Function(RFunction), Function(RFunction),
Thread(VDataThread, VType), Thread(thread::VDataThread, VType),
Reference(Arc<Mutex<VData>>), Reference(Arc<Mutex<VData>>),
EnumVariant(usize, Box<VData>), EnumVariant(usize, Box<VData>),
} }
@ -157,9 +155,19 @@ impl VType {
} }
} }
#[derive(Clone)] pub mod thread {
pub struct VDataThread(Arc<Mutex<VDataThreadEnum>>); use std::{
impl VDataThread { fmt::Debug,
sync::{Arc, Mutex},
thread::JoinHandle,
time::Duration,
};
use super::{VData, VDataEnum};
#[derive(Clone)]
pub struct VDataThread(Arc<Mutex<VDataThreadEnum>>);
impl VDataThread {
pub fn try_get(&self) -> Option<VData> { pub fn try_get(&self) -> Option<VData> {
match &*self.lock() { match &*self.lock() {
VDataThreadEnum::Running(_) => None, VDataThreadEnum::Running(_) => None,
@ -200,21 +208,22 @@ impl VDataThread {
} }
mg mg
} }
} }
impl Debug for VDataThread { impl Debug for VDataThread {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &*self.lock() { match &*self.lock() {
VDataThreadEnum::Running(_) => write!(f, "(thread running)"), VDataThreadEnum::Running(_) => write!(f, "(thread running)"),
VDataThreadEnum::Finished(v) => write!(f, "(thread finished: {v})"), VDataThreadEnum::Finished(v) => write!(f, "(thread finished: {v})"),
} }
} }
} }
pub enum VDataThreadEnum { pub enum VDataThreadEnum {
Running(JoinHandle<VData>), Running(JoinHandle<VData>),
Finished(VData), Finished(VData),
} }
impl VDataThreadEnum { impl VDataThreadEnum {
pub fn to(self) -> VDataThread { pub fn to(self) -> VDataThread {
VDataThread(Arc::new(Mutex::new(self))) VDataThread(Arc::new(Mutex::new(self)))
} }
}
} }

View File

@ -0,0 +1,25 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some(
"
// Comments in mers start at // and end at the end of the line.
// They also work within strings, which can be unexpected in some cases (like \"http://www...\").
/* also works to start a comment.
This comment can even span multiple lines! */
// To return to the menu, uncomment the next line:
// true
",
));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::Bool(true) => break,
other => {
tutor.set_status(format!(" - Returned {} instead of true.", other));
tutor.update(None);
}
}
}
}

View File

@ -0,0 +1,38 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some(
"
// Mers doesn't have a return statement.
// Instead, the value of the last statement is implicitly returned.
// This applies to blocks:
b = {
a = 10
a = a.add(15)
a
}
// b = 25
// To functions:
fn compute_sum(a int b int) {
a.add(b)
}
// returns a+b
// and to the program itself!
// to return to the menu, make the program return 15.
",
));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::Int(15) => break,
other => {
tutor.set_status(format!(" - Returned {} instead of 15.", other));
tutor.update(None);
}
}
}
}

View File

@ -0,0 +1,35 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub fn run(tutor: &mut Tutor) {
tutor.update(Some("
// Mers has the following values:
// bool: Either true or false
// int: An integer. Written 0, 1, 2, 3, -5, and so on
// float: A floating point number. Written 0.5, -12.378, and so on
// string: A piece of text. Surround something with \" and it will be a string: \"Hello, world!\"
// tuples: Multiple values in one place. A fixed-length collection. Surround types or statements with [] to create a tuple: [12 \"a tuple of ints and a string\" -5 -12]
// The empty tuple [] is often used to indicate nothing, while a 1-long tuple [v] indicates the opposite - something.
// list: Similar to tuples, but the closing ] is prefixed with 3 dots: [ ...]
// Unlike tuples, all elements in a list have the same type. Lists are resizable and can grow dynamically, while tuples cannot change their size after being created.
// function: A piece of code in data-form.
// value: anonymous_sum_function = (a int/float b int/float) a.add(b)
// type: fn((int int int)(int float float)(float int float)(float float float))
// the reason why the type syntax is so expressive is because the function doesn't return the same type for any inputs - add will return an int if it added two ints, but will return a float when at least one argument was a float.
// add will NOT return int/float, because if you know the exact input types, you also know the output type: either int and not float or float and not int.
// thread: Represents a different thread. The thread's return value can be retrieved by using .await(). Thread values are returned by the builtin thread() function.
// reference: A mutable reference to some data. Used by things like push() and remove() to avoid having to clone the entire list just to make a small change.
// enums: An enum can wrap any type. Enums are identified by their names and can be created using EnumName: inner_value. The type is written EnumName(InnerType).
// return a value of type GoBackToMenu([int]) to return to the menu.
"));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::EnumVariant(..) => break,
other => {
tutor.set_status(format!(" - Returned {other} instead of an enum."));
tutor.update(None);
}
}
}
}

39
mers/src/tutor/menu.rs Normal file
View File

@ -0,0 +1,39 @@
use crate::script::val_data::VDataEnum;
use super::Tutor;
pub const MAX_POS: usize = 3;
pub fn run(mut tutor: Tutor) {
loop {
tutor.current_pos = 0;
tutor.update(Some(
"
// Welcome to the mers tutor!
// This is the main menu. Change the number to navigate to a specific part.
0
// 1 Comments
// 2 Values
// 3 Returns
",
));
loop {
match tutor.let_user_make_change().run(vec![]).data {
VDataEnum::Int(pos) => {
tutor.current_pos = (pos.max(0) as usize).min(MAX_POS);
match tutor.current_pos {
0 => continue,
1 => super::base_comments::run(&mut tutor),
2 => super::base_values::run(&mut tutor),
3 => super::base_return::run(&mut tutor),
_ => unreachable!(),
}
}
other => {
tutor.set_status(format!(" - Returned {} instead of an integer", other));
}
}
break;
}
}
}

112
mers/src/tutor/mod.rs Normal file
View File

@ -0,0 +1,112 @@
use std::{path::PathBuf, thread::JoinHandle, time::Instant};
use crate::{
parse::{self, parse::ScriptError},
script::{block::RScript, val_data::VDataEnum},
};
mod base_comments;
mod base_return;
mod base_values;
mod menu;
pub fn start(spawn_new_terminal_for_editor: bool) {
let (sender, receiver) = std::sync::mpsc::channel();
let (editor_join_handle, file_path) = crate::interactive_mode::fs_watcher::main(
spawn_new_terminal_for_editor,
"// Welcome to the mers tutor!
// This is an interactive experience. After making a change to this file,
// save and then reload it to see the tutor's updates.
// To begin, change the following value from false to true:
false
",
Box::new(move |file| {
let mut file =
parse::file::File::new(std::fs::read_to_string(file).unwrap(), PathBuf::new());
sender.send(parse::parse::parse(&mut file)).unwrap();
}),
)
.unwrap();
let mut tutor = Tutor {
current_pos: 0,
current_status: String::new(),
written_status_byte_len: 0,
editor_join_handle,
file_path,
receiver,
};
loop {
if let VDataEnum::Bool(true) = tutor.let_user_make_change().run(vec![]).data {
break;
}
}
menu::run(tutor);
}
use menu::MAX_POS;
pub struct Tutor {
current_pos: usize,
current_status: String,
written_status_byte_len: usize,
editor_join_handle: JoinHandle<()>,
file_path: PathBuf,
receiver: std::sync::mpsc::Receiver<Result<RScript, ScriptError>>,
}
impl Tutor {
/// only returns after a successful compile. before returning, does not call self.update() - you have to do that manually.
pub fn let_user_make_change(&mut self) -> RScript {
// eprintln!(" - - - - - - - - - - - - - - - - - - - - - - - - -");
let script = loop {
match self.receiver.recv().unwrap() {
Err(e) => {
self.current_status = format!(
" - Error during build{}",
e.to_string()
.lines()
.map(|v| format!("\n// {v}"))
.collect::<String>()
)
}
Ok(script) => {
break script;
}
}
self.update(None);
};
self.current_status = format!(" - OK");
script
}
pub fn set_status(&mut self, new_status: String) {
self.current_status = new_status;
}
pub fn update(&mut self, overwrite_contents_with: Option<&str>) {
if self.editor_join_handle.is_finished() {
eprintln!("Error has closed!");
std::process::exit(0);
}
let string = std::fs::read_to_string(self.file_path()).unwrap();
let status = format!(
"// Tutor: {}/{MAX_POS}{}\n",
self.current_pos, self.current_status,
);
let status_len = status.len();
std::fs::write(
self.file_path(),
if let Some(new_content) = overwrite_contents_with {
status + new_content
} else {
status + &string[self.written_status_byte_len..]
},
)
.unwrap();
self.written_status_byte_len = status_len;
// ignore this update to the file
_ = self.receiver.recv().unwrap();
}
pub fn file_path(&self) -> &PathBuf {
&self.file_path
}
}

View File

@ -9,13 +9,8 @@ use iced::{
widget::{button, column, row, text}, widget::{button, column, row, text},
Application, Command, Element, Renderer, Settings, Subscription, Theme, Application, Command, Element, Renderer, Settings, Subscription, Theme,
}; };
use mers::{
libs::inlib::{MyLib, MyLibTask}, use mers_libs::{MyLib, MyLibTask, VData, VDataEnum, VSingleType, VType};
script::{
val_data::{VData, VDataEnum},
val_type::{VSingleType, VType},
},
};
/* /*
@ -264,7 +259,6 @@ impl Application for App {
format!("{}", self.title) format!("{}", self.title)
} }
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
let mut commands = vec![];
match message { match message {
Message::Tick => { Message::Tick => {
let mut changed_layout = false; let mut changed_layout = false;
@ -303,7 +297,7 @@ impl Application for App {
)) ))
.unwrap(), .unwrap(),
} }
Command::batch(commands) Command::none()
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
time::every(Duration::from_millis(10)).map(|_| Message::Tick) time::every(Duration::from_millis(10)).map(|_| Message::Tick)

View File

@ -1,10 +1,4 @@
use mers::{ use mers_libs::{MyLib, MyLibTask, VDataEnum, VSingleType, VType};
libs::inlib::{MyLib, MyLibTask},
script::{
val_data::VDataEnum,
val_type::{VSingleType, VType},
},
};
fn main() { fn main() {
let (mut my_lib, mut run) = MyLib::new( let (mut my_lib, mut run) = MyLib::new(