diff --git a/src/built_in_functions/mod.rs b/src/built_in_functions/mod.rs index 2f60f94..9832a4b 100644 --- a/src/built_in_functions/mod.rs +++ b/src/built_in_functions/mod.rs @@ -7,7 +7,7 @@ use std::fmt::{self, Display, Formatter}; use rand::{random, thread_rng, Rng}; use serde::{Deserialize, Serialize}; -use crate::{error::RuntimeError, Context, Format, Type, Value}; +use crate::{error::RuntimeError, Context, EnumInstance, Format, Identifier, Type, Value}; use self::{fs::Fs, json::Json, str::StrFunction}; @@ -70,7 +70,10 @@ impl Callable for BuiltInFunction { fn r#type(&self) -> Type { match self { - BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), + BuiltInFunction::AssertEqual => Type::function( + vec![Type::Any, Type::Any], + Type::Custom(Identifier::new("Result")), + ), BuiltInFunction::Fs(fs_function) => fs_function.r#type(), BuiltInFunction::Json(json_function) => json_function.r#type(), BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer), @@ -93,10 +96,24 @@ impl Callable for BuiltInFunction { BuiltInFunction::AssertEqual => { RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?; - let left = arguments.first().unwrap(); + let left = arguments.get(0).unwrap(); let right = arguments.get(1).unwrap(); - Ok(Value::Boolean(left == right)) + let result = if left == right { + Value::Enum(EnumInstance::new( + "Result".to_string(), + "Ok".to_string(), + Value::none(), + )) + } else { + Value::Enum(EnumInstance::new( + "Result".to_string(), + "Error".to_string(), + Value::none(), + )) + }; + + Ok(result) } BuiltInFunction::Fs(fs_function) => { fs_function.call(arguments, _source, _outer_context) diff --git a/src/context.rs b/src/context.rs index bc03701..5614de9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,3 +1,32 @@ +//! An execution context that store variables and type data during the +//! [Interpreter][crate::Interpreter]'s abstraction and execution process. +//! +//! ## Setting values +//! +//! When data is stored in a context, it can be accessed by dust source code. +//! This allows you to insert values and type definitions before any code is +//! interpreted. +//! +//! ``` +//! # use dust_lang::*; +//! let context = Context::new(); +//! +//! context.set_value( +//! "foobar".to_string(), +//! Value::String("FOOBAR".to_string()) +//! ).unwrap(); +//! +//! interpret_with_context("output foobar", context); +//! +//! // Stdout: "FOOBAR" +//! ``` +//! +//! ## Built-in values and type definitions +//! +//! When looking up values and definitions, the Context will try to use one that +//! has been explicitly set. If nothing is found, it will then check the built- +//! in values and type definitions for a match. This means that the user can +//! override the built-ins. use std::{ cmp::Ordering, collections::BTreeMap, @@ -9,22 +38,29 @@ use crate::{ error::rw_lock_error::RwLockError, Type, TypeDefinition, Value, }; +/// An execution context that variable and type data during the [Interpreter]'s +/// abstraction and execution process. +/// +/// See the [module-level docs][self] for more info. #[derive(Clone, Debug)] pub struct Context { inner: Arc>>, } impl Context { + /// Return a new, empty Context. pub fn new() -> Self { Self { inner: Arc::new(RwLock::new(BTreeMap::new())), } } + /// Return a lock guard to the inner BTreeMap. pub fn inner(&self) -> Result>, RwLockError> { Ok(self.inner.read()?) } + /// Create a new context with all of the data from an existing context. pub fn with_variables_from(other: &Context) -> Result { let mut new_variables = BTreeMap::new(); @@ -37,6 +73,25 @@ impl Context { }) } + /// Modify a context to take on all of the key-value pairs of another. + /// + /// In the case of the conflict, the inherited value will override the previous + /// value. + /// + /// ``` + /// # use dust_lang::*; + /// let first_context = Context::new(); + /// let second_context = Context::new(); + /// + /// second_context.set_value( + /// "Foo".to_string(), + /// Value::String("Bar".to_string()) + /// ); + /// + /// first_context.inherit_from(&second_context).unwrap(); + /// + /// assert_eq!(first_context, second_context); + /// ``` pub fn inherit_from(&self, other: &Context) -> Result<(), RwLockError> { let mut self_variables = self.inner.write()?; @@ -53,6 +108,10 @@ impl Context { Ok(()) } + /// Get a value from the context. + /// + /// This will also return a built-in value if one matches the key. See the + /// [module-level docs][self] for more info. pub fn get_value(&self, key: &str) -> Result, RwLockError> { if let Some(value_data) = self.inner.read()?.get(key) { if let ValueData::Value { inner, .. } = value_data { @@ -69,18 +128,32 @@ impl Context { Ok(None) } + /// Get a type from the context. + /// + /// If the key matches a stored value, its type will be returned. It if + /// matches a type hint, the type hint will be returned. pub fn get_type(&self, key: &str) -> Result, RwLockError> { if let Some(value_data) = self.inner.read()?.get(key) { match value_data { - ValueData::Value { inner, .. } => Ok(Some(inner.r#type())), - ValueData::ExpectedType { inner, .. } => Ok(Some(inner.clone())), + ValueData::Value { inner, .. } => return Ok(Some(inner.r#type())), + ValueData::TypeHint { inner, .. } => return Ok(Some(inner.clone())), ValueData::TypeDefinition(_) => todo!(), } - } else { - Ok(None) } + + for built_in_value in all_built_in_values() { + if key == built_in_value.name() { + return Ok(Some(built_in_value.r#type())); + } + } + + Ok(None) } + /// Get a value from the context. + /// + /// This will also return a built-in type definition if one matches the key. + /// See the [module-level docs][self] for more info. pub fn get_definition(&self, key: &str) -> Result, RwLockError> { if let Some(value_data) = self.inner.read()?.get(key) { if let ValueData::TypeDefinition(definition) = value_data { @@ -97,6 +170,7 @@ impl Context { Ok(None) } + /// Set a value to a key. pub fn set_value(&self, key: String, value: Value) -> Result<(), RwLockError> { self.inner.write()?.insert( key, @@ -109,14 +183,22 @@ impl Context { Ok(()) } + /// Set a type hint. + /// + /// This allows the interpreter to check a value's type before the value + /// actually exists by predicting what the abstract tree will produce. pub fn set_type(&self, key: String, r#type: Type) -> Result<(), RwLockError> { self.inner .write()? - .insert(key, ValueData::ExpectedType { inner: r#type }); + .insert(key, ValueData::TypeHint { inner: r#type }); Ok(()) } + /// Set a type definition. + /// + /// This allows defined types (i.e. structs and enums) to be instantiated + /// later while using this context. pub fn set_definition( &self, key: String, @@ -129,6 +211,7 @@ impl Context { Ok(()) } + /// Remove a key-value pair. pub fn unset(&self, key: &str) -> Result<(), RwLockError> { self.inner.write()?.remove(key); @@ -186,7 +269,7 @@ pub enum ValueData { inner: Value, runtime_uses: Arc>, }, - ExpectedType { + TypeHint { inner: Type, }, TypeDefinition(TypeDefinition), @@ -214,8 +297,8 @@ impl PartialEq for ValueData { } } ( - ValueData::ExpectedType { inner: left_inner }, - ValueData::ExpectedType { inner: right_inner }, + ValueData::TypeHint { inner: left_inner }, + ValueData::TypeHint { inner: right_inner }, ) => left_inner == right_inner, _ => false, } @@ -243,12 +326,12 @@ impl Ord for ValueData { ) => inner_left.cmp(inner_right), (ValueData::Value { .. }, _) => Greater, ( - ValueData::ExpectedType { inner: inner_left }, - ValueData::ExpectedType { inner: inner_right }, + ValueData::TypeHint { inner: inner_left }, + ValueData::TypeHint { inner: inner_right }, ) => inner_left.cmp(inner_right), (ValueData::TypeDefinition(left), ValueData::TypeDefinition(right)) => left.cmp(right), (ValueData::TypeDefinition(_), _) => Greater, - (ValueData::ExpectedType { .. }, _) => Less, + (ValueData::TypeHint { .. }, _) => Less, } } } diff --git a/src/main.rs b/src/main.rs index 2b89dcd..0329543 100644 --- a/src/main.rs +++ b/src/main.rs @@ -311,7 +311,7 @@ impl Completer for DustCompleter { for (key, value_data) in self.context.inner().unwrap().iter() { let value = match value_data { ValueData::Value { inner, .. } => inner, - ValueData::ExpectedType { .. } => continue, + ValueData::TypeHint { .. } => continue, ValueData::TypeDefinition(_) => continue, }; diff --git a/tests/built_in_values.rs b/tests/built_in_values.rs index 1ceff66..9da65b2 100644 --- a/tests/built_in_values.rs +++ b/tests/built_in_values.rs @@ -4,3 +4,29 @@ use dust_lang::*; fn args() { assert!(interpret("args").is_ok_and(|value| value.is_list())); } + +#[test] +fn assert_equal() { + assert_eq!( + interpret("assert_equal"), + Ok(Value::Function(Function::BuiltIn( + BuiltInFunction::AssertEqual + ))) + ); + assert_eq!( + interpret("assert_equal(false, false)"), + Ok(Value::Enum(EnumInstance::new( + "Result".to_string(), + "Ok".to_string(), + Value::none() + ))) + ); + assert_eq!( + interpret("assert_equal(true, false)"), + Ok(Value::Enum(EnumInstance::new( + "Result".to_string(), + "Error".to_string(), + Value::none() + ))) + ); +}