Write docs
This commit is contained in:
parent
540f59e6d8
commit
d9f065fbb6
@ -7,7 +7,7 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
use rand::{random, thread_rng, Rng};
|
use rand::{random, thread_rng, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
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};
|
use self::{fs::Fs, json::Json, str::StrFunction};
|
||||||
|
|
||||||
@ -70,7 +70,10 @@ impl Callable for BuiltInFunction {
|
|||||||
|
|
||||||
fn r#type(&self) -> Type {
|
fn r#type(&self) -> Type {
|
||||||
match self {
|
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::Fs(fs_function) => fs_function.r#type(),
|
||||||
BuiltInFunction::Json(json_function) => json_function.r#type(),
|
BuiltInFunction::Json(json_function) => json_function.r#type(),
|
||||||
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
|
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
|
||||||
@ -93,10 +96,24 @@ impl Callable for BuiltInFunction {
|
|||||||
BuiltInFunction::AssertEqual => {
|
BuiltInFunction::AssertEqual => {
|
||||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
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();
|
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) => {
|
BuiltInFunction::Fs(fs_function) => {
|
||||||
fs_function.call(arguments, _source, _outer_context)
|
fs_function.call(arguments, _source, _outer_context)
|
||||||
|
107
src/context.rs
107
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::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
@ -9,22 +38,29 @@ use crate::{
|
|||||||
error::rw_lock_error::RwLockError, Type, TypeDefinition, Value,
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
inner: Arc<RwLock<BTreeMap<String, ValueData>>>,
|
inner: Arc<RwLock<BTreeMap<String, ValueData>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Return a new, empty Context.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(RwLock::new(BTreeMap::new())),
|
inner: Arc::new(RwLock::new(BTreeMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a lock guard to the inner BTreeMap.
|
||||||
pub fn inner(&self) -> Result<RwLockReadGuard<BTreeMap<String, ValueData>>, RwLockError> {
|
pub fn inner(&self) -> Result<RwLockReadGuard<BTreeMap<String, ValueData>>, RwLockError> {
|
||||||
Ok(self.inner.read()?)
|
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<Context, RwLockError> {
|
pub fn with_variables_from(other: &Context) -> Result<Context, RwLockError> {
|
||||||
let mut new_variables = BTreeMap::new();
|
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> {
|
pub fn inherit_from(&self, other: &Context) -> Result<(), RwLockError> {
|
||||||
let mut self_variables = self.inner.write()?;
|
let mut self_variables = self.inner.write()?;
|
||||||
|
|
||||||
@ -53,6 +108,10 @@ impl Context {
|
|||||||
Ok(())
|
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<Option<Value>, RwLockError> {
|
pub fn get_value(&self, key: &str) -> Result<Option<Value>, RwLockError> {
|
||||||
if let Some(value_data) = self.inner.read()?.get(key) {
|
if let Some(value_data) = self.inner.read()?.get(key) {
|
||||||
if let ValueData::Value { inner, .. } = value_data {
|
if let ValueData::Value { inner, .. } = value_data {
|
||||||
@ -69,18 +128,32 @@ impl Context {
|
|||||||
Ok(None)
|
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<Option<Type>, RwLockError> {
|
pub fn get_type(&self, key: &str) -> Result<Option<Type>, RwLockError> {
|
||||||
if let Some(value_data) = self.inner.read()?.get(key) {
|
if let Some(value_data) = self.inner.read()?.get(key) {
|
||||||
match value_data {
|
match value_data {
|
||||||
ValueData::Value { inner, .. } => Ok(Some(inner.r#type())),
|
ValueData::Value { inner, .. } => return Ok(Some(inner.r#type())),
|
||||||
ValueData::ExpectedType { inner, .. } => Ok(Some(inner.clone())),
|
ValueData::TypeHint { inner, .. } => return Ok(Some(inner.clone())),
|
||||||
ValueData::TypeDefinition(_) => todo!(),
|
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<Option<TypeDefinition>, RwLockError> {
|
pub fn get_definition(&self, key: &str) -> Result<Option<TypeDefinition>, RwLockError> {
|
||||||
if let Some(value_data) = self.inner.read()?.get(key) {
|
if let Some(value_data) = self.inner.read()?.get(key) {
|
||||||
if let ValueData::TypeDefinition(definition) = value_data {
|
if let ValueData::TypeDefinition(definition) = value_data {
|
||||||
@ -97,6 +170,7 @@ impl Context {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a value to a key.
|
||||||
pub fn set_value(&self, key: String, value: Value) -> Result<(), RwLockError> {
|
pub fn set_value(&self, key: String, value: Value) -> Result<(), RwLockError> {
|
||||||
self.inner.write()?.insert(
|
self.inner.write()?.insert(
|
||||||
key,
|
key,
|
||||||
@ -109,14 +183,22 @@ impl Context {
|
|||||||
Ok(())
|
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> {
|
pub fn set_type(&self, key: String, r#type: Type) -> Result<(), RwLockError> {
|
||||||
self.inner
|
self.inner
|
||||||
.write()?
|
.write()?
|
||||||
.insert(key, ValueData::ExpectedType { inner: r#type });
|
.insert(key, ValueData::TypeHint { inner: r#type });
|
||||||
|
|
||||||
Ok(())
|
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(
|
pub fn set_definition(
|
||||||
&self,
|
&self,
|
||||||
key: String,
|
key: String,
|
||||||
@ -129,6 +211,7 @@ impl Context {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a key-value pair.
|
||||||
pub fn unset(&self, key: &str) -> Result<(), RwLockError> {
|
pub fn unset(&self, key: &str) -> Result<(), RwLockError> {
|
||||||
self.inner.write()?.remove(key);
|
self.inner.write()?.remove(key);
|
||||||
|
|
||||||
@ -186,7 +269,7 @@ pub enum ValueData {
|
|||||||
inner: Value,
|
inner: Value,
|
||||||
runtime_uses: Arc<RwLock<u16>>,
|
runtime_uses: Arc<RwLock<u16>>,
|
||||||
},
|
},
|
||||||
ExpectedType {
|
TypeHint {
|
||||||
inner: Type,
|
inner: Type,
|
||||||
},
|
},
|
||||||
TypeDefinition(TypeDefinition),
|
TypeDefinition(TypeDefinition),
|
||||||
@ -214,8 +297,8 @@ impl PartialEq for ValueData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
ValueData::ExpectedType { inner: left_inner },
|
ValueData::TypeHint { inner: left_inner },
|
||||||
ValueData::ExpectedType { inner: right_inner },
|
ValueData::TypeHint { inner: right_inner },
|
||||||
) => left_inner == right_inner,
|
) => left_inner == right_inner,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -243,12 +326,12 @@ impl Ord for ValueData {
|
|||||||
) => inner_left.cmp(inner_right),
|
) => inner_left.cmp(inner_right),
|
||||||
(ValueData::Value { .. }, _) => Greater,
|
(ValueData::Value { .. }, _) => Greater,
|
||||||
(
|
(
|
||||||
ValueData::ExpectedType { inner: inner_left },
|
ValueData::TypeHint { inner: inner_left },
|
||||||
ValueData::ExpectedType { inner: inner_right },
|
ValueData::TypeHint { inner: inner_right },
|
||||||
) => inner_left.cmp(inner_right),
|
) => inner_left.cmp(inner_right),
|
||||||
(ValueData::TypeDefinition(left), ValueData::TypeDefinition(right)) => left.cmp(right),
|
(ValueData::TypeDefinition(left), ValueData::TypeDefinition(right)) => left.cmp(right),
|
||||||
(ValueData::TypeDefinition(_), _) => Greater,
|
(ValueData::TypeDefinition(_), _) => Greater,
|
||||||
(ValueData::ExpectedType { .. }, _) => Less,
|
(ValueData::TypeHint { .. }, _) => Less,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,7 +311,7 @@ impl Completer for DustCompleter {
|
|||||||
for (key, value_data) in self.context.inner().unwrap().iter() {
|
for (key, value_data) in self.context.inner().unwrap().iter() {
|
||||||
let value = match value_data {
|
let value = match value_data {
|
||||||
ValueData::Value { inner, .. } => inner,
|
ValueData::Value { inner, .. } => inner,
|
||||||
ValueData::ExpectedType { .. } => continue,
|
ValueData::TypeHint { .. } => continue,
|
||||||
ValueData::TypeDefinition(_) => continue,
|
ValueData::TypeDefinition(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,3 +4,29 @@ use dust_lang::*;
|
|||||||
fn args() {
|
fn args() {
|
||||||
assert!(interpret("args").is_ok_and(|value| value.is_list()));
|
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()
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user