Source: assembler/assembler.js

if (typeof yasp == 'undefined') yasp = { };

(function () {
  /**
   * Assembler is responsible for generating the bytecode from the assembler
   * @constructor
   */
  yasp.Assembler = function () {
    this.jobs = null;
    this.errors = null; // array containing all the errors that occured while assembling
    this.map = null;
    this.symbols = null;
    this.passes = null;
    
    this.reset();
  };

  /**
   * Resets state of Assembler (does assemble() automatically)
   */
  yasp.Assembler.prototype.reset = function() {
    this.passes = [
      new yasp.Lexer(),
      new yasp.Analyser(),
      new yasp.Parser(),
      new yasp.Generator()
    ];

    this.symbols = {
      labels: { },
      usedRegisters: { },
      defines: { },
      instructions: { }
    };
    this.errors = [ ]; // array containing all the errors that occured while assembling
    this.map = { };
    this.ast = { }; // containing the AST that the generator uses to create the bitcode
    this.jobs = [ ];
  }

  /** Assembles the files
   */
  yasp.Assembler.prototype.assemble = function (params) {
    this.reset();
    
    this.jobs = params.jobs;
    
    try {
      var tmpResult = params.code;
      for (var i = 0; i < this.passes.length; i++) {
        tmpResult = this.passes[i].pass(this, tmpResult);
      }
    } catch (ex) {
      // houston we have a problem!
      if (this.errors.length == 0) {
        // really a problem
        throw ex;
      }
    }
    var result;
    
    if (this.errors.length == 0) {
      result = {
        success: true,
        bitcode: this.jobs.indexOf('bitcode') != -1 ? tmpResult : null,
        symbols: this.jobs.indexOf('symbols') != -1 ? this.symbols : null,
        map: this.jobs.indexOf('map') != -1 ? this.map : null,
        ast: this.jobs.indexOf('ast') != -1 ? this.ast : null
      };
    } else {
      // assembler errors
      var errors = [ ];
      for (var i = 0; i < this.errors.length; i++) {
        var err = this.errors[i];
        errors.push({
          type: err.type,
          name: "E_ERR",
          line: err.token.line,
          char: err.token.char,
          message: err.msg.replace('\n', '↵')
        });
      }
      
      if (this.getFatalErrorCount() == 0) {
        result = {
          success: false,
          errors: errors,
          symbols: this.jobs.indexOf('symbols') != -1 ? this.symbols : null, // no fatal errors, this could still be generated <3
          ast: this.jobs.indexOf('ast') != -1 ? this.ast : null
        };
      } else {
        result = {
          success: false,
          errors: errors
        };
      }
    }
    
    return result;
  };

  /**
   * How many errors are fatal (no AST proper AST could be generated)
   * @returns {number}
   */
  yasp.Assembler.prototype.getFatalErrorCount = function() {
    var count = 0;
    for (var i = 0; i < this.errors.length; i++) {
      if (this.errors[i].type == "error") count++;
    }
    return count;
  }
  
  /** Rises a syntax error
   */
  yasp.Assembler.prototype.riseSyntaxError = function (iterator, msg, type) {
    var token = iterator.current();
    console.log("Syntax error: " + msg + " in line " + token.line + " at character " + token.char);
    this.errors.push({
      token: token,
      msg: msg,
      type: type
    });

    throw msg;
  };

  /**
   * Returns the label with the name
   */
  yasp.Assembler.prototype.getLabel = function(label) {
    return !!this.symbols.labels ? this.symbols.labels[label.toUpperCase()] : null;
  };
  
  /**
   * Creates an iterator that iterates through a token array.
   * It also features some useful methods
   * @param tokens
   * @constructor
   */
  yasp.TokenIterator = function (assembler, tokens) {
    this.tokens = tokens;
    this.pos = 0;
    this.assembler = assembler;
  };

  /**
   * Matches the current token with the specified text, if it fails an error is raised
   * @param text
   * @returns {*}
   */
  yasp.TokenIterator.prototype.match = function (text) {
    if (this.is(text)) {
      return this.next();
    } else {
      this.assembler.riseSyntaxError(this, "Unexpected token " + this.current().toString() + ", expecting '" + text + "'");
    }
  };

  /**
   * Checks whether the current token equals the given text.
   * @param text
   * @returns {boolean}
   */
  yasp.TokenIterator.prototype.is = function (text) {
    return this.current().text == text;
  };

  /**
   * Moves to the next token. If there is none, an error is rised.
   */
  yasp.TokenIterator.prototype.next = function () {
    if (this.hasNext()) {
      this.pos++;
      return this.current();
    } else {
      this.assembler.riseSyntaxError(this, "Unexpected end of file");
    }
  };

  /**
   * If there is a next token => next(), otherwise ignore
   */
  yasp.TokenIterator.prototype.optNext = function() {
    if (this.hasNext()) {
      return this.next();
    } else {
      return this.current();
    }
  }

  /**
   * Returns the current token
   * @returns {*}
   */
  yasp.TokenIterator.prototype.current = function () {
    return this.tokens[this.pos];
  };

  /**
   * Returns whether there is a next token or not
   * @returns {boolean}
   */
  yasp.TokenIterator.prototype.hasNext = function () {
    return this.pos + 1 < this.tokens.length
  };

  /**
   * Restores the TokenIterator to the next consistent state (used for multiple error messages)
   */
  yasp.TokenIterator.prototype.restore = function () {
    // restore state => continue until \n is reached, and then skip the \n
    var hasSkipped = false;
    while (this.hasNext() && !this.is('\n')) {
      this.next();
      hasSkipped = true;
    }
    if (this.hasNext()) this.next();
    while (this.hasNext() && this.is('\n')) this.next();
  };

  /**
   * Wrapper for the yasp.Assembler.riseSyntaxError function
   * @param msg
   */
  yasp.TokenIterator.prototype.riseSyntaxError = function (msg, type) {
    if (typeof type == 'undefined') type = "error";
    this.assembler.riseSyntaxError(this, msg, type);
  };

  /**
   * Iterates through the source code
   * @param func Function that is called at the beginning of each line
   */
  yasp.TokenIterator.prototype.iterate = function(func) {
    // if \n skip!!
    while (this.hasNext() && this.is("\n")) this.next();
    
    while (this.hasNext()) {
      try {
        func();

        if (this.hasNext()) {
          do {
            this.match('\n');
          } while (this.hasNext() && this.is('\n'));
        }
      } catch (ex) {
        if (this.assembler.errors.length > 0) {
          this.restore(); // error occured => try to make state consistent again
        } else {
          throw ex;
        }
      }
    }
  };
})();