Test and implement basic garbage collection
This commit is contained in:
parent
78228ce8d6
commit
2463e44301
@ -6,13 +6,15 @@ use crate::{Identifier, Type, Value};
|
|||||||
/// Garbage-collecting context for variables.
|
/// Garbage-collecting context for variables.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub variables: HashMap<Identifier, (VariableData, UsageData)>,
|
variables: HashMap<Identifier, (VariableData, UsageData)>,
|
||||||
|
is_garbage_collected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
variables: HashMap::new(),
|
variables: HashMap::new(),
|
||||||
|
is_garbage_collected: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +33,23 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_variable_data(&self, identifier: &Identifier) -> Option<&VariableData> {
|
||||||
|
match self.variables.get(identifier) {
|
||||||
|
Some((variable_data, _)) => Some(variable_data),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_value(&self, identifier: &Identifier) -> Option<&Value> {
|
||||||
|
match self.variables.get(identifier) {
|
||||||
|
Some((VariableData::Value(value), _)) => Some(value),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn use_value(&mut self, identifier: &Identifier) -> Option<&Value> {
|
pub fn use_value(&mut self, identifier: &Identifier) -> Option<&Value> {
|
||||||
|
self.is_garbage_collected = false;
|
||||||
|
|
||||||
match self.variables.get_mut(identifier) {
|
match self.variables.get_mut(identifier) {
|
||||||
Some((VariableData::Value(value), usage_data)) => {
|
Some((VariableData::Value(value), usage_data)) => {
|
||||||
usage_data.used += 1;
|
usage_data.used += 1;
|
||||||
@ -42,14 +60,9 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_variable_data(&self, identifier: &Identifier) -> Option<&VariableData> {
|
|
||||||
match self.variables.get(identifier) {
|
|
||||||
Some((variable_data, _)) => Some(variable_data),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_type(&mut self, identifier: Identifier, r#type: Type) {
|
pub fn set_type(&mut self, identifier: Identifier, r#type: Type) {
|
||||||
|
self.is_garbage_collected = false;
|
||||||
|
|
||||||
self.variables.insert(
|
self.variables.insert(
|
||||||
identifier,
|
identifier,
|
||||||
(VariableData::Type(r#type), UsageData::default()),
|
(VariableData::Type(r#type), UsageData::default()),
|
||||||
@ -57,6 +70,8 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
self.variables.insert(
|
self.variables.insert(
|
||||||
identifier,
|
identifier,
|
||||||
(VariableData::Value(value), UsageData::default()),
|
(VariableData::Value(value), UsageData::default()),
|
||||||
@ -64,8 +79,11 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_garbage(&mut self) {
|
pub fn collect_garbage(&mut self) {
|
||||||
|
if !self.is_garbage_collected {
|
||||||
self.variables
|
self.variables
|
||||||
.retain(|_, (_, usage_data)| usage_data.used < usage_data.allowed_uses);
|
.retain(|_, (_, usage_data)| usage_data.used < usage_data.allowed_uses);
|
||||||
|
self.variables.shrink_to_fit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_allowed_use(&mut self, identifier: &Identifier) -> bool {
|
pub fn add_allowed_use(&mut self, identifier: &Identifier) -> bool {
|
||||||
@ -91,8 +109,38 @@ pub enum VariableData {
|
|||||||
Type(Type),
|
Type(Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UsageData {
|
pub struct UsageData {
|
||||||
pub allowed_uses: u16,
|
pub allowed_uses: u16,
|
||||||
pub used: u16,
|
pub used: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for UsageData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
allowed_uses: 1,
|
||||||
|
used: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::vm::run_with_context;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_removes_unused_variables() {
|
||||||
|
let source = "
|
||||||
|
x = 5
|
||||||
|
y = 10
|
||||||
|
z = x + y
|
||||||
|
";
|
||||||
|
let mut context = Context::new();
|
||||||
|
|
||||||
|
run_with_context(source, &mut context).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(context.variables.len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,6 +28,26 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
|||||||
.map_err(|vm_error| DustError::VmError { vm_error, source })
|
.map_err(|vm_error| DustError::VmError { vm_error, source })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_with_context<'src>(
|
||||||
|
source: &'src str,
|
||||||
|
context: &mut Context,
|
||||||
|
) -> Result<Option<Value>, DustError<'src>> {
|
||||||
|
let abstract_syntax_tree = parse(source)?;
|
||||||
|
let mut analyzer = Analyzer::new(&abstract_syntax_tree, context);
|
||||||
|
|
||||||
|
analyzer
|
||||||
|
.analyze()
|
||||||
|
.map_err(|analyzer_error| DustError::AnalyzerError {
|
||||||
|
analyzer_error,
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut vm = Vm::new(abstract_syntax_tree);
|
||||||
|
|
||||||
|
vm.run(context)
|
||||||
|
.map_err(|vm_error| DustError::VmError { vm_error, source })
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Vm {
|
pub struct Vm {
|
||||||
abstract_tree: AbstractSyntaxTree,
|
abstract_tree: AbstractSyntaxTree,
|
||||||
}
|
}
|
||||||
@ -40,17 +60,20 @@ 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_value = None;
|
let mut previous_value = None;
|
||||||
|
|
||||||
while let Some(node) = self.abstract_tree.nodes.pop_front() {
|
while let Some(statement) = self.abstract_tree.nodes.pop_front() {
|
||||||
previous_value = self.run_node(node, context)?;
|
previous_value = self.run_statement(statement, context, true)?;
|
||||||
|
|
||||||
|
context.collect_garbage();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(previous_value)
|
Ok(previous_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_node(
|
fn run_statement(
|
||||||
&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 {
|
||||||
@ -69,7 +92,9 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = if let Some(value) = self.run_node(*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 {
|
||||||
@ -90,7 +115,9 @@ impl Vm {
|
|||||||
position: left.position,
|
position: left.position,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let right_value = if let Some(value) = self.run_node(*right, context)? {
|
let right_value = if let Some(value) =
|
||||||
|
self.run_statement(*right, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -118,14 +145,16 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let left_position = left.position;
|
let left_position = left.position;
|
||||||
let left_value = if let Some(value) = self.run_node(*left, context)? {
|
let left_value =
|
||||||
|
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_node(*right, context)? {
|
let right_value =
|
||||||
|
if let Some(value) = self.run_statement(*right, context, collect_garbage)? {
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -160,7 +189,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_node(statement, context)?;
|
previous_value = self.run_statement(statement, context, collect_garbage)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(previous_value)
|
Ok(previous_value)
|
||||||
@ -175,7 +204,9 @@ impl Vm {
|
|||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
let position = node.position;
|
let position = node.position;
|
||||||
let value = if let Some(value) = self.run_node(node, context)? {
|
let value = if let Some(value) =
|
||||||
|
self.run_statement(node, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -205,7 +236,9 @@ 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) = self.run_node(*function_node, context)? {
|
let function_value = if let Some(value) =
|
||||||
|
self.run_statement(*function_node, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -226,7 +259,9 @@ 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) = self.run_node(node, context)? {
|
let value = if let Some(value) =
|
||||||
|
self.run_statement(node, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -243,7 +278,13 @@ impl Vm {
|
|||||||
Ok(function.clone().call(None, value_parameters, context)?)
|
Ok(function.clone().call(None, value_parameters, context)?)
|
||||||
}
|
}
|
||||||
Statement::Identifier(identifier) => {
|
Statement::Identifier(identifier) => {
|
||||||
if let Some(value) = context.use_value(&identifier) {
|
let value_option = if collect_garbage {
|
||||||
|
context.use_value(&identifier)
|
||||||
|
} else {
|
||||||
|
context.get_value(&identifier)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(value) = value_option {
|
||||||
Ok(Some(value.clone()))
|
Ok(Some(value.clone()))
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::UndefinedVariable {
|
Err(VmError::UndefinedVariable {
|
||||||
@ -253,7 +294,9 @@ 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) = self.run_node(*condition, context)? {
|
let condition_value = if let Some(value) =
|
||||||
|
self.run_statement(*condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -269,7 +312,7 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
self.run_node(*body, context)?;
|
self.run_statement(*body, context, collect_garbage)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -280,7 +323,9 @@ impl Vm {
|
|||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) = self.run_node(*condition, context)? {
|
let condition_value = if let Some(value) =
|
||||||
|
self.run_statement(*condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -290,9 +335,9 @@ impl Vm {
|
|||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_node(*if_body, context)
|
self.run_statement(*if_body, context, collect_garbage)
|
||||||
} else {
|
} else {
|
||||||
self.run_node(*else_body, context)
|
self.run_statement(*else_body, context, collect_garbage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedBoolean {
|
Err(VmError::ExpectedBoolean {
|
||||||
@ -306,7 +351,9 @@ impl Vm {
|
|||||||
else_ifs,
|
else_ifs,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) = self.run_node(*condition, context)? {
|
let condition_value = if let Some(value) =
|
||||||
|
self.run_statement(*condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -316,12 +363,13 @@ impl Vm {
|
|||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_node(*if_body, context)
|
self.run_statement(*if_body, context, collect_garbage)
|
||||||
} 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 =
|
let condition_value = if let Some(value) =
|
||||||
if let Some(value) = self.run_node(condition, context)? {
|
self.run_statement(condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -337,7 +385,7 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
self.run_node(body, context)?;
|
self.run_statement(body, context, collect_garbage)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +404,9 @@ impl Vm {
|
|||||||
else_body,
|
else_body,
|
||||||
} => {
|
} => {
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
let condition_value = if let Some(value) = self.run_node(*condition, context)? {
|
let condition_value = if let Some(value) =
|
||||||
|
self.run_statement(*condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -366,12 +416,13 @@ impl Vm {
|
|||||||
|
|
||||||
if let Some(condition) = condition_value.as_boolean() {
|
if let Some(condition) = condition_value.as_boolean() {
|
||||||
if condition {
|
if condition {
|
||||||
self.run_node(*if_body, context)
|
self.run_statement(*if_body, context, collect_garbage)
|
||||||
} 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 =
|
let condition_value = if let Some(value) =
|
||||||
if let Some(value) = self.run_node(condition, context)? {
|
self.run_statement(condition, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -387,11 +438,11 @@ impl Vm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
return self.run_node(body, context);
|
return self.run_statement(body, context, collect_garbage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_node(*else_body, context)
|
self.run_statement(*else_body, context, collect_garbage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedBoolean {
|
Err(VmError::ExpectedBoolean {
|
||||||
@ -404,7 +455,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_node(node, context)? {
|
if let Some(value) = self.run_statement(node, context, collect_garbage)? {
|
||||||
Ok(value)
|
Ok(value)
|
||||||
} else {
|
} else {
|
||||||
Err(VmError::ExpectedValue { position: span })
|
Err(VmError::ExpectedValue { position: span })
|
||||||
@ -426,7 +477,9 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
let position = value_node.position;
|
let position = value_node.position;
|
||||||
let value = if let Some(value) = self.run_node(value_node, context)? {
|
let value = if let Some(value) =
|
||||||
|
self.run_statement(value_node, context, collect_garbage)?
|
||||||
|
{
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue { position });
|
return Err(VmError::ExpectedValue { position });
|
||||||
@ -438,13 +491,14 @@ impl Vm {
|
|||||||
Ok(Some(Value::map(values)))
|
Ok(Some(Value::map(values)))
|
||||||
}
|
}
|
||||||
Statement::Nil(node) => {
|
Statement::Nil(node) => {
|
||||||
let _return = self.run_node(*node, context)?;
|
let _return = self.run_statement(*node, context, collect_garbage)?;
|
||||||
|
|
||||||
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 = if let Some(value) = self.run_node(*left, context)? {
|
let left_value =
|
||||||
|
if let Some(value) = self.run_statement(*left, context, collect_garbage)? {
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
return Err(VmError::ExpectedValue {
|
return Err(VmError::ExpectedValue {
|
||||||
@ -480,7 +534,9 @@ impl Vm {
|
|||||||
|
|
||||||
let condition_position = condition.position;
|
let condition_position = condition.position;
|
||||||
|
|
||||||
while let Some(condition_value) = self.run_node(*condition.clone(), context)? {
|
while let Some(condition_value) =
|
||||||
|
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;
|
||||||
@ -491,7 +547,7 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return_value = self.run_node(*body.clone(), context)?;
|
return_value = self.run_statement(*body.clone(), context, false)?;
|
||||||
|
|
||||||
if return_value.is_some() {
|
if return_value.is_some() {
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user