From ac6b405a3cd92edfffa6108b836ed781e21d100d Mon Sep 17 00:00:00 2001 From: Mark <> Date: Fri, 7 Feb 2025 21:04:17 +0100 Subject: [PATCH] make `:` syntax an associated function call --- mers/Cargo.toml | 4 +- mers/README.md | 1 + mers_lib/Cargo.toml | 2 +- mers_lib/src/parsing/statements.rs | 19 +- mers_lib/src/program/parsed/field_chain.rs | 58 +++++++ mers_lib/src/program/parsed/mod.rs | 2 + mers_lib/src/program/run/chain.rs | 191 ++++++++++++--------- mers_lib/src/program/run/field_chain.rs | 159 +++++++++++++++++ mers_lib/src/program/run/mod.rs | 2 + 9 files changed, 354 insertions(+), 84 deletions(-) create mode 100644 mers_lib/src/program/parsed/field_chain.rs create mode 100644 mers_lib/src/program/run/field_chain.rs diff --git a/mers/Cargo.toml b/mers/Cargo.toml index 73908a1..95377cc 100644 --- a/mers/Cargo.toml +++ b/mers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mers" -version = "0.9.21" +version = "0.9.22" edition = "2021" license = "MIT OR Apache-2.0" description = "dynamically typed but type-checked programming language" @@ -15,7 +15,7 @@ default = ["colored-output"] colored-output = ["mers_lib/ecolor-term", "mers_lib/pretty-print", "dep:colored"] [dependencies] -mers_lib = "0.9.21" +mers_lib = "0.9.22" # mers_lib = { path = "../mers_lib" } clap = { version = "4.3.19", features = ["derive"] } colored = { version = "2.1.0", optional = true } diff --git a/mers/README.md b/mers/README.md index bed7168..a5ac0ec 100644 --- a/mers/README.md +++ b/mers/README.md @@ -96,6 +96,7 @@ mers only has a few different expressions: - blocks: `{ a, b, c }` - functions: `arg -> expression` - function calls: `arg.func` or `a.func(b, c)`, which becomes `(a, b, c).func` +- associated function calls on objects: `obj:func` or `obj:func(b, c)`, as above - `obj` must contain a function `func` - `if condition expression` and `if condition expression_1 else expression_2` - `loop expression` - type hints `[Int] 5` diff --git a/mers_lib/Cargo.toml b/mers_lib/Cargo.toml index 9150e4c..d35c356 100755 --- a/mers_lib/Cargo.toml +++ b/mers_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mers_lib" -version = "0.9.21" +version = "0.9.22" edition = "2021" license = "MIT OR Apache-2.0" description = "library to use the mers language in other projects" diff --git a/mers_lib/src/parsing/statements.rs b/mers_lib/src/parsing/statements.rs index 495a9df..a544754 100755 --- a/mers_lib/src/parsing/statements.rs +++ b/mers_lib/src/parsing/statements.rs @@ -220,11 +220,26 @@ pub fn parse( } } else if let Some(':') = src.peek_char() { src.next_char(); + let first_start = first.source_range().start(); + let field_start = src.get_pos(); let field = src.next_word().to_owned(); - first = Box::new(program::parsed::field::Field { - pos_in_src: (first.source_range().start(), src.get_pos(), srca).into(), + let field_end = src.get_pos(); + // allow a.f(b, c) syntax (but not f(a, b, c)) + let args = if let Some('(') = src.peek_char() { + src.next_char(); + Some(( + parse_multiple(src, srca, ")")?, + (first_start, src.get_pos(), srca).into(), + )) + } else { + None + }; + first = Box::new(program::parsed::field_chain::FieldChain { + pos_in_src: (first_start, src.get_pos(), srca).into(), object: first, + args, field, + field_pos: (field_start, field_end, srca).into(), }); pos_after_first = src.get_pos(); } else { diff --git a/mers_lib/src/program/parsed/field_chain.rs b/mers_lib/src/program/parsed/field_chain.rs new file mode 100644 index 0000000..5196ca0 --- /dev/null +++ b/mers_lib/src/program/parsed/field_chain.rs @@ -0,0 +1,58 @@ +use crate::{ + data::object::ObjectFieldsMap, + errors::{CheckError, SourceRange}, + info, program, +}; + +use super::{CompInfo, MersStatement}; + +/// Extracts a property with the given name from the object +#[derive(Debug)] +pub struct FieldChain { + pub pos_in_src: SourceRange, + pub object: Box, + pub args: Option<(Vec>, SourceRange)>, + pub field: String, + pub field_pos: SourceRange, +} +impl MersStatement for FieldChain { + fn has_scope(&self) -> bool { + false + } + fn compile_custom( + &self, + info: &mut info::Info, + comp: CompInfo, + ) -> Result, CheckError> { + Ok(Box::new(program::run::field_chain::FieldChain { + pos_in_src: self.pos_in_src.clone(), + object: self.object.compile(info, comp)?, + args: if let Some((args, pos)) = &self.args { + Some(( + args.iter() + .map(|arg| arg.compile(info, comp)) + .collect::, _>>()?, + pos.clone(), + )) + } else { + None + }, + field_str: self.field.clone(), + field_pos: self.field_pos.clone(), + field: info.global.object_fields.get_or_add_field(&self.field), + })) + } + fn source_range(&self) -> SourceRange { + self.pos_in_src.clone() + } + fn inner_statements(&self) -> Vec<&dyn MersStatement> { + let mut o = vec![&*self.object]; + if let Some((args, _)) = &self.args { + o.extend(args.iter().map(|v| &**v)); + } + o + } + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/mers_lib/src/program/parsed/mod.rs b/mers_lib/src/program/parsed/mod.rs index bfb54ff..0d6cb7f 100755 --- a/mers_lib/src/program/parsed/mod.rs +++ b/mers_lib/src/program/parsed/mod.rs @@ -22,6 +22,8 @@ pub mod custom_type; #[cfg(feature = "parse")] pub mod field; #[cfg(feature = "parse")] +pub mod field_chain; +#[cfg(feature = "parse")] pub mod function; #[cfg(feature = "parse")] pub mod r#if; diff --git a/mers_lib/src/program/run/chain.rs b/mers_lib/src/program/run/chain.rs index 7ebf745..c03f10c 100755 --- a/mers_lib/src/program/run/chain.rs +++ b/mers_lib/src/program/run/chain.rs @@ -29,89 +29,28 @@ impl MersStatement for Chain { let arg = self.first.check(info, None)?; let func = self.chained.check(info, None)?; info.global.enable_hooks = prev_enable_hooks; - let mut o = Type::empty(); - for func in &func.types { - if let Some(func) = func.executable() { - match func.o(&arg) { - Ok(t) => o.add_all(&t), - Err(e) => { - return Err(if let Some(_) = &self.as_part_of_include { - CheckError::new() - .src(vec![( - self.pos_in_src.clone(), - Some(EColor::HashIncludeErrorInIncludedFile), - )]) - .msg(vec![( - "Error in #include:".to_owned(), - Some(EColor::HashIncludeErrorInIncludedFile), - )]) - .err_with_diff_src(e) - } else { - CheckError::new() - .src(vec![ - (self.pos_in_src.clone(), None), - (self.first.source_range(), Some(EColor::FunctionArgument)), - (self.chained.source_range(), Some(EColor::Function)), - ]) - .msg(vec![ - ("Can't call ".to_owned(), None), - ("this function".to_owned(), Some(EColor::Function)), - (" with an argument of type ".to_owned(), None), - ( - arg.simplified_as_string(info), - Some(EColor::FunctionArgument), - ), - (":".to_owned(), None), - ]) - .err(e) - }) - } - } - } else { - return Err(CheckError::new() - .src(vec![ - (self.pos_in_src.clone(), None), - ( - self.chained.source_range(), - Some(EColor::ChainWithNonFunction), - ), - ]) - .msg(vec![ - ("cannot chain with a non-function (".to_owned(), None), - ( - func.simplified_as_string(info), - Some(EColor::ChainWithNonFunction), - ), - (")".to_owned(), None), - ])); - } - } - Ok(o) + check( + &arg, + &func, + info, + self.pos_in_src.clone(), + self.first.source_range(), + self.chained.source_range(), + self.as_part_of_include.as_ref(), + ) } fn run_custom(&self, info: &mut super::Info) -> Result { let f = self.first.run(info)?; let c = self.chained.run(info)?; - let c = c.get(); - match c.execute(f, &info.global) { - Some(Ok(v)) => Ok(v), - Some(Err(e)) => Err(if let Some(_) = &self.as_part_of_include { - CheckError::new().err_with_diff_src(e).src(vec![( - self.pos_in_src.clone(), - Some(EColor::StacktraceDescendHashInclude), - )]) - } else { - CheckError::new().err(e).src(vec![ - (self.pos_in_src.clone(), None), - (self.source_range(), Some(EColor::StacktraceDescend)), - ]) - }), - None => Err(CheckError::new() - .msg_str("tried to chain with non-function".to_owned()) - .src(vec![( - self.chained.source_range(), - Some(EColor::ChainWithNonFunction), - )])), - } + run( + f, + c, + info, + self.pos_in_src.clone(), + self.first.source_range(), + self.chained.source_range(), + self.as_part_of_include.as_ref(), + ) } fn has_scope(&self) -> bool { false @@ -126,3 +65,97 @@ impl MersStatement for Chain { self } } + +pub fn check( + arg: &Type, + func: &Type, + info: &mut super::CheckInfo, + pos_in_src: SourceRange, + arg_pos: SourceRange, + func_pos: SourceRange, + as_part_of_include: Option<&Source>, +) -> Result { + let mut o = Type::empty(); + for func in &func.types { + if let Some(func) = func.executable() { + match func.o(&arg) { + Ok(t) => o.add_all(&t), + Err(e) => { + return Err(if let Some(_) = as_part_of_include { + CheckError::new() + .src(vec![( + pos_in_src.clone(), + Some(EColor::HashIncludeErrorInIncludedFile), + )]) + .msg(vec![( + "Error in #include:".to_owned(), + Some(EColor::HashIncludeErrorInIncludedFile), + )]) + .err_with_diff_src(e) + } else { + CheckError::new() + .src(vec![ + (pos_in_src.clone(), None), + (arg_pos, Some(EColor::FunctionArgument)), + (func_pos, Some(EColor::Function)), + ]) + .msg(vec![ + ("Can't call ".to_owned(), None), + ("this function".to_owned(), Some(EColor::Function)), + (" with an argument of type ".to_owned(), None), + ( + arg.simplified_as_string(info), + Some(EColor::FunctionArgument), + ), + (":".to_owned(), None), + ]) + .err(e) + }) + } + } + } else { + return Err(CheckError::new() + .src(vec![ + (pos_in_src, None), + (func_pos, Some(EColor::ChainWithNonFunction)), + ]) + .msg(vec![ + ("cannot chain with a non-function (".to_owned(), None), + ( + func.simplified_as_string(info), + Some(EColor::ChainWithNonFunction), + ), + (")".to_owned(), None), + ])); + } + } + Ok(o) +} + +pub fn run( + arg: Data, + func: Data, + info: &mut super::Info, + pos_in_src: SourceRange, + _arg_pos: SourceRange, + func_pos: SourceRange, + as_part_of_include: Option<&Source>, +) -> Result { + let func = func.get(); + match func.execute(arg, &info.global) { + Some(Ok(v)) => Ok(v), + Some(Err(e)) => Err(if let Some(_) = &as_part_of_include { + CheckError::new().err_with_diff_src(e).src(vec![( + pos_in_src.clone(), + Some(EColor::StacktraceDescendHashInclude), + )]) + } else { + CheckError::new() + .err(e) + .src(vec![(pos_in_src.clone(), Some(EColor::StacktraceDescend))]) + }), + None => Err(CheckError::new() + .msg_str("tried to chain with non-function".to_owned()) + .src(vec![(func_pos, Some(EColor::ChainWithNonFunction))])), + } +} diff --git a/mers_lib/src/program/run/field_chain.rs b/mers_lib/src/program/run/field_chain.rs new file mode 100644 index 0000000..e3930b1 --- /dev/null +++ b/mers_lib/src/program/run/field_chain.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; + +use crate::{ + data::{ + self, + object::ObjectT, + tuple::{Tuple, TupleT}, + Data, MersDataWInfo, MersTypeWInfo, Type, + }, + errors::{CheckError, EColor, SourceRange}, +}; + +use super::MersStatement; + +#[derive(Debug)] +pub struct FieldChain { + pub pos_in_src: SourceRange, + pub object: Box, + pub args: Option<(Vec>, SourceRange)>, + pub field_str: String, + pub field_pos: SourceRange, + pub field: usize, +} +impl MersStatement for FieldChain { + fn check_custom( + &self, + info: &mut super::CheckInfo, + init_to: Option<&Type>, + ) -> Result { + if init_to.is_some() { + return Err("can't init to statement type Field".to_string().into()); + } + let object = self.object.check(info, init_to)?; + let mut o = Type::empty(); + for arg in object.types.iter() { + let iter; + let iter = if let Some(t) = arg.is_reference_to() { + t.types.iter() + } else { + iter = vec![Arc::clone(arg)]; + iter.iter() + }; + let arg = Type::newm(vec![Arc::clone(arg)]); + let arg = if let Some((more_args, _)) = &self.args { + let mut args = vec![arg]; + for res in more_args.iter().map(|arg| arg.check(info, None)) { + args.push(res?); + } + Type::new(TupleT(args)) + } else { + arg + }; + for t in iter { + if let Some(t) = t.as_any().downcast_ref::() { + if let Some(func) = t.get(self.field) { + o.add_all(&super::chain::check( + &arg, + func, + info, + self.pos_in_src.clone(), + self.object.source_range(), + self.field_pos.clone(), + None, + )?); + } else { + return Err(CheckError::new().msg(vec![ + ("can't get field ".to_owned(), None), + (self.field_str.clone(), Some(EColor::ObjectField)), + (" of object ".to_owned(), None), + (t.with_info(info).to_string(), Some(EColor::InitFrom)), + ])); + } + } else { + return Err(CheckError::new().msg(vec![ + ("can't get field ".to_owned(), None), + (self.field_str.clone(), Some(EColor::ObjectField)), + (" of non-object type ".to_owned(), None), + (arg.with_info(info).to_string(), Some(EColor::InitFrom)), + ])); + } + } + } + Ok(o) + } + fn run_custom(&self, info: &mut super::Info) -> Result { + let object = self.object.run(info)?; + let func = { + let object_lock = object.get(); + let obj_ref; + let obj_in_ref; + let object = if let Some(o) = + if let Some(o) = object_lock.as_any().downcast_ref::() { + Some(o) + } else if let Some(r) = object_lock + .as_any() + .downcast_ref::() + { + obj_ref = r.0.read().unwrap(); + obj_in_ref = obj_ref.get(); + obj_in_ref.as_any().downcast_ref::() + } else { + None + } { + o + } else { + Err(format!( + "couldn't extract field {} from non-object and non-&object value {}", + self.field_str, + object_lock.with_info(info) + ))? + }; + let func = object + .get(self.field) + .ok_or_else(|| { + format!( + "couldn't extract field {} from object {}", + self.field_str, + object.with_info(info) + ) + })? + .clone(); + func + }; + let arg = if let Some((more_args, _)) = &self.args { + let mut args = vec![object]; + for res in more_args.iter().map(|arg| arg.run(info)) { + args.push(res?); + } + Data::new(Tuple(args)) + } else { + object + }; + super::chain::run( + arg, + func, + info, + self.pos_in_src.clone(), + self.object.source_range(), + self.field_pos.clone(), + None, + ) + } + fn has_scope(&self) -> bool { + false + } + fn source_range(&self) -> SourceRange { + self.pos_in_src.clone() + } + fn inner_statements(&self) -> Vec<&dyn MersStatement> { + let mut o = vec![&*self.object]; + if let Some((args, _)) = &self.args { + o.extend(args.iter().map(|v| &**v)); + } + o + } + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/mers_lib/src/program/run/mod.rs b/mers_lib/src/program/run/mod.rs index fd54a8b..9a63a17 100755 --- a/mers_lib/src/program/run/mod.rs +++ b/mers_lib/src/program/run/mod.rs @@ -25,6 +25,8 @@ pub mod custom_type; #[cfg(feature = "run")] pub mod field; #[cfg(feature = "run")] +pub mod field_chain; +#[cfg(feature = "run")] pub mod function; #[cfg(feature = "run")] pub mod r#if;