Package cn.bran.japid.compiler

Source Code of cn.bran.japid.compiler.JapidAbstractCompiler

/**
* Copyright 2010 Bing Ran<bing_ran@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.bran.japid.compiler;

import japa.parser.ast.body.Parameter;

import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cn.bran.japid.classmeta.AbstractTemplateClassMetaData;
import cn.bran.japid.classmeta.InnerClassMeta;
import cn.bran.japid.classmeta.MimeTypeEnum;
import cn.bran.japid.compiler.JapidParser.Token;
import cn.bran.japid.compiler.Tag.TagDef;
import cn.bran.japid.compiler.Tag.TagIf;
import cn.bran.japid.compiler.Tag.TagInTag;
import cn.bran.japid.compiler.Tag.TagSet;
import cn.bran.japid.tags.Each;
import cn.bran.japid.template.ActionRunner;
import cn.bran.japid.template.JapidTemplate;
import cn.bran.japid.template.RenderResult;
import cn.bran.japid.util.DirUtil;
import cn.bran.japid.util.WebUtils;

/**
* based on the original code from the Play! Framework
*
* the parent class for all three type compilers: regular template compiler, the
* LayoutCompiler and the TagCompiler.
*
* @author original Play! authors
* @author Bing Ran<bing_ran@hotmail.com>
*
*/
public abstract class JapidAbstractCompiler {
  /**
   *
   */
  private static final String INCLUDE = "include";
  public static final String DO_LAYOUT = "doLayout";
  private static final String ELVIS = "?:";
  // pattern: } else if xxx {
  static final String ELSE_IF_PATTERN_STRING = "\\s*\\}\\s*else\\s*if\\s+([^\\(].*)\\s*";
  static final Pattern ELSE_IF_PATTERN = Pattern.compile(ELSE_IF_PATTERN_STRING);
//  static final String OPEN_ELSE_IF_PATTERN_STRING = "\\s*else\\s*if\\s+([^\\(].*)\\s*";
  // relax the excluding of the ()
  static final String OPEN_ELSE_IF_PATTERN_STRING = "\\s*else\\s*if\\s+(.*)\\s*";
  static final Pattern OPEN_ELSE_IF_PATTERN = Pattern.compile(OPEN_ELSE_IF_PATTERN_STRING);
  static final String OPEN_ELSE_STRING = "\\s*else\\s*";
  static final String SPACE_OR_NONE = "\\s*";
  static final String SPACE_AT_LEAST_ONE = "\\s+";

  // please trim the string before applying the pattern
  // the idea is to substitute the open for with "each" tag
  static final String OPEN_FOR_PATTERN_STRING = "for\\s+([^\\(].+)\\s*:\\s*(.+[^\\{])";
  static final Pattern OPEN_FOR_PATTERN = Pattern.compile(OPEN_FOR_PATTERN_STRING);

  // pattern: if xxx {
  // should really use java parser
  static final String OPEN_IF_PATTERN1 = "if\\s+[^\\(].*";
  static final String IF_PATTERN_STRING = "if\\s*\\((.*)\\).*";
  static final Pattern IF_PATTERN = Pattern.compile(IF_PATTERN_STRING);

  private static final String JAPID_RESULT = "cn.bran.play.JapidResult";

  private static final String ARGS = "args";

  protected static final String HTML = ".html";
  // private static final String DO_BODY = "doBody";
  protected static final String SPACE = " ";
  protected static final String NEW_LINE = "\n";
  protected JapidTemplate template;
  protected JapidParser parser;
  protected boolean doNextScan = true;

  // the tagsStack only tracks the regular tags, particularly not including
  // the open if
  private Stack<Tag> tagsStack = new Stack<Tag>();
  // the shadow is not used to keep the stacking of open if and regular tag
  private Stack<Tag> tagsStackShadow = new Stack<Tag>();

  protected int tagIndex;
  protected boolean skipLineBreak;
  protected boolean useWithPlay = true;
  private String templateShortName;

  public void compile(JapidTemplate t) {
    template = t;
    String tname = t.name;
    int lastSlash = tname.lastIndexOf("/");
    if (lastSlash >= 0) {
      tname = tname.substring(lastSlash + 1);
    }
    this.templateShortName = tname;

    getTemplateClassMetaData().setOriginalTemplate(t.name);
    getTemplateClassMetaData().useWithPlay = this.useWithPlay;
    hop();
  }

  /**
   *
   */
  protected void parse() {
    // Parse
    loop: for (;;) {

      if (doNextScan) {
        stateBeforePreviousState = previousState;
        previousState = state;
        state = parser.nextToken();
      } else {
        doNextScan = true;
      }

      String token = parser.getToken();

      switch (state) {
      case EOF:
        break loop;
      case PLAIN:
        plain(token);
        break;
      case VERBATIM:
        plain(token);
        break;
      case SCRIPT:
        script(token);
        break;
      case CLOSING_BRACE:
        closingBrace(token);
        break;
      case SCRIPT_LINE:
        if (previousState == Token.PLAIN && stateBeforePreviousState == Token.SCRIPT_LINE) {
          String spacer = this.getTemplateClassMetaData().removeLastSingleEmptyLine();
          if (spacer != null) {
            Tag currentScope = this.tagsStack.peek();
            // currentScope.bodyBuffer.append(text);
            int lastIndex = currentScope.bodyTextList.size() - 1;
            currentScope.bodyTextList.remove(lastIndex);
            currentScope.bodyTextList.set(lastIndex - 1, spacer);
          }
        }
        scriptline(token);
        break;
      case EXPR:
        expr(token, false);
        break;
      case EXPR_NATURAL_ESCAPED:
        expr(token, true);
        break;
      case EXPR_ESCAPED:
        expr(token, true);
        break;
      case MESSAGE:
        message(token);
        break;
      case ACTION:
        action(token, false);
        break;
      case ABS_ACTION:
        action(token, true);
        break;
      case COMMENT:
        skipLineBreak = true;
        break;
      case START_TAG:
        startTag(buildTag(token));
        break;
      case END_TAG:
        String tagName = token.trim();
        if (tagsStack.isEmpty()) {
          throw new JapidCompilationException(template, parser.getLineNumber(), "#{/" + tagName + "} is not opened.");
        }
        Tag tag = popStack();
        endTag(tag);
        break;
      case TEMPLATE_ARGS:
        templateArgs(token);
        break;
      }
    }
  }

  /**
   * @return
   */
  private Tag popStack() {
    // tagsStackShadow.pop();
    return tagsStack.pop();
  }

  protected void closingBrace(String token) {
    // ok a } after some space in a line
    // treat it as MARKER }
    print("}");
  }

  protected void plain(String token) {
    String text = token.replace("\\", "\\\\").replaceAll("\"", "\\\\\"");
    if (skipLineBreak && text.startsWith(NEW_LINE)) {
      text = text.substring(1);
    }

    // add static content to classmetadata and print the ref to the
    // generated source code
    if (this.getTemplateClassMetaData().getTrimStaticContent()) {
      String r = text.trim();
      if (r.length() == 0)
        return;
      else {
        text = text.trim();
      }
    }
    String lines = composeValidMultiLines(text);
    String ref = this.getTemplateClassMetaData().addStaticText(lines, text);
    if (ref != null) {
      // print the static content via the variable
      // print("p(" + ref + ");");
      // print the static content directly
      print("p(" + lines + ");");

      markLine();
      println();
    }
  }

  /**
   * break a line with newlines to multiple lines valid in java source code
   *
   * <pre>
   * "line1\n" +
   * "line2\n"  +
   * "line3";
   *
   * </pre>
   *
   * @param src
   * @return
   */
  public static String composeValidMultiLines(String text) {
    // multi-line
    String[] lines = text.split(NEW_LINE, 10000);
    String result = "";
    for (int i = 0; i < lines.length; i++) {
      String line = lines[i];
      if (line.length() > 0 && (int) line.charAt(line.length() - 1) == 13) {
        // remove the last newline
        line = line.substring(0, line.length() - 1);
      }

      result += "\"" + line;

      if (i == lines.length - 1 && !text.endsWith(NEW_LINE)) {
        // last line
        result += "\"";
      } else if (i == lines.length - 1 && line.equals("")) {
        result += "\"";
      } else {
        // regular line
        result += "\\n\" + \n";
      }

      // markLine(parser.getLine() + i);
    }
    String emptySuffix = " + \n\"\"";
    if (result.endsWith(emptySuffix)) {
      result = result.substring(0, result.length() - emptySuffix.length());
    }
    return result;
  }

  protected abstract void startTag(Tag tag);

  protected void println() {
    // print(NEW_LINE);
    Tag currentScope = this.tagsStack.peek();
    currentScope.bodyTextList.add("\t\t");
    currentLine++;
  }

  /**
   * always append to the last line
   *
   * @param text
   */
  protected void print(String text) {
    Tag currentScope = this.tagsStack.peek();
    // currentScope.bodyBuffer.append(text);
    int lastIndex = currentScope.bodyTextList.size() - 1;
    String lastLine = currentScope.bodyTextList.get(lastIndex);
    lastLine += text;
    currentScope.bodyTextList.set(lastIndex, lastLine);
    // else if (this.currentInnerClassName != null)
    // this.currentInnerClassRenderBody.append(text);
    // else
    // mainRenderBodySource.append(text);
  }

  protected void println(String text) {
    print(text);
    println();
    int i = 0;
    while (i++ < indentLevel) {
      print("\t");
    }
  }

  protected void markLine(int line) {
    if (!this.getTemplateClassMetaData().getTrimStaticContent()) {
      print(makeLineMarker(line));
    }

    template.linesMatrix.put(currentLine, line);
  }

  public String makeLineMarker(int line) {
    if (line <= 0)
      return "";

    return DirUtil.LINE_MARKER + line + DirUtil.OF + templateShortName;
  }

  protected void scriptline(String token) {
    // String line = token.trim();
    // if ()
    script(token);
  }

  protected void script(String token) {
    String[] lines = new String[] { token };
    if (token.indexOf(NEW_LINE) > -1) {
      lines = parser.getToken().split(NEW_LINE);
    }

    for (int i = 0; i < lines.length; i++) {
      String line = lines[i];// .trim();
      if (lines.length > 1 && line.trim().length() == 0)
        line = "//japid compiler: artificial line to avoid being treated as a terminating line";

      if (startsWithIgnoreSpace(line, "import")) {
        getTemplateClassMetaData().addImportLine(line);
      } else if (startsWithIgnoreSpace(line, "//")) {
        // ignore
      } else if (startsWithIgnoreSpace(line, "extends")) {
        String layout = line.trim().substring("extends".length()).trim();
        // remove quotes if they present

        boolean hasParam = false;
        int p = 0;
        for (; p < layout.length(); p++) {
          char c = layout.charAt(p);
          if (c == ' ' || c == '\t' || c == '(') {
            hasParam = true;
            break;
          }
        }

        if (!hasParam) {
          layout = layout.replace("'", "");
          layout = layout.replace("\"", "");
          layout = removeEndingString(layout, ";");
          layout = removeEndingString(layout, HTML);
          layout = removeEndingString(layout, "/");
          if (layout.startsWith(".")) {
            // new feature allow extends .sub.layout.html
            if (layout.startsWith("./")) {
              layout = getTemplateClassMetaData().packageName + layout.substring(1);
            } else {
              layout = getTemplateClassMetaData().packageName + layout;
            }
          }
          getTemplateClassMetaData().superClass = layout.replace('/', '.');
        } else {
          String layoutName = layout.substring(0, p);
          layoutName = layoutName.replace("'", "");
          layoutName = layoutName.replace("\"", "");
          layoutName = layoutName.replace('/', '.');
          layoutName = removeEndingString(layoutName, HTML);

          try {
            // due to similarity, let's borrow a tag parsing
            Tag tag = new TagInvocationLineParser().parse(layoutName + layout.substring(p));

            if (tag.tagName.startsWith(".")) {
              // partial path, use current package as the root and
              // append the path to it
              tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
            }
            getTemplateClassMetaData().superClass = tag.tagName;
            getTemplateClassMetaData().superClassRenderArgs = tag.args;
          } catch (RuntimeException e) {
            throw new JapidCompilationException(template, parser.getLineNumber(), e.getMessage());
          }
        }
      } else if (startsWithIgnoreSpace(line, "contentType")) {
        // TODO: should also take standard tag name: Content-Type
        String contentType = line.trim().substring("contentType".length()).trim().replace("'", "").replace("\"", "");
        if (contentType.endsWith(";"))
          contentType = contentType.substring(0, contentType.length());
        getTemplateClassMetaData().setContentType(contentType);
      } else if (startsWithIgnoreSpace(line, "setHeader")) {
        String headerkv = line.trim().substring("setHeader".length()).trim();
        String[] split = headerkv.split("[ |\t]");
        if (split.length < 2) {
          throw new JapidCompilationException(template, parser.getLineNumber(), "setHeaader must take a key and a value string");
        }
        String name = split[0];
        String value = headerkv.substring(name.length()).trim();
        getTemplateClassMetaData().setHeader(name, value);
      } else if (startsWithIgnoreSpace(line, ARGS)) {
        String args = line.trim().substring(ARGS.length()).trim();
        templateArgs(args);
      } else if (startsWithIgnoreSpace(line, "trim")) {
        String sw = line.trim().substring("trim".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(sw) || "true".equals(sw)) {
          getTemplateClassMetaData().trimStaticContent();
        }
      } else if (startsWithIgnoreSpace(line, "stopwatch")) {
        String sw = line.trim().substring("stopwatch".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(sw) || "yes".equals(sw) || "true".equals(sw))
          getTemplateClassMetaData().turnOnStopwatch();
      } else if (startsWithIgnoreSpace(line, "tracefile")) {
        String sw = line.trim().substring("tracefile".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(sw) || "yes".equals(sw) || "true".equals(sw))
          getTemplateClassMetaData().turnOnTraceFile();
        else
          getTemplateClassMetaData().turnOffTraceFile();
      } else if (startsWithIgnoreSpace(line, "log") || line.trim().equals("log")) {
        String args = line.trim().substring("log".length()).trim().replace(";", "");
        if (args.trim().length() == 0)
          args = "\"\"";
        String logLine = "System.out.println(\"" + this.template.name.replace('\\', '/') + "(line "
            + (parser.getLineNumber() + i) + "): \" + " + args + ");";
        println(logLine);
      } else if (startsWithIgnoreSpace(line, "invoke")) {
        String args = line.trim().substring("invoke".length()).trim().replace(";", "");
        doActionInvokeDirective(args);
        markLine(parser.getLineNumber() + i);
        println();
      } else if (startsWith(line, "a")) { // `a == `invoke, the a must be
                        // the first char to avoid
                        // collision
        String args = line.substring(2).trim().replace(";", "");
        doActionInvokeDirective(args);
        markLine(parser.getLineNumber() + i);
        println();
      } else if (startsWithIgnoreSpace(line, "suppressNull") || line.trim().equals("suppressNull")) {
        String npe = line.trim().substring("suppressNull".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(npe) || "yes".equals(npe) || "".equals(npe))
          getTemplateClassMetaData().suppressNull();
      } else if (line.trim().equals("abstract")) {
        getTemplateClassMetaData().setAbstract(true);
      } else if (startsWithIgnoreSpace(line, "tag")) {
        String tagline = line.trim().substring(4);
        doTagDirective(tagline);
      } else if (startsWith(line, "t")) { // `t == `tag, the t must be the
                        // first char to avoid collision
        String tagline = line.substring(2);
        doTagDirective(tagline);
      } else if (startsWithIgnoreSpace(line, "each") || startsWithIgnoreSpace(line, "Each")) {
        // support one line type of tag invocation.
        Tag tag = buildTagDirective(line);
        tag.tagName = "Each";
        tag.hasBody = true;
        startTag(tag);
      } else if (startsWithIgnoreSpace(line, "set")) {
        Tag set = buildTagDirective(line);
        if (line.contains(":") || line.contains("="))
          set.hasBody = false;
        else
          set.hasBody = true;
        startTag(set);

        if (!set.hasBody) { // one liner.
          set = popStack();
        }
      } else if (startsWithIgnoreSpace(line, "get")) {
        Tag get = buildTagDirective(line);
        get.hasBody = false;
        startTag(get);
        get = popStack();
      } else if (startsWithIgnoreSpace(line, "def")) {
        // a function definition block
        Tag get = buildTagDirective(line);
        get.hasBody = true;
        startTag(get);
      } else if (startsWithIgnoreSpace(line, INCLUDE)) {
        // the include directive
        // compile the target and include the layout part in the current output flow
        String target = parseInclude(line);
        String src;
        try {
          src = DirUtil.readFileAsString(target);
          JapidTemplate template = new JapidTemplate(target, src);
          JapidAbstractCompiler c = JapidTemplateTransformer.selectCompiler(src);
          c.setUseWithPlay(getTemplateClassMetaData().useWithPlay);
          c.compile(template);
          String jsrc = template.javaSource;
          getTemplateClassMetaData().merge(c.getTemplateClassMetaData());
          println("/* include %s */", target);
          String code = extractRenderingCode(jsrc);
          println(code);
          println("/* end of %s */", target);
        } catch (Exception e) {
          throw new JapidCompilationException(template, parser.getLineNumber() + i,
              "The target of include does not exist: " + target + ". " + e);
        }
      } else if (line.trim().startsWith("noplay")) {
        // template is play independent
        getTemplateClassMetaData().useWithPlay = this.useWithPlay = false;
      } else if (line.trim().equalsIgnoreCase("xml")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.xml.header);
      } else if (line.trim().equalsIgnoreCase("json")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.json.header);
      } else if (line.trim().equalsIgnoreCase("css")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.css.header);
      } else if (line.trim().equalsIgnoreCase("txt") || line.trim().equalsIgnoreCase("text")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.txt.header);
      } else if (line.trim().equalsIgnoreCase("js") || line.trim().equalsIgnoreCase("javascript")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.js.header);
      } else if (line.trim().startsWith("verbatim")) {
        parser.verbatim = true;
        Tag get = buildTagDirective(line);
        get.hasBody = true;
        startTag(get);
      } else if (startsWithIgnoreSpace(line, "if")) {
        // `if expr {, the last { is optional
        String expr = line.trim();
        String clause = expr.substring(2);
        if (JavaSyntaxTool.isIf(clause)) {
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        } else if (JavaSyntaxTool.isOpenIf(clause)) {
          handleOpenIf(i, expr);
        }

        // String expr = line.trim();
        // if (expr.matches(OPEN_IF_PATTERN1)) {
        // handleOpenIf(i, expr);
        // } else {
        // // plain Java if
        // // wait! but may be open due to regex limitation.
        // Matcher m = IF_PATTERN.matcher(expr);
        // if (m.matches()){
        // String condition = m.group(1);
        // if (JavaSyntaxTool.isValidExpr(condition)){
        // // true classic if
        // print(expr);
        // markLine(parser.getLineNumber() + i);
        // println();
        // }
        // else {
        // // is actually open if
        // handleOpenIf(i, expr);
        // }
        // }
        // }
      } else if (line.matches(ELSE_IF_PATTERN_STRING)) {
        // semi open
        String expr = line.trim();
        Matcher matcher = ELSE_IF_PATTERN.matcher(line);
        if (matcher.matches()) {
          expr = matcher.group(1).trim();
          boolean negative = expr.startsWith("!");
          if (negative)
            expr = expr.substring(1).trim();
          String cleanExpr = removeEndingString(expr, "{");
          verifyExpr(cleanExpr);
          expr = "} else if(" + (negative ? "!" : "") + "asBoolean(" + cleanExpr + ")) {";
        }
        print(expr);
        markLine(parser.getLineNumber() + i);
        println();
      } else if (line.matches(OPEN_ELSE_IF_PATTERN_STRING)) {
        // open
        String expr = line.trim();
        Matcher matcher = OPEN_ELSE_IF_PATTERN.matcher(line);
        if (matcher.matches()) {
          expr = matcher.group(1).trim();
          boolean negative = expr.startsWith("!");
          if (negative) {
            handleOpenElseIf(i, expr.substring(1), negative);
          } else {
            if (expr.startsWith("(") && expr.endsWith(")")) {
              // test if the part is classic if
              String ex = expr.substring(1, expr.length() - 1);
              if (JavaSyntaxTool.isValidExpr(ex)) {
                //OK, the insider is a valid expression (better be boolean!)
                // end previous if shadow and star a new one
                Tag tagShadow = tagsStackShadow.peek();
                if (tagShadow instanceof TagIf) {
                  tagsStackShadow.pop();
                  // to close an open if
                  // start a new if
                  Tag.TagIf iftag = new Tag.TagIf(expr, parser.getLineNumber());
                  pushToStack(iftag);
                  expr = "} else if(" + ex + ")) {";
                  print(expr);
                  markLine(parser.getLineNumber() + i);
                  println();
                } else {
                  throw new JapidCompilationException(template, parser.getLineNumber() + i,
                      "the open \"else if\" statement is not properly matched to a previous if");
                }
              } else {
                handleOpenElseIf(i, expr, negative);
              }
            } else {
              handleOpenElseIf(i, expr, negative);
            }
          }
        } else {
          throw new RuntimeException("JapidAbstractCompiler bug: Should never be here!");
        }
      } else if (line.matches(OPEN_ELSE_STRING)) {
        Tag tagShadow = tagsStackShadow.peek();
        if (tagShadow instanceof TagIf) {
          tagsStackShadow.pop();
          // to close an open if
          // start a new if
          print("} else {");
          markLine(parser.getLineNumber() + i);
          println();
          Tag.TagIf iftag = new Tag.TagIf("", parser.getLineNumber());
          pushToStack(iftag);
        } else {
          throw new JapidCompilationException(template, parser.getLineNumber() + i, "the open \"else\" statement is not properly matched to a previous if");
        }
      } else if (line.trim().matches(OPEN_FOR_PATTERN_STRING)) {
        // simply replace it with a "each" tag call
        String expr = line.trim();
        Matcher matcher = OPEN_FOR_PATTERN.matcher(expr);
        if (matcher.matches()) {
          String instanceDecl = matcher.group(1);
          if (!JavaSyntaxTool.isValidSingleVarDecl(instanceDecl))
            throw new JapidCompilationException(template, parser.getLineNumber() + i, "loop variable declaration error: " + instanceDecl);
          instanceDecl = JavaSyntaxTool.cleanDeclPrimitive(instanceDecl);

          String collection = matcher.group(2);
          if (!JavaSyntaxTool.isValidExpr(collection))
            throw new JapidCompilationException(template, parser.getLineNumber() + i, "syntax error: " + collection);

          expr = "each " + collection + " | " + instanceDecl;
          Tag tag = buildTagDirective(expr);
          tag.tagName = "Each";
          tag.hasBody = true;
          startTag(tag);
        } else {
          // should not happen
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        }
      } else if (line.trim().length() == 0) {
        // a single ` empty line, treated as the closing for `tag
        try {
          Tag tagShadow = tagsStackShadow.peek();
          if (tagShadow.isRoot()) {
            // System.out.println("");
          } else {
            tagShadow = tagsStackShadow.pop();

            if (tagShadow instanceof TagIf) {
              // to close an open if
              print("}");
              markLine(parser.getLineNumber() + i);
              println();
            } else {
              if (!tagsStack.empty()) {
                Tag tag = tagsStack.peek();
                if (!tag.isRoot()) {
                  tag = popStack();
                  endTag(tag);
                }
              }
            }
          }
        } catch (Exception e) {
          // should throw it out?
          e.printStackTrace();
        }
      } else {
        // OK plain Java code
        if (line.length() > 0) {
          print(line);
          markLine(parser.getLineNumber() + i);
          println();
        }
      }
    }
    skipLineBreak = true;
  }

  /**
   * @author Bing Ran (bing.ran@gmail.com)
   * @param jsrc
   * @return
   */
  private String extractRenderingCode(String jsrc) {
    String ret = "";
    String[] lines = jsrc.split("\\n");
    boolean shouldUse = false;
    for (String l : lines) {
      String line = l.trim();
      if (line.startsWith(AbstractTemplateClassMetaData.BEGIN_DO_LAYOUT)) {
        shouldUse = true;
        continue;
      }
     
      if (line.startsWith(AbstractTemplateClassMetaData.END_DO_LAYOUT)) {
        break;
      }
     
      if (shouldUse) {
        ret += l + "\n";
      }
    }
    return ret;
  }

  /**
   * @author Bing Ran (bing.ran@gmail.com)
   * @param line
   * @return
   */
  private String parseInclude(String line) {
    line = line.trim();
    if (line.startsWith(INCLUDE + " ") || line.startsWith(INCLUDE + "\t")) {
      line = line.substring(INCLUDE.length()).trim();
    }
    if (line.endsWith("." + INCLUDE)) {
      return line;
    }
    else {
      throw new JapidCompilationException(template, parser.getLineNumber(),
          "the include target must end with .include");

    }
  }

  /**
   * @author Bing Ran (bing.ran@gmail.com)
   * @param string
   * @param target
   */
  private void println(String format, String target) {
    println(String.format(format, target));
  }

  private void handleOpenElseIf(int i, String expr, boolean negative) {
    // expr = expr.substring(1).trim();
    // end previous if shadow and star a new one
    verifyExpr(expr);
    Tag tagShadow = tagsStackShadow.peek();
    if (tagShadow instanceof TagIf) {
      tagsStackShadow.pop();
      // to close an open if
      // start a new if
      Tag.TagIf iftag = new Tag.TagIf(expr, parser.getLineNumber());
      pushToStack(iftag);
      expr = "} else if(" + (negative ? "!" : "") + "asBoolean(" + expr + ")) {";
      print(expr);
      markLine(parser.getLineNumber() + i);
      println();
    } else {
      throw new JapidCompilationException(template, parser.getLineNumber(), "the open \"else if\" statement is not properly matched to a previous if");
    }
  }

  /**
   *
   * @param i
   * @param expr the open if statement: if my_condition...
   */
  private void handleOpenIf(int i, String expr) {
    // get the expression
    String ex = expr.substring(2).trim();
    boolean negative = ex.startsWith("!");
    if (negative)
      ex = ex.substring(1).trim();

    if (!ex.endsWith("{")) {
      // true open. out a shadow tag, since we want reuse the
      // ` as the end delimiter
      Tag.TagIf iftag = new Tag.TagIf(ex, parser.getLineNumber());
      pushToStack(iftag);
    }
    ex = removeEndingString(ex, "{").trim();
    verifyExpr(ex);
    ex = "if(" + (negative ? "!" : "") + "asBoolean(" + ex + ")) {";
    print(ex);
    markLine(parser.getLineNumber() + i);
    println();
  }

  /**
   * @param string
   * @return
   */
  private String removeEndingString(String string, String ending) {
    if (string.endsWith(ending))
      return string.substring(0, string.lastIndexOf(ending));
    else
      return string;
  }

  /**
   * @param line
   * @param string
   * @return
   */
  protected static boolean startsWithIgnoreSpace(String line, String string) {
    line = line.trim();
    return line.startsWith(string + " ") || line.startsWith(string + "\t");
  }

  /**
   * @param line
   * @param string
   * @return
   */
  private boolean startsWith(String line, String string) {
    return line.startsWith(string + " ") || line.startsWith(string + "\t");
  }

  /**
   * @param args
   */
  private void doActionInvokeDirective(String args) {
    if (!getTemplateClassMetaData().useWithPlay) {
      throw new JapidCompilationException(template, parser.getLineNumber(), "action invocation is only supported in Play environment. ");
    } else {
      this.getTemplateClassMetaData().setHasActionInvocation();
      if (args.trim().length() == 0)
        args = "whatyouwantoinvoke()";
      printActionInvocation(args);
    }

  }

  /**
   * @param tagline
   */
  private void doTagDirective(String tagline) {
    Tag tag = buildTagDirective(tagline);
    startTag(tag);
    if (!tag.hasBody) { // one liner.
      tag = tagsStackShadow.pop();
      tag = popStack();
      endTag(tag);
    }
  }

  protected void expr(String token, boolean escape) {
    String expr = token;

    int i = token.indexOf(ELVIS);
    String substitute = null;
    if (i > 0) {
      expr = token.substring(0, i);
      substitute = token.substring(i + ELVIS.length()).trim();
      // if (substitute.startsWith("\""))
      // substitute = substitute.substring(1);
      // if (substitute.endsWith("\""))
      // substitute = substitute.substring(0, substitute.length() - 1);
    }

    verifyExpr(expr);

    if (escape) {
      expr = "escape(" + expr + ")";
      substitute = "escape(" + substitute + ")";
    }

    if (substitute != null) {
      // trap any null or empty string and use the substitute
      printLine("try {" +
          " Object o = " + expr + "; " +
          "if (o.toString().length() ==0) { " +
          "p(" + substitute + "); } " +
          "else { p(o); } } " +
          "catch (NullPointerException npe) { " +
          "p(" + substitute + "); }");
      // printLine("try { Object o = expr; p(" + expr + "); } " +
      // "catch (NullPointerException npe) { " +
      // "p(\"" + substitute + "\"); }");
    } else {
      if (getTemplateClassMetaData().suppressNull)
        printLine("try { p(" + expr + "); } catch (NullPointerException npe) {}");
      else
        printLine("p(" + expr + ");");
    }
  }

  private void verifyExpr(String expr) {
    if (!JavaSyntaxTool.isValidExpr(expr)) {
      throw new JapidCompilationException(template, parser.getLineNumber(), "invalid Java expression: " + expr);
    }
  }

  protected void printLine(String string) {
    print(string);
    markLine();
    println();
  }

  protected void markLine() {
    markLine(parser.getLineNumber());
  }

  protected void message(String token) {
    token = token.trim();
    List<String> args = null;
    try {
      args = JavaSyntaxTool.parseArgs(token);
    } catch (RuntimeException e) {
      throw new JapidCompilationException(
          template,
          parser.getLineNumber(),
          "Message lookup commmand takes arguments like in a Java method call. Don't use single quotation marks to quote a message name for instance. "
              + token);
    }

    String expr = "";
    if (args.size() == 1) {
      expr = decorQuote(args.get(0));
    } else if (args.size() >= 2) {
      expr = decorQuote(args.get(0));
      for (int i = 1; i < args.size(); i++) {
        expr += ", " + args.get(i);
      }
    } else {
      throw new JapidCompilationException(
          template,
          parser.getLineNumber(),
          "Message lookup commmand must take arguments. Bad number of args: "
              + token);
    }

    print(";p(getMessage(" + expr + "));");
    markLine();
    println();
  }

  private String decorQuote(String token) {
    String expr = token.replace('\'', '"');
    if (!expr.startsWith("\""))
      expr = "\"" + expr;
    if (!expr.endsWith("\""))
      expr += "\"";
    return expr;
  }

  /**
   *
   * @param token
   *
   * @param absolute
   */
  protected void action(String token, boolean absolute) {
    String action = token.trim();
    if (action.matches("^'.*'$") || action.matches("^\".*\"$") || action.startsWith("/")) {
      // static content like @{'my.css'}
      action = action.replace('\'', '"');
      if (action.startsWith("/")) {
        action = '"' + action + '"';
      }
      if (absolute) {
        print("p(lookupStaticAbs(" + action + "));");
      } else {
        print("p(lookupStatic(" + action + "));");
      }
    } else {
      if (!action.endsWith(")")) {
        throw new JapidCompilationException(template, parser.getLineNumber(), "action argument must be a method call. It was: " + action);
      }

      try {
        List<String> parseArgs = JavaSyntaxTool.parseArgs(action);
        if (parseArgs.size() != 1) {
          throw new JapidCompilationException(template, parser.getLineNumber(), "action argument must be a method call. It was: " + action);
        }
      } catch (RuntimeException e) {
        throw new JapidCompilationException(template, parser.getLineNumber(), "action argument must be a method call. It was: " + action);
      }

      // extract params if any
      int indexOfParam = action.indexOf("(");
      if (indexOfParam < 1) {
        throw new JapidCompilationException(template, parser.getLineNumber(), "action arguments must be enclosed in parenthesis.");
      }

      String actionPart = action.substring(0, indexOfParam).trim();

      // extract the param list part
      String params = action.substring(indexOfParam + 1);
      params = params.substring(0, params.length() - 1).trim();
      if (params.length() == 0)
        params = "new Object[]{}";

      if (absolute) {
        print("p(lookupAbs(\"" + actionPart + "\", " + params + "));");
      } else {
        print("p(lookup(\"" + actionPart + "\", " + params + "));");
      }
    }
    markLine();
    println();
  }

  protected void hop() {

    String source = template.source;
    Tag rootTag = new Tag() {
      {
        tagName = ROOT_TAGNAME;
        startLine = 0;
        hasBody = true;
      }
    };

    this.tagsStack.push(rootTag);
    this.tagsStackShadow.push(rootTag);

    this.parser = new JapidParser(source);

    getTemplateClassMetaData().setContentType(template.contentTypeHeader);
    getTemplateClassMetaData().packageName = template.packageName;
    getTemplateClassMetaData().setClassName(template.className);

    parse();

    Tag tag = popStack();
    if (!tagsStack.empty())
      throw new JapidCompilationException(template, parser.getLineNumber(), "There is(are) " + tagsStack.size() + " unclosed tag(s) in the template: " + this.template.name);
    // remove print nothing statement to save a few CPU cycles
    this.getTemplateClassMetaData().body = tag.getBodyText().replace("p(\"\")", "").replace("pln(\"\")", "pln()");
    postParsing(tag);
    template.javaSource = this.getTemplateClassMetaData().generateCode();

  }

  /**
   * add anything before the java source generation
   */
  protected void postParsing(Tag tag) {
    this.getTemplateClassMetaData().renderArgs = tag.callbackArgs;
    this.getTemplateClassMetaData().setArgsLineNum(tag.startLine);

  }

  abstract protected AbstractTemplateClassMetaData getTemplateClassMetaData();

  protected void templateArgs(String token) {
    Integer lineNumber = parser.getLineNumber();
    try {
      JavaSyntaxTool.parseParams(token);
    } catch (RuntimeException e) {
      throw new JapidCompilationException(template, lineNumber, e.getMessage());
    }
    Tag currentTag = this.tagsStack.peek();
    currentTag.callbackArgs = token;
    currentTag.startLine = lineNumber;
  }

  /**
   * @return
   */
  protected Tag buildTag(String token) {
    String tagText = token.trim().replaceAll(NEW_LINE, SPACE);

    boolean hasBody = !parser.checkNext().endsWith("/");

    Integer lineNumber = parser.getLineNumber();
    try {
      Tag tag = new TagInvocationLineParser().parse(tagText);
      if (tag.tagName == null || tag.tagName.length() == 0)
        throw new JapidCompilationException(template, lineNumber, "tag name was empty: " + tagText);

      if (tag.tagName.startsWith(".")) {
        // partial path, use current package as the root and append the path
        // to it
        tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
      }
      tag.startLine = lineNumber;
      tag.hasBody = hasBody;
      tag.tagIndex = tagIndex++;
      return tag;
    } catch (RuntimeException e) {
      throw new JapidCompilationException(template, lineNumber, e.getMessage());
    }
  }

  /**
   * e.g.:
   *
   * <pre>
   * `tag myTag a, c |String c
   * </pre>
   *
   * @return
   */
  protected Tag buildTagDirective(String token) {
    String tagText = token.trim();

    try {
      Tag tag = new TagInvocationLineParser().parse(tagText);
      if (tag.tagName == null || tag.tagName.length() == 0)
        throw new JapidCompilationException(template, tag.startLine, "tag name was empty: " + tagText);

      if (tag.tagName.startsWith(".")) {
        // partial path, use current package as the root and append the path
        // to it
        tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
      }
      tag.startLine = parser.getLineNumber();
      tag.tagIndex = tagIndex++;
      return tag;
    } catch (RuntimeException e) {
      if (e instanceof JapidCompilationException)
        throw e;
      else
        throw new JapidCompilationException(template, parser.getLineNumber(), e.getMessage());
    }
  }

  /**
   * @param actionString
   */
  protected String createActionRunner(String actionString) {
    List<String> params = JavaSyntaxTool.parseArgs(actionString);
    String action = params.get(0);
    // remove the argument part to extract action string as key base
    int left = action.indexOf('(');
    if (left < 1) {
      throw new JapidCompilationException(template, parser.getLineNumber(), "invoke: action arguments must be enclosed in parenthesis.");
    }
    int right = action.lastIndexOf(')');
    String actionPath = "\"" + action.substring(0, left) + "\"";
    String args = action.substring(left + 1, right).trim();
    String ttl = "\"\"";

    if (params.size() >= 2) {
      ttl = params.get(1);
      if (params.size() > 2) {
        for (int i = 2; i < params.size(); i++) {
          args += "," + params.get(i);
        }

        if (args.startsWith(","))
          args = args.substring(1);

        if (args.endsWith(","))
          args = args.substring(0, args.length() - 1);
      }
    }
    return createActionRunner(action, ttl, actionPath, args);
  }

  protected void printActionInvocation(String action) {
    try {
      print(createActionRunner(action));
    } catch (RuntimeException e) {
      throw new JapidCompilationException(template, parser.getLineNumber(), "invalid argument syntax to invoke an action: " + action);
    }
  }

  /**
   * @param tag
   */
  protected void regularTagInvoke(Tag tag) {
    if ("extends".equals(tag.tagName)) {
      String layoutName = tag.args;
      layoutName = layoutName.replace("'", "");
      layoutName = layoutName.replace("\"", "");
      layoutName = removeEndingString(layoutName, HTML);
      layoutName = removeEndingString(layoutName, "/");

      getTemplateClassMetaData().superClass = layoutName.replace('/', '.');
    } else if (tag.tagName.equals("invoke")) {
      invokeAction(tag);
    } else {
      // the safest thing to do is to create a new instance of the tag
      // class
      // this however comes at a performance cost

      String tagClassName = tag.tagName;
      if (tagClassName.equals("this")) {
        // call itself
        tagClassName = this.getTemplateClassMetaData().getClassName();
      }

//      String tagVar = tag.getTagVarName();
//      String tagline = "final " + tagClassName + " " + tagVar + " = new " + tagClassName + "(getOut()); ";
//      tagline += tagVar;
      String tagline = "new " + tagClassName + "(" + getTemplateClassMetaData().getClassName() + ".this)" ;

      if (!tag.hasBody) {
//        if (useWithPlay && !tag.tagName.equals(Each.class.getSimpleName())) {
//          tagline += ".setActionRunners(getActionRunners())";
//        }
//        tagline += ".setOut(getOut()); " + tagVar + ".render(" + tag.args + "); "
//            + makeLineMarker(tag.startLine);
        tagline += ".render(" + tag.args + "); " + makeLineMarker(tag.startLine);

        // String tagClassName = tag.tagName;
        // if (tagClassName.equals("this")) {
        // tagClassName = getTemplateClassMetaData().getClassName();
        // }
        // // tagline = "final " + tagClassName + " " + tagVar +
        // " = new " + tagClassName + "(getOut());";
        // // tagline += " " + tagVar + ".render(" + tag.args + ");";
        //
        // if (getTemplateClassMetaData().useWithPlay) {
        // tagline = "((" + tagClassName + ")(new " + tagClassName +
        // "(getOut()).setActionRunners(getActionRunners()))).render(" +
        // tag.args + ");";
        // }
        // else {
        // tagline = "new " + tagClassName + "(getOut()).render(" +
        // tag.args + ");";
        // }

        print(tagline);
      }
    }
  }

  /**
   * @param tag
   */
  protected void invokeAction(Tag tag) {
    if (tag.hasBody) {
      throw new JapidCompilationException(template, parser.getLineNumber(), "invoke tag cannot have a body. Must be ended with /}");
    }

    this.getTemplateClassMetaData().setHasActionInvocation();
    String action = tag.args;
    printActionInvocation(action);
  }

  /**
   * @param tag
   */
  protected void endRegularTag(Tag tag) {
    if (tag.hasBody) {
      InnerClassMeta bodyInner = this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex,
          tag.callbackArgs,
          tag.getBodyText());

      if (bodyInner == null)
        throw new RuntimeException("compiler bug? " + tag.tagName + " not allowed to have instance of this tag");
      String tagVar = tag.getTagVarName();
      String tagClassName = tag.tagName;
      if (tagClassName.equals("this")) {
        // call itself
        tagClassName = this.getTemplateClassMetaData().getClassName();
      }

//      String tagline = "final " + tagClassName + " " + tagVar + " = new " + tagClassName + "(getOut()); "
//          + tagVar;
      String tagline = "new " + tagClassName + "(" + getTemplateClassMetaData().getClassName() + ".this)";

      // make sure the tag always use the current output buffer;
//      if (useWithPlay && !tag.tagName.equals(Each.class.getSimpleName())) {
//        tagline += ".setActionRunners(getActionRunners())";
//      }
//      tagline += ".setOut(getOut()); " + tagVar;
      if (tag.argsNamed()) {
        tagline += ".render( " + makeLineMarker(tag.startLine) + "\n"
            + bodyInner.getAnonymous(makeLineMarker(tag.startLine)) + ", "
            + (WebUtils.asBoolean(tag.args) ? tag.args : "") + ");";
      } else {
        tagline += ".render(" + makeLineMarker(tag.startLine) + "\n"
            + (WebUtils.asBoolean(tag.args) ? tag.args + ", " : "")
            + bodyInner.getAnonymous(makeLineMarker(tag.startLine)) + ");";
      }

      // tagline += makeLineMarker(tag.startLine);
      print(tagline);
    } else {
      // for simple tag call without call back:
      this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex, null, null);
      // the calling statement has been added in the regularTagInvoke()
      // method
    }
    // is inside of a tag of own scope and retract the tag inner body class
    TagInTag def = getTagInTag();
    if (def != null) {
      this.getTemplateClassMetaData().removeLastCallTagBodyInnerClass();
    }
  }

  // this won't work for nested for loops. too bad
  protected void endEach(Tag tag) {
    String line = "new Runnable() {public void run() {\n";
    line += "int _size = -100; int _index = 0; boolean _isOdd = false; String _parity = \"\"; boolean _isFirst = true; Boolean _isLast = _index == _size;\n";
    line += "for (" + tag.callbackArgs + " : " + tag.args + ") { " + makeLineMarker(tag.startLine) + "\n";
    line += "  _index++; _isOdd = !_isOdd; _parity = _isOdd? \"odd\" : \"even\"; _isFirst = _index == 1; if (_size == -100) _size = getCollectionSize("
        + tag.args + "); _isLast = (_size < 0 ? null : _index == _size);\n";
    line += tag.getBodyText() + "\n";
    line += "}\n";
    line += "}}.run();\n";
    print(line);
  }

  private TagInTag getTagInTag() {
    // recursively search the stack for TagInTag
    Tag t;
    try {
      for (int i = tagsStack.size(); i > 0; i--) {
        t = tagsStack.get(i - 1);
        if (t instanceof TagInTag) {
          return (TagInTag) t;
        }
      }
    } catch (Exception e) {
    }
    return null;
  }

  /**
   * define a string returning method from a block
   *
   * @param tag
   */
  protected void def(Tag tag) {
  }

  protected void endDef(Tag tag) {
    if (tag.hasBody) { // must always
      this.getTemplateClassMetaData().addDefTag((TagDef) tag);
    } else {
      throw new JapidCompilationException(template, tag.startLine, "def tag must have a body");
    }
  }

  protected void endTag(Tag tag) {
    String lastInStack = tag.tagName;
    String tagName = lastInStack;
    // if (!lastInStack.equals(tagName)) {
    // throw new JapidCompilationException(template, tag.startLine, "#{" +
    // tag.tagName + "} is not closed.");
    // }
    if (tagName.equals("def")) {
      endDef(tag);
    } else if (tagName.equals("set")) {
      endSet((TagSet) tag);
    } else if (tagName.equals("doBody")) {
    } else if (tagName.equals("extends")) {
    } else if (tagName.equals("get")) {
      // } else if (tagName.equals("set")) { // the set is handled in the
      // JapidTemplateCompiler endTagSpecial()
    } else if (tagName.equals("invoke")) {
    } else if (tagName.equals(Each.class.getSimpleName())) {
      endEach(tag);
    } else if (tagName.equals(DO_LAYOUT)) {
    } else if (endTagSpecial(tag)) {
    } else {
      endRegularTag(tag);
    }
    markLine(tag.startLine);
    println();
    // tagIndex--;
    skipLineBreak = true;
  }

  abstract void endSet(TagSet tag);

  /**
   * sub class can detect special tag and return true to indicate the tag has
   * been processed.
   *
   * @param tag
   * @return
   */
  protected boolean endTagSpecial(Tag tag) {
    return false;
  }

  String createActionRunner(String action, String ttl, String base, String keys) {
    String actionEscaped = action.replace("\"", "\\\"");
    String controllerActionPart = action.substring(0, action.indexOf('('));
    int lastDot = controllerActionPart.lastIndexOf('.');
    String controllerName = controllerActionPart.substring(0, lastDot);
    String actionName = controllerActionPart.substring(lastDot + 1);

    if (ttl == null) {
      // should be deprecated
      String template =
          "    %s.put(getOut().length(), new %s() {\n" +
              "      @Override\n" +
              "      public %s run() {\n" +
              "        try {\n" +
              "          play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation.initActionCall();\n" +
              "          %s;\n" +
              "        } catch (%s jr) {\n" +
              "          return jr.getRenderResult();\n" +
              "        }\n" +
              "        throw new RuntimeException(\"No render result from running: %s\");\n" +
              "      }\n" +
              "    });";
      return String.format(template,
          AbstractTemplateClassMetaData.ACTION_RUNNERS,
          ActionRunner.class.getName(),
          RenderResult.class.getName(),
          action,
          JAPID_RESULT,
          actionEscaped);
    } else {
      String template = "    %s.put(getOut().length(), new %s(%s, %s, %s, %s) {\n"
          + "      @Override\r\n"
          + "      public void runPlayAction() throws %s {\n"
          + "        %s; " + makeLineMarker(parser.getLineNumber()) + "\n"
          + "      }\n"
          + "    }); p(\"\\n\");"; // hack
      // Should really change the action runner collection to <int, List<ActionRunner>>

      // hard-code the cache action runner name to avoid dependency on the
      // Play jar
      return String.format(template,
          AbstractTemplateClassMetaData.ACTION_RUNNERS,
          "cn.bran.play.CacheablePlayActionRunner",
          ttl,
          controllerName + ".class",
          "\"" + actionName + "\"",
          "".equals(keys) ? "\"\"" : keys,
          JAPID_RESULT,
          action);
      // return String.format(template,
      // AbstractTemplateClassMetaData.ACTION_RUNNERS,
      // "cn.bran.play.CacheablePlayActionRunner",
      // ttl,
      // base,
      // "".equals(keys) ? "\"\"" : keys,
      // JAPID_RESULT,
      // controllerName,
      // actionName,
      // action
      // );
    }

  }

  protected int currentLine = 1;

  protected int indentLevel = 0;
  JapidParser.Token state;
  JapidParser.Token previousState;
  JapidParser.Token stateBeforePreviousState;

  public JapidAbstractCompiler() {
    super();
  }

  public void setUseWithPlay(boolean play) {
    this.useWithPlay = play;
  }

  /**
   * @param tag
   */
  protected void pushToStack(Tag tag) {
    // if calling inside a TagInTag tag, put it in the scope
    TagInTag tagtagf = getTagInTag();
    if (tagtagf != null) {
      if (tag instanceof TagInTag) {
        throw new JapidCompilationException(template, tag.startLine, "Syntax error: def/set tag cannot be nested in another def/set tag.");
      }

      if (!(tag instanceof TagIf))
        tagtagf.tags.add(tag);
    }

    tagsStackShadow.push(tag);
    if (!(tag instanceof TagIf))
      tagsStack.push(tag);

  }
}
TOP

Related Classes of cn.bran.japid.compiler.JapidAbstractCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.