Compare commits
2 Commits
2463e44301
...
74cfef1832
Author | SHA1 | Date | |
---|---|---|---|
74cfef1832 | |||
0e3a3e94c8 |
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -152,6 +152,7 @@ version = "0.5.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets",
|
"annotate-snippets",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
@ -184,9 +185,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.3"
|
version = "0.11.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -232,9 +233,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.21"
|
version = "0.4.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
|
@ -11,7 +11,11 @@ repository.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
annotate-snippets = "0.11.4"
|
annotate-snippets = "0.11.4"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
|
log = "0.4.22"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rayon = "1.9.0"
|
rayon = "1.9.0"
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
serde_json = "1.0.117"
|
serde_json = "1.0.117"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.11.5"
|
||||||
|
@ -127,7 +127,7 @@ impl Statement {
|
|||||||
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(),
|
||||||
Statement::Constant(value) => Some(value.r#type(context)),
|
Statement::Constant(value) => Some(value.r#type(context)),
|
||||||
Statement::FunctionCall { function, .. } => function.inner.expected_type(context),
|
Statement::FunctionCall { function, .. } => function.inner.expected_type(context),
|
||||||
Statement::Identifier(identifier) => context.get_type(identifier).cloned(),
|
Statement::Identifier(identifier) => context.get_type(identifier),
|
||||||
Statement::If { .. } => None,
|
Statement::If { .. } => None,
|
||||||
Statement::IfElse { if_body, .. } => if_body.inner.expected_type(context),
|
Statement::IfElse { if_body, .. } => if_body.inner.expected_type(context),
|
||||||
Statement::IfElseIf { .. } => None,
|
Statement::IfElseIf { .. } => None,
|
||||||
|
@ -67,38 +67,38 @@ impl<'a> Analyzer<'a> {
|
|||||||
|
|
||||||
pub fn analyze(&mut self) -> Result<(), AnalyzerError> {
|
pub fn analyze(&mut self) -> Result<(), AnalyzerError> {
|
||||||
for node in &self.abstract_tree.nodes {
|
for node in &self.abstract_tree.nodes {
|
||||||
self.analyze_node(node)?;
|
self.analyze_statement(node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyze_node(&mut self, node: &Node<Statement>) -> Result<(), AnalyzerError> {
|
fn analyze_statement(&mut self, node: &Node<Statement>) -> Result<(), AnalyzerError> {
|
||||||
match &node.inner {
|
match &node.inner {
|
||||||
Statement::BinaryOperation {
|
Statement::BinaryOperation {
|
||||||
left,
|
left,
|
||||||
operator,
|
operator,
|
||||||
right,
|
right,
|
||||||
} => {
|
} => {
|
||||||
if let BinaryOperator::Assign = operator.inner {
|
if let BinaryOperator::Assign | BinaryOperator::AddAssign = operator.inner {
|
||||||
|
self.analyze_statement(right)?;
|
||||||
|
|
||||||
if let Statement::Identifier(identifier) = &left.inner {
|
if let Statement::Identifier(identifier) = &left.inner {
|
||||||
self.analyze_node(right)?;
|
let right_type = right.inner.expected_type(self.context).ok_or(
|
||||||
|
AnalyzerError::ExpectedValue {
|
||||||
let right_type = right.inner.expected_type(self.context);
|
|
||||||
|
|
||||||
self.context.set_type(
|
|
||||||
identifier.clone(),
|
|
||||||
right_type.ok_or(AnalyzerError::ExpectedValue {
|
|
||||||
actual: right.as_ref().clone(),
|
actual: right.as_ref().clone(),
|
||||||
})?,
|
},
|
||||||
);
|
)?;
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.set_type(identifier.clone(), right_type, left.position);
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(left)?;
|
self.analyze_statement(left)?;
|
||||||
self.analyze_node(right)?;
|
self.analyze_statement(right)?;
|
||||||
|
|
||||||
let left_type = left.inner.expected_type(self.context);
|
let left_type = left.inner.expected_type(self.context);
|
||||||
let right_type = right.inner.expected_type(self.context);
|
let right_type = right.inner.expected_type(self.context);
|
||||||
@ -131,7 +131,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
}
|
}
|
||||||
Statement::Block(statements) => {
|
Statement::Block(statements) => {
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
self.analyze_node(statement)?;
|
self.analyze_statement(statement)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::BuiltInFunctionCall {
|
Statement::BuiltInFunctionCall {
|
||||||
@ -143,7 +143,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
|
|
||||||
if let Some(arguments) = value_arguments {
|
if let Some(arguments) = value_arguments {
|
||||||
for argument in arguments {
|
for argument in arguments {
|
||||||
self.analyze_node(argument)?;
|
self.analyze_statement(argument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if arguments.len() != value_parameters.len() {
|
if arguments.len() != value_parameters.len() {
|
||||||
@ -195,16 +195,16 @@ impl<'a> Analyzer<'a> {
|
|||||||
value_arguments,
|
value_arguments,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
self.analyze_node(function)?;
|
self.analyze_statement(function)?;
|
||||||
|
|
||||||
if let Some(arguments) = value_arguments {
|
if let Some(arguments) = value_arguments {
|
||||||
for argument in arguments {
|
for argument in arguments {
|
||||||
self.analyze_node(argument)?;
|
self.analyze_statement(argument)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Identifier(identifier) => {
|
Statement::Identifier(identifier) => {
|
||||||
let exists = self.context.add_allowed_use(identifier);
|
let exists = self.context.update_last_position(identifier, node.position);
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return Err(AnalyzerError::UndefinedVariable {
|
return Err(AnalyzerError::UndefinedVariable {
|
||||||
@ -213,7 +213,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::If { condition, body } => {
|
Statement::If { condition, body } => {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -223,14 +223,14 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(body)?;
|
self.analyze_statement(body)?;
|
||||||
}
|
}
|
||||||
Statement::IfElse {
|
Statement::IfElse {
|
||||||
condition,
|
condition,
|
||||||
if_body,
|
if_body,
|
||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -240,15 +240,15 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(if_body)?;
|
self.analyze_statement(if_body)?;
|
||||||
self.analyze_node(else_body)?;
|
self.analyze_statement(else_body)?;
|
||||||
}
|
}
|
||||||
Statement::IfElseIf {
|
Statement::IfElseIf {
|
||||||
condition,
|
condition,
|
||||||
if_body,
|
if_body,
|
||||||
else_ifs,
|
else_ifs,
|
||||||
} => {
|
} => {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -258,10 +258,10 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(if_body)?;
|
self.analyze_statement(if_body)?;
|
||||||
|
|
||||||
for (condition, body) in else_ifs {
|
for (condition, body) in else_ifs {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -271,7 +271,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(body)?;
|
self.analyze_statement(body)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::IfElseIfElse {
|
Statement::IfElseIfElse {
|
||||||
@ -280,7 +280,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
else_ifs,
|
else_ifs,
|
||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -290,10 +290,10 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(if_body)?;
|
self.analyze_statement(if_body)?;
|
||||||
|
|
||||||
for (condition, body) in else_ifs {
|
for (condition, body) in else_ifs {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
// Condition is valid
|
// Condition is valid
|
||||||
@ -303,31 +303,31 @@ impl<'a> Analyzer<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(body)?;
|
self.analyze_statement(body)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analyze_node(else_body)?;
|
self.analyze_statement(else_body)?;
|
||||||
}
|
}
|
||||||
Statement::List(statements) => {
|
Statement::List(statements) => {
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
self.analyze_node(statement)?;
|
self.analyze_statement(statement)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Map(properties) => {
|
Statement::Map(properties) => {
|
||||||
for (_key, value_node) in properties {
|
for (_key, value_node) in properties {
|
||||||
self.analyze_node(value_node)?;
|
self.analyze_statement(value_node)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => {
|
Statement::Nil(node) => {
|
||||||
self.analyze_node(node)?;
|
self.analyze_statement(node)?;
|
||||||
}
|
}
|
||||||
Statement::PropertyAccess(left, right) => {
|
Statement::PropertyAccess(left, right) => {
|
||||||
self.analyze_node(left)?;
|
self.analyze_statement(left)?;
|
||||||
|
|
||||||
if let Statement::Identifier(_) = right.inner {
|
if let Statement::Identifier(_) = right.inner {
|
||||||
// Do not expect a value for property accessors
|
// Do not expect a value for property accessors
|
||||||
} else {
|
} else {
|
||||||
self.analyze_node(right)?;
|
self.analyze_statement(right)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Type::List { .. }) = left.inner.expected_type(self.context) {
|
if let Some(Type::List { .. }) = left.inner.expected_type(self.context) {
|
||||||
@ -353,8 +353,8 @@ impl<'a> Analyzer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::While { condition, body } => {
|
Statement::While { condition, body } => {
|
||||||
self.analyze_node(condition)?;
|
self.analyze_statement(condition)?;
|
||||||
self.analyze_node(body)?;
|
self.analyze_statement(body)?;
|
||||||
|
|
||||||
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
if let Some(Type::Boolean) = condition.inner.expected_type(self.context) {
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,94 +1,111 @@
|
|||||||
//! Garbage-collecting context for variables.
|
//! Garbage-collecting context for variables.
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Identifier, Type, Value};
|
use crate::{Identifier, Span, Type, Value};
|
||||||
|
|
||||||
/// Garbage-collecting context for variables.
|
/// Garbage-collecting context for variables.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
variables: HashMap<Identifier, (VariableData, UsageData)>,
|
variables: Arc<RwLock<HashMap<Identifier, (VariableData, Span)>>>,
|
||||||
is_garbage_collected: bool,
|
is_garbage_collected_to: Arc<RwLock<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
variables: HashMap::new(),
|
variables: Arc::new(RwLock::new(HashMap::new())),
|
||||||
is_garbage_collected: true,
|
is_garbage_collected_to: Arc::new(RwLock::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn variable_count(&self) -> usize {
|
||||||
|
self.variables.read().unwrap().len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, identifier: &Identifier) -> bool {
|
pub fn contains(&self, identifier: &Identifier) -> bool {
|
||||||
self.variables.contains_key(identifier)
|
self.variables.read().unwrap().contains_key(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, identifier: &Identifier) -> Option<&(VariableData, UsageData)> {
|
pub fn get(&self, identifier: &Identifier) -> Option<(VariableData, Span)> {
|
||||||
self.variables.get(identifier)
|
self.variables.read().unwrap().get(identifier).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_type(&self, identifier: &Identifier) -> Option<&Type> {
|
pub fn get_type(&self, identifier: &Identifier) -> Option<Type> {
|
||||||
match self.variables.get(identifier) {
|
match self.variables.read().unwrap().get(identifier) {
|
||||||
Some((VariableData::Type(r#type), _)) => Some(r#type),
|
Some((VariableData::Type(r#type), _)) => Some(r#type.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_variable_data(&self, identifier: &Identifier) -> Option<&VariableData> {
|
pub fn get_variable_data(&self, identifier: &Identifier) -> Option<VariableData> {
|
||||||
match self.variables.get(identifier) {
|
match self.variables.read().unwrap().get(identifier) {
|
||||||
Some((variable_data, _)) => Some(variable_data),
|
Some((variable_data, _)) => Some(variable_data.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_value(&self, identifier: &Identifier) -> Option<&Value> {
|
pub fn get_value(&self, identifier: &Identifier) -> Option<Value> {
|
||||||
match self.variables.get(identifier) {
|
match self.variables.read().unwrap().get(identifier) {
|
||||||
Some((VariableData::Value(value), _)) => Some(value),
|
Some((VariableData::Value(value), _)) => Some(value.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_value(&mut self, identifier: &Identifier) -> Option<&Value> {
|
pub fn set_type(&mut self, identifier: Identifier, r#type: Type, position: Span) {
|
||||||
self.is_garbage_collected = false;
|
log::trace!("Setting {identifier} to type {type} at {position:?}");
|
||||||
|
|
||||||
match self.variables.get_mut(identifier) {
|
self.variables
|
||||||
Some((VariableData::Value(value), usage_data)) => {
|
.write()
|
||||||
usage_data.used += 1;
|
.unwrap()
|
||||||
|
.insert(identifier, (VariableData::Type(r#type), position));
|
||||||
Some(value)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_type(&mut self, identifier: Identifier, r#type: Type) {
|
|
||||||
self.is_garbage_collected = false;
|
|
||||||
|
|
||||||
self.variables.insert(
|
|
||||||
identifier,
|
|
||||||
(VariableData::Type(r#type), UsageData::default()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_value(&mut self, identifier: Identifier, value: Value) {
|
pub fn set_value(&mut self, identifier: Identifier, value: Value) {
|
||||||
self.is_garbage_collected = false;
|
log::trace!("Setting {identifier} to value {value}");
|
||||||
|
|
||||||
self.variables.insert(
|
let mut variables = self.variables.write().unwrap();
|
||||||
identifier,
|
|
||||||
(VariableData::Value(value), UsageData::default()),
|
let last_position = variables
|
||||||
);
|
.get(&identifier)
|
||||||
|
.map(|(_, last_position)| *last_position)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
variables.insert(identifier, (VariableData::Value(value), last_position));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_garbage(&mut self) {
|
pub fn collect_garbage(&mut self, current_position: usize) {
|
||||||
if !self.is_garbage_collected {
|
log::trace!("Collecting garbage up to {current_position}");
|
||||||
self.variables
|
|
||||||
.retain(|_, (_, usage_data)| usage_data.used < usage_data.allowed_uses);
|
let mut is_garbage_collected_to = self.is_garbage_collected_to.write().unwrap();
|
||||||
self.variables.shrink_to_fit();
|
|
||||||
|
if current_position < *is_garbage_collected_to {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut variables = self.variables.write().unwrap();
|
||||||
|
|
||||||
|
variables.retain(|identifier, (_, last_used)| {
|
||||||
|
let should_drop = current_position >= last_used.1;
|
||||||
|
|
||||||
|
if should_drop {
|
||||||
|
log::trace!("Removing {identifier}");
|
||||||
|
}
|
||||||
|
|
||||||
|
!should_drop
|
||||||
|
});
|
||||||
|
variables.shrink_to_fit();
|
||||||
|
|
||||||
|
*is_garbage_collected_to = current_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_allowed_use(&mut self, identifier: &Identifier) -> bool {
|
pub fn update_last_position(&mut self, identifier: &Identifier, position: Span) -> bool {
|
||||||
if let Some((_, usage_data)) = self.variables.get_mut(identifier) {
|
if let Some((_, last_position)) = self.variables.write().unwrap().get_mut(identifier) {
|
||||||
usage_data.allowed_uses += 1;
|
*last_position = position;
|
||||||
|
|
||||||
|
log::trace!("Updating {identifier}'s last position to {position:?}");
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@ -109,21 +126,6 @@ pub enum VariableData {
|
|||||||
Type(Type),
|
Type(Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UsageData {
|
|
||||||
pub allowed_uses: u16,
|
|
||||||
pub used: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UsageData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
allowed_uses: 1,
|
|
||||||
used: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::vm::run_with_context;
|
use crate::vm::run_with_context;
|
||||||
@ -131,16 +133,35 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn context_removes_unused_variables() {
|
fn context_removes_used_variables() {
|
||||||
|
env_logger::builder().is_test(true).try_init().unwrap();
|
||||||
|
|
||||||
let source = "
|
let source = "
|
||||||
x = 5
|
x = 5
|
||||||
y = 10
|
y = 10
|
||||||
z = x + y
|
z = x + y
|
||||||
|
z
|
||||||
";
|
";
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
|
|
||||||
run_with_context(source, &mut context).unwrap();
|
run_with_context(source, &mut context).unwrap();
|
||||||
|
|
||||||
assert_eq!(context.variables.len(), 1);
|
assert_eq!(context.variable_count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_removes_variables_after_loop() {
|
||||||
|
let source = "
|
||||||
|
y = 1
|
||||||
|
z = 0
|
||||||
|
while z < 10 {
|
||||||
|
z = z + y
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let mut context = Context::new();
|
||||||
|
|
||||||
|
run_with_context(source, &mut context).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(context.variable_count(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ pub mod vm;
|
|||||||
pub use abstract_tree::{AbstractSyntaxTree, BinaryOperator, Node, Statement};
|
pub use abstract_tree::{AbstractSyntaxTree, BinaryOperator, Node, Statement};
|
||||||
pub use analyzer::{analyze, Analyzer, AnalyzerError};
|
pub use analyzer::{analyze, Analyzer, AnalyzerError};
|
||||||
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
|
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
|
||||||
pub use context::{Context, UsageData, VariableData};
|
pub use context::{Context, VariableData};
|
||||||
pub use dust_error::DustError;
|
pub use dust_error::DustError;
|
||||||
pub use identifier::Identifier;
|
pub use identifier::Identifier;
|
||||||
pub use lexer::{lex, LexError, Lexer};
|
pub use lexer::{lex, LexError, Lexer};
|
||||||
|
@ -11,21 +11,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||||
let abstract_syntax_tree = parse(source)?;
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
let mut analyzer = Analyzer::new(&abstract_syntax_tree, &mut context);
|
|
||||||
|
|
||||||
analyzer
|
run_with_context(source, &mut context)
|
||||||
.analyze()
|
|
||||||
.map_err(|analyzer_error| DustError::AnalyzerError {
|
|
||||||
analyzer_error,
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut vm = Vm::new(abstract_syntax_tree);
|
|
||||||
|
|
||||||
vm.run(&mut context)
|
|
||||||
.map_err(|vm_error| DustError::VmError { vm_error, source })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_context<'src>(
|
pub fn run_with_context<'src>(
|
||||||
@ -58,14 +46,21 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, context: &mut Context) -> Result<Option<Value>, VmError> {
|
pub fn run(&mut self, context: &mut Context) -> Result<Option<Value>, VmError> {
|
||||||
|
let mut previous_position = (0, 0);
|
||||||
let mut previous_value = None;
|
let mut previous_value = None;
|
||||||
|
|
||||||
while let Some(statement) = self.abstract_tree.nodes.pop_front() {
|
while let Some(statement) = self.abstract_tree.nodes.pop_front() {
|
||||||
previous_value = self.run_statement(statement, context, true)?;
|
let new_position = statement.position;
|
||||||
|
|
||||||
context.collect_garbage();
|
previous_value = self.run_statement(statement, context)?;
|
||||||
|
|
||||||
|
context.collect_garbage(previous_position.1);
|
||||||
|
|
||||||
|
previous_position = new_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.collect_garbage(previous_position.1);
|
||||||
|
|
||||||
Ok(previous_value)
|
Ok(previous_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +68,6 @@ impl Vm {
|
|||||||
&self,
|
&self,
|
||||||
node: Node<Statement>,
|
node: Node<Statement>,
|
||||||
context: &mut Context,
|
context: &mut Context,
|
||||||
collect_garbage: bool,
|
|
||||||
) -> Result<Option<Value>, VmError> {
|
) -> Result<Option<Value>, VmError> {
|
||||||
match node.inner {
|
match node.inner {
|
||||||
Statement::BinaryOperation {
|
Statement::BinaryOperation {
|
||||||
@ -91,10 +85,7 @@ impl Vm {
|
|||||||
position: left.position,
|
position: left.position,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
let value = if let Some(value) = self.run_statement(*right, context)? {
|
||||||
let value = if let Some(value) =
|
|
||||||
self.run_statement(*right, context, collect_garbage)?
|
|
||||||
{
|
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -108,30 +99,30 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let BinaryOperator::AddAssign = operator.inner {
|
if let BinaryOperator::AddAssign = operator.inner {
|
||||||
let identifier = if let Statement::Identifier(identifier) = left.inner {
|
let (identifier, left_value) =
|
||||||
identifier
|
if let Statement::Identifier(identifier) = left.inner {
|
||||||
} else {
|
let value = context.get_value(&identifier).ok_or_else(|| {
|
||||||
return Err(VmError::ExpectedIdentifier {
|
VmError::UndefinedVariable {
|
||||||
position: left.position,
|
identifier: Node::new(
|
||||||
});
|
Statement::Identifier(identifier.clone()),
|
||||||
};
|
left.position,
|
||||||
let right_value = if let Some(value) =
|
),
|
||||||
self.run_statement(*right, context, collect_garbage)?
|
}
|
||||||
{
|
})?;
|
||||||
|
|
||||||
|
(identifier, value)
|
||||||
|
} else {
|
||||||
|
return Err(VmError::ExpectedIdentifier {
|
||||||
|
position: left.position,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let right_value = if let Some(value) = self.run_statement(*right, context)? {
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
position: right_position,
|
position: right_position,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let left_value = context.use_value(&identifier).ok_or_else(|| {
|
|
||||||
VmError::UndefinedVariable {
|
|
||||||
identifier: Node::new(
|
|
||||||
Statement::Identifier(identifier.clone()),
|
|
||||||
left.position,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let new_value = left_value.add(&right_value).map_err(|value_error| {
|
let new_value = left_value.add(&right_value).map_err(|value_error| {
|
||||||
VmError::ValueError {
|
VmError::ValueError {
|
||||||
error: value_error,
|
error: value_error,
|
||||||
@ -145,22 +136,20 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let left_position = left.position;
|
let left_position = left.position;
|
||||||
let left_value =
|
let left_value = if let Some(value) = self.run_statement(*left, context)? {
|
||||||
if let Some(value) = self.run_statement(*left, context, collect_garbage)? {
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: left_position,
|
||||||
position: left_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
let right_value = if let Some(value) = self.run_statement(*right, context)? {
|
||||||
let right_value =
|
value
|
||||||
if let Some(value) = self.run_statement(*right, context, collect_garbage)? {
|
} else {
|
||||||
value
|
return Err(VmError::ExpectedValue {
|
||||||
} else {
|
position: right_position,
|
||||||
return Err(VmError::ExpectedValue {
|
});
|
||||||
position: right_position,
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
match operator.inner {
|
match operator.inner {
|
||||||
BinaryOperator::Add => left_value.add(&right_value),
|
BinaryOperator::Add => left_value.add(&right_value),
|
||||||
@ -189,7 +178,7 @@ impl Vm {
|
|||||||
let mut previous_value = None;
|
let mut previous_value = None;
|
||||||
|
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
previous_value = self.run_statement(statement, context, collect_garbage)?;
|
previous_value = self.run_statement(statement, context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(previous_value)
|
Ok(previous_value)
|
||||||
@ -204,9 +193,7 @@ impl Vm {
|
|||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
let position = node.position;
|
let position = node.position;
|
||||||
let value = if let Some(value) =
|
let value = if let Some(value) = self.run_statement(node, context)? {
|
||||||
self.run_statement(node, context, collect_garbage)?
|
|
||||||
{
|
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -236,15 +223,14 @@ impl Vm {
|
|||||||
value_arguments: value_parameter_nodes,
|
value_arguments: value_parameter_nodes,
|
||||||
} => {
|
} => {
|
||||||
let function_position = function_node.position;
|
let function_position = function_node.position;
|
||||||
let function_value = if let Some(value) =
|
let function_value =
|
||||||
self.run_statement(*function_node, context, collect_garbage)?
|
if let Some(value) = self.run_statement(*function_node, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: function_position,
|
||||||
position: function_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
let function = if let Some(function) = function_value.as_function() {
|
let function = if let Some(function) = function_value.as_function() {
|
||||||
function
|
function
|
||||||
} else {
|
} else {
|
||||||
@ -259,9 +245,7 @@ impl Vm {
|
|||||||
|
|
||||||
for node in value_nodes {
|
for node in value_nodes {
|
||||||
let position = node.position;
|
let position = node.position;
|
||||||
let value = if let Some(value) =
|
let value = if let Some(value) = self.run_statement(node, context)? {
|
||||||
self.run_statement(node, context, collect_garbage)?
|
|
||||||
{
|
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -278,11 +262,7 @@ impl Vm {
|
|||||||
Ok(function.clone().call(None, value_parameters, context)?)
|
Ok(function.clone().call(None, value_parameters, context)?)
|
||||||
}
|
}
|
||||||
Statement::Identifier(identifier) => {
|
Statement::Identifier(identifier) => {
|
||||||
let value_option = if collect_garbage {
|
let value_option = context.get_value(&identifier);
|
||||||
context.use_value(&identifier)
|
|
||||||
} else {
|
|
||||||
context.get_value(&identifier)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value) = value_option {
|
if let Some(value) = value_option {
|
||||||
Ok(Some(value.clone()))
|
Ok(Some(value.clone()))
|
||||||
@ -294,15 +274,14 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
Statement::If { condition, body } => {
|
Statement::If { condition, body } => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(*condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(*condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
let condition = if let Some(condition) = condition_value.as_boolean() {
|
let condition = if let Some(condition) = condition_value.as_boolean() {
|
||||||
condition
|
condition
|
||||||
} else {
|
} else {
|
||||||
@ -312,7 +291,7 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
self.run_statement(*body, context, collect_garbage)?;
|
self.run_statement(*body, context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -323,21 +302,20 @@ impl Vm {
|
|||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(*condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(*condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_statement(*if_body, context, collect_garbage)
|
self.run_statement(*if_body, context)
|
||||||
} else {
|
} else {
|
||||||
self.run_statement(*else_body, context, collect_garbage)
|
self.run_statement(*else_body, context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedBoolean {
|
Err(VmError::ExpectedBoolean {
|
||||||
@ -351,31 +329,29 @@ impl Vm {
|
|||||||
else_ifs,
|
else_ifs,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(*condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(*condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_statement(*if_body, context, collect_garbage)
|
self.run_statement(*if_body, context)
|
||||||
} else {
|
} else {
|
||||||
for (condition, body) in else_ifs {
|
for (condition, body) in else_ifs {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
let condition = if let Some(condition) = condition_value.as_boolean() {
|
let condition = if let Some(condition) = condition_value.as_boolean() {
|
||||||
condition
|
condition
|
||||||
} else {
|
} else {
|
||||||
@ -385,7 +361,7 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
self.run_statement(body, context, collect_garbage)?;
|
self.run_statement(body, context)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,31 +380,29 @@ impl Vm {
|
|||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(*condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(*condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_statement(*if_body, context, collect_garbage)
|
self.run_statement(*if_body, context)
|
||||||
} else {
|
} else {
|
||||||
for (condition, body) in else_ifs {
|
for (condition, body) in else_ifs {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) =
|
let condition_value =
|
||||||
self.run_statement(condition, context, collect_garbage)?
|
if let Some(value) = self.run_statement(condition, context)? {
|
||||||
{
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: condition_position,
|
||||||
position: condition_position,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
let condition = if let Some(condition) = condition_value.as_boolean() {
|
let condition = if let Some(condition) = condition_value.as_boolean() {
|
||||||
condition
|
condition
|
||||||
} else {
|
} else {
|
||||||
@ -438,11 +412,11 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
return self.run_statement(body, context, collect_garbage);
|
return self.run_statement(body, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_statement(*else_body, context, collect_garbage)
|
self.run_statement(*else_body, context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedBoolean {
|
Err(VmError::ExpectedBoolean {
|
||||||
@ -455,7 +429,7 @@ impl Vm {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
let span = node.position;
|
let span = node.position;
|
||||||
if let Some(value) = self.run_statement(node, context, collect_garbage)? {
|
if let Some(value) = self.run_statement(node, context)? {
|
||||||
Ok(value)
|
Ok(value)
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedValue { position: span })
|
Err(VmError::ExpectedValue { position: span })
|
||||||
@ -477,9 +451,7 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
let position = value_node.position;
|
let position = value_node.position;
|
||||||
let value = if let Some(value) =
|
let value = if let Some(value) = self.run_statement(value_node, context)? {
|
||||||
self.run_statement(value_node, context, collect_garbage)?
|
|
||||||
{
|
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -491,20 +463,19 @@ impl Vm {
|
|||||||
Ok(Some(Value::map(values)))
|
Ok(Some(Value::map(values)))
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => {
|
Statement::Nil(node) => {
|
||||||
let _return = self.run_statement(*node, context, collect_garbage)?;
|
let _return = self.run_statement(*node, context)?;
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Statement::PropertyAccess(left, right) => {
|
Statement::PropertyAccess(left, right) => {
|
||||||
let left_span = left.position;
|
let left_span = left.position;
|
||||||
let left_value =
|
let left_value = if let Some(value) = self.run_statement(*left, context)? {
|
||||||
if let Some(value) = self.run_statement(*left, context, collect_garbage)? {
|
value
|
||||||
value
|
} else {
|
||||||
} else {
|
return Err(VmError::ExpectedValue {
|
||||||
return Err(VmError::ExpectedValue {
|
position: left_span,
|
||||||
position: left_span,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
let right_span = right.position;
|
let right_span = right.position;
|
||||||
|
|
||||||
if let (Some(list), Statement::Constant(value)) =
|
if let (Some(list), Statement::Constant(value)) =
|
||||||
@ -534,9 +505,7 @@ impl Vm {
|
|||||||
|
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
|
|
||||||
while let Some(condition_value) =
|
while let Some(condition_value) = self.run_statement(*condition.clone(), context)? {
|
||||||
self.run_statement(*condition.clone(), context, false)?
|
|
||||||
{
|
|
||||||
if let ValueInner::Boolean(condition_value) = condition_value.inner().as_ref() {
|
if let ValueInner::Boolean(condition_value) = condition_value.inner().as_ref() {
|
||||||
if !condition_value {
|
if !condition_value {
|
||||||
break;
|
break;
|
||||||
@ -547,7 +516,7 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return_value = self.run_statement(*body.clone(), context, false)?;
|
return_value = self.run_statement(*body.clone(), context)?;
|
||||||
|
|
||||||
if return_value.is_some() {
|
if return_value.is_some() {
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user