From 862418ce986e1bf89c1de0f7dc0359711a91fb2e Mon Sep 17 00:00:00 2001 From: Dummi26 Date: Tue, 14 Mar 2023 20:14:30 +0100 Subject: [PATCH] almost functional type checking for builtins; builtins return types work as expected now (for List.get, Thread.await() etc) --- src/script/block.rs | 72 +++++++++++++++++++++++++++++++++++------- src/script/builtins.rs | 57 ++++++++++++++++++++++++++++++--- src/script/value.rs | 62 +++++++++++++++++++++++++++++------- 3 files changed, 164 insertions(+), 27 deletions(-) diff --git a/src/script/block.rs b/src/script/block.rs index 7aafde2..6c7b55b 100644 --- a/src/script/block.rs +++ b/src/script/block.rs @@ -2,6 +2,7 @@ // 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. use std::{ + collections::HashSet, fmt::Display, sync::{Arc, Mutex}, }; @@ -301,7 +302,11 @@ pub mod to_runnable { } else { // TODO: type-checking for builtins if let Some(builtin) = BuiltinFunction::get(v) { - RStatementEnum::BuiltinFunction(builtin, rargs) + if builtin.can_take(&rargs.iter().map(|v| v.out()).collect()) { + RStatementEnum::BuiltinFunction(builtin, rargs) + } else { + todo!("ERR: Builtin function with wrong args - this isn't a proper error yet, sorry."); + } } else { return Err(ToRunnableError::UseOfUndefinedFunction(v.clone())); } @@ -516,6 +521,19 @@ impl RFunction { }) .expect("invalid args for function! possible issue with type-checker if this can be reached! feel free to report a bug.") } + pub fn out_vt(&self, input_types: &Vec) -> VType { + let mut out = VType { types: vec![] }; + for (itype, otype) in self.input_output_map.iter() { + if itype + .iter() + .zip(input_types.iter()) + .all(|(expected, got)| got.contains(expected)) + { + out = out | otype; + } + } + out + } pub fn out_all(&self) -> VType { self.block.out() } @@ -695,10 +713,7 @@ impl RStatementEnum { t.clone() } } - Self::FunctionCall(f, _) => { - eprintln!("Warn: generalizing a functions return type regardless of the inputs. Type-checker might assume this value can have more types than it really can."); - f.out_all() - } + Self::FunctionCall(f, args) => f.out_vt(&args.iter().map(|v| v.out()).collect()), Self::Block(b) => b.out(), Self::If(_, a, b) => { if let Some(b) = b { @@ -707,7 +722,28 @@ impl RStatementEnum { a.out() } } - Self::While(c) => todo!("while loop output type"), + Self::While(c) => { + let mut output_types = VType { types: vec![] }; + for t in c.out().types { + match t { + VSingleType::Bool => { + output_types = output_types | VSingleType::Tuple(vec![]).to(); + } + VSingleType::Tuple(mut t) => { + if !t.is_empty() { + if t.len() != 1 { + unreachable!("while loop with tuple of length {}>1.", t.len()); + } + output_types = output_types | t.pop().unwrap(); + } + } + _ => unreachable!( + "while loop statement didn't return bool or 0-1 length tuple." + ), + } + } + output_types + } Self::For(_, _, b) => { // returns the return value from the last iteration or nothing if there was no iteration b.out() @@ -715,11 +751,25 @@ impl RStatementEnum { types: vec![VSingleType::Tuple(vec![])], } } - Self::BuiltinFunction(f, _) => f.returns(), - Self::Switch(_, cases) => { + Self::BuiltinFunction(f, args) => f.returns(args.iter().map(|rs| rs.out()).collect()), + Self::Switch(switch_on, cases) => { + let switch_on = switch_on.out().types; + let mut might_return_empty = switch_on.is_empty(); let mut out = VSingleType::Tuple(vec![]).into(); // if nothing is executed - for (_, case) in cases.iter() { - out = out | case.out(); + for switch_on in switch_on { + let switch_on: VType = switch_on.into(); + 'search: { + for (on_type, case) in cases.iter() { + if switch_on.fits_in(&on_type).is_empty() { + out = out | case.out(); + break 'search; + } + } + might_return_empty = true; + } + } + if might_return_empty { + out = out | VSingleType::Tuple(vec![]).to(); } out } @@ -816,7 +866,7 @@ impl Display for VSingleType { Ok(()) } Self::List(t) => write!(f, "[{t}]"), - Self::Function(args, out) => write!(f, "({args:?}) -> {out}"), + Self::Function(_) => write!(f, "FUNCTION"), Self::Thread(_) => write!(f, "THREAD"), Self::Reference(r) => write!(f, "&{r}"), } diff --git a/src/script/builtins.rs b/src/script/builtins.rs index 95ab21f..074fc41 100644 --- a/src/script/builtins.rs +++ b/src/script/builtins.rs @@ -91,8 +91,11 @@ impl BuiltinFunction { _ => return None, }) } - /// for invalid inputs, returns VType { types: vec![] }. - pub fn returns(&self, in: Vec) -> VType { + pub fn can_take(&self, input: &Vec) -> bool { + true // TODO! + } + /// for invalid inputs, may panic + pub fn returns(&self, input: Vec) -> VType { match self { // [] Self::Print | Self::Println | Self::Debug | Self::Sleep => VType { @@ -101,9 +104,53 @@ impl BuiltinFunction { // String Self::ToString | Self::Format => VSingleType::String.into(), // ! - Self::Run | Self::Thread | Self::Await | Self::Pop | Self::Remove | Self::Get => { - VType { types: vec![] } // TODO! - // unreachable!("this has to be implemented somewhere else!") + Self::Run | Self::Thread => { + if let Some(funcs) = input.first() { + let mut out = VType { types: vec![] }; + for func in &funcs.types { + if let VSingleType::Function(io) = func { + for (i, o) in io { + if i.iter() + .zip(input.iter().skip(1)) + .all(|(i, input)| input.contains(i)) + { + out = out | o; + } + } + } else { + unreachable!("run called, first arg not a function") + } + } + match self { + Self::Run => out, + Self::Thread => VSingleType::Thread(out).to(), + _ => unreachable!(), + } + } else { + unreachable!("run or thread called without args") + } + } + Self::Await => { + if let Some(v) = input.first() { + let mut out = VType { types: vec![] }; + for v in &v.types { + if let VSingleType::Thread(v) = v { + out = out | v; + } else { + unreachable!("await called with non-thread arg") + } + } + out + } else { + unreachable!("await called without args") + } + } + Self::Pop | Self::Remove | Self::Get => { + if let Some(v) = input.first() { + v.get_any().expect("cannot use get on this type") + } else { + unreachable!("get, pop or remove called without args") + } } Self::Exit => VType { types: vec![] }, // doesn't return Self::FsList => VType { diff --git a/src/script/value.rs b/src/script/value.rs index 19791b2..e7819fe 100644 --- a/src/script/value.rs +++ b/src/script/value.rs @@ -27,10 +27,7 @@ impl VData { VDataEnum::String(..) => VSingleType::String, VDataEnum::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out()).collect()), VDataEnum::List(t, _) => VSingleType::List(t.clone()), - VDataEnum::Function(f) => VSingleType::Function(f.in_types().clone(), { - eprintln!("Warn: generalizing function return type, disregarding input types. might make the type checker think it can return types it can only return with different arguments as the ones that were actually provided."); - f.out_all() - }), + VDataEnum::Function(f) => VSingleType::Function(f.input_output_map.clone()), VDataEnum::Thread(_, o) => VSingleType::Thread(o.clone()), VDataEnum::Reference(r) => r.lock().unwrap().out_single(), } @@ -153,6 +150,15 @@ impl VSingleType { Self::Reference(r) => r.get(i), } } + pub fn get_any(&self) -> Option { + match self { + Self::Bool | Self::Int | Self::Float | Self::Function(..) | Self::Thread(..) => None, + Self::String => Some(VSingleType::String.into()), + Self::Tuple(t) => Some(t.iter().fold(VType { types: vec![] }, |a, b| a | b)), + Self::List(t) => Some(t.clone()), + Self::Reference(r) => r.get_any(), + } + } } impl VType { pub fn get(&self, i: usize) -> Option { @@ -162,6 +168,13 @@ impl VType { } Some(out) } + pub fn get_any(&self) -> Option { + let mut out = VType { types: vec![] }; + for t in &self.types { + out = out | t.get_any()?; // if we can't use *get* on one type, we can't use it at all. + } + Some(out) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -184,11 +197,14 @@ impl VType { let mut out = VType { types: vec![] }; for t in &self.types { for it in t.inner_types() { - out = out | it.into(); + out = out | it.to(); } } out } + pub fn contains(&self, t: &VSingleType) -> bool { + self.types.contains(t) + } } impl BitOr for VType { type Output = Self; @@ -202,6 +218,18 @@ impl BitOr for VType { Self { types } } } +impl BitOr<&VType> for VType { + type Output = Self; + fn bitor(self, rhs: &Self) -> Self::Output { + let mut types = self.types; + for t in &rhs.types { + if !types.contains(t) { + types.push(t.clone()) + } + } + Self { types } + } +} #[derive(Clone, Debug, PartialEq, Eq)] pub enum VSingleType { @@ -211,11 +239,14 @@ pub enum VSingleType { String, Tuple(Vec), List(VType), - Function(Vec, VType), + Function(Vec<(Vec, VType)>), Thread(VType), Reference(Box), } impl VSingleType { + pub fn to(self) -> VType { + VType { types: vec![self] } + } pub fn inner_types(&self) -> Vec { match self { Self::Tuple(v) => { @@ -252,11 +283,20 @@ impl VSingleType { (Self::Tuple(_), _) => false, (Self::List(a), Self::List(b)) => a.fits_in(b).is_empty(), (Self::List(_), _) => false, - (Self::Function(ai, ao), Self::Function(bi, bo)) => { - ai.iter() - .zip(bi.iter()) - .all(|(a, b)| a.fits_in(b).is_empty()) - && ao.fits_in(bo).is_empty() + (Self::Function(a), Self::Function(b)) => 'func_out: { + for a in a { + 'search: { + for b in b { + if a.1.fits_in(&b.1).is_empty() + && a.0.iter().zip(b.0.iter()).all(|(a, b)| *a == *b) + { + break 'search; + } + } + break 'func_out false; + } + } + true } (Self::Function(..), _) => false, (Self::Thread(a), Self::Thread(b)) => a.fits_in(b).is_empty(),