Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement template literals and tagged templates #997

Merged
merged 16 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions boa/src/syntax/ast/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod return_smt;
pub mod spread;
pub mod statement_list;
pub mod switch;
pub mod template;
pub mod throw;
pub mod try_node;

Expand All @@ -41,6 +42,7 @@ pub use self::{
spread::Spread,
statement_list::{RcStatementList, StatementList},
switch::{Case, Switch},
template::{TaggedTemplate, TemplateLit},
throw::Throw,
try_node::{Catch, Finally, Try},
};
Expand Down Expand Up @@ -160,6 +162,12 @@ pub enum Node {
/// A spread (...x) statement. [More information](./spread/struct.Spread.html).
Spread(Spread),

/// A tagged template. [More information](./template/struct.TaggedTemplate.html).
TaggedTemplate(TaggedTemplate),

/// A template literal. [More information](./template/struct.TemplateLit.html).
TemplateLit(TemplateLit),

/// A throw statement. [More information](./throw/struct.Throw.html).
Throw(Throw),

Expand Down Expand Up @@ -257,6 +265,8 @@ impl Node {
Self::BinOp(ref op) => Display::fmt(op, f),
Self::UnaryOp(ref op) => Display::fmt(op, f),
Self::Return(ref ret) => Display::fmt(ret, f),
Self::TaggedTemplate(ref template) => Display::fmt(template, f),
Self::TemplateLit(ref template) => Display::fmt(template, f),
Self::Throw(ref throw) => Display::fmt(throw, f),
Self::Assign(ref op) => Display::fmt(op, f),
Self::LetDeclList(ref decl) => Display::fmt(decl, f),
Expand Down Expand Up @@ -309,6 +319,8 @@ impl Executable for Node {
Node::UnaryOp(ref op) => op.run(context),
Node::New(ref call) => call.run(context),
Node::Return(ref ret) => ret.run(context),
Node::TaggedTemplate(ref template) => template.run(context),
Node::TemplateLit(ref template) => template.run(context),
Node::Throw(ref throw) => throw.run(context),
Node::Assign(ref op) => op.run(context),
Node::VarDeclList(ref decl) => decl.run(context),
Expand Down
156 changes: 156 additions & 0 deletions boa/src/syntax/ast/node/template/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! Template literal node.

use super::Node;
use crate::{builtins::Array, exec::Executable, value::Type, BoaProfiler, Context, Result, Value};
use gc::{Finalize, Trace};

#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
use std::fmt;

#[cfg(test)]
mod tests;

/// Template literals are string literals allowing embedded expressions.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct TemplateLit {
elements: Vec<TemplateElement>,
}

impl TemplateLit {
pub fn new(elements: Vec<TemplateElement>) -> Self {
TemplateLit { elements }
}
}

impl Executable for TemplateLit {
fn run(&self, context: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("TemplateLiteral", "exec");
let mut result = String::new();

for element in self.elements.iter() {
match element {
TemplateElement::String(s) => {
result.push_str(s);
}
TemplateElement::Expr(node) => {
let value = node.run(context)?;
let s = value.to_string(context)?;
result.push_str(&s);
}
}
}
Ok(result.into())
}
}

impl fmt::Display for TemplateLit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "`")?;
for elt in &self.elements {
match elt {
TemplateElement::String(s) => write!(f, "{}", s)?,
TemplateElement::Expr(n) => write!(f, "${{{}}}", n)?,
}
}
write!(f, "`")
}
}
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct TaggedTemplate {
tag: Box<Node>,
raws: Vec<Box<str>>,
cookeds: Vec<Box<str>>,
exprs: Vec<Node>,
}

impl TaggedTemplate {
pub fn new(tag: Node, raws: Vec<Box<str>>, cookeds: Vec<Box<str>>, exprs: Vec<Node>) -> Self {
Self {
tag: Box::new(tag),
raws,
cookeds,
exprs,
}
}
}

impl Executable for TaggedTemplate {
fn run(&self, context: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("TaggedTemplate", "exec");

let template_object = Array::new_array(context)?;
let raw_array = Array::new_array(context)?;

for (i, raw) in self.raws.iter().enumerate() {
raw_array.set_field(i, Value::from(raw), context)?;
}

for (i, cooked) in self.cookeds.iter().enumerate() {
template_object.set_field(i, Value::from(cooked), context)?;
}
template_object.set_field("raw", raw_array, context)?;

let (this, func) = match *self.tag {
Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(context)?;
if obj.get_type() != Type::Object {
obj = Value::Object(obj.to_object(context)?);
}
(
obj.clone(),
obj.get_field(get_const_field.field(), context)?,
)
}
Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?;
let field = get_field.field().run(context)?;
(
obj.clone(),
obj.get_field(field.to_property_key(context)?, context)?,
)
}
_ => (context.global_object().clone(), self.tag.run(context)?),
};

let mut args = Vec::new();
args.push(template_object);
for expr in self.exprs.iter() {
args.push(expr.run(context)?);
}

context.call(&func, &this, &args)
}
}

impl fmt::Display for TaggedTemplate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}`", self.tag)?;
for (raw, expr) in self.raws.iter().zip(self.exprs.iter()) {
write!(f, "{}${{{}}}", raw, expr)?;
}
write!(f, "`")
}
}

impl From<TaggedTemplate> for Node {
fn from(template: TaggedTemplate) -> Self {
Node::TaggedTemplate(template)
}
}

#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum TemplateElement {
String(Box<str>),
Expr(Node),
}
31 changes: 31 additions & 0 deletions boa/src/syntax/ast/node/template/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::exec;

#[test]
fn template_literal() {
let scenario = r#"
let a = 10;
`result: ${a} and ${a+10}`;
"#;

assert_eq!(&exec(scenario), "\"result: 10 and 20\"");
}

#[test]
fn tagged_template() {
let scenario = r#"
function tag(t, ...args) {
let a = []
a = a.concat([t[0], t[1], t[2]]);
a = a.concat([t.raw[0], t.raw[1], t.raw[2]]);
a = a.concat([args[0], args[1]]);
return a
}
let a = 10;
tag`result: ${a} \x26 ${a+10}`;
"#;

assert_eq!(
&exec(scenario),
r#"[ "result: ", " & ", "", "result: ", " \x26 ", "", 10, 20 ]"#
);
}
10 changes: 10 additions & 0 deletions boa/src/syntax/lexer/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ where
}
}

/// Creates a new Lexer cursor with an initial position.
#[inline]
pub(super) fn with_position(inner: R, pos: Position) -> Self {
Self {
iter: InnerIter::new(inner.bytes()),
pos,
strict_mode: false,
}
}

/// Peeks the next byte.
#[inline]
pub(super) fn peek(&mut self) -> Result<Option<u8>, Error> {
Expand Down
7 changes: 7 additions & 0 deletions boa/src/syntax/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,13 @@ impl<R> Lexer<R> {
))
}
}

pub(crate) fn lex_template(&mut self, start: Position) -> Result<Token, Error>
where
R: Read,
{
TemplateLiteral.lex(&mut self.cursor, start)
}
}

/// ECMAScript goal symbols.
Expand Down
Loading