class StringSegments {
    public final List<StringSegment> segs;
    public int hintLength;

    public StringSegments(final List<StringSegment> segs, final int hintLength) {
      super();
      this.segs = segs;
      this.hintLength = hintLength;
    }

    public boolean isEmpty() {
      return this.segs.isEmpty();
    }

    public String toString(final Map<String, Object> env, final String lexeme) {
      if (this.segs.isEmpty()) {
        return lexeme;
      }
      StringBuilder sb = new StringBuilder(this.hintLength);
      final int size = this.segs.size();
      for (int i = 0; i < size; i++) {
        this.segs.get(i).appendTo(sb, env);
      }
      final String result = sb.toString();
      final int newLen = result.length();
      // Prevent hintLength too large.
      if (newLen > this.hintLength && newLen < 10 * this.hintLength) {
        this.hintLength = newLen;
      }
      return result;
    }
  }

  /**
   * Compile a string to string segments, if string doesn't have a interpolation,returns an empty
   * list.
   *
   * @param lexeme
   * @return
   */
  public StringSegments compileStringSegments(final String lexeme) {
    return this.compileStringSegments(lexeme, null, 1);
  }

  /**
   * Compile a string to string segments, if string doesn't have a interpolation,returns an empty
   * list.
   *
   * @param lexeme
   * @param sourceFile
   * @param lineNo;
   * @return
   */
  public StringSegments compileStringSegments(final String lexeme, final String sourceFile,
      final int lineNo) {
    List<StringSegment> segs = new ArrayList<StringSegment>();
    boolean hasInterpolationOrEscaped = false;
    StringCharacterIterator it = new StringCharacterIterator(lexeme);
    char ch = it.current(), prev = StringCharacterIterator.DONE;
    int lastInterPos = 0;
    int i = 1;
    for (;;) {
      if (ch == '#') {
        if (prev == '\\') {
          // # is escaped, skip the backslash.
          final String segStr = lexeme.substring(lastInterPos, i - 2);
          segs.add(new LiteralSegment(segStr));
          lastInterPos = i - 1;
          hasInterpolationOrEscaped = true;
        } else {
          // # is not escaped.
          prev = ch;
          ch = it.next();
          i++;
          if (ch == '{') {
            // Find a interpolation position.
            if (i - 2 > lastInterPos) {
              final String segStr = lexeme.substring(lastInterPos, i - 2);
              segs.add(new LiteralSegment(segStr));
            }

            try {
              ExpressionLexer lexer = new ExpressionLexer(this, lexeme.substring(i));
              lexer.setLineNo(lineNo);
              ExpressionParser parser =
                  new ExpressionParser(this, lexer, newCodeGenerator(sourceFile, false));

              Expression exp = parser.parse(false);
              final Token<?> lookhead = parser.getLookhead();
              if (lookhead == null || (lookhead.getType() != TokenType.Char
                  || ((CharToken) lookhead).getCh() != '}')) {
                parser.reportSyntaxError("expect '}' to complete string interpolation");
              }
              int expStrLen = lookhead.getStartIndex() + 1;
              while (expStrLen-- > 0) {
                prev = ch;
                ch = it.next();
                i++;
              }
              Token<?> previousToken = null;

              if (parser.getParsedTokens() == 2 && (previousToken = parser.getPrevToken()) != null
                  && previousToken.getType() == TokenType.Variable) {
                // special case for inline variable.
                if (previousToken == Variable.TRUE) {
                  segs.add(new LiteralSegment("true"));
                } else if (previousToken == Variable.FALSE) {
                  segs.add(new LiteralSegment("false"));
                } else if (previousToken == Variable.NIL) {
                  segs.add(new LiteralSegment("null"));
                } else {
                  segs.add(new VarSegment(
                      parser.getSymbolTable().reserve(previousToken.getLexeme()).getLexeme()));
                }
              } else {
                segs.add(new ExpressionSegment(exp));
              }
              hasInterpolationOrEscaped = true;
              lastInterPos = i;
            } catch (Throwable t) {
              throw new CompileExpressionErrorException(
                  "Fail to compile string interpolation: " + lexeme, t);
            }
            // End of interpolation
          }
          // End of # is not escaped.
        }
      }

      if (ch == StringCharacterIterator.DONE) {
        if (i - 1 > lastInterPos) {
          final String segStr = lexeme.substring(lastInterPos, i - 1);
          segs.add(new LiteralSegment(segStr));
        }
        break;
      }

      prev = ch;
      ch = it.next();
      i++;
    }
    if (hasInterpolationOrEscaped) {
      return new StringSegments(segs, lexeme.length() * 2 / 3);
    } else {
      return new StringSegments(Collections.<StringSegment>emptyList(), 0);
    }
  }


  /**
   * check if class is in Options.ALLOWED_CLASS_SET
   *
   * @param checkIfAllow check or not
   * @param clazz the class for check
   * @return the class for check
   */
  public Class<?> checkIfClassIsAllowed(final boolean checkIfAllow, final Class<?> clazz) {<FILL_FUNCTION_BODY>

    if (checkIfAllow) {
      Set<Class<?>> allowedList = this.getOptionValue(Options.ALLOWED_CLASS_SET).classes;
      if (allowedList != null) {
        // Null list means allowing all classes
        if (!allowedList.contains(clazz)) {
          throw new ExpressionRuntimeException(
              "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET");
        }
      }
      Set<Class<?>> assignableList =
          this.getOptionValue(Options.ASSIGNABLE_ALLOWED_CLASS_SET).classes;
      if (assignableList != null) {
        for (Class<?> aClass : assignableList) {
          if (aClass.isAssignableFrom(clazz)) {
            return clazz;
          }
        }
        throw new ExpressionRuntimeException(
            "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET");
      }
    }
    return clazz;
  
<no_super_class>