Package com.google.minijoe.compiler.visitor

Source Code of com.google.minijoe.compiler.visitor.CodeGenerationVisitor$LineNumber

// Copyright 2008 Google Inc.
//
// 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 com.google.minijoe.compiler.visitor;

import com.google.minijoe.compiler.CompilerException;
import com.google.minijoe.compiler.Config;
import com.google.minijoe.compiler.Token;
import com.google.minijoe.compiler.ast.ArrayLiteral;
import com.google.minijoe.compiler.ast.AssignmentExpression;
import com.google.minijoe.compiler.ast.AssignmentOperatorExpression;
import com.google.minijoe.compiler.ast.BinaryOperatorExpression;
import com.google.minijoe.compiler.ast.BlockStatement;
import com.google.minijoe.compiler.ast.BooleanLiteral;
import com.google.minijoe.compiler.ast.BreakStatement;
import com.google.minijoe.compiler.ast.CallExpression;
import com.google.minijoe.compiler.ast.CaseStatement;
import com.google.minijoe.compiler.ast.ConditionalExpression;
import com.google.minijoe.compiler.ast.ContinueStatement;
import com.google.minijoe.compiler.ast.DeleteExpression;
import com.google.minijoe.compiler.ast.DoStatement;
import com.google.minijoe.compiler.ast.EmptyStatement;
import com.google.minijoe.compiler.ast.Expression;
import com.google.minijoe.compiler.ast.ExpressionStatement;
import com.google.minijoe.compiler.ast.ForInStatement;
import com.google.minijoe.compiler.ast.ForStatement;
import com.google.minijoe.compiler.ast.FunctionDeclaration;
import com.google.minijoe.compiler.ast.FunctionLiteral;
import com.google.minijoe.compiler.ast.Identifier;
import com.google.minijoe.compiler.ast.IfStatement;
import com.google.minijoe.compiler.ast.IncrementExpression;
import com.google.minijoe.compiler.ast.LabelledStatement;
import com.google.minijoe.compiler.ast.LogicalAndExpression;
import com.google.minijoe.compiler.ast.LogicalOrExpression;
import com.google.minijoe.compiler.ast.NewExpression;
import com.google.minijoe.compiler.ast.Node;
import com.google.minijoe.compiler.ast.NullLiteral;
import com.google.minijoe.compiler.ast.NumberLiteral;
import com.google.minijoe.compiler.ast.ObjectLiteral;
import com.google.minijoe.compiler.ast.ObjectLiteralProperty;
import com.google.minijoe.compiler.ast.Program;
import com.google.minijoe.compiler.ast.PropertyExpression;
import com.google.minijoe.compiler.ast.ReturnStatement;
import com.google.minijoe.compiler.ast.Statement;
import com.google.minijoe.compiler.ast.StringLiteral;
import com.google.minijoe.compiler.ast.SwitchStatement;
import com.google.minijoe.compiler.ast.ThisLiteral;
import com.google.minijoe.compiler.ast.ThrowStatement;
import com.google.minijoe.compiler.ast.TryStatement;
import com.google.minijoe.compiler.ast.UnaryOperatorExpression;
import com.google.minijoe.compiler.ast.VariableDeclaration;
import com.google.minijoe.compiler.ast.VariableExpression;
import com.google.minijoe.compiler.ast.VariableStatement;
import com.google.minijoe.compiler.ast.WhileStatement;
import com.google.minijoe.compiler.ast.WithStatement;
import com.google.minijoe.sys.JsFunction;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;

/**
* Code generation visitor. This needs two passes, the first pass is used to
* determine the offsets of jump locations, the second pass writes the actual
* byte code.
*
* @author Andy Hayward
* @author Stefan Haustein
*/
public class CodeGenerationVisitor implements Visitor {
  public static final byte BLOCK_COMMENT = (byte) 0x00;
  public static final byte BLOCK_GLOBAL_STRING_TABLE = (byte) 0x10;
  public static final byte BLOCK_NUMBER_LITERALS = (byte) 0x20;
  public static final byte BLOCK_STRING_LITERALS = (byte) 0x30;
  public static final byte BLOCK_REGEX_LITERALS = (byte) 0x40;
  public static final byte BLOCK_FUNCTION_LITERALS = (byte) 0x50;
  public static final byte BLOCK_LOCAL_VARIABLE_NAMES = (byte) 0x60;
  public static final byte BLOCK_CODE = (byte) 0x80;
  public static final byte BLOCK_LINENUMBER = (byte) 0xE0;
  public static final byte BLOCK_DEBUG = (byte) 0xF0;
  public static final byte BLOCK_END = (byte) 0xFF;

  // pop the previous value of the assignment target. should be optimized away
  // in most cases.

  private DataOutputStream dos;
  private ByteArrayOutputStream codeStream = new ByteArrayOutputStream(0);

  private Hashtable globalStringMap = new Hashtable();
  private Vector globalStringTable = new Vector();

  private Vector functionLiterals = new Vector();
  private Vector numberLiterals = new Vector();
  private Vector stringLiterals = new Vector();

  private Hashtable localVariableTable = new Hashtable();

  private Hashtable jumpLabels = new Hashtable();
  private Vector unresolvedJumps = new Vector();
  private Vector labelSet = new Vector();

  private Vector lineNumberVector = new Vector();

  // TODO consider getting rid of this

  private Expression pendingAssignment;

  private Statement currentBreakStatement;
  private Statement currentContinueStatement;
  private Statement currentTryStatement;
  private String currentTryLabel;
 
  private boolean enableLocalsOptimization = false;

  CodeGenerationVisitor parent;

  private class LineNumber {
    private LineNumber(int programCounter, int lineNumber) {
      this.programCounter = programCounter;
      this.lineNumber = lineNumber;
    }

    int programCounter;
    int lineNumber;
  }

  public CodeGenerationVisitor(DataOutputStream stream) {
    this.dos = stream;
    this.globalStringMap = new Hashtable();
    this.globalStringTable = new Vector();
  }

  public CodeGenerationVisitor( CodeGenerationVisitor parent, FunctionLiteral function,
      DataOutputStream dos) throws CompilerException {
    this.parent = parent;
    this.globalStringMap = parent.globalStringMap;
    this.globalStringTable = parent.globalStringTable;
    this.dos = dos;
    this.enableLocalsOptimization = function.enableLocalsOptimization;

    for (int i = 0; i < function.variables.length; i++) {
      Identifier variable = function.variables[i];
      addToGlobalStringTable(variable.string);
      localVariableTable.put(variable, variable);
    }

    for (int i = 0; i < function.variables.length; i++) {
      addToGlobalStringTable(function.variables[i].string);
    }

    for (int i = 0; i < function.functions.length; i++) {
      if (function.functions[i] != null) {
        function.functions[i].visitStatement(this);
      }
    }

    for (int i = 0; i < function.statements.length; i++) {
      if (function.statements[i] != null) {
        function.statements[i].visitStatement(this);
      }
    }

    byte[] byteCode = codeStream.toByteArray();

    // TODO remove this magic numbers.
    int flags = Config.FASTLOCALS && function.enableLocalsOptimization ? 0x01 : 0x00;

    if (function.name != null) {
      writeCommentBlock("function " + function.name.string);
    }

    writeStringLiteralBlock();
    writeNumberLiteralBlock();
    writeFunctionLiteralBlock();
    writeLocalVariableNameBlock(function.variables);
    writeCodeBlock(function.variables.length, function.parameters.length, flags, byteCode);
    writeLineNumberBlock();
    writeEndMarker();
  }

  //
  // utility methods
  //

  private void writeMagic() throws CompilerException {
    try {
      dos.write('M');
      dos.write('i');
      dos.write('n');
      dos.write('i');
      dos.write('J');
      dos.write('o');
      dos.write('e');
      dos.write(0x00); // version
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeCommentBlock(String comment) throws CompilerException {
    try {
      if (comment != null) {
        dos.write(BLOCK_COMMENT);
        dos.writeUTF(comment);
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeGlobalStringTableBlock() throws CompilerException {
    try {
      if (globalStringTable.size() > 0) {
        dos.write(BLOCK_GLOBAL_STRING_TABLE);
        dos.writeShort(globalStringTable.size());
        for (int i = 0; i < globalStringTable.size(); i++) {
          dos.writeUTF((String) globalStringTable.elementAt(i));
        }
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeNumberLiteralBlock() throws CompilerException {
    try {
      if (numberLiterals.size() > 0) {
        dos.write(BLOCK_NUMBER_LITERALS);
        dos.writeShort(numberLiterals.size());
        for (int i = 0; i < numberLiterals.size(); i++) {
          dos.writeDouble(((Double) numberLiterals.elementAt(i)).doubleValue());
        }
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeStringLiteralBlock() throws CompilerException {
    try {
      if (stringLiterals.size() > 0) {
        dos.write(BLOCK_STRING_LITERALS);
        dos.writeShort(stringLiterals.size());
        for (int i = 0; i < stringLiterals.size(); i++) {
          dos.writeShort((short) ((Integer) globalStringMap.get(stringLiterals
              .elementAt(i))).intValue());
        }
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeLocalVariableNameBlock(Identifier[] variables) throws CompilerException {
    try {
      if (variables != null) {
        dos.write(BLOCK_LOCAL_VARIABLE_NAMES);
        dos.writeShort(variables.length);
        for (int i = 0; i < variables.length; i++) {
          dos.writeShort((short) ((Integer) globalStringMap.get(variables[i].string)).intValue());
        }
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeFunctionLiteralBlock() throws CompilerException {
    try {
      if (functionLiterals.size() > 0) {
        dos.write(BLOCK_FUNCTION_LITERALS);
        dos.writeShort(functionLiterals.size());
        for (int i = 0; i < functionLiterals.size(); i++) {
          dos.write((byte[]) functionLiterals.elementAt(i));
        }
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeCodeBlock(int localVariableCount, int paramenterCount,
      int flags, byte[] code) throws CompilerException {
    try {
      dos.write(BLOCK_CODE);
      dos.writeShort(localVariableCount);
      dos.writeShort(paramenterCount);
      dos.write(flags);
      dos.writeShort(code.length);

      for (int i = 0; i < unresolvedJumps.size(); i += 2) {
        String label = (String) unresolvedJumps.elementAt(i);
        int address = ((Integer) unresolvedJumps.elementAt(i + 1)).intValue();
        Integer target = (Integer) jumpLabels.get(label);

        if (target == null) {
          throw new CompilerException("Unresolved Jump Label: " + label);
        }

        int delta = target.intValue() - address - 2;

        code[address + 0] = (byte) (delta >> 8);
        code[address + 1] = (byte) (delta & 255);
      }

      dos.write(code);
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeLineNumberBlock() throws CompilerException {
    try {
      dos.write(BLOCK_LINENUMBER);
      int lineNumberCount = lineNumberVector.size();
      dos.writeShort(lineNumberVector.size());
      for (int i = 0; i < lineNumberCount; i++) {
        LineNumber lineNumber = (LineNumber) lineNumberVector.elementAt(i);
        dos.writeShort(lineNumber.programCounter & 0xffff);
        dos.writeShort(lineNumber.lineNumber & 0xffff);
      }
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeEndMarker() throws CompilerException {
    try {
      dos.write(BLOCK_END);
    } catch (IOException e) {
      throw new CompilerException(e);
    }
  }

  private void writeOp(int op) {
    codeStream.write(op);
  }

  private void writeOpGet(Identifier identifier) {
    int index = identifier.index;

    if (Config.FASTLOCALS && enableLocalsOptimization &&  index >= 0) {
      writeXop(JsFunction.XOP_LCL_GET, index);
    } else {
      writeXop(JsFunction.XOP_PUSH_STR, getStringLiteralIndex(identifier.string));
      writeOp(JsFunction.OP_CTX_GET);
    }
  }

  private void writeOpSet(Identifier identifier) {
    int index = identifier.index;

    if (Config.FASTLOCALS && enableLocalsOptimization &&  index >= 0) {
      writeXop(JsFunction.XOP_LCL_SET, index);
    } else {
      writeXop(JsFunction.XOP_PUSH_STR, getStringLiteralIndex(identifier.string));
      writeOp(JsFunction.OP_CTX_SET);
    }
  }

  void visitWithNewLabelSet(Node node) throws CompilerException {
    Vector saveLabelSet = labelSet;
    labelSet = new Vector();
    if (node instanceof Statement) {
      ((Statement) node).visitStatement(this);
    } else if (node instanceof Expression) {
      ((Expression) node).visitExpression(this);
    }
    labelSet = saveLabelSet;
  }

  /**
   * Write a variable-length operation with an immediate parameter to the given
   * output stream.
   */
  void writeXop(int opcode, int param) {
    if (opcode == JsFunction.XOP_ADD) {
      switch (param) {
        case 1:
          writeOp(JsFunction.OP_INC);
          return;
        case -1:
          writeOp(JsFunction.OP_DEC);
          return;
      }
    }

    if ((param & 0x0ff80) == 0 || (param & 0x0ff80) == 0xff80) {
      codeStream.write(opcode << 1);
      codeStream.write(param);
    } else {
      codeStream.write((opcode << 1) | 1);
      codeStream.write(param >> 8);
      codeStream.write(param & 255);
    }
  }

  void writeJump(int op, Object base, String type) {
    int pos = codeStream.size() + 1;

    if (base instanceof String) {
      type = type + "-" + base;
    } else if (base instanceof Node) {
      type = type + "=" + base.hashCode();
    } else if (base == null) {
      throw new RuntimeException("Invalid position for " + type);
    } else {
      throw new RuntimeException("Illegal Jump base object");
    }

    Integer target = (Integer) jumpLabels.get(type);
    if (jumpLabels.get(type) == null) {
      writeXop(op, 32767);
      unresolvedJumps.addElement(type);
      unresolvedJumps.addElement(new Integer(pos));
    } else {
      // minus one for pc after decoding 8 bit imm
      int delta = target.intValue() - pos - 1;

      if (delta > -127 && delta < 128) {
        codeStream.write(op << 1);
        codeStream.write(delta);
      } else {
        // minus one more for pc after decoding 16 bit imm
        codeStream.write((op << 1) | 1);
        // minus one more for pc after decoding 16 bit imm
        delta -= 1;
        codeStream.write(delta >> 8);
        codeStream.write(delta & 255);
      }
    }
  }

  void setLabel(Node node, String label) {
    Integer pos = new Integer(codeStream.size());
    jumpLabels.put(label + "=" + node.hashCode(), pos);
    for (int i = 0; i < labelSet.size(); i++) {
      jumpLabels.put(label + "-" + labelSet.elementAt(i), pos);
    }
  }

  private void writeBinaryOperator(Token type) {
    if (type == Token.OPERATOR_ASSIGNMENT) {
      // should be handled as special case
      writeOp(JsFunction.OP_DROP);
    } else if (type == Token.OPERATOR_BITWISEAND
        || type == Token.OPERATOR_BITWISEANDASSIGNMENT) {
      writeOp(JsFunction.OP_AND);
    } else if (type == Token.OPERATOR_BITWISEOR
        || type == Token.OPERATOR_BITWISEORASSIGNMENT) {
      writeOp(JsFunction.OP_OR);
    } else if (type == Token.OPERATOR_BITWISEXOR
        || type == Token.OPERATOR_BITWISEXORASSIGNMENT) {
      writeOp(JsFunction.OP_XOR);
    } else if (type == Token.OPERATOR_COMMA) {
      // should be handled as special case in caller to avoid swap
      writeOp(JsFunction.OP_SWAP);
      writeOp(JsFunction.OP_DROP);
    } else if (type == Token.OPERATOR_DIVIDE
        || type == Token.OPERATOR_DIVIDEASSIGNMENT) {
      writeOp(JsFunction.OP_DIV);
    } else if (type == Token.OPERATOR_EQUALEQUAL) {
      writeOp(JsFunction.OP_EQEQ);
    } else if (type == Token.OPERATOR_EQUALEQUALEQUAL) {
      writeOp(JsFunction.OP_EQEQEQ);
    } else if (type == Token.OPERATOR_GREATERTHAN) {
      writeOp(JsFunction.OP_GT);
    } else if (type == Token.OPERATOR_GREATERTHANOREQUAL) {
      writeOp(JsFunction.OP_LT);
      writeOp(JsFunction.OP_NOT);
    } else if (type == Token.OPERATOR_LESSTHAN) {
      writeOp(JsFunction.OP_LT);
    } else if (type == Token.OPERATOR_LESSTHANOREQUAL) {
      writeOp(JsFunction.OP_GT);
      writeOp(JsFunction.OP_NOT);
    } else if (type == Token.OPERATOR_MINUS
        || type == Token.OPERATOR_MINUSASSIGNMENT) {
      writeOp(JsFunction.OP_SUB);
    } else if (type == Token.OPERATOR_MODULO
        || type == Token.OPERATOR_MODULOASSIGNMENT) {
      writeOp(JsFunction.OP_MOD);
    } else if (type == Token.OPERATOR_MULTIPLY
        || type == Token.OPERATOR_MULTIPLYASSIGNMENT) {
      writeOp(JsFunction.OP_MUL);
    } else if (type == Token.OPERATOR_NOTEQUAL) {
      writeOp(JsFunction.OP_EQEQ);
      writeOp(JsFunction.OP_NOT);
    } else if (type == Token.OPERATOR_NOTEQUALEQUAL) {
      writeOp(JsFunction.OP_EQEQEQ);
      writeOp(JsFunction.OP_NOT);
    } else if (type == Token.OPERATOR_PLUS
        || type == Token.OPERATOR_PLUSASSIGNMENT) {
      writeOp(JsFunction.OP_ADD);
    } else if (type == Token.OPERATOR_SHIFTLEFT
        || type == Token.OPERATOR_SHIFTLEFTASSIGNMENT) {
      writeOp(JsFunction.OP_SHL);
    } else if (type == Token.OPERATOR_SHIFTRIGHT
        || type == Token.OPERATOR_SHIFTRIGHTASSIGNMENT) {
      writeOp(JsFunction.OP_SHR);
    } else if (type == Token.OPERATOR_SHIFTRIGHTUNSIGNED
        || type == Token.OPERATOR_SHIFTRIGHTUNSIGNEDASSIGNMENT) {
      writeOp(JsFunction.OP_ASR);
    } else if (type == Token.KEYWORD_IN) {
      writeOp(JsFunction.OP_IN);
    } else if (type == Token.KEYWORD_INSTANCEOF) {
      writeOp(JsFunction.OP_INSTANCEOF);
    } else {
      throw new IllegalArgumentException("Not binary: " + type.toString());
    }
  }

  private void writeUnaryOperator(Token type) {
    if (type == Token.OPERATOR_PLUS) {
      writeXop(JsFunction.XOP_ADD, 0);
    } else if (type == Token.OPERATOR_MINUS) {
      writeOp(JsFunction.OP_NEG);
    } else if (type == Token.OPERATOR_BITWISENOT) {
      writeOp(JsFunction.OP_INV);
    } else if (type == Token.OPERATOR_LOGICALNOT) {
      writeOp(JsFunction.OP_NOT);
    } else if (type == Token.KEYWORD_VOID) {
      writeOp(JsFunction.OP_DROP);
      writeOp(JsFunction.OP_PUSH_UNDEF);
    } else if (type == Token.KEYWORD_TYPEOF) {
      writeOp(JsFunction.OP_TYPEOF);
    } else {
      throw new IllegalArgumentException("Not unary: " + type.toString());
    }
  }

  /** value must be on stack, is kept on stack */
  private void writeVarDef(String name, boolean initialize) {
    if (initialize) {
      writeXop(JsFunction.XOP_PUSH_STR, getStringLiteralIndex(name));
      writeOp(JsFunction.OP_CTX_SET);
    }
  }

  private void addToGlobalStringTable(String s) {
    if (globalStringMap.get(s) == null) {
      globalStringMap.put(s, new Integer(globalStringTable.size()));
      globalStringTable.addElement(s);
    }
  }

  private int getStringLiteralIndex(String string) {
    int i = stringLiterals.indexOf(string);
    if (i == -1) {
      i = stringLiterals.size();
      addToGlobalStringTable(string);
      stringLiterals.addElement(string);
    }
    return i;
  }

  //
  // nodes
  //

  public Program visit(Program program) throws CompilerException {
    for (int i = 0; i < program.functions.length; i++) {
      program.functions[i].visitStatement(this);
    }

    for (int i = 0; i < program.statements.length; i++) {
      program.statements[i].visitStatement(this);
    }

    writeMagic();
    writeGlobalStringTableBlock();
    writeStringLiteralBlock();
    writeNumberLiteralBlock();
    writeFunctionLiteralBlock();
    writeCodeBlock(0, 0, 0x00, codeStream.toByteArray());
    writeLineNumberBlock();
    writeEndMarker();

    return program;
  }

  //
  // statements
  //

  private void addLineNumber(Statement statement) {
    if (Config.LINENUMBER) {
      lineNumberVector.addElement(new LineNumber(codeStream.size(), statement.getLineNumber()));
    }
  }

  public Statement visit(FunctionDeclaration statement) throws CompilerException {
    addLineNumber(statement);

    statement.literal.visitExpression(this);
    writeOp(JsFunction.OP_DROP);
    return statement;
  }

  public Statement visit(BlockStatement statement) throws CompilerException {
    for (int i = 0; i < statement.statements.length; i++) {
      statement.statements[i].visitStatement(this);
    }
    return statement;
  }

  public Statement visit(BreakStatement statement) {
    addLineNumber(statement);

    writeJump(JsFunction.XOP_GO, statement.identifier == null
        ? (Object) currentBreakStatement : statement.identifier.string, "break");
    return statement;
  }

  public Statement visit(CaseStatement statement) {
    throw new RuntimeException("should not be visited");
  }

  public Statement visit(ContinueStatement statement) {
    addLineNumber(statement);

    writeJump(JsFunction.XOP_GO,
        statement.identifier == null
        ? (Object) currentBreakStatement
            : statement.identifier.string,
    "continue");
    return statement;
  }

  public Statement visit(DoStatement statement) throws CompilerException {
    addLineNumber(statement);

    Statement saveBreakStatement = currentBreakStatement;
    Statement saveContinueStatement = currentContinueStatement;
    currentBreakStatement = statement;
    currentContinueStatement = statement;

    setLabel(statement, "do");

    visitWithNewLabelSet(statement.statement);

    setLabel(statement, "continue");
    visitWithNewLabelSet(statement.expression);
    writeOp(JsFunction.OP_NOT);
    writeJump(JsFunction.XOP_IF, statement, "do");
    setLabel(statement, "break");

    currentBreakStatement = saveBreakStatement;
    currentContinueStatement = saveContinueStatement;
    return statement;
  }

  public Statement visit(EmptyStatement statement) {
    return statement;
  }

  public Statement visit(ExpressionStatement statement) throws CompilerException {
    addLineNumber(statement);

    statement.expression.visitExpression(this);
    writeOp(JsFunction.OP_DROP);
    return statement;
  }

  public Statement visit(ForInStatement statement) throws CompilerException {
    addLineNumber(statement);

    Statement saveBreakStatement = currentBreakStatement;
    Statement saveContinueStatement = currentContinueStatement;

    currentBreakStatement = statement;
    currentContinueStatement = statement;

    statement.expression.visitExpression(this);
    writeOp(JsFunction.OP_ENUM);
    setLabel(statement, "continue");
    writeJump(JsFunction.XOP_NEXT, statement, "break");

    if (statement.variable instanceof Identifier) {
      writeXop(JsFunction.XOP_PUSH_STR,
               getStringLiteralIndex(((Identifier) statement.variable).string));
      writeOp(JsFunction.OP_CTX_SET);
    } else if (statement.variable instanceof VariableDeclaration) {
      writeVarDef(((VariableDeclaration) statement.variable).identifier.string, true);
    } else {
      throw new IllegalArgumentException();
    }
    writeOp(JsFunction.OP_DROP);

    statement.statement.visitStatement(this);
    writeJump(JsFunction.XOP_GO, statement, "continue");
    setLabel(statement, "break");
    writeOp(JsFunction.OP_DROP);

    currentBreakStatement = saveBreakStatement;
    currentContinueStatement = saveContinueStatement;

    return statement;
  }

  public Statement visit(ForStatement statement) throws CompilerException {
    addLineNumber(statement);

    if (statement.initial != null) {
      statement.initial.visitExpression(this);
      if (!(statement.initial instanceof VariableExpression)) {
        writeOp(JsFunction.OP_DROP);
      }
    }

    Statement saveBreakStatement = currentBreakStatement;
    Statement saveContinueStatement = currentContinueStatement;

    currentBreakStatement = statement;
    currentContinueStatement = statement;

    setLabel(statement, "start");

    if (statement.condition != null) {
      visitWithNewLabelSet(statement.condition);
      writeJump(JsFunction.XOP_IF, statement, "break");
    }

    if (statement.statement != null) {
      visitWithNewLabelSet(statement.statement);
    }

    setLabel(statement, "continue");

    if (statement.increment != null) {
      visitWithNewLabelSet(statement.increment);
      writeOp(JsFunction.OP_DROP);
    }

    writeJump(JsFunction.XOP_GO, statement, "start");

    setLabel(statement, "break");

    currentBreakStatement = saveBreakStatement;
    currentContinueStatement = saveContinueStatement;

    return statement;
  }

  public Statement visit(IfStatement statement) throws CompilerException {
    addLineNumber(statement);

    statement.expression.visitExpression(this);
    if (statement.falseStatement == null) {
      writeJump(JsFunction.XOP_IF, statement, "endif");
      statement.trueStatement.visitStatement(this);
    } else {
      writeJump(JsFunction.XOP_IF, statement, "else");
      statement.trueStatement.visitStatement(this);
      writeJump(JsFunction.XOP_GO, statement, "endif");
      setLabel(statement, "else");
      statement.falseStatement.visitStatement(this);
    }
    setLabel(statement, "endif");
    return statement;
  }

  public Statement visit(ReturnStatement statement) throws CompilerException {
    addLineNumber(statement);

    if (statement.expression == null) {
      writeOp(JsFunction.OP_PUSH_UNDEF);
    } else {
      statement.expression.visitExpression(this);
    }
    writeOp(JsFunction.OP_RET);
    return statement;
  }

  public Statement visit(SwitchStatement statement) throws CompilerException {
    addLineNumber(statement);

    statement.expression.visitExpression(this);

    Statement saveBreakStatemet = currentBreakStatement;
    currentBreakStatement = statement;

    String defaultLabel = "break";

    for (int i = 0; i < statement.clauses.length; i++) {
      CaseStatement cs = statement.clauses[i];
      if (cs.expression == null) {
        defaultLabel = "case" + i;
      } else {
        writeOp(JsFunction.OP_DUP);
        cs.expression.visitExpression(this);
        writeOp(JsFunction.OP_EQEQEQ);
        writeOp(JsFunction.OP_NOT);
        writeJump(JsFunction.XOP_IF, statement, "case" + i);
      }
    }

    writeOp(JsFunction.OP_DROP);
    writeJump(JsFunction.XOP_GO, statement, defaultLabel);

    for (int i = 0; i < statement.clauses.length; i++) {
      setLabel(statement, "case" + i);
      Statement[] statements = statement.clauses[i].statements;
      for (int j = 0; j < statements.length; j++) {
        statements[j].visitStatement(this);
      }
    }
    setLabel(statement, "break");

    currentBreakStatement = saveBreakStatemet;
    return statement;
  }

  public Statement visit(ThrowStatement statement) throws CompilerException {
    addLineNumber(statement);

    statement.expression.visitExpression(this);
    if (currentTryStatement == null) {
      writeOp(JsFunction.OP_THROW);
    } else {
      writeJump(JsFunction.XOP_GO, currentTryStatement, "catch");
    }
    return statement;
  }

  public Statement visit(TryStatement statement) throws CompilerException {
    addLineNumber(statement);

    Statement saveTryStatement = currentTryStatement;
    String saveTryLabel = currentTryLabel;

    currentTryStatement = statement;
    currentTryLabel = statement.catchBlock != null ? "catch" : "finally";

    statement.tryBlock.visitStatement(this);

    writeJump(JsFunction.XOP_GO, statement, "end");

    if (statement.catchBlock != null) {
      setLabel(statement, "catch");
      if (statement.finallyBlock == null) {
        currentTryLabel = saveTryLabel;
        currentTryStatement = saveTryStatement;
      } else {
        currentTryLabel = "finally";
      }

      // add var and init from stack
      writeVarDef(statement.catchIdentifier.string, true);
      writeOp(JsFunction.OP_DROP);
      statement.catchBlock.visitStatement(this);

      writeJump(JsFunction.XOP_GO, statement, "end");
    }

    // reset everything
    currentTryStatement = saveTryStatement;
    currentTryLabel = saveTryLabel;

    if (statement.finallyBlock != null) {
      // finally block for the case that an exception was thrown --
      // it is kept on the stack and rethrown at the end
      setLabel(statement, "finally");
      statement.finallyBlock.visitStatement(this);

      if (currentTryStatement == null) {
        writeOp(JsFunction.OP_THROW);
      } else {
        writeJump(JsFunction.XOP_GO, currentTryStatement, "catch");
      }
    }

    // finally block if no exception was thrown
    setLabel(statement, "end");

    if (statement.finallyBlock != null) {
      statement.finallyBlock.visitStatement(this);
    }

    return statement;
  }

  public Statement visit(VariableStatement statement) throws CompilerException {
    for (int i = 0; i < statement.declarations.length; i++) {
      statement.declarations[i].visitExpression(this);
    }
    return statement;
  }

  public Statement visit(WhileStatement statement) throws CompilerException {
    addLineNumber(statement);

    Statement saveBreakStatement = currentBreakStatement;
    Statement saveContinueStatement = currentContinueStatement;

    currentBreakStatement = statement;
    currentContinueStatement = statement;

    setLabel(statement, "continue");
    visitWithNewLabelSet(statement.expression);
    writeJump(JsFunction.XOP_IF, statement, "break");

    visitWithNewLabelSet(statement.statement);

    writeJump(JsFunction.XOP_GO, statement, "continue");

    setLabel(statement, "break");

    currentBreakStatement = saveBreakStatement;
    currentContinueStatement = saveContinueStatement;
    return statement;
  }

  public Statement visit(WithStatement statement) throws CompilerException {
    addLineNumber(statement);

    if (currentTryStatement == null) {
      statement.expression.visitExpression(this);
      writeOp(JsFunction.OP_WITH_START);
      statement.statement.visitStatement(this);
      writeOp(JsFunction.OP_WITH_END);
    } else {
      // if an exception is thrown inside the with statement,
      // it is necessary to restore the context
      Statement saveTryStatement = currentTryStatement;
      String saveTryLabel = currentTryLabel;
      currentTryLabel = "finally";
      currentTryStatement = statement;
      statement.expression.visitExpression(this);
      writeOp(JsFunction.OP_WITH_END);
      statement.statement.visitStatement(this);
      writeOp(JsFunction.OP_WITH_END);
      writeJump(JsFunction.XOP_GO, statement, "end");

      currentTryStatement = saveTryStatement;
      currentTryLabel = saveTryLabel;

      setLabel(statement, "finally");
      writeOp(JsFunction.OP_WITH_END);
      writeOp(JsFunction.OP_THROW);
      setLabel(statement, "end");
    }
    return statement;
  }

  //
  // expression
  //

  public Expression visit(Identifier identifier) throws CompilerException {
    Expression pa = pendingAssignment;
    pendingAssignment = null;

    Identifier localVariable = (Identifier) localVariableTable.get(identifier);

    if (localVariable != null) {
      identifier = localVariable;
    }

    if (pa == null) {
      writeOpGet(identifier);
    } else if (pa instanceof AssignmentExpression) {
      ((AssignmentExpression) pa).rightExpression.visitExpression(this);
      writeOpSet(identifier);
    } else if (pa instanceof AssignmentOperatorExpression) {
      writeOpGet(identifier);
      ((AssignmentOperatorExpression) pa).rightExpression.visitExpression(this);
      writeBinaryOperator(((AssignmentOperatorExpression) pa).type);
      writeOpSet(identifier);
    } else if (pa instanceof IncrementExpression) {
      IncrementExpression ie = (IncrementExpression) pa;
      writeOpGet(identifier);
      writeXop(JsFunction.XOP_ADD, ((IncrementExpression) pa).value);
      writeOpSet(identifier);
      if (ie.post) {
        writeXop(JsFunction.XOP_ADD, -((IncrementExpression) pa).value);
      }
    } else if (pa instanceof DeleteExpression) {
      writeOp(JsFunction.OP_CTX);
      writeXop(JsFunction.XOP_PUSH_STR, getStringLiteralIndex(identifier.string));
      writeOp(JsFunction.OP_DEL);
    } else {
      throw new IllegalArgumentException();
    }

    return identifier;
  }

  public Expression visit(BinaryOperatorExpression expression) throws CompilerException {
    expression.leftExpression.visitExpression(this);
    expression.rightExpression.visitExpression(this);
    writeBinaryOperator(expression.operator);
    return expression;
  }

  public Expression visit(UnaryOperatorExpression expression) throws CompilerException {
    expression.subExpression.visitExpression(this);
    writeUnaryOperator(expression.operator);
    return expression;
  }

  public Expression visit(AssignmentExpression expression) throws CompilerException {

    Expression savePendingAssignment = pendingAssignment;
    pendingAssignment = expression;
    expression.leftExpression.visitExpression(this);
    if (pendingAssignment != null) {
      throw new RuntimeException("Pending assignment was not resolved");
    }
    pendingAssignment = savePendingAssignment;
    return expression;
  }

  public Expression visit(AssignmentOperatorExpression expression) throws CompilerException {
    Expression savePendingAssignment = pendingAssignment;
    pendingAssignment = expression;
    expression.leftExpression.visitExpression(this);
    if (pendingAssignment != null) {
      throw new RuntimeException("Pending assignment was not resolved");
    }
    pendingAssignment = savePendingAssignment;
    return expression;
  }

  public Expression visit(CallExpression expression) throws CompilerException {

    if (expression.function instanceof PropertyExpression) {
      PropertyExpression pe = (PropertyExpression) expression.function;
      pe.leftExpression.visitExpression(this);
      writeOp(JsFunction.OP_DUP);
      pe.rightExpression.visitExpression(this);
      writeOp(JsFunction.OP_GET);
    } else {
      writeOp(JsFunction.OP_PUSH_GLOBAL);
      expression.function.visitExpression(this);
    }
    // push arguments
    for (int i = 0; i < expression.arguments.length; i++) {
      expression.arguments[i].visitExpression(this);
    }

    if (currentTryStatement == null) {
      writeXop(JsFunction.XOP_CALL, expression.arguments.length);
    } else {
      writeXop(JsFunction.XOP_TRY_CALL, expression.arguments.length);
      writeJump(JsFunction.XOP_IF, currentTryStatement, currentTryLabel);
    }
    return expression;
  }

  public Expression visit(ConditionalExpression expression) throws CompilerException {
    expression.expression.visitExpression(this);
    writeJump(JsFunction.XOP_IF, expression, "else");
    expression.trueExpression.visitExpression(this);
    writeJump(JsFunction.XOP_GO, expression, "endif");
    setLabel(expression, "else");
    expression.falseExpression.visitExpression(this);
    setLabel(expression, "endif");
    return expression;
  }

  public Expression visit(DeleteExpression expression) throws CompilerException {
    Expression savePendingAssignment = pendingAssignment;
    pendingAssignment = expression;
    expression.subExpression.visitExpression(this);
    if (pendingAssignment != null) {
      throw new RuntimeException("Pending assignment was not resolved");
    }
    pendingAssignment = savePendingAssignment;
    return expression;
  }

  public Expression visit(LogicalAndExpression expression) throws CompilerException {
    expression.leftExpression.visitExpression(this);
    writeOp(JsFunction.OP_DUP);
    // jump (= skip) if false since false && any = false
    writeJump(JsFunction.XOP_IF, expression, "end");
    writeOp(JsFunction.OP_DROP);
    expression.rightExpression.visitExpression(this);
    setLabel(expression, "end");
    return expression;
  }

  public Expression visit(LogicalOrExpression expression) throws CompilerException {
    expression.leftExpression.visitExpression(this);
    writeOp(JsFunction.OP_DUP);
    // jump (= skip) if true since true && any =
    writeOp(JsFunction.OP_NOT);
    writeJump(JsFunction.XOP_IF, expression, "end");
    writeOp(JsFunction.OP_DROP);
    expression.rightExpression.visitExpression(this);
    setLabel(expression, "end");
    return expression;
  }

  public Expression visit(NewExpression expression) throws CompilerException {
    expression.function.visitExpression(this);
    writeOp(JsFunction.OP_NEW);
    if (expression.arguments != null) {
      for (int i = 0; i < expression.arguments.length; i++) {
        expression.arguments[i].visitExpression(this);
      }
      writeXop(JsFunction.XOP_CALL, expression.arguments.length);
    } else {
      writeXop(JsFunction.XOP_CALL, 0);
    }
    writeOp(JsFunction.OP_DROP);
    return expression;
  }

  public Expression visit(IncrementExpression expression) throws CompilerException {
    Expression savePendingAssignment = pendingAssignment;
    pendingAssignment = expression;
    expression.subExpression.visitExpression(this);
    if (pendingAssignment != null) {
      throw new RuntimeException("Pending assignment was not resolved");
    }
    pendingAssignment = savePendingAssignment;
    return expression;
  }

  public Expression visit(PropertyExpression expression) throws CompilerException {
    Expression pa = pendingAssignment;
    pendingAssignment = null;

    if (pa == null) {
      expression.leftExpression.visitExpression(this);
      expression.rightExpression.visitExpression(this);
      writeOp(JsFunction.OP_GET);
    } else if (pa instanceof AssignmentExpression) {
      // push value
      ((AssignmentExpression) pa).rightExpression.visitExpression(this);
      // push object
      expression.leftExpression.visitExpression(this);
      // push property
      expression.rightExpression.visitExpression(this);
      writeOp(JsFunction.OP_SET);
    } else if (pa instanceof AssignmentOperatorExpression) {
      // this case is a bit tricky...
      AssignmentOperatorExpression aoe = (AssignmentOperatorExpression) pa;
      expression.leftExpression.visitExpression(this);
      expression.rightExpression.visitExpression(this);
      // duplicate object and member
      writeOp(JsFunction.OP_DDUP);
      writeOp(JsFunction.OP_GET);
      // push value
      aoe.rightExpression.visitExpression(this);
      // exec assignment op
      writeBinaryOperator(aoe.type);
      // move result value below object and property
      writeOp(JsFunction.OP_ROT);
      writeOp(JsFunction.OP_SET);
    } else if (pa instanceof IncrementExpression) {
      IncrementExpression ie = (IncrementExpression) pa;
      expression.leftExpression.visitExpression(this);
      expression.rightExpression.visitExpression(this);
      // duplicate object and member
      writeOp(JsFunction.OP_DDUP);
      writeOp(JsFunction.OP_GET);
      // increment / decrement
      writeXop(JsFunction.XOP_ADD, ie.value);
      // move result value below object and property
      writeOp(JsFunction.OP_ROT);
      writeOp(JsFunction.OP_SET);
      if (ie.post) {
        writeXop(JsFunction.XOP_ADD, -ie.value);
      }
    } else if (pa instanceof DeleteExpression) {
      expression.leftExpression.visitExpression(this);
      expression.rightExpression.visitExpression(this);
      writeOp(JsFunction.OP_DEL);
    }
    return expression;
  }


  /**
   * Used in for statements only. Does not leave anything on the stack
   * -- in contrast to all other expressions. Handled properly in
   * visit(ForStatement).
   */
  public Expression visit(VariableExpression expression) throws CompilerException {
    for (int i = 0; i < expression.declarations.length; i++) {
      expression.declarations[i].visitExpression(this);
    }
    return expression;
  }

  public Expression visit(VariableDeclaration declaration) throws CompilerException {
    if (declaration.initializer != null) {
      declaration.initializer.visitExpression(this);
      writeVarDef(declaration.identifier.string, true);
      writeOp(JsFunction.OP_DROP);
    } else {
      writeVarDef(declaration.identifier.string, false);
    }
    return declaration;
  }

  //
  // Identifiers and literals
  //

  public Expression visit(ThisLiteral literal) {
    writeOp(JsFunction.OP_PUSH_THIS);
    return literal;
  }

  public Expression visit(NullLiteral literal) {
    writeOp(JsFunction.OP_PUSH_NULL);
    return literal;
  }

  public Expression visit(BooleanLiteral literal) {
    writeOp(literal.value ? JsFunction.OP_PUSH_TRUE
        : JsFunction.OP_PUSH_FALSE);
    return literal;
  }

  public Expression visit(NumberLiteral literal) {
    double v = literal.value;
    if (32767 >= v && v >= -32767 && v == Math.floor(v)) {
      writeXop(JsFunction.XOP_PUSH_INT, (int) v);
    } else {
      Double d = new Double(v);
      int i = numberLiterals.indexOf(d);
      if (i == -1) {
        i = numberLiterals.size();
        numberLiterals.addElement(d);
      }
      writeXop(JsFunction.XOP_PUSH_NUM, i);
    }

    return literal;
  }

  public Expression visit(StringLiteral literal) {
    writeXop(JsFunction.XOP_PUSH_STR, getStringLiteralIndex(literal.string));
    return literal;
  }

  public Expression visit(ArrayLiteral literal) throws CompilerException {
    writeOp(JsFunction.OP_NEW_ARR);
    for (int i = 0; i < literal.elements.length; i++) {
      if (literal.elements[i] == null) {
        writeOp(JsFunction.OP_PUSH_UNDEF);
      } else {
        literal.elements[i].visitExpression(this);
      }
      writeOp(JsFunction.OP_APPEND);
    }
    return literal;
  }

  public Expression visit(FunctionLiteral literal) throws CompilerException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    new CodeGenerationVisitor(this, literal, new DataOutputStream(baos));

    functionLiterals.addElement(baos.toByteArray());

    writeXop(JsFunction.XOP_PUSH_FN, functionLiterals.size() - 1);

    if (literal.name != null) {
      writeVarDef(literal.name.string, true);
    }
    return literal;
  }

  public Expression visit(ObjectLiteral literal) throws CompilerException {
    writeOp(JsFunction.OP_NEW_OBJ);
    for (int i = 0; i < literal.properties.length; i++) {
      literal.properties[i].visitExpression(this);
    }
    return literal;
  }

  public Expression visit(ObjectLiteralProperty property) throws CompilerException {
    property.name.visitExpression(this);
    property.value.visitExpression(this);
    writeOp(JsFunction.OP_SET_KC);

    return property;
  }

  public Statement visit(LabelledStatement statement) throws CompilerException {
    labelSet.addElement(statement.identifier.string);
    statement.statement.visitStatement(this);
    return statement;
  }
}
TOP

Related Classes of com.google.minijoe.compiler.visitor.CodeGenerationVisitor$LineNumber

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.