use crate::parser::ast::{
ClassData, ExpressionPatternType, FunctionBodyData, FunctionData, LiteralType, NumberLiteralType,
PatternType, StatementType, DeclarationType, VariableDeclarationData, VariableDeclarationKind,
BlockStatementData, ExpressionType, SwitchCaseData, CatchClauseData, ForIteratorData,
VariableDeclarationOrPattern,
};
use crate::runner::ds::error::JErrorType;
use crate::runner::ds::value::JsValue;
use crate::runner::plugin::types::EvalContext;
use super::types::{Completion, CompletionType, EvalResult};
use super::expression::{evaluate_expression, to_boolean, create_function_object, evaluate_class};
pub fn execute_statement(
stmt: &StatementType,
ctx: &mut EvalContext,
) -> EvalResult {
match stmt {
StatementType::EmptyStatement { .. } => {
Ok(Completion::normal())
}
StatementType::ExpressionStatement { expression, .. } => {
let value = evaluate_expression(expression, ctx)?;
Ok(Completion::normal_with_value(value))
}
StatementType::BlockStatement(block) => {
execute_block_statement(block, ctx)
}
StatementType::DeclarationStatement(decl) => {
execute_declaration(decl, ctx)
}
StatementType::IfStatement { test, consequent, alternate, .. } => {
execute_if_statement(test, consequent, alternate.as_ref().map(|a| a.as_ref()), ctx)
}
StatementType::WhileStatement { test, body, .. } => {
execute_while_statement(test, body, ctx)
}
StatementType::DoWhileStatement { test, body, .. } => {
execute_do_while_statement(body, test, ctx)
}
StatementType::ForStatement { init, test, update, body, .. } => {
execute_for_statement(init.as_ref(), test.as_ref().map(|t| t.as_ref()), update.as_ref().map(|u| u.as_ref()), body, ctx)
}
StatementType::ForInStatement(data) => {
execute_for_in_statement(data, ctx)
}
StatementType::ForOfStatement(data) => {
execute_for_of_statement(data, ctx)
}
StatementType::SwitchStatement { discriminant, cases, .. } => {
execute_switch_statement(discriminant, cases, ctx)
}
StatementType::BreakStatement { .. } => {
Ok(Completion::break_completion(None))
}
StatementType::ContinueStatement { .. } => {
Ok(Completion::continue_completion(None))
}
StatementType::ReturnStatement { argument, .. } => {
let value = if let Some(arg) = argument {
evaluate_expression(arg, ctx)?
} else {
JsValue::Undefined
};
Ok(Completion::return_value(value))
}
StatementType::ThrowStatement { argument, .. } => {
let value = evaluate_expression(argument, ctx)?;
Ok(Completion {
completion_type: CompletionType::Throw,
value: Some(value),
target: None,
})
}
StatementType::TryStatement { block, handler, finalizer, .. } => {
execute_try_statement(block, handler.as_ref(), finalizer.as_ref(), ctx)
}
StatementType::DebuggerStatement { .. } => {
Ok(Completion::normal())
}
StatementType::FunctionBody(body) => {
execute_function_body(body, ctx)
}
}
}
fn execute_block_statement(
block: &BlockStatementData,
ctx: &mut EvalContext,
) -> EvalResult {
ctx.push_block_scope();
let mut completion = Completion::normal();
for stmt in block.body.iter() {
completion = execute_statement(stmt, ctx)?;
if completion.is_abrupt() {
ctx.pop_block_scope();
return Ok(completion);
}
}
ctx.pop_block_scope();
Ok(completion)
}
fn execute_declaration(
decl: &DeclarationType,
ctx: &mut EvalContext,
) -> EvalResult {
match decl {
DeclarationType::VariableDeclaration(var_decl) => {
execute_variable_declaration(var_decl, ctx)
}
DeclarationType::FunctionOrGeneratorDeclaration(func_data) => {
execute_function_declaration(func_data, ctx)
}
DeclarationType::ClassDeclaration(class_data) => {
execute_class_declaration(class_data, ctx)
}
}
}
fn execute_variable_declaration(
var_decl: &VariableDeclarationData,
ctx: &mut EvalContext,
) -> EvalResult {
let is_const = matches!(var_decl.kind, VariableDeclarationKind::Const);
let is_var = matches!(var_decl.kind, VariableDeclarationKind::Var);
for declarator in &var_decl.declarations {
let value = if let Some(init) = &declarator.init {
evaluate_expression(init, ctx)?
} else {
JsValue::Undefined
};
bind_pattern(&declarator.id, value, ctx, is_const, is_var)?;
}
Ok(Completion::normal())
}
fn bind_pattern(
pattern: &PatternType,
value: JsValue,
ctx: &mut EvalContext,
is_const: bool,
is_var: bool,
) -> Result<(), JErrorType> {
match pattern {
PatternType::PatternWhichCanBeExpression(ExpressionPatternType::Identifier(id)) => {
let name = &id.name;
if is_var {
if ctx.has_var_binding(name) {
ctx.set_var_binding(name, value)?;
} else {
ctx.create_var_binding(name)?;
ctx.initialize_var_binding(name, value)?;
}
} else {
ctx.create_binding(name, is_const)?;
ctx.initialize_binding(name, value)?;
}
Ok(())
}
PatternType::ObjectPattern { properties, .. } => {
for prop in properties {
let prop_data = &prop.0;
let key_name = get_property_key_name(&prop_data.key)?;
let prop_value = get_property_value(&value, &key_name)?;
bind_pattern(&prop_data.value, prop_value, ctx, is_const, is_var)?;
}
Ok(())
}
PatternType::ArrayPattern { elements, .. } => {
for (index, element) in elements.iter().enumerate() {
if let Some(elem_pattern) = element {
if let PatternType::RestElement { argument, .. } = elem_pattern.as_ref() {
let rest_value = get_rest_elements(&value, index)?;
bind_pattern(argument, rest_value, ctx, is_const, is_var)?;
} else {
let elem_value = get_array_element(&value, index)?;
bind_pattern(elem_pattern, elem_value, ctx, is_const, is_var)?;
}
}
}
Ok(())
}
PatternType::AssignmentPattern { left, right, .. } => {
let actual_value = if matches!(value, JsValue::Undefined) {
evaluate_expression(right, ctx)?
} else {
value
};
bind_pattern(left, actual_value, ctx, is_const, is_var)
}
PatternType::RestElement { argument, .. } => {
bind_pattern(argument, value, ctx, is_const, is_var)
}
}
}
fn get_property_key_name(key_expr: &ExpressionType) -> Result<String, JErrorType> {
match key_expr {
ExpressionType::ExpressionWhichCanBePattern(ExpressionPatternType::Identifier(id)) => {
Ok(id.name.clone())
}
ExpressionType::Literal(lit_data) => {
match &lit_data.value {
LiteralType::StringLiteral(s) => Ok(s.clone()),
LiteralType::NumberLiteral(num) => {
match num {
NumberLiteralType::IntegerLiteral(n) => Ok(n.to_string()),
NumberLiteralType::FloatLiteral(n) => Ok(n.to_string()),
}
}
_ => Err(JErrorType::TypeError("Invalid property key in destructuring".to_string())),
}
}
_ => Err(JErrorType::TypeError("Computed property keys not yet supported in destructuring".to_string())),
}
}
fn get_property_value(obj: &JsValue, key: &str) -> Result<JsValue, JErrorType> {
use crate::runner::ds::object_property::{PropertyDescriptor, PropertyKey};
match obj {
JsValue::Object(obj_ref) => {
let borrowed = obj_ref.borrow();
let base = borrowed.as_js_object().get_object_base();
let prop_key = PropertyKey::Str(key.to_string());
if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
Ok(data.value.clone())
} else {
Ok(JsValue::Undefined)
}
}
_ => Err(JErrorType::TypeError("Cannot destructure non-object".to_string())),
}
}
fn get_array_element(arr: &JsValue, index: usize) -> Result<JsValue, JErrorType> {
use crate::runner::ds::object_property::{PropertyDescriptor, PropertyKey};
match arr {
JsValue::Object(obj_ref) => {
let borrowed = obj_ref.borrow();
let base = borrowed.as_js_object().get_object_base();
let prop_key = PropertyKey::Str(index.to_string());
if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
Ok(data.value.clone())
} else {
Ok(JsValue::Undefined)
}
}
_ => Err(JErrorType::TypeError("Cannot destructure non-array".to_string())),
}
}
fn get_rest_elements(arr: &JsValue, start_index: usize) -> Result<JsValue, JErrorType> {
use crate::runner::ds::object::{JsObject, JsObjectType, ObjectType};
use crate::runner::ds::object_property::{PropertyDescriptor, PropertyDescriptorData, PropertyKey};
use crate::runner::ds::value::JsNumberType;
use crate::runner::plugin::types::SimpleObject;
use std::cell::RefCell;
use std::rc::Rc;
match arr {
JsValue::Object(obj_ref) => {
let borrowed = obj_ref.borrow();
let base = borrowed.as_js_object().get_object_base();
let length = if let Some(PropertyDescriptor::Data(data)) =
base.properties.get(&PropertyKey::Str("length".to_string()))
{
match &data.value {
JsValue::Number(JsNumberType::Integer(n)) => *n as usize,
JsValue::Number(JsNumberType::Float(n)) => *n as usize,
_ => 0,
}
} else {
0
};
let mut rest_obj = SimpleObject::new();
let mut rest_index = 0;
for i in start_index..length {
let prop_key = PropertyKey::Str(i.to_string());
if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
rest_obj.get_object_base_mut().properties.insert(
PropertyKey::Str(rest_index.to_string()),
PropertyDescriptor::Data(PropertyDescriptorData {
value: data.value.clone(),
writable: true,
enumerable: true,
configurable: true,
}),
);
rest_index += 1;
}
}
rest_obj.get_object_base_mut().properties.insert(
PropertyKey::Str("length".to_string()),
PropertyDescriptor::Data(PropertyDescriptorData {
value: JsValue::Number(JsNumberType::Integer(rest_index as i64)),
writable: true,
enumerable: false,
configurable: false,
}),
);
let obj: JsObjectType = Rc::new(RefCell::new(ObjectType::Ordinary(Box::new(rest_obj))));
Ok(JsValue::Object(obj))
}
_ => Err(JErrorType::TypeError("Cannot use rest with non-array".to_string())),
}
}
fn execute_function_declaration(
func_data: &FunctionData,
ctx: &mut EvalContext,
) -> EvalResult {
let name = func_data.id.as_ref()
.ok_or_else(|| JErrorType::TypeError("Function declaration must have a name".to_string()))?
.name.clone();
let func_value = create_function_object(func_data, ctx)?;
if ctx.has_var_binding(&name) {
ctx.set_var_binding(&name, func_value)?;
} else {
ctx.create_var_binding(&name)?;
ctx.initialize_var_binding(&name, func_value)?;
}
Ok(Completion::normal())
}
fn execute_class_declaration(
class_data: &ClassData,
ctx: &mut EvalContext,
) -> EvalResult {
let name = class_data.id.as_ref()
.ok_or_else(|| JErrorType::TypeError("Class declaration must have a name".to_string()))?
.name.clone();
let class_value = evaluate_class(class_data, ctx)?;
ctx.create_binding(&name, false)?;
ctx.initialize_binding(&name, class_value)?;
Ok(Completion::normal())
}
fn get_binding_name(pattern: &PatternType) -> Result<String, JErrorType> {
match pattern {
PatternType::PatternWhichCanBeExpression(ExpressionPatternType::Identifier(id)) => {
Ok(id.name.clone())
}
PatternType::ObjectPattern { .. } => {
Err(JErrorType::TypeError("Object destructuring not yet supported".to_string()))
}
PatternType::ArrayPattern { .. } => {
Err(JErrorType::TypeError("Array destructuring not yet supported".to_string()))
}
PatternType::RestElement { .. } => {
Err(JErrorType::TypeError("Rest element not yet supported".to_string()))
}
PatternType::AssignmentPattern { .. } => {
Err(JErrorType::TypeError("Default value patterns not yet supported".to_string()))
}
}
}
fn execute_if_statement(
test: &ExpressionType,
consequent: &StatementType,
alternate: Option<&StatementType>,
ctx: &mut EvalContext,
) -> EvalResult {
let test_value = evaluate_expression(test, ctx)?;
if to_boolean(&test_value) {
execute_statement(consequent, ctx)
} else if let Some(alt) = alternate {
execute_statement(alt, ctx)
} else {
Ok(Completion::normal())
}
}
fn execute_while_statement(
test: &ExpressionType,
body: &StatementType,
ctx: &mut EvalContext,
) -> EvalResult {
let mut completion = Completion::normal();
loop {
let test_value = evaluate_expression(test, ctx)?;
if !to_boolean(&test_value) {
break;
}
completion = execute_statement(body, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Continue => {
continue;
}
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
}
Ok(completion)
}
fn execute_do_while_statement(
body: &StatementType,
test: &ExpressionType,
ctx: &mut EvalContext,
) -> EvalResult {
loop {
let completion = execute_statement(body, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Continue => {}
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
let test_value = evaluate_expression(test, ctx)?;
if !to_boolean(&test_value) {
break;
}
}
Ok(Completion::normal())
}
fn execute_for_statement(
init: Option<&crate::parser::ast::VariableDeclarationOrExpression>,
test: Option<&ExpressionType>,
update: Option<&ExpressionType>,
body: &StatementType,
ctx: &mut EvalContext,
) -> EvalResult {
use crate::parser::ast::VariableDeclarationOrExpression;
if let Some(init) = init {
match init {
VariableDeclarationOrExpression::VariableDeclaration(decl) => {
execute_variable_declaration(decl, ctx)?;
}
VariableDeclarationOrExpression::Expression(expr) => {
evaluate_expression(expr, ctx)?;
}
}
}
let mut completion = Completion::normal();
loop {
if let Some(test) = test {
let test_value = evaluate_expression(test, ctx)?;
if !to_boolean(&test_value) {
break;
}
}
completion = execute_statement(body, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Continue => {}
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
if let Some(update) = update {
evaluate_expression(update, ctx)?;
}
}
Ok(completion)
}
fn execute_function_body(
body: &FunctionBodyData,
ctx: &mut EvalContext,
) -> EvalResult {
let mut completion = Completion::normal();
for stmt in body.body.iter() {
completion = execute_statement(stmt, ctx)?;
match completion.completion_type {
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Break | CompletionType::Continue => {
return Ok(completion);
}
CompletionType::Normal => {}
}
}
Ok(completion)
}
fn execute_switch_statement(
discriminant: &ExpressionType,
cases: &[SwitchCaseData],
ctx: &mut EvalContext,
) -> EvalResult {
use super::expression::strict_equality;
let switch_value = evaluate_expression(discriminant, ctx)?;
let mut found_match = false;
let mut default_index: Option<usize> = None;
let mut start_index: Option<usize> = None;
for (i, case) in cases.iter().enumerate() {
if let Some(test) = &case.test {
let case_value = evaluate_expression(test, ctx)?;
if strict_equality(&switch_value, &case_value) {
start_index = Some(i);
found_match = true;
break;
}
} else {
default_index = Some(i);
}
}
if !found_match {
start_index = default_index;
}
let mut completion = Completion::normal();
if let Some(start) = start_index {
for case in cases.iter().skip(start) {
for stmt in &case.consequent {
completion = execute_statement(stmt, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Return | CompletionType::Throw | CompletionType::Continue | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
}
}
}
Ok(completion)
}
fn execute_try_statement(
block: &BlockStatementData,
handler: Option<&CatchClauseData>,
finalizer: Option<&BlockStatementData>,
ctx: &mut EvalContext,
) -> EvalResult {
let try_result = execute_block_statement(block, ctx);
let mut completion = match try_result {
Ok(comp) => {
if comp.completion_type == CompletionType::Throw {
if let Some(catch_clause) = handler {
handle_catch(comp, catch_clause, ctx)?
} else {
comp
}
} else {
comp
}
}
Err(err) => {
if let Some(catch_clause) = handler {
let throw_completion = Completion {
completion_type: CompletionType::Throw,
value: Some(error_to_js_value(&err)),
target: None,
};
handle_catch(throw_completion, catch_clause, ctx)?
} else {
return Err(err);
}
}
};
if let Some(finally_block) = finalizer {
let finally_result = execute_block_statement(finally_block, ctx)?;
if finally_result.is_abrupt() {
completion = finally_result;
}
}
Ok(completion)
}
fn handle_catch(
thrown: Completion,
catch_clause: &CatchClauseData,
ctx: &mut EvalContext,
) -> EvalResult {
ctx.push_block_scope();
let param_name = get_binding_name(&catch_clause.param)?;
let error_value = thrown.value.unwrap_or(JsValue::Undefined);
ctx.create_binding(¶m_name, false)?;
ctx.initialize_binding(¶m_name, error_value)?;
let result = execute_block_statement(&catch_clause.body, ctx);
ctx.pop_block_scope();
result
}
fn error_to_js_value(err: &JErrorType) -> JsValue {
match err {
JErrorType::TypeError(msg) => JsValue::String(format!("TypeError: {}", msg)),
JErrorType::ReferenceError(msg) => JsValue::String(format!("ReferenceError: {}", msg)),
JErrorType::SyntaxError(msg) => JsValue::String(format!("SyntaxError: {}", msg)),
JErrorType::RangeError(msg) => JsValue::String(format!("RangeError: {}", msg)),
JErrorType::YieldValue(v) => v.clone(),
}
}
fn execute_for_in_statement(
data: &ForIteratorData,
ctx: &mut EvalContext,
) -> EvalResult {
let obj_value = evaluate_expression(&data.right, ctx)?;
let keys: Vec<String> = match &obj_value {
JsValue::Object(obj) => {
let obj_ref = obj.borrow();
obj_ref.as_js_object().get_object_base().properties
.keys()
.filter_map(|k| match k {
crate::runner::ds::object_property::PropertyKey::Str(s) => Some(s.clone()),
_ => None,
})
.collect()
}
JsValue::String(s) => {
(0..s.len()).map(|i| i.to_string()).collect()
}
JsValue::Null | JsValue::Undefined => {
return Ok(Completion::normal());
}
_ => Vec::new(),
};
let mut completion = Completion::normal();
for key in keys {
bind_for_iterator_variable(&data.left, JsValue::String(key), ctx)?;
completion = execute_statement(&data.body, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Continue => {
continue;
}
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
}
Ok(completion)
}
fn execute_for_of_statement(
data: &ForIteratorData,
ctx: &mut EvalContext,
) -> EvalResult {
use crate::runner::ds::value::JsNumberType;
use crate::runner::ds::object_property::PropertyKey;
let iterable_value = evaluate_expression(&data.right, ctx)?;
let values: Vec<JsValue> = match &iterable_value {
JsValue::Object(obj) => {
let obj_ref = obj.borrow();
let base = obj_ref.as_js_object().get_object_base();
if let Some(prop) = base.properties.get(&PropertyKey::Str("length".to_string())) {
if let crate::runner::ds::object_property::PropertyDescriptor::Data(length_data) = prop {
if let JsValue::Number(JsNumberType::Integer(len)) = &length_data.value {
let len = *len as usize;
let mut vals = Vec::with_capacity(len);
for i in 0..len {
let key = PropertyKey::Str(i.to_string());
if let Some(prop) = base.properties.get(&key) {
if let crate::runner::ds::object_property::PropertyDescriptor::Data(d) = prop {
vals.push(d.value.clone());
} else {
vals.push(JsValue::Undefined);
}
} else {
vals.push(JsValue::Undefined);
}
}
drop(obj_ref);
return execute_for_of_with_values(data, vals, ctx);
}
}
}
base.properties
.values()
.filter_map(|prop| {
if let crate::runner::ds::object_property::PropertyDescriptor::Data(d) = prop {
if d.enumerable {
Some(d.value.clone())
} else {
None
}
} else {
None
}
})
.collect()
}
JsValue::String(s) => {
s.chars().map(|c| JsValue::String(c.to_string())).collect()
}
JsValue::Null | JsValue::Undefined => {
return Err(JErrorType::TypeError(
"Cannot iterate over null or undefined".to_string(),
));
}
_ => {
return Err(JErrorType::TypeError(
"Object is not iterable".to_string(),
));
}
};
execute_for_of_with_values(data, values, ctx)
}
fn execute_for_of_with_values(
data: &ForIteratorData,
values: Vec<JsValue>,
ctx: &mut EvalContext,
) -> EvalResult {
let mut completion = Completion::normal();
for value in values {
bind_for_iterator_variable(&data.left, value, ctx)?;
completion = execute_statement(&data.body, ctx)?;
match completion.completion_type {
CompletionType::Break => {
return Ok(Completion::normal_with_value(completion.get_value()));
}
CompletionType::Continue => {
continue;
}
CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
return Ok(completion);
}
CompletionType::Normal => {}
}
}
Ok(completion)
}
fn bind_for_iterator_variable(
left: &VariableDeclarationOrPattern,
value: JsValue,
ctx: &mut EvalContext,
) -> Result<(), JErrorType> {
match left {
VariableDeclarationOrPattern::VariableDeclaration(var_decl) => {
for declarator in &var_decl.declarations {
let name = get_binding_name(&declarator.id)?;
let is_var = matches!(var_decl.kind, VariableDeclarationKind::Var);
if is_var {
if !ctx.has_binding(&name) {
ctx.create_var_binding(&name)?;
}
ctx.initialize_var_binding(&name, value.clone())?;
} else {
if !ctx.has_binding(&name) {
ctx.create_binding(&name, matches!(var_decl.kind, VariableDeclarationKind::Const))?;
ctx.initialize_binding(&name, value.clone())?;
} else {
ctx.set_binding(&name, value.clone())?;
}
}
}
}
VariableDeclarationOrPattern::Pattern(pattern) => {
let name = get_binding_name(pattern)?;
ctx.set_binding(&name, value)?;
}
}
Ok(())
}