From ca1dbf272262a73625d1b508ba3e1007b37b6fe2 Mon Sep 17 00:00:00 2001 From: Dummi26 Date: Tue, 25 Apr 2023 02:55:16 +0200 Subject: [PATCH] changed "var type = value" syntax to "var::type = value" and added the tutor (mers -t) --- README.md | 4 + mers/Cargo.toml | 4 +- mers/src/interactive_mode.rs | 130 +++++++++++++++++++++++++ mers/src/lib.rs | 10 ++ mers/src/main.rs | 123 ++++++----------------- mers/src/parse/parse.rs | 77 ++++++++------- mers/src/script/block.rs | 30 ++++-- mers/src/script/builtins.rs | 17 +++- mers/src/script/val_data.rs | 113 +++++++++++---------- mers/src/tutor/base_comments.rs | 25 +++++ mers/src/tutor/base_return.rs | 38 ++++++++ mers/src/tutor/base_values.rs | 35 +++++++ mers/src/tutor/menu.rs | 39 ++++++++ mers/src/tutor/mod.rs | 112 +++++++++++++++++++++ mers_libs/gui_v1/src/main.rs | 12 +-- mers_libs/http_requests_v1/src/main.rs | 8 +- 16 files changed, 561 insertions(+), 216 deletions(-) create mode 100644 mers/src/interactive_mode.rs create mode 100644 mers/src/lib.rs create mode 100644 mers/src/tutor/base_comments.rs create mode 100644 mers/src/tutor/base_return.rs create mode 100644 mers/src/tutor/base_values.rs create mode 100644 mers/src/tutor/menu.rs create mode 100644 mers/src/tutor/mod.rs diff --git a/README.md b/README.md index 81a7b30..2cb7539 100755 --- a/README.md +++ b/README.md @@ -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")`. 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 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. diff --git a/mers/Cargo.toml b/mers/Cargo.toml index 63b43c2..508e015 100755 --- a/mers/Cargo.toml +++ b/mers/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "mers" -path = "src/main.rs" +name = "mers_libs" +path = "src/lib.rs" [dependencies] edit = "0.1.4" diff --git a/mers/src/interactive_mode.rs b/mers/src/interactive_mode.rs new file mode 100644 index 0000000..4df94ff --- /dev/null +++ b/mers/src/interactive_mode.rs @@ -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, + ) -> 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| { + 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))); + } + } +} diff --git a/mers/src/lib.rs b/mers/src/lib.rs new file mode 100644 index 0000000..d0a0c79 --- /dev/null +++ b/mers/src/lib.rs @@ -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::*}; diff --git a/mers/src/main.rs b/mers/src/main.rs index 4e78718..a9776f7 100755 --- a/mers/src/main.rs +++ b/mers/src/main.rs @@ -1,13 +1,16 @@ +#![allow(unused)] +#![allow(dead_code)] + use std::{fs, time::Instant}; use notify::Watcher as FsWatcher; -pub mod libs; -pub mod parse; -pub mod script; +mod interactive_mode; +mod libs; +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() { let args: Vec<_> = std::env::args().skip(1).collect(); #[cfg(debug_assertions)] @@ -27,9 +30,11 @@ fn main() { _ => { if args[0].trim_start().starts_with("-") { let mut execute = false; + let mut print_version = false; let mut verbose = 0; let mut interactive = 0; let mut interactive_use_new_terminal = false; + let mut teachme = false; let mut prev_char = None; let mut advanced = false; for ch in args[0][1..].chars() { @@ -41,7 +46,9 @@ fn main() { match ch { 'e' => execute = true, 'v' => verbose += 1, + 'V' => print_version = true, 'i' => interactive += 1, + 't' => teachme = true, ch => { eprintln!("Ignoring -{ch}. (unknown char)"); continue; @@ -65,103 +72,29 @@ fn main() { 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 { eprintln!("info: set verbosity level to {verbose}. this doesn't do anything yet. [TODO!]"); } if interactive >= 0 { - let (contents, path) = match interactive { - 1 => { + match interactive { + _ => { // basic: open file and watch for fs changes - let temp_file_edit = - 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| { - 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); - } + interactive_mode::fs_watcher::playground(interactive_use_new_terminal) } - _ => (String::new(), String::new()), }; - parse::file::File::new(contents, path.into()) + return; } else if execute { parse::file::File::new( args.iter().skip(1).fold(String::new(), |mut s, v| { diff --git a/mers/src/parse/parse.rs b/mers/src/parse/parse.rs index 0d6a671..e70f916 100755 --- a/mers/src/parse/parse.rs +++ b/mers/src/parse/parse.rs @@ -455,7 +455,7 @@ fn parse_statement_adv( 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), @@ -463,6 +463,40 @@ fn parse_statement_adv( }; match nchar { 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 :: = "), 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( start, parse_statement(file)?, @@ -484,8 +518,8 @@ fn parse_statement_adv( 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, + match parse_statement(file) { + Ok(v) => break v.output_to(start[derefs..].to_owned(), derefs), Err(mut e) => { e.context.push(( format!( @@ -495,43 +529,8 @@ fn parse_statement_adv( )); 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 = "), 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 let start = start.trim(); match start { diff --git a/mers/src/script/block.rs b/mers/src/script/block.rs index c8f89ee..a7236ed 100755 --- a/mers/src/script/block.rs +++ b/mers/src/script/block.rs @@ -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. // 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::{ fmt::Display, @@ -133,7 +134,11 @@ pub mod to_runnable { WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec), WrongArgsForLibFunction(String, Vec), ForLoopContainerHasNoInnerTypes, - StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType), + StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA( + VType, + VType, + VType, + ), } impl Debug for ToRunnableError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -150,7 +155,7 @@ pub mod to_runnable { Self::UseOfUndefinedVariable(v) => write!(f, "Cannot use variable \"{v}\" as it isn't defined (yet?)."), Self::UseOfUndefinedFunction(v) => write!(f, "Cannot use function \"{v}\" as it isn't defined (yet?)."), Self::CannotDeclareVariableWithDereference(v) => write!(f, "Cannot declare a variable and dereference it (variable '{v}')."), - Self::CannotDereferenceTypeNTimes(og_type, derefs_wanted, last_valid_type) => write!(f, + Self::CannotDereferenceTypeNTimes(og_type, derefs_wanted, last_valid_type) => write!(f, "Cannot dereference type {og_type} {derefs_wanted} times (stopped at {last_valid_type})." ), Self::FunctionWrongArgCount(v, a, b) => write!(f, "Tried to call function \"{v}\", which takes {a} arguments, with {b} arguments instead."), @@ -658,7 +663,7 @@ pub mod to_runnable { if problematic_types.is_empty() { statement.force_output_type = Some(force_opt.clone()); } 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 { @@ -669,7 +674,11 @@ pub mod to_runnable { var_derefd = if let Some(v) = var_derefd.dereference() { v } 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); @@ -693,16 +702,19 @@ pub mod to_runnable { out = if let Some(v) = out.dereference() { v } else { - return Err(ToRunnableError::CannotDereferenceTypeNTimes(statement.out(), *derefs, out)); + return Err(ToRunnableError::CannotDereferenceTypeNTimes( + statement.out(), + *derefs, + out, + )); } } - linfo - .vars - .insert(opt.clone(), (ginfo.vars, out)); + linfo.vars.insert(opt.clone(), (ginfo.vars, out)); statement.output_to = Some((ginfo.vars, *derefs)); ginfo.vars += 1; } - } Ok(statement) + } + Ok(statement) } } diff --git a/mers/src/script/builtins.rs b/mers/src/script/builtins.rs index 124be6e..84db3a6 100755 --- a/mers/src/script/builtins.rs +++ b/mers/src/script/builtins.rs @@ -8,7 +8,7 @@ use crate::libs; use super::{ block::RStatement, - val_data::{VData, VDataEnum, VDataThreadEnum}, + val_data::{thread::VDataThreadEnum, VData, VDataEnum}, val_type::{VSingleType, VType}, }; @@ -236,10 +236,21 @@ impl BuiltinFunction { Self::Run | Self::Thread => { if input.len() >= 1 { input[0].types.iter().all(|v| { + // all possible types of the input function must be function types 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!)"); - // 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 } else { false diff --git a/mers/src/script/val_data.rs b/mers/src/script/val_data.rs index 1acf644..9ebdcb2 100755 --- a/mers/src/script/val_data.rs +++ b/mers/src/script/val_data.rs @@ -1,8 +1,6 @@ use std::{ fmt::Debug, sync::{Arc, Mutex}, - thread::JoinHandle, - time::Duration, }; use super::{ @@ -25,7 +23,7 @@ pub enum VDataEnum { Tuple(Vec), List(VType, Vec), Function(RFunction), - Thread(VDataThread, VType), + Thread(thread::VDataThread, VType), Reference(Arc>), EnumVariant(usize, Box), } @@ -157,64 +155,75 @@ impl VType { } } -#[derive(Clone)] -pub struct VDataThread(Arc>); -impl VDataThread { - pub fn try_get(&self) -> Option { - match &*self.lock() { - VDataThreadEnum::Running(_) => None, - VDataThreadEnum::Finished(v) => Some(v.clone()), - } - } - pub fn get(&self) -> VData { - let dur = Duration::from_millis(100); - loop { +pub mod thread { + use std::{ + fmt::Debug, + sync::{Arc, Mutex}, + thread::JoinHandle, + time::Duration, + }; + + use super::{VData, VDataEnum}; + + #[derive(Clone)] + pub struct VDataThread(Arc>); + impl VDataThread { + pub fn try_get(&self) -> Option { match &*self.lock() { - VDataThreadEnum::Running(v) => { - while !v.is_finished() { - std::thread::sleep(dur); - } - } - VDataThreadEnum::Finished(v) => return v.clone(), + VDataThreadEnum::Running(_) => None, + VDataThreadEnum::Finished(v) => Some(v.clone()), } } - } - pub fn lock(&self) -> std::sync::MutexGuard { - let mut mg = self.0.lock().unwrap(); - match &*mg { - VDataThreadEnum::Running(v) => { - if v.is_finished() { - let m = std::mem::replace( - &mut *mg, - VDataThreadEnum::Finished(VDataEnum::Bool(false).to()), - ); - match m { - VDataThreadEnum::Running(v) => { - *mg = VDataThreadEnum::Finished(v.join().unwrap()) + pub fn get(&self) -> VData { + let dur = Duration::from_millis(100); + loop { + match &*self.lock() { + VDataThreadEnum::Running(v) => { + while !v.is_finished() { + std::thread::sleep(dur); } - _ => unreachable!(), } + VDataThreadEnum::Finished(v) => return v.clone(), } } - _ => (), } - mg + pub fn lock(&self) -> std::sync::MutexGuard { + let mut mg = self.0.lock().unwrap(); + match &*mg { + VDataThreadEnum::Running(v) => { + if v.is_finished() { + let m = std::mem::replace( + &mut *mg, + VDataThreadEnum::Finished(VDataEnum::Bool(false).to()), + ); + match m { + VDataThreadEnum::Running(v) => { + *mg = VDataThreadEnum::Finished(v.join().unwrap()) + } + _ => unreachable!(), + } + } + } + _ => (), + } + mg + } } -} -impl Debug for VDataThread { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &*self.lock() { - VDataThreadEnum::Running(_) => write!(f, "(thread running)"), - VDataThreadEnum::Finished(v) => write!(f, "(thread finished: {v})"), + impl Debug for VDataThread { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &*self.lock() { + VDataThreadEnum::Running(_) => write!(f, "(thread running)"), + VDataThreadEnum::Finished(v) => write!(f, "(thread finished: {v})"), + } + } + } + pub enum VDataThreadEnum { + Running(JoinHandle), + Finished(VData), + } + impl VDataThreadEnum { + pub fn to(self) -> VDataThread { + VDataThread(Arc::new(Mutex::new(self))) } } } -pub enum VDataThreadEnum { - Running(JoinHandle), - Finished(VData), -} -impl VDataThreadEnum { - pub fn to(self) -> VDataThread { - VDataThread(Arc::new(Mutex::new(self))) - } -} diff --git a/mers/src/tutor/base_comments.rs b/mers/src/tutor/base_comments.rs new file mode 100644 index 0000000..63c22b7 --- /dev/null +++ b/mers/src/tutor/base_comments.rs @@ -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); + } + } + } +} diff --git a/mers/src/tutor/base_return.rs b/mers/src/tutor/base_return.rs new file mode 100644 index 0000000..308a24d --- /dev/null +++ b/mers/src/tutor/base_return.rs @@ -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); + } + } + } +} diff --git a/mers/src/tutor/base_values.rs b/mers/src/tutor/base_values.rs new file mode 100644 index 0000000..e709e84 --- /dev/null +++ b/mers/src/tutor/base_values.rs @@ -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); + } + } + } +} diff --git a/mers/src/tutor/menu.rs b/mers/src/tutor/menu.rs new file mode 100644 index 0000000..0758b46 --- /dev/null +++ b/mers/src/tutor/menu.rs @@ -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; + } + } +} diff --git a/mers/src/tutor/mod.rs b/mers/src/tutor/mod.rs new file mode 100644 index 0000000..199c2b1 --- /dev/null +++ b/mers/src/tutor/mod.rs @@ -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>, +} +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::() + ) + } + 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 + } +} diff --git a/mers_libs/gui_v1/src/main.rs b/mers_libs/gui_v1/src/main.rs index 7dc4fb9..72e0842 100755 --- a/mers_libs/gui_v1/src/main.rs +++ b/mers_libs/gui_v1/src/main.rs @@ -9,13 +9,8 @@ use iced::{ widget::{button, column, row, text}, Application, Command, Element, Renderer, Settings, Subscription, Theme, }; -use mers::{ - libs::inlib::{MyLib, MyLibTask}, - script::{ - val_data::{VData, VDataEnum}, - val_type::{VSingleType, VType}, - }, -}; + +use mers_libs::{MyLib, MyLibTask, VData, VDataEnum, VSingleType, VType}; /* @@ -264,7 +259,6 @@ impl Application for App { format!("{}", self.title) } fn update(&mut self, message: Self::Message) -> Command { - let mut commands = vec![]; match message { Message::Tick => { let mut changed_layout = false; @@ -303,7 +297,7 @@ impl Application for App { )) .unwrap(), } - Command::batch(commands) + Command::none() } fn subscription(&self) -> Subscription { time::every(Duration::from_millis(10)).map(|_| Message::Tick) diff --git a/mers_libs/http_requests_v1/src/main.rs b/mers_libs/http_requests_v1/src/main.rs index 8f2f7a3..0705ace 100755 --- a/mers_libs/http_requests_v1/src/main.rs +++ b/mers_libs/http_requests_v1/src/main.rs @@ -1,10 +1,4 @@ -use mers::{ - libs::inlib::{MyLib, MyLibTask}, - script::{ - val_data::VDataEnum, - val_type::{VSingleType, VType}, - }, -}; +use mers_libs::{MyLib, MyLibTask, VDataEnum, VSingleType, VType}; fn main() { let (mut my_lib, mut run) = MyLib::new(