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))
This commit is contained in:
Julius 2022-07-03 20:32:31 +02:00
parent c67cbf66e6
commit 25a9a772e6
Signed by: j00lz
GPG key ID: AF241B0AA237BBA2
9 changed files with 298 additions and 20 deletions

7
Cargo.lock generated
View file

@ -44,6 +44,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "dyn-clone"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "140206b78fb2bc3edbcfc9b5ccbd0b30699cfe8d348b8b31b330e47df5291a5a"
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -148,6 +154,7 @@ dependencies = [
name = "sunflower" name = "sunflower"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dyn-clone",
"pest", "pest",
"pest_derive", "pest_derive",
] ]

View file

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dyn-clone = "1.0.6"
pest = "2.1.3" pest = "2.1.3"
pest_derive = "2.1.0" pest_derive = "2.1.0"

View file

@ -43,6 +43,7 @@ pub enum Expression {
Constant(Value), Constant(Value),
Variable(String), Variable(String),
Call(String, Vec<Expression>), Call(String, Vec<Expression>),
CallMethod(Box<Expression>, String, Vec<Expression>),
Binary(Box<Expression>, BinaryOperation, Box<Expression>), Binary(Box<Expression>, BinaryOperation, Box<Expression>),
} }
@ -58,6 +59,17 @@ impl Expression {
&args.iter().map(|ex| ex.eval(e, f)).collect::<Vec<_>>(), &args.iter().map(|ex| ex.eval(e, f)).collect::<Vec<_>>(),
) )
.unwrap(), .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::<Vec<_>>();
target.call_method(name, e, f, &args)
}
Expression::Binary(lhs, op, rhs) => { Expression::Binary(lhs, op, rhs) => {
let lhs = lhs.eval(e, f); let lhs = lhs.eval(e, f);
let rhs = rhs.eval(e, f); let rhs = rhs.eval(e, f);
@ -179,4 +191,29 @@ mod tests {
Value::Int(20) 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)
);
}
} }

View file

@ -1,4 +1,5 @@
WHITESPACE = _{ " " | "\t" | NEWLINE } WHITESPACE = _{ " " | "\t" | NEWLINE }
COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" | "//" ~ (!NEWLINE ~ ANY)* }
number = @{ number = @{
"-"? "-"?
@ -21,7 +22,7 @@ char = ${ "\'" ~ character ~ "\'" }
boolean = { "true" | "false" } boolean = { "true" | "false" }
identifier = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" )* }
call = ${ call = ${
identifier ~ call_params identifier ~ call_params

View file

@ -1,9 +1,9 @@
use std::fs::read_to_string; use std::{any::Any, fs::read_to_string};
use env::Env; use env::Env;
use function::FunctionMap; use function::FunctionMap;
use statement::Statement; use statement::Statement;
use value::Value; use value::{custom::CustomValue, Value};
mod env; mod env;
mod expression; mod expression;
@ -20,6 +20,29 @@ fn run(functions: FunctionMap, statement: Statement) {
statement.eval(&env, &functions); 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() { fn main() {
let f = read_to_string("./test.foo").unwrap(); let f = read_to_string("./test.foo").unwrap();
let (res, funcs) = pest_parser::parse(f); let (res, funcs) = pest_parser::parse(f);
@ -29,5 +52,11 @@ fn main() {
println!("{}", a); println!("{}", a);
}); });
m.insert_native("waluigi", || "Waluigi"); 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); run(m, res);
} }

View file

@ -138,7 +138,20 @@ fn handle_rules(mut p: pest::iterators::Pairs<Rule>) -> (Statement, FuncHolder)
match pair.as_rule() { match pair.as_rule() {
Rule::expression => { Rule::expression => {
let mut inner = pair.into_inner(); 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 => { Rule::equality => {
let mut inner = pair.into_inner(); let mut inner = pair.into_inner();

View file

@ -1,15 +1,24 @@
use std::{ use std::{
any::Any,
cmp::Ordering,
fmt::Display, 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 { pub enum Value {
Int(i32), Int(i32),
Float(f32), Float(f32),
String(String), String(String),
Bool(bool), Bool(bool),
Char(char), Char(char),
Custom(Box<dyn CustomValue>),
#[default] #[default]
Nothing, Nothing,
Error, Error,
@ -25,22 +34,84 @@ impl Value {
Value::String(v) => !v.is_empty(), Value::String(v) => !v.is_empty(),
Value::Bool(v) => *v, Value::Bool(v) => *v,
Value::Char(v) => *v != '\0', Value::Char(v) => *v != '\0',
Value::Custom(_) => false,
Value::Nothing => false, Value::Nothing => false,
Value::Error => false, Value::Error => false,
} }
} }
pub fn value_kind(&self) -> &'static str { pub fn value_kind(&self) -> &str {
match self { match self {
Value::Int(_) => "int", Value::Int(_) => "int",
Value::Float(_) => "float", Value::Float(_) => "float",
Value::String(_) => "string", Value::String(_) => "string",
Value::Bool(_) => "bool", Value::Bool(_) => "bool",
Value::Char(_) => "char", Value::Char(_) => "char",
Value::Custom(c) => c.value_kind(),
Value::Nothing => "nothing", Value::Nothing => "nothing",
Value::Error => "error", 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<T>(&self) -> Option<&T>
where
T: Any + 'static,
{
self.as_any().downcast_ref::<T>()
}
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 { macro_rules! math_trait {
@ -66,6 +137,9 @@ math_trait!(Rem, rem);
impl Display for Value { impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Value::Custom(c) = self {
c.format(f)
} else {
write!( write!(
f, f,
"{}", "{}",
@ -77,10 +151,12 @@ impl Display for Value {
Value::Char(v) => v.to_string(), Value::Char(v) => v.to_string(),
Value::Nothing => "()".to_string(), Value::Nothing => "()".to_string(),
Value::Error => "Error".to_string(), Value::Error => "Error".to_string(),
_ => unreachable!(),
} }
) )
} }
} }
}
impl From<()> for Value { impl From<()> for Value {
fn from(_: ()) -> Self { fn from(_: ()) -> Self {
@ -119,6 +195,21 @@ impl FromValue for Value {
} }
} }
impl<T: 'static + CustomValue> FromValue for T {
fn from_value(v: &Value) -> Option<&Self> {
match v {
Value::Custom(v) => v.as_any().downcast_ref(),
_ => None,
}
}
}
impl<T: CustomValue> From<T> for Value {
fn from(v: T) -> Self {
Value::Custom(v.clone_box())
}
}
from_impl!(i32 => Int, f32 => Float, String => String, bool => Bool, char => Char); from_impl!(i32 => Int, f32 => Float, String => String, bool => Bool, char => Char);
impl From<&'_ str> for Value { 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 { impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) { match (self, other) {
@ -147,6 +255,8 @@ impl PartialOrd for Value {
(Value::Nothing, Value::Nothing) => Some(std::cmp::Ordering::Equal), (Value::Nothing, Value::Nothing) => Some(std::cmp::Ordering::Equal),
(Value::Int(lhs), Value::Float(rhs)) => (*lhs as f32).partial_cmp(rhs), (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::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, _ => None,
} }
} }

72
src/value/custom.rs Normal file
View file

@ -0,0 +1,72 @@
use crate::function::FunctionMap;
use super::Value;
fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
std::any::type_name::<T>()
}
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<std::cmp::Ordering> {
None
}
fn functions(&self) -> Option<&FunctionMap> {
None
}
}
impl PartialEq<Value> for dyn CustomValue {
fn eq(&self, other: &Value) -> bool {
self.eq(other)
}
fn ne(&self, other: &Value) -> bool {
self.ne(other)
}
}
impl PartialOrd<Value> for dyn CustomValue {
fn partial_cmp(&self, other: &Value) -> Option<std::cmp::Ordering> {
self.partial_cmp(other)
}
}
pub trait BoxClone {
fn clone_box(&self) -> Box<dyn CustomValue>;
}
impl<T> BoxClone for T
where
T: 'static + CustomValue + Clone,
{
fn clone_box(&self) -> Box<dyn CustomValue> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn CustomValue> {
fn clone(&self) -> Self {
self.clone_box()
}
}

View file

@ -9,3 +9,11 @@ let x = 10;
let y = x-5; let y = x-5;
print(y); 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));