Implement custom and built-in types
This commit is contained in:
parent
e1c3e8bc0d
commit
e7f5d66297
@ -14,7 +14,6 @@ pub struct Assignment {
|
||||
type_specification: Option<TypeSpecification>,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
|
||||
syntax_position: SourcePosition,
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl EnumDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instantiate(&self, variant: String, content: Value) -> EnumInstance {
|
||||
pub fn instantiate(&self, variant: String, content: Option<Value>) -> EnumInstance {
|
||||
EnumInstance::new(self.identifier.inner().clone(), variant, content)
|
||||
}
|
||||
|
||||
|
@ -89,9 +89,11 @@ impl AbstractTree for Match {
|
||||
{
|
||||
let statement_context = Context::with_variables_from(context)?;
|
||||
|
||||
if let Some(identifier) = enum_pattern.inner_identifier() {
|
||||
if let (Some(identifier), Some(value)) =
|
||||
(enum_pattern.inner_identifier(), enum_instance.value())
|
||||
{
|
||||
statement_context
|
||||
.set_value(identifier.inner().clone(), enum_instance.value().clone())?;
|
||||
.set_value(identifier.inner().clone(), value.as_ref().clone())?;
|
||||
}
|
||||
|
||||
return statement.run(source, &statement_context);
|
||||
|
@ -3,6 +3,7 @@ use std::fmt::{self, Display, Formatter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
built_in_types::BuiltInType,
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Context, Format, Identifier, SyntaxNode, Value,
|
||||
};
|
||||
@ -13,6 +14,10 @@ pub enum Type {
|
||||
Boolean,
|
||||
Collection,
|
||||
Custom(Identifier),
|
||||
CustomWithArgument {
|
||||
name: Identifier,
|
||||
argument: Box<Type>,
|
||||
},
|
||||
Float,
|
||||
Function {
|
||||
parameter_types: Vec<Type>,
|
||||
@ -21,14 +26,17 @@ pub enum Type {
|
||||
Integer,
|
||||
List(Box<Type>),
|
||||
Map,
|
||||
None,
|
||||
Number,
|
||||
String,
|
||||
Range,
|
||||
Option(Box<Type>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn option(inner_type: Option<Type>) -> Self {
|
||||
BuiltInType::Option.get(inner_type).clone()
|
||||
}
|
||||
|
||||
pub fn list(item_type: Type) -> Self {
|
||||
Type::List(Box::new(item_type))
|
||||
}
|
||||
@ -40,10 +48,6 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn option(optional_type: Type) -> Self {
|
||||
Type::Option(Box::new(optional_type))
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether is type is accepting of the other.
|
||||
///
|
||||
/// The types do not need to match exactly. For example, the Any variant matches all of the
|
||||
@ -70,17 +74,22 @@ impl Type {
|
||||
| (Type::Number, Type::Float)
|
||||
| (Type::Integer, Type::Number)
|
||||
| (Type::Float, Type::Number)
|
||||
| (Type::None, Type::None)
|
||||
| (Type::String, Type::String) => true,
|
||||
(Type::Custom(left), Type::Custom(right)) => left == right,
|
||||
(Type::Option(_), Type::None) => true,
|
||||
(Type::Option(left), Type::Option(right)) => {
|
||||
if let Type::Any = left.as_ref() {
|
||||
true
|
||||
} else if left == right {
|
||||
true
|
||||
} else {
|
||||
(
|
||||
Type::CustomWithArgument {
|
||||
name: left_name,
|
||||
argument: left_argument,
|
||||
},
|
||||
Type::CustomWithArgument {
|
||||
name: right_name,
|
||||
argument: right_argument,
|
||||
},
|
||||
) => {
|
||||
if left_name != right_name {
|
||||
false
|
||||
} else {
|
||||
left_argument == right_argument
|
||||
}
|
||||
}
|
||||
(Type::List(self_item_type), Type::List(other_item_type)) => {
|
||||
@ -136,7 +145,21 @@ impl AbstractTree for Type {
|
||||
let type_node = node.child(0).unwrap();
|
||||
|
||||
let r#type = match type_node.kind() {
|
||||
"identifier" => Type::Custom(Identifier::from_syntax(type_node, _source, _context)?),
|
||||
"identifier" => {
|
||||
if node.child_count() == 1 {
|
||||
Type::Custom(Identifier::from_syntax(type_node, _source, _context)?)
|
||||
} else {
|
||||
let name = Identifier::from_syntax(type_node, _source, _context)?;
|
||||
|
||||
let argument_node = node.child(2).unwrap();
|
||||
let argument = Type::from_syntax(argument_node, _source, _context)?;
|
||||
|
||||
Type::CustomWithArgument {
|
||||
name,
|
||||
argument: Box::new(argument),
|
||||
}
|
||||
}
|
||||
}
|
||||
"[" => {
|
||||
let item_type_node = node.child(1).unwrap();
|
||||
let item_type = Type::from_syntax(item_type_node, _source, _context)?;
|
||||
@ -165,7 +188,7 @@ impl AbstractTree for Type {
|
||||
let return_type = if final_node.is_named() {
|
||||
Type::from_syntax(final_node, _source, _context)?
|
||||
} else {
|
||||
Type::None
|
||||
Type::option(None)
|
||||
};
|
||||
|
||||
Type::Function {
|
||||
@ -178,16 +201,9 @@ impl AbstractTree for Type {
|
||||
"num" => Type::Number,
|
||||
"none" => Type::None,
|
||||
"str" => Type::String,
|
||||
"option" => {
|
||||
let inner_type_node = node.child(2).unwrap();
|
||||
let inner_type = Type::from_syntax(inner_type_node, _source, _context)?;
|
||||
|
||||
Type::Option(Box::new(inner_type))
|
||||
}
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "any, bool, float, int, num, str, option, custom type, (, [ or {"
|
||||
.to_string(),
|
||||
expected: "any, bool, float, int, num, str, custom type, (, [ or {".to_string(),
|
||||
actual: type_node.kind().to_string(),
|
||||
location: type_node.start_position(),
|
||||
relevant_source: _source[type_node.byte_range()].to_string(),
|
||||
@ -217,8 +233,11 @@ impl Format for Type {
|
||||
Type::Any => output.push_str("any"),
|
||||
Type::Boolean => output.push_str("bool"),
|
||||
Type::Collection => output.push_str("collection"),
|
||||
|
||||
Type::Custom(_) => todo!(),
|
||||
Type::CustomWithArgument {
|
||||
name: _,
|
||||
argument: _,
|
||||
} => todo!(),
|
||||
Type::Float => output.push_str("float"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
@ -246,14 +265,9 @@ impl Format for Type {
|
||||
Type::Map => {
|
||||
output.push_str("map");
|
||||
}
|
||||
Type::None => output.push_str("none"),
|
||||
Type::None => output.push_str("Option::None"),
|
||||
Type::Number => output.push_str("num"),
|
||||
Type::String => output.push_str("str"),
|
||||
Type::Option(optional_type) => {
|
||||
output.push_str("option(");
|
||||
optional_type.format(output, indent_level);
|
||||
output.push(')');
|
||||
}
|
||||
Type::Range => todo!(),
|
||||
}
|
||||
}
|
||||
@ -266,6 +280,9 @@ impl Display for Type {
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Collection => write!(f, "collection"),
|
||||
Type::Custom(identifier) => write!(f, "{identifier}"),
|
||||
Type::CustomWithArgument { name, argument } => {
|
||||
write!(f, "{name}<{argument}>")
|
||||
}
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
@ -290,9 +307,6 @@ impl Display for Type {
|
||||
Type::Number => write!(f, "num"),
|
||||
Type::None => write!(f, "none"),
|
||||
Type::String => write!(f, "str"),
|
||||
Type::Option(inner_type) => {
|
||||
write!(f, "option({})", inner_type)
|
||||
}
|
||||
Type::Range => todo!(),
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ pub enum ValueNode {
|
||||
Integer(String),
|
||||
String(String),
|
||||
List(Vec<Expression>),
|
||||
Option(Option<Box<Expression>>),
|
||||
Map(MapNode),
|
||||
Range(RangeInclusive<i64>),
|
||||
Struct {
|
||||
@ -68,18 +67,6 @@ impl AbstractTree for ValueNode {
|
||||
"map" => {
|
||||
ValueNode::Map(MapNode::from_syntax(child, source, context)?)
|
||||
}
|
||||
"option" => {
|
||||
let first_grandchild = child.child(0).unwrap();
|
||||
|
||||
if first_grandchild.kind() == "none" {
|
||||
ValueNode::Option(None)
|
||||
} else {
|
||||
let expression_node = child.child(2).unwrap();
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
ValueNode::Option(Some(Box::new(expression)))
|
||||
}
|
||||
}
|
||||
"range" => {
|
||||
let mut split = source[child.byte_range()].split("..");
|
||||
let start = split.next().unwrap().parse().unwrap();
|
||||
@ -158,19 +145,21 @@ impl AbstractTree for ValueNode {
|
||||
Type::List(Box::new(Type::Any))
|
||||
}
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
if let Some(expression) = option {
|
||||
Type::Option(Box::new(expression.expected_type(context)?))
|
||||
} else {
|
||||
Type::None
|
||||
}
|
||||
}
|
||||
ValueNode::Map(_) => Type::Map,
|
||||
ValueNode::Struct { name, .. } => {
|
||||
Type::Custom(name.clone())
|
||||
}
|
||||
ValueNode::Range(_) => Type::Range,
|
||||
ValueNode::Enum { name, .. } => Type::Custom(name.clone()),
|
||||
ValueNode::Enum { name, variant: _, expression } => {
|
||||
if let Some(expression) = expression {
|
||||
Type::CustomWithArgument {
|
||||
name: name.clone(),
|
||||
argument: Box::new(expression.expected_type(context)?)
|
||||
}
|
||||
} else {
|
||||
Type::Custom(name.clone())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(r#type)
|
||||
@ -212,15 +201,6 @@ impl AbstractTree for ValueNode {
|
||||
|
||||
Value::List(List::with_items(values))
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
let option_value = if let Some(expression) = option {
|
||||
Some(Box::new(expression.run(source, context)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Value::Option(option_value)
|
||||
}
|
||||
ValueNode::Map(map_node) => map_node.run(source, context)?,
|
||||
ValueNode::Range(range) => Value::Range(range.clone()),
|
||||
ValueNode::Struct { name, properties } => {
|
||||
@ -247,7 +227,7 @@ impl AbstractTree for ValueNode {
|
||||
};
|
||||
let instance = if let Some(definition) = context.get_definition(name.inner())? {
|
||||
if let TypeDefinition::Enum(enum_defintion) = definition {
|
||||
enum_defintion.instantiate(variant.inner().clone(), value)
|
||||
enum_defintion.instantiate(variant.inner().clone(), Some(value))
|
||||
} else {
|
||||
return Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::ExpectedEnumDefintion {
|
||||
@ -290,15 +270,6 @@ impl Format for ValueNode {
|
||||
|
||||
output.push(']');
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
if let Some(expression) = option {
|
||||
output.push_str("some(");
|
||||
expression.format(output, indent_level);
|
||||
output.push(')');
|
||||
} else {
|
||||
output.push_str("none");
|
||||
}
|
||||
}
|
||||
ValueNode::Map(map_node) => map_node.format(output, indent_level),
|
||||
ValueNode::Struct { name, properties } => {
|
||||
name.format(output, indent_level);
|
||||
@ -325,8 +296,6 @@ impl Ord for ValueNode {
|
||||
(ValueNode::String(_), _) => Ordering::Greater,
|
||||
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
|
||||
(ValueNode::List(_), _) => Ordering::Greater,
|
||||
(ValueNode::Option(left), ValueNode::Option(right)) => left.cmp(right),
|
||||
(ValueNode::Option(_), _) => Ordering::Greater,
|
||||
(ValueNode::Map(left), ValueNode::Map(right)) => left.cmp(right),
|
||||
(ValueNode::Map(_), _) => Ordering::Greater,
|
||||
(ValueNode::Struct{ name: left_name, properties: left_properties }, ValueNode::Struct {name: right_name, properties: right_properties} ) => {
|
||||
|
@ -103,13 +103,13 @@ impl Callable for BuiltInFunction {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Ok".to_string(),
|
||||
Value::none(),
|
||||
Some(Value::none()),
|
||||
))
|
||||
} else {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Error".to_string(),
|
||||
Value::none(),
|
||||
Some(Value::none()),
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::RuntimeError, Context, List, Type, Value};
|
||||
use crate::{error::RuntimeError, Context, EnumInstance, List, Type, Value};
|
||||
|
||||
use super::Callable;
|
||||
|
||||
@ -122,7 +122,7 @@ impl Callable for StrFunction {
|
||||
}
|
||||
StrFunction::Find => Type::function(
|
||||
vec![Type::String, Type::String],
|
||||
Type::option(Type::Integer),
|
||||
Type::option(Some(Type::Integer)),
|
||||
),
|
||||
StrFunction::Insert => Type::function(
|
||||
vec![Type::String, Type::Integer, Type::String],
|
||||
@ -137,7 +137,7 @@ impl Callable for StrFunction {
|
||||
StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
|
||||
StrFunction::Remove => Type::function(
|
||||
vec![Type::String, Type::Integer],
|
||||
Type::option(Type::String),
|
||||
Type::option(Some(Type::String)),
|
||||
),
|
||||
StrFunction::ReplaceRange => Type::function(
|
||||
vec![Type::String, Type::list(Type::Integer), Type::String],
|
||||
@ -175,9 +175,10 @@ impl Callable for StrFunction {
|
||||
StrFunction::StartsWith => {
|
||||
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||
}
|
||||
StrFunction::StripPrefix => {
|
||||
Type::function(vec![Type::String, Type::String], Type::option(Type::String))
|
||||
}
|
||||
StrFunction::StripPrefix => Type::function(
|
||||
vec![Type::String, Type::String],
|
||||
Type::option(Some(Type::String)),
|
||||
),
|
||||
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::Truncate => {
|
||||
@ -233,9 +234,21 @@ impl Callable for StrFunction {
|
||||
let pattern = pattern_string.as_str();
|
||||
let find = string
|
||||
.find(pattern)
|
||||
.map(|index| Box::new(Value::Integer(index as i64)));
|
||||
.map(|index| Value::Integer(index as i64));
|
||||
|
||||
Value::Option(find)
|
||||
if let Some(index) = find {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"Some".to_string(),
|
||||
Some(index),
|
||||
))
|
||||
} else {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"None".to_string(),
|
||||
Some(Value::none()),
|
||||
))
|
||||
}
|
||||
}
|
||||
StrFunction::IsAscii => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
@ -292,9 +305,9 @@ impl Callable for StrFunction {
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
if let Ok(integer) = string.parse::<i64>() {
|
||||
Value::option(Some(Value::Integer(integer)))
|
||||
Value::Integer(integer)
|
||||
} else if let Ok(float) = string.parse::<f64>() {
|
||||
Value::option(Some(Value::Float(float)))
|
||||
Value::Float(float)
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
@ -413,7 +426,11 @@ impl Callable for StrFunction {
|
||||
]))
|
||||
});
|
||||
|
||||
Value::option(sections)
|
||||
if let Some(sections) = sections {
|
||||
Value::some(sections)
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StrFunction::SplitTerminator => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
@ -458,7 +475,15 @@ impl Callable for StrFunction {
|
||||
.strip_prefix(prefix)
|
||||
.map(|remainder| Value::string(remainder.to_string()));
|
||||
|
||||
Value::option(stripped)
|
||||
if let Some(value) = stripped {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"Some".to_string(),
|
||||
Some(value),
|
||||
))
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StrFunction::ToLowercase => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
36
src/built_in_types.rs
Normal file
36
src/built_in_types.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Identifier, Type};
|
||||
|
||||
static OPTION: OnceLock<Type> = OnceLock::new();
|
||||
|
||||
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BuiltInType {
|
||||
Option,
|
||||
}
|
||||
|
||||
impl BuiltInType {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInType::Option => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, inner_type: Option<Type>) -> &Type {
|
||||
match self {
|
||||
BuiltInType::Option => OPTION.get_or_init(|| {
|
||||
if let Some(inner_type) = inner_type {
|
||||
Type::CustomWithArgument {
|
||||
name: Identifier::new("Option"),
|
||||
argument: Box::new(inner_type),
|
||||
}
|
||||
} else {
|
||||
Type::Custom(Identifier::new("Option"))
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
built_in_functions::{fs::fs_functions, json::json_functions, str::string_functions, Callable},
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, BuiltInFunction, Context, Format, Function, List, Map, SyntaxNode, Type, Value,
|
||||
AbstractTree, BuiltInFunction, Context, EnumInstance, Format, Function, Identifier, List, Map,
|
||||
SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
static ARGS: OnceLock<Value> = OnceLock::new();
|
||||
@ -14,6 +15,7 @@ static FS: OnceLock<Value> = OnceLock::new();
|
||||
static JSON: OnceLock<Value> = OnceLock::new();
|
||||
static RANDOM: OnceLock<Value> = OnceLock::new();
|
||||
static STRING: OnceLock<Value> = OnceLock::new();
|
||||
static NONE: OnceLock<Value> = OnceLock::new();
|
||||
|
||||
/// Returns the entire built-in value API.
|
||||
pub fn all_built_in_values() -> impl Iterator<Item = BuiltInValue> {
|
||||
@ -38,6 +40,9 @@ pub enum BuiltInValue {
|
||||
/// Get the length of a collection.
|
||||
Length,
|
||||
|
||||
/// The absence of a value.
|
||||
None,
|
||||
|
||||
/// Print a value to stdout.
|
||||
Output,
|
||||
|
||||
@ -57,6 +62,7 @@ impl BuiltInValue {
|
||||
BuiltInValue::Fs => "fs",
|
||||
BuiltInValue::Json => "json",
|
||||
BuiltInValue::Length => BuiltInFunction::Length.name(),
|
||||
BuiltInValue::None => "None",
|
||||
BuiltInValue::Output => "output",
|
||||
BuiltInValue::Random => "random",
|
||||
BuiltInValue::Str => "str",
|
||||
@ -73,6 +79,7 @@ impl BuiltInValue {
|
||||
BuiltInValue::Fs => "File and directory tools.",
|
||||
BuiltInValue::Json => "JSON formatting tools.",
|
||||
BuiltInValue::Length => BuiltInFunction::Length.description(),
|
||||
BuiltInValue::None => "The absence of a value.",
|
||||
BuiltInValue::Output => "output",
|
||||
BuiltInValue::Random => "random",
|
||||
BuiltInValue::Str => "string",
|
||||
@ -89,6 +96,7 @@ impl BuiltInValue {
|
||||
BuiltInValue::Fs => Type::Map,
|
||||
BuiltInValue::Json => Type::Map,
|
||||
BuiltInValue::Length => BuiltInFunction::Length.r#type(),
|
||||
BuiltInValue::None => Type::Custom(Identifier::new("Option")),
|
||||
BuiltInValue::Output => BuiltInFunction::Output.r#type(),
|
||||
BuiltInValue::Random => Type::Map,
|
||||
BuiltInValue::Str => Type::Map,
|
||||
@ -134,6 +142,13 @@ impl BuiltInValue {
|
||||
Value::Map(Map::with_values(json_context))
|
||||
}),
|
||||
BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
|
||||
BuiltInValue::None => NONE.get_or_init(|| {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"None".to_string(),
|
||||
None,
|
||||
))
|
||||
}),
|
||||
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
|
||||
BuiltInValue::Random => RANDOM.get_or_init(|| {
|
||||
let mut random_context = BTreeMap::new();
|
||||
|
@ -18,6 +18,7 @@ pub use tree_sitter::Node as SyntaxNode;
|
||||
pub mod abstract_tree;
|
||||
pub mod built_in_functions;
|
||||
pub mod built_in_type_definitions;
|
||||
pub mod built_in_types;
|
||||
pub mod built_in_values;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
|
@ -1,20 +1,22 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Value;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct EnumInstance {
|
||||
name: String,
|
||||
variant_name: String,
|
||||
value: Box<Value>,
|
||||
value: Option<Box<Value>>,
|
||||
}
|
||||
|
||||
impl EnumInstance {
|
||||
pub fn new(name: String, variant_name: String, value: Value) -> Self {
|
||||
pub fn new(name: String, variant_name: String, value: Option<Value>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
variant_name,
|
||||
value: Box::new(value),
|
||||
value: value.map(|value| Box::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,13 +28,13 @@ impl EnumInstance {
|
||||
&self.variant_name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &Value {
|
||||
pub fn value(&self) -> &Option<Box<Value>> {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EnumInstance {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}::{}({})", self.name, self.variant_name, self.value)
|
||||
write!(f, "{}::{}({:?})", self.name, self.variant_name, self.value)
|
||||
}
|
||||
}
|
||||
|
103
src/value/mod.rs
103
src/value/mod.rs
@ -1,5 +1,7 @@
|
||||
//! Types that represent runtime values.
|
||||
use crate::{error::RuntimeError, Identifier, Type, TypeSpecification};
|
||||
use crate::{
|
||||
built_in_values::BuiltInValue, error::RuntimeError, Identifier, Type, TypeSpecification,
|
||||
};
|
||||
|
||||
use serde::{
|
||||
de::{MapAccess, SeqAccess, Visitor},
|
||||
@ -42,18 +44,23 @@ pub enum Value {
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
Range(RangeInclusive<i64>),
|
||||
Option(Option<Box<Value>>),
|
||||
Struct(StructInstance),
|
||||
Enum(EnumInstance),
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn none() -> Self {
|
||||
BuiltInValue::None.get().clone()
|
||||
}
|
||||
|
||||
pub fn some(value: Value) -> Value {
|
||||
Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"Some".to_string(),
|
||||
Some(value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn string<T: Into<String>>(string: T) -> Self {
|
||||
Value::String(string.into())
|
||||
}
|
||||
@ -102,31 +109,12 @@ impl Value {
|
||||
Value::Float(_) => Type::Float,
|
||||
Value::Integer(_) => Type::Integer,
|
||||
Value::Boolean(_) => Type::Boolean,
|
||||
Value::Option(option) => {
|
||||
if let Some(value) = option {
|
||||
Type::Option(Box::new(value.r#type()))
|
||||
} else {
|
||||
Type::None
|
||||
}
|
||||
}
|
||||
Value::Range(_) => todo!(),
|
||||
Value::Struct(_) => todo!(),
|
||||
Value::Enum(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
Value::Option(None)
|
||||
}
|
||||
|
||||
pub fn some(value: Value) -> Self {
|
||||
Value::Option(Some(Box::new(value)))
|
||||
}
|
||||
|
||||
pub fn option(option: Option<Value>) -> Self {
|
||||
Value::Option(option.map(Box::new))
|
||||
}
|
||||
|
||||
pub fn is_string(&self) -> bool {
|
||||
matches!(self, Value::String(_))
|
||||
}
|
||||
@ -151,14 +139,6 @@ impl Value {
|
||||
matches!(self, Value::List(_))
|
||||
}
|
||||
|
||||
pub fn is_option(&self) -> bool {
|
||||
matches!(self, Value::Option(_))
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Value::Option(None))
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
matches!(self, Value::Map(_))
|
||||
}
|
||||
@ -167,6 +147,10 @@ impl Value {
|
||||
matches!(self, Value::Function(_))
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
self == &Value::none()
|
||||
}
|
||||
|
||||
/// Borrows the value stored in `self` as `&String`, or returns `Err` if `self` is not a `Value::String`.
|
||||
pub fn as_string(&self) -> Result<&String, RuntimeError> {
|
||||
match self {
|
||||
@ -259,39 +243,17 @@ impl Value {
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Option`, or returns `Err` if `self` is not a `Value::Option`.
|
||||
pub fn as_option(&self) -> Result<&Option<Box<Value>>, RuntimeError> {
|
||||
match self {
|
||||
Value::Option(option) => Ok(option),
|
||||
value => Err(RuntimeError::ExpectedOption {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `()`, or returns `Err` if `self` is not a `Value::none()`.
|
||||
pub fn as_none(&self) -> Result<(), RuntimeError> {
|
||||
match self {
|
||||
Value::Option(option) => {
|
||||
if option.is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RuntimeError::ExpectedNone {
|
||||
actual: self.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
value => Err(RuntimeError::ExpectedNone {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for &Value {
|
||||
fn default() -> Self {
|
||||
&Value::Option(None)
|
||||
BuiltInValue::None.get()
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,7 +407,6 @@ impl PartialEq for Value {
|
||||
(Value::List(left), Value::List(right)) => left == right,
|
||||
(Value::Map(left), Value::Map(right)) => left == right,
|
||||
(Value::Function(left), Value::Function(right)) => left == right,
|
||||
(Value::Option(left), Value::Option(right)) => left == right,
|
||||
(Value::Range(left), Value::Range(right)) => left == right,
|
||||
(Value::Struct(left), Value::Struct(right)) => left == right,
|
||||
(Value::Enum(left), Value::Enum(right)) => left == right,
|
||||
@ -495,9 +456,7 @@ impl Ord for Value {
|
||||
|
||||
left_len.cmp(&right_len)
|
||||
}
|
||||
(Value::Range(_), _) => Ordering::Greater,
|
||||
(Value::Option(left), Value::Option(right)) => left.cmp(right),
|
||||
(Value::Option(_), _) => Ordering::Less,
|
||||
(Value::Range(_), _) => Ordering::Less,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,7 +481,6 @@ impl Serialize for Value {
|
||||
|
||||
list.end()
|
||||
}
|
||||
Value::Option(inner) => inner.serialize(serializer),
|
||||
Value::Map(map) => {
|
||||
let entries = map.inner();
|
||||
let mut map = serializer.serialize_map(Some(entries.len()))?;
|
||||
@ -548,13 +506,6 @@ impl Display for Value {
|
||||
Value::Float(float) => write!(f, "{float}"),
|
||||
Value::Integer(int) => write!(f, "{int}"),
|
||||
Value::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
Value::Option(option) => {
|
||||
if let Some(value) = option {
|
||||
write!(f, "some({})", value)
|
||||
} else {
|
||||
write!(f, "none")
|
||||
}
|
||||
}
|
||||
Value::List(list) => write!(f, "{list}"),
|
||||
Value::Map(map) => write!(f, "{map}"),
|
||||
Value::Function(function) => write!(f, "{function}"),
|
||||
@ -839,9 +790,7 @@ impl<'de> Visitor<'de> for ValueVisitor {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(Value::Option(Some(Box::new(Value::deserialize(
|
||||
deserializer,
|
||||
)?))))
|
||||
Ok(Value::Enum(EnumInstance::deserialize(deserializer)?))
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
|
||||
|
@ -29,11 +29,11 @@ fn ends_with() {
|
||||
fn find() {
|
||||
let result = interpret("str:find('abc', 'a')");
|
||||
|
||||
assert_eq!(result, Ok(Value::Option(Some(Box::new(Value::Integer(0))))));
|
||||
assert_eq!(result, Ok(Value::some(Value::Integer(0))));
|
||||
|
||||
let result = interpret("str:find('foobar', 'b')");
|
||||
|
||||
assert_eq!(result, Ok(Value::Option(Some(Box::new(Value::Integer(3))))));
|
||||
assert_eq!(result, Ok(Value::some(Value::Integer(3))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -7,7 +7,7 @@ fn option() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"None".to_string(),
|
||||
Value::none()
|
||||
Some(Value::none()),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
@ -21,7 +21,7 @@ fn option() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Option".to_string(),
|
||||
"Some".to_string(),
|
||||
Value::Integer(1),
|
||||
Some(Value::Integer(1)),
|
||||
)))
|
||||
);
|
||||
}
|
||||
@ -33,7 +33,7 @@ fn result() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Ok".to_string(),
|
||||
Value::Integer(1)
|
||||
Some(Value::Integer(1)),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
@ -46,7 +46,7 @@ fn result() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Error".to_string(),
|
||||
Value::String("uh-oh!".to_string())
|
||||
Some(Value::String("uh-oh!".to_string())),
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ fn assert_equal() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Ok".to_string(),
|
||||
Value::none()
|
||||
Some(Value::none()),
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
@ -26,7 +26,7 @@ fn assert_equal() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Result".to_string(),
|
||||
"Error".to_string(),
|
||||
Value::none()
|
||||
Some(Value::none()),
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ fn simple_enum() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Foobar".to_string(),
|
||||
"Foo".to_string(),
|
||||
Value::none()
|
||||
Some(Value::none())
|
||||
)))
|
||||
);
|
||||
}
|
||||
@ -45,11 +45,11 @@ fn nested_enum() {
|
||||
Ok(Value::Enum(EnumInstance::new(
|
||||
"Foobar".to_string(),
|
||||
"Bar".to_string(),
|
||||
Value::Enum(EnumInstance::new(
|
||||
Some(Value::Enum(EnumInstance::new(
|
||||
"Fizzbuzz".to_string(),
|
||||
"Fizz".to_string(),
|
||||
Value::none()
|
||||
))
|
||||
Some(Value::none())
|
||||
)))
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ fn callback() {
|
||||
|
||||
#[test]
|
||||
fn built_in_function_call() {
|
||||
assert_eq!(interpret("output('Hiya')"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("output('Hiya')"), Ok(Value::none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,8 +2,8 @@ use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(interpret("x = 9"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("x = 1 + 1"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("x = 9"), Ok(Value::none()));
|
||||
assert_eq!(interpret("x = 1 + 1"), Ok(Value::none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,7 +2,7 @@ use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn while_loop() {
|
||||
assert_eq!(interpret("while false { 'foo' }"), Ok(Value::Option(None)))
|
||||
assert_eq!(interpret("while false { 'foo' }"), Ok(Value::none()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -380,12 +380,6 @@ module.exports = grammar({
|
||||
')',
|
||||
optional(seq('->', $.type)),
|
||||
),
|
||||
seq(
|
||||
'option',
|
||||
'(',
|
||||
$.type,
|
||||
')',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user