diff --git a/crates/js/src/bytecode/builder.rs b/crates/js/src/bytecode/builder.rs index 08eb8355..93e5e6bd 100644 --- a/crates/js/src/bytecode/builder.rs +++ b/crates/js/src/bytecode/builder.rs @@ -351,4 +351,10 @@ impl<'a> BasicBlockBuilder<'a> { dst } + + #[must_use] + /// + pub fn call(&mut self, callable: Register, arguments: Vec) -> Register { + todo!("function call evaluation") + } } diff --git a/crates/js/src/parser/expressions/call.rs b/crates/js/src/parser/expressions/call.rs new file mode 100644 index 00000000..8fb884bf --- /dev/null +++ b/crates/js/src/parser/expressions/call.rs @@ -0,0 +1,66 @@ +//! +use crate::{ + bytecode::{self, CompileToBytecode}, + parser::{ + tokenization::{Punctuator, SkipLineTerminators, Token, Tokenizer}, + SyntaxError, + }, +}; + +use super::{AssignmentExpression, Expression}; + +/// +#[derive(Clone, Debug)] +pub struct CallExpression { + pub callable: Box, + pub arguments: Vec, +} + +/// +pub fn parse_arguments( + tokenizer: &mut Tokenizer<'_>, +) -> Result, SyntaxError> { + tokenizer.expect_punctuator(Punctuator::ParenthesisOpen)?; + + let mut arguments = vec![]; + let mut next_token = tokenizer.peek(0, SkipLineTerminators::Yes)?; + + while !matches!( + next_token, + Some(Token::Punctuator(Punctuator::ParenthesisClose)) + ) { + let argument = AssignmentExpression::parse::(tokenizer)?; + arguments.push(argument); + + next_token = tokenizer.peek(0, SkipLineTerminators::Yes)?; + + // There may or may not be a comma - if there's not, then this is the last element + if matches!(next_token, Some(Token::Punctuator(Punctuator::Comma))) { + tokenizer.next(SkipLineTerminators::Yes)?; + next_token = tokenizer.peek(0, SkipLineTerminators::Yes)?; + } else { + break; + } + } + + // Consume the final semicolon + tokenizer.next(SkipLineTerminators::Yes)?; + + Ok(arguments) +} + +impl CompileToBytecode for CallExpression { + type Result = bytecode::Register; + + fn compile(&self, builder: &mut bytecode::ProgramBuilder) -> Self::Result { + // https://262.ecma-international.org/14.0/#sec-function-calls-runtime-semantics-evaluation + let callable = self.callable.compile(builder); + let arguments = self + .arguments + .iter() + .map(|arg| arg.compile(builder)) + .collect(); + + builder.get_current_block().call(callable, arguments) + } +} diff --git a/crates/js/src/parser/expressions/left_hand_side_expression.rs b/crates/js/src/parser/expressions/left_hand_side_expression.rs index 43137c0b..244d971f 100644 --- a/crates/js/src/parser/expressions/left_hand_side_expression.rs +++ b/crates/js/src/parser/expressions/left_hand_side_expression.rs @@ -3,12 +3,14 @@ use crate::{ bytecode::{self, CompileToBytecode}, parser::{ - tokenization::{SkipLineTerminators, Token, Tokenizer}, + tokenization::{Punctuator, SkipLineTerminators, Token, Tokenizer}, SyntaxError, }, }; -use super::{parse_primary_expression, Expression, MemberExpression}; +use super::{ + call::parse_arguments, parse_primary_expression, CallExpression, Expression, MemberExpression, +}; /// #[derive(Clone, Debug)] @@ -30,7 +32,24 @@ pub fn parse_lefthandside_expression( Token::Identifier(ident) if ident == "new" => { NewExpression::parse::(tokenizer)? }, - _ => MemberExpression::parse::(tokenizer)?, + _ => { + let member_expression = MemberExpression::parse::(tokenizer)?; + + let expression = match tokenizer.peek(0, SkipLineTerminators::Yes)? { + Some(Token::Punctuator(Punctuator::ParenthesisOpen)) => { + // Parse call expression + let arguments = parse_arguments::(tokenizer)?; + + CallExpression { + callable: Box::new(member_expression), + arguments, + } + .into() + }, + _ => member_expression, + }; + expression + }, }; Ok(lhs_expression) diff --git a/crates/js/src/parser/expressions/mod.rs b/crates/js/src/parser/expressions/mod.rs index 2daf4f45..40c1bbe1 100644 --- a/crates/js/src/parser/expressions/mod.rs +++ b/crates/js/src/parser/expressions/mod.rs @@ -2,6 +2,7 @@ mod assignment_expression; mod binary_expression; +mod call; mod conditional; mod left_hand_side_expression; mod member; @@ -12,6 +13,7 @@ mod update_expression; pub use assignment_expression::AssignmentExpression; pub use binary_expression::BinaryExpression; +pub use call::CallExpression; pub use conditional::ConditionalExpression; pub use member::MemberExpression; pub use unary_expression::UnaryExpression; @@ -44,6 +46,7 @@ pub enum Expression { Assignment(AssignmentExpression), ConditionalExpression(ConditionalExpression), Member(MemberExpression), + Call(CallExpression), } /// @@ -111,6 +114,7 @@ impl CompileToBytecode for Expression { Self::This => todo!(), Self::Assignment(assignment_expression) => assignment_expression.compile(builder), Self::Binary(binary_expression) => binary_expression.compile(builder), + Self::Call(call_expression) => call_expression.compile(builder), Self::ConditionalExpression(conditional_expression) => { conditional_expression.compile(builder) }, @@ -185,3 +189,9 @@ impl From for Expression { Self::Member(value) } } + +impl From for Expression { + fn from(value: CallExpression) -> Self { + Self::Call(value) + } +}