text: ﻿/*
  Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved.
  The copyrights embodied in the content of this file are licensed by
  Yahoo! Inc. under the BSD (revised) open source license
  
  @author Dan Vlad Dascalescu <dandv@yahoo-inc.com>
  
  
  Parse function for PHP. Makes use of the tokenizer from tokenizephp.js.
  Based on parsejavascript.js by Marijn Haverbeke.
  
  
  Features:
   + special "deprecated" style for PHP4 keywords like 'var'
   + support for PHP 5.3 keywords: 'namespace', 'use'
   + 911 predefined constants, 1301 predefined functions, 105 predeclared classes
     from a typical PHP installation in a LAMP environment
   + new feature: syntax error flagging, thus enabling strict parsing of:
     + function definitions with explicitly or implicitly typed arguments and default values
     + modifiers (public, static etc.) applied to method and member definitions
     + foreach(array_expression as $key [=> $value]) loops
   + differentiation between single-quoted strings and double-quoted interpolating strings
  
  */
  
  
  // add the Array.indexOf method for JS engines that don't support it (e.g. IE)
  // code from https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array/IndexOf
  if (!Array.prototype.indexOf)
  {
    Array.prototype.indexOf = function(elt /*, from*/)
    {
      var len = this.length;
  
      var from = Number(arguments[1]) || 0;
      from = (from < 0)
           ? Math.ceil(from)
           : Math.floor(from);
      if (from < 0)
        from += len;
  
      for (; from < len; from++)
      {
        if (from in this &&
            this[from] === elt)
          return from;
      }
      return -1;
    };
  }
  
  
  var PHPParser = Editor.Parser = (function() {
    // Token types that can be considered to be atoms, part of operator expressions
    var atomicTypes = {
      "atom": true, "number": true, "variable": true, "string": true
    };
    // Constructor for the lexical context objects.
    function PHPLexical(indented, column, type, align, prev, info) {
      // indentation at start of this line
      this.indented = indented;
      // column at which this scope was opened
      this.column = column;
      // type of scope ('stat' (statement), 'form' (special form), '[', '{', or '(')
      this.type = type;
      // '[', '{', or '(' blocks that have any text after their opening
      // character are said to be 'aligned' -- any lines below are
      // indented all the way to the opening character.
      if (align != null)
        this.align = align;
      // Parent scope, if any.
      this.prev = prev;
      this.info = info;
    }
  
    // PHP indentation rules
    function indentPHP(lexical) {
      return function(firstChars) {
        var firstChar = firstChars && firstChars.charAt(0), type = lexical.type;
        var closing = firstChar == type;
        if (type == "form" && firstChar == "{")
          return lexical.indented;
        else if (type == "stat" || type == "form")
          return lexical.indented + indentUnit;
        else if (lexical.info == "switch" && !closing)
          return lexical.indented + (/^(?:case|default)\b/.test(firstChars) ? indentUnit : 2 * indentUnit);
        else if (lexical.align)
          return lexical.column - (closing ? 1 : 0);
        else
          return lexical.indented + (closing ? 0 : indentUnit);
      };
    }
  
    // The parser-iterator-producing function itself.
    function parsePHP(input, basecolumn) {
      // Wrap the input in a token stream
      var tokens = tokenizePHP(input);
      // The parser state. cc is a stack of actions that have to be
      // performed to finish the current statement. For example we might
      // know that we still need to find a closing parenthesis and a
      // semicolon. Actions at the end of the stack go first. It is
      // initialized with an infinitely looping action that consumes
      // whole statements.
      var cc = [statements];
      // The lexical scope, used mostly for indentation.
      var lexical = new PHPLexical((basecolumn || 0) - indentUnit, 0, "block", false);
      // Current column, and the indentation at the start of the current
      // line. Used to create lexical scope objects.
      var column = 0;
      var indented = 0;
      // Variables which are used by the mark, cont, and pass functions
      // below to communicate with the driver loop in the 'next' function.
      var consume, marked;
  
      // The iterator object.
      var parser = {next: next, copy: copy};
  
      // parsing is accomplished by calling next() repeatedly
      function next(){
        // Start by performing any 'lexical' actions (adjusting the
        // lexical variable), or the operations below will be working
        // with the wrong lexical state.
        while(cc[cc.length - 1].lex)
          cc.pop()();
  
        // Fetch the next token.
        var token = tokens.next();
  
        // Adjust column and indented.
        if (token.type == "whitespace" && column == 0)
          indented = token.value.length;
        column += token.value.length;
        if (token.content == "\n"){
          indented = column = 0;
          // If the lexical scope's align property is still undefined at
          // the end of the line, it is an un-aligned scope.
          if (!("align" in lexical))
            lexical.align = false;
          // Newline tokens get an indentation function associated with
          // them.
          token.indentation = indentPHP(lexical);
        }
        // No more processing for meaningless tokens.
        if (token.type == "whitespace" || token.type == "comment"
          || token.type == "string_not_terminated" )
          return token;
        // When a meaningful token is found and the lexical scope's
        // align is undefined, it is an aligned scope.
        if (!("align" in lexical))
          lexical.align = true;
  
        // Execute actions until one 'consumes' the token and we can
        // return it. 'marked' is used to change the style of the current token.
        while(true) {
          consume = marked = false;
          // Take and execute the topmost action.
          var action = cc.pop();
          action(token);
  
          if (consume){
            if (marked)
              token.style = marked;
            // Here we differentiate between local and global variables.
            return token;
          }
        }
        return 1; // Firebug workaround for http://code.google.com/p/fbug/issues/detail?id=1239#c1
      }
  
      // This makes a copy of the parser state. It stores all the
      // stateful variables in a closure, and returns a function that
      // will restore them when called with a new input stream. Note
      // that the cc array has to be copied, because it is contantly
      // being modified. Lexical objects are not mutated, so they can
      // be shared between runs of the parser.
      function copy(){
        var _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state;
  
        return function copyParser(input){
          lexical = _lexical;
          cc = _cc.concat([]); // copies the array
          column = indented = 0;
          tokens = tokenizePHP(input, _tokenState);
          return parser;
        };
      }
  
      // Helper function for pushing a number of actions onto the cc
      // stack in reverse order.
      function push(fs){
        for (var i = fs.length - 1; i >= 0; i--)
          cc.push(fs[i]);
      }
      // cont and pass are used by the action functions to add other
      // actions to the stack. cont will cause the current token to be
      // consumed, pass will leave it for the next action.
      function cont(){
        push(arguments);
        consume = true;
      }
      function pass(){
        push(arguments);
        consume = false;
      }
      // Used to change the style of the current token.
      function mark(style){
        marked = style;
      }
      // Add a lyer of style to the current token, for example syntax-error
      function mark_add(style){
        marked = marked + ' ' + style;
      }
  
      // Push a new lexical context of the given type.
      function pushlex(type, info) {
        var result = function pushlexing() {
          lexical = new PHPLexical(indented, column, type, null, lexical, info)
        };
        result.lex = true;
        return result;
      }
      // Pop off the current lexical context.
      function poplex(){
        if (lexical.prev)
          lexical = lexical.prev;
      }
      poplex.lex = true;
      // The 'lex' flag on these actions is used by the 'next' function
      // to know they can (and have to) be ran before moving on to the
      // next token.
  
      // Creates an action that discards tokens until it finds one of
      // the given type. This will ignore (and recover from) syntax errors.
      function expect(wanted){
        return function expecting(token){
          if (token.type == wanted) cont();  // consume the token
          else {
            cont(arguments.callee);  // continue expecting() - call itself
          }
        };
      }
  
      // Require a specific token type, or one of the tokens passed in the 'wanted' array
      // Used to detect blatant syntax errors. 'execute' is used to pass extra code
      // to be executed if the token is matched. For example, a '(' match could
      // 'execute' a cont( compasep(funcarg), require(")") )
      function require(wanted, execute){
        return function requiring(token){
          var ok;
          var type = token.type;
          if (typeof(wanted) == "string")
            ok = (type == wanted) -1;
          else
            ok = wanted.indexOf(type);
          if (ok >= 0) {
            if (execute && typeof(execute[ok]) == "function") pass(execute[ok]);
            else cont();
          }
          else {
            if (!marked) mark(token.style);
            mark_add("syntax-error");
            cont(arguments.callee);
          }
        };
      }
  
      // Looks for a statement, and then calls itself.
      function statements(token){
        return pass(statement, statements);
      }
      // Dispatches various types of statements based on the type of the current token.
      function statement(token){
        var type = token.type;
        if (type == "keyword a") cont(pushlex("form"), expression, altsyntax, statement, poplex);
        else if (type == "keyword b") cont(pushlex("form"), altsyntax, statement, poplex);
        else if (type == "{") cont(pushlex("}"), block, poplex);
        else if (type == "function") funcdef();
        // technically, "class implode {...}" is correct, but we'll flag that as an error because it overrides a predefined function
        else if (type == "class") classdef();
        else if (type == "foreach") cont(pushlex("form"), require("("), pushlex(")"), expression, require("as"), require("variable"), /* => $value */ expect(")"), altsyntax, poplex, statement, poplex);
        else if (type == "for") cont(pushlex("form"), require("("), pushlex(")"), expression, require(";"), expression, require(";"), expression, require(")"), altsyntax, poplex, statement, poplex);
        // public final function foo(), protected static $bar;
        else if (type == "modifier") cont(require(["modifier", "variable", "function", "abstract"],
                                                  [null, commasep(require("variable")), funcdef, absfun]));
        else if (type == "abstract") abs();
        else if (type == "switch") cont(pushlex("form"), require("("), expression, require(")"), pushlex("}", "switch"), require([":", "{"]), block, poplex, poplex);
        else if (type == "case") cont(expression, require(":"));
        else if (type == "default") cont(require(":"));
        else if (type == "catch") cont(pushlex("form"), require("("), require("t_string"), require("variable"), require(")"), statement, poplex);
        else if (type == "const") cont(require("t_string"));  // 'const static x=5' is a syntax error
        // technically, "namespace implode {...}" is correct, but we'll flag that as an error because it overrides a predefined function
        else if (type == "namespace") cont(namespacedef, require(";"));
        // $variables may be followed by operators, () for variable function calls, or [] subscripts
        else pass(pushlex("stat"), expression, require(";"), poplex);
      }
      // Dispatch expression types.
      function expression(token){
        var type = token.type;
        if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator);
        else if (type == "<<<") cont(require("string"), maybeoperator);  // heredoc/nowdoc
        else if (type == "t_string") cont(maybe_double_colon, maybeoperator);
        else if (type == "keyword c" || type == "operator") cont(expression);
        // lambda
        else if (type == "function") lambdadef();
        // function call or parenthesized expression: $a = ($b + 1) * 2;
        else if (type == "(") cont(pushlex(")"), commasep(expression), require(")"), poplex, maybeoperator);
      }
      // Called for places where operators, function calls, or subscripts are
      // valid. Will skip on to the next action if none is found.
      function maybeoperator(token){
        var type = token.type;
        if (type == "operator") {
          if (token.content == "?") cont(expression, require(":"), expression);  // ternary operator
          else cont(expression);
        }
        else if (type == "(") cont(pushlex(")"), expression, commasep(expression), require(")"), poplex, maybeoperator /* $varfunc() + 3 */);
        else if (type == "[") cont(pushlex("]"), expression, require("]"), maybeoperator /* for multidimensional arrays, or $func[$i]() */, poplex);
      }
      // A regular use of the double colon to specify a class, as in self::func() or myclass::$var;
      // Differs from `namespace` or `use` in that only one class can be the parent; chains (A::B::$var) are a syntax error.
      function maybe_double_colon(token) {
        if (token.type == "t_double_colon")
          // A::$var, A::func(), A::const
          cont(require(["t_string", "variable"]), maybeoperator);
        else {
          // a t_string wasn't followed by ::, such as in a function call: foo()
          pass(expression)
        }
      }
      // the declaration or definition of a function
      function funcdef() {
        cont(require("t_string"), require("("), pushlex(")"), commasep(funcarg), require(")"), poplex, block);
      }
      // the declaration or definition of a lambda
      function lambdadef() {
        cont(require("("), pushlex(")"), commasep(funcarg), require(")"), maybe_lambda_use, poplex, require("{"), pushlex("}"), block, poplex);
      }
      // optional lambda 'use' statement
      function maybe_lambda_use(token) {
        if(token.type == "namespace") {
          cont(require('('), commasep(funcarg), require(')'));
        }
        else {
          pass(expression);
        }
      }
      // the definition of a class
      function classdef() {
        cont(require("t_string"), expect("{"), pushlex("}"), block, poplex);
      }
      // either funcdef if the current token is "function", or the keyword "function" + funcdef
      function absfun(token) {
        if(token.type == "function") funcdef();
        else cont(require(["function"], [funcdef]));
      }
      // the abstract class or function (with optional modifier)
      function abs(token) {
        cont(require(["modifier", "function", "class"], [absfun, funcdef, classdef]));
      }
      // Parses a comma-separated list of the things that are recognized
      // by the 'what' argument.
      function commasep(what){
        function proceed(token) {
          if (token.type == ",") cont(what, proceed);
        }
        return function commaSeparated() {
          pass(what, proceed);
        };
      }
      // Look for statements until a closing brace is found.
      function block(token) {
        if (token.type == "}") cont();
        else pass(statement, block);
      }
      function empty_parens_if_array(token) {
        if(token.content == "array")
          cont(require("("), require(")"));
      }
      function maybedefaultparameter(token){
        if (token.content == "=") cont(require(["t_string", "string", "number", "atom"], [empty_parens_if_array, null, null]));
      }
      function var_or_reference(token) {
        if(token.type == "variable") cont(maybedefaultparameter);
        else if(token.content == "&") cont(require("variable"), maybedefaultparameter);
      }
      // support for default arguments: http://us.php.net/manual/en/functions.arguments.php#functions.arguments.default
      function funcarg(token){
        // function foo(myclass $obj) {...} or function foo(myclass &objref) {...}
        if (token.type == "t_string") cont(var_or_reference);
        // function foo($var) {...} or function foo(&$ref) {...}
        else var_or_reference(token);
      }
  
      // A namespace definition or use
      function maybe_double_colon_def(token) {
        if (token.type == "t_double_colon")
          cont(namespacedef);
      }
      function namespacedef(token) {
        pass(require("t_string"), maybe_double_colon_def);
      }
  
      function altsyntax(token){
      	if(token.content==':')
      		cont(altsyntaxBlock,poplex);
      }
  
      function altsyntaxBlock(token){
      	if (token.type == "altsyntaxend") cont(require(';'));
        else pass(statement, altsyntaxBlock);
      }
  
  
      return parser;
    }
  
    return {make: parsePHP, electricChars: "{}:"};
  
  })();
  