8134941: Implement ES6 template literal support

Reviewed-by: attila, hannesw
This commit is contained in:
Andreas Woess 2015-10-28 10:54:05 +01:00 committed by Michael Haupt
parent 1b3ee82ffc
commit d65a7b5c34
7 changed files with 311 additions and 23 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -83,7 +83,9 @@ public class RuntimeNode extends Expression {
/** is undefined */ /** is undefined */
IS_UNDEFINED(TokenType.EQ_STRICT, Type.BOOLEAN, 2), IS_UNDEFINED(TokenType.EQ_STRICT, Type.BOOLEAN, 2),
/** is not undefined */ /** is not undefined */
IS_NOT_UNDEFINED(TokenType.NE_STRICT, Type.BOOLEAN, 2); IS_NOT_UNDEFINED(TokenType.NE_STRICT, Type.BOOLEAN, 2),
/** Get template object from raw and cooked string arrays. */
GET_TEMPLATE_OBJECT(TokenType.TEMPLATE, Type.SCRIPT_OBJECT, 2);
/** token type */ /** token type */
private final TokenType tokenType; private final TokenType tokenType;

View File

@ -46,6 +46,10 @@ import static jdk.nashorn.internal.parser.TokenType.RBRACE;
import static jdk.nashorn.internal.parser.TokenType.REGEX; import static jdk.nashorn.internal.parser.TokenType.REGEX;
import static jdk.nashorn.internal.parser.TokenType.RPAREN; import static jdk.nashorn.internal.parser.TokenType.RPAREN;
import static jdk.nashorn.internal.parser.TokenType.STRING; import static jdk.nashorn.internal.parser.TokenType.STRING;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_HEAD;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_MIDDLE;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_TAIL;
import static jdk.nashorn.internal.parser.TokenType.XML; import static jdk.nashorn.internal.parser.TokenType.XML;
import java.io.Serializable; import java.io.Serializable;
@ -96,6 +100,8 @@ public class Lexer extends Scanner {
private final boolean pauseOnFunctionBody; private final boolean pauseOnFunctionBody;
private boolean pauseOnNextLeftBrace; private boolean pauseOnNextLeftBrace;
private int templateExpressionOpenBraces;
private static final String SPACETAB = " \t"; // ASCII space and tab private static final String SPACETAB = " \t"; // ASCII space and tab
private static final String LFCR = "\n\r"; // line feed and carriage return (ctrl-m) private static final String LFCR = "\n\r"; // line feed and carriage return (ctrl-m)
@ -392,13 +398,19 @@ public class Lexer extends Scanner {
} }
/** /**
* Test if char is a string delimiter, e.g. '\' or '"'. Also scans exec * Test if char is a string delimiter, e.g. '\' or '"'.
* strings ('`') in scripting mode.
* @param ch a char * @param ch a char
* @return true if string delimiter * @return true if string delimiter
*/ */
protected boolean isStringDelimiter(final char ch) { protected boolean isStringDelimiter(final char ch) {
return ch == '\'' || ch == '"' || (scripting && ch == '`'); return ch == '\'' || ch == '"';
}
/**
* Test if char is a template literal delimiter ('`').
*/
private static boolean isTemplateDelimiter(char ch) {
return ch == '`';
} }
/** /**
@ -943,6 +955,10 @@ public class Lexer extends Scanner {
sb.append(next); sb.append(next);
break; break;
} }
} else if (ch0 == '\r') {
// Convert CR-LF or CR to LF line terminator.
sb.append('\n');
skip(ch1 == '\n' ? 2 : 1);
} else { } else {
// Add regular character. // Add regular character.
sb.append(ch0); sb.append(ch0);
@ -958,7 +974,7 @@ public class Lexer extends Scanner {
/** /**
* Scan over a string literal. * Scan over a string literal.
* @param add true if we nare not just scanning but should actually modify the token stream * @param add true if we are not just scanning but should actually modify the token stream
*/ */
protected void scanString(final boolean add) { protected void scanString(final boolean add) {
// Type of string. // Type of string.
@ -1033,6 +1049,70 @@ public class Lexer extends Scanner {
} }
} }
/**
* Scan over a template string literal.
*/
private void scanTemplate() {
assert ch0 == '`';
TokenType type = TEMPLATE;
// Skip over quote and record beginning of string content.
skip(1);
State stringState = saveState();
// Scan until close quote
while (!atEOF()) {
// Skip over escaped character.
if (ch0 == '`') {
skip(1);
// Record end of string.
stringState.setLimit(position - 1);
add(type == TEMPLATE ? type : TEMPLATE_TAIL, stringState.position, stringState.limit);
return;
} else if (ch0 == '$' && ch1 == '{') {
skip(2);
stringState.setLimit(position - 2);
add(type == TEMPLATE ? TEMPLATE_HEAD : type, stringState.position, stringState.limit);
// scan to RBRACE
Lexer expressionLexer = new Lexer(this, saveState());
expressionLexer.templateExpressionOpenBraces = 1;
expressionLexer.lexify();
restoreState(expressionLexer.saveState());
// scan next middle or tail of the template literal
assert ch0 == '}';
type = TEMPLATE_MIDDLE;
// Skip over rbrace and record beginning of string content.
skip(1);
stringState = saveState();
continue;
} else if (ch0 == '\\') {
skip(1);
// EscapeSequence
if (!isEscapeCharacter(ch0)) {
error(Lexer.message("invalid.escape.char"), TEMPLATE, position, limit);
}
if (isEOL(ch0)) {
// LineContinuation
skipEOL(false);
continue;
}
} else if (isEOL(ch0)) {
// LineTerminatorSequence
skipEOL(false);
continue;
}
// Skip literal character.
skip(1);
}
error(Lexer.message("missing.close.quote"), TEMPLATE, position, limit);
}
/** /**
* Is the given character a valid escape char after "\" ? * Is the given character a valid escape char after "\" ?
* *
@ -1621,6 +1701,16 @@ public class Lexer extends Scanner {
// Scan and add a number. // Scan and add a number.
scanNumber(); scanNumber();
} else if ((type = TokenLookup.lookupOperator(ch0, ch1, ch2, ch3)) != null) { } else if ((type = TokenLookup.lookupOperator(ch0, ch1, ch2, ch3)) != null) {
if (templateExpressionOpenBraces > 0) {
if (type == LBRACE) {
templateExpressionOpenBraces++;
} else if (type == RBRACE) {
if (--templateExpressionOpenBraces == 0) {
break;
}
}
}
// Get the number of characters in the token. // Get the number of characters in the token.
final int typeLength = type.getLength(); final int typeLength = type.getLength();
// Skip that many characters. // Skip that many characters.
@ -1644,6 +1734,12 @@ public class Lexer extends Scanner {
} else if (Character.isDigit(ch0)) { } else if (Character.isDigit(ch0)) {
// Scan and add a number. // Scan and add a number.
scanNumber(); scanNumber();
} else if (isTemplateDelimiter(ch0) && es6) {
// Scan and add template in ES6 mode.
scanTemplate();
} else if (isTemplateDelimiter(ch0) && scripting) {
// Scan and add an exec string ('`') in scripting mode.
scanString(true);
} else { } else {
// Don't recognize this character. // Don't recognize this character.
skip(1); skip(1);
@ -1699,6 +1795,11 @@ public class Lexer extends Scanner {
return valueOfIdent(start, len); // String return valueOfIdent(start, len); // String
case REGEX: case REGEX:
return valueOfPattern(start, len); // RegexToken::LexerToken return valueOfPattern(start, len); // RegexToken::LexerToken
case TEMPLATE:
case TEMPLATE_HEAD:
case TEMPLATE_MIDDLE:
case TEMPLATE_TAIL:
return valueOfString(start, len, true); // String
case XML: case XML:
return valueOfXML(start, len); // XMLToken::LexerToken return valueOfXML(start, len); // XMLToken::LexerToken
case DIRECTIVE_COMMENT: case DIRECTIVE_COMMENT:
@ -1710,6 +1811,45 @@ public class Lexer extends Scanner {
return null; return null;
} }
/**
* Get the raw string value of a template literal string part.
*
* @param token template string token
* @return raw string
*/
public String valueOfRawString(final long token) {
final int start = Token.descPosition(token);
final int length = Token.descLength(token);
// Save the current position.
final int savePosition = position;
// Calculate the end position.
final int end = start + length;
// Reset to beginning of string.
reset(start);
// Buffer for recording characters.
final StringBuilder sb = new StringBuilder(length);
// Scan until end of string.
while (position < end) {
if (ch0 == '\r') {
// Convert CR-LF or CR to LF line terminator.
sb.append('\n');
skip(ch1 == '\n' ? 2 : 1);
} else {
// Add regular character.
sb.append(ch0);
skip(1);
}
}
// Restore position.
reset(savePosition);
return sb.toString();
}
/** /**
* Get the correctly localized error message for a given message id format arguments * Get the correctly localized error message for a given message id format arguments
* @param msgId message id * @param msgId message id

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -51,6 +51,10 @@ import static jdk.nashorn.internal.parser.TokenType.RBRACE;
import static jdk.nashorn.internal.parser.TokenType.RBRACKET; import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
import static jdk.nashorn.internal.parser.TokenType.RPAREN; import static jdk.nashorn.internal.parser.TokenType.RPAREN;
import static jdk.nashorn.internal.parser.TokenType.SEMICOLON; import static jdk.nashorn.internal.parser.TokenType.SEMICOLON;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_HEAD;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_MIDDLE;
import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_TAIL;
import static jdk.nashorn.internal.parser.TokenType.TERNARY; import static jdk.nashorn.internal.parser.TokenType.TERNARY;
import static jdk.nashorn.internal.parser.TokenType.WHILE; import static jdk.nashorn.internal.parser.TokenType.WHILE;
@ -64,6 +68,7 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import jdk.internal.dynalink.support.NameCodec; import jdk.internal.dynalink.support.NameCodec;
import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.Namespace; import jdk.nashorn.internal.codegen.Namespace;
@ -1926,10 +1931,10 @@ loop:
* Literal * Literal
* ArrayLiteral * ArrayLiteral
* ObjectLiteral * ObjectLiteral
* RegularExpressionLiteral
* TemplateLiteral
* ( Expression ) * ( Expression )
* *
* See 11.1
*
* Parse primary expression. * Parse primary expression.
* @return Expression node. * @return Expression node.
*/ */
@ -1989,6 +1994,9 @@ loop:
expect(RPAREN); expect(RPAREN);
return expression; return expression;
case TEMPLATE:
case TEMPLATE_HEAD:
return templateLiteral();
default: default:
// In this context some operator tokens mark the start of a literal. // In this context some operator tokens mark the start of a literal.
@ -2387,6 +2395,8 @@ loop:
} }
/** /**
* Parse left hand side expression.
*
* LeftHandSideExpression : * LeftHandSideExpression :
* NewExpression * NewExpression
* CallExpression * CallExpression
@ -2396,10 +2406,8 @@ loop:
* CallExpression Arguments * CallExpression Arguments
* CallExpression [ Expression ] * CallExpression [ Expression ]
* CallExpression . IdentifierName * CallExpression . IdentifierName
* CallExpression TemplateLiteral
* *
* See 11.2
*
* Parse left hand side expression.
* @return Expression node. * @return Expression node.
*/ */
private Expression leftHandSideExpression() { private Expression leftHandSideExpression() {
@ -2426,7 +2434,7 @@ loop:
callToken = token; callToken = token;
switch (type) { switch (type) {
case LPAREN: case LPAREN: {
// Get NEW or FUNCTION arguments. // Get NEW or FUNCTION arguments.
final List<Expression> arguments = optimizeList(argumentList()); final List<Expression> arguments = optimizeList(argumentList());
@ -2434,8 +2442,8 @@ loop:
lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false); lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
break; break;
}
case LBRACKET: case LBRACKET: {
next(); next();
// Get array index. // Get array index.
@ -2447,8 +2455,8 @@ loop:
lhs = new IndexNode(callToken, finish, lhs, rhs); lhs = new IndexNode(callToken, finish, lhs, rhs);
break; break;
}
case PERIOD: case PERIOD: {
next(); next();
final IdentNode property = getIdentifierName(); final IdentNode property = getIdentifierName();
@ -2457,7 +2465,16 @@ loop:
lhs = new AccessNode(callToken, finish, lhs, property.getName()); lhs = new AccessNode(callToken, finish, lhs, property.getName());
break; break;
}
case TEMPLATE:
case TEMPLATE_HEAD: {
// tagged template literal
final List<Expression> arguments = templateLiteralArgumentList();
lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
break;
}
default: default:
break loop; break loop;
} }
@ -2516,16 +2533,16 @@ loop:
} }
/** /**
* Parse member expression.
*
* MemberExpression : * MemberExpression :
* PrimaryExpression * PrimaryExpression
* FunctionExpression * FunctionExpression
* MemberExpression [ Expression ] * MemberExpression [ Expression ]
* MemberExpression . IdentifierName * MemberExpression . IdentifierName
* MemberExpression TemplateLiteral
* new MemberExpression Arguments * new MemberExpression Arguments
* *
* See 11.2
*
* Parse member expression.
* @return Expression node. * @return Expression node.
*/ */
private Expression memberExpression() { private Expression memberExpression() {
@ -2582,6 +2599,16 @@ loop:
break; break;
} }
case TEMPLATE:
case TEMPLATE_HEAD: {
// tagged template literal
final int callLine = line;
final List<Expression> arguments = templateLiteralArgumentList();
lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
break;
}
default: default:
break loop; break loop;
} }
@ -3035,6 +3062,20 @@ loop:
final ParserState parserState = (ParserState)data.getEndParserState(); final ParserState parserState = (ParserState)data.getEndParserState();
assert parserState != null; assert parserState != null;
if (k < stream.last() && start < parserState.position && parserState.position <= Token.descPosition(stream.get(stream.last()))) {
// RBRACE is already in the token stream, so fast forward to it
for (; k < stream.last(); k++) {
long nextToken = stream.get(k + 1);
if (Token.descPosition(nextToken) == parserState.position && Token.descType(nextToken) == RBRACE) {
token = stream.get(k);
type = Token.descType(token);
next();
assert type == RBRACE && start == parserState.position;
return true;
}
}
}
stream.reset(); stream.reset();
lexer = parserState.createLexer(source, lexer, stream, scripting && !env._no_syntax_extensions, env._es6); lexer = parserState.createLexer(source, lexer, stream, scripting && !env._no_syntax_extensions, env._es6);
line = parserState.line; line = parserState.line;
@ -3425,6 +3466,79 @@ loop:
} }
} }
/**
* Parse untagged template literal as string concatenation.
*/
private Expression templateLiteral() {
assert type == TEMPLATE || type == TEMPLATE_HEAD;
final boolean noSubstitutionTemplate = type == TEMPLATE;
long lastLiteralToken = token;
LiteralNode<?> literal = getLiteral();
if (noSubstitutionTemplate) {
return literal;
}
Expression concat = literal;
TokenType lastLiteralType;
do {
Expression expression = expression();
if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) {
throw error(AbstractParser.message("unterminated.template.expression"), token);
}
concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, expression);
lastLiteralType = type;
lastLiteralToken = token;
literal = getLiteral();
concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, literal);
} while (lastLiteralType == TEMPLATE_MIDDLE);
return concat;
}
/**
* Parse tagged template literal as argument list.
* @return argument list for a tag function call (template object, ...substitutions)
*/
private List<Expression> templateLiteralArgumentList() {
assert type == TEMPLATE || type == TEMPLATE_HEAD;
final ArrayList<Expression> argumentList = new ArrayList<>();
final ArrayList<Expression> rawStrings = new ArrayList<>();
final ArrayList<Expression> cookedStrings = new ArrayList<>();
argumentList.add(null); // filled at the end
final long templateToken = token;
final boolean hasSubstitutions = type == TEMPLATE_HEAD;
addTemplateLiteralString(rawStrings, cookedStrings);
if (hasSubstitutions) {
TokenType lastLiteralType;
do {
Expression expression = expression();
if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) {
throw error(AbstractParser.message("unterminated.template.expression"), token);
}
argumentList.add(expression);
lastLiteralType = type;
addTemplateLiteralString(rawStrings, cookedStrings);
} while (lastLiteralType == TEMPLATE_MIDDLE);
}
final LiteralNode<Expression[]> rawStringArray = LiteralNode.newInstance(templateToken, finish, rawStrings);
final LiteralNode<Expression[]> cookedStringArray = LiteralNode.newInstance(templateToken, finish, cookedStrings);
final RuntimeNode templateObject = new RuntimeNode(templateToken, finish, RuntimeNode.Request.GET_TEMPLATE_OBJECT, rawStringArray, cookedStringArray);
argumentList.set(0, templateObject);
return optimizeList(argumentList);
}
private void addTemplateLiteralString(final ArrayList<Expression> rawStrings, final ArrayList<Expression> cookedStrings) {
final long stringToken = token;
final String rawString = lexer.valueOfRawString(stringToken);
final String cookedString = (String) getValue();
next();
rawStrings.add(LiteralNode.newInstance(stringToken, finish, rawString));
cookedStrings.add(LiteralNode.newInstance(stringToken, finish, cookedString));
}
@Override @Override
public String toString() { public String toString() {
return "'JavaScript Parsing'"; return "'JavaScript Parsing'";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -71,11 +71,21 @@ public class Token {
public static long withDelimiter(final long token) { public static long withDelimiter(final long token) {
final TokenType tokenType = Token.descType(token); final TokenType tokenType = Token.descType(token);
switch(tokenType) { switch(tokenType) {
case STRING: case ESCSTRING: case EXECSTRING: { case STRING:
case ESCSTRING:
case EXECSTRING:
case TEMPLATE:
case TEMPLATE_TAIL: {
final int start = Token.descPosition(token) - 1; final int start = Token.descPosition(token) - 1;
final int len = Token.descLength(token) + 2; final int len = Token.descLength(token) + 2;
return toDesc(tokenType, start, len); return toDesc(tokenType, start, len);
} }
case TEMPLATE_HEAD:
case TEMPLATE_MIDDLE: {
final int start = Token.descPosition(token) - 1;
final int len = Token.descLength(token) + 3;
return toDesc(tokenType, start, len);
}
default: { default: {
return token; return token;
} }

View File

@ -183,6 +183,10 @@ public enum TokenType {
XML (LITERAL, null), XML (LITERAL, null),
OBJECT (LITERAL, null), OBJECT (LITERAL, null),
ARRAY (LITERAL, null), ARRAY (LITERAL, null),
TEMPLATE (LITERAL, null),
TEMPLATE_HEAD (LITERAL, null),
TEMPLATE_MIDDLE(LITERAL, null),
TEMPLATE_TAIL (LITERAL, null),
COMMALEFT (IR, null), COMMALEFT (IR, null),
DECPOSTFIX (IR, null), DECPOSTFIX (IR, null),

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -45,6 +45,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.beans.StaticClass;
import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.ScriptObjectMirror; import jdk.nashorn.api.scripting.ScriptObjectMirror;
@ -1050,4 +1051,20 @@ public final class ScriptRuntime {
context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint");
SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
} }
/**
* ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral).
*
* @param rawStrings array of template raw values
* @param cookedStrings array of template values
* @return template object
*/
public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) {
final ScriptObject template = (ScriptObject)cookedStrings;
final ScriptObject rawObj = (ScriptObject)rawStrings;
assert rawObj.getArray().length() == template.getArray().length();
template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze());
template.freeze();
return template;
}
} }

View File

@ -60,6 +60,7 @@ parser.error.regex.repeated.flag=Repeated RegExp flag: {0}
parser.error.regex.syntax={0} parser.error.regex.syntax={0}
parser.error.trailing.comma.in.json=Trailing comma is not allowed in JSON parser.error.trailing.comma.in.json=Trailing comma is not allowed in JSON
parser.error.missing.const.assignment=Missing assignment to constant "{0}" parser.error.missing.const.assignment=Missing assignment to constant "{0}"
parser.error.unterminated.template.expression=Expected } after expression in template literal
# strict mode error messages # strict mode error messages
parser.error.strict.no.with="with" statement cannot be used in strict mode parser.error.strict.no.with="with" statement cannot be used in strict mode