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