Browse Source

ES6 String Literals (#570)

Fixes #547
Marko Lahma 6 years ago
parent
commit
f65d588a7e

+ 4 - 3
.gitattributes

@@ -1,3 +1,4 @@
-Jint.Tests/* linguist-vendored
-Jint.Tests.Ecma/* linguist-vendored
-Jint.Tests.CommonScripts/* linguist-vendored
+Jint.Tests/** linguist-vendored
+Jint.Tests.Ecma/** linguist-vendored
+Jint.Tests.CommonScripts/** linguist-vendored
+Jint.Tests.Test262/** linguist-vendored

+ 39 - 0
Jint.Benchmark/StringBuilderBenchmark.cs

@@ -0,0 +1,39 @@
+using BenchmarkDotNet.Attributes;
+
+namespace Jint.Benchmark
+{
+    [MemoryDiagnoser]
+    public class StringBuilderBenchmark
+    {
+        private const string script = @"
+var x = 'some string';
+";
+
+        private Engine engine;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            engine = new Engine();
+            engine.Execute(script);
+        }
+
+        [Benchmark]
+        public void One()
+        {
+            engine.Execute("`hello ${x}`");
+        }
+
+        [Benchmark]
+        public void Two()
+        {
+            engine.Execute("`hello ${x}, hello ${x}`");
+        }
+
+        [Benchmark]
+        public void Three()
+        {
+            engine.Execute("`hello ${x}, hello ${x}, hello ${x}`");
+        }
+    }
+}

+ 8 - 0
Jint.Tests.Test262/LanguageTests.cs

@@ -35,5 +35,13 @@ namespace Jint.Tests.Test262
         {
         {
             RunTestInternal(sourceFile);
             RunTestInternal(sourceFile);
         }
         }
+
+        [Theory(DisplayName = "language\\expressions\\template-literal")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\template-literal", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\template-literal", true, Skip = "Skipped")]
+        protected void ExpressionsTemplateLiteral(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
     }
     }
 }
 }

+ 7 - 6
Jint.Tests.Test262/Test262Test.cs

@@ -4,7 +4,6 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
-using Esprima;
 using Jint.Runtime;
 using Jint.Runtime;
 using Newtonsoft.Json.Linq;
 using Newtonsoft.Json.Linq;
 using Xunit;
 using Xunit;
@@ -213,16 +212,18 @@ namespace Jint.Tests.Test262
                     }
                     }
                 }
                 }
 
 
-                if (name.StartsWith("built-ins/String/raw/"))
+                if (code.IndexOf("SpecialCasing.txt") > -1)
                 {
                 {
                     skip = true;
                     skip = true;
-                    reason = "requires template string";
+                    reason = "SpecialCasing.txt not implemented";
                 }
                 }
 
 
-                if (code.IndexOf("SpecialCasing.txt") > -1)
+                if (file.EndsWith("tv-line-continuation.js")
+                    || file.EndsWith("tv-line-terminator-sequence.js")
+                    || file.EndsWith("special-characters.js"))
                 {
                 {
-                    skip = true;
-                    reason = "SpecialCasing.txt not implemented";
+                    // LF endings required
+                    code = code.Replace("\r\n", "\n");
                 }
                 }
 
 
                 var sourceFile = new SourceFile(
                 var sourceFile = new SourceFile(

+ 2 - 1
Jint.Tests.Test262/test/built-ins/String/raw/special-characters.js

@@ -13,7 +13,8 @@ assert.sameValue(
   'Unicode escape sequences'
   'Unicode escape sequences'
 );
 );
 assert.sameValue(
 assert.sameValue(
-  String.raw`\
\
+  String.raw`\
+\
 \
 \
 `,
 `,
   '\\\n\\\n\\\n',
   '\\\n\\\n\\\n',

+ 3 - 2
Jint.Tests.Test262/test/language/expressions/template-literal/tv-line-continuation.js

@@ -20,9 +20,10 @@ calls = 0;
   assert.sameValue(
   assert.sameValue(
     cs.raw[0], '\u005C\n\u005C\n\u005C\n', 'Line Feed and Carriage Return'
     cs.raw[0], '\u005C\n\u005C\n\u005C\n', 'Line Feed and Carriage Return'
   );
   );
-})`\
+})`\
 \
 \
-\
`
+\
+`
 assert.sameValue(calls, 1);
 assert.sameValue(calls, 1);
 
 
 calls = 0;
 calls = 0;

+ 4 - 0
Jint.Tests.Test262/test/skipped.json

@@ -361,5 +361,9 @@
   {
   {
     "source": "built-ins/Math/pow/int32_min-exponent.js",
     "source": "built-ins/Math/pow/int32_min-exponent.js",
     "reason": "const not implemented"
     "reason": "const not implemented"
+  },
+  {
+    "source": "language/expressions/template-literal/tv-line-terminator-sequence.js",
+    "reason": "Line feed problems (git, windows, linux)"
   }
   }
 ]
 ]

+ 0 - 24
Jint/Native/Array/ArrayExecutionContext.cs

@@ -1,24 +0,0 @@
-using System.Text;
-using System.Threading;
-
-namespace Jint.Native.Array
-{
-    /// <summary>
-    /// Helper to cache common data structures needed in array access on a per thread basis.
-    /// </summary>
-    internal class ArrayExecutionContext
-    {
-        // cache key container for array iteration for less allocations
-        private static readonly ThreadLocal<ArrayExecutionContext> _executionContext = new ThreadLocal<ArrayExecutionContext>(() => new ArrayExecutionContext());
-
-        private StringBuilder _stringBuilder;
-
-        private ArrayExecutionContext()
-        {
-        }
-
-        public StringBuilder StringBuilder => _stringBuilder = _stringBuilder ?? new StringBuilder();
-
-        public static ArrayExecutionContext Current => _executionContext.Value;
-    }
-}

+ 10 - 8
Jint/Native/Array/ArrayPrototype.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
@@ -1009,16 +1010,17 @@ namespace Jint.Native.Array
                 return s;
                 return s;
             }
             }
 
 
-            var sb = ArrayExecutionContext.Current.StringBuilder;
-            sb.Clear();
-            sb.Append(s);
-            for (uint k = 1; k < len; k++)
+            using (var sb = StringBuilderPool.GetInstance())
             {
             {
-                sb.Append(sep);
-                sb.Append(StringFromJsValue(o.Get(k)));
-            }
+                sb.Builder.Append(s);
+                for (uint k = 1; k < len; k++)
+                {
+                    sb.Builder.Append(sep);
+                    sb.Builder.Append(StringFromJsValue(o.Get(k)));
+                }
 
 
-            return sb.ToString();
+                return sb.ToString();
+            }
         }
         }
 
 
         private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)
         private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)

+ 20 - 15
Jint/Native/Number/NumberPrototype.cs

@@ -2,6 +2,7 @@
 using System.Globalization;
 using System.Globalization;
 using System.Text;
 using System.Text;
 using Jint.Native.Number.Dtoa;
 using Jint.Native.Number.Dtoa;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
 
 
@@ -234,15 +235,17 @@ namespace Jint.Native.Number
                 return "0";
                 return "0";
             }
             }
 
 
-            var result = new StringBuilder();
-            while (n > 0)
+            using (var result = StringBuilderPool.GetInstance())
             {
             {
-                var digit = (int)(n % radix);
-                n = n / radix;
-                result.Insert(0, digits[digit]);
-            }
+                while (n > 0)
+                {
+                    var digit = (int) (n % radix);
+                    n = n / radix;
+                    result.Builder.Insert(0, digits[digit]);
+                }
 
 
-            return result.ToString();
+                return result.ToString();
+            }
         }
         }
 
 
         public static string ToFractionBase(double n, int radix)
         public static string ToFractionBase(double n, int radix)
@@ -256,17 +259,19 @@ namespace Jint.Native.Number
                 return "0";
                 return "0";
             }
             }
 
 
-            var result = new StringBuilder();
-            while (n > 0 && result.Length < 50) // arbitrary limit
+            using (var result = StringBuilderPool.GetInstance())
             {
             {
-                var c = n*radix;
-                var d = (int) c;
-                n = c - d;
+                while (n > 0 && result.Length < 50) // arbitrary limit
+                {
+                    var c = n*radix;
+                    var d = (int) c;
+                    n = c - d;
 
 
-                result.Append(digits[d]);
-            }
+                    result.Builder.Append(digits[d]);
+                }
 
 
-            return result.ToString();
+                return result.ToString();
+            }
         }
         }
 
 
         public static string ToNumberString(double m)
         public static string ToNumberString(double m)

+ 39 - 0
Jint/Native/String/StringConstructor.cs

@@ -1,6 +1,9 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Text;
+using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.Object;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
@@ -35,6 +38,7 @@ namespace Jint.Native.String
         {
         {
             SetOwnProperty("fromCharCode", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCharCode", FromCharCode, 1), PropertyFlag.NonEnumerable));
             SetOwnProperty("fromCharCode", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCharCode", FromCharCode, 1), PropertyFlag.NonEnumerable));
             SetOwnProperty("fromCodePoint", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCodePoint", FromCodePoint, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
             SetOwnProperty("fromCodePoint", new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromCodePoint", FromCodePoint, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
+            SetOwnProperty("raw", new PropertyDescriptor(new ClrFunctionInstance(Engine, "raw", Raw, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable));
         }
         }
 
 
         private static JsValue FromCharCode(JsValue thisObj, JsValue[] arguments)
         private static JsValue FromCharCode(JsValue thisObj, JsValue[] arguments)
@@ -88,6 +92,41 @@ namespace Jint.Native.String
             return result + FromCharCode(null, codeUnits.ToArray());
             return result + FromCharCode(null, codeUnits.ToArray());
         }
         }
 
 
+        /// <summary>
+        /// https://www.ecma-international.org/ecma-262/6.0/#sec-string.raw
+        /// </summary>
+        private JsValue Raw(JsValue thisObj, JsValue[] arguments)
+        {
+            var cooked = TypeConverter.ToObject(_engine, arguments.At(0));
+            var raw = TypeConverter.ToObject(_engine, cooked.Get("raw"));
+
+            var operations = ArrayPrototype.ArrayOperations.For(raw);
+            var length = operations.GetLength();
+
+            if (length <= 0)
+            {
+                return JsString.Empty;
+            }
+
+            using (var result = StringBuilderPool.GetInstance())
+            {
+                for (var i = 0; i < length; i++)
+                {
+                    if (i > 0)
+                    {
+                        if (i < arguments.Length && !arguments[i].IsUndefined())
+                        {
+                            result.Builder.Append(TypeConverter.ToString(arguments[i]));
+                        }
+                    }
+
+                    result.Builder.Append(TypeConverter.ToString(operations.Get((ulong) i)));
+                }
+
+                return result.ToString();
+            }
+        }
+
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
         {
             if (arguments.Length == 0)
             if (arguments.Length == 0)

+ 1 - 16
Jint/Native/String/StringExecutionContext.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Text;
 using System.Threading;
 using System.Threading;
 
 
 namespace Jint.Native.String
 namespace Jint.Native.String
@@ -11,7 +10,6 @@ namespace Jint.Native.String
     {
     {
         private static readonly ThreadLocal<StringExecutionContext> _executionContext = new ThreadLocal<StringExecutionContext>(() => new StringExecutionContext());
         private static readonly ThreadLocal<StringExecutionContext> _executionContext = new ThreadLocal<StringExecutionContext>(() => new StringExecutionContext());
 
 
-        private StringBuilder _stringBuilder;
         private List<string> _splitSegmentList;
         private List<string> _splitSegmentList;
         private string[] _splitArray1;
         private string[] _splitArray1;
 
 
@@ -19,21 +17,8 @@ namespace Jint.Native.String
         {
         {
         }
         }
 
 
-        public StringBuilder GetStringBuilder(int capacity)
-        {
-            if (_stringBuilder == null)
-            {
-                _stringBuilder = new StringBuilder(capacity);
-            }
-            else
-            {
-                _stringBuilder.EnsureCapacity(capacity);
-            }
-
-            return _stringBuilder;
-        }
-
         public List<string> SplitSegmentList => _splitSegmentList = _splitSegmentList ?? new List<string>();
         public List<string> SplitSegmentList => _splitSegmentList = _splitSegmentList ?? new List<string>();
+
         public string[] SplitArray1 => _splitArray1 = _splitArray1 ?? new string[1];
         public string[] SplitArray1 => _splitArray1 = _splitArray1 ?? new string[1];
 
 
         public static StringExecutionContext Current => _executionContext.Value;
         public static StringExecutionContext Current => _executionContext.Value;

+ 62 - 54
Jint/Native/String/StringPrototype.cs

@@ -7,6 +7,7 @@ using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Native.RegExp;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
@@ -520,62 +521,64 @@ namespace Jint.Native.String
                     // $`	Inserts the portion of the string that precedes the matched substring.
                     // $`	Inserts the portion of the string that precedes the matched substring.
                     // $'	Inserts the portion of the string that follows the matched substring.
                     // $'	Inserts the portion of the string that follows the matched substring.
                     // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
                     // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
-                    var replacementBuilder = StringExecutionContext.Current.GetStringBuilder(0);
-                    replacementBuilder.Clear();
-                    for (int i = 0; i < replaceString.Length; i++)
+                    using (var replacementBuilder = StringBuilderPool.GetInstance())
                     {
                     {
-                        char c = replaceString[i];
-                        if (c == '$' && i < replaceString.Length - 1)
+                        for (int i = 0; i < replaceString.Length; i++)
                         {
                         {
-                            c = replaceString[++i];
-                            if (c == '$')
-                                replacementBuilder.Append('$');
-                            else if (c == '&')
-                                replacementBuilder.Append(matchValue);
-                            else if (c == '`')
-                                replacementBuilder.Append(thisString.Substring(0, matchIndex));
-                            else if (c == '\'')
-                                replacementBuilder.Append(thisString.Substring(matchIndex + matchValue.Length));
-                            else if (c >= '0' && c <= '9')
+                            char c = replaceString[i];
+                            if (c == '$' && i < replaceString.Length - 1)
                             {
                             {
-                                int matchNumber1 = c - '0';
-
-                                // The match number can be one or two digits long.
-                                int matchNumber2 = 0;
-                                if (i < replaceString.Length - 1 && replaceString[i + 1] >= '0' && replaceString[i + 1] <= '9')
-                                    matchNumber2 = matchNumber1 * 10 + (replaceString[i + 1] - '0');
-
-                                // Try the two digit capture first.
-                                if (matchNumber2 > 0 && matchNumber2 < args.Length - 2)
-                                {
-                                    // Two digit capture replacement.
-                                    replacementBuilder.Append(TypeConverter.ToString(args[matchNumber2]));
-                                    i++;
-                                }
-                                else if (matchNumber1 > 0 && matchNumber1 < args.Length - 2)
+                                c = replaceString[++i];
+                                if (c == '$')
+                                    replacementBuilder.Builder.Append('$');
+                                else if (c == '&')
+                                    replacementBuilder.Builder.Append(matchValue);
+                                else if (c == '`')
+                                    replacementBuilder.Builder.Append(thisString.Substring(0, matchIndex));
+                                else if (c == '\'')
+                                    replacementBuilder.Builder.Append(thisString.Substring(matchIndex + matchValue.Length));
+                                else if (c >= '0' && c <= '9')
                                 {
                                 {
-                                    // Single digit capture replacement.
-                                    replacementBuilder.Append(TypeConverter.ToString(args[matchNumber1]));
+                                    int matchNumber1 = c - '0';
+
+                                    // The match number can be one or two digits long.
+                                    int matchNumber2 = 0;
+                                    if (i < replaceString.Length - 1 && replaceString[i + 1] >= '0' && replaceString[i + 1] <= '9')
+                                        matchNumber2 = matchNumber1 * 10 + (replaceString[i + 1] - '0');
+
+                                    // Try the two digit capture first.
+                                    if (matchNumber2 > 0 && matchNumber2 < args.Length - 2)
+                                    {
+                                        // Two digit capture replacement.
+                                        replacementBuilder.Builder.Append(TypeConverter.ToString(args[matchNumber2]));
+                                        i++;
+                                    }
+                                    else if (matchNumber1 > 0 && matchNumber1 < args.Length - 2)
+                                    {
+                                        // Single digit capture replacement.
+                                        replacementBuilder.Builder.Append(TypeConverter.ToString(args[matchNumber1]));
+                                    }
+                                    else
+                                    {
+                                        // Capture does not exist.
+                                        replacementBuilder.Builder.Append('$');
+                                        i--;
+                                    }
                                 }
                                 }
                                 else
                                 else
                                 {
                                 {
-                                    // Capture does not exist.
-                                    replacementBuilder.Append('$');
-                                    i--;
+                                    // Unknown replacement pattern.
+                                    replacementBuilder.Builder.Append('$');
+                                    replacementBuilder.Builder.Append(c);
                                 }
                                 }
                             }
                             }
                             else
                             else
-                            {
-                                // Unknown replacement pattern.
-                                replacementBuilder.Append('$');
-                                replacementBuilder.Append(c);
-                            }
+                                replacementBuilder.Builder.Append(c);
                         }
                         }
-                        else
-                            replacementBuilder.Append(c);
+
+                        return replacementBuilder.ToString();
                     }
                     }
 
 
-                    return replacementBuilder.ToString();
                 });
                 });
             }
             }
 
 
@@ -638,12 +641,14 @@ namespace Jint.Native.String
                 _engine._jsValueArrayPool.ReturnArray(args);
                 _engine._jsValueArrayPool.ReturnArray(args);
 
 
                 // Replace only the first match.
                 // Replace only the first match.
-                var result = StringExecutionContext.Current.GetStringBuilder(thisString.Length + (substr.Length - substr.Length));
-                result.Clear();
-                result.Append(thisString, 0, start);
-                result.Append(replaceString);
-                result.Append(thisString, end, thisString.Length - end);
-                return result.ToString();
+                using (var result = StringBuilderPool.GetInstance())
+                {
+                    result.Builder.EnsureCapacity(thisString.Length + (substr.Length - substr.Length));
+                    result.Builder.Append(thisString, 0, start);
+                    result.Builder.Append(replaceString);
+                    result.Builder.Append(thisString, end, thisString.Length - end);
+                    return result.ToString();
+                }
             }
             }
         }
         }
 
 
@@ -1125,13 +1130,16 @@ namespace Jint.Native.String
                 return new string(str[0], n);
                 return new string(str[0], n);
             }
             }
 
 
-            var sb = new StringBuilder(n * str.Length);
-            for (var i = 0; i < n; ++i)
+            using (var sb = StringBuilderPool.GetInstance())
             {
             {
-                sb.Append(str);
-            }
+                sb.Builder.EnsureCapacity(n * str.Length);
+                for (var i = 0; i < n; ++i)
+                {
+                    sb.Builder.Append(str);
+                }
 
 
-            return sb.ToString();
+                return sb.ToString();
+            }
         }
         }
     }
     }
 }
 }

+ 74 - 0
Jint/Pooling/StringBuilderPool.cs

@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Text;
+
+namespace Jint.Pooling
+{
+    /// <summary>
+    /// The usage is:
+    ///        var inst = PooledStringBuilder.GetInstance();
+    ///        var sb = inst.builder;
+    ///        ... Do Stuff...
+    ///        ... sb.ToString() ...
+    ///        inst.Free();
+    /// </summary>
+    internal sealed class StringBuilderPool : IDisposable
+    {
+        // global pool
+        private static readonly ObjectPool<StringBuilderPool> s_poolInstance = CreatePool();
+
+        public readonly StringBuilder Builder = new StringBuilder();
+        private readonly ObjectPool<StringBuilderPool> _pool;
+
+        private StringBuilderPool(ObjectPool<StringBuilderPool> pool)
+        {
+            Debug.Assert(pool != null);
+            _pool = pool;
+        }
+
+        public int Length => Builder.Length;
+
+        // if someone needs to create a private pool;
+        /// <summary>
+        /// If someone need to create a private pool
+        /// </summary>
+        /// <param name="size">The size of the pool.</param>
+        /// <returns></returns>
+        internal static ObjectPool<StringBuilderPool> CreatePool(int size = 32)
+        {
+            ObjectPool<StringBuilderPool> pool = null;
+            pool = new ObjectPool<StringBuilderPool>(() => new StringBuilderPool(pool), size);
+            return pool;
+        }
+
+        public static StringBuilderPool GetInstance()
+        {
+            var builder = s_poolInstance.Allocate();
+            Debug.Assert(builder.Builder.Length == 0);
+            return builder;
+        }
+
+        public override string ToString()
+        {
+            return Builder.ToString();
+        }
+
+        public void Dispose()
+        {
+            var builder = Builder;
+
+            // do not store builders that are too large.
+            if (builder.Capacity <= 1024)
+            {
+                builder.Clear();
+                _pool.Free(this);
+            }
+            else
+            {
+                _pool.ForgetTrackedObject(this);
+            }
+        }
+    }
+}

+ 6 - 0
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -117,6 +117,12 @@ namespace Jint.Runtime.Interpreter.Expressions
                 case Nodes.SpreadElement:
                 case Nodes.SpreadElement:
                     return new JintSpreadExpression(engine, (SpreadElement) expression);
                     return new JintSpreadExpression(engine, (SpreadElement) expression);
 
 
+                case Nodes.TemplateLiteral:
+                    return new JintTemplateLiteralExpression(engine, (TemplateLiteral) expression);
+
+                case Nodes.TaggedTemplateExpression:
+                    return new JintTaggedTemplateExpression(engine, (TaggedTemplateExpression) expression);
+
                 default:
                 default:
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
                     return null;
                     return null;

+ 69 - 0
Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs

@@ -0,0 +1,69 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Array;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintTaggedTemplateExpression : JintExpression
+    {
+        private readonly TaggedTemplateExpression _taggedTemplateExpression;
+        private JintExpression _tagIdentifier;
+        private JintTemplateLiteralExpression _quasi;
+
+        public JintTaggedTemplateExpression(Engine engine, TaggedTemplateExpression expression) : base(engine, expression)
+        {
+            _taggedTemplateExpression = expression;
+            _initialized = false;
+        }
+
+        protected override void Initialize()
+        {
+            _tagIdentifier = Build(_engine, _taggedTemplateExpression.Tag);
+            _quasi = (JintTemplateLiteralExpression) Build(_engine, _taggedTemplateExpression.Quasi);
+            _quasi.DoInitialize();
+        }
+
+        protected override object EvaluateInternal()
+        {
+            var tagger = _engine.GetValue(_tagIdentifier.GetValue()) as ICallable
+                         ?? ExceptionHelper.ThrowTypeError<ICallable>(_engine, "Argument must be callable");
+
+            var expressions = _quasi._expressions;
+
+            var args = _engine._jsValueArrayPool.RentArray((expressions.Length + 1));
+
+            var template = GetTemplateObject();
+            args[0] = template;
+
+            for (int i = 0; i < expressions.Length; ++i)
+            {
+                args[i + 1] = expressions[i].GetValue();
+            }
+
+            var result = tagger.Call(JsValue.Undefined, args);
+            _engine._jsValueArrayPool.ReturnArray(args);
+
+            return result;
+        }
+
+        /// <summary>
+        /// https://www.ecma-international.org/ecma-262/6.0/#sec-gettemplateobject
+        /// </summary>
+        private ArrayInstance GetTemplateObject()
+        {
+            var count = (uint) _quasi._templateLiteralExpression.Quasis.Count;
+            var template = _engine.Array.ConstructFast(count);
+            var rawObj = _engine.Array.ConstructFast(count);
+            for (int i = 0; i < _quasi._templateLiteralExpression.Quasis.Count; ++i)
+            {
+                var templateElementValue = _quasi._templateLiteralExpression.Quasis[i].Value;
+                template.SetIndexValue((uint) i, templateElementValue.Cooked, updateLength: false);
+                rawObj.SetIndexValue((uint) i, templateElementValue.Raw, updateLength: false);
+            }
+
+            template.DefineOwnProperty("raw", new PropertyDescriptor(rawObj, PropertyFlag.AllForbidden), false);
+            return template;
+        }
+    }
+}

+ 59 - 0
Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs

@@ -0,0 +1,59 @@
+using System.Text;
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Pooling;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintTemplateLiteralExpression : JintExpression
+    {
+        internal readonly TemplateLiteral _templateLiteralExpression;
+        internal JintExpression[] _expressions;
+
+        public JintTemplateLiteralExpression(Engine engine, TemplateLiteral expression) : base(engine, expression)
+        {
+            _templateLiteralExpression = expression;
+            _initialized = false;
+        }
+
+        protected override void Initialize()
+        {
+            DoInitialize();
+        }
+
+        internal void DoInitialize()
+        {
+            _expressions = new JintExpression[_templateLiteralExpression.Expressions.Count];
+            for (var i = 0; i < _templateLiteralExpression.Expressions.Count; i++)
+            {
+                var exp = _templateLiteralExpression.Expressions[i];
+                _expressions[i] = Build(_engine, exp);
+            }
+
+            _initialized = true;
+        }
+
+        private JsString BuildString()
+        {
+            using (var sb = StringBuilderPool.GetInstance())
+            {
+                for (var i = 0; i < _templateLiteralExpression.Quasis.Count; i++)
+                {
+                    var quasi = _templateLiteralExpression.Quasis[i];
+                    sb.Builder.Append(quasi.Value.Cooked);
+                    if (i < _expressions.Length)
+                    {
+                        sb.Builder.Append(_expressions[i].GetValue());
+                    }
+                }
+
+                return JsString.Create(sb.ToString());
+            }
+        }
+
+        protected override object EvaluateInternal()
+        {
+            return BuildString();
+        }
+    }
+}

+ 28 - 24
Jint/Runtime/JavaScriptException.cs

@@ -4,6 +4,7 @@ using Esprima;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Error;
 using Jint.Native.Error;
+using Jint.Pooling;
 
 
 namespace Jint.Runtime
 namespace Jint.Runtime
 {
 {
@@ -37,34 +38,37 @@ namespace Jint.Runtime
         public JavaScriptException SetCallstack(Engine engine, Location location = null)
         public JavaScriptException SetCallstack(Engine engine, Location location = null)
         {
         {
             Location = location;
             Location = location;
-            var sb = new StringBuilder();
-            foreach (var cse in engine.CallStack)
+            using (var sb = StringBuilderPool.GetInstance())
             {
             {
-                sb.Append(" at ")
-                    .Append(cse)
-                    .Append("(");
-
-                for (var index = 0; index < cse.CallExpression.Arguments.Count; index++)
+                foreach (var cse in engine.CallStack)
                 {
                 {
-                    if (index != 0)
-                        sb.Append(", ");
-                    var arg = cse.CallExpression.Arguments[index];
-                    if (arg is Expression pke)
-                        sb.Append(pke.GetKey());
-                    else
-                        sb.Append(arg);
+                    sb.Builder.Append(" at ")
+                        .Append(cse)
+                        .Append("(");
+
+                    for (var index = 0; index < cse.CallExpression.Arguments.Count; index++)
+                    {
+                        if (index != 0)
+                            sb.Builder.Append(", ");
+                        var arg = cse.CallExpression.Arguments[index];
+                        if (arg is Expression pke)
+                            sb.Builder.Append(pke.GetKey());
+                        else
+                            sb.Builder.Append(arg);
+                    }
+
+
+                    sb.Builder.Append(") @ ")
+                        .Append(cse.CallExpression.Location.Source)
+                        .Append(" ")
+                        .Append(cse.CallExpression.Location.Start.Column)
+                        .Append(":")
+                        .Append(cse.CallExpression.Location.Start.Line)
+                        .AppendLine();
                 }
                 }
-
-
-                sb.Append(") @ ")
-                    .Append(cse.CallExpression.Location.Source)
-                    .Append(" ")
-                    .Append(cse.CallExpression.Location.Start.Column)
-                    .Append(":")
-                    .Append(cse.CallExpression.Location.Start.Line)
-                    .AppendLine();
+                CallStack = sb.ToString();
             }
             }
-            CallStack = sb.ToString();
+
             return this;
             return this;
         }
         }
 
 

+ 1 - 1
README.md

@@ -155,7 +155,7 @@ ES6 features which are being implemented:
 - [ ] [arrows](https://github.com/lukehoban/es6features/blob/master/README.md#arrows)
 - [ ] [arrows](https://github.com/lukehoban/es6features/blob/master/README.md#arrows)
 - [ ] [classes](https://github.com/lukehoban/es6features/blob/master/README.md#classes)
 - [ ] [classes](https://github.com/lukehoban/es6features/blob/master/README.md#classes)
 - [ ] [enhanced object literals](https://github.com/lukehoban/es6features/blob/master/README.md#enhanced-object-literals)
 - [ ] [enhanced object literals](https://github.com/lukehoban/es6features/blob/master/README.md#enhanced-object-literals)
-- [ ] [template strings](https://github.com/lukehoban/es6features/blob/master/README.md#template-strings)
+- [x] [template strings](https://github.com/lukehoban/es6features/blob/master/README.md#template-strings)
 - [ ] [destructuring](https://github.com/lukehoban/es6features/blob/master/README.md#destructuring)
 - [ ] [destructuring](https://github.com/lukehoban/es6features/blob/master/README.md#destructuring)
 - [x] [default + rest + spread](https://github.com/lukehoban/es6features/blob/master/README.md#default--rest--spread)
 - [x] [default + rest + spread](https://github.com/lukehoban/es6features/blob/master/README.md#default--rest--spread)
 - [ ] [let + const](https://github.com/lukehoban/es6features/blob/master/README.md#let--const)
 - [ ] [let + const](https://github.com/lukehoban/es6features/blob/master/README.md#let--const)