Rewrite while loops
This commit is contained in:
parent
fed119f38b
commit
c3402394a2
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
error::{RuntimeError, ValidationError},
|
error::{RuntimeError, ValidationError},
|
||||||
|
value::ValueInner,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AbstractTree, Action, Expression, Type, WithPosition};
|
use super::{AbstractTree, Action, Expression, Type, WithPosition};
|
||||||
@ -25,24 +26,43 @@ impl FunctionCall {
|
|||||||
|
|
||||||
impl AbstractTree for FunctionCall {
|
impl AbstractTree for FunctionCall {
|
||||||
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
|
||||||
if let Type::Function { return_type, .. } = self.function.node.expected_type(_context)? {
|
let function_node_type = self.function.node.expected_type(_context)?;
|
||||||
|
|
||||||
|
if let Type::Function { return_type, .. } = function_node_type {
|
||||||
Ok(*return_type)
|
Ok(*return_type)
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedFunction)
|
Err(ValidationError::ExpectedFunction {
|
||||||
|
actual: function_node_type,
|
||||||
|
position: self.function.position,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
|
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
|
||||||
if let Type::Function { .. } = self.function.node.expected_type(_context)? {
|
let function_node_type = self.function.node.expected_type(_context)?;
|
||||||
|
|
||||||
|
if let Type::Function { .. } = function_node_type {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedFunction)
|
Err(ValidationError::ExpectedFunction {
|
||||||
|
actual: function_node_type,
|
||||||
|
position: self.function.position,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(self, context: &Context) -> Result<Action, RuntimeError> {
|
fn run(self, context: &Context) -> Result<Action, RuntimeError> {
|
||||||
let value = self.function.node.run(context)?.as_return_value()?;
|
let value = self.function.node.run(context)?.as_return_value()?;
|
||||||
let function = value.as_function()?;
|
let function = if let ValueInner::Function(function) = value.inner().as_ref() {
|
||||||
|
function
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedFunction {
|
||||||
|
actual: value.r#type(),
|
||||||
|
position: self.function.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
let mut arguments = Vec::with_capacity(self.arguments.len());
|
let mut arguments = Vec::with_capacity(self.arguments.len());
|
||||||
|
|
||||||
for expression in self.arguments {
|
for expression in self.arguments {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
error::{RuntimeError, ValidationError},
|
error::{RuntimeError, ValidationError},
|
||||||
|
value::ValueInner,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AbstractTree, Action, Block, Expression, Type, WithPosition};
|
use super::{AbstractTree, Action, Block, Expression, Type, WithPosition};
|
||||||
@ -35,7 +36,9 @@ impl AbstractTree for IfElse {
|
|||||||
self.if_expression.node.validate(context)?;
|
self.if_expression.node.validate(context)?;
|
||||||
self.if_block.validate(context)?;
|
self.if_block.validate(context)?;
|
||||||
|
|
||||||
if let Type::Boolean = self.if_expression.node.expected_type(context)? {
|
let if_expression_type = self.if_expression.node.expected_type(context)?;
|
||||||
|
|
||||||
|
if let Type::Boolean = if_expression_type {
|
||||||
if let Some(else_block) = &self.else_block {
|
if let Some(else_block) = &self.else_block {
|
||||||
else_block.validate(context)?;
|
else_block.validate(context)?;
|
||||||
|
|
||||||
@ -53,25 +56,32 @@ impl AbstractTree for IfElse {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedBoolean)
|
Err(ValidationError::ExpectedBoolean {
|
||||||
|
actual: if_expression_type,
|
||||||
|
position: self.if_expression.position,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(self, _context: &Context) -> Result<Action, RuntimeError> {
|
fn run(self, _context: &Context) -> Result<Action, RuntimeError> {
|
||||||
let if_boolean = self
|
let value = self.if_expression.node.run(_context)?.as_return_value()?;
|
||||||
.if_expression
|
|
||||||
.node
|
|
||||||
.run(_context)?
|
|
||||||
.as_return_value()?
|
|
||||||
.as_boolean()?;
|
|
||||||
|
|
||||||
if if_boolean {
|
if let ValueInner::Boolean(if_boolean) = value.inner().as_ref() {
|
||||||
|
if *if_boolean {
|
||||||
self.if_block.run(_context)
|
self.if_block.run(_context)
|
||||||
} else if let Some(else_statement) = self.else_block {
|
} else if let Some(else_statement) = self.else_block {
|
||||||
else_statement.run(_context)
|
else_statement.run(_context)
|
||||||
} else {
|
} else {
|
||||||
Ok(Action::None)
|
Ok(Action::None)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: value.r#type(),
|
||||||
|
position: self.if_expression.position,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
error::{RuntimeError, ValidationError},
|
error::{RuntimeError, ValidationError},
|
||||||
|
value::ValueInner,
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,14 +53,20 @@ impl AbstractTree for Logic {
|
|||||||
if let (Type::Boolean, Type::Boolean) = (left, right) {
|
if let (Type::Boolean, Type::Boolean) = (left, right) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedBoolean)
|
Err(ValidationError::ExpectedBoolean {
|
||||||
|
actual: todo!(),
|
||||||
|
position: todo!(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Logic::Not(expression) => {
|
Logic::Not(expression) => {
|
||||||
if let Type::Boolean = expression.node.expected_type(context)? {
|
if let Type::Boolean = expression.node.expected_type(context)? {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedBoolean)
|
Err(ValidationError::ExpectedBoolean {
|
||||||
|
actual: todo!(),
|
||||||
|
position: todo!(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,25 +111,72 @@ impl AbstractTree for Logic {
|
|||||||
left <= right
|
left <= right
|
||||||
}
|
}
|
||||||
Logic::And(left, right) => {
|
Logic::And(left, right) => {
|
||||||
let left = left.node.run(_context)?.as_return_value()?.as_boolean()?;
|
let left_value = left.node.run(_context)?.as_return_value()?;
|
||||||
let right = right.node.run(_context)?.as_return_value()?.as_boolean()?;
|
let right_value = right.node.run(_context)?.as_return_value()?;
|
||||||
|
|
||||||
left && right
|
let left = if let ValueInner::Boolean(boolean) = left_value.inner().as_ref() {
|
||||||
|
boolean
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: left_value.r#type(),
|
||||||
|
position: left.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let right = if let ValueInner::Boolean(boolean) = right_value.inner().as_ref() {
|
||||||
|
boolean
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: right_value.r#type(),
|
||||||
|
position: right.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
*left && *right
|
||||||
}
|
}
|
||||||
Logic::Or(left, right) => {
|
Logic::Or(left, right) => {
|
||||||
let left = left.node.run(_context)?.as_return_value()?.as_boolean()?;
|
let left_value = left.node.run(_context)?.as_return_value()?;
|
||||||
let right = right.node.run(_context)?.as_return_value()?.as_boolean()?;
|
let right_value = right.node.run(_context)?.as_return_value()?;
|
||||||
|
|
||||||
left || right
|
let left = if let ValueInner::Boolean(boolean) = left_value.inner().as_ref() {
|
||||||
|
boolean
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: left_value.r#type(),
|
||||||
|
position: left.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let right = if let ValueInner::Boolean(boolean) = right_value.inner().as_ref() {
|
||||||
|
boolean
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: right_value.r#type(),
|
||||||
|
position: right.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
*left || *right
|
||||||
}
|
}
|
||||||
Logic::Not(statement) => {
|
Logic::Not(statement) => {
|
||||||
let boolean = statement
|
let value = statement.node.run(_context)?.as_return_value()?;
|
||||||
.node
|
|
||||||
.run(_context)?
|
|
||||||
.as_return_value()?
|
|
||||||
.as_boolean()?;
|
|
||||||
|
|
||||||
|
if let ValueInner::Boolean(boolean) = value.inner().as_ref() {
|
||||||
!boolean
|
!boolean
|
||||||
|
} else {
|
||||||
|
return Err(RuntimeError::ValidationFailure(
|
||||||
|
ValidationError::ExpectedBoolean {
|
||||||
|
actual: value.r#type(),
|
||||||
|
position: statement.position,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
error::{RuntimeError, ValidationError},
|
error::{RuntimeError, ValidationError},
|
||||||
|
value::ValueInner,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AbstractTree, Action, Block, Expression, Type, WithPosition};
|
use super::{AbstractTree, Action, Expression, Statement, Type, WithPosition};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct While {
|
pub struct While {
|
||||||
expression: WithPosition<Expression>,
|
expression: WithPosition<Expression>,
|
||||||
block: Block,
|
statements: Vec<WithPosition<Statement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl While {
|
impl While {
|
||||||
pub fn new(expression: WithPosition<Expression>, block: Block) -> Self {
|
pub fn new(
|
||||||
Self { expression, block }
|
expression: WithPosition<Expression>,
|
||||||
|
statements: Vec<WithPosition<Statement>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
expression,
|
||||||
|
statements,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,23 +32,79 @@ impl AbstractTree for While {
|
|||||||
|
|
||||||
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
|
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
|
||||||
self.expression.node.validate(_context)?;
|
self.expression.node.validate(_context)?;
|
||||||
self.block.validate(_context)
|
|
||||||
|
for statement in &self.statements {
|
||||||
|
statement.node.validate(_context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(self, context: &Context) -> Result<Action, RuntimeError> {
|
Ok(())
|
||||||
while self
|
}
|
||||||
.expression
|
|
||||||
.node
|
fn run(self, _context: &Context) -> Result<Action, RuntimeError> {
|
||||||
.clone()
|
let get_boolean = || -> Result<Value, RuntimeError> {
|
||||||
.run(context)?
|
let value = self.expression.node.run(_context)?.as_return_value()?;
|
||||||
.as_return_value()?
|
|
||||||
.as_boolean()?
|
Ok(value)
|
||||||
{
|
};
|
||||||
if let Action::Break = self.block.clone().run(context)? {
|
|
||||||
break;
|
if let ValueInner::Boolean(boolean) = get_boolean()?.inner().as_ref() {
|
||||||
|
while *boolean {
|
||||||
|
for statement in &self.statements {
|
||||||
|
let action = statement.node.clone().run(_context)?;
|
||||||
|
|
||||||
|
match action {
|
||||||
|
Action::Return(_) => {}
|
||||||
|
Action::None => {}
|
||||||
|
Action::Break => return Ok(Action::Break),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Action::None)
|
Ok(Action::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::abstract_tree::{
|
||||||
|
Assignment, AssignmentOperator, Block, Identifier, Logic, ValueNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_while_loop() {
|
||||||
|
let result = Statement::Block(Block::new(vec![
|
||||||
|
Statement::Assignment(Assignment::new(
|
||||||
|
Identifier::new("i"),
|
||||||
|
None,
|
||||||
|
AssignmentOperator::Assign,
|
||||||
|
Statement::Expression(Expression::Value(ValueNode::Integer(3)))
|
||||||
|
.with_position((0, 0)),
|
||||||
|
))
|
||||||
|
.with_position((0, 0)),
|
||||||
|
Statement::While(While {
|
||||||
|
expression: Expression::Logic(Box::new(Logic::Less(
|
||||||
|
Expression::Identifier(Identifier::new("i")).with_position((0, 0)),
|
||||||
|
Expression::Value(ValueNode::Integer(3)).with_position((0, 0)),
|
||||||
|
)))
|
||||||
|
.with_position((0, 0)),
|
||||||
|
statements: vec![Statement::Assignment(Assignment::new(
|
||||||
|
Identifier::new("i"),
|
||||||
|
None,
|
||||||
|
AssignmentOperator::AddAssign,
|
||||||
|
Statement::Expression(Expression::Value(ValueNode::Integer(1)))
|
||||||
|
.with_position((0, 0)),
|
||||||
|
))
|
||||||
|
.with_position((0, 0))],
|
||||||
|
})
|
||||||
|
.with_position((0, 0)),
|
||||||
|
Statement::Expression(Expression::Identifier(Identifier::new("i")))
|
||||||
|
.with_position((0, 0)),
|
||||||
|
]))
|
||||||
|
.run(&Context::new());
|
||||||
|
|
||||||
|
assert_eq!(result, Ok(Action::Return(Value::integer(3))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
24
src/error.rs
24
src/error.rs
@ -64,11 +64,17 @@ impl Error {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Error::Validation { error, position } => match error {
|
Error::Validation { error, position } => match error {
|
||||||
ValidationError::ExpectedBoolean => {
|
ValidationError::ExpectedBoolean { actual, position } => {
|
||||||
builder.add_label(Label::new(0..0).with_message("Expected boolean."));
|
builder.add_label(
|
||||||
|
Label::new(position.0..position.1)
|
||||||
|
.with_message(format!("Expected boolean but got {actual}.")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ValidationError::ExpectedIntegerOrFloat => {
|
ValidationError::ExpectedIntegerOrFloat => {
|
||||||
builder.add_label(Label::new(0..0).with_message("Expected integer or float."));
|
builder.add_label(
|
||||||
|
Label::new(position.0..position.1)
|
||||||
|
.with_message("Expected integer or float."),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ValidationError::RwLockPoison(_) => todo!(),
|
ValidationError::RwLockPoison(_) => todo!(),
|
||||||
ValidationError::TypeCheck {
|
ValidationError::TypeCheck {
|
||||||
@ -95,7 +101,7 @@ impl Error {
|
|||||||
ValidationError::CannotIndex(_) => todo!(),
|
ValidationError::CannotIndex(_) => todo!(),
|
||||||
ValidationError::CannotIndexWith(_, _) => todo!(),
|
ValidationError::CannotIndexWith(_, _) => todo!(),
|
||||||
ValidationError::InterpreterExpectedReturn => todo!(),
|
ValidationError::InterpreterExpectedReturn => todo!(),
|
||||||
ValidationError::ExpectedFunction => todo!(),
|
ValidationError::ExpectedFunction { actual, position } => todo!(),
|
||||||
ValidationError::ExpectedValue => todo!(),
|
ValidationError::ExpectedValue => todo!(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -144,8 +150,14 @@ impl From<ValidationError> for RuntimeError {
|
|||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
CannotIndex(Type),
|
CannotIndex(Type),
|
||||||
CannotIndexWith(Type, Type),
|
CannotIndexWith(Type, Type),
|
||||||
ExpectedBoolean,
|
ExpectedBoolean {
|
||||||
ExpectedFunction,
|
actual: Type,
|
||||||
|
position: SourcePosition,
|
||||||
|
},
|
||||||
|
ExpectedFunction {
|
||||||
|
actual: Type,
|
||||||
|
position: SourcePosition,
|
||||||
|
},
|
||||||
ExpectedIntegerOrFloat,
|
ExpectedIntegerOrFloat,
|
||||||
ExpectedValue,
|
ExpectedValue,
|
||||||
InterpreterExpectedReturn,
|
InterpreterExpectedReturn,
|
||||||
|
@ -386,9 +386,18 @@ pub fn parser<'src>() -> DustParser<'src> {
|
|||||||
|
|
||||||
let r#while = just(Token::Keyword("while"))
|
let r#while = just(Token::Keyword("while"))
|
||||||
.ignore_then(positioned_expression.clone())
|
.ignore_then(positioned_expression.clone())
|
||||||
.then(block.clone())
|
.then(
|
||||||
.map_with(|(expression, block), state| {
|
positioned_statement
|
||||||
Statement::While(While::new(expression, block)).with_position(state.span())
|
.clone()
|
||||||
|
.repeated()
|
||||||
|
.collect()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::Control(Control::CurlyOpen)),
|
||||||
|
just(Token::Control(Control::CurlyClose)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map_with(|(expression, statements), state| {
|
||||||
|
Statement::While(While::new(expression, statements)).with_position(state.span())
|
||||||
});
|
});
|
||||||
|
|
||||||
let if_else = just(Token::Keyword("if"))
|
let if_else = just(Token::Keyword("if"))
|
||||||
@ -431,14 +440,14 @@ mod tests {
|
|||||||
parse(&lex("while true { output('hi') }").unwrap()).unwrap()[0].node,
|
parse(&lex("while true { output('hi') }").unwrap()).unwrap()[0].node,
|
||||||
Statement::While(While::new(
|
Statement::While(While::new(
|
||||||
Expression::Value(ValueNode::Boolean(true)).with_position((6, 11)),
|
Expression::Value(ValueNode::Boolean(true)).with_position((6, 11)),
|
||||||
Block::new(vec![Statement::Expression(Expression::FunctionCall(
|
vec![
|
||||||
FunctionCall::new(
|
Statement::Expression(Expression::FunctionCall(FunctionCall::new(
|
||||||
Expression::Identifier(Identifier::new("output")).with_position((13, 19)),
|
Expression::Identifier(Identifier::new("output")).with_position((13, 19)),
|
||||||
vec![Expression::Value(ValueNode::String("hi".to_string()))
|
vec![Expression::Value(ValueNode::String("hi".to_string()))
|
||||||
.with_position((20, 24))]
|
.with_position((20, 24))]
|
||||||
)
|
)))
|
||||||
))
|
.with_position((13, 26))
|
||||||
.with_position((13, 26))])
|
]
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
24
src/value.rs
24
src/value.rs
@ -93,28 +93,12 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_boolean(&self) -> Result<bool, ValidationError> {
|
pub fn as_boolean(&self) -> Option<bool> {
|
||||||
if let ValueInner::Boolean(boolean) = self.0.as_ref() {
|
if let ValueInner::Boolean(boolean) = self.0.as_ref() {
|
||||||
return Ok(*boolean);
|
Some(*boolean)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ValidationError::ExpectedBoolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_number(&self) -> Result<bool, ValidationError> {
|
|
||||||
if let ValueInner::Boolean(boolean) = self.0.as_ref() {
|
|
||||||
return Ok(*boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ValidationError::ExpectedBoolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_function(&self) -> Result<&Function, ValidationError> {
|
|
||||||
if let ValueInner::Function(function) = self.0.as_ref() {
|
|
||||||
return Ok(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ValidationError::ExpectedFunction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_list(&self) -> Option<&Vec<Value>> {
|
pub fn as_list(&self) -> Option<&Vec<Value>> {
|
||||||
|
Loading…
Reference in New Issue
Block a user