mirror of https://github.com/boa-dev/boa.git
Browse Source
* Implement template literals and tagged templates * Merge master into for-in * Implement suggestions from review * Implement suggestions from review Co-authored-by: tofpie <tofpie@users.noreply.github.com>pull/1069/head
tofpie
4 years ago
committed by
GitHub
20 changed files with 749 additions and 148 deletions
@ -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), |
||||||
|
} |
@ -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 ]"# |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
use crate::{ |
||||||
|
profiler::BoaProfiler, |
||||||
|
syntax::{ |
||||||
|
ast::node::TaggedTemplate, |
||||||
|
ast::{Node, Position, Punctuator}, |
||||||
|
lexer::TokenKind, |
||||||
|
parser::{ |
||||||
|
cursor::Cursor, expression::Expression, AllowAwait, AllowYield, ParseError, |
||||||
|
ParseResult, TokenParser, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
use std::io::Read; |
||||||
|
|
||||||
|
/// Parses a tagged template.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-TemplateLiteral
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub(super) struct TaggedTemplateLiteral { |
||||||
|
allow_yield: AllowYield, |
||||||
|
allow_await: AllowAwait, |
||||||
|
start: Position, |
||||||
|
tag: Node, |
||||||
|
} |
||||||
|
|
||||||
|
impl TaggedTemplateLiteral { |
||||||
|
/// Creates a new `TaggedTemplateLiteral` parser.
|
||||||
|
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A, start: Position, tag: Node) -> Self |
||||||
|
where |
||||||
|
Y: Into<AllowYield>, |
||||||
|
A: Into<AllowAwait>, |
||||||
|
{ |
||||||
|
Self { |
||||||
|
allow_yield: allow_yield.into(), |
||||||
|
allow_await: allow_await.into(), |
||||||
|
start, |
||||||
|
tag, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for TaggedTemplateLiteral |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = Node; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>) -> ParseResult { |
||||||
|
let _timer = BoaProfiler::global().start_event("TaggedTemplateLiteral", "Parsing"); |
||||||
|
|
||||||
|
let mut raws = Vec::new(); |
||||||
|
let mut cookeds = Vec::new(); |
||||||
|
let mut exprs = Vec::new(); |
||||||
|
|
||||||
|
let mut token = cursor.next()?.ok_or(ParseError::AbruptEnd)?; |
||||||
|
|
||||||
|
loop { |
||||||
|
match token.kind() { |
||||||
|
TokenKind::TemplateMiddle { raw, cooked } => { |
||||||
|
raws.push(raw.clone()); |
||||||
|
cookeds.push(cooked.clone()); |
||||||
|
exprs.push( |
||||||
|
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?, |
||||||
|
); |
||||||
|
cursor.expect( |
||||||
|
TokenKind::Punctuator(Punctuator::CloseBlock), |
||||||
|
"template literal", |
||||||
|
)?; |
||||||
|
} |
||||||
|
TokenKind::TemplateNoSubstitution { raw, cooked } => { |
||||||
|
raws.push(raw.clone()); |
||||||
|
cookeds.push(cooked.clone()); |
||||||
|
return Ok(Node::from(TaggedTemplate::new( |
||||||
|
self.tag, raws, cookeds, exprs, |
||||||
|
))); |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(ParseError::general( |
||||||
|
"cannot parse tagged template literal", |
||||||
|
self.start, |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
token = cursor.lex_template(self.start)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,104 @@ |
|||||||
|
//! Template literal parsing.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//! - [ECMAScript specification][spec]
|
||||||
|
//!
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-template-literals
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
profiler::BoaProfiler, |
||||||
|
syntax::{ |
||||||
|
ast::node::template::{TemplateElement, TemplateLit}, |
||||||
|
ast::Position, |
||||||
|
ast::Punctuator, |
||||||
|
lexer::TokenKind, |
||||||
|
parser::cursor::Cursor, |
||||||
|
parser::expression::Expression, |
||||||
|
parser::{AllowAwait, AllowYield, ParseError, TokenParser}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
use std::io::Read; |
||||||
|
|
||||||
|
/// Parses a template literal.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
/// - [ECMAScript specification][spec]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#prod-TemplateLiteral
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub(super) struct TemplateLiteral { |
||||||
|
allow_yield: AllowYield, |
||||||
|
allow_await: AllowAwait, |
||||||
|
start: Position, |
||||||
|
first: String, |
||||||
|
} |
||||||
|
|
||||||
|
impl TemplateLiteral { |
||||||
|
/// Creates a new `TemplateLiteral` parser.
|
||||||
|
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A, start: Position, first: &str) -> Self |
||||||
|
where |
||||||
|
Y: Into<AllowYield>, |
||||||
|
A: Into<AllowAwait>, |
||||||
|
{ |
||||||
|
Self { |
||||||
|
allow_yield: allow_yield.into(), |
||||||
|
allow_await: allow_await.into(), |
||||||
|
start, |
||||||
|
first: first.to_owned(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<R> TokenParser<R> for TemplateLiteral |
||||||
|
where |
||||||
|
R: Read, |
||||||
|
{ |
||||||
|
type Output = TemplateLit; |
||||||
|
|
||||||
|
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { |
||||||
|
let _timer = BoaProfiler::global().start_event("TemplateLiteral", "Parsing"); |
||||||
|
|
||||||
|
let mut elements = Vec::new(); |
||||||
|
elements.push(TemplateElement::String(self.first.into_boxed_str())); |
||||||
|
elements.push(TemplateElement::Expr( |
||||||
|
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?, |
||||||
|
)); |
||||||
|
cursor.expect( |
||||||
|
TokenKind::Punctuator(Punctuator::CloseBlock), |
||||||
|
"template literal", |
||||||
|
)?; |
||||||
|
|
||||||
|
loop { |
||||||
|
match cursor.lex_template(self.start)?.kind() { |
||||||
|
TokenKind::TemplateMiddle { |
||||||
|
cooked: template, .. |
||||||
|
} => { |
||||||
|
elements.push(TemplateElement::String(template.to_owned())); |
||||||
|
elements.push(TemplateElement::Expr( |
||||||
|
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?, |
||||||
|
)); |
||||||
|
cursor.expect( |
||||||
|
TokenKind::Punctuator(Punctuator::CloseBlock), |
||||||
|
"template literal", |
||||||
|
)?; |
||||||
|
} |
||||||
|
TokenKind::TemplateNoSubstitution { |
||||||
|
cooked: template, .. |
||||||
|
} => { |
||||||
|
elements.push(TemplateElement::String(template.to_owned())); |
||||||
|
return Ok(TemplateLit::new(elements)); |
||||||
|
} |
||||||
|
_ => { |
||||||
|
return Err(ParseError::general( |
||||||
|
"cannot parse template literal", |
||||||
|
self.start, |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue