diff --git a/mers/c.mers b/mers/c.mers index 32caa97..b544b26 100644 --- a/mers/c.mers +++ b/mers/c.mers @@ -1,4 +1,6 @@ list = [1 ...] b = list &list.pop() +// &list.push(5) +println(b.to_string()) list \ No newline at end of file diff --git a/mers/src/lang/builtins.rs b/mers/src/lang/builtins.rs index cc416b2..aeaefc0 100755 --- a/mers/src/lang/builtins.rs +++ b/mers/src/lang/builtins.rs @@ -1388,9 +1388,9 @@ impl BuiltinFunction { _ => unreachable!("max: not a number"), }) }), - Self::Push => args[0].run(info).operate_on_data_mut(|list| { + Self::Push => args[0].run(info).operate_on_data_mut(info, |list| { if let VDataEnum::Reference(v) = list { - v.operate_on_data_mut(|list| { + v.operate_on_data_mut(info, |list| { if let VDataEnum::List(_, v) = list { v.push(args[1].run(info)); } @@ -1400,11 +1400,11 @@ impl BuiltinFunction { unreachable!("push: not a reference") } }), - Self::Insert => args[0].run(info).operate_on_data_mut(|v| { + Self::Insert => args[0].run(info).operate_on_data_mut(info, |v| { args[1].run(info).operate_on_data_immut(|i| { // TODO: find out why the fuck this helps if let (VDataEnum::Reference(v), VDataEnum::Int(i)) = (v, i) { - v.operate_on_data_mut(|v| { + v.operate_on_data_mut(info, |v| { if let VDataEnum::List(_, v) = v { v.insert(*i as _, args[2].run(info)); } @@ -1415,9 +1415,9 @@ impl BuiltinFunction { } }) }), - Self::Pop => args[0].run(info).operate_on_data_mut(|v| { + Self::Pop => args[0].run(info).operate_on_data_mut(info, |v| { if let VDataEnum::Reference(v) = v { - v.operate_on_data_mut(|v| { + v.operate_on_data_mut(info, |v| { if let VDataEnum::List(_, v) = v { if let Some(v) = v.pop() { VDataEnum::Tuple(vec![v]) @@ -1433,12 +1433,12 @@ impl BuiltinFunction { unreachable!("pop: not a reference") } }), - Self::Remove => args[0].run(info).operate_on_data_mut(|v| { + Self::Remove => args[0].run(info).operate_on_data_mut(info, |v| { args[1].run(info).operate_on_data_immut(|i| // this being a reference means we wont need to call make_mut() later, so a .as_ref() borrow is enough. if let (VDataEnum::Reference(v), VDataEnum::Int(i)) = (v, i ) { - v.operate_on_data_mut(|v| { + v.operate_on_data_mut(info, |v| { if let VDataEnum::List(_, v) = v { if *i >= 0 && v.len() > *i as _ { let v = v.remove(*i as _); @@ -1482,12 +1482,12 @@ impl BuiltinFunction { } }) }), - Self::GetRef => args[0].run(info).operate_on_data_mut(|container| { + Self::GetRef => args[0].run(info).operate_on_data_mut(info, |container| { args[1].run(info).operate_on_data_immut(|i| { if let (VDataEnum::Reference(container), VDataEnum::Int(i)) = (container, i) { if *i >= 0 { // we can get mutably because this is the content of a reference - match container.operate_on_data_mut(|container| match container { + match container.operate_on_data_mut(info, |container| match container { VDataEnum::List(_, v) | VDataEnum::Tuple(v) => { if let Some(v) = v.get_mut(*i as usize) { Some(VDataEnum::Reference(v.clone_mut()).to()) diff --git a/mers/src/lang/code_runnable.rs b/mers/src/lang/code_runnable.rs index c61c32d..b9b5072 100755 --- a/mers/src/lang/code_runnable.rs +++ b/mers/src/lang/code_runnable.rs @@ -112,7 +112,14 @@ impl RStatement { 'init: { if *is_init && *derefs == 0 { if let RStatementEnum::Variable(var, _, _) = v.statement.as_ref() { - *var.lock().unwrap() = out; + let mut varl = var.lock().unwrap(); + #[cfg(debug_assertions)] + let varname = varl.1.clone(); + *varl = out; + #[cfg(debug_assertions)] + { + varl.1 = varname; + } break 'init; } } @@ -123,7 +130,7 @@ impl RStatement { None => unreachable!("can't dereference..."), }; } - val.assign(out); + val.assign(info, out); } VDataEnum::Tuple(vec![]).to() } else { @@ -174,7 +181,7 @@ impl RStatementEnum { } Self::FunctionCall(func, args) => { for (i, input) in func.inputs.iter().enumerate() { - input.lock().unwrap().assign(args[i].run(info)); + input.lock().unwrap().assign(info, args[i].run(info)); } func.run(info) } diff --git a/mers/src/lang/global_info.rs b/mers/src/lang/global_info.rs index 71a7961..b77f2bf 100755 --- a/mers/src/lang/global_info.rs +++ b/mers/src/lang/global_info.rs @@ -1,8 +1,16 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + fmt::Display, + sync::{Arc, Mutex}, +}; use crate::libs; -use super::{builtins, val_type::VType}; +use super::{ + builtins, + val_data::VDataEnum, + val_type::{VSingleType, VType}, +}; pub type GSInfo = Arc; @@ -15,6 +23,9 @@ pub struct GlobalScriptInfo { pub custom_type_names: HashMap, pub custom_types: Vec, + + #[cfg(debug_assertions)] + pub log: Logger, } impl GlobalScriptInfo { @@ -31,6 +42,8 @@ impl Default for GlobalScriptInfo { enum_variants: Self::default_enum_variants(), custom_type_names: HashMap::new(), custom_types: vec![], + #[cfg(debug_assertions)] + log: Logger::new(), } } } @@ -43,3 +56,85 @@ impl GlobalScriptInfo { .collect() } } + +#[cfg(debug_assertions)] +#[derive(Debug)] +pub struct Logger { + logs: Arc>>, + + pub vdata_clone: LogKind, + pub vtype_fits_in: LogKind, + pub vsingletype_fits_in: LogKind, +} +#[cfg(debug_assertions)] +impl Logger { + pub fn new() -> Self { + Self { + logs: Arc::new(Mutex::new(vec![])), + vdata_clone: Default::default(), + vtype_fits_in: Default::default(), + vsingletype_fits_in: Default::default(), + } + } +} + +#[cfg(debug_assertions)] +#[derive(Debug)] +pub enum LogMsg { + VDataClone(Option, VDataEnum, usize, usize), + VTypeFitsIn(VType, VType, Vec), + VSingleTypeFitsIn(VSingleType, VSingleType, bool), +} +#[cfg(debug_assertions)] +impl Logger { + pub fn log(&self, msg: LogMsg) { + let kind = match msg { + LogMsg::VDataClone(..) => &self.vdata_clone, + LogMsg::VTypeFitsIn(..) => &self.vtype_fits_in, + LogMsg::VSingleTypeFitsIn(..) => &self.vsingletype_fits_in, + }; + if kind.stderr { + eprintln!("{msg}"); + } + if kind.log { + if let Ok(mut logs) = self.logs.lock() { + logs.push(msg); + } + } + } +} +#[cfg(debug_assertions)] +impl Display for LogMsg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::VDataClone(varname, data, src_addr, new_addr) => { + write!( + f, + "VDataClone :: {data} ({}{src_addr} -> {new_addr})", + if let Some(v) = varname { + format!("{v} | ") + } else { + String::new() + } + ) + } + Self::VTypeFitsIn(a, b, no) => write!(f, "VTypeFitsIn :: {a} in {b} ? -> {no:?}"), + Self::VSingleTypeFitsIn(a, b, fits) => { + write!(f, "VSingleTypeFitsIn :: {a} in {b} ? -> {fits}") + } + } + } +} + +#[cfg(debug_assertions)] +#[derive(Clone, Debug, Default)] +pub struct LogKind { + pub stderr: bool, + pub log: bool, +} +#[cfg(debug_assertions)] +impl LogKind { + pub fn log(&self) -> bool { + self.stderr || self.log + } +} diff --git a/mers/src/lang/to_runnable.rs b/mers/src/lang/to_runnable.rs index 22bbc6d..4f02c73 100755 --- a/mers/src/lang/to_runnable.rs +++ b/mers/src/lang/to_runnable.rs @@ -223,6 +223,9 @@ fn get_all_functions( vartype.to() } } + // block is parsed multiple times (this is why we get duplicates in stderr): + // - n times for the function args to generate the input-output map + // - 1 more time here, where the function args aren't single types out.push(( inputs.clone(), block(&s.block, ginfo, linfo.clone())?.out(ginfo), @@ -367,7 +370,11 @@ fn statement_adv( if !linfo.vars.contains_key(v) { if let Some((t, is_init)) = to_be_assigned_to { *is_init = true; - linfo.vars.insert(v.to_owned(), (Arc::new(Mutex::new(VData::new_placeholder())), t)); + #[cfg(not(debug_assertions))] + let var = VData::new_placeholder(); + #[cfg(debug_assertions)] + let var = VData::new_placeholder_with_name(v.to_owned()); + linfo.vars.insert(v.to_owned(), (Arc::new(Mutex::new(var)), t)); } } if let Some(var) = linfo.vars.get(v) { diff --git a/mers/src/lang/val_data.rs b/mers/src/lang/val_data.rs index a9913f3..7faeb42 100755 --- a/mers/src/lang/val_data.rs +++ b/mers/src/lang/val_data.rs @@ -1,5 +1,6 @@ use std::{ fmt::{self, Debug, Display, Formatter}, + ops::Deref, sync::{Arc, Mutex, MutexGuard}, }; @@ -9,6 +10,9 @@ use super::{ val_type::{VSingleType, VType}, }; +#[cfg(debug_assertions)] +use super::global_info::LogMsg; + #[derive(Debug)] pub enum VDataEnum { Bool(bool), @@ -23,8 +27,11 @@ pub enum VDataEnum { EnumVariant(usize, Box), } +#[cfg(not(debug_assertions))] pub struct VData(Arc>); -enum VDataInner { +#[cfg(debug_assertions)] +pub struct VData(pub Arc>, pub Option); +pub enum VDataInner { Data(usize, Box), Mut(Arc>), ClonedFrom(VData), @@ -35,7 +42,10 @@ enum VDataInner { /// - any Mut will eventually point to a ClonedFrom or a Data variant. It can also point to another Mut. impl VDataInner { fn to(self) -> VData { - VData(Arc::new(Mutex::new(self))) + #[cfg(not(debug_assertions))] + return VData(Arc::new(Mutex::new(self))); + #[cfg(debug_assertions)] + return VData(Arc::new(Mutex::new(self)), None); } } impl VDataEnum { @@ -48,9 +58,17 @@ impl VData { pub fn new_placeholder() -> Self { VDataEnum::Bool(false).to() } + #[cfg(debug_assertions)] + pub fn new_placeholder_with_name(name: String) -> Self { + let mut o = VDataEnum::Bool(false).to(); + o.1 = Some(name); + o + } /// clones self, retrurning a new instance of self that will always yield the value self had when this function was called. /// note to dev: since the actual data is stored in VDataEnum, which either clones data or calls clone() (= clone_data()) on further VData, this automatically borrows all child data as immutable too. rust's Drop::drop() implementation (probably) handles everything for us too, so this can be implemented without thinking about recursion. pub fn clone_data(&self) -> Self { + // TODO! implement CopyOnWrite. For now, just always copy. This also prevents mut references not existing since in ::Dat(cloned, _), cloned will always stay 0. + return self.operate_on_data_immut(|v| v.clone()).to(); match &mut *self.0.lock().unwrap() { VDataInner::Data(cloned, _data) => { *cloned += 1; @@ -65,7 +83,10 @@ impl VData { VDataInner::Mut(Arc::new(Mutex::new(self.clone_arc()))).to() } fn clone_arc(&self) -> Self { - Self(Arc::clone(&self.0)) + #[cfg(not(debug_assertions))] + return Self(Arc::clone(&self.0)); + #[cfg(debug_assertions)] + return Self(Arc::clone(&self.0), self.1.clone()); } pub fn operate_on_data_immut(&self, mut func: F) -> O where @@ -80,27 +101,39 @@ impl VData { /// runs func on the underlying data. /// attempts to get a mutable reference to the data. if this fails, it will (partially) clone the data, then point the VData to the new data, /// so that other VDatas pointing to the same original data aren't changed. - pub fn operate_on_data_mut(&mut self, mut func: F) -> O + pub fn operate_on_data_mut(&mut self, info: &GlobalScriptInfo, mut func: F) -> O where F: FnOnce(&mut VDataEnum) -> O, { let (new_val, o) = { - match &mut *self.0.lock().unwrap() { + let mut lock = self.0.lock().unwrap(); + match &mut *lock { VDataInner::Data(count, data) => { if *count == 0 { (None, func(data.as_mut())) } else { - #[cfg(debug_assertions)] - eprintln!("Cloning: data should be modified, but was borrowed immutably."); let mut new_data = data.clone(); let o = func(new_data.as_mut()); // *self doesn't modify the ::Data, it instead points the value that wraps it to a new ::Data, leaving the old one as it was. // for proof: data is untouched, only the new_data is ever modified. - (Some(VDataInner::Data(0, new_data).to()), o) + let new_vdata = VDataInner::Data(0, new_data).to(); + #[cfg(debug_assertions)] + if info.log.vdata_clone.log() { + drop(lock); + info.log.log(LogMsg::VDataClone( + self.1.clone(), + self.inner_cloned(), + Arc::as_ptr(&self.0) as usize, + Arc::as_ptr(&new_vdata.0) as usize, + )); + } + (Some(new_vdata), o) } } - VDataInner::Mut(inner) => (None, inner.lock().unwrap().operate_on_data_mut(func)), - VDataInner::ClonedFrom(inner) => (None, inner.operate_on_data_mut(func)), + VDataInner::Mut(inner) => { + (None, inner.lock().unwrap().operate_on_data_mut(info, func)) + } + VDataInner::ClonedFrom(inner) => (None, inner.operate_on_data_mut(info, func)), } }; if let Some(nv) = new_val { @@ -111,12 +144,13 @@ impl VData { /// Since operate_on_data_mut can clone, it may be inefficient for just assigning (where we don't care about the previous value, so it doesn't need to be cloned). /// This is what this function is for. (TODO: actually make it more efficient instead of using operate_on_data_mut) - pub fn assign_data(&mut self, new_data: VDataEnum) { - self.operate_on_data_mut(|d| *d = new_data) + pub fn assign_data(&mut self, info: &GlobalScriptInfo, new_data: VDataEnum) { + let o = self.operate_on_data_mut(info, |d| *d = new_data); + o } /// Assigns the new_data to self. Affects all muts pointing to the same data, but no ClonedFroms. - pub fn assign(&mut self, new: VData) { - self.assign_data(new.inner_cloned()) + pub fn assign(&mut self, info: &GlobalScriptInfo, new: VData) { + self.assign_data(info, new.inner_cloned()) // !PROBLEM! If ClonedFrom always has to point to a Data, this may break things! // match &mut *self.0.lock().unwrap() { // VDataInner::Data(count, data) => { @@ -133,9 +167,7 @@ impl Drop for VDataInner { fn drop(&mut self) { if let Self::ClonedFrom(origin) = self { if let Self::Data(ref_count, _data) = &mut *origin.0.lock().unwrap() { - eprint!("rc: {}", *ref_count); - *ref_count = ref_count.saturating_sub(1); - eprintln!(" -> {}", *ref_count); + // *ref_count = ref_count.saturating_sub(1); } } } diff --git a/mers/src/lang/val_type.rs b/mers/src/lang/val_type.rs index eb9961e..61d3ebd 100755 --- a/mers/src/lang/val_type.rs +++ b/mers/src/lang/val_type.rs @@ -6,6 +6,9 @@ use std::{ use super::global_info::{self, GSInfo, GlobalScriptInfo}; +#[cfg(debug_assertions)] +use super::global_info::LogMsg; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct VType { pub types: Vec, @@ -151,8 +154,6 @@ impl VType { impl VType { /// Returns a vec with all types in self that aren't covered by rhs. If the returned vec is empty, self fits in rhs. pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> Vec { - #[cfg(debug_assertions)] - eprintln!("{} in {}? [VType]", self, rhs); let mut no = vec![]; for t in &self.types { // if t doesnt fit in any of rhs's types @@ -160,6 +161,11 @@ impl VType { no.push(t.clone()) } } + #[cfg(debug_assertions)] + if info.log.vtype_fits_in.log() { + info.log + .log(LogMsg::VTypeFitsIn(self.clone(), rhs.clone(), no.clone())) + } no } pub fn inner_types(&self) -> VType { @@ -294,8 +300,6 @@ impl VSingleType { } } pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool { - #[cfg(debug_assertions)] - eprintln!("{self} in {rhs}?"); let o = match (self, rhs) { (Self::Reference(r), Self::Reference(b)) => r.fits_in(b, info), (Self::Reference(_), _) | (_, Self::Reference(_)) => false, @@ -356,7 +360,10 @@ impl VSingleType { (Self::Thread(..), _) => false, }; #[cfg(debug_assertions)] - eprintln!(" -> {}", o); + if info.log.vsingletype_fits_in.log() { + info.log + .log(LogMsg::VSingleTypeFitsIn(self.clone(), rhs.clone(), o)); + } o } pub fn fits_in_type(&self, rhs: &VType, info: &GlobalScriptInfo) -> bool { diff --git a/mers/src/main.rs b/mers/src/main.rs index 2a69eec..41650aa 100755 --- a/mers/src/main.rs +++ b/mers/src/main.rs @@ -3,6 +3,9 @@ use std::{fs, time::Instant}; +use lang::global_info::GlobalScriptInfo; +#[cfg(debug_assertions)] +use lang::global_info::LogKind; use notify::Watcher as FsWatcher; mod interactive_mode; @@ -22,14 +25,8 @@ fn main() { fn normal_main() { let args: Vec<_> = std::env::args().skip(1).collect(); - #[cfg(debug_assertions)] - let args = if args.len() == 0 { - let mut args = args; - args.push("../script.mers".to_owned()); - args - } else { - args - }; + let mut info = GlobalScriptInfo::default(); + let mut args_to_skip = 2; let mut file = match args.len() { 0 => { println!("Please provide some arguments, such as the path to a file or \"-e \"."); @@ -39,7 +36,8 @@ fn normal_main() { if args[0].trim_start().starts_with("-") { let mut execute = false; let mut print_version = false; - let mut verbose = 0; + let mut verbose = false; + let mut verbose_args = String::new(); let mut interactive = 0; let mut interactive_use_new_terminal = false; let mut teachme = false; @@ -53,7 +51,7 @@ fn normal_main() { } match ch { 'e' => execute = true, - 'v' => verbose += 1, + 'v' => verbose = true, 'V' => print_version = true, 'i' => interactive += 1, 't' => teachme = true, @@ -64,12 +62,19 @@ fn normal_main() { } prev_char = Some(ch); } else { + advanced = false; if let Some(prev_char) = prev_char { match prev_char { 'i' => match ch { 't' => interactive_use_new_terminal = true, _ => eprintln!("Ignoring i+{ch}. (unknown adv char)"), }, + 'v' => { + if ch != '+' { + advanced = true; + verbose_args.push(ch); + } + } _ => (), } } else { @@ -77,7 +82,6 @@ fn normal_main() { "Ignoring advanced args because there was no previous argument." ); } - advanced = false; } } if print_version { @@ -92,8 +96,55 @@ fn normal_main() { tutor::start(false); return; } - if verbose != 0 { - eprintln!("info: set verbosity level to {verbose}. this doesn't do anything yet. [TODO!]"); + if verbose { + #[cfg(not(debug_assertions))] + eprintln!("WARN: Verbose (-v) only works in debug builds!"); + #[cfg(debug_assertions)] + if verbose_args.is_empty() { + fn f() -> LogKind { + LogKind { + stderr: true, + log: true, + } + } + info.log.vdata_clone = f(); + info.log.vtype_fits_in = f(); + info.log.vsingletype_fits_in = f(); + } else { + fn kind(val: Option<&str>) -> LogKind { + match val { + Some("stderr") => LogKind { + stderr: true, + ..Default::default() + }, + Some("log") => LogKind { + log: true, + ..Default::default() + }, + Some("log+stderr" | "stderr+log") => LogKind { + stderr: true, + log: true, + ..Default::default() + }, + _ => LogKind { + stderr: true, + ..Default::default() + }, + } + } + for verbose_arg in verbose_args.split(',') { + let (arg, val) = match verbose_arg.split_once('=') { + Some((left, right)) => (left, Some(right)), + None => (verbose_arg, None), + }; + match arg { + "vdata_clone" => info.log.vdata_clone = kind(val), + "vtype_fits_in" => info.log.vtype_fits_in = kind(val), + "vsingletype_fits_in" => info.log.vsingletype_fits_in = kind(val), + _ => eprintln!("Warn: -v+ unknown arg '{arg}'."), + } + } + } } if interactive > 0 { match interactive { @@ -115,8 +166,16 @@ fn normal_main() { std::path::PathBuf::new(), ) } else { - println!("please provide either a file or -e and a script to run!"); - std::process::exit(101); + args_to_skip += 1; + if let Some(file) = args.get(1) { + parsing::file::File::new( + std::fs::read_to_string(file).unwrap(), + file.into(), + ) + } else { + println!("please provide either a file or -e and a script to run!"); + std::process::exit(101); + } } } else { parsing::file::File::new( @@ -126,13 +185,11 @@ fn normal_main() { } } }; - match parsing::parse::parse(&mut file) { + match parsing::parse::parse_custom_info(&mut file, info) { Ok(script) => { - #[cfg(debug_assertions)] - eprintln!("{script:#?}"); println!(" - - - - -"); let start = Instant::now(); - let out = script.run(std::env::args().skip(2).collect()); + let out = script.run(std::env::args().skip(args_to_skip).collect()); let elapsed = start.elapsed(); println!(" - - - - -"); println!("Output ({}s)\n{out}", elapsed.as_secs_f64()); diff --git a/mers/src/parsing/file.rs b/mers/src/parsing/file.rs index 8d2f60f..e4594e7 100755 --- a/mers/src/parsing/file.rs +++ b/mers/src/parsing/file.rs @@ -216,8 +216,8 @@ impl Iterator for File { } _ => self.pos.current_column += 1, } - #[cfg(debug_assertions)] - eprint!("{ch}"); + // #[cfg(debug_assertions)] + // eprint!("{ch}"); Some(*ch) } None => None, diff --git a/mers/src/parsing/parse.rs b/mers/src/parsing/parse.rs index 8f6d296..ce5f3f5 100755 --- a/mers/src/parsing/parse.rs +++ b/mers/src/parsing/parse.rs @@ -136,8 +136,10 @@ impl Error { /// executes the 4 parse_steps in order: lib_paths => interpret => libs_load => compile pub fn parse(file: &mut File) -> Result { - let mut ginfo = GlobalScriptInfo::default(); - + parse_custom_info(file, GlobalScriptInfo::default()) +} +/// like parse, but GlobalInfo can be something other than Default::default(). +pub fn parse_custom_info(file: &mut File, mut ginfo: GlobalScriptInfo) -> Result { let libs = match parse_step_lib_paths(file) { Ok(v) => v, Err(e) => return Err((e.into(), ginfo.to_arc()).into()),