From 25a9a772e64c776ff259d0063fb0296cae0bc4f9 Mon Sep 17 00:00:00 2001 From: Julius de Jeu Date: Sun, 3 Jul 2022 20:32:31 +0200 Subject: [PATCH] Add custom types. Make an attempt at method calls. You just need to implement `CustomValue`, `Debug` and `Clone` for your own types, and the type can be used in your code. Next on the list: method calls (e.g. x.y(), instead of y(x)) --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/expression.rs | 37 ++++++++++++ src/grammar.pest | 3 +- src/main.rs | 33 +++++++++- src/pest_parser.rs | 15 ++++- src/value.rs | 142 +++++++++++++++++++++++++++++++++++++++----- src/value/custom.rs | 72 ++++++++++++++++++++++ test.foo | 8 +++ 9 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 src/value/custom.rs diff --git a/Cargo.lock b/Cargo.lock index 994ba3a..2f4df9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dyn-clone" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140206b78fb2bc3edbcfc9b5ccbd0b30699cfe8d348b8b31b330e47df5291a5a" + [[package]] name = "fake-simd" version = "0.1.2" @@ -148,6 +154,7 @@ dependencies = [ name = "sunflower" version = "0.1.0" dependencies = [ + "dyn-clone", "pest", "pest_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 1957222..e490796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dyn-clone = "1.0.6" pest = "2.1.3" pest_derive = "2.1.0" diff --git a/src/expression.rs b/src/expression.rs index 8da50ab..3e3aef7 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -43,6 +43,7 @@ pub enum Expression { Constant(Value), Variable(String), Call(String, Vec), + CallMethod(Box, String, Vec), Binary(Box, BinaryOperation, Box), } @@ -58,6 +59,17 @@ impl Expression { &args.iter().map(|ex| ex.eval(e, f)).collect::>(), ) .unwrap(), + Expression::CallMethod(target, name, args) => { + let target = target.eval(e, f); + let v = vec![target.clone()]; + let args = v + .iter() + .cloned() + .chain(args.iter().map(|ex| ex.eval(e, f))) + .collect::>(); + + target.call_method(name, e, f, &args) + } Expression::Binary(lhs, op, rhs) => { let lhs = lhs.eval(e, f); let rhs = rhs.eval(e, f); @@ -179,4 +191,29 @@ mod tests { Value::Int(20) ); } + + #[test] + fn eq_method_test() { + let f = FunctionMap::new(); + let e = Env::new(); + e.set("test", 10); + assert_eq!( + Expression::CallMethod( + Box::new(Expression::Variable("test".to_string())), + "eq".to_string(), + vec![Expression::Constant(Value::Int(10))] + ) + .eval(&e, &f), + Value::Bool(true) + ); + assert_eq!( + Expression::CallMethod( + Box::new(Expression::Variable("test".to_string())), + "eq".to_string(), + vec![Expression::Constant(Value::Int(11))] + ) + .eval(&e, &f), + Value::Bool(false) + ); + } } diff --git a/src/grammar.pest b/src/grammar.pest index 7aa27d1..6be1930 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -1,4 +1,5 @@ WHITESPACE = _{ " " | "\t" | NEWLINE } +COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" | "//" ~ (!NEWLINE ~ ANY)* } number = @{ "-"? @@ -21,7 +22,7 @@ char = ${ "\'" ~ character ~ "\'" } boolean = { "true" | "false" } -identifier = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } +identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" )* } call = ${ identifier ~ call_params diff --git a/src/main.rs b/src/main.rs index 8b594c1..eada698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use std::fs::read_to_string; +use std::{any::Any, fs::read_to_string}; use env::Env; use function::FunctionMap; use statement::Statement; -use value::Value; +use value::{custom::CustomValue, Value}; mod env; mod expression; @@ -20,6 +20,29 @@ fn run(functions: FunctionMap, statement: Statement) { statement.eval(&env, &functions); } +#[derive(Clone, Debug, PartialEq, Eq)] +struct Foo { + x: i32, +} + +impl CustomValue for Foo { + fn as_any(&self) -> &dyn Any { + self + } + + fn format(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Foo({})", self.x) + } + + fn eq(&self, other: &Value) -> bool { + if let Some(v) = other.as_t() { + self == v + } else { + false + } + } +} + fn main() { let f = read_to_string("./test.foo").unwrap(); let (res, funcs) = pest_parser::parse(f); @@ -29,5 +52,11 @@ fn main() { println!("{}", a); }); m.insert_native("waluigi", || "Waluigi"); + m.insert_native("native_test", || Foo { x: 41 }); + m.insert_native("edit_foo", |f: &Foo| { + let mut f = f.clone(); + f.x += 1; + f + }); run(m, res); } diff --git a/src/pest_parser.rs b/src/pest_parser.rs index e3d7d9c..7075d93 100644 --- a/src/pest_parser.rs +++ b/src/pest_parser.rs @@ -138,7 +138,20 @@ fn handle_rules(mut p: pest::iterators::Pairs) -> (Statement, FuncHolder) match pair.as_rule() { Rule::expression => { let mut inner = pair.into_inner(); - parse_expression(inner.next().unwrap()) + let expr = parse_expression(inner.next().unwrap()); + if let Some(c) = inner.next() { + let mut inner = c.into_inner(); + let name = inner.next().unwrap(); + let args = inner + .next() + .unwrap() + .into_inner() + .map(parse_expression) + .collect(); + Expression::CallMethod(Box::new(expr), name.as_str().to_string(), args) + } else { + expr + } } Rule::equality => { let mut inner = pair.into_inner(); diff --git a/src/value.rs b/src/value.rs index 3271601..1277f94 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,15 +1,24 @@ use std::{ + any::Any, + cmp::Ordering, fmt::Display, - ops::{Add, Div, Mul, Sub, Rem}, + ops::{Add, Div, Mul, Rem, Sub}, }; -#[derive(Debug, Clone, PartialEq, Default)] +use crate::{env::Env, function::FunctionMap}; + +use self::custom::CustomValue; + +pub mod custom; + +#[derive(Debug, Clone, Default)] pub enum Value { Int(i32), Float(f32), String(String), Bool(bool), Char(char), + Custom(Box), #[default] Nothing, Error, @@ -25,22 +34,84 @@ impl Value { Value::String(v) => !v.is_empty(), Value::Bool(v) => *v, Value::Char(v) => *v != '\0', + Value::Custom(_) => false, Value::Nothing => false, Value::Error => false, } } - pub fn value_kind(&self) -> &'static str { + pub fn value_kind(&self) -> &str { match self { Value::Int(_) => "int", Value::Float(_) => "float", Value::String(_) => "string", Value::Bool(_) => "bool", Value::Char(_) => "char", + Value::Custom(c) => c.value_kind(), Value::Nothing => "nothing", Value::Error => "error", } } + + pub fn as_any(&self) -> &dyn Any { + match self { + Value::Int(i) => i, + Value::Float(f) => f, + Value::String(s) => s, + Value::Bool(b) => b, + Value::Char(c) => c, + Value::Custom(c) => c.as_any(), + Value::Nothing => &() as &dyn Any, + Value::Error => &() as &dyn Any, + } + } + + pub fn as_t(&self) -> Option<&T> + where + T: Any + 'static, + { + self.as_any().downcast_ref::() + } + + pub fn value_functions() -> FunctionMap { + let mut map = FunctionMap::new(); + map.insert_native("eq", Self::eq); + map.insert_native("ne", Self::ne); + map.insert_native("lt", Self::lt); + map.insert_native("le", Self::le); + map.insert_native("gt", Self::gt); + map.insert_native("ge", Self::ge); + + map + } + + pub fn inner_functions(&self) -> Option<&FunctionMap> { + if let Self::Custom(c) = self { + c.functions() + } else { + None + } + } + + pub fn call_method( + &self, + name: &str, + env: &Env, + _global_functions: &FunctionMap, + args: &[Value], + ) -> Value { + if let Some(i) = self.inner_functions() { + match i.call(name, env, args) { + Ok(r) => return r, + Err(e) => println!("{}", e), + } + } + match Value::value_functions().call(name, env, args) { + Ok(r) => return r, + Err(e) => println!("{}", e), + } + Value::Nothing + } } macro_rules! math_trait { @@ -66,19 +137,24 @@ math_trait!(Rem, rem); impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Value::Int(v) => v.to_string(), - Value::Float(v) => v.to_string(), - Value::String(v) => v.clone(), - Value::Bool(v) => v.to_string(), - Value::Char(v) => v.to_string(), - Value::Nothing => "()".to_string(), - Value::Error => "Error".to_string(), - } - ) + if let Value::Custom(c) = self { + c.format(f) + } else { + write!( + f, + "{}", + match self { + Value::Int(v) => v.to_string(), + Value::Float(v) => v.to_string(), + Value::String(v) => v.clone(), + Value::Bool(v) => v.to_string(), + Value::Char(v) => v.to_string(), + Value::Nothing => "()".to_string(), + Value::Error => "Error".to_string(), + _ => unreachable!(), + } + ) + } } } @@ -119,6 +195,21 @@ impl FromValue for Value { } } +impl FromValue for T { + fn from_value(v: &Value) -> Option<&Self> { + match v { + Value::Custom(v) => v.as_any().downcast_ref(), + _ => None, + } + } +} + +impl From for Value { + fn from(v: T) -> Self { + Value::Custom(v.clone_box()) + } +} + from_impl!(i32 => Int, f32 => Float, String => String, bool => Bool, char => Char); impl From<&'_ str> for Value { @@ -136,6 +227,23 @@ impl FromValue for str { } } +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Int(lhs), Value::Int(rhs)) => lhs == rhs, + (Value::Float(lhs), Value::Float(rhs)) => lhs == rhs, + (Value::String(lhs), Value::String(rhs)) => lhs == rhs, + (Value::Bool(lhs), Value::Bool(rhs)) => lhs == rhs, + (Value::Char(lhs), Value::Char(rhs)) => lhs == rhs, + (Value::Custom(lhs), other) => lhs.as_ref() == other, + (other, Value::Custom(rhs)) => rhs.as_ref() == other, + (Value::Nothing, Value::Nothing) => true, + (Value::Error, Value::Error) => true, + _ => false, + } + } +} + impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { @@ -147,6 +255,8 @@ impl PartialOrd for Value { (Value::Nothing, Value::Nothing) => Some(std::cmp::Ordering::Equal), (Value::Int(lhs), Value::Float(rhs)) => (*lhs as f32).partial_cmp(rhs), (Value::Float(lhs), Value::Int(rhs)) => lhs.partial_cmp(&(*rhs as f32)), + (Value::Custom(lhs), other) => lhs.as_ref().partial_cmp(other), + (other, Value::Custom(rhs)) => rhs.as_ref().partial_cmp(other).map(Ordering::reverse), _ => None, } } diff --git a/src/value/custom.rs b/src/value/custom.rs new file mode 100644 index 0000000..2a0b704 --- /dev/null +++ b/src/value/custom.rs @@ -0,0 +1,72 @@ +use crate::function::FunctionMap; + +use super::Value; + +fn type_name_of_val(_val: &T) -> &'static str { + std::any::type_name::() +} + +pub trait CustomValue: std::fmt::Debug + BoxClone { + /// Cursed workaround. Since every type has to implement this + /// method, we can get a handle back to the type. + fn as_any(&self) -> &dyn std::any::Any; + + fn value_kind(&self) -> &'static str { + type_name_of_val(self) + } + + fn format(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } + + fn eq(&self, _: &Value) -> bool { + false + } + + fn ne(&self, other: &Value) -> bool { + !self.eq(other) + } + + fn partial_cmp(&self, _: &Value) -> Option { + None + } + + fn functions(&self) -> Option<&FunctionMap> { + None + } +} + +impl PartialEq for dyn CustomValue { + fn eq(&self, other: &Value) -> bool { + self.eq(other) + } + + fn ne(&self, other: &Value) -> bool { + self.ne(other) + } +} + +impl PartialOrd for dyn CustomValue { + fn partial_cmp(&self, other: &Value) -> Option { + self.partial_cmp(other) + } +} + +pub trait BoxClone { + fn clone_box(&self) -> Box; +} + +impl BoxClone for T +where + T: 'static + CustomValue + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} diff --git a/test.foo b/test.foo index 895d1e5..a612bba 100644 --- a/test.foo +++ b/test.foo @@ -9,3 +9,11 @@ let x = 10; let y = x-5; print(y); +print( edit_foo(native_test()) ); + +// Parameter 0 was not of type sunflower::Foo, it was string +// fancy error isn't it? +// edit_foo("hello"); + +// this doesnt work yet :(((( +// print(10.eq(20)); \ No newline at end of file