LibJS/Rust: Cargo fmt on all source files
This commit is contained in:
parent
809f81704c
commit
e897b77e83
13 changed files with 4046 additions and 1650 deletions
|
|
@ -160,7 +160,11 @@ impl FunctionTable {
|
|||
self.collect_from_statement(child, result);
|
||||
}
|
||||
}
|
||||
StatementKind::If { test, consequent, alternate } => {
|
||||
StatementKind::If {
|
||||
test,
|
||||
consequent,
|
||||
alternate,
|
||||
} => {
|
||||
self.collect_from_expression(test, result);
|
||||
self.collect_from_statement(consequent, result);
|
||||
if let Some(alt) = alternate {
|
||||
|
|
@ -175,15 +179,24 @@ impl FunctionTable {
|
|||
self.collect_from_statement(body, result);
|
||||
self.collect_from_expression(test, result);
|
||||
}
|
||||
StatementKind::For { init, test, update, body } => {
|
||||
StatementKind::For {
|
||||
init,
|
||||
test,
|
||||
update,
|
||||
body,
|
||||
} => {
|
||||
if let Some(init) = init {
|
||||
match init {
|
||||
ForInit::Expression(expr) => self.collect_from_expression(expr, result),
|
||||
ForInit::Declaration(decl) => self.collect_from_statement(decl, result),
|
||||
}
|
||||
}
|
||||
if let Some(test) = test { self.collect_from_expression(test, result); }
|
||||
if let Some(update) = update { self.collect_from_expression(update, result); }
|
||||
if let Some(test) = test {
|
||||
self.collect_from_expression(test, result);
|
||||
}
|
||||
if let Some(update) = update {
|
||||
self.collect_from_expression(update, result);
|
||||
}
|
||||
self.collect_from_statement(body, result);
|
||||
}
|
||||
StatementKind::ForInOf { lhs, rhs, body, .. } => {
|
||||
|
|
@ -214,7 +227,9 @@ impl FunctionTable {
|
|||
self.collect_from_statement(item, result);
|
||||
}
|
||||
StatementKind::Return(arg) => {
|
||||
if let Some(expr) = arg { self.collect_from_expression(expr, result); }
|
||||
if let Some(expr) = arg {
|
||||
self.collect_from_expression(expr, result);
|
||||
}
|
||||
}
|
||||
StatementKind::Throw(expr) => {
|
||||
self.collect_from_expression(expr, result);
|
||||
|
|
@ -276,8 +291,7 @@ impl FunctionTable {
|
|||
ExpressionKind::Class(class_data) => {
|
||||
self.collect_from_class(class_data, result);
|
||||
}
|
||||
ExpressionKind::Binary { lhs, rhs, .. }
|
||||
| ExpressionKind::Logical { lhs, rhs, .. } => {
|
||||
ExpressionKind::Binary { lhs, rhs, .. } | ExpressionKind::Logical { lhs, rhs, .. } => {
|
||||
self.collect_from_expression(lhs, result);
|
||||
self.collect_from_expression(rhs, result);
|
||||
}
|
||||
|
|
@ -294,15 +308,23 @@ impl FunctionTable {
|
|||
}
|
||||
self.collect_from_expression(rhs, result);
|
||||
}
|
||||
ExpressionKind::Conditional { test, consequent, alternate } => {
|
||||
ExpressionKind::Conditional {
|
||||
test,
|
||||
consequent,
|
||||
alternate,
|
||||
} => {
|
||||
self.collect_from_expression(test, result);
|
||||
self.collect_from_expression(consequent, result);
|
||||
self.collect_from_expression(alternate, result);
|
||||
}
|
||||
ExpressionKind::Sequence(exprs) => {
|
||||
for expr in exprs { self.collect_from_expression(expr, result); }
|
||||
for expr in exprs {
|
||||
self.collect_from_expression(expr, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::Member { object, property, .. } => {
|
||||
ExpressionKind::Member {
|
||||
object, property, ..
|
||||
} => {
|
||||
self.collect_from_expression(object, result);
|
||||
self.collect_from_expression(property, result);
|
||||
}
|
||||
|
|
@ -311,7 +333,9 @@ impl FunctionTable {
|
|||
for reference in references {
|
||||
match reference {
|
||||
OptionalChainReference::Call { arguments, .. } => {
|
||||
for arg in arguments { self.collect_from_expression(&arg.value, result); }
|
||||
for arg in arguments {
|
||||
self.collect_from_expression(&arg.value, result);
|
||||
}
|
||||
}
|
||||
OptionalChainReference::ComputedReference { expression, .. } => {
|
||||
self.collect_from_expression(expression, result);
|
||||
|
|
@ -323,10 +347,14 @@ impl FunctionTable {
|
|||
}
|
||||
ExpressionKind::Call(data) | ExpressionKind::New(data) => {
|
||||
self.collect_from_expression(&data.callee, result);
|
||||
for arg in &data.arguments { self.collect_from_expression(&arg.value, result); }
|
||||
for arg in &data.arguments {
|
||||
self.collect_from_expression(&arg.value, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::SuperCall(data) => {
|
||||
for arg in &data.arguments { self.collect_from_expression(&arg.value, result); }
|
||||
for arg in &data.arguments {
|
||||
self.collect_from_expression(&arg.value, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::Spread(expr) | ExpressionKind::Await(expr) => {
|
||||
self.collect_from_expression(expr, result);
|
||||
|
|
@ -345,18 +373,27 @@ impl FunctionTable {
|
|||
}
|
||||
}
|
||||
ExpressionKind::TemplateLiteral(data) => {
|
||||
for expr in &data.expressions { self.collect_from_expression(expr, result); }
|
||||
for expr in &data.expressions {
|
||||
self.collect_from_expression(expr, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::TaggedTemplateLiteral { tag, template_literal } => {
|
||||
ExpressionKind::TaggedTemplateLiteral {
|
||||
tag,
|
||||
template_literal,
|
||||
} => {
|
||||
self.collect_from_expression(tag, result);
|
||||
self.collect_from_expression(template_literal, result);
|
||||
}
|
||||
ExpressionKind::Yield { argument, .. } => {
|
||||
if let Some(expr) = argument { self.collect_from_expression(expr, result); }
|
||||
if let Some(expr) = argument {
|
||||
self.collect_from_expression(expr, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::ImportCall { specifier, options } => {
|
||||
self.collect_from_expression(specifier, result);
|
||||
if let Some(opts) = options { self.collect_from_expression(opts, result); }
|
||||
if let Some(opts) = options {
|
||||
self.collect_from_expression(opts, result);
|
||||
}
|
||||
}
|
||||
ExpressionKind::NumericLiteral(_)
|
||||
| ExpressionKind::StringLiteral(_)
|
||||
|
|
@ -386,7 +423,9 @@ impl FunctionTable {
|
|||
self.collect_from_expression(key, result);
|
||||
self.collect_from_expression(function, result);
|
||||
}
|
||||
ClassElement::Field { key, initializer, .. } => {
|
||||
ClassElement::Field {
|
||||
key, initializer, ..
|
||||
} => {
|
||||
self.collect_from_expression(key, result);
|
||||
if let Some(init) = initializer {
|
||||
self.collect_from_expression(init, result);
|
||||
|
|
@ -406,8 +445,12 @@ impl FunctionTable {
|
|||
}
|
||||
if let Some(ref alias) = entry.alias {
|
||||
match alias {
|
||||
BindingEntryAlias::BindingPattern(sub) => self.collect_from_pattern(sub, result),
|
||||
BindingEntryAlias::MemberExpression(expr) => self.collect_from_expression(expr, result),
|
||||
BindingEntryAlias::BindingPattern(sub) => {
|
||||
self.collect_from_pattern(sub, result)
|
||||
}
|
||||
BindingEntryAlias::MemberExpression(expr) => {
|
||||
self.collect_from_expression(expr, result)
|
||||
}
|
||||
BindingEntryAlias::Identifier(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -417,7 +460,11 @@ impl FunctionTable {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_from_target(&mut self, target: &VariableDeclaratorTarget, result: &mut FunctionTable) {
|
||||
fn collect_from_target(
|
||||
&mut self,
|
||||
target: &VariableDeclaratorTarget,
|
||||
result: &mut FunctionTable,
|
||||
) {
|
||||
if let VariableDeclaratorTarget::BindingPattern(pat) = target {
|
||||
self.collect_from_pattern(pat, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ fn print_node(state: &DumpState, text: &str) {
|
|||
let line = if state.is_root {
|
||||
text.to_string()
|
||||
} else {
|
||||
let connector = if state.is_last { "\u{2514}\u{2500} " } else { "\u{251c}\u{2500} " };
|
||||
let connector = if state.is_last {
|
||||
"\u{2514}\u{2500} "
|
||||
} else {
|
||||
"\u{251c}\u{2500} "
|
||||
};
|
||||
if state.use_color {
|
||||
format!("{}{}{}{}{}", state.prefix, DIM, connector, RESET, text)
|
||||
} else {
|
||||
|
|
@ -98,11 +102,7 @@ fn child_prefix(state: &DumpState) -> String {
|
|||
if state.is_root {
|
||||
return String::new();
|
||||
}
|
||||
let branch = if state.is_last {
|
||||
" "
|
||||
} else {
|
||||
"\u{2502} "
|
||||
};
|
||||
let branch = if state.is_last { " " } else { "\u{2502} " };
|
||||
if state.use_color {
|
||||
format!("{}{}{}{}", state.prefix, DIM, branch, RESET)
|
||||
} else {
|
||||
|
|
@ -146,7 +146,7 @@ fn color_string(state: &DumpState, value: &str) -> String {
|
|||
if !state.use_color {
|
||||
return format!("\"{}\"", value);
|
||||
}
|
||||
format!("{}\"{}\"{}",GREEN, value, RESET)
|
||||
format!("{}\"{}\"{}", GREEN, value, RESET)
|
||||
}
|
||||
|
||||
fn color_string_utf16(state: &DumpState, value: &[u16]) -> String {
|
||||
|
|
@ -240,7 +240,9 @@ fn format_f64(value: f64) -> String {
|
|||
}
|
||||
let mut buffer = [0u8; 128];
|
||||
let length = unsafe { rust_format_double(value, buffer.as_mut_ptr(), buffer.len()) };
|
||||
std::str::from_utf8(&buffer[..length]).expect("C++ produced invalid UTF-8").to_string()
|
||||
std::str::from_utf8(&buffer[..length])
|
||||
.expect("C++ produced invalid UTF-8")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
op_to_string!(binary_op_to_string, BinaryOp, {
|
||||
|
|
@ -357,12 +359,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
if s.children.len() == 1
|
||||
&& matches!(
|
||||
s.children[0].inner,
|
||||
StatementKind::For { .. }
|
||||
| StatementKind::ForInOf { .. }
|
||||
) {
|
||||
dump_statement(&s.children[0], state);
|
||||
return;
|
||||
}
|
||||
StatementKind::For { .. } | StatementKind::ForInOf { .. }
|
||||
)
|
||||
{
|
||||
dump_statement(&s.children[0], state);
|
||||
return;
|
||||
}
|
||||
dump_scope_node("BlockStatement", &s, &statement.range, state);
|
||||
}
|
||||
|
||||
|
|
@ -431,8 +433,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
let init_state = child_state(state, false);
|
||||
print_node(&init_state, &color_label(state, "init"));
|
||||
match init {
|
||||
ForInit::Expression(expr) => dump_expression(expr, &child_state(&init_state, true)),
|
||||
ForInit::Declaration(decl) => dump_statement(decl, &child_state(&init_state, true)),
|
||||
ForInit::Expression(expr) => {
|
||||
dump_expression(expr, &child_state(&init_state, true))
|
||||
}
|
||||
ForInit::Declaration(decl) => {
|
||||
dump_statement(decl, &child_state(&init_state, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(test) = test {
|
||||
|
|
@ -444,7 +450,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
dump_labeled_statement("body", body, true, state);
|
||||
}
|
||||
|
||||
StatementKind::ForInOf { kind, lhs, rhs, body } => {
|
||||
StatementKind::ForInOf {
|
||||
kind,
|
||||
lhs,
|
||||
rhs,
|
||||
body,
|
||||
} => {
|
||||
let name = match kind {
|
||||
ForInOfKind::ForIn => "ForInStatement",
|
||||
ForInOfKind::ForOf => "ForOfStatement",
|
||||
|
|
@ -460,7 +471,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
|
||||
StatementKind::Switch(data) => {
|
||||
dump_node!(state, "SwitchStatement", &statement.range);
|
||||
dump_labeled_expression("discriminant", &data.discriminant, data.cases.is_empty(), state);
|
||||
dump_labeled_expression(
|
||||
"discriminant",
|
||||
&data.discriminant,
|
||||
data.cases.is_empty(),
|
||||
state,
|
||||
);
|
||||
for (i, case) in data.cases.iter().enumerate() {
|
||||
dump_switch_case(case, &child_state(state, i == data.cases.len() - 1), state);
|
||||
}
|
||||
|
|
@ -473,7 +489,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
}
|
||||
|
||||
StatementKind::Labelled { label, item } => {
|
||||
dump_node!(state, "LabelledStatement", &statement.range, color_string_utf16(state, label));
|
||||
dump_node!(
|
||||
state,
|
||||
"LabelledStatement",
|
||||
&statement.range,
|
||||
color_string_utf16(state, label)
|
||||
);
|
||||
dump_statement(item, &child_state(state, true));
|
||||
}
|
||||
|
||||
|
|
@ -513,34 +534,65 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
}
|
||||
|
||||
StatementKind::VariableDeclaration { kind, declarations } => {
|
||||
dump_node!(state, "VariableDeclaration", &statement.range, color_op(state, declaration_kind_to_string(*kind)));
|
||||
dump_node!(
|
||||
state,
|
||||
"VariableDeclaration",
|
||||
&statement.range,
|
||||
color_op(state, declaration_kind_to_string(*kind))
|
||||
);
|
||||
for (i, declaration) in declarations.iter().enumerate() {
|
||||
dump_variable_declarator(declaration, &child_state(state, i == declarations.len() - 1), state);
|
||||
dump_variable_declarator(
|
||||
declaration,
|
||||
&child_state(state, i == declarations.len() - 1),
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StatementKind::UsingDeclaration { declarations } => {
|
||||
dump_node!(state, "UsingDeclaration", &statement.range);
|
||||
for (i, declaration) in declarations.iter().enumerate() {
|
||||
dump_variable_declarator(declaration, &child_state(state, i == declarations.len() - 1), state);
|
||||
dump_variable_declarator(
|
||||
declaration,
|
||||
&child_state(state, i == declarations.len() - 1),
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StatementKind::FunctionDeclaration { function_id, .. } => {
|
||||
let function_data = state.function_table().get(*function_id);
|
||||
dump_function(function_data, "FunctionDeclaration", &statement.range, state);
|
||||
dump_function(
|
||||
function_data,
|
||||
"FunctionDeclaration",
|
||||
&statement.range,
|
||||
state,
|
||||
);
|
||||
}
|
||||
|
||||
StatementKind::ClassDeclaration(class_data) => {
|
||||
dump_node!(state, "ClassDeclaration", &statement.range);
|
||||
dump_class(class_data, &statement.range, &child_state(state, true), state);
|
||||
dump_class(
|
||||
class_data,
|
||||
&statement.range,
|
||||
&child_state(state, true),
|
||||
state,
|
||||
);
|
||||
}
|
||||
|
||||
StatementKind::Import(data) => {
|
||||
let module_spec = utf16_to_string(&data.module_request.module_specifier);
|
||||
let assert_clauses = format_assert_clauses(&data.module_request);
|
||||
dump_node!(state, "ImportStatement", &statement.range,
|
||||
format!("from {}{}", color_string(state, &module_spec), assert_clauses));
|
||||
dump_node!(
|
||||
state,
|
||||
"ImportStatement",
|
||||
&statement.range,
|
||||
format!(
|
||||
"from {}{}",
|
||||
color_string(state, &module_spec),
|
||||
assert_clauses
|
||||
)
|
||||
);
|
||||
if !data.entries.is_empty() {
|
||||
for (i, entry) in data.entries.iter().enumerate() {
|
||||
let import_name = match &entry.import_name {
|
||||
|
|
@ -583,10 +635,8 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
None => "null".to_string(),
|
||||
}
|
||||
};
|
||||
let mut desc = format!(
|
||||
"ExportName: {}, LocalName: {}",
|
||||
export_name, local_name
|
||||
);
|
||||
let mut desc =
|
||||
format!("ExportName: {}, LocalName: {}", export_name, local_name);
|
||||
if let Some(ref module_request) = data.module_request {
|
||||
desc.push_str(&format!(
|
||||
", ModuleRequest: {}{}",
|
||||
|
|
@ -602,10 +652,7 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
}
|
||||
|
||||
if let Some(ref statement) = data.statement {
|
||||
print_node(
|
||||
&child_state(state, true),
|
||||
&color_label(state, "statement"),
|
||||
);
|
||||
print_node(&child_state(state, true), &color_label(state, "statement"));
|
||||
let inner_state = &child_state(&child_state(state, true), true);
|
||||
// For `export default <expression>`, the C++ AST stores the
|
||||
// expression directly without an ExpressionStatement wrapper.
|
||||
|
|
@ -635,15 +682,30 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
|
|||
fn dump_expression(expression: &Expression, state: &DumpState) {
|
||||
match &expression.inner {
|
||||
ExpressionKind::NumericLiteral(value) => {
|
||||
dump_node!(state, "NumericLiteral", &expression.range, color_number_f64(state, *value));
|
||||
dump_node!(
|
||||
state,
|
||||
"NumericLiteral",
|
||||
&expression.range,
|
||||
color_number_f64(state, *value)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::StringLiteral(value) => {
|
||||
dump_node!(state, "StringLiteral", &expression.range, color_string_utf16(state, value));
|
||||
dump_node!(
|
||||
state,
|
||||
"StringLiteral",
|
||||
&expression.range,
|
||||
color_string_utf16(state, value)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::BooleanLiteral(value) => {
|
||||
dump_node!(state, "BooleanLiteral", &expression.range, color_number_bool(state, *value));
|
||||
dump_node!(
|
||||
state,
|
||||
"BooleanLiteral",
|
||||
&expression.range,
|
||||
color_number_bool(state, *value)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::NullLiteral => {
|
||||
|
|
@ -651,13 +713,23 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
}
|
||||
|
||||
ExpressionKind::BigIntLiteral(value) => {
|
||||
dump_node!(state, "BigIntLiteral", &expression.range, color_number_str(state, value));
|
||||
dump_node!(
|
||||
state,
|
||||
"BigIntLiteral",
|
||||
&expression.range,
|
||||
color_number_str(state, value)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::RegExpLiteral(data) => {
|
||||
let pattern = utf16_to_string(&data.pattern);
|
||||
let flags = utf16_to_string(&data.flags);
|
||||
dump_node!(state, "RegExpLiteral", &expression.range, format!("/{}/{}", pattern, flags));
|
||||
dump_node!(
|
||||
state,
|
||||
"RegExpLiteral",
|
||||
&expression.range,
|
||||
format!("/{}/{}", pattern, flags)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::Identifier(ident) => {
|
||||
|
|
@ -665,23 +737,43 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
}
|
||||
|
||||
ExpressionKind::PrivateIdentifier(ident) => {
|
||||
dump_node!(state, "PrivateIdentifier", &expression.range, color_string_utf16(state, &ident.name));
|
||||
dump_node!(
|
||||
state,
|
||||
"PrivateIdentifier",
|
||||
&expression.range,
|
||||
color_string_utf16(state, &ident.name)
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::Binary { op, lhs, rhs } => {
|
||||
dump_node!(state, "BinaryExpression", &expression.range, color_op(state, binary_op_to_string(*op)));
|
||||
dump_node!(
|
||||
state,
|
||||
"BinaryExpression",
|
||||
&expression.range,
|
||||
color_op(state, binary_op_to_string(*op))
|
||||
);
|
||||
dump_expression(lhs, &child_state(state, false));
|
||||
dump_expression(rhs, &child_state(state, true));
|
||||
}
|
||||
|
||||
ExpressionKind::Logical { op, lhs, rhs } => {
|
||||
dump_node!(state, "LogicalExpression", &expression.range, color_op(state, logical_op_to_string(*op)));
|
||||
dump_node!(
|
||||
state,
|
||||
"LogicalExpression",
|
||||
&expression.range,
|
||||
color_op(state, logical_op_to_string(*op))
|
||||
);
|
||||
dump_expression(lhs, &child_state(state, false));
|
||||
dump_expression(rhs, &child_state(state, true));
|
||||
}
|
||||
|
||||
ExpressionKind::Unary { op, operand } => {
|
||||
dump_node!(state, "UnaryExpression", &expression.range, color_op(state, unary_op_to_string(*op)));
|
||||
dump_node!(
|
||||
state,
|
||||
"UnaryExpression",
|
||||
&expression.range,
|
||||
color_op(state, unary_op_to_string(*op))
|
||||
);
|
||||
dump_expression(operand, &child_state(state, true));
|
||||
}
|
||||
|
||||
|
|
@ -691,12 +783,22 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
prefixed,
|
||||
} => {
|
||||
let prefix_str = if *prefixed { "prefix" } else { "postfix" };
|
||||
dump_node!(state, "UpdateExpression", &expression.range, format!("({}, {})", update_op_to_string(*op), prefix_str));
|
||||
dump_node!(
|
||||
state,
|
||||
"UpdateExpression",
|
||||
&expression.range,
|
||||
format!("({}, {})", update_op_to_string(*op), prefix_str)
|
||||
);
|
||||
dump_expression(argument, &child_state(state, true));
|
||||
}
|
||||
|
||||
ExpressionKind::Assignment { op, lhs, rhs } => {
|
||||
dump_node!(state, "AssignmentExpression", &expression.range, color_op(state, assignment_op_to_string(*op)));
|
||||
dump_node!(
|
||||
state,
|
||||
"AssignmentExpression",
|
||||
&expression.range,
|
||||
color_op(state, assignment_op_to_string(*op))
|
||||
);
|
||||
match lhs {
|
||||
AssignmentLhs::Expression(expression) => {
|
||||
dump_expression(expression, &child_state(state, false));
|
||||
|
|
@ -731,7 +833,11 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
property,
|
||||
computed,
|
||||
} => {
|
||||
let name = if *computed { "MemberExpression [computed]" } else { "MemberExpression" };
|
||||
let name = if *computed {
|
||||
"MemberExpression [computed]"
|
||||
} else {
|
||||
"MemberExpression"
|
||||
};
|
||||
dump_node!(state, name, &expression.range);
|
||||
dump_expression(object, &child_state(state, false));
|
||||
dump_expression(property, &child_state(state, true));
|
||||
|
|
@ -764,7 +870,11 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
&ref_state,
|
||||
&format!("MemberReference({})", optional_mode_str(*mode)),
|
||||
);
|
||||
dump_identifier(identifier, &identifier.range, &child_state(&ref_state, true));
|
||||
dump_identifier(
|
||||
identifier,
|
||||
&identifier.range,
|
||||
&child_state(&ref_state, true),
|
||||
);
|
||||
}
|
||||
OptionalChainReference::PrivateMemberReference {
|
||||
private_identifier,
|
||||
|
|
@ -792,7 +902,10 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
dump_node!(state, "CallExpression", &expression.range);
|
||||
dump_expression(&data.callee, &child_state(state, data.arguments.is_empty()));
|
||||
for (i, argument) in data.arguments.iter().enumerate() {
|
||||
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
|
||||
dump_expression(
|
||||
&argument.value,
|
||||
&child_state(state, i == data.arguments.len() - 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -800,14 +913,20 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
dump_node!(state, "NewExpression", &expression.range);
|
||||
dump_expression(&data.callee, &child_state(state, data.arguments.is_empty()));
|
||||
for (i, argument) in data.arguments.iter().enumerate() {
|
||||
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
|
||||
dump_expression(
|
||||
&argument.value,
|
||||
&child_state(state, i == data.arguments.len() - 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionKind::SuperCall(data) => {
|
||||
dump_node!(state, "SuperCall", &expression.range);
|
||||
for (i, argument) in data.arguments.iter().enumerate() {
|
||||
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
|
||||
dump_expression(
|
||||
&argument.value,
|
||||
&child_state(state, i == data.arguments.len() - 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -826,7 +945,12 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
|
|||
|
||||
ExpressionKind::Function(function_id) => {
|
||||
let function_data = state.function_table().get(*function_id);
|
||||
dump_function(function_data, "FunctionExpression", &expression.range, state);
|
||||
dump_function(
|
||||
function_data,
|
||||
"FunctionExpression",
|
||||
&expression.range,
|
||||
state,
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionKind::Class(class_data) => {
|
||||
|
|
@ -927,7 +1051,10 @@ fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) {
|
|||
} else {
|
||||
"variable"
|
||||
};
|
||||
desc.push_str(&format!(" {}", color_local(state, kind, ident.local_index.get())));
|
||||
desc.push_str(&format!(
|
||||
" {}",
|
||||
color_local(state, kind, ident.local_index.get())
|
||||
));
|
||||
} else if ident.is_global.get() {
|
||||
desc.push_str(&format!(" {}", color_global(state)));
|
||||
}
|
||||
|
|
@ -948,12 +1075,7 @@ fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) {
|
|||
// Helper dumpers
|
||||
// ============================================================================
|
||||
|
||||
fn dump_scope_node(
|
||||
class_name: &str,
|
||||
scope: &ScopeData,
|
||||
range: &SourceRange,
|
||||
state: &DumpState,
|
||||
) {
|
||||
fn dump_scope_node(class_name: &str, scope: &ScopeData, range: &SourceRange, state: &DumpState) {
|
||||
dump_node!(state, class_name, range);
|
||||
for (i, child) in scope.children.iter().enumerate() {
|
||||
dump_statement(child, &child_state(state, i == scope.children.len() - 1));
|
||||
|
|
@ -1001,10 +1123,7 @@ fn dump_function(
|
|||
));
|
||||
}
|
||||
if function_data.parsing_insights.might_need_arguments_object {
|
||||
desc.push_str(&format!(
|
||||
" {}",
|
||||
color_flag(state, "might-need-arguments")
|
||||
));
|
||||
desc.push_str(&format!(" {}", color_flag(state, "might-need-arguments")));
|
||||
}
|
||||
desc.push_str(&format_position(state, range));
|
||||
print_node(state, &desc);
|
||||
|
|
@ -1016,13 +1135,18 @@ fn dump_function(
|
|||
);
|
||||
let parameters_state = child_state(state, false);
|
||||
for (i, parameter) in function_data.parameters.iter().enumerate() {
|
||||
let parameter_state = child_state(¶meters_state, i == function_data.parameters.len() - 1);
|
||||
let parameter_state =
|
||||
child_state(¶meters_state, i == function_data.parameters.len() - 1);
|
||||
let has_default = parameter.default_value.is_some();
|
||||
if parameter.is_rest {
|
||||
print_node(¶meter_state, &color_label(state, "rest"));
|
||||
match ¶meter.binding {
|
||||
FunctionParameterBinding::Identifier(ident) => {
|
||||
dump_identifier(ident, &ident.range, &child_state(¶meter_state, !has_default));
|
||||
dump_identifier(
|
||||
ident,
|
||||
&ident.range,
|
||||
&child_state(¶meter_state, !has_default),
|
||||
);
|
||||
}
|
||||
FunctionParameterBinding::BindingPattern(pattern) => {
|
||||
dump_binding_pattern(
|
||||
|
|
@ -1038,7 +1162,10 @@ fn dump_function(
|
|||
dump_identifier(
|
||||
ident,
|
||||
&ident.range,
|
||||
&child_state(¶meters_state, i == function_data.parameters.len() - 1),
|
||||
&child_state(
|
||||
¶meters_state,
|
||||
i == function_data.parameters.len() - 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
FunctionParameterBinding::BindingPattern(pattern) => {
|
||||
|
|
@ -1059,21 +1186,29 @@ fn dump_function(
|
|||
&color_label(state, "default"),
|
||||
);
|
||||
dump_expression(
|
||||
parameter.default_value.as_ref().expect("guarded by is_some check"),
|
||||
parameter
|
||||
.default_value
|
||||
.as_ref()
|
||||
.expect("guarded by is_some check"),
|
||||
&child_state(&child_state(¶meter_state, true), true),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_node(
|
||||
&child_state(state, true),
|
||||
&color_label(state, "body"),
|
||||
print_node(&child_state(state, true), &color_label(state, "body"));
|
||||
dump_statement(
|
||||
&function_data.body,
|
||||
&child_state(&child_state(state, true), true),
|
||||
);
|
||||
dump_statement(&function_data.body, &child_state(&child_state(state, true), true));
|
||||
}
|
||||
|
||||
fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, root_state: &DumpState) {
|
||||
fn dump_class(
|
||||
class_data: &ClassData,
|
||||
range: &SourceRange,
|
||||
state: &DumpState,
|
||||
root_state: &DumpState,
|
||||
) {
|
||||
let name_str = match &class_data.name {
|
||||
Some(ident) => utf16_to_string(&ident.name),
|
||||
None => String::new(),
|
||||
|
|
@ -1096,7 +1231,10 @@ fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, ro
|
|||
&color_label(root_state, "super class"),
|
||||
);
|
||||
dump_expression(
|
||||
class_data.super_class.as_ref().expect("guarded by has_super_class check"),
|
||||
class_data
|
||||
.super_class
|
||||
.as_ref()
|
||||
.expect("guarded by has_super_class check"),
|
||||
&child_state(&child_state(state, false), true),
|
||||
);
|
||||
}
|
||||
|
|
@ -1121,7 +1259,10 @@ fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, ro
|
|||
dump_class_element(
|
||||
&element.inner,
|
||||
&element.range,
|
||||
&child_state(&child_state(state, true), i == class_data.elements.len() - 1),
|
||||
&child_state(
|
||||
&child_state(state, true),
|
||||
i == class_data.elements.len() - 1,
|
||||
),
|
||||
root_state,
|
||||
);
|
||||
}
|
||||
|
|
@ -1190,11 +1331,7 @@ fn dump_class_element(
|
|||
}
|
||||
}
|
||||
|
||||
fn dump_binding_pattern(
|
||||
pattern: &BindingPattern,
|
||||
state: &DumpState,
|
||||
root_state: &DumpState,
|
||||
) {
|
||||
fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state: &DumpState) {
|
||||
let kind_str = match pattern.kind {
|
||||
BindingPatternKind::Array => "array",
|
||||
BindingPatternKind::Object => "object",
|
||||
|
|
@ -1268,29 +1405,20 @@ fn dump_binding_pattern(
|
|||
dump_identifier(
|
||||
ident,
|
||||
&ident.range,
|
||||
&child_state(
|
||||
&child_state(&entry_state, !has_initializer),
|
||||
true,
|
||||
),
|
||||
&child_state(&child_state(&entry_state, !has_initializer), true),
|
||||
);
|
||||
}
|
||||
BindingEntryAlias::BindingPattern(sub) => {
|
||||
dump_binding_pattern(
|
||||
sub,
|
||||
&child_state(
|
||||
&child_state(&entry_state, !has_initializer),
|
||||
true,
|
||||
),
|
||||
&child_state(&child_state(&entry_state, !has_initializer), true),
|
||||
root_state,
|
||||
);
|
||||
}
|
||||
BindingEntryAlias::MemberExpression(expression) => {
|
||||
dump_expression(
|
||||
expression,
|
||||
&child_state(
|
||||
&child_state(&entry_state, !has_initializer),
|
||||
true,
|
||||
),
|
||||
&child_state(&child_state(&entry_state, !has_initializer), true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1302,7 +1430,10 @@ fn dump_binding_pattern(
|
|||
&color_label(root_state, "initializer"),
|
||||
);
|
||||
dump_expression(
|
||||
entry.initializer.as_ref().expect("guarded by is_some check"),
|
||||
entry
|
||||
.initializer
|
||||
.as_ref()
|
||||
.expect("guarded by is_some check"),
|
||||
&child_state(&child_state(&entry_state, true), true),
|
||||
);
|
||||
}
|
||||
|
|
@ -1310,10 +1441,7 @@ fn dump_binding_pattern(
|
|||
}
|
||||
|
||||
fn is_elision(entry: &BindingEntry) -> bool {
|
||||
entry.name.is_none()
|
||||
&& entry.alias.is_none()
|
||||
&& entry.initializer.is_none()
|
||||
&& !entry.is_rest
|
||||
entry.name.is_none() && entry.alias.is_none() && entry.initializer.is_none() && !entry.is_rest
|
||||
}
|
||||
|
||||
fn dump_variable_declarator(
|
||||
|
|
@ -1343,11 +1471,7 @@ fn dump_variable_declarator(
|
|||
}
|
||||
}
|
||||
|
||||
fn dump_object_property(
|
||||
property: &ObjectProperty,
|
||||
state: &DumpState,
|
||||
root_state: &DumpState,
|
||||
) {
|
||||
fn dump_object_property(property: &ObjectProperty, state: &DumpState, root_state: &DumpState) {
|
||||
if property.property_type == ObjectPropertyType::Spread {
|
||||
print_node(
|
||||
state,
|
||||
|
|
@ -1377,11 +1501,7 @@ fn dump_object_property(
|
|||
}
|
||||
}
|
||||
|
||||
fn dump_catch_clause(
|
||||
clause: &CatchClause,
|
||||
state: &DumpState,
|
||||
root_state: &DumpState,
|
||||
) {
|
||||
fn dump_catch_clause(clause: &CatchClause, state: &DumpState, root_state: &DumpState) {
|
||||
print_node(
|
||||
state,
|
||||
&format!(
|
||||
|
|
@ -1419,11 +1539,7 @@ fn dump_catch_clause(
|
|||
dump_statement(&clause.body, &child_state(state, true));
|
||||
}
|
||||
|
||||
fn dump_switch_case(
|
||||
case: &SwitchCase,
|
||||
state: &DumpState,
|
||||
root_state: &DumpState,
|
||||
) {
|
||||
fn dump_switch_case(case: &SwitchCase, state: &DumpState, root_state: &DumpState) {
|
||||
if let Some(ref test) = case.test {
|
||||
print_node(
|
||||
state,
|
||||
|
|
@ -1433,10 +1549,7 @@ fn dump_switch_case(
|
|||
format_position(root_state, &case.range)
|
||||
),
|
||||
);
|
||||
print_node(
|
||||
&child_state(state, false),
|
||||
&color_label(root_state, "test"),
|
||||
);
|
||||
print_node(&child_state(state, false), &color_label(root_state, "test"));
|
||||
dump_expression(test, &child_state(&child_state(state, false), true));
|
||||
} else {
|
||||
print_node(
|
||||
|
|
@ -1457,7 +1570,10 @@ fn dump_switch_case(
|
|||
let scope = case.scope.borrow();
|
||||
let children = &scope.children;
|
||||
for (i, child) in children.iter().enumerate() {
|
||||
dump_statement(child, &child_state(&consequent_state, i == children.len() - 1));
|
||||
dump_statement(
|
||||
child,
|
||||
&child_state(&consequent_state, i == children.len() - 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,13 +60,19 @@ pub struct FFIUtf16Slice {
|
|||
|
||||
impl From<&[u16]> for FFIUtf16Slice {
|
||||
fn from(slice: &[u16]) -> Self {
|
||||
Self { data: slice.as_ptr(), length: slice.len() }
|
||||
Self {
|
||||
data: slice.as_ptr(),
|
||||
length: slice.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Utf16String> for FFIUtf16Slice {
|
||||
fn from(s: &Utf16String) -> Self {
|
||||
Self { data: s.as_ptr(), length: s.len() }
|
||||
Self {
|
||||
data: s.as_ptr(),
|
||||
length: s.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,11 +86,17 @@ pub struct FFIOptionalU32 {
|
|||
|
||||
impl FFIOptionalU32 {
|
||||
pub fn none() -> Self {
|
||||
Self { value: 0, has_value: false }
|
||||
Self {
|
||||
value: 0,
|
||||
has_value: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn some(value: u32) -> Self {
|
||||
Self { value, has_value: true }
|
||||
Self {
|
||||
value,
|
||||
has_value: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,10 +222,20 @@ extern "C" {
|
|||
// Callbacks for populating Script GDI data from Rust.
|
||||
pub fn script_gdi_push_lexical_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn script_gdi_push_var_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn script_gdi_push_function(ctx: *mut c_void, sfd: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn script_gdi_push_function(
|
||||
ctx: *mut c_void,
|
||||
sfd: *mut c_void,
|
||||
name: *const u16,
|
||||
len: usize,
|
||||
);
|
||||
pub fn script_gdi_push_var_scoped_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn script_gdi_push_annex_b_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn script_gdi_push_lexical_binding(ctx: *mut c_void, name: *const u16, len: usize, is_constant: bool);
|
||||
pub fn script_gdi_push_lexical_binding(
|
||||
ctx: *mut c_void,
|
||||
name: *const u16,
|
||||
len: usize,
|
||||
is_constant: bool,
|
||||
);
|
||||
|
||||
// Callbacks for populating eval EDI data from Rust.
|
||||
pub fn eval_gdi_set_strict(ctx: *mut c_void, is_strict: bool);
|
||||
|
|
@ -221,7 +243,12 @@ extern "C" {
|
|||
pub fn eval_gdi_push_function(ctx: *mut c_void, sfd: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn eval_gdi_push_var_scoped_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn eval_gdi_push_annex_b_name(ctx: *mut c_void, name: *const u16, len: usize);
|
||||
pub fn eval_gdi_push_lexical_binding(ctx: *mut c_void, name: *const u16, len: usize, is_constant: bool);
|
||||
pub fn eval_gdi_push_lexical_binding(
|
||||
ctx: *mut c_void,
|
||||
name: *const u16,
|
||||
len: usize,
|
||||
is_constant: bool,
|
||||
);
|
||||
|
||||
pub fn rust_compile_regex(
|
||||
pattern_data: *const u16,
|
||||
|
|
@ -241,7 +268,11 @@ extern "C" {
|
|||
|
||||
// Get an intrinsic abstract operation function as an opaque Value.
|
||||
// name/name_len is the function name (e.g. "GetMethod").
|
||||
pub fn get_abstract_operation_function(vm_ptr: *mut c_void, name: *const u16, name_len: usize) -> u64;
|
||||
pub fn get_abstract_operation_function(
|
||||
vm_ptr: *mut c_void,
|
||||
name: *const u16,
|
||||
name_len: usize,
|
||||
) -> u64;
|
||||
}
|
||||
|
||||
/// Create a SharedFunctionInstanceData from a FunctionData.
|
||||
|
|
@ -293,7 +324,9 @@ pub unsafe fn create_shared_function_data(
|
|||
if let FunctionParameterBinding::Identifier(ref id) = p.binding {
|
||||
FFIUtf16Slice::from(id.name.as_ref())
|
||||
} else {
|
||||
unreachable!("has_simple_parameter_list guarantees all bindings are identifiers")
|
||||
unreachable!(
|
||||
"has_simple_parameter_list guarantees all bindings are identifiers"
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -335,7 +368,10 @@ pub unsafe fn create_shared_function_data(
|
|||
|
||||
let sfd_ptr = rust_create_sfd(vm_ptr, source_code_ptr, &ffi_data);
|
||||
|
||||
assert!(!sfd_ptr.is_null(), "create_shared_function_data: rust_create_sfd returned null");
|
||||
assert!(
|
||||
!sfd_ptr.is_null(),
|
||||
"create_shared_function_data: rust_create_sfd returned null"
|
||||
);
|
||||
sfd_ptr
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +386,14 @@ pub unsafe fn create_sfd_for_gdi(
|
|||
source_code_ptr: *const c_void,
|
||||
is_strict: bool,
|
||||
) -> *mut c_void {
|
||||
create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None)
|
||||
create_shared_function_data(
|
||||
function_data,
|
||||
subtable,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
is_strict,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constant tags for the FFI constant buffer (ABI-compatible with BytecodeFactory).
|
||||
|
|
@ -530,14 +573,18 @@ pub fn compile_regex(pattern: &[u16], flags: &[u16]) -> Result<*mut c_void, Stri
|
|||
unsafe {
|
||||
let mut error: *const std::os::raw::c_char = std::ptr::null();
|
||||
let handle = rust_compile_regex(
|
||||
pattern.as_ptr(), pattern.len(),
|
||||
flags.as_ptr(), flags.len(),
|
||||
pattern.as_ptr(),
|
||||
pattern.len(),
|
||||
flags.as_ptr(),
|
||||
flags.len(),
|
||||
&mut error,
|
||||
);
|
||||
if error.is_null() {
|
||||
Ok(handle)
|
||||
} else {
|
||||
let msg = std::ffi::CStr::from_ptr(error).to_string_lossy().into_owned();
|
||||
let msg = std::ffi::CStr::from_ptr(error)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
rust_free_error_string(error);
|
||||
Err(msg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ impl std::fmt::Debug for ScopedOperandInner {
|
|||
impl Drop for ScopedOperandInner {
|
||||
fn drop(&mut self) {
|
||||
if self.operand.is_register() && self.operand.index() >= Register::RESERVED_COUNT {
|
||||
self.free_register_pool.borrow_mut().push(Register(self.operand.index()));
|
||||
self.free_register_pool
|
||||
.borrow_mut()
|
||||
.push(Register(self.operand.index()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -351,11 +353,17 @@ impl Generator {
|
|||
// --- Function kind queries ---
|
||||
|
||||
pub fn is_in_generator_function(&self) -> bool {
|
||||
matches!(self.enclosing_function_kind, FunctionKind::Generator | FunctionKind::AsyncGenerator)
|
||||
matches!(
|
||||
self.enclosing_function_kind,
|
||||
FunctionKind::Generator | FunctionKind::AsyncGenerator
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_in_async_function(&self) -> bool {
|
||||
matches!(self.enclosing_function_kind, FunctionKind::Async | FunctionKind::AsyncGenerator)
|
||||
matches!(
|
||||
self.enclosing_function_kind,
|
||||
FunctionKind::Async | FunctionKind::AsyncGenerator
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_in_async_generator_function(&self) -> bool {
|
||||
|
|
@ -383,12 +391,7 @@ impl Generator {
|
|||
self.next_register += 1;
|
||||
r
|
||||
} else {
|
||||
let min_index = pool
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by_key(|(_, r)| r.0)
|
||||
.unwrap()
|
||||
.0;
|
||||
let min_index = pool.iter().enumerate().min_by_key(|(_, r)| r.0).unwrap().0;
|
||||
pool.remove(min_index)
|
||||
}
|
||||
};
|
||||
|
|
@ -427,7 +430,10 @@ impl Generator {
|
|||
/// Copy a local variable into a fresh register to prevent later
|
||||
/// side effects from changing its value. Returns the operand unchanged
|
||||
/// if it is not a local.
|
||||
pub fn copy_if_needed_to_preserve_evaluation_order(&mut self, operand: &ScopedOperand) -> ScopedOperand {
|
||||
pub fn copy_if_needed_to_preserve_evaluation_order(
|
||||
&mut self,
|
||||
operand: &ScopedOperand,
|
||||
) -> ScopedOperand {
|
||||
if operand.operand().is_local() {
|
||||
let reg = self.allocate_register();
|
||||
self.emit_mov(®, operand);
|
||||
|
|
@ -520,9 +526,24 @@ impl Generator {
|
|||
|
||||
// --- Table interning ---
|
||||
|
||||
define_intern_method!(intern_string, StringTableIndex, string_table, string_table_index);
|
||||
define_intern_method!(intern_identifier, IdentifierTableIndex, identifier_table, identifier_table_index);
|
||||
define_intern_method!(intern_property_key, PropertyKeyTableIndex, property_key_table, property_key_table_index);
|
||||
define_intern_method!(
|
||||
intern_string,
|
||||
StringTableIndex,
|
||||
string_table,
|
||||
string_table_index
|
||||
);
|
||||
define_intern_method!(
|
||||
intern_identifier,
|
||||
IdentifierTableIndex,
|
||||
identifier_table,
|
||||
identifier_table_index
|
||||
);
|
||||
define_intern_method!(
|
||||
intern_property_key,
|
||||
PropertyKeyTableIndex,
|
||||
property_key_table,
|
||||
property_key_table_index
|
||||
);
|
||||
|
||||
/// If `operand` is a constant string that is not an array index, intern it
|
||||
/// as a property key and return the index. Uses split borrows to avoid
|
||||
|
|
@ -681,58 +702,82 @@ impl Generator {
|
|||
|
||||
// OPTIMIZATION: If the condition is a register with ref_count == 1 and the last
|
||||
// instruction is a comparison whose dst matches condition, fuse into a JumpXxx.
|
||||
if condition.operand().is_register()
|
||||
&& std::rc::Rc::strong_count(&condition.inner) == 1
|
||||
{
|
||||
if condition.operand().is_register() && std::rc::Rc::strong_count(&condition.inner) == 1 {
|
||||
let block = &mut self.basic_blocks[self.current_block_index.basic_block_index()];
|
||||
if let Some((last_instruction, _)) = block.instructions.last() {
|
||||
let fused = match last_instruction {
|
||||
Instruction::LessThan { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Some(Instruction::JumpLessThan {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::LessThanEquals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Instruction::LessThanEquals { dst, lhs, rhs }
|
||||
if *dst == condition.operand() =>
|
||||
{
|
||||
Some(Instruction::JumpLessThanEquals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::GreaterThan { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Some(Instruction::JumpGreaterThan {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::GreaterThanEquals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Instruction::GreaterThanEquals { dst, lhs, rhs }
|
||||
if *dst == condition.operand() =>
|
||||
{
|
||||
Some(Instruction::JumpGreaterThanEquals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::LooselyEquals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Some(Instruction::JumpLooselyEquals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::LooselyInequals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Instruction::LooselyInequals { dst, lhs, rhs }
|
||||
if *dst == condition.operand() =>
|
||||
{
|
||||
Some(Instruction::JumpLooselyInequals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::StrictlyEquals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Instruction::StrictlyEquals { dst, lhs, rhs }
|
||||
if *dst == condition.operand() =>
|
||||
{
|
||||
Some(Instruction::JumpStrictlyEquals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
Instruction::StrictlyInequals { dst, lhs, rhs } if *dst == condition.operand() => {
|
||||
Instruction::StrictlyInequals { dst, lhs, rhs }
|
||||
if *dst == condition.operand() =>
|
||||
{
|
||||
Some(Instruction::JumpStrictlyInequals {
|
||||
lhs: *lhs, rhs: *rhs,
|
||||
true_target, false_target,
|
||||
lhs: *lhs,
|
||||
rhs: *rhs,
|
||||
true_target,
|
||||
false_target,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
|
|
@ -763,8 +808,12 @@ impl Generator {
|
|||
// --- Lexical environment helpers ---
|
||||
|
||||
pub fn current_lexical_environment(&mut self) -> ScopedOperand {
|
||||
self.lexical_environment_register_stack.last().cloned()
|
||||
.unwrap_or_else(|| self.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT)))
|
||||
self.lexical_environment_register_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
self.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn end_variable_scope(&mut self) {
|
||||
|
|
@ -797,7 +846,8 @@ impl Generator {
|
|||
parent: parent.operand(),
|
||||
capacity,
|
||||
});
|
||||
self.lexical_environment_register_stack.push(new_env.clone());
|
||||
self.lexical_environment_register_stack
|
||||
.push(new_env.clone());
|
||||
new_env
|
||||
}
|
||||
|
||||
|
|
@ -814,7 +864,12 @@ impl Generator {
|
|||
|
||||
// --- Break/continue scope management ---
|
||||
|
||||
pub fn begin_breakable_scope(&mut self, target: Label, label_set: Vec<Utf16String>, completion: Option<ScopedOperand>) {
|
||||
pub fn begin_breakable_scope(
|
||||
&mut self,
|
||||
target: Label,
|
||||
label_set: Vec<Utf16String>,
|
||||
completion: Option<ScopedOperand>,
|
||||
) {
|
||||
self.breakable_scopes.push(LabelableScope {
|
||||
bytecode_target: target,
|
||||
language_label_set: label_set,
|
||||
|
|
@ -828,7 +883,12 @@ impl Generator {
|
|||
self.breakable_scopes.pop();
|
||||
}
|
||||
|
||||
pub fn begin_continuable_scope(&mut self, target: Label, label_set: Vec<Utf16String>, completion: Option<ScopedOperand>) {
|
||||
pub fn begin_continuable_scope(
|
||||
&mut self,
|
||||
target: Label,
|
||||
label_set: Vec<Utf16String>,
|
||||
completion: Option<ScopedOperand>,
|
||||
) {
|
||||
self.continuable_scopes.push(LabelableScope {
|
||||
bytecode_target: target,
|
||||
language_label_set: label_set,
|
||||
|
|
@ -843,7 +903,10 @@ impl Generator {
|
|||
}
|
||||
|
||||
pub fn set_current_breakable_scope_completion_register(&mut self, completion: ScopedOperand) {
|
||||
self.breakable_scopes.last_mut().expect("no active breakable scope").completion_register = Some(completion);
|
||||
self.breakable_scopes
|
||||
.last_mut()
|
||||
.expect("no active breakable scope")
|
||||
.completion_register = Some(completion);
|
||||
}
|
||||
|
||||
pub fn find_breakable_scope(&self, label: Option<&[u16]>) -> Option<&LabelableScope> {
|
||||
|
|
@ -898,7 +961,9 @@ impl Generator {
|
|||
/// Register a jump target with the current FinallyContext.
|
||||
/// Assigns a unique completion_type index and emits code to set it and jump to finally.
|
||||
pub fn register_jump_in_finally_context(&mut self, target: Label) {
|
||||
let index = self.current_finally_context.expect("no active finally context");
|
||||
let index = self
|
||||
.current_finally_context
|
||||
.expect("no active finally context");
|
||||
let ctx = &mut self.finally_contexts[index];
|
||||
let jump_index = ctx.next_jump_index;
|
||||
ctx.next_jump_index += 1;
|
||||
|
|
@ -921,7 +986,9 @@ impl Generator {
|
|||
self.register_jump_in_finally_context(trampoline_block);
|
||||
self.switch_to_basic_block(trampoline_block);
|
||||
// Pop to the parent FinallyContext (simulating the inner finally completing).
|
||||
let index = self.current_finally_context.expect("no active finally context");
|
||||
let index = self
|
||||
.current_finally_context
|
||||
.expect("no active finally context");
|
||||
self.current_finally_context = self.finally_contexts[index].parent_index;
|
||||
}
|
||||
|
||||
|
|
@ -955,10 +1022,15 @@ impl Generator {
|
|||
let boundary = self.boundaries[i];
|
||||
match boundary {
|
||||
BlockBoundaryType::Break if is_break => {
|
||||
let target_scope = self.breakable_scopes.last().expect("no active breakable scope");
|
||||
let target_scope = self
|
||||
.breakable_scopes
|
||||
.last()
|
||||
.expect("no active breakable scope");
|
||||
let target = target_scope.bytecode_target;
|
||||
let completion = target_scope.completion_register.clone();
|
||||
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
|
||||
if let (Some(cur), Some(tgt)) =
|
||||
(self.current_completion_register.clone(), completion)
|
||||
{
|
||||
if cur != tgt {
|
||||
self.emit_mov(&tgt, &cur);
|
||||
}
|
||||
|
|
@ -968,10 +1040,15 @@ impl Generator {
|
|||
return;
|
||||
}
|
||||
BlockBoundaryType::Continue if !is_break => {
|
||||
let target_scope = self.continuable_scopes.last().expect("no active continuable scope");
|
||||
let target_scope = self
|
||||
.continuable_scopes
|
||||
.last()
|
||||
.expect("no active continuable scope");
|
||||
let target = target_scope.bytecode_target;
|
||||
let completion = target_scope.completion_register.clone();
|
||||
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
|
||||
if let (Some(cur), Some(tgt)) =
|
||||
(self.current_completion_register.clone(), completion)
|
||||
{
|
||||
if cur != tgt {
|
||||
self.emit_mov(&tgt, &cur);
|
||||
}
|
||||
|
|
@ -990,13 +1067,19 @@ impl Generator {
|
|||
BlockBoundaryType::ReturnToFinally => {
|
||||
if !self.has_outer_finally_before_target(is_break, i + 1) {
|
||||
let target_scope = if is_break {
|
||||
self.breakable_scopes.last().expect("no active breakable scope")
|
||||
self.breakable_scopes
|
||||
.last()
|
||||
.expect("no active breakable scope")
|
||||
} else {
|
||||
self.continuable_scopes.last().expect("no active continuable scope")
|
||||
self.continuable_scopes
|
||||
.last()
|
||||
.expect("no active continuable scope")
|
||||
};
|
||||
let target = target_scope.bytecode_target;
|
||||
let completion = target_scope.completion_register.clone();
|
||||
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
|
||||
if let (Some(cur), Some(tgt)) =
|
||||
(self.current_completion_register.clone(), completion)
|
||||
{
|
||||
if cur != tgt {
|
||||
self.emit_mov(&tgt, &cur);
|
||||
}
|
||||
|
|
@ -1023,13 +1106,25 @@ impl Generator {
|
|||
self.breakable_scopes
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|s| (s.bytecode_target, s.language_label_set.clone(), s.completion_register.clone()))
|
||||
.map(|s| {
|
||||
(
|
||||
s.bytecode_target,
|
||||
s.language_label_set.clone(),
|
||||
s.completion_register.clone(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
self.continuable_scopes
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|s| (s.bytecode_target, s.language_label_set.clone(), s.completion_register.clone()))
|
||||
.map(|s| {
|
||||
(
|
||||
s.bytecode_target,
|
||||
s.language_label_set.clone(),
|
||||
s.completion_register.clone(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
|
|
@ -1051,7 +1146,9 @@ impl Generator {
|
|||
if !self.has_outer_finally_before_target(is_break, current_boundary + 1)
|
||||
&& label_set.iter().any(|l| l == label)
|
||||
{
|
||||
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion.clone()) {
|
||||
if let (Some(cur), Some(tgt)) =
|
||||
(self.current_completion_register.clone(), completion.clone())
|
||||
{
|
||||
if cur != tgt {
|
||||
self.emit_mov(&tgt, &cur);
|
||||
}
|
||||
|
|
@ -1072,14 +1169,14 @@ impl Generator {
|
|||
}
|
||||
|
||||
if label_set.iter().any(|l| l == label) {
|
||||
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion.clone()) {
|
||||
if let (Some(cur), Some(tgt)) =
|
||||
(self.current_completion_register.clone(), completion.clone())
|
||||
{
|
||||
if cur != tgt {
|
||||
self.emit_mov(&tgt, &cur);
|
||||
}
|
||||
}
|
||||
self.emit(Instruction::Jump {
|
||||
target: *target,
|
||||
});
|
||||
self.emit(Instruction::Jump { target: *target });
|
||||
self.current_finally_context = saved_ctx;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1097,7 +1194,8 @@ impl Generator {
|
|||
match self.boundaries[i] {
|
||||
BlockBoundaryType::LeaveLexicalEnvironment => {
|
||||
env_stack_offset -= 1;
|
||||
let parent_env = self.lexical_environment_register_stack[env_stack_offset - 1].clone();
|
||||
let parent_env =
|
||||
self.lexical_environment_register_stack[env_stack_offset - 1].clone();
|
||||
self.emit(Instruction::SetLexicalEnvironment {
|
||||
environment: parent_env.operand(),
|
||||
});
|
||||
|
|
@ -1213,11 +1311,9 @@ impl Generator {
|
|||
OperandType::Constant => {
|
||||
op.offset_index_by(number_of_registers + number_of_locals)
|
||||
}
|
||||
OperandType::Argument => {
|
||||
op.offset_index_by(
|
||||
number_of_registers + number_of_locals + number_of_constants,
|
||||
)
|
||||
}
|
||||
OperandType::Argument => op.offset_index_by(
|
||||
number_of_registers + number_of_locals + number_of_constants,
|
||||
),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1282,7 +1378,11 @@ impl Generator {
|
|||
block_actions.push(InstAction::Emit);
|
||||
offset += instruction.encoded_size();
|
||||
}
|
||||
Instruction::JumpIf { condition, true_target, false_target } => {
|
||||
Instruction::JumpIf {
|
||||
condition,
|
||||
true_target,
|
||||
false_target,
|
||||
} => {
|
||||
let true_block = true_target.0 as usize;
|
||||
let false_block = false_target.0 as usize;
|
||||
// OPTIMIZATION: Replace JumpIf where one target is next block
|
||||
|
|
@ -1322,7 +1422,9 @@ impl Generator {
|
|||
}
|
||||
// Unterminated blocks get an implicit End(undefined) appended.
|
||||
if !block.terminated {
|
||||
let dummy_end = Instruction::End { value: Operand::constant(0) };
|
||||
let dummy_end = Instruction::End {
|
||||
value: Operand::constant(0),
|
||||
};
|
||||
offset += dummy_end.encoded_size();
|
||||
}
|
||||
actions.push(block_actions);
|
||||
|
|
@ -1396,7 +1498,10 @@ impl Generator {
|
|||
let replacement = Instruction::End { value };
|
||||
replacement.encode(self.strict, &mut bytecode);
|
||||
}
|
||||
InstAction::EmitJumpFalse { condition, mut target } => {
|
||||
InstAction::EmitJumpFalse {
|
||||
condition,
|
||||
mut target,
|
||||
} => {
|
||||
// Patch label for the target
|
||||
let target_block = target.0 as usize;
|
||||
target.0 = u32_from_usize(block_offsets[target_block]);
|
||||
|
|
@ -1409,7 +1514,10 @@ impl Generator {
|
|||
let replacement = Instruction::JumpFalse { condition, target };
|
||||
replacement.encode(self.strict, &mut bytecode);
|
||||
}
|
||||
InstAction::EmitJumpTrue { condition, mut target } => {
|
||||
InstAction::EmitJumpTrue {
|
||||
condition,
|
||||
mut target,
|
||||
} => {
|
||||
let target_block = target.0 as usize;
|
||||
target.0 = u32_from_usize(block_offsets[target_block]);
|
||||
let instruction_offset = bytecode.len();
|
||||
|
|
@ -1426,9 +1534,12 @@ impl Generator {
|
|||
|
||||
// Unterminated blocks get an implicit End(undefined).
|
||||
if !block.terminated {
|
||||
let mut undef_rewritten = undefined_constant_operand.expect("undefined constant must exist");
|
||||
let mut undef_rewritten =
|
||||
undefined_constant_operand.expect("undefined constant must exist");
|
||||
undef_rewritten.offset_index_by(number_of_registers + number_of_locals);
|
||||
let end_instruction = Instruction::End { value: undef_rewritten };
|
||||
let end_instruction = Instruction::End {
|
||||
value: undef_rewritten,
|
||||
};
|
||||
let instruction_offset = bytecode.len();
|
||||
source_map.push(SourceMapEntry {
|
||||
bytecode_offset: u32_from_usize(instruction_offset),
|
||||
|
|
@ -1443,7 +1554,9 @@ impl Generator {
|
|||
exception_handlers.push(ExceptionHandler {
|
||||
start_offset: u32_from_usize(block_start),
|
||||
end_offset: u32_from_usize(bytecode.len()),
|
||||
handler_offset: u32_from_usize(block_offsets[handler_label.basic_block_index()]),
|
||||
handler_offset: u32_from_usize(
|
||||
block_offsets[handler_label.basic_block_index()],
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1539,10 +1652,12 @@ pub fn parse_bigint(s: &str) -> Option<num_bigint::BigInt> {
|
|||
}
|
||||
|
||||
/// Use `preferred_dst` if available, otherwise allocate a fresh register.
|
||||
pub fn choose_dst(generator: &mut Generator, preferred_dst: Option<&ScopedOperand>) -> ScopedOperand {
|
||||
pub fn choose_dst(
|
||||
generator: &mut Generator,
|
||||
preferred_dst: Option<&ScopedOperand>,
|
||||
) -> ScopedOperand {
|
||||
match preferred_dst {
|
||||
Some(dst) => dst.clone(),
|
||||
None => generator.allocate_register(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ impl Operand {
|
|||
}
|
||||
|
||||
pub fn operand_type(self) -> OperandType {
|
||||
assert!(!self.is_invalid(), "operand_type() called on INVALID operand");
|
||||
assert!(
|
||||
!self.is_invalid(),
|
||||
"operand_type() called on INVALID operand"
|
||||
);
|
||||
match (self.0 >> Self::TYPE_SHIFT) & 0x7 {
|
||||
0 => OperandType::Register,
|
||||
1 => OperandType::Local,
|
||||
|
|
|
|||
|
|
@ -138,7 +138,10 @@ fn decode_code_point(source: &[u16], pos: usize) -> (u32, usize) {
|
|||
return (0xFFFD, 1);
|
||||
}
|
||||
let cu = source[pos];
|
||||
if is_utf16_high_surrogate(cu) && pos + 1 < source.len() && is_utf16_low_surrogate(source[pos + 1]) {
|
||||
if is_utf16_high_surrogate(cu)
|
||||
&& pos + 1 < source.len()
|
||||
&& is_utf16_low_surrogate(source[pos + 1])
|
||||
{
|
||||
let hi = cu as u32;
|
||||
let lo = source[pos + 1] as u32;
|
||||
let cp = 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
|
||||
|
|
@ -151,7 +154,10 @@ fn decode_code_point(source: &[u16], pos: usize) -> (u32, usize) {
|
|||
// https://tc39.es/ecma262/#sec-line-terminators
|
||||
// LineTerminator :: <LF> | <CR> | <LS> | <PS>
|
||||
fn is_line_terminator_cp(cp: u32) -> bool {
|
||||
cp == '\n' as u32 || cp == '\r' as u32 || cp == LINE_SEPARATOR as u32 || cp == PARAGRAPH_SEPARATOR as u32
|
||||
cp == '\n' as u32
|
||||
|| cp == '\r' as u32
|
||||
|| cp == LINE_SEPARATOR as u32
|
||||
|| cp == PARAGRAPH_SEPARATOR as u32
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-white-space
|
||||
|
|
@ -165,10 +171,7 @@ fn is_whitespace_cp(cp: u32) -> bool {
|
|||
return true;
|
||||
}
|
||||
// Unicode General Category "Space_Separator" (Zs)
|
||||
matches!(
|
||||
cp,
|
||||
0x1680 | 0x2000..=0x200A | 0x202F | 0x205F | 0x3000
|
||||
)
|
||||
matches!(cp, 0x1680 | 0x2000..=0x200A | 0x202F | 0x205F | 0x3000)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-identifier-names
|
||||
|
|
@ -186,7 +189,12 @@ fn is_identifier_start_cp(cp: u32) -> bool {
|
|||
// https://tc39.es/ecma262/#sec-identifier-names
|
||||
// IdentifierPartChar :: UnicodeIDContinue | $ | <ZWNJ> | <ZWJ>
|
||||
fn is_identifier_continue_cp(cp: u32) -> bool {
|
||||
if is_ascii_alphanumeric(cp) || cp == '$' as u32 || cp == '_' as u32 || cp == ZERO_WIDTH_NON_JOINER || cp == ZERO_WIDTH_JOINER {
|
||||
if is_ascii_alphanumeric(cp)
|
||||
|| cp == '$' as u32
|
||||
|| cp == '_' as u32
|
||||
|| cp == ZERO_WIDTH_NON_JOINER
|
||||
|| cp == ZERO_WIDTH_JOINER
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if cp < 128 {
|
||||
|
|
@ -213,62 +221,142 @@ fn keyword_from_str(s: &[u16]) -> Option<TokenType> {
|
|||
// against compile-time UTF-16 constants (zero allocation).
|
||||
match s.len() {
|
||||
2 => {
|
||||
if s == utf16!("do") { return Some(TokenType::Do); }
|
||||
if s == utf16!("if") { return Some(TokenType::If); }
|
||||
if s == utf16!("in") { return Some(TokenType::In); }
|
||||
if s == utf16!("do") {
|
||||
return Some(TokenType::Do);
|
||||
}
|
||||
if s == utf16!("if") {
|
||||
return Some(TokenType::If);
|
||||
}
|
||||
if s == utf16!("in") {
|
||||
return Some(TokenType::In);
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if s == utf16!("for") { return Some(TokenType::For); }
|
||||
if s == utf16!("let") { return Some(TokenType::Let); }
|
||||
if s == utf16!("new") { return Some(TokenType::New); }
|
||||
if s == utf16!("try") { return Some(TokenType::Try); }
|
||||
if s == utf16!("var") { return Some(TokenType::Var); }
|
||||
if s == utf16!("for") {
|
||||
return Some(TokenType::For);
|
||||
}
|
||||
if s == utf16!("let") {
|
||||
return Some(TokenType::Let);
|
||||
}
|
||||
if s == utf16!("new") {
|
||||
return Some(TokenType::New);
|
||||
}
|
||||
if s == utf16!("try") {
|
||||
return Some(TokenType::Try);
|
||||
}
|
||||
if s == utf16!("var") {
|
||||
return Some(TokenType::Var);
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
if s == utf16!("case") { return Some(TokenType::Case); }
|
||||
if s == utf16!("else") { return Some(TokenType::Else); }
|
||||
if s == utf16!("enum") { return Some(TokenType::Enum); }
|
||||
if s == utf16!("null") { return Some(TokenType::NullLiteral); }
|
||||
if s == utf16!("this") { return Some(TokenType::This); }
|
||||
if s == utf16!("true") { return Some(TokenType::BoolLiteral); }
|
||||
if s == utf16!("void") { return Some(TokenType::Void); }
|
||||
if s == utf16!("with") { return Some(TokenType::With); }
|
||||
if s == utf16!("case") {
|
||||
return Some(TokenType::Case);
|
||||
}
|
||||
if s == utf16!("else") {
|
||||
return Some(TokenType::Else);
|
||||
}
|
||||
if s == utf16!("enum") {
|
||||
return Some(TokenType::Enum);
|
||||
}
|
||||
if s == utf16!("null") {
|
||||
return Some(TokenType::NullLiteral);
|
||||
}
|
||||
if s == utf16!("this") {
|
||||
return Some(TokenType::This);
|
||||
}
|
||||
if s == utf16!("true") {
|
||||
return Some(TokenType::BoolLiteral);
|
||||
}
|
||||
if s == utf16!("void") {
|
||||
return Some(TokenType::Void);
|
||||
}
|
||||
if s == utf16!("with") {
|
||||
return Some(TokenType::With);
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
if s == utf16!("async") { return Some(TokenType::Async); }
|
||||
if s == utf16!("await") { return Some(TokenType::Await); }
|
||||
if s == utf16!("break") { return Some(TokenType::Break); }
|
||||
if s == utf16!("catch") { return Some(TokenType::Catch); }
|
||||
if s == utf16!("class") { return Some(TokenType::Class); }
|
||||
if s == utf16!("const") { return Some(TokenType::Const); }
|
||||
if s == utf16!("false") { return Some(TokenType::BoolLiteral); }
|
||||
if s == utf16!("super") { return Some(TokenType::Super); }
|
||||
if s == utf16!("throw") { return Some(TokenType::Throw); }
|
||||
if s == utf16!("while") { return Some(TokenType::While); }
|
||||
if s == utf16!("yield") { return Some(TokenType::Yield); }
|
||||
if s == utf16!("async") {
|
||||
return Some(TokenType::Async);
|
||||
}
|
||||
if s == utf16!("await") {
|
||||
return Some(TokenType::Await);
|
||||
}
|
||||
if s == utf16!("break") {
|
||||
return Some(TokenType::Break);
|
||||
}
|
||||
if s == utf16!("catch") {
|
||||
return Some(TokenType::Catch);
|
||||
}
|
||||
if s == utf16!("class") {
|
||||
return Some(TokenType::Class);
|
||||
}
|
||||
if s == utf16!("const") {
|
||||
return Some(TokenType::Const);
|
||||
}
|
||||
if s == utf16!("false") {
|
||||
return Some(TokenType::BoolLiteral);
|
||||
}
|
||||
if s == utf16!("super") {
|
||||
return Some(TokenType::Super);
|
||||
}
|
||||
if s == utf16!("throw") {
|
||||
return Some(TokenType::Throw);
|
||||
}
|
||||
if s == utf16!("while") {
|
||||
return Some(TokenType::While);
|
||||
}
|
||||
if s == utf16!("yield") {
|
||||
return Some(TokenType::Yield);
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if s == utf16!("delete") { return Some(TokenType::Delete); }
|
||||
if s == utf16!("export") { return Some(TokenType::Export); }
|
||||
if s == utf16!("import") { return Some(TokenType::Import); }
|
||||
if s == utf16!("return") { return Some(TokenType::Return); }
|
||||
if s == utf16!("delete") {
|
||||
return Some(TokenType::Delete);
|
||||
}
|
||||
if s == utf16!("export") {
|
||||
return Some(TokenType::Export);
|
||||
}
|
||||
if s == utf16!("import") {
|
||||
return Some(TokenType::Import);
|
||||
}
|
||||
if s == utf16!("return") {
|
||||
return Some(TokenType::Return);
|
||||
}
|
||||
// NB: "static" is intentionally NOT lexed as TokenType::Static.
|
||||
// C++ lexes it as Identifier and handles it contextually in class parsing.
|
||||
if s == utf16!("switch") { return Some(TokenType::Switch); }
|
||||
if s == utf16!("typeof") { return Some(TokenType::Typeof); }
|
||||
if s == utf16!("switch") {
|
||||
return Some(TokenType::Switch);
|
||||
}
|
||||
if s == utf16!("typeof") {
|
||||
return Some(TokenType::Typeof);
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
if s == utf16!("default") { return Some(TokenType::Default); }
|
||||
if s == utf16!("extends") { return Some(TokenType::Extends); }
|
||||
if s == utf16!("finally") { return Some(TokenType::Finally); }
|
||||
if s == utf16!("default") {
|
||||
return Some(TokenType::Default);
|
||||
}
|
||||
if s == utf16!("extends") {
|
||||
return Some(TokenType::Extends);
|
||||
}
|
||||
if s == utf16!("finally") {
|
||||
return Some(TokenType::Finally);
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
if s == utf16!("continue") { return Some(TokenType::Continue); }
|
||||
if s == utf16!("debugger") { return Some(TokenType::Debugger); }
|
||||
if s == utf16!("function") { return Some(TokenType::Function); }
|
||||
if s == utf16!("continue") {
|
||||
return Some(TokenType::Continue);
|
||||
}
|
||||
if s == utf16!("debugger") {
|
||||
return Some(TokenType::Debugger);
|
||||
}
|
||||
if s == utf16!("function") {
|
||||
return Some(TokenType::Function);
|
||||
}
|
||||
}
|
||||
10 => {
|
||||
if s == utf16!("instanceof") { return Some(TokenType::Instanceof); }
|
||||
if s == utf16!("instanceof") {
|
||||
return Some(TokenType::Instanceof);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -375,7 +463,12 @@ impl<'a> Lexer<'a> {
|
|||
lexer
|
||||
}
|
||||
|
||||
pub fn new_at_offset(source: &'a [u16], offset: usize, line_number: u32, line_column: u32) -> Self {
|
||||
pub fn new_at_offset(
|
||||
source: &'a [u16],
|
||||
offset: usize,
|
||||
line_number: u32,
|
||||
line_column: u32,
|
||||
) -> Self {
|
||||
let mut lexer = Lexer {
|
||||
source,
|
||||
position: offset,
|
||||
|
|
@ -394,11 +487,15 @@ impl<'a> Lexer<'a> {
|
|||
}
|
||||
|
||||
fn current_template_state(&self) -> &TemplateState {
|
||||
self.template_states.last().expect("template_states must not be empty")
|
||||
self.template_states
|
||||
.last()
|
||||
.expect("template_states must not be empty")
|
||||
}
|
||||
|
||||
fn current_template_state_mut(&mut self) -> &mut TemplateState {
|
||||
self.template_states.last_mut().expect("template_states must not be empty")
|
||||
self.template_states
|
||||
.last_mut()
|
||||
.expect("template_states must not be empty")
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-html-like-comments
|
||||
|
|
@ -589,7 +686,7 @@ impl<'a> Lexer<'a> {
|
|||
/// Re-scan the source from `scan_start` (1-based position) to `self.position`
|
||||
/// and build a decoded identifier value. Only called when escapes are present.
|
||||
fn build_identifier_value(&self, scan_start: usize) -> Utf16String {
|
||||
let raw = &self.source[scan_start - 1 .. self.position - 1];
|
||||
let raw = &self.source[scan_start - 1..self.position - 1];
|
||||
let mut result = Utf16String(Vec::with_capacity(raw.len()));
|
||||
let mut i = 0;
|
||||
while i < raw.len() {
|
||||
|
|
@ -680,7 +777,9 @@ impl<'a> Lexer<'a> {
|
|||
if self.position + 1 >= self.source_len() {
|
||||
return false;
|
||||
}
|
||||
self.current_code_unit == a && self.source[self.position] == b && self.source[self.position + 1] == c
|
||||
self.current_code_unit == a
|
||||
&& self.source[self.position] == b
|
||||
&& self.source[self.position + 1] == c
|
||||
}
|
||||
|
||||
fn match4(&self, a: u16, b: u16, c: u16, d: u16) -> bool {
|
||||
|
|
@ -713,7 +812,9 @@ impl<'a> Lexer<'a> {
|
|||
fn is_line_comment_start(&self, line_has_token_yet: bool) -> bool {
|
||||
self.match2(ch(b'/'), ch(b'/'))
|
||||
|| (self.allow_html_comments && self.match4(ch(b'<'), ch(b'!'), ch(b'-'), ch(b'-')))
|
||||
|| (self.allow_html_comments && !line_has_token_yet && self.match3(ch(b'-'), ch(b'-'), ch(b'>')))
|
||||
|| (self.allow_html_comments
|
||||
&& !line_has_token_yet
|
||||
&& self.match3(ch(b'-'), ch(b'-'), ch(b'>')))
|
||||
|| (self.match2(ch(b'#'), ch(b'!')) && self.position == 1)
|
||||
}
|
||||
|
||||
|
|
@ -757,7 +858,9 @@ impl<'a> Lexer<'a> {
|
|||
if !is_ascii_digit(self.current_code_unit) {
|
||||
return false;
|
||||
}
|
||||
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
|
||||
while is_ascii_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
true
|
||||
|
|
@ -779,7 +882,9 @@ impl<'a> Lexer<'a> {
|
|||
if !is_octal_digit(self.current_code_unit) {
|
||||
return false;
|
||||
}
|
||||
while is_octal_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_octal_digit) {
|
||||
while is_octal_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_octal_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
true
|
||||
|
|
@ -790,7 +895,9 @@ impl<'a> Lexer<'a> {
|
|||
if !is_ascii_hex_digit(self.current_code_unit) {
|
||||
return false;
|
||||
}
|
||||
while is_ascii_hex_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_hex_digit) {
|
||||
while is_ascii_hex_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_ascii_hex_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
true
|
||||
|
|
@ -808,7 +915,9 @@ impl<'a> Lexer<'a> {
|
|||
if !is_binary_digit(self.current_code_unit) {
|
||||
return false;
|
||||
}
|
||||
while is_binary_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_binary_digit) {
|
||||
while is_binary_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_binary_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
true
|
||||
|
|
@ -817,7 +926,9 @@ impl<'a> Lexer<'a> {
|
|||
fn consume_regex_literal(&mut self) -> TokenType {
|
||||
self.regex_is_in_character_class = false;
|
||||
while !self.is_eof() {
|
||||
if self.is_line_terminator() || (!self.regex_is_in_character_class && self.current_code_unit == ch(b'/')) {
|
||||
if self.is_line_terminator()
|
||||
|| (!self.regex_is_in_character_class && self.current_code_unit == ch(b'/'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -915,11 +1026,15 @@ impl<'a> Lexer<'a> {
|
|||
|
||||
if self.current_token_type == TokenType::RegexLiteral
|
||||
&& !self.is_eof()
|
||||
&& (self.current_code_unit < 128 && (self.current_code_unit as u8 as char).is_ascii_alphabetic())
|
||||
&& (self.current_code_unit < 128
|
||||
&& (self.current_code_unit as u8 as char).is_ascii_alphabetic())
|
||||
&& !did_consume_whitespace_or_comments
|
||||
{
|
||||
token_type = TokenType::RegexFlags;
|
||||
while !self.is_eof() && self.current_code_unit < 128 && (self.current_code_unit as u8 as char).is_ascii_alphabetic() {
|
||||
while !self.is_eof()
|
||||
&& self.current_code_unit < 128
|
||||
&& (self.current_code_unit as u8 as char).is_ascii_alphabetic()
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
} else if self.current_code_unit == ch(b'`') {
|
||||
|
|
@ -958,7 +1073,10 @@ impl<'a> Lexer<'a> {
|
|||
self.consume();
|
||||
self.current_template_state_mut().in_expression = true;
|
||||
} else {
|
||||
while !self.match2(ch(b'$'), ch(b'{')) && self.current_code_unit != ch(b'`') && !self.is_eof() {
|
||||
while !self.match2(ch(b'$'), ch(b'{'))
|
||||
&& self.current_code_unit != ch(b'`')
|
||||
&& !self.is_eof()
|
||||
{
|
||||
if self.match2(ch(b'\\'), ch(b'$'))
|
||||
|| self.match2(ch(b'\\'), ch(b'`'))
|
||||
|| self.match2(ch(b'\\'), ch(b'\\'))
|
||||
|
|
@ -983,7 +1101,9 @@ impl<'a> Lexer<'a> {
|
|||
token_type = TokenType::PrivateIdentifier;
|
||||
} else {
|
||||
token_type = TokenType::Invalid;
|
||||
token_message = Some("Start of private name '#' but not followed by valid identifier".to_string());
|
||||
token_message = Some(
|
||||
"Start of private name '#' but not followed by valid identifier".to_string(),
|
||||
);
|
||||
}
|
||||
} else if let Some((_cp, len)) = self.is_identifier_start() {
|
||||
let has_escape = self.scan_identifier_body(len);
|
||||
|
|
@ -1001,7 +1121,7 @@ impl<'a> Lexer<'a> {
|
|||
}
|
||||
identifier_value = Some(decoded);
|
||||
} else {
|
||||
let source_slice = &self.source[value_start - 1 .. self.position - 1];
|
||||
let source_slice = &self.source[value_start - 1..self.position - 1];
|
||||
if let Some(kw) = keyword_from_str(source_slice) {
|
||||
token_type = kw;
|
||||
} else {
|
||||
|
|
@ -1044,7 +1164,9 @@ impl<'a> Lexer<'a> {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
|
||||
while is_ascii_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
if self.current_code_unit == ch(b'n') {
|
||||
|
|
@ -1056,7 +1178,9 @@ impl<'a> Lexer<'a> {
|
|||
if self.current_code_unit == ch(b'_') {
|
||||
is_invalid = true;
|
||||
}
|
||||
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
|
||||
while is_ascii_digit(self.current_code_unit)
|
||||
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
|
||||
{
|
||||
self.consume();
|
||||
}
|
||||
}
|
||||
|
|
@ -1171,8 +1295,10 @@ impl<'a> Lexer<'a> {
|
|||
if token_type == TokenType::CurlyOpen {
|
||||
self.current_template_state_mut().open_bracket_count += 1;
|
||||
} else if token_type == TokenType::CurlyClose {
|
||||
self.current_template_state_mut().open_bracket_count =
|
||||
self.current_template_state().open_bracket_count.saturating_sub(1);
|
||||
self.current_template_state_mut().open_bracket_count = self
|
||||
.current_template_state()
|
||||
.open_bracket_count
|
||||
.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1181,7 +1307,12 @@ impl<'a> Lexer<'a> {
|
|||
let trivia_has_line_terminator = if trivia_start > 0 && value_start > trivia_start {
|
||||
self.source[trivia_start - 1..value_start - 1]
|
||||
.iter()
|
||||
.any(|&cu| cu == ch(b'\n') || cu == ch(b'\r') || cu == LINE_SEPARATOR || cu == PARAGRAPH_SEPARATOR)
|
||||
.any(|&cu| {
|
||||
cu == ch(b'\n')
|
||||
|| cu == ch(b'\r')
|
||||
|| cu == LINE_SEPARATOR
|
||||
|| cu == PARAGRAPH_SEPARATOR
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
|
|||
|
|
@ -164,8 +164,13 @@ unsafe fn source_from_raw<'a>(source: *const u16, len: usize) -> Option<&'a [u16
|
|||
}
|
||||
|
||||
/// Callback type for reporting parse errors to C++.
|
||||
type ParseErrorCallback =
|
||||
unsafe extern "C" fn(ctx: *mut c_void, message: *const u8, message_len: usize, line: u32, column: u32);
|
||||
type ParseErrorCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void,
|
||||
message: *const u8,
|
||||
message_len: usize,
|
||||
line: u32,
|
||||
column: u32,
|
||||
);
|
||||
|
||||
/// Log parser and scope collector errors, returning true if any were found.
|
||||
fn check_errors(parser: &mut Parser) -> bool {
|
||||
|
|
@ -205,13 +210,15 @@ fn check_errors_with_callback(
|
|||
|
||||
/// Convert scope local variables to generator LocalVariable format.
|
||||
fn convert_local_variables(scope: &ast::ScopeData) -> Vec<bytecode::generator::LocalVariable> {
|
||||
scope.local_variables.iter().map(|lv| {
|
||||
bytecode::generator::LocalVariable {
|
||||
scope
|
||||
.local_variables
|
||||
.iter()
|
||||
.map(|lv| bytecode::generator::LocalVariable {
|
||||
name: lv.name.clone(),
|
||||
is_lexically_declared: lv.kind == ast::LocalVarKind::LetOrConst,
|
||||
is_initialized_during_declaration_instantiation: false,
|
||||
}
|
||||
}).collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Create a Generator configured for program-level compilation.
|
||||
|
|
@ -333,7 +340,8 @@ pub unsafe extern "C" fn rust_compile_program(
|
|||
return std::ptr::null_mut();
|
||||
};
|
||||
|
||||
let mut gen = new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len);
|
||||
let mut gen =
|
||||
new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len);
|
||||
gen.function_table = std::mem::take(&mut parser.function_table);
|
||||
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr)
|
||||
})
|
||||
|
|
@ -374,7 +382,11 @@ pub unsafe extern "C" fn rust_compile_script(
|
|||
let Some(source_slice) = source_from_raw(source, source_len) else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
let mut parser = Parser::new_with_line_offset(source_slice, ProgramType::Script, u32_from_usize(initial_line_number));
|
||||
let mut parser = Parser::new_with_line_offset(
|
||||
source_slice,
|
||||
ProgramType::Script,
|
||||
u32_from_usize(initial_line_number),
|
||||
);
|
||||
|
||||
let program = parser.parse_program(false);
|
||||
|
||||
|
|
@ -389,7 +401,12 @@ pub unsafe extern "C" fn rust_compile_script(
|
|||
ast_dump::dump_program(&program, use_color, &parser.function_table);
|
||||
}
|
||||
|
||||
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
|
||||
write_ast_dump_output(
|
||||
&program,
|
||||
&parser.function_table,
|
||||
ast_dump_output,
|
||||
ast_dump_output_len,
|
||||
);
|
||||
|
||||
let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner {
|
||||
(data.scope.clone(), data.is_strict_mode)
|
||||
|
|
@ -399,12 +416,20 @@ pub unsafe extern "C" fn rust_compile_script(
|
|||
|
||||
let mut gen = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len);
|
||||
gen.function_table = std::mem::take(&mut parser.function_table);
|
||||
let exec_ptr = compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
|
||||
let exec_ptr =
|
||||
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
|
||||
if exec_ptr.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
extract_script_gdi(&scope_ref.borrow(), is_strict, vm_ptr, source_code_ptr, gdi_context, &mut gen.function_table);
|
||||
extract_script_gdi(
|
||||
&scope_ref.borrow(),
|
||||
is_strict,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
gdi_context,
|
||||
&mut gen.function_table,
|
||||
);
|
||||
|
||||
exec_ptr
|
||||
})
|
||||
|
|
@ -462,7 +487,12 @@ pub unsafe extern "C" fn rust_compile_eval(
|
|||
|
||||
parser.scope_collector.analyze(true);
|
||||
|
||||
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
|
||||
write_ast_dump_output(
|
||||
&program,
|
||||
&parser.function_table,
|
||||
ast_dump_output,
|
||||
ast_dump_output_len,
|
||||
);
|
||||
|
||||
let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner {
|
||||
(data.scope.clone(), data.is_strict_mode)
|
||||
|
|
@ -472,12 +502,20 @@ pub unsafe extern "C" fn rust_compile_eval(
|
|||
|
||||
let mut gen = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len);
|
||||
gen.function_table = std::mem::take(&mut parser.function_table);
|
||||
let exec_ptr = compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
|
||||
let exec_ptr =
|
||||
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
|
||||
if exec_ptr.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
extract_eval_gdi(&scope_ref.borrow(), is_strict, vm_ptr, source_code_ptr, gdi_context, &mut gen.function_table);
|
||||
extract_eval_gdi(
|
||||
&scope_ref.borrow(),
|
||||
is_strict,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
gdi_context,
|
||||
&mut gen.function_table,
|
||||
);
|
||||
|
||||
exec_ptr
|
||||
})
|
||||
|
|
@ -530,7 +568,8 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
// Validate parameters standalone.
|
||||
// First lex independently to catch lexer errors (e.g. unterminated comments)
|
||||
// with correct line/column positions relative to the parameter string.
|
||||
let Some(parameters_slice) = source_from_raw(parameters_source, parameters_source_len) else {
|
||||
let Some(parameters_slice) = source_from_raw(parameters_source, parameters_source_len)
|
||||
else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
{
|
||||
|
|
@ -541,10 +580,18 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
break;
|
||||
}
|
||||
if token.token_type == token::TokenType::Invalid {
|
||||
let msg = token.message.unwrap_or_else(|| format!("Unexpected token {}", token.token_type.name()));
|
||||
let msg = token
|
||||
.message
|
||||
.unwrap_or_else(|| format!("Unexpected token {}", token.token_type.name()));
|
||||
if let Some(cb) = error_callback {
|
||||
unsafe {
|
||||
cb(error_context, msg.as_ptr(), msg.len(), token.line_number, token.line_column);
|
||||
cb(
|
||||
error_context,
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
token.line_number,
|
||||
token.line_column,
|
||||
);
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
|
|
@ -555,10 +602,18 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
{
|
||||
let mut validate_src: Vec<u16> = Vec::new();
|
||||
match kind {
|
||||
ast::FunctionKind::Generator => validate_src.extend_from_slice(utf16!("function* test(")),
|
||||
ast::FunctionKind::Async => validate_src.extend_from_slice(utf16!("async function test(")),
|
||||
ast::FunctionKind::AsyncGenerator => validate_src.extend_from_slice(utf16!("async function* test(")),
|
||||
ast::FunctionKind::Normal => validate_src.extend_from_slice(utf16!("function test(")),
|
||||
ast::FunctionKind::Generator => {
|
||||
validate_src.extend_from_slice(utf16!("function* test("))
|
||||
}
|
||||
ast::FunctionKind::Async => {
|
||||
validate_src.extend_from_slice(utf16!("async function test("))
|
||||
}
|
||||
ast::FunctionKind::AsyncGenerator => {
|
||||
validate_src.extend_from_slice(utf16!("async function* test("))
|
||||
}
|
||||
ast::FunctionKind::Normal => {
|
||||
validate_src.extend_from_slice(utf16!("function test("))
|
||||
}
|
||||
}
|
||||
validate_src.extend_from_slice(parameters_slice);
|
||||
validate_src.extend_from_slice(utf16!("\n) {}"));
|
||||
|
|
@ -625,7 +680,12 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
|
||||
write_ast_dump_output(
|
||||
&program,
|
||||
&parser.function_table,
|
||||
ast_dump_output,
|
||||
ast_dump_output_len,
|
||||
);
|
||||
|
||||
// Extract the FunctionExpression from the program.
|
||||
// The program should contain a single ExpressionStatement wrapping a FunctionExpression.
|
||||
|
|
@ -665,7 +725,13 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
let is_strict = function_data.is_strict_mode;
|
||||
let subtable = parser.function_table.extract_reachable(&function_data);
|
||||
|
||||
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
|
||||
bytecode::ffi::create_sfd_for_gdi(
|
||||
function_data,
|
||||
subtable,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
is_strict,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -708,15 +774,22 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
|
|||
let program = parser.parse_program(true); // strict mode
|
||||
|
||||
if parser.has_errors() {
|
||||
let errors: Vec<String> = parser.errors().iter().map(|e| {
|
||||
format!("{}:{}: {}", e.line, e.column, e.message)
|
||||
}).collect();
|
||||
let errors: Vec<String> = parser
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|e| format!("{}:{}: {}", e.line, e.column, e.message))
|
||||
.collect();
|
||||
panic!("Parse errors in builtin file: {}", errors.join("; "));
|
||||
}
|
||||
|
||||
parser.scope_collector.analyze(false);
|
||||
|
||||
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
|
||||
write_ast_dump_output(
|
||||
&program,
|
||||
&parser.function_table,
|
||||
ast_dump_output,
|
||||
ast_dump_output_len,
|
||||
);
|
||||
|
||||
let scope_ref = if let StatementKind::Program(ref data) = program.inner {
|
||||
data.scope.clone()
|
||||
|
|
@ -726,7 +799,12 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
|
|||
|
||||
let scope = scope_ref.borrow();
|
||||
for child in &scope.children {
|
||||
if let StatementKind::FunctionDeclaration { function_id, ref name, .. } = child.inner {
|
||||
if let StatementKind::FunctionDeclaration {
|
||||
function_id,
|
||||
ref name,
|
||||
..
|
||||
} = child.inner
|
||||
{
|
||||
let function_data = parser.function_table.take(function_id);
|
||||
let subtable = parser.function_table.extract_reachable(&function_data);
|
||||
let sfd_ptr = bytecode::ffi::create_sfd_for_gdi(
|
||||
|
|
@ -760,9 +838,13 @@ type ModuleBoolCallback = unsafe extern "C" fn(ctx: *mut c_void, value: bool);
|
|||
type ModuleNameCallback = unsafe extern "C" fn(ctx: *mut c_void, name: *const u16, name_len: usize);
|
||||
type ModuleImportEntryCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void,
|
||||
import_name: *const u16, import_name_len: usize, is_namespace: bool,
|
||||
local_name: *const u16, local_name_len: usize,
|
||||
module_specifier: *const u16, specifier_len: usize,
|
||||
import_name: *const u16,
|
||||
import_name_len: usize,
|
||||
is_namespace: bool,
|
||||
local_name: *const u16,
|
||||
local_name_len: usize,
|
||||
module_specifier: *const u16,
|
||||
specifier_len: usize,
|
||||
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_count: usize,
|
||||
|
|
@ -770,25 +852,32 @@ type ModuleImportEntryCallback = unsafe extern "C" fn(
|
|||
type ModuleExportEntryCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void,
|
||||
kind: u8,
|
||||
export_name: *const u16, export_name_len: usize,
|
||||
local_or_import_name: *const u16, local_or_import_name_len: usize,
|
||||
module_specifier: *const u16, specifier_len: usize,
|
||||
export_name: *const u16,
|
||||
export_name_len: usize,
|
||||
local_or_import_name: *const u16,
|
||||
local_or_import_name_len: usize,
|
||||
module_specifier: *const u16,
|
||||
specifier_len: usize,
|
||||
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_count: usize,
|
||||
);
|
||||
type ModuleRequestedModuleCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void,
|
||||
specifier: *const u16, specifier_len: usize,
|
||||
specifier: *const u16,
|
||||
specifier_len: usize,
|
||||
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
|
||||
attribute_count: usize,
|
||||
);
|
||||
type ModuleFunctionCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void, sfd_ptr: *mut c_void, name: *const u16, name_len: usize,
|
||||
);
|
||||
type ModuleFunctionCallback =
|
||||
unsafe extern "C" fn(ctx: *mut c_void, sfd_ptr: *mut c_void, name: *const u16, name_len: usize);
|
||||
type ModuleLexicalBindingCallback = unsafe extern "C" fn(
|
||||
ctx: *mut c_void, name: *const u16, name_len: usize, is_constant: bool, function_index: i32,
|
||||
ctx: *mut c_void,
|
||||
name: *const u16,
|
||||
name_len: usize,
|
||||
is_constant: bool,
|
||||
function_index: i32,
|
||||
);
|
||||
|
||||
/// Module callback table passed from C++ to avoid many function pointer parameters.
|
||||
|
|
@ -809,13 +898,18 @@ pub struct ModuleCallbacks {
|
|||
/// Helper to build FFI attribute arrays from a ModuleRequest.
|
||||
fn build_attribute_slices(
|
||||
attributes: &[ast::ImportAttribute],
|
||||
) -> (Vec<bytecode::ffi::FFIUtf16Slice>, Vec<bytecode::ffi::FFIUtf16Slice>) {
|
||||
) -> (
|
||||
Vec<bytecode::ffi::FFIUtf16Slice>,
|
||||
Vec<bytecode::ffi::FFIUtf16Slice>,
|
||||
) {
|
||||
attributes
|
||||
.iter()
|
||||
.map(|a| (
|
||||
bytecode::ffi::FFIUtf16Slice::from(a.key.as_ref()),
|
||||
bytecode::ffi::FFIUtf16Slice::from(a.value.as_ref()),
|
||||
))
|
||||
.map(|a| {
|
||||
(
|
||||
bytecode::ffi::FFIUtf16Slice::from(a.key.as_ref()),
|
||||
bytecode::ffi::FFIUtf16Slice::from(a.value.as_ref()),
|
||||
)
|
||||
})
|
||||
.unzip()
|
||||
}
|
||||
|
||||
|
|
@ -838,14 +932,31 @@ unsafe fn call_export_callback(
|
|||
if let Some(mr) = module_request {
|
||||
let (keys, values) = build_attribute_slices(&mr.attributes);
|
||||
callback(
|
||||
ctx, kind, en_ptr, en_len, lin_ptr, lin_len,
|
||||
mr.module_specifier.as_ptr(), mr.module_specifier.len(),
|
||||
keys.as_ptr(), values.as_ptr(), keys.len(),
|
||||
ctx,
|
||||
kind,
|
||||
en_ptr,
|
||||
en_len,
|
||||
lin_ptr,
|
||||
lin_len,
|
||||
mr.module_specifier.as_ptr(),
|
||||
mr.module_specifier.len(),
|
||||
keys.as_ptr(),
|
||||
values.as_ptr(),
|
||||
keys.len(),
|
||||
);
|
||||
} else {
|
||||
callback(
|
||||
ctx, kind, en_ptr, en_len, lin_ptr, lin_len,
|
||||
std::ptr::null(), 0, std::ptr::null(), std::ptr::null(), 0,
|
||||
ctx,
|
||||
kind,
|
||||
en_ptr,
|
||||
en_len,
|
||||
lin_ptr,
|
||||
lin_len,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -903,7 +1014,12 @@ pub unsafe extern "C" fn rust_compile_module(
|
|||
ast_dump::dump_program(&program, use_color, &parser.function_table);
|
||||
}
|
||||
|
||||
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
|
||||
write_ast_dump_output(
|
||||
&program,
|
||||
&parser.function_table,
|
||||
ast_dump_output,
|
||||
ast_dump_output_len,
|
||||
);
|
||||
|
||||
let program_data = if let StatementKind::Program(ref data) = program.inner {
|
||||
data
|
||||
|
|
@ -924,7 +1040,12 @@ pub unsafe extern "C" fn rust_compile_module(
|
|||
|
||||
// 4. Extract var declared names and lexical bindings.
|
||||
extract_module_declarations(
|
||||
&scope_ref.borrow(), vm_ptr, source_code_ptr, module_context, cb, &mut function_table,
|
||||
&scope_ref.borrow(),
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
module_context,
|
||||
cb,
|
||||
&mut function_table,
|
||||
);
|
||||
|
||||
// 5. Compute requested modules (sorted by source offset).
|
||||
|
|
@ -934,7 +1055,13 @@ pub unsafe extern "C" fn rust_compile_module(
|
|||
if has_top_level_await {
|
||||
// Compile as an async wrapper function.
|
||||
let exec_ptr = compile_module_as_async(
|
||||
&program, &scope_ref, vm_ptr, source_code_ptr, source, source_len, function_table,
|
||||
&program,
|
||||
&scope_ref,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
source,
|
||||
source_len,
|
||||
function_table,
|
||||
);
|
||||
if !tla_executable_out.is_null() {
|
||||
*tla_executable_out = exec_ptr;
|
||||
|
|
@ -953,11 +1080,7 @@ pub unsafe extern "C" fn rust_compile_module(
|
|||
}
|
||||
|
||||
/// Extract import/export metadata from a module's scope and call C++ callbacks.
|
||||
unsafe fn extract_module_metadata(
|
||||
scope: &ast::ScopeData,
|
||||
ctx: *mut c_void,
|
||||
cb: &ModuleCallbacks,
|
||||
) {
|
||||
unsafe fn extract_module_metadata(scope: &ast::ScopeData, ctx: *mut c_void, cb: &ModuleCallbacks) {
|
||||
use ast::{ExportEntryKind, StatementKind};
|
||||
|
||||
// Collect all import entries with their module requests.
|
||||
|
|
@ -975,15 +1098,22 @@ unsafe fn extract_module_metadata(
|
|||
let (in_ptr, in_len, is_ns) = entry
|
||||
.import_name
|
||||
.as_ref()
|
||||
.map_or((std::ptr::null(), 0, true), |n| (n.as_ptr(), n.len(), false));
|
||||
.map_or((std::ptr::null(), 0, true), |n| {
|
||||
(n.as_ptr(), n.len(), false)
|
||||
});
|
||||
let (keys, values) = build_attribute_slices(&import_data.module_request.attributes);
|
||||
(cb.push_import_entry)(
|
||||
ctx,
|
||||
in_ptr, in_len, is_ns,
|
||||
entry.local_name.as_ptr(), entry.local_name.len(),
|
||||
in_ptr,
|
||||
in_len,
|
||||
is_ns,
|
||||
entry.local_name.as_ptr(),
|
||||
entry.local_name.len(),
|
||||
import_data.module_request.module_specifier.as_ptr(),
|
||||
import_data.module_request.module_specifier.len(),
|
||||
keys.as_ptr(), values.as_ptr(), keys.len(),
|
||||
keys.as_ptr(),
|
||||
values.as_ptr(),
|
||||
keys.len(),
|
||||
);
|
||||
|
||||
all_import_entries.push(ImportEntryWithRequest {
|
||||
|
|
@ -1004,9 +1134,7 @@ unsafe fn extract_module_metadata(
|
|||
};
|
||||
|
||||
// Handle default export binding name.
|
||||
if export_data.is_default_export
|
||||
&& export_data.entries.len() == 1
|
||||
{
|
||||
if export_data.is_default_export && export_data.entries.len() == 1 {
|
||||
let entry = &export_data.entries[0];
|
||||
// If the default export is not a declaration (function/class/etc.),
|
||||
// its binding name is the local_or_import_name.
|
||||
|
|
@ -1032,22 +1160,26 @@ unsafe fn extract_module_metadata(
|
|||
|
||||
if !has_module_request {
|
||||
// No module request: check against import entries.
|
||||
let matching_import = all_import_entries.iter().find(|ie| {
|
||||
entry.local_or_import_name.as_ref() == Some(&ie.local_name)
|
||||
});
|
||||
let matching_import = all_import_entries
|
||||
.iter()
|
||||
.find(|ie| entry.local_or_import_name.as_ref() == Some(&ie.local_name));
|
||||
|
||||
if let Some(import_entry) = matching_import {
|
||||
if import_entry.import_name.is_none() {
|
||||
// Namespace re-export → local export.
|
||||
call_export_callback(
|
||||
cb.push_local_export, ctx,
|
||||
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
|
||||
cb.push_local_export,
|
||||
ctx,
|
||||
entry.kind as u8,
|
||||
&entry.export_name,
|
||||
&entry.local_or_import_name,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
// Re-export of a specific binding → indirect export.
|
||||
call_export_callback(
|
||||
cb.push_indirect_export, ctx,
|
||||
cb.push_indirect_export,
|
||||
ctx,
|
||||
ExportEntryKind::NamedExport as u8,
|
||||
&entry.export_name,
|
||||
&import_entry.import_name,
|
||||
|
|
@ -1057,23 +1189,32 @@ unsafe fn extract_module_metadata(
|
|||
} else {
|
||||
// Direct local export.
|
||||
call_export_callback(
|
||||
cb.push_local_export, ctx,
|
||||
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
|
||||
cb.push_local_export,
|
||||
ctx,
|
||||
entry.kind as u8,
|
||||
&entry.export_name,
|
||||
&entry.local_or_import_name,
|
||||
None,
|
||||
);
|
||||
}
|
||||
} else if entry.kind == ExportEntryKind::ModuleRequestAllButDefault {
|
||||
// export * from "module"
|
||||
call_export_callback(
|
||||
cb.push_star_export, ctx,
|
||||
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
|
||||
cb.push_star_export,
|
||||
ctx,
|
||||
entry.kind as u8,
|
||||
&entry.export_name,
|
||||
&entry.local_or_import_name,
|
||||
export_data.module_request.as_ref(),
|
||||
);
|
||||
} else {
|
||||
// export { x } from "module" or export { x as y } from "module"
|
||||
call_export_callback(
|
||||
cb.push_indirect_export, ctx,
|
||||
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
|
||||
cb.push_indirect_export,
|
||||
ctx,
|
||||
entry.kind as u8,
|
||||
&entry.export_name,
|
||||
&entry.local_or_import_name,
|
||||
export_data.module_request.as_ref(),
|
||||
);
|
||||
}
|
||||
|
|
@ -1114,14 +1255,22 @@ unsafe fn extract_module_declarations(
|
|||
};
|
||||
|
||||
match declaration {
|
||||
StatementKind::FunctionDeclaration { function_id, ref name, .. } => {
|
||||
let is_default = is_exported
|
||||
&& name.as_ref().is_some_and(|n| n.name == default_name);
|
||||
StatementKind::FunctionDeclaration {
|
||||
function_id,
|
||||
ref name,
|
||||
..
|
||||
} => {
|
||||
let is_default =
|
||||
is_exported && name.as_ref().is_some_and(|n| n.name == default_name);
|
||||
|
||||
let function_data = function_table.take(*function_id);
|
||||
let subtable = function_table.extract_reachable(&function_data);
|
||||
let sfd_ptr = bytecode::ffi::create_sfd_for_gdi(
|
||||
function_data, subtable, vm_ptr, source_code_ptr, true,
|
||||
function_data,
|
||||
subtable,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
true,
|
||||
);
|
||||
if sfd_ptr.is_null() {
|
||||
continue;
|
||||
|
|
@ -1149,7 +1298,13 @@ unsafe fn extract_module_declarations(
|
|||
function_count += 1;
|
||||
|
||||
// Lexical binding uses the AST name (e.g., "*default*").
|
||||
(cb.push_lexical_binding)(ctx, binding_name.as_ptr(), binding_name.len(), false, function_index);
|
||||
(cb.push_lexical_binding)(
|
||||
ctx,
|
||||
binding_name.as_ptr(),
|
||||
binding_name.len(),
|
||||
false,
|
||||
function_index,
|
||||
);
|
||||
}
|
||||
StatementKind::ClassDeclaration(class_data) => {
|
||||
if let Some(ref name_ident) = class_data.name {
|
||||
|
|
@ -1347,10 +1502,7 @@ extern "C" {
|
|||
|
||||
/// Recursively collect var-declared names from a statement and all nested
|
||||
/// statements, excluding function/class bodies (which create new var scopes).
|
||||
fn collect_var_names_recursive(
|
||||
statement: &ast::StatementKind,
|
||||
push_name: &mut dyn FnMut(&[u16]),
|
||||
) {
|
||||
fn collect_var_names_recursive(statement: &ast::StatementKind, push_name: &mut dyn FnMut(&[u16])) {
|
||||
match statement {
|
||||
ast::StatementKind::VariableDeclaration {
|
||||
kind: ast::DeclarationKind::Var,
|
||||
|
|
@ -1391,7 +1543,11 @@ fn extract_gdi_common(
|
|||
// Var names (var declarations at any nesting level + top-level function declarations)
|
||||
for child in &scope.children {
|
||||
collect_var_names_recursive(&child.inner, push_var_name);
|
||||
if let StatementKind::FunctionDeclaration { name: Some(ref name_ident), .. } = child.inner {
|
||||
if let StatementKind::FunctionDeclaration {
|
||||
name: Some(ref name_ident),
|
||||
..
|
||||
} = child.inner
|
||||
{
|
||||
push_var_name(&name_ident.name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1400,7 +1556,12 @@ fn extract_gdi_common(
|
|||
let mut seen_names: HashSet<ast::Utf16String> = HashSet::new();
|
||||
let mut functions_to_init: Vec<(ast::FunctionId, ast::Utf16String)> = Vec::new();
|
||||
for child in scope.children.iter().rev() {
|
||||
if let StatementKind::FunctionDeclaration { function_id, name: Some(ref name_ident), .. } = child.inner {
|
||||
if let StatementKind::FunctionDeclaration {
|
||||
function_id,
|
||||
name: Some(ref name_ident),
|
||||
..
|
||||
} = child.inner
|
||||
{
|
||||
if seen_names.insert(name_ident.name.clone()) {
|
||||
functions_to_init.push((function_id, name_ident.name.clone()));
|
||||
}
|
||||
|
|
@ -1410,7 +1571,13 @@ fn extract_gdi_common(
|
|||
let function_data = function_table.take(*function_id);
|
||||
let subtable = function_table.extract_reachable(&function_data);
|
||||
let sfd_ptr = unsafe {
|
||||
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
|
||||
bytecode::ffi::create_sfd_for_gdi(
|
||||
function_data,
|
||||
subtable,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
is_strict,
|
||||
)
|
||||
};
|
||||
assert!(!sfd_ptr.is_null(), "create_sfd_for_gdi returned null");
|
||||
push_function(sfd_ptr, name);
|
||||
|
|
@ -1472,12 +1639,17 @@ unsafe fn extract_eval_gdi(
|
|||
eval_gdi_set_strict(ctx, is_strict);
|
||||
|
||||
extract_gdi_common(
|
||||
scope, vm_ptr, source_code_ptr, is_strict,
|
||||
scope,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
is_strict,
|
||||
&mut |name| eval_gdi_push_var_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |sfd_ptr, name| eval_gdi_push_function(ctx, sfd_ptr, name.as_ptr(), name.len()),
|
||||
&mut |name| eval_gdi_push_var_scoped_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |name| eval_gdi_push_annex_b_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |name, is_const| eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const),
|
||||
&mut |name, is_const| {
|
||||
eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const)
|
||||
},
|
||||
function_table,
|
||||
);
|
||||
}
|
||||
|
|
@ -1527,19 +1699,27 @@ unsafe fn extract_script_gdi(
|
|||
}
|
||||
|
||||
extract_gdi_common(
|
||||
scope, vm_ptr, source_code_ptr, is_strict,
|
||||
scope,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
is_strict,
|
||||
&mut |name| script_gdi_push_var_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |sfd_ptr, name| script_gdi_push_function(ctx, sfd_ptr, name.as_ptr(), name.len()),
|
||||
&mut |name| script_gdi_push_var_scoped_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |name| script_gdi_push_annex_b_name(ctx, name.as_ptr(), name.len()),
|
||||
&mut |name, is_const| script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const),
|
||||
&mut |name, is_const| {
|
||||
script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const)
|
||||
},
|
||||
function_table,
|
||||
);
|
||||
}
|
||||
|
||||
/// Visit each child statement of a statement, excluding function/class bodies
|
||||
/// (which create new var scopes). This enables recursive var-declaration walking.
|
||||
fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&ast::StatementKind)) {
|
||||
fn for_each_child_statement(
|
||||
statement: &ast::StatementKind,
|
||||
f: &mut dyn FnMut(&ast::StatementKind),
|
||||
) {
|
||||
use ast::StatementKind;
|
||||
|
||||
match statement {
|
||||
|
|
@ -1548,7 +1728,11 @@ fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&a
|
|||
f(&child.inner);
|
||||
}
|
||||
}
|
||||
StatementKind::If { consequent, alternate, .. } => {
|
||||
StatementKind::If {
|
||||
consequent,
|
||||
alternate,
|
||||
..
|
||||
} => {
|
||||
f(&consequent.inner);
|
||||
if let Some(alt) = alternate {
|
||||
f(&alt.inner);
|
||||
|
|
@ -1676,110 +1860,113 @@ pub unsafe extern "C" fn rust_compile_function(
|
|||
builtin_abstract_operations_enabled: bool,
|
||||
) -> *mut c_void {
|
||||
abort_on_panic(|| {
|
||||
if rust_function_ast.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload);
|
||||
let function_data = Box::new(payload.data);
|
||||
if rust_function_ast.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload);
|
||||
let function_data = Box::new(payload.data);
|
||||
|
||||
let body_scope = match &function_data.body.inner {
|
||||
StatementKind::FunctionBody { ref scope, .. } => Some(scope),
|
||||
StatementKind::Block(ref scope) => Some(scope),
|
||||
_ => None,
|
||||
};
|
||||
let body_scope = match &function_data.body.inner {
|
||||
StatementKind::FunctionBody { ref scope, .. } => Some(scope),
|
||||
StatementKind::Block(ref scope) => Some(scope),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Compute SFD metadata before codegen so the generator can use
|
||||
// function_environment_needed to optimize `this` access.
|
||||
let sfd_metadata = compute_sfd_metadata(&function_data);
|
||||
// Compute SFD metadata before codegen so the generator can use
|
||||
// function_environment_needed to optimize `this` access.
|
||||
let sfd_metadata = compute_sfd_metadata(&function_data);
|
||||
|
||||
let mut gen = bytecode::generator::Generator::new();
|
||||
gen.strict = function_data.is_strict_mode;
|
||||
gen.function_environment_needed = sfd_metadata.function_environment_needed;
|
||||
gen.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
|
||||
gen.function_table = payload.function_table;
|
||||
gen.vm_ptr = vm_ptr;
|
||||
gen.source_code_ptr = source_code_ptr;
|
||||
gen.source_len = source_len;
|
||||
gen.enclosing_function_kind = function_data.kind;
|
||||
let mut gen = bytecode::generator::Generator::new();
|
||||
gen.strict = function_data.is_strict_mode;
|
||||
gen.function_environment_needed = sfd_metadata.function_environment_needed;
|
||||
gen.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
|
||||
gen.function_table = payload.function_table;
|
||||
gen.vm_ptr = vm_ptr;
|
||||
gen.source_code_ptr = source_code_ptr;
|
||||
gen.source_len = source_len;
|
||||
gen.enclosing_function_kind = function_data.kind;
|
||||
|
||||
if let Some(scope) = body_scope {
|
||||
gen.local_variables = convert_local_variables(&scope.borrow());
|
||||
}
|
||||
if let Some(scope) = body_scope {
|
||||
gen.local_variables = convert_local_variables(&scope.borrow());
|
||||
}
|
||||
|
||||
let entry_block = gen.make_block();
|
||||
gen.switch_to_basic_block(entry_block);
|
||||
let entry_block = gen.make_block();
|
||||
gen.switch_to_basic_block(entry_block);
|
||||
|
||||
// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
|
||||
// For async (non-generator) functions, emit the initial Yield BEFORE
|
||||
// GetLexicalEnvironment so that parameter evaluation errors are caught
|
||||
// by the async promise wrapper. This matches C++ ordering.
|
||||
if gen.is_in_async_function() && !gen.is_in_generator_function() {
|
||||
let start_block = gen.make_block();
|
||||
let undef = gen.add_constant_undefined();
|
||||
gen.emit(bytecode::instruction::Instruction::Yield {
|
||||
continuation_label: Some(start_block),
|
||||
value: undef.operand(),
|
||||
});
|
||||
gen.switch_to_basic_block(start_block);
|
||||
}
|
||||
|
||||
{
|
||||
use bytecode::operand::{Operand, Register};
|
||||
let env_reg = gen.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT));
|
||||
gen.emit(bytecode::instruction::Instruction::GetLexicalEnvironment {
|
||||
dst: env_reg.operand(),
|
||||
});
|
||||
gen.lexical_environment_register_stack.push(env_reg);
|
||||
}
|
||||
|
||||
if let Some(scope) = body_scope {
|
||||
bytecode::codegen::emit_function_declaration_instantiation(
|
||||
&mut gen, &function_data, &scope.borrow(),
|
||||
);
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-generatorstart
|
||||
// For generator functions (including async generators), emit the initial Yield
|
||||
// AFTER FDI. Parameter evaluation happens synchronously before the generator starts.
|
||||
if gen.is_in_generator_function() {
|
||||
let start_block = gen.make_block();
|
||||
let undef = gen.add_constant_undefined();
|
||||
gen.emit(bytecode::instruction::Instruction::Yield {
|
||||
continuation_label: Some(start_block),
|
||||
value: undef.operand(),
|
||||
});
|
||||
gen.switch_to_basic_block(start_block);
|
||||
}
|
||||
|
||||
let result = bytecode::codegen::generate_statement(&function_data.body, &mut gen, None);
|
||||
|
||||
if !gen.is_current_block_terminated() {
|
||||
if gen.is_in_generator_or_async_function() {
|
||||
// Generator/async functions end with Yield (no continuation = done).
|
||||
// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
|
||||
// For async (non-generator) functions, emit the initial Yield BEFORE
|
||||
// GetLexicalEnvironment so that parameter evaluation errors are caught
|
||||
// by the async promise wrapper. This matches C++ ordering.
|
||||
if gen.is_in_async_function() && !gen.is_in_generator_function() {
|
||||
let start_block = gen.make_block();
|
||||
let undef = gen.add_constant_undefined();
|
||||
gen.emit(bytecode::instruction::Instruction::Yield {
|
||||
continuation_label: None,
|
||||
continuation_label: Some(start_block),
|
||||
value: undef.operand(),
|
||||
});
|
||||
} else if let Some(value) = result {
|
||||
gen.emit(bytecode::instruction::Instruction::End {
|
||||
value: value.operand(),
|
||||
});
|
||||
gen.switch_to_basic_block(start_block);
|
||||
}
|
||||
// If result is None, the assembler will add End(undefined) as a
|
||||
// fallthrough for unterminated blocks, matching C++ compile().
|
||||
}
|
||||
|
||||
// For generator/async functions, terminate all unterminated blocks with Yield.
|
||||
if gen.is_in_generator_or_async_function() {
|
||||
gen.terminate_unterminated_blocks_with_yield();
|
||||
}
|
||||
{
|
||||
use bytecode::operand::{Operand, Register};
|
||||
let env_reg =
|
||||
gen.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT));
|
||||
gen.emit(bytecode::instruction::Instruction::GetLexicalEnvironment {
|
||||
dst: env_reg.operand(),
|
||||
});
|
||||
gen.lexical_environment_register_stack.push(env_reg);
|
||||
}
|
||||
|
||||
let assembled = gen.assemble();
|
||||
if let Some(scope) = body_scope {
|
||||
bytecode::codegen::emit_function_declaration_instantiation(
|
||||
&mut gen,
|
||||
&function_data,
|
||||
&scope.borrow(),
|
||||
);
|
||||
}
|
||||
|
||||
write_sfd_metadata(sfd_ptr, &sfd_metadata);
|
||||
// https://tc39.es/ecma262/#sec-generatorstart
|
||||
// For generator functions (including async generators), emit the initial Yield
|
||||
// AFTER FDI. Parameter evaluation happens synchronously before the generator starts.
|
||||
if gen.is_in_generator_function() {
|
||||
let start_block = gen.make_block();
|
||||
let undef = gen.add_constant_undefined();
|
||||
gen.emit(bytecode::instruction::Instruction::Yield {
|
||||
continuation_label: Some(start_block),
|
||||
value: undef.operand(),
|
||||
});
|
||||
gen.switch_to_basic_block(start_block);
|
||||
}
|
||||
|
||||
bytecode::ffi::create_executable(&gen, &assembled, vm_ptr, source_code_ptr)
|
||||
let result = bytecode::codegen::generate_statement(&function_data.body, &mut gen, None);
|
||||
|
||||
if !gen.is_current_block_terminated() {
|
||||
if gen.is_in_generator_or_async_function() {
|
||||
// Generator/async functions end with Yield (no continuation = done).
|
||||
let undef = gen.add_constant_undefined();
|
||||
gen.emit(bytecode::instruction::Instruction::Yield {
|
||||
continuation_label: None,
|
||||
value: undef.operand(),
|
||||
});
|
||||
} else if let Some(value) = result {
|
||||
gen.emit(bytecode::instruction::Instruction::End {
|
||||
value: value.operand(),
|
||||
});
|
||||
}
|
||||
// If result is None, the assembler will add End(undefined) as a
|
||||
// fallthrough for unterminated blocks, matching C++ compile().
|
||||
}
|
||||
|
||||
// For generator/async functions, terminate all unterminated blocks with Yield.
|
||||
if gen.is_in_generator_or_async_function() {
|
||||
gen.terminate_unterminated_blocks_with_yield();
|
||||
}
|
||||
|
||||
let assembled = gen.assemble();
|
||||
|
||||
write_sfd_metadata(sfd_ptr, &sfd_metadata);
|
||||
|
||||
bytecode::ffi::create_executable(&gen, &assembled, vm_ptr, source_code_ptr)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1833,12 +2020,17 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
|
|||
|| function_data.parsing_insights.uses_this_from_environment,
|
||||
might_need_arguments: function_data.parsing_insights.might_need_arguments_object,
|
||||
has_function_named_arguments: fsd.is_some_and(|f| f.has_function_named_arguments),
|
||||
has_lexically_declared_arguments: fsd.is_some_and(|f| f.has_lexically_declared_arguments),
|
||||
has_lexically_declared_arguments: fsd
|
||||
.is_some_and(|f| f.has_lexically_declared_arguments),
|
||||
non_local_var_count: fsd.map_or(0, |f| f.non_local_var_count),
|
||||
non_local_var_count_for_parameter_expressions: fsd.map_or(0, |f| f.non_local_var_count_for_parameter_expressions),
|
||||
non_local_var_count_for_parameter_expressions: fsd
|
||||
.map_or(0, |f| f.non_local_var_count_for_parameter_expressions),
|
||||
var_names: fsd.map(|f| &f.var_names).cloned().unwrap_or_default(),
|
||||
annexb_function_names: sd.annexb_function_names.clone(),
|
||||
has_arguments_object_local: sd.local_variables.iter().any(|lv| lv.kind == ast::LocalVarKind::ArgumentsObject),
|
||||
has_arguments_object_local: sd
|
||||
.local_variables
|
||||
.iter()
|
||||
.any(|lv| lv.kind == ast::LocalVarKind::ArgumentsObject),
|
||||
}
|
||||
} else {
|
||||
BodyScopeInfo {
|
||||
|
|
@ -1868,17 +2060,15 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
|
|||
for parameter in &function_data.parameters {
|
||||
match ¶meter.binding {
|
||||
ast::FunctionParameterBinding::Identifier(ident) => {
|
||||
if parameter_names.insert(ident.name.clone())
|
||||
&& !ident.is_local() {
|
||||
parameters_in_environment += 1;
|
||||
}
|
||||
if parameter_names.insert(ident.name.clone()) && !ident.is_local() {
|
||||
parameters_in_environment += 1;
|
||||
}
|
||||
}
|
||||
ast::FunctionParameterBinding::BindingPattern(pattern) => {
|
||||
for_each_binding_pattern_identifier(pattern, &mut |ident| {
|
||||
if parameter_names.insert(ident.name.clone())
|
||||
&& !ident.is_local() {
|
||||
parameters_in_environment += 1;
|
||||
}
|
||||
if parameter_names.insert(ident.name.clone()) && !ident.is_local() {
|
||||
parameters_in_environment += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1893,8 +2083,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
|
|||
&& (has_parameter_expressions || !bsi.has_lexically_declared_arguments);
|
||||
|
||||
// Arguments object needs an environment binding if it's not a local variable.
|
||||
let arguments_object_needs_binding =
|
||||
arguments_object_needed && !bsi.has_arguments_object_local;
|
||||
let arguments_object_needs_binding = arguments_object_needed && !bsi.has_arguments_object_local;
|
||||
|
||||
let mut function_environment_bindings_count: usize = 0;
|
||||
let mut var_environment_bindings_count: usize = 0;
|
||||
|
|
@ -2039,10 +2228,7 @@ fn count_non_local_names_in_target(target: &ast::VariableDeclaratorTarget, count
|
|||
}
|
||||
}
|
||||
|
||||
fn count_non_local_names_in_binding_pattern(
|
||||
pattern: &ast::BindingPattern,
|
||||
count: &mut usize,
|
||||
) {
|
||||
fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count: &mut usize) {
|
||||
for entry in &pattern.entries {
|
||||
match &entry.alias {
|
||||
Some(ast::BindingEntryAlias::Identifier(ident)) => {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ use std::rc::Rc;
|
|||
|
||||
use crate::ast::{
|
||||
BindingPattern, Expression, ExpressionKind, FunctionParameter, FunctionTable, Identifier,
|
||||
PrivateIdentifier, SourceRange, Statement, StatementKind, ScopeData, ProgramData, Utf16String,
|
||||
PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind, Utf16String,
|
||||
};
|
||||
use crate::lexer::{ch, Lexer};
|
||||
use crate::scope_collector::{ScopeCollector, ScopeCollectorState};
|
||||
|
|
@ -48,11 +48,11 @@ mod declarations;
|
|||
mod expressions;
|
||||
mod statements;
|
||||
|
||||
pub use crate::ast::Position;
|
||||
pub use crate::ast::DeclarationKind;
|
||||
pub use crate::ast::FunctionKind;
|
||||
pub use crate::ast::ProgramType;
|
||||
pub use crate::ast::FunctionParsingInsights;
|
||||
pub use crate::ast::Position;
|
||||
pub use crate::ast::ProgramType;
|
||||
|
||||
// Named precedence levels for parse_expression().
|
||||
// These correspond to the operator precedence table in ECMA-262.
|
||||
|
|
@ -126,7 +126,10 @@ impl ForbiddenTokens {
|
|||
}
|
||||
|
||||
pub fn with_in() -> Self {
|
||||
Self { forbid_in: true, ..Self::default() }
|
||||
Self {
|
||||
forbid_in: true,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allows(&self, token: TokenType) -> bool {
|
||||
|
|
@ -147,7 +150,8 @@ impl ForbiddenTokens {
|
|||
forbid_logical: self.forbid_logical || other.forbid_logical,
|
||||
forbid_coalesce: self.forbid_coalesce || other.forbid_coalesce,
|
||||
forbid_paren_open: self.forbid_paren_open || other.forbid_paren_open,
|
||||
forbid_question_mark_period: self.forbid_question_mark_period || other.forbid_question_mark_period,
|
||||
forbid_question_mark_period: self.forbid_question_mark_period
|
||||
|| other.forbid_question_mark_period,
|
||||
forbid_equals: self.forbid_equals || other.forbid_equals,
|
||||
}
|
||||
}
|
||||
|
|
@ -287,7 +291,11 @@ impl<'a> Parser<'a> {
|
|||
Self::new_with_line_offset(source, program_type, 1)
|
||||
}
|
||||
|
||||
pub fn new_with_line_offset(source: &'a [u16], program_type: ProgramType, initial_line_number: u32) -> Self {
|
||||
pub fn new_with_line_offset(
|
||||
source: &'a [u16],
|
||||
program_type: ProgramType,
|
||||
initial_line_number: u32,
|
||||
) -> Self {
|
||||
let mut lexer = Lexer::new(source, initial_line_number, 0);
|
||||
if program_type == ProgramType::Module {
|
||||
lexer.disallow_html_comments();
|
||||
|
|
@ -327,7 +335,10 @@ impl<'a> Parser<'a> {
|
|||
// === AST construction helpers ===
|
||||
|
||||
pub(crate) fn range_from(&self, start: Position) -> SourceRange {
|
||||
SourceRange { start, end: self.position() }
|
||||
SourceRange {
|
||||
start,
|
||||
end: self.position(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expression(&self, start: Position, expression: ExpressionKind) -> Expression {
|
||||
|
|
@ -338,7 +349,11 @@ impl<'a> Parser<'a> {
|
|||
Statement::new(self.range_from(start), statement)
|
||||
}
|
||||
|
||||
pub(crate) fn make_identifier(&self, start: Position, name: impl Into<Utf16String>) -> Rc<Identifier> {
|
||||
pub(crate) fn make_identifier(
|
||||
&self,
|
||||
start: Position,
|
||||
name: impl Into<Utf16String>,
|
||||
) -> Rc<Identifier> {
|
||||
Rc::new(Identifier::new(self.range_from(start), name.into()))
|
||||
}
|
||||
|
||||
|
|
@ -365,7 +380,13 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
(id.name.clone(), parameter.is_rest, false)
|
||||
};
|
||||
entries.push(ParameterEntry { name, identifier: Some(id.clone()), is_rest, is_from_pattern, is_first_from_pattern: false });
|
||||
entries.push(ParameterEntry {
|
||||
name,
|
||||
identifier: Some(id.clone()),
|
||||
is_rest,
|
||||
is_from_pattern,
|
||||
is_first_from_pattern: false,
|
||||
});
|
||||
}
|
||||
FunctionParameterBinding::BindingPattern(pattern) => {
|
||||
if pattern.contains_expression() {
|
||||
|
|
@ -373,17 +394,32 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
// Push a placeholder entry for the pattern parameter itself
|
||||
// so subsequent parameters get correct positional indices.
|
||||
entries.push(ParameterEntry { name: Utf16String::default(), identifier: None, is_rest: false, is_from_pattern: true, is_first_from_pattern: true });
|
||||
entries.push(ParameterEntry {
|
||||
name: Utf16String::default(),
|
||||
identifier: None,
|
||||
is_rest: false,
|
||||
is_from_pattern: true,
|
||||
is_first_from_pattern: true,
|
||||
});
|
||||
// Then push bound names from this pattern.
|
||||
while info_index < parameter_info.len() && parameter_info[info_index].is_from_pattern {
|
||||
while info_index < parameter_info.len()
|
||||
&& parameter_info[info_index].is_from_pattern
|
||||
{
|
||||
let pi = ¶meter_info[info_index];
|
||||
entries.push(ParameterEntry { name: pi.name.clone(), identifier: pi.identifier.clone(), is_rest: pi.is_rest, is_from_pattern: true, is_first_from_pattern: false });
|
||||
entries.push(ParameterEntry {
|
||||
name: pi.name.clone(),
|
||||
identifier: pi.identifier.clone(),
|
||||
is_rest: pi.is_rest,
|
||||
is_from_pattern: true,
|
||||
is_first_from_pattern: false,
|
||||
});
|
||||
info_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.scope_collector.set_function_parameters(&entries, has_parameter_expressions);
|
||||
self.scope_collector
|
||||
.set_function_parameters(&entries, has_parameter_expressions);
|
||||
}
|
||||
|
||||
// === Token access ===
|
||||
|
|
@ -456,7 +492,11 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
let start = token.value_start as usize;
|
||||
let end = start + token.value_len as usize;
|
||||
if end <= self.source.len() { &self.source[start..end] } else { &[] }
|
||||
if end <= self.source.len() {
|
||||
&self.source[start..end]
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
};
|
||||
if value == utf16!("arguments") {
|
||||
if self.flags.in_class_field_initializer {
|
||||
|
|
@ -488,8 +528,10 @@ impl<'a> Parser<'a> {
|
|||
// https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
|
||||
// In strict mode, legacy octal literals (0-prefixed) are not permitted.
|
||||
let value = self.token_value(&token);
|
||||
if value.len() > 1 && value[0] == ch(b'0')
|
||||
&& value[1] >= ch(b'0') && value[1] <= ch(b'9')
|
||||
if value.len() > 1
|
||||
&& value[0] == ch(b'0')
|
||||
&& value[1] >= ch(b'0')
|
||||
&& value[1] <= ch(b'9')
|
||||
{
|
||||
self.syntax_error("Unprefixed octal number not allowed in strict mode");
|
||||
}
|
||||
|
|
@ -614,7 +656,11 @@ impl<'a> Parser<'a> {
|
|||
|
||||
/// Compile a regex pattern+flags and return the opaque compiled handle.
|
||||
/// On error, reports a syntax error and returns null.
|
||||
pub(crate) fn compile_regex_pattern(&mut self, pattern: &[u16], flags: &[u16]) -> *mut std::ffi::c_void {
|
||||
pub(crate) fn compile_regex_pattern(
|
||||
&mut self,
|
||||
pattern: &[u16],
|
||||
flags: &[u16],
|
||||
) -> *mut std::ffi::c_void {
|
||||
match crate::bytecode::ffi::compile_regex(pattern, flags) {
|
||||
Ok(handle) => handle,
|
||||
Err(msg) => {
|
||||
|
|
@ -625,15 +671,30 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub(crate) fn validate_regex_flags(&mut self, flags: &[u16]) {
|
||||
let valid_flags: &[u16] = &[ch(b'd'), ch(b'g'), ch(b'i'), ch(b'm'), ch(b's'), ch(b'u'), ch(b'v'), ch(b'y')];
|
||||
let valid_flags: &[u16] = &[
|
||||
ch(b'd'),
|
||||
ch(b'g'),
|
||||
ch(b'i'),
|
||||
ch(b'm'),
|
||||
ch(b's'),
|
||||
ch(b'u'),
|
||||
ch(b'v'),
|
||||
ch(b'y'),
|
||||
];
|
||||
let mut seen = [false; 128];
|
||||
for &flag in flags {
|
||||
if flag >= 128 || !valid_flags.contains(&flag) {
|
||||
self.syntax_error(&format!("Invalid RegExp flag '{}'", char::from_u32(flag as u32).unwrap_or('?')));
|
||||
self.syntax_error(&format!(
|
||||
"Invalid RegExp flag '{}'",
|
||||
char::from_u32(flag as u32).unwrap_or('?')
|
||||
));
|
||||
return;
|
||||
}
|
||||
if seen[flag as usize] {
|
||||
self.syntax_error(&format!("Repeated RegExp flag '{}'", char::from_u32(flag as u32).unwrap_or('?')));
|
||||
self.syntax_error(&format!(
|
||||
"Repeated RegExp flag '{}'",
|
||||
char::from_u32(flag as u32).unwrap_or('?')
|
||||
));
|
||||
return;
|
||||
}
|
||||
seen[flag as usize] = true;
|
||||
|
|
@ -692,14 +753,18 @@ impl<'a> Parser<'a> {
|
|||
tt == TokenType::Identifier
|
||||
|| (tt == TokenType::EscapedKeyword && !self.match_invalid_escaped_keyword())
|
||||
|| (tt == TokenType::Let && !self.flags.strict_mode)
|
||||
|| (tt == TokenType::Yield && !self.flags.strict_mode && !self.flags.in_generator_function_context)
|
||||
|| (tt == TokenType::Await && !self.flags.await_expression_is_valid && self.program_type != ProgramType::Module && !self.flags.in_class_static_init_block)
|
||||
|| (tt == TokenType::Yield
|
||||
&& !self.flags.strict_mode
|
||||
&& !self.flags.in_generator_function_context)
|
||||
|| (tt == TokenType::Await
|
||||
&& !self.flags.await_expression_is_valid
|
||||
&& self.program_type != ProgramType::Module
|
||||
&& !self.flags.in_class_static_init_block)
|
||||
|| tt == TokenType::Async
|
||||
}
|
||||
|
||||
pub(crate) fn match_identifier_name(&self) -> bool {
|
||||
self.current_token.token_type.is_identifier_name()
|
||||
|| self.match_identifier()
|
||||
self.current_token.token_type.is_identifier_name() || self.match_identifier()
|
||||
}
|
||||
|
||||
pub(crate) fn match_invalid_escaped_keyword(&self) -> bool {
|
||||
|
|
@ -708,7 +773,9 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
let value = self.token_value(&self.current_token);
|
||||
if value == utf16!("await") {
|
||||
return self.program_type == ProgramType::Module || self.flags.await_expression_is_valid || self.flags.in_class_static_init_block;
|
||||
return self.program_type == ProgramType::Module
|
||||
|| self.flags.await_expression_is_valid
|
||||
|| self.flags.in_class_static_init_block;
|
||||
}
|
||||
if value == utf16!("async") {
|
||||
return false;
|
||||
|
|
@ -725,13 +792,22 @@ impl<'a> Parser<'a> {
|
|||
value != utf16!("let") && value != utf16!("static")
|
||||
}
|
||||
|
||||
pub(crate) fn check_identifier_name_for_assignment_validity(&mut self, name: &[u16], force_strict: bool) {
|
||||
pub(crate) fn check_identifier_name_for_assignment_validity(
|
||||
&mut self,
|
||||
name: &[u16],
|
||||
force_strict: bool,
|
||||
) {
|
||||
if self.flags.strict_mode || force_strict {
|
||||
if name == utf16!("arguments") || name == utf16!("eval") {
|
||||
self.syntax_error("Binding pattern target may not be called 'arguments' or 'eval' in strict mode");
|
||||
self.syntax_error(
|
||||
"Binding pattern target may not be called 'arguments' or 'eval' in strict mode",
|
||||
);
|
||||
} else if is_strict_reserved_word(name) {
|
||||
let name_str = String::from_utf16_lossy(name);
|
||||
self.syntax_error(&format!("Identifier must not be a reserved word in strict mode ('{}')", name_str));
|
||||
self.syntax_error(&format!(
|
||||
"Identifier must not be a reserved word in strict mode ('{}')",
|
||||
name_str
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -838,10 +914,13 @@ impl<'a> Parser<'a> {
|
|||
Some(pattern)
|
||||
}
|
||||
|
||||
pub(crate) fn is_simple_assignment_target(expression: &Expression, allow_call_expression: bool) -> bool {
|
||||
matches!(&expression.inner,
|
||||
ExpressionKind::Identifier(_)
|
||||
| ExpressionKind::Member { .. }
|
||||
pub(crate) fn is_simple_assignment_target(
|
||||
expression: &Expression,
|
||||
allow_call_expression: bool,
|
||||
) -> bool {
|
||||
matches!(
|
||||
&expression.inner,
|
||||
ExpressionKind::Identifier(_) | ExpressionKind::Member { .. }
|
||||
) || (allow_call_expression && matches!(&expression.inner, ExpressionKind::Call(_)))
|
||||
}
|
||||
|
||||
|
|
@ -881,23 +960,29 @@ impl<'a> Parser<'a> {
|
|||
// Now close it after children are set.
|
||||
self.scope_collector.set_scope_node(scope.clone());
|
||||
self.scope_collector.close_scope();
|
||||
self.statement(start, StatementKind::Program(ProgramData {
|
||||
scope,
|
||||
program_type: ProgramType::Script,
|
||||
is_strict_mode: is_strict,
|
||||
has_top_level_await: false,
|
||||
}))
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Program(ProgramData {
|
||||
scope,
|
||||
program_type: ProgramType::Script,
|
||||
is_strict_mode: is_strict,
|
||||
has_top_level_await: false,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
let (children, has_top_level_await) = self.parse_module();
|
||||
let scope = ScopeData::shared_with_children(children);
|
||||
self.scope_collector.set_scope_node(scope.clone());
|
||||
self.scope_collector.close_scope();
|
||||
self.statement(start, StatementKind::Program(ProgramData {
|
||||
scope,
|
||||
program_type: ProgramType::Module,
|
||||
is_strict_mode: true,
|
||||
has_top_level_await,
|
||||
}))
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Program(ProgramData {
|
||||
scope,
|
||||
program_type: ProgramType::Module,
|
||||
is_strict_mode: true,
|
||||
has_top_level_await,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -984,7 +1069,10 @@ impl<'a> Parser<'a> {
|
|||
collect_binding_names(&decl.target, &mut declared_names);
|
||||
}
|
||||
}
|
||||
StatementKind::FunctionDeclaration { name: Some(ref name), .. } => {
|
||||
StatementKind::FunctionDeclaration {
|
||||
name: Some(ref name),
|
||||
..
|
||||
} => {
|
||||
declared_names.insert(name.name.clone());
|
||||
}
|
||||
StatementKind::ClassDeclaration(data) => {
|
||||
|
|
@ -1005,7 +1093,10 @@ impl<'a> Parser<'a> {
|
|||
collect_binding_names(&decl.target, &mut declared_names);
|
||||
}
|
||||
}
|
||||
StatementKind::FunctionDeclaration { name: Some(ref name), .. } => {
|
||||
StatementKind::FunctionDeclaration {
|
||||
name: Some(ref name),
|
||||
..
|
||||
} => {
|
||||
declared_names.insert(name.name.clone());
|
||||
}
|
||||
StatementKind::ClassDeclaration(class_data) => {
|
||||
|
|
@ -1066,7 +1157,9 @@ impl<'a> Parser<'a> {
|
|||
if is_use_strict(raw_value) {
|
||||
found_use_strict = true;
|
||||
if self.flags.string_legacy_octal_escape_sequence_in_scope {
|
||||
self.syntax_error("Octal escape sequence in string literal not allowed in strict mode");
|
||||
self.syntax_error(
|
||||
"Octal escape sequence in string literal not allowed in strict mode",
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -1075,7 +1168,10 @@ impl<'a> Parser<'a> {
|
|||
(found_use_strict, statements)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_statement_list(&mut self, allow_labelled_functions: bool) -> Vec<Statement> {
|
||||
pub(crate) fn parse_statement_list(
|
||||
&mut self,
|
||||
allow_labelled_functions: bool,
|
||||
) -> Vec<Statement> {
|
||||
let mut statements = Vec::new();
|
||||
while !self.done() {
|
||||
if self.match_export_or_import() {
|
||||
|
|
@ -1153,8 +1249,10 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
fn match_iteration_start(&self) -> bool {
|
||||
matches!(self.current_token_type(),
|
||||
TokenType::For | TokenType::While | TokenType::Do)
|
||||
matches!(
|
||||
self.current_token_type(),
|
||||
TokenType::For | TokenType::While | TokenType::Do
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn match_export_or_import(&mut self) -> bool {
|
||||
|
|
@ -1163,8 +1261,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
if self.match_token(TokenType::Import) {
|
||||
let next = self.next_token();
|
||||
return next.token_type != TokenType::ParenOpen
|
||||
&& next.token_type != TokenType::Period;
|
||||
return next.token_type != TokenType::ParenOpen && next.token_type != TokenType::Period;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -1173,16 +1270,32 @@ impl<'a> Parser<'a> {
|
|||
|
||||
pub(crate) fn operator_precedence(tt: TokenType) -> i32 {
|
||||
match tt {
|
||||
TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod => 20,
|
||||
TokenType::Period
|
||||
| TokenType::BracketOpen
|
||||
| TokenType::ParenOpen
|
||||
| TokenType::QuestionMarkPeriod => 20,
|
||||
TokenType::New => 19,
|
||||
TokenType::PlusPlus | TokenType::MinusMinus => 18,
|
||||
TokenType::ExclamationMark | TokenType::Tilde | TokenType::Typeof | TokenType::Void | TokenType::Delete | TokenType::Await => 17,
|
||||
TokenType::ExclamationMark
|
||||
| TokenType::Tilde
|
||||
| TokenType::Typeof
|
||||
| TokenType::Void
|
||||
| TokenType::Delete
|
||||
| TokenType::Await => 17,
|
||||
TokenType::DoubleAsterisk => 16,
|
||||
TokenType::Asterisk | TokenType::Slash | TokenType::Percent => 15,
|
||||
TokenType::Plus | TokenType::Minus => 14,
|
||||
TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight => 13,
|
||||
TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals | TokenType::In | TokenType::Instanceof => 12,
|
||||
TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals => 11,
|
||||
TokenType::LessThan
|
||||
| TokenType::LessThanEquals
|
||||
| TokenType::GreaterThan
|
||||
| TokenType::GreaterThanEquals
|
||||
| TokenType::In
|
||||
| TokenType::Instanceof => 12,
|
||||
TokenType::EqualsEquals
|
||||
| TokenType::ExclamationMarkEquals
|
||||
| TokenType::EqualsEqualsEquals
|
||||
| TokenType::ExclamationMarkEqualsEquals => 11,
|
||||
TokenType::Ampersand => 10,
|
||||
TokenType::Caret => 9,
|
||||
TokenType::Pipe => 8,
|
||||
|
|
@ -1190,11 +1303,21 @@ impl<'a> Parser<'a> {
|
|||
TokenType::DoubleAmpersand => 6,
|
||||
TokenType::DoublePipe => 5,
|
||||
TokenType::QuestionMark => 4,
|
||||
TokenType::Equals | TokenType::PlusEquals | TokenType::MinusEquals
|
||||
| TokenType::DoubleAsteriskEquals | TokenType::AsteriskEquals | TokenType::SlashEquals
|
||||
| TokenType::PercentEquals | TokenType::ShiftLeftEquals | TokenType::ShiftRightEquals
|
||||
| TokenType::UnsignedShiftRightEquals | TokenType::AmpersandEquals | TokenType::CaretEquals
|
||||
| TokenType::PipeEquals | TokenType::DoubleAmpersandEquals | TokenType::DoublePipeEquals
|
||||
TokenType::Equals
|
||||
| TokenType::PlusEquals
|
||||
| TokenType::MinusEquals
|
||||
| TokenType::DoubleAsteriskEquals
|
||||
| TokenType::AsteriskEquals
|
||||
| TokenType::SlashEquals
|
||||
| TokenType::PercentEquals
|
||||
| TokenType::ShiftLeftEquals
|
||||
| TokenType::ShiftRightEquals
|
||||
| TokenType::UnsignedShiftRightEquals
|
||||
| TokenType::AmpersandEquals
|
||||
| TokenType::CaretEquals
|
||||
| TokenType::PipeEquals
|
||||
| TokenType::DoubleAmpersandEquals
|
||||
| TokenType::DoublePipeEquals
|
||||
| TokenType::DoubleQuestionMarkEquals => 3,
|
||||
TokenType::Yield => 2,
|
||||
TokenType::Comma => 1,
|
||||
|
|
@ -1204,16 +1327,38 @@ impl<'a> Parser<'a> {
|
|||
|
||||
pub(crate) fn operator_associativity(tt: TokenType) -> Associativity {
|
||||
match tt {
|
||||
TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod
|
||||
| TokenType::Asterisk | TokenType::Slash | TokenType::Percent
|
||||
| TokenType::Plus | TokenType::Minus
|
||||
| TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight
|
||||
| TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals
|
||||
| TokenType::In | TokenType::Instanceof
|
||||
| TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals
|
||||
| TokenType::Typeof | TokenType::Void | TokenType::Delete | TokenType::Await
|
||||
| TokenType::Ampersand | TokenType::Caret | TokenType::Pipe
|
||||
| TokenType::DoubleQuestionMark | TokenType::DoubleAmpersand | TokenType::DoublePipe
|
||||
TokenType::Period
|
||||
| TokenType::BracketOpen
|
||||
| TokenType::ParenOpen
|
||||
| TokenType::QuestionMarkPeriod
|
||||
| TokenType::Asterisk
|
||||
| TokenType::Slash
|
||||
| TokenType::Percent
|
||||
| TokenType::Plus
|
||||
| TokenType::Minus
|
||||
| TokenType::ShiftLeft
|
||||
| TokenType::ShiftRight
|
||||
| TokenType::UnsignedShiftRight
|
||||
| TokenType::LessThan
|
||||
| TokenType::LessThanEquals
|
||||
| TokenType::GreaterThan
|
||||
| TokenType::GreaterThanEquals
|
||||
| TokenType::In
|
||||
| TokenType::Instanceof
|
||||
| TokenType::EqualsEquals
|
||||
| TokenType::ExclamationMarkEquals
|
||||
| TokenType::EqualsEqualsEquals
|
||||
| TokenType::ExclamationMarkEqualsEquals
|
||||
| TokenType::Typeof
|
||||
| TokenType::Void
|
||||
| TokenType::Delete
|
||||
| TokenType::Await
|
||||
| TokenType::Ampersand
|
||||
| TokenType::Caret
|
||||
| TokenType::Pipe
|
||||
| TokenType::DoubleQuestionMark
|
||||
| TokenType::DoubleAmpersand
|
||||
| TokenType::DoublePipe
|
||||
| TokenType::Comma => Associativity::Left,
|
||||
_ => Associativity::Right,
|
||||
}
|
||||
|
|
@ -1227,7 +1372,10 @@ fn is_use_strict(raw: &[u16]) -> bool {
|
|||
}
|
||||
|
||||
/// Collect all binding names introduced by a variable declarator target.
|
||||
fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &mut HashSet<Utf16String>) {
|
||||
fn collect_binding_names(
|
||||
target: &crate::ast::VariableDeclaratorTarget,
|
||||
names: &mut HashSet<Utf16String>,
|
||||
) {
|
||||
match target {
|
||||
crate::ast::VariableDeclaratorTarget::Identifier(identifier) => {
|
||||
names.insert(identifier.name.clone());
|
||||
|
|
@ -1239,7 +1387,10 @@ fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &
|
|||
}
|
||||
|
||||
/// Collect all binding names from a binding pattern (object or array destructuring).
|
||||
fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &mut HashSet<Utf16String>) {
|
||||
fn collect_binding_pattern_names(
|
||||
pattern: &crate::ast::BindingPattern,
|
||||
names: &mut HashSet<Utf16String>,
|
||||
) {
|
||||
for entry in &pattern.entries {
|
||||
if let Some(ref alias) = entry.alias {
|
||||
match alias {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -57,7 +57,9 @@ impl<'a> Parser<'a> {
|
|||
self.syntax_error("Keyword must not contain escaped characters");
|
||||
}
|
||||
if self.match_identifier_name() {
|
||||
if let Some(labelled) = self.try_parse_labelled_statement(allow_labelled_function) {
|
||||
if let Some(labelled) =
|
||||
self.try_parse_labelled_statement(allow_labelled_function)
|
||||
{
|
||||
return labelled;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,13 +102,21 @@ impl<'a> Parser<'a> {
|
|||
|
||||
if self.match_token(TokenType::Async) {
|
||||
let lookahead = self.next_token();
|
||||
if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator {
|
||||
self.syntax_error("Async function declaration not allowed in single-statement context");
|
||||
if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator
|
||||
{
|
||||
self.syntax_error(
|
||||
"Async function declaration not allowed in single-statement context",
|
||||
);
|
||||
}
|
||||
} else if self.match_token(TokenType::Function) || self.match_token(TokenType::Class) {
|
||||
let name = self.current_token.token_type.name();
|
||||
self.syntax_error(&format!("{} declaration not allowed in single-statement context", name));
|
||||
} else if self.match_token(TokenType::Let) && self.next_token().token_type == TokenType::BracketOpen {
|
||||
self.syntax_error(&format!(
|
||||
"{} declaration not allowed in single-statement context",
|
||||
name
|
||||
));
|
||||
} else if self.match_token(TokenType::Let)
|
||||
&& self.next_token().token_type == TokenType::BracketOpen
|
||||
{
|
||||
self.syntax_error("let followed by [ is not allowed in single-statement context");
|
||||
}
|
||||
|
||||
|
|
@ -191,10 +201,17 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
|
||||
if label.is_none() && !self.flags.in_break_context {
|
||||
self.syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
|
||||
self.syntax_error(
|
||||
"Unlabeled 'break' not allowed outside of a loop or switch statement",
|
||||
);
|
||||
}
|
||||
|
||||
self.statement(start, StatementKind::Break { target_label: label })
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Break {
|
||||
target_label: label,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-continue-statement
|
||||
|
|
@ -232,7 +249,12 @@ impl<'a> Parser<'a> {
|
|||
|
||||
self.consume_or_insert_semicolon();
|
||||
|
||||
self.statement(start, StatementKind::Continue { target_label: label })
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Continue {
|
||||
target_label: label,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_debugger_statement(&mut self) -> Statement {
|
||||
|
|
@ -258,7 +280,9 @@ impl<'a> Parser<'a> {
|
|||
let alternate = if self.match_token(TokenType::Else) {
|
||||
self.consume();
|
||||
if !self.flags.strict_mode && self.match_token(TokenType::Function) {
|
||||
Some(Box::new(self.parse_function_declaration_as_block_statement(start)))
|
||||
Some(Box::new(
|
||||
self.parse_function_declaration_as_block_statement(start),
|
||||
))
|
||||
} else {
|
||||
Some(Box::new(self.parse_statement(false)))
|
||||
}
|
||||
|
|
@ -266,11 +290,14 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
self.statement(start, StatementKind::If {
|
||||
test: Box::new(predicate),
|
||||
consequent: Box::new(consequent),
|
||||
alternate,
|
||||
})
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::If {
|
||||
test: Box::new(predicate),
|
||||
consequent: Box::new(consequent),
|
||||
alternate,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Annex B: Parse a function declaration as if wrapped in a synthetic block.
|
||||
|
|
@ -307,10 +334,13 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let body = self.parse_loop_body();
|
||||
|
||||
self.statement(start, StatementKind::While {
|
||||
test: Box::new(test),
|
||||
body: Box::new(body),
|
||||
})
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::While {
|
||||
test: Box::new(test),
|
||||
body: Box::new(body),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_do_while_statement(&mut self) -> Statement {
|
||||
|
|
@ -328,10 +358,13 @@ impl<'a> Parser<'a> {
|
|||
// the regular ASI rules not applying.
|
||||
self.eat(TokenType::Semicolon);
|
||||
|
||||
self.statement(start, StatementKind::DoWhile {
|
||||
test: Box::new(test),
|
||||
body: Box::new(body),
|
||||
})
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::DoWhile {
|
||||
test: Box::new(test),
|
||||
body: Box::new(body),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-for-statement
|
||||
|
|
@ -365,15 +398,21 @@ impl<'a> Parser<'a> {
|
|||
let init_start = self.position();
|
||||
let is_var_init = self.match_token(TokenType::Var);
|
||||
let is_using = self.match_for_using_declaration();
|
||||
let is_let = self.match_token(TokenType::Let) && (self.flags.strict_mode || self.try_match_let_declaration());
|
||||
let is_declaration = is_var_init || is_using || is_let || self.match_token(TokenType::Const);
|
||||
let is_let = self.match_token(TokenType::Let)
|
||||
&& (self.flags.strict_mode || self.try_match_let_declaration());
|
||||
let is_declaration =
|
||||
is_var_init || is_using || is_let || self.match_token(TokenType::Const);
|
||||
let init = if is_using {
|
||||
LocalForInit::Declaration(self.parse_using_declaration(true))
|
||||
} else if is_declaration {
|
||||
LocalForInit::Declaration(self.parse_variable_declaration(true))
|
||||
} else {
|
||||
let forbidden = ForbiddenTokens::with_in();
|
||||
LocalForInit::Expression(self.parse_expression(PRECEDENCE_COMMA, Associativity::Right, forbidden))
|
||||
LocalForInit::Expression(self.parse_expression(
|
||||
PRECEDENCE_COMMA,
|
||||
Associativity::Right,
|
||||
forbidden,
|
||||
))
|
||||
};
|
||||
|
||||
// Check for in
|
||||
|
|
@ -393,7 +432,10 @@ impl<'a> Parser<'a> {
|
|||
if self.for_loop_declaration_has_init {
|
||||
// https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
|
||||
// Annex B: In sloppy mode, a single `var` with an initializer is permitted.
|
||||
if !(self.for_loop_declaration_is_var && self.for_loop_declaration_count == 1 && !self.flags.strict_mode) {
|
||||
if !(self.for_loop_declaration_is_var
|
||||
&& self.for_loop_declaration_count == 1
|
||||
&& !self.flags.strict_mode)
|
||||
{
|
||||
self.syntax_error("Variable initializer not allowed in for..in/of");
|
||||
}
|
||||
}
|
||||
|
|
@ -407,12 +449,15 @@ impl<'a> Parser<'a> {
|
|||
let body = self.parse_loop_body();
|
||||
|
||||
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
|
||||
let result = self.statement(forin_start, StatementKind::ForInOf {
|
||||
kind: ForInOfKind::ForIn,
|
||||
lhs,
|
||||
rhs: Box::new(rhs),
|
||||
body: Box::new(body),
|
||||
});
|
||||
let result = self.statement(
|
||||
forin_start,
|
||||
StatementKind::ForInOf {
|
||||
kind: ForInOfKind::ForIn,
|
||||
lhs,
|
||||
rhs: Box::new(rhs),
|
||||
body: Box::new(body),
|
||||
},
|
||||
);
|
||||
return self.close_for_loop_scope(start, result);
|
||||
}
|
||||
|
||||
|
|
@ -449,20 +494,31 @@ impl<'a> Parser<'a> {
|
|||
let body = self.parse_loop_body();
|
||||
|
||||
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
|
||||
let for_of_kind = if is_await { ForInOfKind::ForAwaitOf } else { ForInOfKind::ForOf };
|
||||
let result = self.statement(forof_start, StatementKind::ForInOf {
|
||||
kind: for_of_kind,
|
||||
lhs,
|
||||
rhs: Box::new(rhs),
|
||||
body: Box::new(body),
|
||||
});
|
||||
let for_of_kind = if is_await {
|
||||
ForInOfKind::ForAwaitOf
|
||||
} else {
|
||||
ForInOfKind::ForOf
|
||||
};
|
||||
let result = self.statement(
|
||||
forof_start,
|
||||
StatementKind::ForInOf {
|
||||
kind: for_of_kind,
|
||||
lhs,
|
||||
rhs: Box::new(rhs),
|
||||
body: Box::new(body),
|
||||
},
|
||||
);
|
||||
return self.close_for_loop_scope(start, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Standard for loop — const requires initializer.
|
||||
if let LocalForInit::Declaration(ref declaration) = init {
|
||||
if let StatementKind::VariableDeclaration { kind: DeclarationKind::Const, ref declarations } = declaration.inner {
|
||||
if let StatementKind::VariableDeclaration {
|
||||
kind: DeclarationKind::Const,
|
||||
ref declarations,
|
||||
} = declaration.inner
|
||||
{
|
||||
for d in declarations {
|
||||
if d.init.is_none() {
|
||||
self.syntax_error("Missing initializer in const declaration");
|
||||
|
|
@ -472,7 +528,9 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
self.consume_token(TokenType::Semicolon);
|
||||
let for_init = match init {
|
||||
LocalForInit::Declaration(declaration) => Some(ForInit::Declaration(Box::new(declaration))),
|
||||
LocalForInit::Declaration(declaration) => {
|
||||
Some(ForInit::Declaration(Box::new(declaration)))
|
||||
}
|
||||
LocalForInit::Expression(expression) => Some(ForInit::Expression(Box::new(expression))),
|
||||
};
|
||||
let result = self.parse_standard_for_loop(start, for_init);
|
||||
|
|
@ -505,12 +563,15 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let body = self.parse_loop_body();
|
||||
|
||||
self.statement(start, StatementKind::For {
|
||||
init,
|
||||
test,
|
||||
update,
|
||||
body: Box::new(body),
|
||||
})
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::For {
|
||||
init,
|
||||
test,
|
||||
update,
|
||||
body: Box::new(body),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-with-statement
|
||||
|
|
@ -524,10 +585,13 @@ impl<'a> Parser<'a> {
|
|||
self.scope_collector.open_with_scope(None);
|
||||
let body = self.parse_statement(false);
|
||||
self.scope_collector.close_scope();
|
||||
self.statement(start, StatementKind::With {
|
||||
object: Box::new(object),
|
||||
body: Box::new(body),
|
||||
})
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::With {
|
||||
object: Box::new(object),
|
||||
body: Box::new(body),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-switch-statement
|
||||
|
|
@ -567,11 +631,14 @@ impl<'a> Parser<'a> {
|
|||
self.scope_collector.set_scope_node(scope.clone());
|
||||
self.scope_collector.close_scope();
|
||||
|
||||
self.statement(start, StatementKind::Switch(SwitchStatementData {
|
||||
scope,
|
||||
discriminant: Box::new(discriminant),
|
||||
cases,
|
||||
}))
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Switch(SwitchStatementData {
|
||||
scope,
|
||||
discriminant: Box::new(discriminant),
|
||||
cases,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_switch_case(&mut self) -> SwitchCase {
|
||||
|
|
@ -637,11 +704,14 @@ impl<'a> Parser<'a> {
|
|||
self.syntax_error("try statement must have a catch or finally clause");
|
||||
}
|
||||
|
||||
self.statement(start, StatementKind::Try(TryStatementData {
|
||||
block: Box::new(block),
|
||||
handler,
|
||||
finalizer,
|
||||
}))
|
||||
self.statement(
|
||||
start,
|
||||
StatementKind::Try(TryStatementData {
|
||||
block: Box::new(block),
|
||||
handler,
|
||||
finalizer,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-try-statement
|
||||
|
|
@ -656,19 +726,31 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let parameter = if self.match_token(TokenType::ParenOpen) {
|
||||
self.consume();
|
||||
let parameter = if self.match_token(TokenType::CurlyOpen) || self.match_token(TokenType::BracketOpen) {
|
||||
let parameter = if self.match_token(TokenType::CurlyOpen)
|
||||
|| self.match_token(TokenType::BracketOpen)
|
||||
{
|
||||
self.pattern_bound_names.clear();
|
||||
let pattern = self.parse_binding_pattern();
|
||||
let names_to_check: Vec<Utf16String> = self.pattern_bound_names.iter().map(|(n, _)| n.clone()).collect();
|
||||
let names_to_check: Vec<Utf16String> = self
|
||||
.pattern_bound_names
|
||||
.iter()
|
||||
.map(|(n, _)| n.clone())
|
||||
.collect();
|
||||
for name in &names_to_check {
|
||||
self.check_identifier_name_for_assignment_validity(name, false);
|
||||
}
|
||||
let bound_names: Vec<&[u16]> = self.pattern_bound_names.iter().map(|(n, _)| n.as_slice()).collect();
|
||||
self.scope_collector.add_catch_parameter_pattern(&bound_names);
|
||||
let bound_names: Vec<&[u16]> = self
|
||||
.pattern_bound_names
|
||||
.iter()
|
||||
.map(|(n, _)| n.as_slice())
|
||||
.collect();
|
||||
self.scope_collector
|
||||
.add_catch_parameter_pattern(&bound_names);
|
||||
// Register each binding pattern identifier for scope analysis
|
||||
// so they get is_local() annotations (matching variable declarations).
|
||||
for (name, id) in &self.pattern_bound_names {
|
||||
self.scope_collector.register_identifier(id.clone(), name, None);
|
||||
self.scope_collector
|
||||
.register_identifier(id.clone(), name, None);
|
||||
}
|
||||
Some(CatchBinding::BindingPattern(pattern))
|
||||
} else if self.match_identifier() {
|
||||
|
|
@ -676,9 +758,14 @@ impl<'a> Parser<'a> {
|
|||
let token = self.consume();
|
||||
let value = self.token_value(&token).to_vec();
|
||||
self.check_identifier_name_for_assignment_validity(&value, false);
|
||||
let id = Rc::new(Identifier::new(self.range_from(parameter_start), value.clone().into()));
|
||||
self.scope_collector.register_identifier(id.clone(), &value, None);
|
||||
self.scope_collector.add_catch_parameter_identifier(&value, id.clone());
|
||||
let id = Rc::new(Identifier::new(
|
||||
self.range_from(parameter_start),
|
||||
value.clone().into(),
|
||||
));
|
||||
self.scope_collector
|
||||
.register_identifier(id.clone(), &value, None);
|
||||
self.scope_collector
|
||||
.add_catch_parameter_identifier(&value, id.clone());
|
||||
Some(CatchBinding::Identifier(id))
|
||||
} else {
|
||||
self.expected("catch parameter");
|
||||
|
|
@ -723,7 +810,9 @@ impl<'a> Parser<'a> {
|
|||
self.discard_saved_state();
|
||||
self.consume(); // consume :
|
||||
|
||||
if self.flags.strict_mode && (label == utf16!("let") || crate::parser::is_strict_reserved_word(&label)) {
|
||||
if self.flags.strict_mode
|
||||
&& (label == utf16!("let") || crate::parser::is_strict_reserved_word(&label))
|
||||
{
|
||||
self.syntax_error("Strict mode reserved word is not allowed in label");
|
||||
}
|
||||
if self.flags.in_generator_function_context && label == utf16!("yield") {
|
||||
|
|
@ -738,7 +827,9 @@ impl<'a> Parser<'a> {
|
|||
self.syntax_error(&format!("Label '{}' has already been declared", label_str));
|
||||
}
|
||||
|
||||
if self.match_token(TokenType::Function) && (!allow_labelled_function || self.flags.strict_mode) {
|
||||
if self.match_token(TokenType::Function)
|
||||
&& (!allow_labelled_function || self.flags.strict_mode)
|
||||
{
|
||||
self.syntax_error("Not allowed to declare a function here");
|
||||
}
|
||||
if self.match_token(TokenType::Async) {
|
||||
|
|
@ -760,10 +851,14 @@ impl<'a> Parser<'a> {
|
|||
if let StatementKind::FunctionDeclaration { kind, .. } = fn_decl.inner {
|
||||
match kind {
|
||||
FunctionKind::Generator | FunctionKind::AsyncGenerator => {
|
||||
self.syntax_error("Generator functions cannot be defined in labelled statements");
|
||||
self.syntax_error(
|
||||
"Generator functions cannot be defined in labelled statements",
|
||||
);
|
||||
}
|
||||
FunctionKind::Async => {
|
||||
self.syntax_error("Async functions cannot be defined in labelled statements");
|
||||
self.syntax_error(
|
||||
"Async functions cannot be defined in labelled statements",
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -778,7 +873,9 @@ impl<'a> Parser<'a> {
|
|||
if let Some(Some((line, col))) = self.labels_in_scope.get(label.as_slice()) {
|
||||
self.syntax_error_at(
|
||||
"labelled continue statement cannot use non iterating statement",
|
||||
*line, *col);
|
||||
*line,
|
||||
*col,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -786,10 +883,13 @@ impl<'a> Parser<'a> {
|
|||
self.flags.in_break_context = break_before;
|
||||
self.last_inner_label_is_iteration = is_iteration;
|
||||
|
||||
Some(self.statement(start, StatementKind::Labelled {
|
||||
label,
|
||||
item: Box::new(body),
|
||||
}))
|
||||
Some(self.statement(
|
||||
start,
|
||||
StatementKind::Labelled {
|
||||
label,
|
||||
item: Box::new(body),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn match_for_using_declaration(&mut self) -> bool {
|
||||
|
|
@ -817,9 +917,12 @@ impl<'a> Parser<'a> {
|
|||
/// Validate that an expression-form LHS is valid for for-in/for-of.
|
||||
fn validate_for_in_of_lhs(&mut self, init: &LocalForInit) {
|
||||
if let LocalForInit::Expression(ref expression) = *init {
|
||||
if !Self::is_identifier(expression) && !Self::is_member_expression(expression)
|
||||
if !Self::is_identifier(expression)
|
||||
&& !Self::is_member_expression(expression)
|
||||
&& !Self::is_call_expression(expression)
|
||||
&& !Self::is_object_expression(expression) && !Self::is_array_expression(expression) {
|
||||
&& !Self::is_object_expression(expression)
|
||||
&& !Self::is_array_expression(expression)
|
||||
{
|
||||
self.syntax_error("Invalid left-hand side in for-loop");
|
||||
}
|
||||
}
|
||||
|
|
@ -829,9 +932,12 @@ impl<'a> Parser<'a> {
|
|||
/// pattern when the LHS is an array or object expression.
|
||||
fn synthesize_for_in_of_lhs(&mut self, init: LocalForInit, init_start: Position) -> ForInOfLhs {
|
||||
match init {
|
||||
LocalForInit::Declaration(declaration) => ForInOfLhs::Declaration(Box::new(declaration)),
|
||||
LocalForInit::Declaration(declaration) => {
|
||||
ForInOfLhs::Declaration(Box::new(declaration))
|
||||
}
|
||||
LocalForInit::Expression(expression) => {
|
||||
if Self::is_array_expression(&expression) || Self::is_object_expression(&expression) {
|
||||
if Self::is_array_expression(&expression) || Self::is_object_expression(&expression)
|
||||
{
|
||||
if let Some(pattern) = self.synthesize_binding_pattern(init_start) {
|
||||
for (name, id) in self.pattern_bound_names.drain(..) {
|
||||
self.scope_collector.register_identifier(id, &name, None);
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ use std::collections::{HashMap, HashSet};
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::ast::{
|
||||
FunctionScopeData, Identifier, LocalBinding, LocalVarKind,
|
||||
LocalVariable, ScopeData, Utf16String, VarToInit,
|
||||
FunctionScopeData, Identifier, LocalBinding, LocalVarKind, LocalVariable, ScopeData,
|
||||
Utf16String, VarToInit,
|
||||
};
|
||||
use crate::parser::{DeclarationKind, FunctionKind, ProgramType};
|
||||
use crate::u32_from_usize;
|
||||
|
|
@ -216,7 +216,11 @@ struct ScopeRecord {
|
|||
}
|
||||
|
||||
impl ScopeRecord {
|
||||
fn new(scope_type: ScopeType, scope_level: ScopeLevel, scope_data: Option<Rc<RefCell<ScopeData>>>) -> Self {
|
||||
fn new(
|
||||
scope_type: ScopeType,
|
||||
scope_level: ScopeLevel,
|
||||
scope_data: Option<Rc<RefCell<ScopeData>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
scope_type,
|
||||
scope_level,
|
||||
|
|
@ -251,7 +255,9 @@ impl ScopeRecord {
|
|||
}
|
||||
|
||||
fn has_flag(&self, name: &[u16], flags: VarFlags) -> bool {
|
||||
self.variables.get(name).is_some_and(|v| v.flags.intersects(flags))
|
||||
self.variables
|
||||
.get(name)
|
||||
.is_some_and(|v| v.flags.intersects(flags))
|
||||
}
|
||||
|
||||
fn get_parameter_index(&self, name: &[u16]) -> Option<u32> {
|
||||
|
|
@ -266,7 +272,9 @@ impl ScopeRecord {
|
|||
}
|
||||
|
||||
fn has_rest_parameter_with_name(&self, name: &[u16]) -> bool {
|
||||
self.parameter_names.iter().any(|param| param.is_rest && param.name == name)
|
||||
self.parameter_names
|
||||
.iter()
|
||||
.any(|param| param.is_rest && param.name == name)
|
||||
}
|
||||
|
||||
fn has_hoistable_function_named(&self, name: &[u16]) -> bool {
|
||||
|
|
@ -281,7 +289,8 @@ fn ancestor_scopes(start: usize, records: &[ScopeRecord]) -> impl Iterator<Item
|
|||
|
||||
fn last_function_scope(index: usize, records: &[ScopeRecord]) -> Option<usize> {
|
||||
ancestor_scopes(index, records).find(|&i| {
|
||||
records[i].scope_type == ScopeType::Function || records[i].scope_type == ScopeType::ClassStaticInit
|
||||
records[i].scope_type == ScopeType::Function
|
||||
|| records[i].scope_type == ScopeType::ClassStaticInit
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -339,7 +348,10 @@ impl ScopeCollector {
|
|||
|
||||
fn already_declared_error(&mut self, name: &[u16], line: u32, column: u32) {
|
||||
self.errors.push(ScopeError {
|
||||
message: format!("Identifier '{}' already declared", String::from_utf16_lossy(name)),
|
||||
message: format!(
|
||||
"Identifier '{}' already declared",
|
||||
String::from_utf16_lossy(name)
|
||||
),
|
||||
line,
|
||||
column,
|
||||
});
|
||||
|
|
@ -383,7 +395,9 @@ impl ScopeCollector {
|
|||
self.errors.truncate(state.errors_len);
|
||||
// Remove any child indices that pointed to now-truncated records.
|
||||
if let Some(current_index) = self.current {
|
||||
self.records[current_index].children.retain(|&c| c < saved_len);
|
||||
self.records[current_index]
|
||||
.children
|
||||
.retain(|&c| c < saved_len);
|
||||
}
|
||||
// Restore flags on ancestor function scopes that may have been
|
||||
// modified by set_uses_this() or set_uses_new_target() during
|
||||
|
|
@ -391,14 +405,20 @@ impl ScopeCollector {
|
|||
for saved in &state.saved_flags {
|
||||
if saved.index < self.records.len() {
|
||||
self.records[saved.index].uses_this = saved.uses_this;
|
||||
self.records[saved.index].uses_this_from_environment = saved.uses_this_from_environment;
|
||||
self.records[saved.index].uses_this_from_environment =
|
||||
saved.uses_this_from_environment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Open/close scopes ===
|
||||
|
||||
fn open_scope(&mut self, scope_type: ScopeType, scope_data: Option<Rc<RefCell<ScopeData>>>, scope_level: ScopeLevel) {
|
||||
fn open_scope(
|
||||
&mut self,
|
||||
scope_type: ScopeType,
|
||||
scope_data: Option<Rc<RefCell<ScopeData>>>,
|
||||
scope_level: ScopeLevel,
|
||||
) {
|
||||
let index = self.records.len();
|
||||
let mut record = ScopeRecord::new(scope_type, scope_level, scope_data);
|
||||
record.parent = self.current;
|
||||
|
|
@ -433,7 +453,8 @@ impl ScopeCollector {
|
|||
let arguments = c.contains_access_to_arguments_object_in_non_strict_mode;
|
||||
let eval = c.contains_direct_call_to_eval;
|
||||
let contains_await = c.contains_await_expression;
|
||||
self.records[parent_index].contains_access_to_arguments_object_in_non_strict_mode |= arguments;
|
||||
self.records[parent_index]
|
||||
.contains_access_to_arguments_object_in_non_strict_mode |= arguments;
|
||||
self.records[parent_index].contains_direct_call_to_eval |= eval;
|
||||
self.records[parent_index].contains_await_expression |= contains_await;
|
||||
}
|
||||
|
|
@ -480,7 +501,11 @@ impl ScopeCollector {
|
|||
}
|
||||
|
||||
pub fn open_static_init_scope(&mut self, scope_data: Option<Rc<RefCell<ScopeData>>>) {
|
||||
self.open_scope(ScopeType::ClassStaticInit, scope_data, ScopeLevel::StaticInitTopLevel);
|
||||
self.open_scope(
|
||||
ScopeType::ClassStaticInit,
|
||||
scope_data,
|
||||
ScopeLevel::StaticInitTopLevel,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn open_class_field_scope(&mut self, scope_data: Option<Rc<RefCell<ScopeData>>>) {
|
||||
|
|
@ -511,7 +536,12 @@ impl ScopeCollector {
|
|||
|
||||
for name in bound_names {
|
||||
let flags = self.records[index].variable(name).flags;
|
||||
if flags.intersects(VarFlags::VAR | VarFlags::FORBIDDEN_LEXICAL | VarFlags::FUNCTION | VarFlags::LEXICAL) {
|
||||
if flags.intersects(
|
||||
VarFlags::VAR
|
||||
| VarFlags::FORBIDDEN_LEXICAL
|
||||
| VarFlags::FUNCTION
|
||||
| VarFlags::LEXICAL,
|
||||
) {
|
||||
self.already_declared_error(name, declaration_line, declaration_column);
|
||||
}
|
||||
self.records[index].variable(name).flags |= VarFlags::LEXICAL;
|
||||
|
|
@ -541,7 +571,9 @@ impl ScopeCollector {
|
|||
let mut scope_index = index;
|
||||
loop {
|
||||
let existing_flags = self.records[scope_index].variable(name).flags;
|
||||
if existing_flags.intersects(VarFlags::LEXICAL | VarFlags::FUNCTION | VarFlags::FORBIDDEN_VAR) {
|
||||
if existing_flags
|
||||
.intersects(VarFlags::LEXICAL | VarFlags::FUNCTION | VarFlags::FORBIDDEN_VAR)
|
||||
{
|
||||
self.already_declared_error(name, declaration_line, declaration_column);
|
||||
}
|
||||
let var = self.records[scope_index].variable(name);
|
||||
|
|
@ -550,7 +582,9 @@ impl ScopeCollector {
|
|||
if self.records[scope_index].is_top_level() {
|
||||
break;
|
||||
}
|
||||
scope_index = self.records[scope_index].parent.expect("scope has no parent");
|
||||
scope_index = self.records[scope_index]
|
||||
.parent
|
||||
.expect("scope has no parent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -586,7 +620,10 @@ impl ScopeCollector {
|
|||
} else {
|
||||
// Check flags first, then modify. This avoids borrow checker issues
|
||||
// since we need to access both variables and functions_to_hoist.
|
||||
let existing_flags = self.records[index].variables.get(name).map_or(VarFlags::EMPTY, |v| v.flags);
|
||||
let existing_flags = self.records[index]
|
||||
.variables
|
||||
.get(name)
|
||||
.map_or(VarFlags::EMPTY, |v| v.flags);
|
||||
|
||||
if existing_flags.intersects(VarFlags::VAR | VarFlags::LEXICAL) {
|
||||
self.already_declared_error(name, declaration_line, declaration_column);
|
||||
|
|
@ -602,10 +639,12 @@ impl ScopeCollector {
|
|||
|
||||
if !existing_flags.intersects(VarFlags::LEXICAL) {
|
||||
let block_scope = self.records[index].scope_data.clone();
|
||||
self.records[index].functions_to_hoist.push(HoistableFunction {
|
||||
name: Utf16String::from(name),
|
||||
block_scope_data: block_scope,
|
||||
});
|
||||
self.records[index]
|
||||
.functions_to_hoist
|
||||
.push(HoistableFunction {
|
||||
name: Utf16String::from(name),
|
||||
block_scope_data: block_scope,
|
||||
});
|
||||
}
|
||||
|
||||
let var = self.records[index].variable(name);
|
||||
|
|
@ -634,9 +673,15 @@ impl ScopeCollector {
|
|||
|
||||
// === Identifier registration ===
|
||||
|
||||
pub fn register_identifier(&mut self, id: Rc<Identifier>, name: &[u16], declaration_kind: Option<DeclarationKind>) {
|
||||
pub fn register_identifier(
|
||||
&mut self,
|
||||
id: Rc<Identifier>,
|
||||
name: &[u16],
|
||||
declaration_kind: Option<DeclarationKind>,
|
||||
) {
|
||||
let index = self.current.expect("no current scope");
|
||||
self.records[index].identifier_groups
|
||||
self.records[index]
|
||||
.identifier_groups
|
||||
.entry(Utf16String::from(name))
|
||||
.and_modify(|group| {
|
||||
group.identifiers.push(id.clone());
|
||||
|
|
@ -669,16 +714,25 @@ impl ScopeCollector {
|
|||
// Placeholder for a pattern parameter — push an empty
|
||||
// entry so subsequent parameters get the correct
|
||||
// positional index. Don't register anything else.
|
||||
self.records[index].parameter_names.push(ParameterName { name: Utf16String::default(), is_rest: false });
|
||||
self.records[index].parameter_names.push(ParameterName {
|
||||
name: Utf16String::default(),
|
||||
is_rest: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
self.records[index].parameter_names.push(ParameterName { name: entry.name.clone(), is_rest: entry.is_rest });
|
||||
self.records[index].parameter_names.push(ParameterName {
|
||||
name: entry.name.clone(),
|
||||
is_rest: entry.is_rest,
|
||||
});
|
||||
}
|
||||
if let Some(ref id) = entry.identifier {
|
||||
self.register_identifier(id.clone(), &entry.name, None);
|
||||
}
|
||||
let var = self.records[index].variables.entry(entry.name.clone()).or_default();
|
||||
let var = self.records[index]
|
||||
.variables
|
||||
.entry(entry.name.clone())
|
||||
.or_default();
|
||||
var.flags |= VarFlags::PARAMETER_CANDIDATE | VarFlags::FORBIDDEN_LEXICAL;
|
||||
}
|
||||
|
||||
|
|
@ -687,12 +741,15 @@ impl ScopeCollector {
|
|||
// declares the same name, it must not be optimized to a local, since the
|
||||
// default expression needs to resolve it from the outer scope.
|
||||
if has_parameter_expressions {
|
||||
let names_to_mark: Vec<Utf16String> = self.records[index].identifier_groups.keys()
|
||||
let names_to_mark: Vec<Utf16String> = self.records[index]
|
||||
.identifier_groups
|
||||
.keys()
|
||||
.filter(|name| !self.records[index].has_flag(name, VarFlags::FORBIDDEN_LEXICAL))
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in names_to_mark {
|
||||
self.records[index].variable(&name).flags |= VarFlags::REFERENCED_IN_FORMAL_PARAMETERS;
|
||||
self.records[index].variable(&name).flags |=
|
||||
VarFlags::REFERENCED_IN_FORMAL_PARAMETERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -778,19 +835,23 @@ impl ScopeCollector {
|
|||
// === Getters ===
|
||||
|
||||
pub fn contains_direct_call_to_eval(&self) -> bool {
|
||||
self.current.is_some_and(|index| self.records[index].contains_direct_call_to_eval)
|
||||
self.current
|
||||
.is_some_and(|index| self.records[index].contains_direct_call_to_eval)
|
||||
}
|
||||
|
||||
pub fn uses_this_from_environment(&self) -> bool {
|
||||
self.current.is_some_and(|index| self.records[index].uses_this_from_environment)
|
||||
self.current
|
||||
.is_some_and(|index| self.records[index].uses_this_from_environment)
|
||||
}
|
||||
|
||||
pub fn uses_this(&self) -> bool {
|
||||
self.current.is_some_and(|index| self.records[index].uses_this)
|
||||
self.current
|
||||
.is_some_and(|index| self.records[index].uses_this)
|
||||
}
|
||||
|
||||
pub fn contains_await_expression(&self) -> bool {
|
||||
self.current.is_some_and(|index| self.records[index].contains_await_expression)
|
||||
self.current
|
||||
.is_some_and(|index| self.records[index].contains_await_expression)
|
||||
}
|
||||
|
||||
pub fn scope_type(&self) -> Option<ScopeType> {
|
||||
|
|
@ -798,7 +859,8 @@ impl ScopeCollector {
|
|||
}
|
||||
|
||||
pub fn can_have_using_declaration(&self) -> bool {
|
||||
self.current.is_some_and(|index| self.records[index].scope_level != ScopeLevel::ScriptTopLevel)
|
||||
self.current
|
||||
.is_some_and(|index| self.records[index].scope_level != ScopeLevel::ScriptTopLevel)
|
||||
}
|
||||
|
||||
pub fn has_declaration(&self, name: &[u16]) -> bool {
|
||||
|
|
@ -812,14 +874,19 @@ impl ScopeCollector {
|
|||
}
|
||||
|
||||
pub fn has_declaration_in_current_function(&self, name: &[u16]) -> bool {
|
||||
let Some(index) = self.current else { return false };
|
||||
let Some(index) = self.current else {
|
||||
return false;
|
||||
};
|
||||
let fn_scope = last_function_scope(index, &self.records);
|
||||
let stop = fn_scope.and_then(|fi| self.records[fi].parent);
|
||||
for si in ancestor_scopes(index, &self.records) {
|
||||
if Some(si) == stop {
|
||||
break;
|
||||
}
|
||||
if self.records[si].has_flag(name, VarFlags::LEXICAL | VarFlags::VAR | VarFlags::PARAMETER_CANDIDATE) {
|
||||
if self.records[si].has_flag(
|
||||
name,
|
||||
VarFlags::LEXICAL | VarFlags::VAR | VarFlags::PARAMETER_CANDIDATE,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if self.records[si].has_hoistable_function_named(name) {
|
||||
|
|
@ -867,7 +934,12 @@ impl ScopeCollector {
|
|||
// 1. Propagate eval() flags from children to parent.
|
||||
Self::propagate_eval_poisoning(&mut self.records, index);
|
||||
// 2. Match identifier references to declarations; optimize as locals.
|
||||
Self::resolve_identifiers(&mut self.records, index, initiated_by_eval, suppress_globals);
|
||||
Self::resolve_identifiers(
|
||||
&mut self.records,
|
||||
index,
|
||||
initiated_by_eval,
|
||||
suppress_globals,
|
||||
);
|
||||
// 3. Annex B: hoist block-scoped functions to enclosing function scope.
|
||||
Self::hoist_functions(&mut self.records, index);
|
||||
|
||||
|
|
@ -875,7 +947,8 @@ impl ScopeCollector {
|
|||
// the bytecode generator uses to initialize function-scoped variables.
|
||||
if self.records[index].scope_data.is_some() {
|
||||
let st = self.records[index].scope_type;
|
||||
let needs_fsd = (st == ScopeType::Function && self.records[index].has_function_parameters)
|
||||
let needs_fsd = (st == ScopeType::Function
|
||||
&& self.records[index].has_function_parameters)
|
||||
|| st == ScopeType::ClassStaticInit
|
||||
|| st == ScopeType::ClassField;
|
||||
if needs_fsd {
|
||||
|
|
@ -894,12 +967,16 @@ impl ScopeCollector {
|
|||
/// function (propagates through blocks but stops at function boundaries)
|
||||
fn propagate_eval_poisoning(records: &mut [ScopeRecord], index: usize) {
|
||||
if let Some(parent_index) = records[index].parent {
|
||||
if records[index].contains_direct_call_to_eval || records[index].poisoned_by_eval_in_scope_chain {
|
||||
if records[index].contains_direct_call_to_eval
|
||||
|| records[index].poisoned_by_eval_in_scope_chain
|
||||
{
|
||||
records[parent_index].poisoned_by_eval_in_scope_chain = true;
|
||||
}
|
||||
// eval_in_current_function propagates upward through blocks but
|
||||
// stops at function boundaries (each function is independent).
|
||||
if records[index].eval_in_current_function && records[index].scope_type != ScopeType::Function {
|
||||
if records[index].eval_in_current_function
|
||||
&& records[index].scope_type != ScopeType::Function
|
||||
{
|
||||
records[parent_index].eval_in_current_function = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -917,7 +994,12 @@ impl ScopeCollector {
|
|||
/// - It's NOT captured by a nested function
|
||||
/// - It's NOT used inside a `with` statement
|
||||
/// - The scope chain is NOT poisoned by `eval()`
|
||||
fn resolve_identifiers(records: &mut [ScopeRecord], index: usize, initiated_by_eval: bool, suppress_globals: bool) {
|
||||
fn resolve_identifiers(
|
||||
records: &mut [ScopeRecord],
|
||||
index: usize,
|
||||
initiated_by_eval: bool,
|
||||
suppress_globals: bool,
|
||||
) {
|
||||
let groups = std::mem::take(&mut records[index].identifier_groups);
|
||||
// Sort groups by name for deterministic local variable indices
|
||||
// (HashMap iteration order is arbitrary).
|
||||
|
|
@ -933,7 +1015,10 @@ impl ScopeCollector {
|
|||
}
|
||||
}
|
||||
|
||||
let var_flags = records[index].variables.get(&name).map_or(VarFlags::EMPTY, |v| v.flags);
|
||||
let var_flags = records[index]
|
||||
.variables
|
||||
.get(&name)
|
||||
.map_or(VarFlags::EMPTY, |v| v.flags);
|
||||
|
||||
// Determine what kind of local variable this is (if any).
|
||||
// Priority: var (at top-level) > let/const > function declaration.
|
||||
|
|
@ -978,7 +1063,8 @@ impl ScopeCollector {
|
|||
&& !var_flags.intersects(VarFlags::FORBIDDEN_LEXICAL)
|
||||
{
|
||||
if let Some(parent_index) = records[index].parent {
|
||||
records[parent_index].identifier_groups
|
||||
records[parent_index]
|
||||
.identifier_groups
|
||||
.entry(name.clone())
|
||||
.or_insert_with(|| IdentifierGroup {
|
||||
captured_by_nested_function: false,
|
||||
|
|
@ -1031,7 +1117,8 @@ impl ScopeCollector {
|
|||
}
|
||||
|
||||
if records[index].scope_type == ScopeType::Program {
|
||||
let can_use_global = !(suppress_globals || group.used_inside_with_statement || initiated_by_eval);
|
||||
let can_use_global =
|
||||
!(suppress_globals || group.used_inside_with_statement || initiated_by_eval);
|
||||
if can_use_global {
|
||||
for id in &group.identifiers {
|
||||
if !id.is_inside_scope_with_eval.get() {
|
||||
|
|
@ -1054,7 +1141,8 @@ impl ScopeCollector {
|
|||
&& !var_flags.intersects(VarFlags::FORBIDDEN_LEXICAL)
|
||||
{
|
||||
if let Some(parent_index) = records[index].parent {
|
||||
records[parent_index].identifier_groups
|
||||
records[parent_index]
|
||||
.identifier_groups
|
||||
.entry(name.clone())
|
||||
.or_insert_with(|| IdentifierGroup {
|
||||
captured_by_nested_function: false,
|
||||
|
|
@ -1102,7 +1190,8 @@ impl ScopeCollector {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
let kind = local_var_kind.expect("local_var_kind must be set for local variables");
|
||||
let kind = local_var_kind
|
||||
.expect("local_var_kind must be set for local variables");
|
||||
let lvi = u32_from_usize(sd.local_variables.len());
|
||||
sd.local_variables.push(LocalVariable {
|
||||
name: name.clone(),
|
||||
|
|
@ -1167,7 +1256,9 @@ impl ScopeCollector {
|
|||
None => return,
|
||||
};
|
||||
|
||||
let has_argument_parameter = record.variables.get(utf16!("arguments") as &[u16])
|
||||
let has_argument_parameter = record
|
||||
.variables
|
||||
.get(utf16!("arguments") as &[u16])
|
||||
.is_some_and(|v| v.flags.intersects(VarFlags::FORBIDDEN_LEXICAL));
|
||||
|
||||
let mut vars_to_initialize = Vec::new();
|
||||
|
|
@ -1184,11 +1275,13 @@ impl ScopeCollector {
|
|||
{
|
||||
let sd = scope_data.borrow();
|
||||
for i in (0..sd.children.len()).rev() {
|
||||
if let crate::ast::StatementKind::FunctionDeclaration { name: Some(ref name_ident), .. } = sd.children[i].inner {
|
||||
if let crate::ast::StatementKind::FunctionDeclaration {
|
||||
name: Some(ref name_ident),
|
||||
..
|
||||
} = sd.children[i].inner
|
||||
{
|
||||
if seen_function_names.insert(name_ident.name.clone()) {
|
||||
functions_to_initialize.push(crate::ast::FunctionToInit {
|
||||
child_index: i,
|
||||
});
|
||||
functions_to_initialize.push(crate::ast::FunctionToInit { child_index: i });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1207,7 +1300,10 @@ impl ScopeCollector {
|
|||
let local_info = if let Some(ref ident) = var.var_identifier {
|
||||
if ident.is_local() {
|
||||
Some(LocalBinding {
|
||||
local_type: ident.local_type.get().expect("is_local() implies local_type is Some"),
|
||||
local_type: ident
|
||||
.local_type
|
||||
.get()
|
||||
.expect("is_local() implies local_type is Some"),
|
||||
index: ident.local_index.get(),
|
||||
})
|
||||
} else {
|
||||
|
|
@ -1230,7 +1326,6 @@ impl ScopeCollector {
|
|||
is_function_name,
|
||||
local: local_info,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Sort by name for deterministic output (HashMap iteration order is arbitrary).
|
||||
|
|
@ -1241,7 +1336,9 @@ impl ScopeCollector {
|
|||
has_function_named_arguments = true;
|
||||
}
|
||||
|
||||
if record.variables.get(utf16!("arguments") as &[u16])
|
||||
if record
|
||||
.variables
|
||||
.get(utf16!("arguments") as &[u16])
|
||||
.is_some_and(|v| v.flags.intersects(VarFlags::LEXICAL))
|
||||
{
|
||||
has_lexically_declared_arguments = true;
|
||||
|
|
@ -1266,8 +1363,8 @@ impl ScopeCollector {
|
|||
// during lazy compilation (write_sfd_metadata, FDI emission).
|
||||
sd.uses_this = record.uses_this;
|
||||
sd.uses_this_from_environment = record.uses_this_from_environment;
|
||||
sd.contains_direct_call_to_eval = record.contains_direct_call_to_eval
|
||||
|| record.poisoned_by_eval_in_scope_chain;
|
||||
sd.contains_direct_call_to_eval =
|
||||
record.contains_direct_call_to_eval || record.poisoned_by_eval_in_scope_chain;
|
||||
sd.contains_access_to_arguments_object =
|
||||
record.contains_access_to_arguments_object_in_non_strict_mode;
|
||||
}
|
||||
|
|
@ -1294,7 +1391,8 @@ impl ScopeCollector {
|
|||
|
||||
for function in functions {
|
||||
// A let/const or forbidden var with the same name blocks hoisting.
|
||||
if records[index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FORBIDDEN_VAR) {
|
||||
if records[index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FORBIDDEN_VAR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1324,7 +1422,9 @@ impl ScopeCollector {
|
|||
let bs = block_scope.borrow();
|
||||
for child in &bs.children {
|
||||
if let crate::ast::StatementKind::FunctionDeclaration {
|
||||
ref name, ref is_hoisted, ..
|
||||
ref name,
|
||||
ref is_hoisted,
|
||||
..
|
||||
} = child.inner
|
||||
{
|
||||
if name.as_ref().is_some_and(|n| n.name == function.name) {
|
||||
|
|
@ -1335,7 +1435,9 @@ impl ScopeCollector {
|
|||
}
|
||||
} else if let Some(parent_index) = records[index].parent {
|
||||
// Not yet at top level — keep propagating upward unless blocked.
|
||||
if !records[parent_index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FUNCTION) {
|
||||
if !records[parent_index]
|
||||
.has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FUNCTION)
|
||||
{
|
||||
records[parent_index].functions_to_hoist.push(function);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue