Browse Source

Implemented template literals and \u{xxxx}. Closes #260

Dmitry Panov 4 years ago
parent
commit
ce3fee827a
12 changed files with 582 additions and 69 deletions
  1. 18 0
      ast/node.go
  2. 106 11
      compiler_expr.go
  3. 49 0
      compiler_test.go
  4. 2 2
      object_test.go
  5. 44 4
      parser/expression.go
  6. 154 21
      parser/lexer.go
  7. 77 0
      parser/parser_test.go
  8. 22 12
      parser/regexp.go
  9. 10 2
      parser/regexp_test.go
  10. 11 17
      tc39_test.go
  11. 2 0
      token/token_const.go
  12. 87 0
      vm.go

+ 18 - 0
ast/node.go

@@ -226,6 +226,21 @@ type (
 		Value   unistring.String
 	}
 
+	TemplateElement struct {
+		Idx     file.Idx
+		Literal string
+		Parsed  unistring.String
+		Valid   bool
+	}
+
+	TemplateLiteral struct {
+		OpenQuote   file.Idx
+		CloseQuote  file.Idx
+		Tag         Expression
+		Elements    []*TemplateElement
+		Expressions []Expression
+	}
+
 	ThisExpression struct {
 		Idx file.Idx
 	}
@@ -264,6 +279,7 @@ func (*ObjectLiteral) _expressionNode()         {}
 func (*RegExpLiteral) _expressionNode()         {}
 func (*SequenceExpression) _expressionNode()    {}
 func (*StringLiteral) _expressionNode()         {}
+func (*TemplateLiteral) _expressionNode()       {}
 func (*ThisExpression) _expressionNode()        {}
 func (*UnaryExpression) _expressionNode()       {}
 func (*MetaProperty) _expressionNode()          {}
@@ -555,6 +571,7 @@ func (self *ObjectLiteral) Idx0() file.Idx         { return self.LeftBrace }
 func (self *RegExpLiteral) Idx0() file.Idx         { return self.Idx }
 func (self *SequenceExpression) Idx0() file.Idx    { return self.Sequence[0].Idx0() }
 func (self *StringLiteral) Idx0() file.Idx         { return self.Idx }
+func (self *TemplateLiteral) Idx0() file.Idx       { return self.OpenQuote }
 func (self *ThisExpression) Idx0() file.Idx        { return self.Idx }
 func (self *UnaryExpression) Idx0() file.Idx       { return self.Idx }
 func (self *MetaProperty) Idx0() file.Idx          { return self.Idx }
@@ -615,6 +632,7 @@ func (self *ObjectPattern) Idx1() file.Idx         { return self.RightBrace + 1
 func (self *RegExpLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
 func (self *SequenceExpression) Idx1() file.Idx    { return self.Sequence[len(self.Sequence)-1].Idx1() }
 func (self *StringLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *TemplateLiteral) Idx1() file.Idx       { return self.CloseQuote + 1 }
 func (self *ThisExpression) Idx1() file.Idx        { return self.Idx + 4 }
 func (self *UnaryExpression) Idx1() file.Idx {
 	if self.Postfix {

+ 106 - 11
compiler_expr.go

@@ -61,6 +61,13 @@ type compiledLiteral struct {
 	val Value
 }
 
+type compiledTemplateLiteral struct {
+	baseCompiledExpr
+	tag         compiledExpr
+	elements    []*ast.TemplateElement
+	expressions []compiledExpr
+}
+
 type compiledAssignExpr struct {
 	baseCompiledExpr
 	left, right compiledExpr
@@ -204,6 +211,8 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
 		return c.compileNumberLiteral(v)
 	case *ast.StringLiteral:
 		return c.compileStringLiteral(v)
+	case *ast.TemplateLiteral:
+		return c.compileTemplateLiteral(v)
 	case *ast.BooleanLiteral:
 		return c.compileBooleanLiteral(v)
 	case *ast.NullLiteral:
@@ -825,6 +834,73 @@ func (e *compiledLiteral) constant() bool {
 	return true
 }
 
+func (e *compiledTemplateLiteral) emitGetter(putOnStack bool) {
+	if e.tag == nil {
+		if len(e.elements) == 0 {
+			e.c.emit(loadVal(e.c.p.defineLiteralValue(stringEmpty)))
+		} else {
+			tail := e.elements[len(e.elements)-1].Parsed
+			if len(e.elements) == 1 {
+				e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail))))
+			} else {
+				stringCount := 0
+				if head := e.elements[0].Parsed; head != "" {
+					e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(head))))
+					stringCount++
+				}
+				e.expressions[0].emitGetter(true)
+				e.c.emit(_toString{})
+				stringCount++
+				for i := 1; i < len(e.elements)-1; i++ {
+					if elt := e.elements[i].Parsed; elt != "" {
+						e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(elt))))
+						stringCount++
+					}
+					e.expressions[i].emitGetter(true)
+					e.c.emit(_toString{})
+					stringCount++
+				}
+				if tail != "" {
+					e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail))))
+					stringCount++
+				}
+				e.c.emit(concatStrings(stringCount))
+			}
+		}
+	} else {
+		cooked := make([]Value, len(e.elements))
+		raw := make([]Value, len(e.elements))
+		for i, elt := range e.elements {
+			raw[i] = &valueProperty{
+				enumerable: true,
+				value:      newStringValue(elt.Literal),
+			}
+			var cookedVal Value
+			if elt.Valid {
+				cookedVal = stringValueFromRaw(elt.Parsed)
+			} else {
+				cookedVal = _undefined
+			}
+			cooked[i] = &valueProperty{
+				enumerable: true,
+				value:      cookedVal,
+			}
+		}
+		e.c.emitCallee(e.tag)
+		e.c.emit(&getTaggedTmplObject{
+			raw:    raw,
+			cooked: cooked,
+		})
+		for _, expr := range e.expressions {
+			expr.emitGetter(true)
+		}
+		e.c.emit(call(len(e.expressions) + 1))
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
 func (c *compiler) compileParameterBindingIdentifier(name unistring.String, offset int) (*binding, bool) {
 	if c.scope.strict {
 		c.checkIdentifierName(name, offset)
@@ -1848,28 +1924,32 @@ func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr {
 	return r
 }
 
-func (e *compiledCallExpr) emitGetter(putOnStack bool) {
-	var calleeName unistring.String
-	if e.isVariadic {
-		e.c.emit(startVariadic)
-	}
-	switch callee := e.callee.(type) {
+func (c *compiler) emitCallee(callee compiledExpr) (calleeName unistring.String) {
+	switch callee := callee.(type) {
 	case *compiledDotExpr:
 		callee.left.emitGetter(true)
-		e.c.emit(dup)
-		e.c.emit(getPropCallee(callee.name))
+		c.emit(dup)
+		c.emit(getPropCallee(callee.name))
 	case *compiledBracketExpr:
 		callee.left.emitGetter(true)
-		e.c.emit(dup)
+		c.emit(dup)
 		callee.member.emitGetter(true)
-		e.c.emit(getElemCallee)
+		c.emit(getElemCallee)
 	case *compiledIdentifierExpr:
 		calleeName = callee.name
 		callee.emitGetterAndCallee()
 	default:
-		e.c.emit(loadUndef)
+		c.emit(loadUndef)
 		callee.emitGetter(true)
 	}
+	return
+}
+
+func (e *compiledCallExpr) emitGetter(putOnStack bool) {
+	if e.isVariadic {
+		e.c.emit(startVariadic)
+	}
+	calleeName := e.c.emitCallee(e.callee)
 
 	for _, expr := range e.args {
 		expr.emitGetter(true)
@@ -2000,6 +2080,21 @@ func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr {
 	return r
 }
 
+func (c *compiler) compileTemplateLiteral(v *ast.TemplateLiteral) compiledExpr {
+	r := &compiledTemplateLiteral{}
+	if v.Tag != nil {
+		r.tag = c.compileExpression(v.Tag)
+	}
+	ce := make([]compiledExpr, len(v.Expressions))
+	for i, expr := range v.Expressions {
+		ce[i] = c.compileExpression(expr)
+	}
+	r.expressions = ce
+	r.elements = v.Elements
+	r.init(c, v.Idx0())
+	return r
+}
+
 func (c *compiler) compileBooleanLiteral(v *ast.BooleanLiteral) compiledExpr {
 	var val Value
 	if v.Value {

+ 49 - 0
compiler_test.go

@@ -4169,6 +4169,55 @@ func TestEvalInIterScope(t *testing.T) {
 	testScript1(SCRIPT, valueInt(0), t)
 }
 
+func TestTemplateLiterals(t *testing.T) {
+	vm := New()
+	_, err := vm.RunString("const a = 1, b = 'b';")
+	if err != nil {
+		t.Fatal(err)
+	}
+	f := func(t *testing.T, template, expected string) {
+		res, err := vm.RunString(template)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if actual := res.Export(); actual != expected {
+			t.Fatalf("Expected: %q, actual: %q", expected, actual)
+		}
+	}
+	t.Run("empty", func(t *testing.T) {
+		f(t, "``", "")
+	})
+	t.Run("noSub", func(t *testing.T) {
+		f(t, "`test`", "test")
+	})
+	t.Run("emptyTail", func(t *testing.T) {
+		f(t, "`a=${a},b=${b}`", "a=1,b=b")
+	})
+	t.Run("emptyHead", func(t *testing.T) {
+		f(t, "`${a},b=${b}$`", "1,b=b$")
+	})
+	t.Run("headAndTail", func(t *testing.T) {
+		f(t, "`a=${a},b=${b}$`", "a=1,b=b$")
+	})
+}
+
+func TestTaggedTemplate(t *testing.T) {
+	const SCRIPT = `
+		let res;
+		const o = {
+			tmpl() {
+				res = this;
+				return () => {};
+			}
+		}
+		` +
+		"o.tmpl()`test`;" + `
+		res === o;
+		`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
 /*
 func TestBabel(t *testing.T) {
 	src, err := ioutil.ReadFile("babel7.js")

+ 2 - 2
object_test.go

@@ -381,7 +381,7 @@ func BenchmarkGetStr(b *testing.B) {
 	}
 }
 
-func _toString(v Value) string {
+func __toString(v Value) string {
 	switch v := v.(type) {
 	case asciiString:
 		return string(v)
@@ -402,7 +402,7 @@ func BenchmarkToString2(b *testing.B) {
 	v := asciiString("test")
 
 	for i := 0; i < b.N; i++ {
-		_toString(v)
+		__toString(v)
 	}
 }
 

+ 44 - 4
parser/expression.go

@@ -85,11 +85,9 @@ func (self *_parser) parsePrimaryExpression() ast.Expression {
 	case token.LEFT_BRACKET:
 		return self.parseArrayLiteral()
 	case token.LEFT_PARENTHESIS:
-		/*self.expect(token.LEFT_PARENTHESIS)
-		expression := self.parseExpression()
-		self.expect(token.RIGHT_PARENTHESIS)
-		return expression*/
 		return self.parseParenthesisedExpression()
+	case token.BACKTICK:
+		return self.parseTemplateLiteral(false)
 	case token.THIS:
 		self.next()
 		return &ast.ThisExpression{
@@ -465,6 +463,46 @@ func (self *_parser) parseArrayLiteral() *ast.ArrayLiteral {
 	}
 }
 
+func (self *_parser) parseTemplateLiteral(tagged bool) *ast.TemplateLiteral {
+	res := &ast.TemplateLiteral{
+		OpenQuote: self.idx,
+	}
+	for self.chr != -1 {
+		start := self.idx + 1
+		literal, parsed, finished, parseErr, err := self.parseTemplateCharacters()
+		if err != nil {
+			self.error(self.idx, err.Error())
+		}
+		res.Elements = append(res.Elements, &ast.TemplateElement{
+			Idx:     start,
+			Literal: literal,
+			Parsed:  parsed,
+			Valid:   parseErr == nil,
+		})
+		if !tagged && parseErr != nil {
+			self.error(self.idx, parseErr.Error())
+		}
+		end := self.idx + 1
+		self.next()
+		if finished {
+			res.CloseQuote = end
+			break
+		}
+		expr := self.parseExpression()
+		res.Expressions = append(res.Expressions, expr)
+		if self.token != token.RIGHT_BRACE {
+			self.errorUnexpectedToken(self.token)
+		}
+	}
+	return res
+}
+
+func (self *_parser) parseTaggedTemplateLiteral(tag ast.Expression) *ast.TemplateLiteral {
+	l := self.parseTemplateLiteral(true)
+	l.Tag = tag
+	return l
+}
+
 func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, idx1 file.Idx) {
 	idx0 = self.expect(token.LEFT_PARENTHESIS)
 	if self.token != token.RIGHT_PARENTHESIS {
@@ -644,6 +682,8 @@ func (self *_parser) parsePostfixExpression() ast.Expression {
 			Operand:  operand,
 			Postfix:  true,
 		}
+	case token.BACKTICK:
+		return self.parseTaggedTemplateLiteral(operand)
 	}
 
 	return operand

+ 154 - 21
parser/lexer.go

@@ -104,7 +104,8 @@ func (self *_parser) scanIdentifier() (string, unistring.String, bool, error) {
 	var parsed unistring.String
 	if hasEscape || isUnicode {
 		var err error
-		parsed, err = parseStringLiteral1(literal, length, isUnicode)
+		// TODO strict
+		parsed, err = parseStringLiteral(literal, length, isUnicode, false)
 		if err != nil {
 			return "", "", false, err
 		}
@@ -424,6 +425,8 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
 				if err != nil {
 					tkn = token.ILLEGAL
 				}
+			case '`':
+				tkn = token.BACKTICK
 			default:
 				self.errorUnexpected(idx, chr)
 				tkn = token.ILLEGAL
@@ -611,20 +614,40 @@ func (self *_parser) scanEscape(quote rune) (int, bool) {
 		length, base = 2, 16
 	case 'u':
 		self.read()
-		length, base = 4, 16
+		if self.chr == '{' {
+			self.read()
+			length, base = 0, 16
+		} else {
+			length, base = 4, 16
+		}
 	default:
 		self.read() // Always make progress
 	}
 
-	if length > 0 {
+	if base > 0 {
 		var value uint32
-		for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
-			digit := uint32(digitValue(self.chr))
-			if digit >= base {
-				break
+		if length > 0 {
+			for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
+				digit := uint32(digitValue(self.chr))
+				if digit >= base {
+					break
+				}
+				value = value*base + digit
+				self.read()
+			}
+		} else {
+			for self.chr != quote && self.chr >= 0 && value < utf8.MaxRune {
+				if self.chr == '}' {
+					self.read()
+					break
+				}
+				digit := uint32(digitValue(self.chr))
+				if digit >= base {
+					break
+				}
+				value = value*base + digit
+				self.read()
 			}
-			value = value*base + digit
-			self.read()
 		}
 		chr = rune(value)
 	}
@@ -682,7 +705,8 @@ func (self *_parser) scanString(offset int, parse bool) (literal string, parsed
 	self.read()
 	literal = self.str[offset:self.chrOffset]
 	if parse {
-		parsed, err = parseStringLiteral1(literal[1:len(literal)-1], length, isUnicode)
+		// TODO strict
+		parsed, err = parseStringLiteral(literal[1:len(literal)-1], length, isUnicode, false)
 	}
 	return
 
@@ -706,6 +730,84 @@ func (self *_parser) scanNewline() {
 	self.read()
 }
 
+func (self *_parser) parseTemplateCharacters() (literal string, parsed unistring.String, finished bool, parseErr, err error) {
+	offset := self.chrOffset
+	var end int
+	length := 0
+	isUnicode := false
+	hasCR := false
+	for {
+		chr := self.chr
+		if chr < 0 {
+			goto unterminated
+		}
+		self.read()
+		if chr == '`' {
+			finished = true
+			end = self.chrOffset - 1
+			break
+		}
+		if chr == '\\' {
+			if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
+				if self.chr == '\r' {
+					hasCR = true
+				}
+				self.scanNewline()
+			} else {
+				l, u := self.scanEscape('`')
+				length += l
+				if u {
+					isUnicode = true
+				}
+			}
+			continue
+		}
+		if chr == '$' && self.chr == '{' {
+			self.read()
+			end = self.chrOffset - 2
+			break
+		}
+		if chr >= utf8.RuneSelf {
+			isUnicode = true
+			if chr > 0xFFFF {
+				length++
+			}
+		} else if chr == '\r' {
+			hasCR = true
+			if self.chr == '\n' {
+				length--
+			}
+		}
+		length++
+	}
+	literal = self.str[offset:end]
+	if hasCR {
+		literal = normaliseCRLF(literal)
+	}
+	parsed, parseErr = parseStringLiteral(literal, length, isUnicode, true)
+	self.insertSemicolon = true
+	return
+unterminated:
+	err = errors.New(err_UnexpectedEndOfInput)
+	return
+}
+
+func normaliseCRLF(s string) string {
+	var buf strings.Builder
+	buf.Grow(len(s))
+	for i := 0; i < len(s); i++ {
+		if s[i] == '\r' {
+			buf.WriteByte('\n')
+			if i < len(s)-1 && s[i+1] == '\n' {
+				i++
+			}
+		} else {
+			buf.WriteByte(s[i])
+		}
+	}
+	return buf.String()
+}
+
 func hex2decimal(chr byte) (value rune, ok bool) {
 	{
 		chr := rune(chr)
@@ -760,7 +862,7 @@ error:
 	return nil, errors.New("Illegal numeric literal")
 }
 
-func parseStringLiteral1(literal string, length int, unicode bool) (unistring.String, error) {
+func parseStringLiteral(literal string, length int, unicode, strict bool) (unistring.String, error) {
 	var sb strings.Builder
 	var chars []uint16
 	if unicode {
@@ -829,17 +931,46 @@ func parseStringLiteral1(literal string, length int, unicode bool) (unistring.St
 				case 'x':
 					size = 2
 				case 'u':
-					size = 4
-				}
-				if len(str) < size {
-					return "", fmt.Errorf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size)
+					if str == "" || str[0] != '{' {
+						size = 4
+					}
 				}
-				for j := 0; j < size; j++ {
-					decimal, ok := hex2decimal(str[j])
-					if !ok {
-						return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size])
+				if size > 0 {
+					if len(str) < size {
+						return "", fmt.Errorf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size)
+					}
+					for j := 0; j < size; j++ {
+						decimal, ok := hex2decimal(str[j])
+						if !ok {
+							return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size])
+						}
+						value = value<<4 | decimal
+					}
+				} else {
+					str = str[1:]
+					var val rune
+					value = -1
+					for ; size < len(str); size++ {
+						if str[size] == '}' {
+							if size == 0 {
+								return "", fmt.Errorf("invalid escape: \\%s", string(chr))
+							}
+							size++
+							value = val
+							break
+						}
+						decimal, ok := hex2decimal(str[size])
+						if !ok {
+							return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size+1])
+						}
+						val = val<<4 | decimal
+						if val > utf8.MaxRune {
+							return "", fmt.Errorf("undefined Unicode code-point: %q", str[:size+1])
+						}
+					}
+					if value == -1 {
+						return "", fmt.Errorf("unterminated \\u{: %q", str)
 					}
-					value = value<<4 | decimal
 				}
 				str = str[size:]
 				if chr == 'x' {
@@ -855,7 +986,9 @@ func parseStringLiteral1(literal string, length int, unicode bool) (unistring.St
 				}
 				fallthrough
 			case '1', '2', '3', '4', '5', '6', '7':
-				// TODO strict
+				if strict {
+					return "", errors.New("Octal escape sequences are not allowed in this context")
+				}
 				value = rune(chr) - '0'
 				j := 0
 				for ; j < 2; j++ {

+ 77 - 0
parser/parser_test.go

@@ -8,6 +8,7 @@ import (
 
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
 	"github.com/dop251/goja/unistring"
 )
 
@@ -1090,3 +1091,79 @@ var x = {};
 		is(requestedPath, "https://site.com/delme.js.map")
 	})
 }
+
+func TestParseTemplateCharacters(t *testing.T) {
+	parser := newParser("", "`test\\\r\\\n${a}`")
+	parser.next()
+	if parser.token != token.BACKTICK {
+		t.Fatalf("Token: %s", parser.token)
+	}
+	checkParseTemplateChars := func(expectedLiteral string, expectedParsed unistring.String, expectedFinished, expectParseErr, expectErr bool) {
+		literal, parsed, finished, parseErr, err := parser.parseTemplateCharacters()
+		if err != nil != expectErr {
+			t.Fatal(err)
+		}
+		if literal != expectedLiteral {
+			t.Fatalf("Literal: %q", literal)
+		}
+		if parsed != expectedParsed {
+			t.Fatalf("Parsed: %q", parsed)
+		}
+		if finished != expectedFinished {
+			t.Fatal(finished)
+		}
+		if parseErr != nil != expectParseErr {
+			t.Fatalf("parseErr: %v", parseErr)
+		}
+	}
+	checkParseTemplateChars("test\\\n\\\n", "test", false, false, false)
+	parser.next()
+	parser.expect(token.IDENTIFIER)
+	if len(parser.errors) > 0 {
+		t.Fatal(parser.errors)
+	}
+	if parser.token != token.RIGHT_BRACE {
+		t.Fatal("Expected }")
+	}
+	if len(parser.errors) > 0 {
+		t.Fatal(parser.errors)
+	}
+	checkParseTemplateChars("", "", true, false, false)
+	if parser.chr != -1 {
+		t.Fatal("Expected EOF")
+	}
+}
+
+func TestParseTemplateLiteral(t *testing.T) {
+	parser := newParser("", "f()\n`test${a}`")
+	prg, err := parser.parse()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if st, ok := prg.Body[0].(*ast.ExpressionStatement); ok {
+		if expr, ok := st.Expression.(*ast.TemplateLiteral); ok {
+			if expr.Tag == nil {
+				t.Fatal("tag is nil")
+			}
+			if idx0 := expr.Tag.Idx0(); idx0 != 1 {
+				t.Fatalf("Tag.Idx0(): %d", idx0)
+			}
+			if expr.OpenQuote != 5 {
+				t.Fatalf("OpenQuote: %d", expr.OpenQuote)
+			}
+			if expr.CloseQuote != 14 {
+				t.Fatalf("CloseQuote: %d", expr.CloseQuote)
+			}
+			if l := len(expr.Elements); l != 2 {
+				t.Fatalf("len elements: %d", l)
+			}
+			if l := len(expr.Expressions); l != 1 {
+				t.Fatalf("len expressions: %d", l)
+			}
+		} else {
+			t.Fatal(st)
+		}
+	} else {
+		t.Fatal(prg.Body[0])
+	}
+}

+ 22 - 12
parser/regexp.go

@@ -284,7 +284,12 @@ func (self *_RegExp_parser) scanEscape(inClass bool) {
 
 	case 'u':
 		self.read()
-		length, base = 4, 16
+		if self.chr == '{' {
+			self.read()
+			length, base = 0, 16
+		} else {
+			length, base = 4, 16
+		}
 
 	case 'b':
 		if inClass {
@@ -365,31 +370,36 @@ func (self *_RegExp_parser) scanEscape(inClass bool) {
 	// Otherwise, we're a \u.... or \x...
 	valueOffset := self.chrOffset
 
-	var value uint32
-	{
-		length := length
-		for ; length > 0; length-- {
+	if length > 0 {
+		for length := length; length > 0; length-- {
+			digit := uint32(digitValue(self.chr))
+			if digit >= base {
+				// Not a valid digit
+				goto skip
+			}
+			self.read()
+		}
+	} else {
+		for self.chr != '}' && self.chr != -1 {
 			digit := uint32(digitValue(self.chr))
 			if digit >= base {
 				// Not a valid digit
 				goto skip
 			}
-			value = value*base + digit
 			self.read()
 		}
 	}
 
-	if length == 4 {
+	if length == 4 || length == 0 {
 		self.write([]byte{
 			'\\',
 			'x',
 			'{',
-			self.str[valueOffset+0],
-			self.str[valueOffset+1],
-			self.str[valueOffset+2],
-			self.str[valueOffset+3],
-			'}',
 		})
+		self.passString(valueOffset, self.chrOffset)
+		if length != 0 {
+			self.writeByte('}')
+		}
 	} else if length == 2 {
 		self.passString(offset-1, valueOffset+2)
 	} else {

+ 10 - 2
parser/regexp_test.go

@@ -153,11 +153,19 @@ func TestTransformRegExp(t *testing.T) {
 	tt(t, func() {
 		pattern, err := TransformRegExp(`\s+abc\s+`)
 		is(err, nil)
-		_, incompat := err.(RegexpErrorIncompatible)
-		is(incompat, false)
 		is(pattern, `[`+WhitespaceChars+`]+abc[`+WhitespaceChars+`]+`)
 		is(regexp.MustCompile(pattern).MatchString("\t abc def"), true)
 	})
+	tt(t, func() {
+		pattern, err := TransformRegExp(`\u{1d306}`)
+		is(err, nil)
+		is(pattern, `\x{1d306}`)
+	})
+	tt(t, func() {
+		pattern, err := TransformRegExp(`\u1234`)
+		is(err, nil)
+		is(pattern, `\x{1234}`)
+	})
 }
 
 func BenchmarkTransformRegExp(b *testing.B) {

+ 11 - 17
tc39_test.go

@@ -85,10 +85,6 @@ var (
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
 
-		// \u{xxxxx}
-		"test/annexB/built-ins/escape/escape-above-astral.js": true,
-		"test/built-ins/RegExp/prototype/source/value-u.js":   true,
-
 		// SharedArrayBuffer
 		"test/built-ins/ArrayBuffer/prototype/slice/this-is-sharedarraybuffer.js": true,
 
@@ -227,19 +223,6 @@ var (
 		"test/language/expressions/arrow-function/lexical-super-property.js":                                         true,
 		"test/language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js":               true,
 
-		// template strings
-		"test/built-ins/String/raw/zero-literal-segments.js":                                                       true,
-		"test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js":                           true,
-		"test/built-ins/String/raw/special-characters.js":                                                          true,
-		"test/built-ins/String/raw/return-the-string-value-from-template.js":                                       true,
-		"test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js":             true,
-		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":                   true,
-		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":                     true,
-		"test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js": true,
-		"test/built-ins/TypedArrayConstructors/internals/Set/conversion-operation-consistent-nan.js":               true,
-		"test/built-ins/RegExp/named-groups/functional-replace-non-global.js":                                      true,
-		"test/built-ins/RegExp/named-groups/functional-replace-global.js":                                          true,
-
 		// restricted unicode regexp syntax
 		"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js":         true,
 		"test/built-ins/RegExp/unicode_restricted_octal_escape.js":                   true,
@@ -262,6 +245,8 @@ var (
 		"test/built-ins/RegExp/prototype/Symbol.replace/result-coerce-groups.js":          true,
 		"test/built-ins/RegExp/prototype/Symbol.replace/result-get-groups-err.js":         true,
 		"test/built-ins/RegExp/prototype/Symbol.replace/result-get-groups-prop-err.js":    true,
+		"test/built-ins/RegExp/named-groups/functional-replace-non-global.js":             true,
+		"test/built-ins/RegExp/named-groups/functional-replace-global.js":                 true,
 
 		// Because goja parser works in UTF-8 it is not possible to pass strings containing invalid UTF-16 code points.
 		// This is mitigated by escaping them as \uXXXX, however because of this the RegExp source becomes
@@ -291,6 +276,8 @@ var (
 		"test/built-ins/Array/prototype/unshift/length-near-integer-limit.js":                     true,
 		"test/built-ins/Array/prototype/unshift/throws-if-integer-limit-exceeded.js":              true,
 		"test/built-ins/String/prototype/split/separator-undef-limit-custom.js":                   true,
+		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":  true,
+		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":    true,
 
 		// generators
 		"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true,
@@ -311,12 +298,15 @@ var (
 	es6IdWhiteList = []string{
 		"8.1.2.1",
 		"9.5",
+		"11.8.6",
 		"12.1",
 		"12.2.1",
 		"12.2.2",
 		"12.2.5",
 		"12.2.6.1",
 		"12.2.6.8",
+		"12.2.8",
+		"12.2.9",
 		"12.4",
 		"12.5",
 		"12.6",
@@ -411,6 +401,10 @@ var (
 		"sec-integer-indexed-exotic-objects-set-p-v-receiver",
 		"sec-destructuring-binding-patterns",
 		"sec-runtime-semantics-keyeddestructuringassignmentevaluation",
+		"sec-gettemplateobject",
+		"sec-tagged-templates-runtime-semantics-evaluation",
+		"sec-template-literal-lexical-components",
+		"sec-template-literals",
 	}
 )
 

+ 2 - 0
token/token_const.go

@@ -73,6 +73,7 @@ const (
 	QUESTION_MARK     // ?
 	ARROW             // =>
 	ELLIPSIS          // ...
+	BACKTICK          // `
 
 	firstKeyword
 	IF
@@ -174,6 +175,7 @@ var token2string = [...]string{
 	QUESTION_MARK:               "?",
 	ARROW:                       "=>",
 	ELLIPSIS:                    "...",
+	BACKTICK:                    "`",
 	IF:                          "if",
 	IN:                          "in",
 	OF:                          "of",

+ 87 - 0
vm.go

@@ -5,6 +5,7 @@ import (
 	"math"
 	"runtime"
 	"strconv"
+	"strings"
 	"sync"
 	"sync/atomic"
 
@@ -1242,6 +1243,14 @@ func (_toPropertyKey) exec(vm *vm) {
 	vm.pc++
 }
 
+type _toString struct{}
+
+func (_toString) exec(vm *vm) {
+	p := vm.sp - 1
+	vm.stack[p] = vm.stack[p].toString()
+	vm.pc++
+}
+
 type _getElemRef struct{}
 
 var getElemRef _getElemRef
@@ -3907,3 +3916,81 @@ func (_createArgsRestStash) exec(vm *vm) {
 	vm.stash.extraArgs = nil
 	vm.pc++
 }
+
+type concatStrings int
+
+func (n concatStrings) exec(vm *vm) {
+	strs := vm.stack[vm.sp-int(n) : vm.sp]
+	length := 0
+	allAscii := true
+	for _, s := range strs {
+		if allAscii {
+			if _, ok := s.(unicodeString); ok {
+				allAscii = false
+			}
+		}
+		length += s.(valueString).length()
+	}
+
+	vm.sp -= int(n) - 1
+	if allAscii {
+		var buf strings.Builder
+		buf.Grow(length)
+		for _, s := range strs {
+			buf.WriteString(string(s.(asciiString)))
+		}
+		vm.stack[vm.sp-1] = asciiString(buf.String())
+	} else {
+		var buf unicodeStringBuilder
+		buf.Grow(length)
+		for _, s := range strs {
+			buf.WriteString(s.(valueString))
+		}
+		vm.stack[vm.sp-1] = buf.String()
+	}
+	vm.pc++
+}
+
+type getTaggedTmplObject struct {
+	raw, cooked []Value
+}
+
+// As tagged template objects are not cached (because it's hard to ensure the cache is cleaned without using
+// finalizers) this wrapper is needed to override the equality method so that two objects for the same template
+// literal appeared be equal from the code's point of view.
+type taggedTemplateArray struct {
+	*arrayObject
+	idPtr *[]Value
+}
+
+func (a *taggedTemplateArray) equal(other objectImpl) bool {
+	if o, ok := other.(*taggedTemplateArray); ok {
+		return a.idPtr == o.idPtr
+	}
+	return false
+}
+
+func (c *getTaggedTmplObject) exec(vm *vm) {
+	cooked := vm.r.newArrayObject()
+	setArrayValues(cooked, c.cooked)
+	cooked.lengthProp.writable = false
+
+	raw := vm.r.newArrayObject()
+	setArrayValues(raw, c.raw)
+	raw.lengthProp.writable = false
+	raw.preventExtensions(true)
+	raw.val.self = &taggedTemplateArray{
+		arrayObject: raw,
+		idPtr:       &c.raw,
+	}
+
+	cooked._putProp("raw", raw.val, false, false, false)
+	cooked.preventExtensions(true)
+	cooked.val.self = &taggedTemplateArray{
+		arrayObject: cooked,
+		idPtr:       &c.cooked,
+	}
+
+	vm.push(cooked.val)
+	vm.pc++
+}