Implement option value

This commit is contained in:
Jeff 2023-12-26 19:33:19 -05:00
parent 20a6e707c5
commit 9dfaf1420c
6 changed files with 51 additions and 42 deletions

6
examples/option.ds Normal file
View File

@ -0,0 +1,6 @@
create_user = (fn email <string>, name <option(string)>) <map> {
{
email = email
username = (either_or username email)
}
}

View File

@ -28,6 +28,7 @@ impl AbstractTree for FunctionCall {
let function_expression = Expression::from_syntax_node(source, expression_node, context)?; let function_expression = Expression::from_syntax_node(source, expression_node, context)?;
let function_type = function_expression.expected_type(context)?; let function_type = function_expression.expected_type(context)?;
let mut minimum_parameter_count = 0;
let mut arguments = Vec::new(); let mut arguments = Vec::new();
for index in 2..node.child_count() - 1 { for index in 2..node.child_count() - 1 {
@ -43,6 +44,11 @@ impl AbstractTree for FunctionCall {
} = &function_type } = &function_type
{ {
if let Some(r#type) = parameter_types.get(argument_index) { if let Some(r#type) = parameter_types.get(argument_index) {
if let Type::Option(_) = r#type {
} else {
minimum_parameter_count += 1;
}
r#type r#type
.check(&expression_type) .check(&expression_type)
.map_err(|error| error.at_node(child, source))?; .map_err(|error| error.at_node(child, source))?;
@ -54,13 +60,13 @@ impl AbstractTree for FunctionCall {
} }
if let Type::Function { if let Type::Function {
parameter_types, .. parameter_types: _, ..
} = &function_type } = &function_type
{ {
if arguments.len() != parameter_types.len() { if arguments.len() < minimum_parameter_count {
return Err(Error::ExpectedFunctionArgumentAmount { return Err(Error::ExpectedFunctionArgumentMinimum {
source: source[expression_node.byte_range()].to_string(), source: source[expression_node.byte_range()].to_string(),
expected: parameter_types.len(), minumum_expected: minimum_parameter_count,
actual: arguments.len(), actual: arguments.len(),
}); });
} }

View File

@ -41,14 +41,18 @@ impl BuiltInFunction for Sh {
} }
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> { fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
Error::expect_argument_amount(self, 1, arguments.len())?;
let command_text = arguments.first().unwrap().as_string()?; let command_text = arguments.first().unwrap().as_string()?;
let mut command = Command::new("sh"); let mut command = Command::new("sh");
command.arg("-c"); command.arg("-c");
command.arg(command_text); command.arg(command_text);
let extra_command_text = arguments.get(1).unwrap_or_default().as_option()?;
if let Some(text) = extra_command_text {
command.args(["--", text.as_string()?]);
}
let output = command.spawn()?.wait_with_output()?.stdout; let output = command.spawn()?.wait_with_output()?.stdout;
Ok(Value::String(String::from_utf8(output)?)) Ok(Value::String(String::from_utf8(output)?))
@ -56,7 +60,7 @@ impl BuiltInFunction for Sh {
fn r#type(&self) -> crate::Type { fn r#type(&self) -> crate::Type {
Type::Function { Type::Function {
parameter_types: vec![Type::String], parameter_types: vec![Type::String, Type::Option(Box::new(Type::String))],
return_type: Box::new(Type::String), return_type: Box::new(Type::String),
} }
} }

View File

@ -74,9 +74,9 @@ pub enum Error {
}, },
/// A function was called with the wrong amount of arguments. /// A function was called with the wrong amount of arguments.
ExpectedArgumentMinimum { ExpectedFunctionArgumentMinimum {
function_name: &'static str, source: String,
minimum: usize, minumum_expected: usize,
actual: usize, actual: usize,
}, },
@ -136,6 +136,10 @@ pub enum Error {
actual: Value, actual: Value,
}, },
ExpectedOption {
actual: Value,
},
/// A string, list, map or table value was expected. /// A string, list, map or table value was expected.
ExpectedCollection { ExpectedCollection {
actual: Value, actual: Value,
@ -205,22 +209,6 @@ impl Error {
} }
} }
pub fn expect_argument_minimum<F: BuiltInFunction>(
function: &F,
minimum: usize,
actual: usize,
) -> Result<()> {
if actual < minimum {
Ok(())
} else {
Err(Error::ExpectedArgumentMinimum {
function_name: function.name(),
minimum,
actual,
})
}
}
pub fn is_type_check_error(&self, other: &Error) -> bool { pub fn is_type_check_error(&self, other: &Error) -> bool {
match self { match self {
Error::WithContext { error, .. } => { Error::WithContext { error, .. } => {
@ -335,14 +323,16 @@ impl fmt::Display for Error {
f, f,
"{source} expected {expected} arguments, but got {actual}.", "{source} expected {expected} arguments, but got {actual}.",
), ),
ExpectedArgumentMinimum { ExpectedFunctionArgumentMinimum {
function_name, source,
minimum, minumum_expected,
actual, actual,
} => write!( } => {
write!(
f, f,
"{function_name} expected a minimum of {minimum} arguments, but got {actual}.", "{source} expected at least {minumum_expected} arguments, but got {actual}."
), )
}
ExpectedString { actual } => { ExpectedString { actual } => {
write!(f, "Expected a string but got {actual}.") write!(f, "Expected a string but got {actual}.")
} }
@ -379,6 +369,7 @@ impl fmt::Display for Error {
ExpectedFunction { actual } => { ExpectedFunction { actual } => {
write!(f, "Expected function, but got {actual}.") write!(f, "Expected function, but got {actual}.")
} }
ExpectedOption { actual } => write!(f, "Expected option, but got {actual}."),
ExpectedCollection { actual } => { ExpectedCollection { actual } => {
write!( write!(
f, f,

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{AbstractTree, Block, Error, Identifier, Map, Result, Type, Value}; use crate::{AbstractTree, Block, Identifier, Map, Result, Type, Value};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Function { pub struct Function {
@ -48,14 +48,6 @@ impl Function {
} }
pub fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result<Value> { pub fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result<Value> {
if self.parameters.len() != arguments.len() {
return Err(Error::ExpectedFunctionArgumentAmount {
source: "unknown".to_string(),
expected: self.parameters.len(),
actual: arguments.len(),
});
}
let context = Map::clone_from(outer_context)?; let context = Map::clone_from(outer_context)?;
let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter()); let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter());

View File

@ -220,6 +220,16 @@ impl Value {
} }
} }
/// Returns `Option`, or returns `Err` if `self` is not a `Value::Option`.
pub fn as_option(&self) -> Result<&Option<Box<Value>>> {
match self {
Value::Option(option) => Ok(option),
value => Err(Error::ExpectedOption {
actual: value.clone(),
}),
}
}
/// Returns `()`, or returns `Err` if `self` is not a `Value::Option(None)`. /// Returns `()`, or returns `Err` if `self` is not a `Value::Option(None)`.
pub fn as_none(&self) -> Result<()> { pub fn as_none(&self) -> Result<()> {
match self { match self {