/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package foam.lib.json;

import foam.lib.parse.PStream;
import foam.lib.parse.Parser;
import foam.lib.parse.ParserContext;
import foam.lib.parse.Alt;
import foam.lib.parse.Literal;
import foam.lib.parse.AnyChar;
import foam.lib.parse.Seq1;

public class StringParser
  implements Parser
{
  final Parser delimiterParser = new Alt(
    new Literal("\"\"\""),
    new Literal("\""),
    new Literal("'")
  );

  final char ESCAPE = '\\';

  // An escape is either a Unicode code like \u001a, an ASCII escape like \n or
  // just a literal escape next character.

  final Parser escapeParser = new Alt(
    new UnicodeParser(),
    new ASCIIEscapeParser(),
    new Seq1(1, new Literal(Character.toString(ESCAPE)), new AnyChar())
  );

  public StringParser() {
  }

  public PStream parse(PStream ps, ParserContext x) {
    ps = ps.apply(delimiterParser, x);
    if ( ps == null ) return null;

    Parser delimiter = new Literal((String) ps.value());

    // TODO: Use thread-local StringBuilder
    StringBuilder sb = new StringBuilder();

    PStream result;
    boolean escaping = false;

    while ( ps.valid() ) {
      char c;

      if ( escaping ) {
        ps = ps.apply(escapeParser, x);
        if ( ps == null ) return null;

        sb.append((Character) ps.value());
        escaping = false;

        continue;
      }

      result = ps.apply(delimiter, x);
      if ( result != null ) {
        ps = result;
        break;
      }

      c = ps.head();

      if ( c == ESCAPE ) {
        escaping = true;
        continue;
      }

      sb.append(c);
      ps = ps.tail();
    }

    // Internalize small strings so we don't end up with millions of distinct
    // but equivalent strings, especially the empty string.
    return ps.setValue(sb.length() < 6 ? sb.toString().intern() : sb.toString());
  }
}

@@ -405,16 +405,338 @@
 arser%0A%7B%0A
+  protected ThreadLocal%3CStringBuilder%3E builder__ = new ThreadLocal%3CStringBuilder%3E() %7B%0A    @Override%0A    protected StringBuilder initialValue() %7B%0A      return new StringBuilder();%0A    %7D%0A    @Override%0A    public StringBuilder get() %7B%0A      StringBuilder sb = super.get();%0A      sb.setLength(0);%0A      return sb;%0A    %7D%0A  %7D;%0A%0A
   final 
@@ -1333,32 +1333,39 @@
 ll;%0A%0A    Parser 
+       
 delimiter = new 
@@ -1398,98 +1398,55 @@
 ));%0A
-%0A
     
-// TODO: Use thread-local StringBuilder%0A    StringBuilder sb = new StringBuilder
+StringBuilder sb        = builder__.get
 ();%0A
-%0A
     
@@ -1453,16 +1453,22 @@
 PStream 
+      
 result;%0A
@@ -1479,16 +1479,22 @@
 boolean 
+      
 escaping
@@ -1486,32 +1486,33 @@
        escaping 
+ 
 = false;%0A%0A    wh

Add ThreadLocal StringBuilder to StringParser to generate less garbage.