Kaynağa Gözat

Initial import

Jani Taskinen 14 yıl önce
işleme
1c23a38026
51 değiştirilmiş dosya ile 5426 ekleme ve 0 silme
  1. 2 0
      CREDITS
  2. 11 0
      Makefile.frag
  3. 64 0
      README
  4. 5 0
      TODO
  5. 73 0
      config.m4
  6. 839 0
      js/json-template.js
  7. 339 0
      js/jstparser.js
  8. 41 0
      package.xml
  9. 73 0
      package2.xml
  10. 36 0
      php_v8js.h
  11. 100 0
      php_v8js_macros.h
  12. 15 0
      samples/dlopen.supp
  13. 74 0
      samples/test_call.php
  14. 32 0
      samples/test_callback.php
  15. 11 0
      samples/test_closure.php
  16. 5 0
      samples/test_crash.php
  17. 9 0
      samples/test_date.php
  18. 59 0
      samples/test_dumper.php
  19. 26 0
      samples/test_exception.php
  20. 34 0
      samples/test_exception2.php
  21. 23 0
      samples/test_extend.php
  22. 9 0
      samples/test_extension.php
  23. 48 0
      samples/test_method.php
  24. 173 0
      test.php
  25. 25 0
      tests/basic.phpt
  26. 48 0
      tests/callbacks.phpt
  27. 21 0
      tests/closures_basic.phpt
  28. 30 0
      tests/closures_dynamic.phpt
  29. 24 0
      tests/construct.phpt
  30. 78 0
      tests/context_preserving.phpt
  31. 38 0
      tests/context_separation.phpt
  32. 68 0
      tests/exception.phpt
  33. 37 0
      tests/exception_propagation_1.phpt
  34. 101 0
      tests/exception_propagation_2.phpt
  35. 38 0
      tests/exception_propagation_3.phpt
  36. 33 0
      tests/execute_flags.phpt
  37. 75 0
      tests/execute_flags_args.phpt
  38. 35 0
      tests/extensions_basic.phpt
  39. 39 0
      tests/extensions_circular_dependency.phpt
  40. 41 0
      tests/get_accessor.phpt
  41. 50 0
      tests/object.phpt
  42. 144 0
      tests/object_method_call.phpt
  43. 82 0
      tests/object_prototype.phpt
  44. 22 0
      tests/object_reuse.phpt
  45. 82 0
      tests/return_value.phpt
  46. 5 0
      tests/skipif.inc
  47. 58 0
      tests/variable_passing.phpt
  48. 1282 0
      v8js.cc
  49. 595 0
      v8js_convert.cc
  50. 183 0
      v8js_methods.cc
  51. 91 0
      v8js_variables.cc

+ 2 - 0
CREDITS

@@ -0,0 +1,2 @@
+v8js
+Jani Taskinen

+ 11 - 0
Makefile.frag

@@ -0,0 +1,11 @@
+
+testv8: all
+	$(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
+
+debugv8: all
+	gdb --arg $(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
+
+valgrindv8: all
+	USE_ZEND_ALLOC=0 valgrind --leak-check=full --show-reachable=yes --track-origins=yes $(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php 2> valgrind.dump
+
+

+ 64 - 0
README

@@ -0,0 +1,64 @@
+V8Js
+====
+
+This is a PHP extension for Google's V8 Javascript engine 
+
+
+Minimum requirements
+--------------------
+
+- V8 library version 2.5.8 <http://code.google.com/p/v8/> (trunk)
+
+- PHP 5.3.3+ (non-ZTS build preferred)
+  Note: V8 engine is not natively thread safe and this extension
+  has not been designed to work around it either yet and might or
+  might not work properly with ZTS enabled PHP. :)
+
+
+API
+===
+
+class V8Js
+{
+  /* Constants */
+
+  const string V8_VERSION;
+  const int FLAG_NONE;
+  const int FLAG_FORCE_ARRAY;
+
+  /* Methods */
+
+  // Initializes and starts V8 engine and Returns new V8Js object with it's own V8 context.
+  public __construct ( [string object_name = "PHP" [, array variables = NULL [, array extensions = NULL [, bool report_uncaught_exceptions = TRUE]]] )
+
+  // Compiles and executes script in object's context with optional identifier string.
+  public mixed V8Js::executeString( string script [, string identifier [, int flags = V8Js::FLAG_NONE]])
+
+  // Returns uncaught pending exception or null if there is no pending exception.
+  public V8JsException V8Js::getPendingException( void )
+
+  /** Static methods **/
+
+  // Registers persistent context independent global Javascript extension.
+  // NOTE! These extensions exist until PHP is shutdown and they need to be registered before V8 is initialized. 
+  // For best performance V8 is initialized only once per process thus this call has to be done before any V8Js objects are created!
+  public static bool V8Js::registerExtension(string ext_name, string script [, array deps [, bool auto_enable = FALSE]])
+
+  // Returns extensions successfully registered with V8Js::registerExtension().
+  public static array V8Js::getExtensions( void )
+}
+
+final class V8JsException extends Exception
+{
+  /* Properties */
+  protected string JsFileName = NULL;
+  protected int JsLineNumber = NULL;
+  protected string JsSourceLine = NULL;
+  protected string JsTrace = NULL;
+
+  /* Methods */
+  final public string getJsFileName( void )
+  final public int getJsLineNumber( void )
+  final public string getJsSourceLine( void )
+  final public string getJsTrace( void )
+}

+ 5 - 0
TODO

@@ -0,0 +1,5 @@
+- Feature: Extension registering from php.ini
+- Feature: Thread safety
+- Missing: Indexed property handlers
+- Bug: exception propagation fails when property getter is set
+- Bug: method_exists() leaks when used with V8 objects

+ 73 - 0
config.m4

@@ -0,0 +1,73 @@
+dnl $Id$
+
+PHP_ARG_WITH(v8js, for V8 Javascript Engine,
+[  --with-v8js           Include V8 JavaScript Engine])
+
+if test "$PHP_V8JS" != "no"; then
+  SEARCH_PATH="/usr/local /usr"
+  SEARCH_FOR="/include/v8.h"
+  
+  if test -r $PHP_V8JS/$SEARCH_FOR; then
+    V8_DIR=$PHP_V8JS
+  else
+    AC_MSG_CHECKING([for V8 files in default path])
+    for i in $SEARCH_PATH ; do
+      if test -r $i/$SEARCH_FOR; then
+        V8_DIR=$i
+        AC_MSG_RESULT(found in $i)
+      fi
+    done
+  fi
+
+  if test -z "$V8_DIR"; then
+    AC_MSG_RESULT([not found])
+    AC_MSG_ERROR([Please reinstall the v8 distribution])
+  fi
+
+  PHP_ADD_INCLUDE($V8_DIR/include)
+  PHP_ADD_LIBRARY_WITH_PATH(v8, $V8_DIR/$PHP_LIBDIR, V8JS_SHARED_LIBADD)
+  PHP_SUBST(V8JS_SHARED_LIBADD)
+  PHP_REQUIRE_CXX()
+
+  AC_CACHE_CHECK(for V8 version, ac_cv_v8_version, [
+old_LIBS=$LIBS
+old_LDFLAGS=$LDFLAGS
+LDFLAGS=-L$V8_DIR/$PHP_LIBDIR
+LIBS=-lv8
+AC_LANG_SAVE
+AC_LANG_CPLUSPLUS
+AC_TRY_RUN([#include <v8.h>
+#include <iostream>
+#include <fstream>
+using namespace std;
+
+int main ()
+{
+	ofstream testfile ("conftestval");
+	if (testfile.is_open()) {
+		testfile << v8::V8::GetVersion();
+		testfile << "\n";
+		testfile.close();
+		return 0;
+	}
+	return 1;
+}], [ac_cv_v8_version=`cat ./conftestval`], [ac_cv_v8_version=NONE], [ac_cv_v8_version=NONE])
+AC_LANG_RESTORE
+LIBS=$old_LIBS
+LDFLAGS=$old_LDFLAGS
+])
+
+  if test "$ac_cv_v8_version" != "NONE"; then
+    ac_IFS=$IFS
+    IFS=.
+    set $ac_cv_v8_version
+    IFS=$ac_IFS
+    V8_API_VERSION=`expr [$]1 \* 1000000 + [$]2 \* 1000 + [$]3`
+    AC_DEFINE_UNQUOTED([PHP_V8_API_VERSION], $V8_API_VERSION, [ ])
+    AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
+  fi
+  
+  PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc, $ext_shared)
+
+  PHP_ADD_MAKEFILE_FRAGMENT
+fi

+ 839 - 0
js/json-template.js

@@ -0,0 +1,839 @@
+// Copyright (C) 2009 Andy Chu
+//
+// 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.
+
+// $Id$
+
+//
+// JavaScript implementation of json-template.
+//
+
+// This is predefined in tests, shouldn't be defined anywhere else.  TODO: Do
+// something nicer.
+var log = log || function() {};
+var repr = repr || function() {};
+
+
+// The "module" exported by this script is called "jsontemplate":
+
+var jsontemplate = function() {
+
+
+// Regex escaping for metacharacters
+function EscapeMeta(meta) {
+  return meta.replace(/([\{\}\(\)\[\]\|\^\$\-\+\?])/g, '\\$1');
+}
+
+var token_re_cache = {};
+
+function _MakeTokenRegex(meta_left, meta_right) {
+  var key = meta_left + meta_right;
+  var regex = token_re_cache[key];
+  if (regex === undefined) {
+    var str = '(' + EscapeMeta(meta_left) + '.*?' + EscapeMeta(meta_right) +
+              '\n?)';
+    regex = new RegExp(str, 'g');
+  }
+  return regex;
+}
+
+//
+// Formatters
+//
+
+function HtmlEscape(s) {
+  return s.replace(/&/g,'&amp;').
+           replace(/>/g,'&gt;').
+           replace(/</g,'&lt;');
+}
+
+function HtmlTagEscape(s) {
+  return s.replace(/&/g,'&amp;').
+           replace(/>/g,'&gt;').
+           replace(/</g,'&lt;').
+           replace(/"/g,'&quot;');
+}
+
+// Default ToString can be changed
+function ToString(s) {
+  if (s === null) {
+    return 'null';
+  }
+  return s.toString();
+}
+
+// Formatter to pluralize words
+function _Pluralize(value, unused_context, args) {
+  var s, p;
+  switch (args.length) {
+    case 0:
+      s = ''; p = 's';
+      break;
+    case 1:
+      s = ''; p = args[0];
+      break;
+    case 2:
+      s = args[0]; p = args[1];
+      break;
+    default:
+      // Should have been checked at compile time
+      throw {
+        name: 'EvaluationError', message: 'pluralize got too many args'
+      };
+  }
+  return (value > 1) ? p : s;
+}
+
+function _Cycle(value, unused_context, args) {
+  // Cycle between various values on consecutive integers.
+  // @index starts from 1, so use 1-based indexing.
+  return args[(value - 1) % args.length];
+}
+
+var DEFAULT_FORMATTERS = {
+  'html': HtmlEscape,
+  'htmltag': HtmlTagEscape,
+  'html-attr-value': HtmlTagEscape,
+  'str': ToString,
+  'raw': function(x) { return x; },
+  'AbsUrl': function(value, context) {
+    // TODO: Normalize leading/trailing slashes
+    return context.get('base-url') + '/' + value;
+  }
+};
+
+var DEFAULT_PREDICATES = {
+  'singular?': function(x) { return  x == 1; },
+  'plural?': function(x) { return x > 1; },
+  'Debug?': function(unused, context) {
+    try {
+      return context.get('debug');
+    } catch(err) {
+      if (err.name == 'UndefinedVariable') {
+        return false;
+      } else {
+        throw err;
+      }
+    }
+  }
+};
+
+var FunctionRegistry = function() {
+  return {
+    lookup: function(user_str) {
+      return [null, null];
+    }
+  };
+};
+
+var SimpleRegistry = function(obj) {
+  return {
+    lookup: function(user_str) {
+      var func = obj[user_str] || null;
+      return [func, null];
+    }
+  };
+};
+
+var CallableRegistry = function(callable) {
+  return {
+    lookup: function(user_str) {
+      var func = callable(user_str);
+      return [func, null];
+    }
+  };
+};
+
+// Default formatters which can't be expressed in DEFAULT_FORMATTERS
+var PrefixRegistry = function(functions) {
+  return {
+    lookup: function(user_str) {
+      for (var i = 0; i < functions.length; i++) {
+        var name = functions[i].name, func = functions[i].func;
+        if (user_str.slice(0, name.length) == name) {
+          // Delimiter is usually a space, but could be something else
+          var args;
+          var splitchar = user_str.charAt(name.length);
+          if (splitchar === '') {
+            args = [];  // No arguments
+          } else {
+            args = user_str.split(splitchar).slice(1);
+          }
+          return [func, args];
+        } 
+      }
+      return [null, null];  // No formatter
+    }
+  };
+};
+
+var ChainedRegistry = function(registries) {
+  return {
+    lookup: function(user_str) {
+      for (var i=0; i<registries.length; i++) {
+        var result = registries[i].lookup(user_str);
+        if (result[0]) {
+          return result;
+        }
+      }
+      return [null, null];  // Nothing found
+    }
+  };
+};
+
+//
+// Template implementation
+//
+
+function _ScopedContext(context, undefined_str) {
+  // The stack contains:
+  //   The current context (an object).
+  //   An iteration index.  -1 means we're NOT iterating.
+  var stack = [{context: context, index: -1}];
+
+  return {
+    PushSection: function(name) {
+      if (name === undefined || name === null) {
+        return null;
+      }
+      var new_context;
+      if (name == '@') {
+        new_context = stack[stack.length-1].context;
+      } else {
+        new_context = stack[stack.length-1].context[name] || null;
+      }
+      stack.push({context: new_context, index: -1});
+      return new_context;
+    },
+
+    Pop: function() {
+      stack.pop();
+    },
+
+    next: function() {
+      var stacktop = stack[stack.length-1];
+
+      // Now we're iterating -- push a new mutable object onto the stack
+      if (stacktop.index == -1) {
+        stacktop = {context: null, index: 0};
+        stack.push(stacktop);
+      }
+
+      // The thing we're iterating over
+      var context_array = stack[stack.length-2].context;
+
+      // We're already done
+      if (stacktop.index == context_array.length) {
+        stack.pop();
+        return undefined;  // sentinel to say that we're done
+      }
+
+      stacktop.context = context_array[stacktop.index++];
+      return true;  // OK, we mutated the stack
+    },
+
+    _Undefined: function(name) {
+      if (undefined_str === undefined) {
+        throw {
+          name: 'UndefinedVariable', message: name + ' is not defined'
+        };
+      } else {
+        return undefined_str;
+      }
+    },
+
+    _LookUpStack: function(name) {
+      var i = stack.length - 1;
+      while (true) {
+        var frame = stack[i];
+        if (name == '@index') {
+          if (frame.index != -1) {  // -1 is undefined
+            return frame.index;
+          }
+        } else {
+          var context = frame.context;
+          if (typeof context === 'object') {
+            var value = context[name];
+            if (value !== undefined) {
+              return value;
+            }
+          }
+        }
+        i--;
+        if (i <= -1) {
+          return this._Undefined(name);
+        }
+      }
+    },
+
+    get: function(name) {
+      if (name == '@') {
+        return stack[stack.length-1].context;
+      }
+      var parts = name.split('.');
+      var value = this._LookUpStack(parts[0]);
+      if (parts.length > 1) {
+        for (var i=1; i<parts.length; i++) {
+          value = value[parts[i]];
+          if (value === undefined) {
+            return this._Undefined(parts[i]);
+          }
+        }
+      }
+      return value;
+    }
+
+  };
+}
+
+
+// Crockford's "functional inheritance" pattern
+
+var _AbstractSection = function(spec) {
+  var that = {};
+  that.current_clause = [];
+
+  that.Append = function(statement) {
+    that.current_clause.push(statement);
+  };
+
+  that.AlternatesWith = function() {
+    throw {
+      name: 'TemplateSyntaxError',
+      message:
+          '{.alternates with} can only appear with in {.repeated section ...}'
+    };
+  };
+
+  that.NewOrClause = function(pred) {
+    throw { name: 'NotImplemented' };  // "Abstract"
+  };
+
+  return that;
+};
+
+var _Section = function(spec) {
+  var that = _AbstractSection(spec);
+  that.statements = {'default': that.current_clause};
+
+  that.section_name = spec.section_name;
+
+  that.Statements = function(clause) {
+    clause = clause || 'default';
+    return that.statements[clause] || [];
+  };
+
+  that.NewOrClause = function(pred) {
+    if (pred) {
+      throw {
+        name: 'TemplateSyntaxError',
+        message: '{.or} clause only takes a predicate inside predicate blocks'
+      };
+    }
+    that.current_clause = [];
+    that.statements['or'] = that.current_clause;
+  };
+
+  return that;
+};
+
+// Repeated section is like section, but it supports {.alternates with}
+var _RepeatedSection = function(spec) {
+  var that = _Section(spec);
+
+  that.AlternatesWith = function() {
+    that.current_clause = [];
+    that.statements['alternate'] = that.current_clause;
+  };
+
+  return that;
+};
+
+// Represents a sequence of predicate clauses.
+var _PredicateSection = function(spec) {
+  var that = _AbstractSection(spec);
+  // Array of func, statements
+  that.clauses = [];
+
+  that.NewOrClause = function(pred) {
+    // {.or} always executes if reached, so use identity func with no args
+    pred = pred || [function(x) { return true; }, null];
+    that.current_clause = [];
+    that.clauses.push([pred, that.current_clause]);
+  };
+
+  return that;
+};
+
+
+function _Execute(statements, context, callback) {
+  for (var i=0; i<statements.length; i++) {
+    var statement = statements[i];
+
+    if (typeof(statement) == 'string') {
+      callback(statement);
+    } else {
+      var func = statement[0];
+      var args = statement[1];
+      func(args, context, callback);
+    }
+  }
+}
+
+function _DoSubstitute(statement, context, callback) {
+  var value;
+  value = context.get(statement.name);
+
+  // Format values
+  for (var i=0; i<statement.formatters.length; i++) {
+    var pair = statement.formatters[i];
+    var formatter = pair[0];
+    var args = pair[1];
+    value = formatter(value, context, args);
+  }
+
+  callback(value);
+}
+
+// for [section foo]
+function _DoSection(args, context, callback) {
+
+  var block = args;
+  var value = context.PushSection(block.section_name);
+  var do_section = false;
+
+  // "truthy" values should have their sections executed.
+  if (value) {
+    do_section = true;
+  }
+  // Except: if the value is a zero-length array (which is "truthy")
+  if (value && value.length === 0) {
+    do_section = false;
+  }
+
+  if (do_section) {
+    _Execute(block.Statements(), context, callback);
+    context.Pop();
+  } else {  // Empty list, None, False, etc.
+    context.Pop();
+    _Execute(block.Statements('or'), context, callback);
+  }
+}
+
+// {.pred1?} A {.or pred2?} B ... {.or} Z {.end}
+function _DoPredicates(args, context, callback) {
+  // Here we execute the first clause that evaluates to true, and then stop.
+  var block = args;
+  var value = context.get('@');
+  for (var i=0; i<block.clauses.length; i++) {
+    var clause = block.clauses[i];
+    var predicate = clause[0][0];
+    var pred_args = clause[0][1];
+    var statements = clause[1];
+
+    var do_clause = predicate(value, context, pred_args);
+    if (do_clause) {
+      _Execute(statements, context, callback);
+      break;
+    }
+  }
+}
+
+
+function _DoRepeatedSection(args, context, callback) {
+  var block = args;
+
+  items = context.PushSection(block.section_name);
+  pushed = true;
+
+  if (items && items.length > 0) {
+    // TODO: check that items is an array; apparently this is hard in JavaScript
+    //if type(items) is not list:
+    //  raise EvaluationError('Expected a list; got %s' % type(items))
+
+    // Execute the statements in the block for every item in the list.
+    // Execute the alternate block on every iteration except the last.  Each
+    // item could be an atom (string, integer, etc.) or a dictionary.
+    
+    var last_index = items.length - 1;
+    var statements = block.Statements();
+    var alt_statements = block.Statements('alternate');
+
+    for (var i=0; context.next() !== undefined; i++) {
+      _Execute(statements, context, callback);
+      if (i != last_index) {
+        _Execute(alt_statements, context, callback);
+      }
+    }
+  } else {
+    _Execute(block.Statements('or'), context, callback);
+  }
+
+  context.Pop();
+}
+
+
+var _SECTION_RE = /(repeated)?\s*(section)\s+(\S+)?/;
+var _OR_RE = /or(?:\s+(.+))?/;
+var _IF_RE = /if(?:\s+(.+))?/;
+
+
+// Turn a object literal, function, or Registry into a Registry
+function MakeRegistry(obj) {
+  if (!obj) {
+    // if null/undefined, use a totally empty FunctionRegistry
+    return new FunctionRegistry();
+  } else if (typeof obj === 'function') {
+    return new CallableRegistry(obj);
+  } else if (obj.lookup !== undefined) {
+    // TODO: Is this a good pattern?  There is a namespace conflict where get
+    // could be either a formatter or a method on a FunctionRegistry.
+    // instanceof might be more robust.
+    return obj;
+  } else if (typeof obj === 'object') {
+    return new SimpleRegistry(obj);
+  }
+}
+
+// TODO: The compile function could be in a different module, in case we want to
+// compile on the server side.
+function _Compile(template_str, options) {
+  var more_formatters = MakeRegistry(options.more_formatters);
+
+  // default formatters with arguments
+  var default_formatters = PrefixRegistry([
+      {name: 'pluralize', func: _Pluralize},
+      {name: 'cycle', func: _Cycle}
+      ]);
+  var all_formatters = new ChainedRegistry([
+      more_formatters,
+      SimpleRegistry(DEFAULT_FORMATTERS),
+      default_formatters
+      ]);
+
+  var more_predicates = MakeRegistry(options.more_predicates);
+
+  // TODO: Add defaults
+  var all_predicates = new ChainedRegistry([
+      more_predicates, SimpleRegistry(DEFAULT_PREDICATES)
+      ]);
+
+  // We want to allow an explicit null value for default_formatter, which means
+  // that an error is raised if no formatter is specified.
+  var default_formatter;
+  if (options.default_formatter === undefined) {
+    default_formatter = 'str';
+  } else {
+    default_formatter = options.default_formatter;
+  }
+
+  function GetFormatter(format_str) {
+    var pair = all_formatters.lookup(format_str);
+    if (!pair[0]) {
+      throw {
+        name: 'BadFormatter',
+        message: format_str + ' is not a valid formatter'
+      };
+    }
+    return pair;
+  }
+
+  function GetPredicate(pred_str) {
+    var pair = all_predicates.lookup(pred_str);
+    if (!pair[0]) {
+      throw {
+        name: 'BadPredicate',
+        message: pred_str + ' is not a valid predicate'
+      };
+    }
+    return pair;
+  }
+
+  var format_char = options.format_char || '|';
+  if (format_char != ':' && format_char != '|') {
+    throw {
+      name: 'ConfigurationError',
+      message: 'Only format characters : and | are accepted'
+    };
+  }
+
+  var meta = options.meta || '{}';
+  var n = meta.length;
+  if (n % 2 == 1) {
+    throw {
+      name: 'ConfigurationError',
+      message: meta + ' has an odd number of metacharacters'
+    };
+  }
+  var meta_left = meta.substring(0, n/2);
+  var meta_right = meta.substring(n/2, n);
+
+  var token_re = _MakeTokenRegex(meta_left, meta_right);
+  var current_block = _Section({});
+  var stack = [current_block];
+
+  var strip_num = meta_left.length;  // assume they're the same length
+
+  var token_match;
+  var last_index = 0;
+
+  while (true) {
+    token_match = token_re.exec(template_str);
+    if (token_match === null) {
+      break;
+    } else {
+      var token = token_match[0];
+    }
+
+    // Add the previous literal to the program
+    if (token_match.index > last_index) {
+      var tok = template_str.slice(last_index, token_match.index);
+      current_block.Append(tok);
+    }
+    last_index = token_re.lastIndex;
+
+    var had_newline = false;
+    if (token.slice(-1) == '\n') {
+      token = token.slice(null, -1);
+      had_newline = true;
+    }
+
+    token = token.slice(strip_num, -strip_num);
+
+    if (token.charAt(0) == '#') {
+      continue;  // comment
+    }
+
+    if (token.charAt(0) == '.') {  // Keyword
+      token = token.substring(1, token.length);
+
+      var literal = {
+          'meta-left': meta_left,
+          'meta-right': meta_right,
+          'space': ' ',
+          'tab': '\t',
+          'newline': '\n'
+          }[token];
+
+      if (literal !== undefined) {
+        current_block.Append(literal);
+        continue;
+      }
+
+      var new_block, func;
+
+      var section_match = token.match(_SECTION_RE);
+      if (section_match) {
+        var repeated = section_match[1];
+        var section_name = section_match[3];
+
+        if (repeated) {
+          func = _DoRepeatedSection;
+          new_block = _RepeatedSection({section_name: section_name});
+        } else {
+          func = _DoSection;
+          new_block = _Section({section_name: section_name});
+        }
+        current_block.Append([func, new_block]);
+        stack.push(new_block);
+        current_block = new_block;
+        continue;
+      }
+
+      var pred_str, pred;
+
+      // Check {.or pred?} before {.pred?}
+      var or_match = token.match(_OR_RE);
+      if (or_match) {
+        pred_str = or_match[1];
+        pred = pred_str ? GetPredicate(pred_str) : null;
+        current_block.NewOrClause(pred);
+        continue;
+      }
+
+      // Match either {.pred?} or {.if pred?}
+      var matched = false;
+
+      var if_match = token.match(_IF_RE);
+      if (if_match) {
+        pred_str = if_match[1];
+        matched = true;
+      } else if (token.charAt(token.length-1) == '?') {
+        pred_str = token;
+        matched = true;
+      }
+      if (matched) {
+        pred = pred_str ? GetPredicate(pred_str) : null;
+        new_block = _PredicateSection();
+        new_block.NewOrClause(pred);
+        current_block.Append([_DoPredicates, new_block]);
+        stack.push(new_block);
+        current_block = new_block;
+        continue;
+      }
+
+      if (token == 'alternates with') {
+        current_block.AlternatesWith();
+        continue;
+      }
+
+      if (token == 'end') {
+        // End the block
+        stack.pop();
+        if (stack.length > 0) {
+          current_block = stack[stack.length-1];
+        } else {
+          throw {
+            name: 'TemplateSyntaxError',
+            message: 'Got too many {end} statements'
+          };
+        }
+        continue;
+      }
+    }
+
+    // A variable substitution
+    var parts = token.split(format_char);
+    var formatters;
+    var name;
+    if (parts.length == 1) {
+      if (default_formatter === null) {
+          throw {
+            name: 'MissingFormatter',
+            message: 'This template requires explicit formatters.'
+          };
+      }
+      // If no formatter is specified, use the default.
+      formatters = [GetFormatter(default_formatter)];
+      name = token;
+    } else {
+      formatters = [];
+      for (var j=1; j<parts.length; j++) {
+        formatters.push(GetFormatter(parts[j]));
+      }
+      name = parts[0];
+    }
+    current_block.Append([_DoSubstitute, {name: name, formatters: formatters}]);
+    if (had_newline) {
+      current_block.Append('\n');
+    }
+  }
+
+  // Add the trailing literal
+  current_block.Append(template_str.slice(last_index));
+
+  if (stack.length !== 1) {
+    throw {
+      name: 'TemplateSyntaxError',
+      message: 'Got too few {end} statements'
+    };
+  }
+  return current_block;
+}
+
+// The Template class is defined in the traditional style so that users can add
+// methods by mutating the prototype attribute.  TODO: Need a good idiom for
+// inheritance without mutating globals.
+
+function Template(template_str, options) {
+
+  // Add 'new' if we were not called with 'new', so prototyping works.
+  if(!(this instanceof Template)) {
+    return new Template(template_str, options);
+  }
+
+  this._options = options || {};
+  this._program = _Compile(template_str, this._options);
+}
+
+Template.prototype.render = function(data_dict, callback) {
+  // options.undefined_str can either be a string or undefined
+  var context = _ScopedContext(data_dict, this._options.undefined_str);
+  _Execute(this._program.Statements(), context, callback);
+};
+
+Template.prototype.expand = function(data_dict) {
+  var tokens = [];
+  this.render(data_dict, function(x) { tokens.push(x); });
+  return tokens.join('');
+};
+
+// fromString is a construction method that allows metadata to be written at the
+// beginning of the template string.  See Python's FromFile for a detailed
+// description of the format.
+//
+// The argument 'options' takes precedence over the options in the template, and
+// can be used for non-serializable options like template formatters.
+
+var OPTION_RE = /^([a-zA-Z\-]+):\s*(.*)/;
+var OPTION_NAMES = [
+    'meta', 'format-char', 'default-formatter', 'undefined-str'];
+// Use this "linear search" instead of Array.indexOf, which is nonstandard
+var OPTION_NAMES_RE = new RegExp(OPTION_NAMES.join('|'));
+
+function fromString(s, options) {
+  var parsed = {};
+  var begin = 0, end = 0;
+
+  while (true) {
+    var parsedOption = false;
+    end = s.indexOf('\n', begin);
+    if (end == -1) {
+      break;
+    }
+    var line = s.slice(begin, end);
+    begin = end+1;
+    var match = line.match(OPTION_RE);
+    if (match !== null) {
+      var name = match[1].toLowerCase(), value = match[2];
+      if (name.match(OPTION_NAMES_RE)) {
+        name = name.replace('-', '_');
+        value = value.replace(/^\s+/, '').replace(/\s+$/, '');
+        if (name == 'default_formatter' && value.toLowerCase() == 'none') {
+          value = null;
+        }
+        parsed[name] = value;
+        parsedOption = true;
+      }
+    }
+    if (!parsedOption) {
+      break;
+    }
+  }
+  // TODO: This doesn't enforce the blank line between options and template, but
+  // that might be more trouble than it's worth
+  if (parsed !== {}) {
+    body = s.slice(begin);
+  } else {
+    body = s;
+  }
+  for (var o in options) {
+    parsed[o] = options[o];
+  }
+  return Template(body, parsed);
+}
+
+
+// We just export one name for now, the Template "class".
+// We need HtmlEscape in the browser tests, so might as well export it.
+
+return {
+    Template: Template, HtmlEscape: HtmlEscape,
+    FunctionRegistry: FunctionRegistry, SimpleRegistry: SimpleRegistry,
+    CallableRegistry: CallableRegistry, ChainedRegistry: ChainedRegistry,
+    fromString: fromString,
+    // Private but exposed for testing
+    _Section: _Section
+    };
+
+}();

+ 339 - 0
js/jstparser.js

@@ -0,0 +1,339 @@
+/*
+Copyright 2008, mark turansky (www.markturansky.com)
+Copyright 2010, Andrew Kelley (superjoesoftware.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. 
+
+(This is the license from www.json.org and I think it's awesome)
+*/
+
+String.prototype.endsWith = function endsWith(c) {
+    if (this.charAt(this.length - 1) == c) {
+        return true;
+    } else {
+        return false;
+    }
+};
+
+String.prototype.startsWith = function startsWith(c) {
+    if (this.charAt(0) == c) {
+        return true;
+    } else {
+        return false;
+    }
+};
+
+RegExp.quote = function(str) {
+    return str.replace(/([.?*+\^$\[\]\\(){}\-])/g, "\\$1");
+};
+
+String.prototype.replaceAll = function replaceAll(a, b) {
+    return this.replace(new RegExp(RegExp.quote(a), 'g'), b);
+};
+
+var Jst = function () {
+    // private variables:
+    var that; // reference to the public object
+    
+    // all write and writeln functions eval'd by Jst
+    // concatenate to this internal variable
+    var html = "";
+
+    // private functions
+    function CharacterStack(str) {
+        var i;
+
+        this.characters = [];
+        this.peek = function peek() {
+            return this.characters[this.characters.length - 1];
+        };
+        this.pop = function pop() {
+            return this.characters.pop();
+        };
+        this.push = function push(c) {
+            this.characters.push(c);
+        };
+        this.hasMore =  function hasMore() {
+            if (this.characters.length > 0) {
+                return true;
+            } else {
+                return false;
+            }
+        };
+
+        for (i = str.length; i >= 0; i -= 1) {
+            this.characters.push(str.charAt(i));
+        }
+    }
+
+    function StringWriter() {
+        this.str = "";
+        this.write = function write(s) {
+            this.str += s;
+        };
+        this.toString = function toString() {
+            return this.str;
+        };
+    }
+
+    function parseScriptlet(stack) {
+        var fragment = new StringWriter();
+        var c; // character
+
+        while (stack.hasMore()) {
+            if (stack.peek() == '%') { //possible end delimiter
+                c = stack.pop();
+                if (stack.peek() == '>') { //end delimiter
+                    // pop > so that it is not available to main parse loop
+                    stack.pop();
+                    if (stack.peek() == '\n') {
+                        fragment.write(stack.pop());
+                    }
+                    break;
+                } else {
+                    fragment.write(c);
+                }
+            } else {
+                fragment.write(stack.pop());
+            }
+        }
+        return fragment.toString();
+    }
+
+    function isOpeningDelimiter(c) {
+        if (c == "<" || c == "%lt;") {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    function isClosingDelimiter(c) {
+        if (c == ">" || c == "%gt;") {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    function appendExpressionFragment(writer, fragment, jstWriter) {
+        var i,j;
+        var c;
+
+        // check to be sure quotes are on both ends of a string literal
+        if (fragment.startsWith("\"") && !fragment.endsWith("\"")) {
+            //some scriptlets end with \n, especially if the script ends the file
+            if (fragment.endsWith("\n") && fragment.charAt(fragment.length - 2) == '"') {
+                //we're ok...
+            } else {
+                throw { "message":"'" + fragment + "' is not properly quoted"};
+            }
+        }
+
+        if (!fragment.startsWith("\"") && fragment.endsWith("\"")) {
+            throw { "message":"'" + fragment + "' is not properly quoted"};
+        }
+
+        // print or println?
+        if (fragment.endsWith("\n")) {
+            writer.write(jstWriter + "ln(");
+            //strip the newline
+            fragment = fragment.substring(0, fragment.length - 1);
+        } else {
+            writer.write(jstWriter + "(");
+        }
+
+        if (fragment.startsWith("\"") && fragment.endsWith("\"")) {
+            //strip the quotes
+            fragment = fragment.substring(1, fragment.length - 1);
+            writer.write("\"");
+            for (i = 0; i < fragment.length; i += 1) {
+                c = fragment.charAt(i);
+                if (c == '"') {
+                    writer.write("\\");
+                    writer.write(c);
+                }
+            }
+            writer.write("\"");
+        } else {
+            for (j = 0; j < fragment.length; j += 1) {
+                writer.write(fragment.charAt(j));
+            }
+        }
+
+        writer.write(");");
+    }
+
+    function appendTextFragment(writer, fragment) {
+        var i;
+        var c;
+
+        if (fragment.endsWith("\n")) {
+            writer.write("writeln(\"");
+        } else {
+            writer.write("write(\"");
+        }
+
+        for (i = 0; i < fragment.length; i += 1) {
+            c = fragment.charAt(i);
+            if (c == '"') {
+                writer.write("\\");
+            }
+            // we took care of the line break with print vs. println
+            if (c != '\n' && c != '\r') {
+                writer.write(c);
+            }
+        }
+
+        writer.write("\");");
+    }
+
+    function parseExpression(stack) {
+        var fragment = new StringWriter();
+        var c;
+
+        while (stack.hasMore()) {
+            if (stack.peek() == '%') { //possible end delimiter
+                c = stack.pop();
+                if (isClosingDelimiter(stack.peek())) { //end delimiter
+                    //pop > so that it is not available to main parse loop
+                    stack.pop();
+                    if (stack.peek() == '\n') {
+                        fragment.write(stack.pop());
+                    }
+                    break;
+                } else {
+                    fragment.write("%");
+                }
+            } else {
+                fragment.write(stack.pop());
+            }
+        }
+
+        return fragment.toString();
+    }
+
+    function parseText(stack) {
+        var fragment = new StringWriter();
+        var c,d;
+
+        while (stack.hasMore()) {
+            if (isOpeningDelimiter(stack.peek())) { //possible delimiter
+                c = stack.pop();
+                if (stack.peek() == '%') { // delimiter!
+                    // push c onto the stack to be used in main parse loop
+                    stack.push(c);
+                    break;
+                } else {
+                    fragment.write(c);
+                }
+            } else {
+                d = stack.pop();
+                fragment.write(d);
+                if (d == '\n') { //done with this fragment.  println it.
+                    break;
+                }
+            }
+        }
+        return fragment.toString();
+    }
+
+    function safeWrite(s) {
+        s = s.toString();
+        s = s.replaceAll('&', '&amp;');
+        s = s.replaceAll('"', '&quot;');
+        s = s.replaceAll('<', '&lt;');
+        s = s.replaceAll('>', '&gt;');
+        html += s;
+    }
+
+    function safeWriteln(s) {
+        safeWrite(s + "\n");
+    }
+
+    function write(s) {
+        html += s;
+    }
+
+    function writeln(s) {
+        write(s + "\n");
+    }
+
+    that = {
+        // public methods:
+        // pre-compile a template for quicker rendering. save the return value and 
+        // pass it to evaluate.
+        compile: function (src) {
+            var stack = new CharacterStack(src);
+            var writer = new StringWriter();
+
+            var c;
+            var fragment;
+            while (stack.hasMore()) {
+                if (isOpeningDelimiter(stack.peek())) { //possible delimiter
+                    c = stack.pop();
+                    if (stack.peek() == '%') { //delimiter!
+                        c = stack.pop();
+                        if (stack.peek() == "=") {
+                            // expression, escape all html
+                            stack.pop();
+                            fragment = parseExpression(stack);
+                            appendExpressionFragment(writer, fragment,
+                                "safeWrite");
+                        } else if (stack.peek() == "+") {
+                            // expression, don't escape html
+                            stack.pop();
+                            fragment = parseExpression(stack);
+                            appendExpressionFragment(writer, fragment,
+                                "write");
+                        } else {
+                            fragment = parseScriptlet(stack);
+                            writer.write(fragment);
+                        }
+                    } else {  //not a delimiter
+                        stack.push(c);
+                        fragment = parseText(stack);
+                        appendTextFragment(writer, fragment);
+                    }
+                } else {
+                    fragment = parseText(stack);
+                    appendTextFragment(writer, fragment);
+                }
+            }
+            return writer.toString();
+        },
+
+        // evaluate a pre-compiled script. recommended approach
+        evaluate: function (script, args) {
+            with(args) {
+                html = "";
+                eval(script);
+                return html;
+            }
+        },
+
+        // if you're lazy, you can use this
+        evaluateSingleShot: function (src, args) {
+            return this.evaluate(this.compile(src), args);
+        }
+    };
+    return that;
+}();

+ 41 - 0
package.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0">
+<package version="1.0" packagerversion="1.9.1">
+ <name>v8js</name>
+ <summary>V8 Javascript Engine for PHP</summary>
+ <description>This extension embeds the Google&apos;s open source JavaScript engine V8 in PHP.
+ </description>
+ <maintainers>
+  <maintainer>
+   <user>jani</user>
+   <name>Jani Taskinen</name>
+   <email>[email protected]</email>
+   <role>lead</role>
+  </maintainer>
+  </maintainers>
+ <release>
+  <version>0.1.0</version>
+  <date>2010-12-02</date>
+  <license>PHP</license>
+  <state>beta</state>
+  <notes>- Initial PECL release
+  </notes>
+  <deps>
+   <dep type="php" rel="ge" version="5.3.3"/>
+  </deps>
+  <configureoptions>
+   <configureoption name="with-v8js" default="autodetect" prompt="Please provide the installation prefix of libv8"/>
+  </configureoptions>
+  <filelist>
+   <file role="src" name="config.m4"/>
+   <file role="doc" name="CREDITS"/>
+   <file role="src" name="Makefile.frag"/>
+   <file role="src" name="php_v8js.h"/>
+   <file role="src" name="php_v8js_macros.h"/>
+   <file role="src" name="v8js.cc"/>
+   <file role="src" name="v8js_convert.cc"/>
+   <file role="src" name="v8js_methods.cc"/>
+   <file role="src" name="v8js_variables.cc"/>
+  </filelist>
+ </release>
+</package>

+ 73 - 0
package2.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.11" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>v8js</name>
+ <channel>pecl.php.net</channel>
+ <summary>V8 Javascript Engine for PHP</summary>
+ <description>
+  This extension embeds the Google's open source JavaScript engine V8 in PHP.
+ </description>
+ <lead>
+  <name>Jani Taskinen</name>
+  <user>jani</user>
+  <email>[email protected]</email>
+  <active>yes</active>
+ </lead>
+ <date>2010-12-12</date>
+ <time>12:12:12</time>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://www.php.net/license">PHP</license>
+ <notes>- Initial PECL release
+ </notes>
+ <contents>
+  <dir name="/">
+   <file name="CREDITS" role="doc" />
+   <file name="config.m4" role="src" />
+   <file name="Makefile.frag" role="src" />
+   <file name="v8js.cc" role="src" />
+   <file name="v8js_convert.cc" role="src" />
+   <file name="v8js_methods.cc" role="src" />
+   <file name="v8js_variables.cc" role="src" />
+   <file name="php_v8js.h" role="src" />
+   <file name="php_v8js_macros.h" role="src" />
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.3.3</min>
+   </php>
+   <pearinstaller>
+    <min>1.4.0</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <providesextension>v8js</providesextension>
+ <extsrcrelease>
+  <configureoption default="autodetect" name="with-v8js" prompt="Please provide the installation prefix of libv8" />
+ </extsrcrelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.1.0</release>
+    <api>0.1.0</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2010-12-12</date>
+   <notes>- Initial PECL release.
+   </notes>
+  </release>
+ </changelog>
+</package>

+ 36 - 0
php_v8js.h

@@ -0,0 +1,36 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
+
+#ifndef PHP_V8JS_H
+#define PHP_V8JS_H
+
+extern zend_module_entry v8js_module_entry;
+#define phpext_v8js_ptr &v8js_module_entry
+
+#endif	/* PHP_V8JS_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 100 - 0
php_v8js_macros.h

@@ -0,0 +1,100 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
+
+#ifndef PHP_V8JS_MACROS_H
+#define PHP_V8JS_MACROS_H
+
+#include <v8.h>
+
+/* V8Js Version */
+#define V8JS_VERSION "0.1.0"
+
+/* Helper macros */
+#define V8JS_SYM(v)			v8::String::NewSymbol(v, sizeof(v) - 1)
+#define V8JS_SYML(v, l)		v8::String::NewSymbol(v, l)
+#define V8JS_STR(v)			v8::String::New(v)
+#define V8JS_STRL(v, l)		v8::String::New(v, l)
+#define V8JS_INT(v)			v8::Integer::New(v)
+#define V8JS_FLOAT(v)		v8::Number::New(v)
+#define V8JS_BOOL(v)		v8::Boolean::New(v)
+#define V8JS_NULL			v8::Null()
+#define V8JS_MN(name)		v8js_method_##name
+#define V8JS_METHOD(name)	v8::Handle<v8::Value> V8JS_MN(name)(const v8::Arguments& args)
+#define V8JS_THROW(type, message, message_len)	v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len)))
+#define V8JS_GLOBAL			v8::Context::GetCurrent()->Global()
+
+/* Helper macros */
+#if PHP_V8_API_VERSION < 2005009
+# define V8JS_GET_CLASS_NAME(var, obj) \
+	/* Hack to prevent calling possibly set user-defined __toString() messing class name */ \
+	v8::Local<v8::Function> constructor = v8::Local<v8::Function>::Cast(obj->Get(V8JS_SYM("constructor"))); \
+	v8::String::Utf8Value var(constructor->GetName());
+#else
+# define V8JS_GET_CLASS_NAME(var, obj) \
+	v8::String::Utf8Value var(obj->GetConstructorName());
+#endif
+
+/* Global flags */
+#define V8JS_GLOBAL_SET_FLAGS(flags)	V8JS_GLOBAL->SetHiddenValue(V8JS_SYM("__php_flags__"), V8JS_INT(flags))
+#define V8JS_GLOBAL_GET_FLAGS()			V8JS_GLOBAL->GetHiddenValue(V8JS_SYM("__php_flags__"))->IntegerValue();
+
+/* Options */
+#define V8JS_FLAG_NONE			(1<<0)
+#define V8JS_FLAG_FORCE_ARRAY	(1<<1)
+
+/* Extracts a C string from a V8 Utf8Value. */
+static const char * ToCString(const v8::String::Utf8Value &value) /* {{{ */
+{
+	return *value ? *value : "<string conversion failed>";
+}
+/* }}} */
+
+/* Extern Class entries */
+extern zend_class_entry *php_ce_v8_object;
+extern zend_class_entry *php_ce_v8_function;
+
+/* Create PHP V8 object */
+void php_v8js_create_v8(zval *, v8::Handle<v8::Value>, int TSRMLS_DC);
+
+/* Fetch V8 object properties */
+int php_v8js_v8_get_properties_hash(v8::Handle<v8::Value>, HashTable *, int TSRMLS_DC);
+
+/* Convert zval into V8 value */
+v8::Handle<v8::Value> zval_to_v8js(zval * TSRMLS_DC);
+
+/* Convert V8 value into zval */
+int v8js_to_zval(v8::Handle<v8::Value>, zval *, int TSRMLS_DC);
+
+/* Register builtin methods into passed object */
+void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate>);
+
+/* Register accessors into passed object */
+void php_v8js_register_accessors(v8::Local<v8::ObjectTemplate>, zval * TSRMLS_DC);
+
+#endif	/* PHP_V8JS_MACROS_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 15 - 0
samples/dlopen.supp

@@ -0,0 +1,15 @@
+{
+   Ignore dlopen bug #1.
+   Memcheck:Leak
+   ...
+   fun:_dl_open
+   ...
+}
+
+{
+   Ignore dlopen bug #2.
+   Memcheck:Leak
+   ...
+   fun:_dl_close
+   ...
+}

+ 74 - 0
samples/test_call.php

@@ -0,0 +1,74 @@
+<?php
+
+class Foo {
+	var $bar = 'foobar';
+
+	function MyOwnFunc() {}
+
+	function __Get($name) {
+		echo "Called __get(): ";
+		var_dump($name);
+	}
+	function __Set($name, $args) {
+		echo "Called __set(): ";
+		var_dump($name, $args);
+	}
+	function __Isset($name) {
+		echo "Called __isset(): ";
+		var_dump($name);
+		return true;
+	}
+	function __call($name, $args) {
+		echo "Called __call(): ";
+		var_dump($name, $args);
+	}
+	function __Invoke($name, $arg1, $arg2) {
+		echo "Called __invoke(): ";
+		var_dump(func_get_args());
+		return 'foobar';
+	}
+	function __toString() {
+		echo "Called __tostring: ";
+		return $this->bar;
+	}
+}
+
+$blaa = new V8Js();
+$blaa->obj = new Foo;
+
+try {
+  echo "__invoke()\n";
+  $blaa->executeString("var_dump(PHP.obj('arg1','arg2','arg3'));", "invoke_test1 #1.js");
+  echo "------------\n";
+
+  echo " __invoke() with new\n";
+  $blaa->executeString("myobj = new PHP.obj('arg1','arg2','arg3'); var_dump(myobj);", "invoke_test2 #2.js");
+  echo "------------\n";
+
+  echo " __tostring()\n";
+  $blaa->executeString('print(PHP.obj + "\n");', "tostring_test #3.js");
+  echo "------------\n";
+
+  echo " __isset() not called with existing property\n";
+  $blaa->executeString('if ("bar" in PHP.obj) print("bar exists\n");', "isset_test1 #4.js");
+  echo "------------\n";
+
+  echo " __isset() with non-existing property\n";
+  $blaa->executeString('if (!("foobar" in PHP.obj)) print("foobar does not exist\n"); else print("We called __isset and it said yes!\n");', "isset_test2 #5.js");
+  echo "------------\n";
+
+  echo " __get() not called with existing property\n";
+  $blaa->executeString('var_dump(PHP.obj.bar);', "get_test1 #6.js");
+  echo "------------\n";
+
+  echo " __get() with non-existing property\n";
+  $blaa->executeString('var_dump(PHP.obj.foo);', "get_test2 #7.js");
+  echo "------------\n";
+
+  echo " __call()\n";
+  $blaa->executeString('PHP.obj.foo(1,2,3);', "call_test1 #8.js");
+  echo "------------\n";
+
+} catch (V8JsException $e) {
+  echo $e->getMessage(), "\n";
+}

+ 32 - 0
samples/test_callback.php

@@ -0,0 +1,32 @@
+<?php
+	
+$a = new V8Js();
+
+// Should not work with closure
+$a->test = function ($params) { return (method_exists($params, 'cb1')) ? $params->cb1("hello") : false; };
+$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
+var_dump(__LINE__, $ret);
+
+// Test is_a()
+$a->test = function ($params) { return (is_a($params, 'V8Object')) ? $params->cb1("hello") : false; };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+// Test is_a()
+$a->test = function ($params) { return (is_a($params, 'V8Function')) ? $params("hello") : false; };
+$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
+var_dump(__LINE__, $ret);
+
+// Should not work with object
+$a->test = function ($params) { return (is_a($params, 'Closure')) ? $params("hello") : false; };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+$a->test = function ($params) { var_dump($params); return $params->cb1("hello"); };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+// FIX! method_exists() Leaks!
+$a->test = function ($params) { var_dump($params, method_exists($params, 'cb1'), $params->cb1); };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+

+ 11 - 0
samples/test_closure.php

@@ -0,0 +1,11 @@
+<?php
+
+$a = new V8Js();
+$a->func = function ($a) { echo "Closure..\n"; };
+
+try {
+  $a->executeString("print(PHP.func); PHP.func(1);", "closure_test.js");
+  $a->executeString("print(PHP.func); PHP.func(1);", "closure_test.js");
+} catch (V8JsException $e) {
+  echo $e->getMessage(), "\n";
+}

+ 5 - 0
samples/test_crash.php

@@ -0,0 +1,5 @@
+<?php {
+    $a = new V8Js();
+
+    var_dump($a->executeString('Jst.write = function(s) { html += "EI TOIMI"; };' ."\n" .' Jst.evaluate("lol testi <%= 1 %>", {});'));
+}

+ 9 - 0
samples/test_date.php

@@ -0,0 +1,9 @@
+<?php
+
+$a = new V8Js();
+
+try {
+	var_dump($a->executeString("date = new Date('September 8, 1975 09:00:00'); print(date + '\\n'); date;", "test.js"));
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}

+ 59 - 0
samples/test_dumper.php

@@ -0,0 +1,59 @@
+<?php
+
+class Foo {
+	var $foo = 'bar';
+	var $true = true;
+	var $false = false;
+	var $bar = array(1,2,3,1.23456789);
+	var $ass = array("life" => 42, "foo" => "bar");
+	function __set($name, $value)
+	{
+		echo "I'm setter!\n";
+		var_dump($name, $value);
+	}
+	function __get($name)
+	{
+		echo "I'm getter!\n";
+		var_dump($name);
+	}
+	function __call($name, $args)
+	{
+		echo "I'm caller!\n";
+		var_dump($name, $args);
+	}
+}
+
+$a = new V8Js();
+$obj = new Foo;
+$a->arr = array("foobar" => $obj);
+
+$JS = <<< 'EOF'
+  var example = new Object;
+  example.foo = function () {
+    print("this is foo");
+  }
+  example.bar = function () {
+    print("this is bar");
+  }
+  example.__noSuchMethod__ = function (id, args) {
+    print("tried to handle unknown method " + id);
+    if (args.length != 0)
+      print("it had arguments: " + args);
+  }
+  example.foo();        // alerts "this is foo"
+  example.bar();        // alerts "this is bar"
+  example.grill();      // alerts "tried to handle unknown method grill"
+  example.ding("dong"); // alerts "tried to handle unknown method ding"
+EOF;
+
+try {
+  $a->executeString("var myarr = new Array(); myarr[0] = 'foo'; myarr[1] = 'bar'; var_dump(myarr); var_dump(new Date('September 8, 1975 09:00:00'))", "call_test1.js");
+  $a->executeString("var_dump(PHP.arr.foobar.bar);", "call_test2.js");
+  $a->executeString("var_dump(PHP.arr.foobar.bar[0]);", "call_test3.js");
+  $a->executeString("var_dump(var_dump(PHP.arr));", "call_test4.js");
+  $a->executeString("var patt1=/[^a-h]/g; var_dump(patt1);", "call_test5.js");
+  $a->executeString("var_dump(Math.PI, Infinity, null, undefined);", "call_test6.js");
+//  $a->executeString($JS);
+} catch (V8JsException $e) {
+  echo $e->getMessage(), "\n";
+}

+ 26 - 0
samples/test_exception.php

@@ -0,0 +1,26 @@
+<?php {
+    class Foo {
+      private $v8 = NULL;
+
+      public function __construct()
+      {
+		$this->v8 = new V8Js();
+		$this->v8->foo = $this;
+		var_dump($this->v8->executeString('throw 1; PHP.foo.bar();', 'trycatch1'));
+		var_dump($this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch2'));
+      }
+
+      public function bar()
+      {
+      	echo "To Bar!\n";
+      	var_dump($this->v8->executeString('throw new Error();', 'throw'));
+      }
+    }
+    
+    try {
+      $foo = new Foo();
+    } catch (V8JsException $e) {
+      echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
+    }
+
+}

+ 34 - 0
samples/test_exception2.php

@@ -0,0 +1,34 @@
+<?php {
+    class Foo {
+      private $v8 = NULL;
+
+      public function __construct()
+      {
+		$this->v8 = new V8Js(null, array(), false);
+		$this->v8->foo = $this;
+//		$this->v8->executeString('asdasda< / sd', 'trycatch0');
+//		$this->v8->executeString('blahnothere', 'trycatch1');
+//	  	$this->v8->executeString('throw new SyntaxError();', 'throw');
+//		$this->v8->executeString('function foo() {throw new SyntaxError();}', 'trycatch2');
+//		$this->v8->executeString('try { foo(); } catch (e) { print(e + " catched by pure JS!\n"); }', 'trycatch3');
+//		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " catched via PHP callback!\n"); }', 'trycatch4');
+//		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch5');
+//		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch5');
+		var_dump($this->v8->getPendingException());
+      }
+
+      public function bar()
+      {
+//		$this->v8->executeString('asdasda< / sd', 'trycatch0');
+//		$this->v8->executeString('blahnothere', 'trycatch1');
+      	$this->v8->executeString('throw new Error();', 'throw');
+      }
+    }
+    
+    try {
+      $foo = new Foo();
+    } catch (V8JsException $e) {
+      echo "PHP Exception: ", $e->getMessage(), "\n";
+    }
+
+}

+ 23 - 0
samples/test_extend.php

@@ -0,0 +1,23 @@
+<?php
+
+// Test class
+class Testing extends V8Js
+{
+	public $foo = 'ORIGINAL';
+	private $my_private = 'arf'; // Should not show in JS side
+	protected $my_protected = 'argh'; // Should not show in JS side
+
+	public function mytest($a, $b, $c = NULL)
+	{
+		var_dump(func_get_args());
+	}
+}
+
+$a = new Testing();
+echo $a;
+
+try {
+	$a->executeString("PHP.mytest(PHP.foo, PHP.my_private, PHP.my_protected);", "test7.js");
+} catch (V8JsException $e) {
+	var_dump($e);
+}

+ 9 - 0
samples/test_extension.php

@@ -0,0 +1,9 @@
+<?php
+
+V8Js::registerExtension('a', file_get_contents('js/json-template.js'), array('b'));
+V8Js::registerExtension('b', file_get_contents('js/jstparser.js'), array('a'));
+
+var_dump(V8JS::getExtensions());
+
+$a = new V8Js('myobj', array(), array('b'));
+

+ 48 - 0
samples/test_method.php

@@ -0,0 +1,48 @@
+<?php
+
+// Test class
+class Testing
+{
+	public $foo = 'ORIGINAL';
+	private $my_private = 'arf'; // Should not show in JS side
+	protected $my_protected = 'argh'; // Should not show in JS side
+
+	function mytest($a, $b, $c = NULL)
+	{
+		var_dump(func_get_args());
+	}
+}
+
+$a = new V8Js();
+$a->myobj = new Testing();
+
+$a->executeString("PHP.myobj.mytest('arg1', 'arg2');", "test1.js");
+$a->executeString("PHP.myobj.mytest(true, false, 1234567890);", "test2.js");
+$a->executeString("PHP.myobj.mytest(3.14, 42, null);", "test3.js");
+
+// Invalid parameters
+try {
+	$a->executeString("PHP.myobj.mytest();", "test4.js");
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}
+
+try {
+	$a->executeString("PHP.myobj.mytest('arg1', 'arg2', 'arg3', 'extra_arg');", "test5.js");
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}
+
+// Array / Object
+try {
+//	date_default_timezone_set("UTC");
+	$a->executeString("date = new Date('September 8, 1975 09:00:00'); PHP.print(date); PHP.myobj.mytest(date, PHP.myobj, new Array(1,2,3));", "test6.js");
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+try {
+	$a->executeString("PHP.myobj.mytest(PHP.myobj, new Array(1,2,3), new Array('foo', 'bar', PHP.myobj));", "test7.js");
+} catch (V8JsException $e) {
+	var_dump($e);
+}

+ 173 - 0
test.php

@@ -0,0 +1,173 @@
+<?php
+/*
+$v8 = new V8Js;
+$v8->func = function ($a) { return var_export(func_get_args(), TRUE); };
+$v8->executeString("PHP.func();", "arg_test1.js");
+exit;
+*/
+
+var_dump(V8Js::registerExtension('myparser.js', 'function foo() { print("foobar!\n"}', array('jstparser.js', 'json-template.js'), false));
+var_dump(V8Js::registerExtension('myparser.js', 'function foo() { print("foobar!\n"}', array('jstparser.js', 'json-template.js'), false));
+var_dump(V8Js::registerExtension('jstparser.js', file_get_contents('js/jstparser.js'), array(), false));
+//V8Js::registerExtension('json-template.js', file_get_contents('js/json-template.js'), array(), false);
+
+var_dump(V8JS::getExtensions());
+
+$a = new V8Js('myobj', array(), array('jstparser.js'));
+
+$jstparser = <<< 'EOT'
+var template = 'Gold &amp; Hot Rod Red, as seen in the new <a href="http://blog.markturansky.com/archives/51">Iron Man trailer</a>!' + "\n" +
+'<table cellspacing="0" cellpadding="4">' + "\n" +
+'    <% for(var i = 0; i < 10; i++){ %> ' + "\n" +
+'        <tr>' + "\n" +
+'        <td style="background-color: <%= i % 2 == 0 ? \'red\' : \'gold\' %>">' + "\n" +
+'            Hi, <%=name%>! i is <%= i %>' + "\n" +
+'        </td>' + "\n" +
+'        </tr>' + "\n" +
+'    <% } %>' + "\n" +
+'</table>' + "\n" +
+'Note that name is HTML escaped by default. Here it is without escaping:'+
+'<%+ name %>';
+Jst.evaluateSingleShot(template, {"name":"foobar"});
+EOT;
+
+echo($a->executeString($jstparser, "ext_test1.js")), "\n";
+
+$a->_SERVER = $_SERVER;
+$a->func = function ($a) { echo "Closure..\n"; };
+
+$a->executeString("print(myobj._SERVER['HOSTNAME']);", "test1.js");
+$a->executeString("print(myobj.func); myobj.func(1);", "closure_test.js");
+
+$JS = <<<'EOT'
+function dump(a)
+{
+	for (var i in a) { 
+	  var val = a[i];
+	  print(i + ' => ' + val + "\n");
+	}
+}
+function foo()
+{
+  var bar = 'bar';
+  var foo = 'foo';
+  return foo + bar;
+}
+function test()
+{
+  var a = 'PHP version: ' + PHP.phpver;
+  phpver = 'changed in JS!';
+  return a;
+}
+function loop()
+{
+  var foo = 'foo';
+  while(true)
+  {
+    foo + 'bar';
+  }
+}
+function output()
+{
+	while(true)
+	{
+		print("output:foo\n");
+		sleep(5);
+		exit();
+	}
+};
+function simplearray()
+{
+	print(myarray.a + "\n");
+	print(myarray.b + "\n");
+	print(myarray.c + "\n");
+	print(myarray.d + "\n");
+}
+function bigarray()
+{
+	print(PHP.$_SERVER['HOSTNAME'] + "\n");
+	print(PHP.$_SERVER.argv + "\n");
+}
+EOT;
+
+$jsontemplate = <<< EOT
+var t = jsontemplate.Template("{# This is a comment and will be removed from the output.}{.section songs}<h2>Songs in '{playlist-name}'</h2><table width=\"100%\">{.repeated section @}<tr><td><a href=\"{url-base|htmltag}{url|htmltag}\">Play</a><td><i>{title}</i></td><td>{artist}</td></tr>{.end}</table>{.or}<p><em>(No page content matches)</em></p>{.end}");
+t.expand({
+"url-base": "http://example.com/music/", 
+"playlist-name": "Epic Playlist", 
+"songs": [
+{
+"url": "1.mp3", 
+"artist": "Grayceon", 
+"title": "Sounds Like Thunder"
+}, 
+{
+"url": "2.mp3", 
+"artist": "Thou", 
+"title": "Their Hooves Carve Craters in the Earth"
+}]});
+EOT;
+
+class tester
+{
+	public $foo = 'bar';
+	private $my_private = 'arf';
+	protected $my_protected = 'argh';
+
+	function mytest() { echo 'Here be monsters..', "\n"; }
+}
+
+$a = new V8Js();
+$a->obj = new tester();
+$a->phpver = phpversion();
+$a->argv = $_SERVER['argv'];
+$a->integer = 1;
+$a->float = 3.14;
+$a->{'$'._SERVER} = $_SERVER;
+$a->GLOBALS = $GLOBALS;
+$a->myarray = array(
+	'a' => 'Array value for key A',
+	'b' => 'Array value for key B',
+	'c' => 'Array value for key C',
+	'd' => 'Array value for key D',
+);
+$a->arr = array("first", "second", "third");
+
+$a->executeString($JS, "test1.js");
+$a->executeString("bigarray()", "test1.js");
+
+try {
+  echo($a->executeString($jstparser, "test2.js")), "\n";
+  var_dump($a->executeString($jsontemplate, "test1.js"));
+} catch (V8JsException $e) {
+  echo $e->getMessage();
+}
+
+// Test for context handling
+
+$a->executeString($JS, "test1.js");
+$a->executeString("bigarray();");
+
+echo '$a->obj: ', "\n"; $a->executeString("dump(PHP.obj);");
+echo '$a->arr: ', "\n"; $a->executeString("dump(PHP.arr);");
+echo '$a->argv: ', "\n"; $a->executeString("dump(PHP.argv);");
+
+var_dump($a->argv);
+var_dump($a->executeString("test();"));
+var_dump($a->executeString("test();"));
+
+$b = new V8Js();
+
+var_dump($a->phpver, $a->executeString("test();"));
+
+$b->executeString($JS, "test2.js");
+var_dump($b->executeString("test();"));
+var_dump($b->executeString("print('foobar\\n');"));
+
+// Exception methods
+
+try {
+  $b->executeString("foobar; foo();", "extest.js");
+} catch (V8JsException $e) {
+  var_dump($e, $e->getJsFileName(), $e->getJsLineNumber(), $e->getJsSourceLine(), $e->getJsTrace());
+}

+ 25 - 0
tests/basic.phpt

@@ -0,0 +1,25 @@
+--TEST--
+Test V8::executeString() : Simple test
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+len = print('Hello' + ' ' + 'World!' + "\\n");
+len;
+EOT;
+
+$v8 = new V8Js();
+
+try {
+	var_dump($v8->executeString($JS, 'basic.js'));
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECT--
+Hello World!
+int(13)
+===EOF===

+ 48 - 0
tests/callbacks.phpt

@@ -0,0 +1,48 @@
+--TEST--
+Test V8::executeString() : Call JS from PHP
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+	
+$a = new V8Js();
+
+// Should not work with closure
+$a->test = function ($params) { return (method_exists($params, 'cb1')) ? $params->cb1("hello") : false; };
+$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
+var_dump(__LINE__, $ret);
+
+// Test is_a()
+$a->test = function ($params) { return (is_a($params, 'V8Object')) ? $params->cb1("hello") : false; };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+// Test is_a()
+$a->test = function ($params) { return (is_a($params, 'V8Function')) ? $params("hello") : false; };
+$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
+var_dump(__LINE__, $ret);
+
+// Should not work with object
+$a->test = function ($params) { return (is_a($params, 'Closure')) ? $params("hello") : false; };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+// Works
+$a->test = function ($params) { return $params->cb1("hello"); };
+$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
+var_dump(__LINE__, $ret);
+
+?>
+===EOF===
+--EXPECT--
+int(8)
+bool(false)
+int(13)
+string(11) "hello world"
+int(18)
+string(11) "hello world"
+int(23)
+bool(false)
+int(28)
+string(11) "hello world"
+===EOF===

+ 21 - 0
tests/closures_basic.phpt

@@ -0,0 +1,21 @@
+--TEST--
+Test V8::executeString() : Simple test
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$a = new V8Js();
+$a->func = function ($arg) { echo "Hello {$arg}, I'm Closure!\n"; };
+
+try {
+  $a->executeString('print(PHP.func + "\n"); PHP.func("foobar");', "closure_test.js");
+} catch (V8JsException $e) {
+  echo $e->getMessage(), "\n";
+}
+?>
+===EOF===
+--EXPECT--
+[object Closure]
+Hello foobar, I'm Closure!
+===EOF===

+ 30 - 0
tests/closures_dynamic.phpt

@@ -0,0 +1,30 @@
+--TEST--
+Test V8::executeString() : Dynamic closure call test
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo
+{
+  public static function bar($s)
+  {
+     return 'Hello ' . $s;
+  }
+}
+
+$a = new V8Js();
+$b = array('Foo', 'bar');
+$a->func = function ($arg) use ($b) { return call_user_func($b, $arg); };
+
+try {
+  $a->executeString('print(PHP.func + "\n"); print(PHP.func("world") + "\n");', "closure_test.js");
+} catch (V8JsException $e) {
+  echo $e->getMessage(), "\n";
+}
+?>
+===EOF===
+--EXPECT--
+[object Closure]
+Hello world
+===EOF===

+ 24 - 0
tests/construct.phpt

@@ -0,0 +1,24 @@
+--TEST--
+Test V8::executeString() : Calling construct twice
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+print('Hello' + ' ' + 'World!' + "\\n");
+EOT;
+
+$v8 = new V8Js("myObj");
+$v8->__construct();
+
+try {
+	$v8->executeString($JS, 'basic.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECT--
+Hello World!
+===EOF===

+ 78 - 0
tests/context_preserving.phpt

@@ -0,0 +1,78 @@
+--TEST--
+Test V8::executeString() : test context preserving
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS_set = <<< EOT
+var preserved = "ORIGINAL";
+print("Set variable (" + PHP.ctx + ")\\n");
+EOT;
+
+$JS_change = <<< EOT
+preserved = "CHANGED";
+print("Change variable (" + PHP.ctx + ")\\n");
+EOT;
+
+$JS_read = <<< EOT
+print("Read variable (" + PHP.ctx + ")\\n");
+print("Result: " + preserved + "\\n");
+EOT;
+
+// First context: Set variable
+$a = new V8Js();
+$a->ctx = '#1';
+
+try {
+	echo '1. ';
+	$a->executeString($JS_set, 'set.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+// Second context: Change variable
+$b = new V8Js();
+$b->ctx = '#2';
+
+try {
+	echo '2. ';
+	$b->executeString($JS_change, 'change.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+// First context: Read variable
+try {
+	echo '3. ';
+	$a->executeString($JS_read, 'read.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+// First context: Change variable
+try {
+	echo '4. ';
+	$a->executeString($JS_change, 'change.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+// First context: Read variable again
+try {
+	echo '5. ';
+	$a->executeString($JS_read, 'read.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECT--
+1. Set variable (#1)
+2. Change variable (#2)
+3. Read variable (#1)
+Result: ORIGINAL
+4. Change variable (#1)
+5. Read variable (#1)
+Result: CHANGED
+===EOF===

+ 38 - 0
tests/context_separation.phpt

@@ -0,0 +1,38 @@
+--TEST--
+Test V8::executeString() : test context separation
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+print(PHP.foo);
+EOT;
+
+$a = new V8Js();
+$a->foo = 'from first.js';
+
+try {
+	$a->executeString($JS, 'first.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+echo "\n";
+
+$b = new V8Js();
+$b->foo = 'from second.js';
+
+try {
+	$b->executeString($JS, 'second.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+echo "\n";
+?>
+===EOF===
+--EXPECTF--
+from first.js
+from second.js
+===EOF===

+ 68 - 0
tests/exception.phpt

@@ -0,0 +1,68 @@
+--TEST--
+Test V8::executeString() : V8JsException
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+this_function_does_not_exist();
+EOT;
+
+$v8 = new V8Js();
+
+try {
+	$v8->executeString($JS, 'exception.js');
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECTF--
+object(V8JsException)#2 (11) {
+  ["message":protected]=>
+  string(75) "exception.js:1: ReferenceError: this_function_does_not_exist is not defined"
+  ["string":"Exception":private]=>
+  string(0) ""
+  ["code":protected]=>
+  int(0)
+  ["file":protected]=>
+  string(%d) "%s"
+  ["line":protected]=>
+  int(10)
+  ["trace":"Exception":private]=>
+  array(1) {
+    [0]=>
+    array(6) {
+      ["file"]=>
+      string(%d) "%s"
+      ["line"]=>
+      int(10)
+      ["function"]=>
+      string(13) "executeString"
+      ["class"]=>
+      string(4) "V8Js"
+      ["type"]=>
+      string(2) "->"
+      ["args"]=>
+      array(2) {
+        [0]=>
+        string(31) "this_function_does_not_exist();"
+        [1]=>
+        string(12) "exception.js"
+      }
+    }
+  }
+  ["previous":"Exception":private]=>
+  NULL
+  ["JsFileName":protected]=>
+  string(12) "exception.js"
+  ["JsLineNumber":protected]=>
+  int(1)
+  ["JsSourceLine":protected]=>
+  string(31) "this_function_does_not_exist();"
+  ["JsTrace":protected]=>
+  string(83) "ReferenceError: this_function_does_not_exist is not defined
+    at exception.js:1:1"
+}
+===EOF===

+ 37 - 0
tests/exception_propagation_1.phpt

@@ -0,0 +1,37 @@
+--TEST--
+Test V8::executeString() : Exception propagation test 1
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+	private $v8 = NULL;
+
+	public function __construct()
+	{
+		$this->v8 = new V8Js();
+		$this->v8->foo = $this;
+		$this->v8->executeString('fooobar', 'throw_0');
+		var_dump($this->v8->getPendingException());
+		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch1');
+		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch2');
+	}
+
+	public function bar()
+	{
+		echo "To Bar!\n";
+		$this->v8->executeString('throw new Error();', 'throw_1');
+	}
+}
+    
+try {
+	$foo = new Foo();
+} catch (V8JsException $e) {
+	echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
+}
+?>
+===EOF===
+--EXPECTF--
+PHP Exception: throw_0:1: ReferenceError: fooobar is not defined
+===EOF===

+ 101 - 0
tests/exception_propagation_2.phpt

@@ -0,0 +1,101 @@
+--TEST--
+Test V8::executeString() : Exception propagation test 2
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+	private $v8 = NULL;
+
+	public function __construct()
+	{
+		$this->v8 = new V8Js(null, array(), array(), false);
+		$this->v8->foo = $this;
+		$this->v8->executeString('fooobar', 'throw_0');
+		var_dump($this->v8->getPendingException());
+		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch1');
+		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch2');
+	}
+
+	public function bar()
+	{
+		echo "To Bar!\n";
+		$this->v8->executeString('throw new Error();', 'throw_1');
+	}
+}
+    
+try {
+	$foo = new Foo();
+} catch (V8JsException $e) {
+	echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
+}
+?>
+===EOF===
+--EXPECTF--
+object(V8JsException)#3 (11) {
+  ["message":protected]=>
+  string(49) "throw_0:1: ReferenceError: fooobar is not defined"
+  ["string":"Exception":private]=>
+  string(0) ""
+  ["code":protected]=>
+  int(0)
+  ["file":protected]=>
+  string(%d) "%s"
+  ["line":protected]=>
+  int(10)
+  ["trace":"Exception":private]=>
+  array(2) {
+    [0]=>
+    array(6) {
+      ["file"]=>
+      string(%d) "%s"
+      ["line"]=>
+      int(10)
+      ["function"]=>
+      string(13) "executeString"
+      ["class"]=>
+      string(4) "V8Js"
+      ["type"]=>
+      string(2) "->"
+      ["args"]=>
+      array(2) {
+        [0]=>
+        string(7) "fooobar"
+        [1]=>
+        string(7) "throw_0"
+      }
+    }
+    [1]=>
+    array(6) {
+      ["file"]=>
+      string(%d) "%s"
+      ["line"]=>
+      int(24)
+      ["function"]=>
+      string(11) "__construct"
+      ["class"]=>
+      string(3) "Foo"
+      ["type"]=>
+      string(2) "->"
+      ["args"]=>
+      array(0) {
+      }
+    }
+  }
+  ["previous":"Exception":private]=>
+  NULL
+  ["JsFileName":protected]=>
+  string(7) "throw_0"
+  ["JsLineNumber":protected]=>
+  int(1)
+  ["JsSourceLine":protected]=>
+  string(7) "fooobar"
+  ["JsTrace":protected]=>
+  string(57) "ReferenceError: fooobar is not defined
+    at throw_0:1:1"
+}
+To Bar!
+Error caught!
+PHP Exception: throw_0:1: ReferenceError: fooobar is not defined
+===EOF===

+ 38 - 0
tests/exception_propagation_3.phpt

@@ -0,0 +1,38 @@
+--TEST--
+Test V8::executeString() : Exception propagation test 3
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+class Foo {
+	private $v8 = NULL;
+
+	public function __construct()
+	{
+		$this->v8 = new V8Js(null, array(), array(), false);
+		$this->v8->foo = $this;
+		$this->v8->executeString('function foobar() { throw new SyntaxError(); }', 'throw_1');
+		$this->v8->executeString('try { foobar(); } catch (e) { print(e + " caught in JS!\n"); }', 'trycatch1');
+		$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught via PHP callback!\n"); }', 'trycatch2');
+	}
+
+	public function bar()
+	{
+		echo "To Bar!\n";
+		$this->v8->executeString('throw new Error();', 'throw_2');
+	}
+}
+    
+try {
+	$foo = new Foo();
+} catch (V8JsException $e) {
+	echo "PHP Exception: ", $e->getMessage(), "\n";
+}
+?>
+===EOF===
+--EXPECT--
+SyntaxError caught in JS!
+To Bar!
+Error caught via PHP callback!
+===EOF===

+ 33 - 0
tests/execute_flags.phpt

@@ -0,0 +1,33 @@
+--TEST--
+Test V8::executeString() : Forcing to arrays
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php 
+
+$js = <<<'EOT'
+var a = { 'hello' : 'world' }; a;
+EOT;
+
+$v8 = new V8Js();
+
+try {
+	var_dump($v8->executeString($js, 'assoc_no_flags.js'));
+	echo "---\n";
+	var_dump($v8->executeString($js, 'assoc_force_to_array.js', V8Js::FLAG_FORCE_ARRAY));
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECT--
+object(V8Object)#2 (1) {
+  ["hello"]=>
+  string(5) "world"
+}
+---
+array(1) {
+  ["hello"]=>
+  string(5) "world"
+}
+===EOF===

+ 75 - 0
tests/execute_flags_args.phpt

@@ -0,0 +1,75 @@
+--TEST--
+Test V8::executeString() : Forcing to arrays
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php 
+
+$js = <<<'EOT'
+String.prototype.test = function(){ return PHP.test(this, arguments); };
+"Foobar".test("foo", "bar");
+EOT;
+
+$v8 = new V8Js();
+$v8->test = function ($value) { var_dump(func_get_args()); };
+
+try {
+	$v8->executeString($js, 'no_flags.js');
+	echo "---\n";
+	$v8->executeString($js, 'force_to_array.js', V8Js::FLAG_FORCE_ARRAY);
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECT--
+array(2) {
+  [0]=>
+  object(V8Object)#3 (6) {
+    ["0"]=>
+    string(1) "F"
+    ["1"]=>
+    string(1) "o"
+    ["2"]=>
+    string(1) "o"
+    ["3"]=>
+    string(1) "b"
+    ["4"]=>
+    string(1) "a"
+    ["5"]=>
+    string(1) "r"
+  }
+  [1]=>
+  object(V8Object)#4 (2) {
+    ["0"]=>
+    string(3) "foo"
+    ["1"]=>
+    string(3) "bar"
+  }
+}
+---
+array(2) {
+  [0]=>
+  array(6) {
+    [0]=>
+    string(1) "F"
+    [1]=>
+    string(1) "o"
+    [2]=>
+    string(1) "o"
+    [3]=>
+    string(1) "b"
+    [4]=>
+    string(1) "a"
+    [5]=>
+    string(1) "r"
+  }
+  [1]=>
+  array(2) {
+    [0]=>
+    string(3) "foo"
+    [1]=>
+    string(3) "bar"
+  }
+}
+===EOF===

+ 35 - 0
tests/extensions_basic.phpt

@@ -0,0 +1,35 @@
+--TEST--
+Test V8::registerExtension() : Basic extension registering
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+V8Js::registerExtension('a', 'print("world!\n");', array('b'));
+V8Js::registerExtension('b', 'print("Hello ");');
+
+var_dump(V8JS::getExtensions());
+
+$a = new V8Js('myobj', array(), array('a'));
+?>
+===EOF===
+--EXPECT--
+array(2) {
+  ["a"]=>
+  array(2) {
+    ["auto_enable"]=>
+    bool(false)
+    ["deps"]=>
+    array(1) {
+      [0]=>
+      string(1) "b"
+    }
+  }
+  ["b"]=>
+  array(1) {
+    ["auto_enable"]=>
+    bool(false)
+  }
+}
+Hello world!
+===EOF===

+ 39 - 0
tests/extensions_circular_dependency.phpt

@@ -0,0 +1,39 @@
+--TEST--
+Test V8::registerExtension() : Circular dependencies
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+V8Js::registerExtension('a', 'print("A");', array('b'));
+V8Js::registerExtension('b', 'print("B");', array('a'));
+
+var_dump(V8JS::getExtensions());
+
+$a = new V8Js('myobj', array(), array('a'));
+?>
+--EXPECTF--
+array(2) {
+  ["a"]=>
+  array(2) {
+    ["auto_enable"]=>
+    bool(false)
+    ["deps"]=>
+    array(1) {
+      [0]=>
+      string(1) "b"
+    }
+  }
+  ["b"]=>
+  array(2) {
+    ["auto_enable"]=>
+    bool(false)
+    ["deps"]=>
+    array(1) {
+      [0]=>
+      string(1) "a"
+    }
+  }
+}
+
+Fatal error: v8::Context::New() Circular extension dependency in %s on line 8

+ 41 - 0
tests/get_accessor.phpt

@@ -0,0 +1,41 @@
+--TEST--
+Test V8::executeString() : PHP variables via get accessor
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--INI--
+display_errors=off
+--FILE--
+<?php
+
+$JS = <<<'EOT'
+print(typeof $foobar + "\n"); // Undefined
+print(myobj.$foobar + "\n");  // Undefined (in 1st run!)
+print(myobj.$_SERVER['REQUEST_TIME'] + "\n");
+myobj.$foobar = 'CHANGED'; // should be read only!
+print(myobj.$foobar + "\n");  // Undefined (in 1st run!)
+EOT;
+
+$a = new V8Js("myobj", array('$_SERVER' => '_SERVER', '$foobar' => 'myfoobar'));
+$a->executeString($JS, "test1.js");
+
+$myfoobar = 'myfoobarfromphp';
+
+$a->executeString($JS, "test2.js");
+
+// Check that variables do not get in object .. 
+var_dump($a->myfoobar, $a->foobar);
+
+?>
+===EOF===
+--EXPECTF--
+undefined
+undefined
+%d
+undefined
+undefined
+myfoobarfromphp
+%d
+myfoobarfromphp
+NULL
+NULL
+===EOF===

+ 50 - 0
tests/object.phpt

@@ -0,0 +1,50 @@
+--TEST--
+Test V8::executeString() : Object passed from PHP
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+function dump(a)
+{
+	for (var i in a) { 
+		var val = a[i];
+		print(i + ' => ' + val + "\\n");
+	}
+}
+function test()
+{
+	dump(PHP.myobj);
+	PHP.myobj.foo = 'CHANGED';
+	PHP.myobj.mytest();
+}
+test();
+print(PHP.myobj.foo + "\\n");
+EOT;
+
+// Test class
+class Testing
+{
+	public $foo = 'ORIGINAL';
+	private $my_private = 'arf'; // Should not show in JS side
+	protected $my_protected = 'argh'; // Should not show in JS side
+
+	function mytest() { echo 'Here be monsters..', "\n"; }
+}
+
+$a = new V8Js();
+$a->myobj = new Testing();
+$a->executeString($JS, "test.js");
+
+// Check that variable has not been modified
+var_dump($a->myobj->foo);
+?>
+===EOF===
+--EXPECT--
+mytest => function () { [native code] }
+foo => ORIGINAL
+Here be monsters..
+ORIGINAL
+string(8) "ORIGINAL"
+===EOF===

+ 144 - 0
tests/object_method_call.phpt

@@ -0,0 +1,144 @@
+--TEST--
+Test V8::executeString() : Calling methods of object passed from PHP
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+// Test class
+class Testing
+{
+	public $foo = 'ORIGINAL';
+	private $my_private = 'arf'; // Should not show in JS side
+	protected $my_protected = 'argh'; // Should not show in JS side
+
+	function mytest($a, $b, $c = NULL)
+	{
+		var_dump(func_get_args());
+	}
+}
+
+$a = new V8Js();
+$a->myobj = new Testing();
+
+$a->executeString("PHP.myobj.mytest('arg1', 'arg2');", "test1.js");
+$a->executeString("PHP.myobj.mytest(true, false, 1234567890);", "test2.js");
+$a->executeString("PHP.myobj.mytest(3.14, 42, null);", "test3.js");
+
+// Invalid parameters
+try {
+	$a->executeString("PHP.myobj.mytest();", "test4.js");
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}
+
+try {
+	$a->executeString("PHP.myobj.mytest('arg1', 'arg2', 'arg3', 'extra_arg');", "test5.js");
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}
+
+try {
+	date_default_timezone_set("UTC");
+	echo "\nTEST: Javascript Date -> PHP DateTime\n";
+	echo "======================================\n";
+	$a->executeString("date = new Date('September 8, 1975 09:00:00'); print(date + '\\n'); PHP.myobj.mytest(date, 'foo');", "test6.js");
+} catch (V8JsException $e) {
+	echo $e->getMessage(), "\n";
+}
+
+// Array / Object
+try {
+	$a->executeString("PHP.myobj.mytest(PHP.myobj, new Array(1,2,3), new Array('foo', 'bar', PHP.myobj));", "test7.js");
+} catch (V8JsException $e) {
+	var_dump($e);
+}
+
+?>
+===EOF===
+--EXPECT--
+array(2) {
+  [0]=>
+  string(4) "arg1"
+  [1]=>
+  string(4) "arg2"
+}
+array(3) {
+  [0]=>
+  bool(true)
+  [1]=>
+  bool(false)
+  [2]=>
+  int(1234567890)
+}
+array(3) {
+  [0]=>
+  float(3.14)
+  [1]=>
+  int(42)
+  [2]=>
+  NULL
+}
+test4.js:1: TypeError: Testing::mytest() expects at least 2 parameters, 0 given
+array(4) {
+  [0]=>
+  string(4) "arg1"
+  [1]=>
+  string(4) "arg2"
+  [2]=>
+  string(4) "arg3"
+  [3]=>
+  string(9) "extra_arg"
+}
+
+TEST: Javascript Date -> PHP DateTime
+======================================
+Mon Sep 08 1975 09:00:00 GMT+0200 (EET)
+array(2) {
+  [0]=>
+  object(DateTime)#4 (3) {
+    ["date"]=>
+    string(19) "1975-09-08 09:00:00"
+    ["timezone_type"]=>
+    int(1)
+    ["timezone"]=>
+    string(6) "+02:00"
+  }
+  [1]=>
+  string(3) "foo"
+}
+array(3) {
+  [0]=>
+  object(V8Object)#4 (2) {
+    ["mytest"]=>
+    object(V8Function)#6 (0) {
+    }
+    ["foo"]=>
+    string(8) "ORIGINAL"
+  }
+  [1]=>
+  array(3) {
+    [0]=>
+    int(1)
+    [1]=>
+    int(2)
+    [2]=>
+    int(3)
+  }
+  [2]=>
+  array(3) {
+    [0]=>
+    string(3) "foo"
+    [1]=>
+    string(3) "bar"
+    [2]=>
+    object(V8Object)#5 (2) {
+      ["mytest"]=>
+      object(V8Function)#6 (0) {
+      }
+      ["foo"]=>
+      string(8) "ORIGINAL"
+    }
+  }
+}
+===EOF===

+ 82 - 0
tests/object_prototype.phpt

@@ -0,0 +1,82 @@
+--TEST--
+Test V8::executeString() : Prototype with PHP callbacks
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php 
+$js = <<<'EOT'
+
+String.prototype.test = function(){ return PHP.test(this.toString(), arguments); };
+String.prototype.test_two = function(){ return PHP.test_two.func(this.toString(), arguments); };
+Array.prototype.test = function(){ return PHP.test(this.toString(), arguments); };
+Array.prototype.test_two = function(){ return PHP.test_two.func(this.toString(), arguments); };
+
+"Foobar".test("foo", "bar");
+"Foobar".test_two("foo", "bar");
+
+["a","b","c"].test("foo", "bar");
+["a","b","c"].test_two("foo", "bar");
+
+EOT;
+
+class A 
+{
+  public function __call($name, $args)
+  {
+    var_dump($args);
+    return NULL;
+  }
+}
+
+$a = new V8Js();
+$a->test = function ($value) { var_dump(func_get_args()); return 'HELLO: ' . md5($value); };
+$a->test_two = new A();
+$a->executeString($js, 'foo');
+?>
+===EOF===
+--EXPECT--
+array(2) {
+  [0]=>
+  string(6) "Foobar"
+  [1]=>
+  object(V8Object)#4 (2) {
+    ["0"]=>
+    string(3) "foo"
+    ["1"]=>
+    string(3) "bar"
+  }
+}
+array(2) {
+  [0]=>
+  string(6) "Foobar"
+  [1]=>
+  object(V8Object)#4 (2) {
+    ["0"]=>
+    string(3) "foo"
+    ["1"]=>
+    string(3) "bar"
+  }
+}
+array(2) {
+  [0]=>
+  string(5) "a,b,c"
+  [1]=>
+  object(V8Object)#4 (2) {
+    ["0"]=>
+    string(3) "foo"
+    ["1"]=>
+    string(3) "bar"
+  }
+}
+array(2) {
+  [0]=>
+  string(5) "a,b,c"
+  [1]=>
+  object(V8Object)#4 (2) {
+    ["0"]=>
+    string(3) "foo"
+    ["1"]=>
+    string(3) "bar"
+  }
+}
+===EOF===

+ 22 - 0
tests/object_reuse.phpt

@@ -0,0 +1,22 @@
+--TEST--
+Test V8::executeString() : Test PHP object reusage
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$v8 = new V8Js();
+
+// Reusing should work..
+
+$v8 = new V8Js();
+$foo = array('foo' => 'Destructor did not mess anything!');
+$v8->foobar = $foo;
+
+$v8->executeString("print(PHP.foobar['foo'] + '\\n');", "dtor.js");
+
+?>
+===EOF===
+--EXPECT--
+Destructor did not mess anything!
+===EOF===

+ 82 - 0
tests/return_value.phpt

@@ -0,0 +1,82 @@
+--TEST--
+Test V8::executeString() : Return values
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+function test(passed)
+{
+	return passed;
+}
+EOT;
+
+// Test class
+class Testing
+{
+	public $foo = 'ORIGINAL';
+	private $my_private = 'arf'; // Should not show in JS side
+	protected $my_protected = 'argh'; // Should not show in JS side
+
+	function mytest() { echo 'Here be monsters..', "\n"; }
+}
+
+$a = new V8Js();
+$a->myobj = new Testing();
+var_dump($a->executeString($JS, "test.js"));
+var_dump($a->executeString("test(PHP.myobj);", "test1.js"));
+var_dump($a->executeString("test(new Array(1,2,3));", "test2.js"));
+var_dump($a->executeString("test(new Array('foo', 'bar'));", "test3.js"));
+var_dump($a->executeString("test(new Array('foo', 'bar'));", "test3.js"));
+var_dump($a->executeString("test(new Date('September 8, 1975 09:00:00'));", "test4.js"));
+var_dump($a->executeString("test(1234567890);", "test5.js"));
+var_dump($a->executeString("test(123.456789);", "test6.js"));
+var_dump($a->executeString("test('some string');", "test7.js"));
+var_dump($a->executeString("test(true);", "test8.js"));
+var_dump($a->executeString("test(false);", "test9.js"));
+?>
+===EOF===
+--EXPECT--
+NULL
+object(V8Object)#3 (2) {
+  ["mytest"]=>
+  object(V8Function)#4 (0) {
+  }
+  ["foo"]=>
+  string(8) "ORIGINAL"
+}
+array(3) {
+  [0]=>
+  int(1)
+  [1]=>
+  int(2)
+  [2]=>
+  int(3)
+}
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  string(3) "bar"
+}
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  string(3) "bar"
+}
+object(DateTime)#3 (3) {
+  ["date"]=>
+  string(19) "1975-09-08 09:00:00"
+  ["timezone_type"]=>
+  int(1)
+  ["timezone"]=>
+  string(6) "+02:00"
+}
+int(1234567890)
+float(123.456789)
+string(11) "some string"
+bool(true)
+bool(false)
+===EOF===

+ 5 - 0
tests/skipif.inc

@@ -0,0 +1,5 @@
+<?php
+
+if (!extension_loaded('v8js')) {
+	die("skip");
+}

+ 58 - 0
tests/variable_passing.phpt

@@ -0,0 +1,58 @@
+--TEST--
+Test V8::executeString() : simple variables passed from PHP
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--FILE--
+<?php
+
+$JS = <<< EOT
+function dump(a)
+{
+	for (var i in a) { 
+		var val = a[i];
+		print(i + ' => ' + val + "\\n");
+	}
+}
+function test()
+{
+	var a = 'From PHP: ' + PHP.somevar;
+	PHP.somevar = 'changed in JS!'; // Should not change..
+
+	dump(PHP.myarray);
+
+	return a;
+}
+print(test() + "\\n");
+print(PHP.myinteger + "\\n");
+print(PHP.myfloat + "\\n");
+EOT;
+
+$a = new V8Js();
+$a->somevar = "From PHP with love!"; 
+$a->myinteger = 123;
+$a->myfloat = 3.14;
+$a->_SERVER = $_SERVER;
+$a->GLOBALS = $GLOBALS;
+$a->myarray = array(
+	'a' => 'value for key A',
+	'b' => 'value for key B',
+	'c' => 'value for key C',
+	'd' => 'value for key D',
+);
+
+$a->executeString($JS, "test.js");
+
+// Check that variable has not been modified
+var_dump($a->somevar);
+?>
+===EOF===
+--EXPECT--
+a => value for key A
+b => value for key B
+c => value for key C
+d => value for key D
+From PHP: From PHP with love!
+123
+3.14
+string(19) "From PHP with love!"
+===EOF===

+ 1282 - 0
v8js.cc

@@ -0,0 +1,1282 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
+
+#define V8JS_DEBUG 0
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/standard/php_string.h"
+#include "ext/standard/php_smart_str.h"
+#include "zend_exceptions.h"
+#include "php_v8js.h"
+}
+
+#include <v8.h>
+#include "php_v8js_macros.h"
+
+/* Forward declarations */
+static void php_v8js_throw_exception(v8::TryCatch * TSRMLS_DC);
+static void php_v8js_create_exception(zval *, v8::TryCatch * TSRMLS_DC);
+
+/* Module globals */
+ZEND_BEGIN_MODULE_GLOBALS(v8js)
+	int v8_initialized;
+	HashTable *extensions;
+	v8::Persistent<v8::FunctionTemplate> global_template;
+	int disposed_contexts; /* Disposed contexts since last time V8 did GC */
+
+	/* Ini globals */
+	zval *v8_flags; /* V8 command line flags */
+	int max_disposed_contexts; /* Max disposed context allowed before forcing V8 GC */
+ZEND_END_MODULE_GLOBALS(v8js)
+
+#ifdef ZTS
+# define V8JSG(v) TSRMG(v8js_globals_id, zend_v8js_globals *, v)
+#else
+# define V8JSG(v) (v8js_globals.v)
+#endif
+
+ZEND_DECLARE_MODULE_GLOBALS(v8js)
+
+/* {{{ INI Settings */
+
+static ZEND_INI_MH(v8js_OnUpdateMaxDisposedContexts) /* {{{ */
+{
+	int max_disposed = zend_atoi(new_value, new_value_length);
+
+	if (max_disposed <= 0) {
+		return FAILURE;
+	}
+
+	V8JSG(max_disposed_contexts) = max_disposed;
+
+	return SUCCESS;
+}
+/* }}} */
+
+static ZEND_INI_MH(v8js_OnUpdateV8Flags) /* {{{ */
+{
+	if (new_value) {
+		if (V8JSG(v8_flags)) {
+			zval_ptr_dtor(&V8JSG(v8_flags));
+			V8JSG(v8_flags) = NULL;
+		}
+		if (!new_value[0]) {
+			return FAILURE;
+		}
+	}
+
+	MAKE_STD_ZVAL(V8JSG(v8_flags));
+	ZVAL_STRINGL(V8JSG(v8_flags), new_value, new_value_length, 1);
+
+	return SUCCESS;
+}
+/* }}} */
+
+ZEND_INI_BEGIN() /* {{{ */
+	ZEND_INI_ENTRY("v8js.max_disposed_contexts", "25", ZEND_INI_ALL, v8js_OnUpdateMaxDisposedContexts)
+	ZEND_INI_ENTRY("v8js.flags", "", ZEND_INI_ALL, v8js_OnUpdateV8Flags)
+ZEND_INI_END()
+/* }}} */
+
+/* }}} INI */
+
+/* {{{ Class Entries */
+zend_class_entry *php_ce_v8_object;
+zend_class_entry *php_ce_v8_function;
+static zend_class_entry *php_ce_v8js;
+static zend_class_entry *php_ce_v8js_exception;
+/* }}} */
+
+/* {{{ Object Handlers */
+static zend_object_handlers v8js_object_handlers;
+static zend_object_handlers v8_object_handlers;
+/* }}} */
+
+/* {{{ Extension container */
+struct php_v8js_jsext {
+	zend_bool auto_enable;
+	HashTable *deps_ht;
+	const char **deps;
+	int deps_count;
+	char *name;
+	char *source;
+};
+/* }}} */
+
+/* {{{ Context container */
+struct php_v8js_ctx {
+	zend_object std;
+	v8::Persistent<v8::String> object_name;
+	v8::Persistent<v8::Context> context;
+	zend_bool report_uncaught;
+	zval *pending_exception;
+	int in_execution;
+};
+/* }}} */
+
+/* {{{ Object container */
+struct php_v8js_object {
+	zend_object std;
+	v8::Persistent<v8::Value> v8obj;
+	int flags;
+};
+/* }}} */
+
+#ifdef COMPILE_DL_V8JS
+ZEND_GET_MODULE(v8js)
+#endif
+
+/* {{{ Class: V8 */
+
+#define V8JS_V8_INVOKE_FUNC_NAME "V8Js::V8::Invoke"
+
+/* V8 Object handlers */
+
+static zval *php_v8js_v8_read_property(zval *object, zval *member, int type TSRMLS_DC) /* {{{ */
+{
+	zval *retval = NULL;
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+	if (Z_TYPE_P(member) == IS_STRING && obj->v8obj->IsObject() && !obj->v8obj->IsFunction())
+	{
+		v8::Local<v8::Object> jsObj = obj->v8obj->ToObject();
+		v8::Local<v8::String> jsKey = V8JS_STRL(Z_STRVAL_P(member), Z_STRLEN_P(member));
+		v8::Local<v8::Value> jsVal;
+
+		/* Skip any prototype properties */
+		if (jsObj->HasRealNamedProperty(jsKey) || jsObj->HasRealNamedCallbackProperty(jsKey)) {
+			jsVal = jsObj->Get(jsKey);
+			
+			if (jsVal->IsObject()) {
+				ALLOC_INIT_ZVAL(retval);
+				Z_SET_REFCOUNT_P(retval, 0);
+			} else {
+				MAKE_STD_ZVAL(retval);
+			}
+
+			if (v8js_to_zval(jsVal, retval, obj->flags TSRMLS_CC) == SUCCESS) {
+				return retval;
+			}
+		}
+	}
+
+	ALLOC_INIT_ZVAL(retval);
+
+	return retval;
+}
+/* }}} */
+
+static void php_v8js_v8_write_property(zval *object, zval *member, zval *value TSRMLS_DC) /* {{{ */
+{
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+	if (obj->v8obj->IsObject() && !obj->v8obj->IsFunction()) {
+		obj->v8obj->ToObject()->ForceSet(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)), zval_to_v8js(value TSRMLS_CC));
+	}
+}
+/* }}} */
+
+static void php_v8js_v8_unset_property(zval *object, zval *member TSRMLS_DC) /* {{{ */
+{
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+	if (obj->v8obj->IsObject() && !obj->v8obj->IsFunction()) {
+		obj->v8obj->ToObject()->ForceDelete(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)));
+	}
+}
+/* }}} */
+
+int php_v8js_v8_get_properties_hash(v8::Handle<v8::Value> jsValue, HashTable *retval, int flags TSRMLS_DC) /* {{{ */
+{
+	v8::Local<v8::Object> jsObj = jsValue->ToObject();
+
+	if (!jsObj.IsEmpty()) {
+		v8::Local<v8::Array> jsKeys = jsObj->GetPropertyNames();
+
+		for (unsigned i = 0; i < jsKeys->Length(); i++)
+		{
+			v8::Local<v8::String> jsKey = jsKeys->Get(i)->ToString();
+
+			/* Skip any prototype properties */
+			if (!jsObj->HasRealNamedProperty(jsKey) && !jsObj->HasRealNamedCallbackProperty(jsKey) && !jsObj->HasRealIndexedProperty(i)) {
+				continue;
+			}
+
+			v8::Local<v8::Value> jsVal = jsObj->Get(jsKey);
+			v8::String::Utf8Value cstr(jsKey);
+			const char *key = ToCString(cstr);
+			zval *value = NULL;
+
+			MAKE_STD_ZVAL(value);
+
+			if (v8js_to_zval(jsVal, value, flags TSRMLS_CC) == FAILURE) {
+				zval_ptr_dtor(&value);
+				return FAILURE;
+			}
+
+			if ((flags & V8JS_FLAG_FORCE_ARRAY) || jsValue->IsArray()) {
+				zend_symtable_update(retval, key, strlen(key) + 1, (void *)&value, sizeof(zval *), NULL);
+			} else {
+				zend_hash_update(retval, key, strlen(key) + 1, (void *) &value, sizeof(zval *), NULL);
+			}
+		}
+		return SUCCESS;
+	}
+	return FAILURE;
+}
+/* }}} */
+
+static HashTable *php_v8js_v8_get_properties(zval *object TSRMLS_DC) /* {{{ */
+{
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+	HashTable *retval;
+	
+	ALLOC_HASHTABLE(retval);
+	zend_hash_init(retval, 0, NULL, ZVAL_PTR_DTOR, 0);
+
+	v8::HandleScope local_scope;
+	v8::Handle<v8::Context> temp_context = v8::Context::New();
+	v8::Context::Scope temp_scope(temp_context);
+
+	if (php_v8js_v8_get_properties_hash(obj->v8obj, retval, obj->flags TSRMLS_CC) == SUCCESS) {
+		return retval;
+	}
+	return NULL;
+}
+/* }}} */
+
+static HashTable *php_v8js_v8_get_debug_info(zval *object, int *is_temp TSRMLS_DC) /* {{{ */
+{
+	*is_temp = 1;
+	return php_v8js_v8_get_properties(object TSRMLS_CC);
+}
+/* }}} */
+                        
+static zend_function *php_v8js_v8_get_method(zval **object_ptr, char *method, int method_len TSRMLS_DC) /* {{{ */
+{
+	zend_function *f;
+	v8::Local<v8::String> jsKey = V8JS_STRL(method, method_len);
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(*object_ptr TSRMLS_CC);
+
+	if (!obj->v8obj.IsEmpty() && obj->v8obj->IsObject() && !obj->v8obj->IsFunction()) {
+		v8::Local<v8::Object> jsObj = obj->v8obj->ToObject();
+		
+		if (jsObj->Has(jsKey) && jsObj->Get(jsKey)->IsFunction()) {
+			f = (zend_function *) ecalloc(1, sizeof(*f));
+			f->type = ZEND_OVERLOADED_FUNCTION_TEMPORARY;
+			f->common.function_name = estrndup(method, method_len);
+			return f;
+		}
+	}
+
+	return NULL;
+}
+/* }}} */
+
+static int php_v8js_v8_call_method(char *method, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */
+{
+	zval *object = this_ptr, ***argv = NULL;
+	int i = 0, argc = ZEND_NUM_ARGS();
+	php_v8js_object *obj;
+
+	obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+	if (obj->v8obj.IsEmpty()) {
+		zval_ptr_dtor(&object);
+		return FAILURE;
+	}
+
+	if (argc > 0) {
+		argv = (zval***)safe_emalloc(sizeof(zval**), argc, 0);
+		zend_get_parameters_array_ex(argc, argv);
+	}
+
+	v8::Local<v8::String> method_name = V8JS_SYML(method, strlen(method));
+	v8::Local<v8::Object> v8obj = obj->v8obj->ToObject();
+	v8::Local<v8::Function> cb;
+
+	if (method_name->Equals(V8JS_SYM(V8JS_V8_INVOKE_FUNC_NAME))) {
+		cb = v8::Local<v8::Function>::Cast(v8obj);
+	} else {
+		cb = v8::Local<v8::Function>::Cast(v8obj->Get(method_name));
+	} 
+
+	v8::Local<v8::Value> *jsArgv = new v8::Local<v8::Value>[argc];
+	v8::Local<v8::Value> js_retval;
+
+	for (i = 0; i < argc; i++) {
+		jsArgv[i] = v8::Local<v8::Value>::New(zval_to_v8js(*argv[i] TSRMLS_CC));
+	}
+
+	js_retval = cb->Call(V8JS_GLOBAL, argc, jsArgv);
+
+	zval_ptr_dtor(&object);
+
+	if (argc > 0) {
+		efree(argv);
+	}
+
+	if (return_value_used) {
+		return v8js_to_zval(js_retval, return_value, obj->flags TSRMLS_CC);
+	}
+
+	return SUCCESS;
+}
+/* }}} */
+
+static int php_v8js_v8_get_closure(zval *object, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) /* {{{ */
+{
+	zend_function *invoke;
+
+	php_v8js_object *obj = (php_v8js_object *) zend_object_store_get_object(object TSRMLS_CC);
+
+	if (!obj->v8obj->IsFunction()) {
+		return FAILURE;
+	}
+
+	invoke = (zend_function *) ecalloc(1, sizeof(*invoke));
+	invoke->type = ZEND_OVERLOADED_FUNCTION_TEMPORARY;
+	invoke->common.function_name = estrndup(V8JS_V8_INVOKE_FUNC_NAME, sizeof(V8JS_V8_INVOKE_FUNC_NAME) - 1);
+
+	*fptr_ptr = invoke;
+
+	if (zobj_ptr) {
+		*zobj_ptr = object;
+	}
+
+	*ce_ptr = NULL;
+
+	return SUCCESS;
+}
+/* }}} */
+
+static void php_v8js_v8_free_storage(void *object, zend_object_handle handle TSRMLS_DC) /* {{{ */
+{
+	php_v8js_object *c = (php_v8js_object *) object;
+
+	zend_object_std_dtor(&c->std TSRMLS_CC);
+
+	if (!c->v8obj.IsEmpty()) {
+		c->v8obj.Dispose();
+	}
+
+	efree(object);
+}
+/* }}} */
+
+static zend_object_value php_v8js_v8_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */
+{
+	zend_object_value retval;
+	php_v8js_object *c;
+	
+	c = (php_v8js_object *) ecalloc(1, sizeof(*c));
+
+	zend_object_std_init(&c->std, ce TSRMLS_CC);
+
+	retval.handle = zend_objects_store_put(c, NULL, (zend_objects_free_object_storage_t) php_v8js_v8_free_storage, NULL TSRMLS_CC);
+	retval.handlers = &v8_object_handlers;
+
+	return retval;
+}
+/* }}} */
+
+void php_v8js_create_v8(zval *res, v8::Handle<v8::Value> value, int flags TSRMLS_DC) /* {{{ */
+{
+	php_v8js_object *c;
+
+	object_init_ex(res, value->IsFunction() ? php_ce_v8_function : php_ce_v8_object);
+
+	c = (php_v8js_object *) zend_object_store_get_object(res TSRMLS_CC);
+
+	c->v8obj = v8::Persistent<v8::Value>::New(value);
+	c->flags = flags;
+}
+/* }}} */
+
+/* }}} V8 */
+
+/* {{{ Class: V8Js */
+
+static void php_v8js_free_storage(void *object TSRMLS_DC) /* {{{ */
+{
+	php_v8js_ctx *c = (php_v8js_ctx *) object;
+
+	zend_object_std_dtor(&c->std TSRMLS_CC);
+
+	if (c->pending_exception) {
+		zval_ptr_dtor(&c->pending_exception);
+	}
+	
+	c->object_name.Dispose();
+
+	/* Clear global object, dispose context */
+	if (!c->context.IsEmpty()) {
+		c->context.Dispose();
+		c->context.Clear();
+		V8JSG(disposed_contexts) = v8::V8::ContextDisposedNotification();
+#if V8JS_DEBUG
+		fprintf(stderr, "Context dispose notification sent (%d)\n", V8JSG(disposed_contexts));
+		fflush(stderr);
+#endif
+	}
+
+	efree(object);
+}
+/* }}} */
+
+static zend_object_value php_v8js_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */
+{
+	zend_object_value retval;
+	php_v8js_ctx *c;
+
+	c = (php_v8js_ctx *) ecalloc(1, sizeof(*c));
+	zend_object_std_init(&c->std, ce TSRMLS_CC);
+
+	retval.handle = zend_objects_store_put(c, NULL, (zend_objects_free_object_storage_t) php_v8js_free_storage, NULL TSRMLS_CC);
+	retval.handlers = &v8js_object_handlers;
+
+	return retval;
+}
+/* }}} */
+
+static void _php_v8js_free_ext_strarr(const char **arr, int count) /* {{{ */
+{
+	int i;
+
+	if (arr) {
+		for (i = 0; i < count; i++) {
+			if (arr[i]) {
+				free((void *) arr[i]);
+			}
+		}
+		free(arr);
+	}
+}
+/* }}} */
+
+static void php_v8js_jsext_dtor(php_v8js_jsext *jsext) /* {{{ */
+{
+	if (jsext->deps_ht) {
+		zend_hash_destroy(jsext->deps_ht);
+		free(jsext->deps_ht);
+	}
+	if (jsext->deps) {
+		_php_v8js_free_ext_strarr(jsext->deps, jsext->deps_count);
+	}
+	free(jsext->name);
+	free(jsext->source);
+}
+/* }}} */
+
+static int _php_v8js_create_ext_strarr(const char ***retval, int count, HashTable *ht) /* {{{ */
+{
+	const char **exts = NULL;
+	HashPosition pos;
+	zval **tmp;
+	int i = 0;
+
+	exts = (const char **) calloc(1, count * sizeof(char *));
+	zend_hash_internal_pointer_reset_ex(ht, &pos);
+	while (zend_hash_get_current_data_ex(ht, (void **) &tmp, &pos) == SUCCESS) {
+		if (Z_TYPE_PP(tmp) == IS_STRING) {
+			exts[i++] = zend_strndup(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
+		} else {
+			_php_v8js_free_ext_strarr(exts, i);
+			return FAILURE;
+		}
+		zend_hash_move_forward_ex(ht, &pos);
+	}
+	*retval = exts;
+
+	return SUCCESS;
+}
+/* }}} */
+
+static void php_v8js_fatal_error_handler(const char *location, const char *message) /* {{{ */
+{
+	if (location) {
+		zend_error(E_ERROR, "%s %s", location, message);
+	} else {
+		zend_error(E_ERROR, "%s", message);
+	}
+}
+/* }}} */
+
+static void php_v8js_init(TSRMLS_D) /* {{{ */
+{
+	/* Run only once */
+	if (V8JSG(v8_initialized)) {
+		return;
+	}
+
+	/* Set V8 command line flags (must be done before V8::Initialize()!) */
+	if (V8JSG(v8_flags)) {
+		v8::V8::SetFlagsFromString(Z_STRVAL_P(V8JSG(v8_flags)), Z_STRLEN_P(V8JSG(v8_flags)));
+	}
+
+	/* Initialize V8 */
+	v8::V8::Initialize();
+
+	/* Redirect fatal errors to PHP error handler */
+	v8::V8::SetFatalErrorHandler(php_v8js_fatal_error_handler);
+
+	/* Run only once */
+	V8JSG(v8_initialized) = 1;
+}
+/* }}} */
+
+/* {{{ proto void V8Js::__construct([string object_name [, array variables [, array extensions [, bool report_uncaught_exceptions]]])
+   __construct for V8Js */
+static PHP_METHOD(V8Js, __construct)
+{
+	char *object_name = NULL, *class_name = NULL;
+	int object_name_len = 0, free = 0;
+	zend_uint class_name_len = 0;
+	zend_bool report_uncaught = 1;
+	zval *vars_arr = NULL, *exts_arr = NULL;
+	const char **exts = NULL;
+	int exts_count = 0;
+
+	php_v8js_ctx *c = (php_v8js_ctx *) zend_object_store_get_object(getThis() TSRMLS_CC);
+
+	if (!c->context.IsEmpty()) {
+		/* called __construct() twice, bail out */
+		return;
+	}
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|saab", &object_name, &object_name_len, &vars_arr, &exts_arr, &report_uncaught) == FAILURE) {
+		return;
+	}
+
+	/* Throw PHP exception if uncaught exceptions exist */
+	c->report_uncaught = report_uncaught;
+	c->pending_exception = NULL;
+	c->in_execution = 0;
+
+	/* Initialize V8 */
+	php_v8js_init(TSRMLS_C);
+
+	/* Include extensions used by this context */
+	/* Note: Extensions registered with auto_enable do not need to be added separately like this. */
+	if (exts_arr)
+	{
+		exts_count = zend_hash_num_elements(Z_ARRVAL_P(exts_arr));
+		if (_php_v8js_create_ext_strarr(&exts, exts_count, Z_ARRVAL_P(exts_arr)) == FAILURE) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid extensions array passed");
+			return;
+		}
+	}
+
+	/* Declare configuration for extensions */
+	v8::ExtensionConfiguration extension_conf(exts_count, exts);
+
+	/* Handle scope */
+	v8::HandleScope handle_scope;
+
+	/* Create global template for global object */
+	if (V8JSG(global_template).IsEmpty()) {
+		V8JSG(global_template) = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New());
+		V8JSG(global_template)->SetClassName(V8JS_SYM("V8Js"));
+
+		/* Register builtin methods */
+		php_v8js_register_methods(V8JSG(global_template)->InstanceTemplate());
+	}
+
+	/* Create context */
+	c->context = v8::Context::New(&extension_conf, V8JSG(global_template)->InstanceTemplate());
+
+	if (exts) {
+		_php_v8js_free_ext_strarr(exts, exts_count);
+	}
+
+	/* If extensions have errors, context will be empty. (NOTE: This is V8 stuff, they expect the passed sources to compile :) */
+	if (c->context.IsEmpty()) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to create V8 context. Check that registered extensions do not have errors.");
+		ZVAL_NULL(getThis());
+		return;
+	}
+
+	/* Enter context */
+	v8::Context::Scope context_scope(c->context);
+
+	/* Create the PHP container object's function template */
+	v8::Local<v8::FunctionTemplate> php_obj_t = v8::FunctionTemplate::New();
+
+	/* Set class name for PHP object */
+	free = !zend_get_object_classname(getThis(), &class_name, &class_name_len TSRMLS_CC);
+	php_obj_t->SetClassName(V8JS_SYML(class_name, class_name_len));
+
+	if (free) {
+		efree(class_name);
+	}
+	
+	/* Register Get accessor for passed variables */
+	if (vars_arr && zend_hash_num_elements(Z_ARRVAL_P(vars_arr)) > 0) {
+		php_v8js_register_accessors(php_obj_t->InstanceTemplate(), vars_arr TSRMLS_CC);
+	}
+
+	/* Set name for the PHP JS object */
+	c->object_name = v8::Persistent<v8::String>::New((object_name_len) ? V8JS_SYML(object_name, object_name_len) : V8JS_SYM("PHP"));
+
+	/* Add the PHP object into global object */
+	V8JS_GLOBAL->Set(c->object_name, php_obj_t->InstanceTemplate()->NewInstance(), v8::ReadOnly);
+}
+/* }}} */
+
+#define V8JS_BEGIN_CTX(ctx, object) \
+	php_v8js_ctx *(ctx); \
+	\
+	if (!V8JSG(v8_initialized)) { \
+		zend_error(E_ERROR, "V8 not initialized"); \
+		return; \
+	} \
+	\
+	(ctx) = (php_v8js_ctx *) zend_object_store_get_object(object TSRMLS_CC); \
+	v8::Context::Scope context_scope((ctx)->context);
+
+/* {{{ proto mixed V8Js::executeString(string script [, string identifier [, int flags]])
+ */
+static PHP_METHOD(V8Js, executeString)
+{
+	char *str = NULL, *identifier = NULL;
+	int str_len = 0, identifier_len = 0;
+	long flags = V8JS_FLAG_NONE;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|sl", &str, &str_len, &identifier, &identifier_len, &flags) == FAILURE) {
+		return;
+	}
+
+	V8JS_BEGIN_CTX(c, getThis())
+
+	/* Catch JS exceptions */
+	v8::TryCatch try_catch;
+
+	v8::HandleScope handle_scope;
+
+	/* Set script identifier */
+	v8::Local<v8::String> sname = identifier_len ? V8JS_SYML(identifier, identifier_len) : V8JS_SYM("V8Js::executeString()");
+
+	/* Compiles a string context independently. TODO: Add a php function which calls this and returns the result as resource which can be executed later. */
+	v8::Local<v8::String> source = v8::String::New(str, str_len);
+	v8::Local<v8::Script> script = v8::Script::New(source, sname);
+
+	/* Compile errors? */
+	if (script.IsEmpty()) {
+		php_v8js_throw_exception(&try_catch TSRMLS_CC);
+		return;
+	}
+
+	/* Set flags for runtime use */
+	V8JS_GLOBAL_SET_FLAGS(flags);
+
+	/* Execute script */
+	c->in_execution++;
+	v8::Local<v8::Value> result = script->Run();
+	c->in_execution--;
+
+	/* Script possibly terminated, return immediately */
+	if (!try_catch.CanContinue()) {
+		/* TODO: throw PHP exception here? */
+		return;
+	}
+
+	/* There was pending exception left from earlier executions -> throw to PHP */
+	if (c->pending_exception) {
+		zend_throw_exception_object(c->pending_exception TSRMLS_CC);
+		c->pending_exception = NULL;
+	}
+
+	/* Handle runtime JS exceptions */
+	if (try_catch.HasCaught()) {
+
+		/* Pending exceptions are set only in outer caller, inner caller exceptions are always rethrown */
+		if (c->in_execution < 1) {
+
+			/* Report immediately if report_uncaught is true */
+			if (c->report_uncaught) {
+				php_v8js_throw_exception(&try_catch TSRMLS_CC);
+				return;
+			}
+
+			/* Exception thrown from JS, preserve it for future execution */
+			if (result.IsEmpty()) {
+				MAKE_STD_ZVAL(c->pending_exception);
+				php_v8js_create_exception(c->pending_exception, &try_catch TSRMLS_CC);
+			}
+		}
+
+		/* Rethrow back to JS */
+		try_catch.ReThrow();
+		return;
+	}
+
+	/* Convert V8 value to PHP value */
+	if (!result.IsEmpty()) {
+		v8js_to_zval(result, return_value, flags TSRMLS_CC);
+	}
+}
+/* }}} */
+
+/* {{{ proto mixed V8Js::getPendingException()
+ */
+static PHP_METHOD(V8Js, getPendingException)
+{
+	php_v8js_ctx *c;
+
+	if (zend_parse_parameters_none() == FAILURE) {
+		return;
+	}
+
+	c = (php_v8js_ctx *) zend_object_store_get_object(getThis() TSRMLS_CC);
+
+	if (c->pending_exception) {
+		RETURN_ZVAL(c->pending_exception, 1, 0);
+	}
+}
+/* }}} */
+
+static void php_v8js_persistent_zval_ctor(zval **p) /* {{{ */
+{
+	zval *orig_ptr = *p;
+	*p = (zval *) malloc(sizeof(zval));
+	**p = *orig_ptr;
+	switch (Z_TYPE_P(*p)) {
+		case IS_STRING:
+			Z_STRVAL_P(*p) = (char *) zend_strndup(Z_STRVAL_P(*p), Z_STRLEN_P(*p));
+			break;
+		default:
+			zend_bailout();
+	}
+	INIT_PZVAL(*p);
+}
+/* }}} */
+
+static void php_v8js_persistent_zval_dtor(zval **p) /* {{{ */
+{
+	switch (Z_TYPE_P(*p)) {
+		case IS_STRING:
+			free(Z_STRVAL_P(*p));
+			break;
+		default:
+			zend_bailout();
+	}
+	free(*p);
+}
+/* }}} */
+
+static int php_v8js_register_extension(char *name, uint name_len, char *source, uint source_len, zval *deps_arr, zend_bool auto_enable TSRMLS_DC) /* {{{ */
+{
+	php_v8js_jsext *jsext = NULL;
+
+	if (!V8JSG(extensions)) {
+		V8JSG(extensions) = (HashTable *) malloc(sizeof(HashTable));
+		zend_hash_init(V8JSG(extensions), 1, NULL, (dtor_func_t) php_v8js_jsext_dtor, 1);
+	} else if (zend_hash_exists(V8JSG(extensions), name, name_len + 1)) {
+		return FAILURE;
+	}
+
+	jsext = (php_v8js_jsext *) calloc(1, sizeof(php_v8js_jsext));
+
+	if (deps_arr) {
+		jsext->deps_count = zend_hash_num_elements(Z_ARRVAL_P(deps_arr));
+
+		if (_php_v8js_create_ext_strarr(&jsext->deps, jsext->deps_count, Z_ARRVAL_P(deps_arr)) == FAILURE) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid dependancy array passed");
+			php_v8js_jsext_dtor(jsext);
+			free(jsext);
+			return FAILURE;
+		}
+	}
+
+	jsext->auto_enable = auto_enable;
+	jsext->name = zend_strndup(name, name_len);
+              	jsext->source = zend_strndup(source, source_len);
+
+	if (jsext->deps) {
+		jsext->deps_ht = (HashTable *) malloc(sizeof(HashTable));
+		zend_hash_init(jsext->deps_ht, jsext->deps_count, NULL, (dtor_func_t) php_v8js_persistent_zval_dtor, 1);
+		zend_hash_copy(jsext->deps_ht, Z_ARRVAL_P(deps_arr), (copy_ctor_func_t) php_v8js_persistent_zval_ctor, NULL, sizeof(zval *));
+	}
+
+	if (zend_hash_add(V8JSG(extensions), name, name_len + 1, jsext, sizeof(php_v8js_jsext), NULL) == FAILURE) {
+		php_v8js_jsext_dtor(jsext);
+		free(jsext);
+		return FAILURE;
+	}
+
+	v8::Extension *extension = new v8::Extension(jsext->name, jsext->source, jsext->deps_count, jsext->deps);
+	extension->set_auto_enable(auto_enable ? true : false);
+	v8::RegisterExtension(extension);
+
+	free(jsext);
+
+	return SUCCESS;
+}
+/* }}} */
+
+/* {{{ proto bool V8Js::registerExtension(string ext_name, string script [, array deps [, bool auto_enable]])
+ */
+static PHP_METHOD(V8Js, registerExtension)
+{
+	char *ext_name, *script;
+	zval *deps_arr = NULL;
+	int ext_name_len, script_len;
+	zend_bool auto_enable = 0;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|ab", &ext_name, &ext_name_len, &script, &script_len, &deps_arr, &auto_enable) == FAILURE) {
+		return;
+	}
+
+	if (!ext_name_len) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Extension name cannot be empty");
+	} else if (!script_len) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Script cannot be empty");
+	} else if (php_v8js_register_extension(ext_name, ext_name_len, script, script_len, deps_arr, auto_enable TSRMLS_CC) == SUCCESS) {
+		RETURN_TRUE;
+	}
+	RETURN_FALSE;
+}
+/* }}} */
+
+/* ## Static methods ## */
+
+/* {{{ proto array V8Js::getExtensions()
+ */
+static PHP_METHOD(V8Js, getExtensions)
+{
+	php_v8js_jsext *jsext;
+	zval *ext, *deps_arr;
+	HashPosition pos;
+	ulong index;
+	char *key;
+	uint key_len;
+
+	if (zend_parse_parameters_none() == FAILURE) {
+		return;
+	}
+
+	array_init(return_value);
+	if (V8JSG(extensions)) {
+		zend_hash_internal_pointer_reset_ex(V8JSG(extensions), &pos);
+		while (zend_hash_get_current_data_ex(V8JSG(extensions), (void **) &jsext, &pos) == SUCCESS) {
+			if (zend_hash_get_current_key_ex(V8JSG(extensions), &key, &key_len, &index, 0, &pos) == HASH_KEY_IS_STRING) {
+				MAKE_STD_ZVAL(ext)
+				array_init(ext);
+				add_assoc_bool_ex(ext, ZEND_STRS("auto_enable"), jsext->auto_enable);
+				if (jsext->deps_ht) {
+					MAKE_STD_ZVAL(deps_arr);
+					array_init(deps_arr);
+					zend_hash_copy(Z_ARRVAL_P(deps_arr), jsext->deps_ht, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+					add_assoc_zval_ex(ext, ZEND_STRS("deps"), deps_arr);
+				}
+				add_assoc_zval_ex(return_value, key, key_len, ext);
+			}
+			zend_hash_move_forward_ex(V8JSG(extensions), &pos);
+		}
+	}
+}
+/* }}} */
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_construct, 0, 0, 0)
+	ZEND_ARG_INFO(0, object_name)
+	ZEND_ARG_INFO(0, variables)
+	ZEND_ARG_INFO(0, extensions)
+	ZEND_ARG_INFO(0, report_uncaught_exceptions)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_executestring, 0, 0, 1)
+	ZEND_ARG_INFO(0, script)
+	ZEND_ARG_INFO(0, identifier)
+	ZEND_ARG_INFO(0, flags)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_v8js_getpendingexception, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_registerextension, 0, 0, 2)
+	ZEND_ARG_INFO(0, ext_name)
+	ZEND_ARG_INFO(0, script)
+	ZEND_ARG_INFO(0, auto_enable)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_v8js_getextensions, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_v8jsexception_no_args, 0)
+ZEND_END_ARG_INFO()
+/* }}} */
+
+static const zend_function_entry v8js_methods[] = { /* {{{ */
+	PHP_ME(V8Js,	__construct,			arginfo_v8js_construct,				ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+	PHP_ME(V8Js,	executeString,			arginfo_v8js_executestring,			ZEND_ACC_PUBLIC)
+	PHP_ME(V8Js,	getPendingException,	arginfo_v8js_getpendingexception,	ZEND_ACC_PUBLIC)
+	PHP_ME(V8Js,	registerExtension,		arginfo_v8js_registerextension,		ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+	PHP_ME(V8Js,	getExtensions,			arginfo_v8js_getextensions,			ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+	{NULL, NULL, NULL}
+};
+/* }}} */
+
+/* V8Js object handlers */
+
+static void php_v8js_write_property(zval *object, zval *member, zval *value TSRMLS_DC) /* {{{ */
+{
+	V8JS_BEGIN_CTX(c, object)
+
+	v8::HandleScope handle_scope;
+
+	/* Global PHP JS object */
+	v8::Local<v8::Object> jsobj = V8JS_GLOBAL->Get(c->object_name)->ToObject();
+
+	/* Write value to PHP JS object */
+	jsobj->ForceSet(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)), zval_to_v8js(value TSRMLS_CC), v8::ReadOnly);
+
+	/* Write value to PHP object */
+	std_object_handlers.write_property(object, member, value TSRMLS_CC);
+}
+/* }}} */
+
+static void php_v8js_unset_property(zval *object, zval *member TSRMLS_DC) /* {{{ */
+{
+	V8JS_BEGIN_CTX(c, object)
+
+	v8::HandleScope handle_scope;
+
+	/* Global PHP JS object */
+	v8::Local<v8::Object> jsobj = V8JS_GLOBAL->Get(c->object_name)->ToObject();
+	
+	/* Delete value from PHP JS object */
+	jsobj->ForceDelete(V8JS_SYML(Z_STRVAL_P(member), Z_STRLEN_P(member)));
+
+	/* Unset from PHP object */
+	std_object_handlers.unset_property(object, member TSRMLS_CC);
+}
+/* }}} */
+
+/* }}} V8Js */
+
+/* {{{ Class: V8JsException */
+
+static void php_v8js_create_exception(zval *return_value, v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
+{
+	v8::String::Utf8Value exception(try_catch->Exception());
+	const char *exception_string = ToCString(exception);
+	v8::Handle<v8::Message> tc_message = try_catch->Message();
+	const char *filename_string, *sourceline_string;
+	char *message_string;
+	int linenum, message_len;
+
+	object_init_ex(return_value, php_ce_v8js_exception);
+
+#define PHPV8_EXPROP(type, name, value) \
+	zend_update_property##type(php_ce_v8js_exception, return_value, #name, sizeof(#name) - 1, value TSRMLS_CC);
+
+	if (tc_message.IsEmpty()) {
+		message_len = spprintf(&message_string, 0, "%s", exception_string);
+	}
+	else
+	{
+		v8::String::Utf8Value filename(tc_message->GetScriptResourceName());
+		filename_string = ToCString(filename);
+		PHPV8_EXPROP(_string, JsFileName, filename_string);
+
+		v8::String::Utf8Value sourceline(tc_message->GetSourceLine());
+		sourceline_string = ToCString(sourceline);
+		PHPV8_EXPROP(_string, JsSourceLine, sourceline_string);
+
+		linenum = tc_message->GetLineNumber();
+		PHPV8_EXPROP(_long, JsLineNumber, linenum);
+
+		message_len = spprintf(&message_string, 0, "%s:%d: %s", filename_string, linenum, exception_string);
+
+		v8::String::Utf8Value stacktrace(try_catch->StackTrace());
+		if (stacktrace.length() > 0) {
+			const char* stacktrace_string = ToCString(stacktrace);
+			PHPV8_EXPROP(_string, JsTrace, stacktrace_string);
+		}
+	}
+
+	PHPV8_EXPROP(_string, message, message_string);
+
+	efree(message_string);
+}
+/* }}} */
+
+static void php_v8js_throw_exception(v8::TryCatch *try_catch TSRMLS_DC) /* {{{ */
+{
+	v8::String::Utf8Value exception(try_catch->Exception());
+	const char *exception_string = ToCString(exception);
+	zval *zexception = NULL;
+
+	if (try_catch->Message().IsEmpty()) {
+		zend_throw_exception(php_ce_v8js_exception, (char *) exception_string, 0 TSRMLS_CC);
+	} else {
+		MAKE_STD_ZVAL(zexception);
+		php_v8js_create_exception(zexception, try_catch TSRMLS_CC);
+		zend_throw_exception_object(zexception TSRMLS_CC);
+	}
+}
+/* }}} */
+
+#define V8JS_EXCEPTION_METHOD(property) \
+	static PHP_METHOD(V8JsException, get##property) \
+	{ \
+		zval *value; \
+		\
+		if (zend_parse_parameters_none() == FAILURE) { \
+			return; \
+		} \
+		value = zend_read_property(php_ce_v8js_exception, getThis(), #property, sizeof(#property) - 1, 0 TSRMLS_CC); \
+		*return_value = *value; \
+		zval_copy_ctor(return_value); \
+		INIT_PZVAL(return_value); \
+	}
+
+/* {{{ proto string V8JsException::getJsFileName()
+ */
+V8JS_EXCEPTION_METHOD(JsFileName);
+/* }}} */
+
+/* {{{ proto string V8JsException::getJsLineNumber()
+ */
+V8JS_EXCEPTION_METHOD(JsLineNumber);
+/* }}} */
+
+/* {{{ proto string V8JsException::getJsSourceLine()
+ */
+V8JS_EXCEPTION_METHOD(JsSourceLine);
+/* }}} */
+
+/* {{{ proto string V8JsException::getJsTrace()
+ */
+V8JS_EXCEPTION_METHOD(JsTrace);	
+/* }}} */
+
+static const zend_function_entry v8js_exception_methods[] = { /* {{{ */
+	PHP_ME(V8JsException,	getJsFileName,		arginfo_v8jsexception_no_args,	ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+	PHP_ME(V8JsException,	getJsLineNumber,	arginfo_v8jsexception_no_args,	ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+	PHP_ME(V8JsException,	getJsSourceLine,	arginfo_v8jsexception_no_args,	ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+	PHP_ME(V8JsException,	getJsTrace,			arginfo_v8jsexception_no_args,	ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+	{NULL, NULL, NULL}
+};
+/* }}} */
+
+/* }}} V8JsException */
+
+/* {{{ PHP_MINIT_FUNCTION
+ */
+static PHP_MINIT_FUNCTION(v8js)
+{
+	zend_class_entry ce;
+
+	/* V8Object Class */
+	INIT_CLASS_ENTRY(ce, "V8Object", NULL);
+	php_ce_v8_object = zend_register_internal_class(&ce TSRMLS_CC);
+	php_ce_v8_object->ce_flags |= ZEND_ACC_FINAL;
+	php_ce_v8_object->create_object = php_v8js_v8_new;
+
+	/* V8Function Class */
+	INIT_CLASS_ENTRY(ce, "V8Function", NULL);
+	php_ce_v8_function = zend_register_internal_class(&ce TSRMLS_CC);
+	php_ce_v8_function->ce_flags |= ZEND_ACC_FINAL;
+	php_ce_v8_function->create_object = php_v8js_v8_new;
+
+	/* V8<Object|Function> handlers */
+	memcpy(&v8_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+	v8_object_handlers.clone_obj = NULL;
+	v8_object_handlers.cast_object = NULL;
+	v8_object_handlers.get_constructor = NULL;
+	v8_object_handlers.get_property_ptr_ptr = NULL;
+//	v8_object_handlers.has_property = php_v8js_v8_has_property; // Not implemented yet
+	v8_object_handlers.read_property = php_v8js_v8_read_property;
+	v8_object_handlers.write_property = php_v8js_v8_write_property;
+	v8_object_handlers.unset_property = php_v8js_v8_unset_property;
+	v8_object_handlers.get_properties = php_v8js_v8_get_properties;
+	v8_object_handlers.get_method = php_v8js_v8_get_method;
+	v8_object_handlers.call_method = php_v8js_v8_call_method;
+	v8_object_handlers.get_debug_info = php_v8js_v8_get_debug_info;
+	v8_object_handlers.get_closure = php_v8js_v8_get_closure;
+
+	/* V8Js Class */
+	INIT_CLASS_ENTRY(ce, "V8Js", v8js_methods);
+	php_ce_v8js = zend_register_internal_class(&ce TSRMLS_CC);
+	php_ce_v8js->create_object = php_v8js_new;
+
+	/* V8Js handlers */
+	memcpy(&v8js_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+	v8js_object_handlers.clone_obj = NULL;
+	v8js_object_handlers.write_property = php_v8js_write_property;
+	v8js_object_handlers.unset_property = php_v8js_unset_property;
+
+	/* V8Js Class Constants */
+	zend_declare_class_constant_string(php_ce_v8js, ZEND_STRL("V8_VERSION"),		PHP_V8_VERSION			TSRMLS_CC);
+	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("FLAG_NONE"),			V8JS_FLAG_NONE			TSRMLS_CC);
+	zend_declare_class_constant_long(php_ce_v8js, ZEND_STRL("FLAG_FORCE_ARRAY"),	V8JS_FLAG_FORCE_ARRAY	TSRMLS_CC);
+
+	/* V8JsException Class */
+	INIT_CLASS_ENTRY(ce, "V8JsException", v8js_exception_methods);
+	php_ce_v8js_exception = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC);
+	php_ce_v8js_exception->ce_flags |= ZEND_ACC_FINAL;
+
+	/* Add custom JS specific properties */
+	zend_declare_property_null(php_ce_v8js_exception, ZEND_STRL("JsFileName"),		ZEND_ACC_PROTECTED TSRMLS_CC);
+	zend_declare_property_null(php_ce_v8js_exception, ZEND_STRL("JsLineNumber"),	ZEND_ACC_PROTECTED TSRMLS_CC);
+	zend_declare_property_null(php_ce_v8js_exception, ZEND_STRL("JsSourceLine"),	ZEND_ACC_PROTECTED TSRMLS_CC);
+	zend_declare_property_null(php_ce_v8js_exception, ZEND_STRL("JsTrace"),			ZEND_ACC_PROTECTED TSRMLS_CC);
+
+	REGISTER_INI_ENTRIES();
+
+	return SUCCESS;
+}
+/* }}} */
+
+static void php_v8js_force_v8_gc(void) /* {{{ */
+{
+#if V8JS_DEBUG
+	TSRMLS_FETCH();
+	fprintf(stderr, "############ Running V8 Idle notification: disposed/max_disposed %d/%d ############\n", V8JSG(disposed_contexts), V8JSG(max_disposed_contexts));
+	fflush(stderr);
+#endif
+
+	while (!v8::V8::IdleNotification()) {
+#if V8JS_DEBUG
+		fprintf(stderr, ".");
+		fflush(stderr);
+#endif
+	}
+
+#if V8JS_DEBUG
+	fprintf(stderr, "\n");
+	fflush(stderr);
+#endif
+}
+/* }}} */
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION
+ */
+static PHP_MSHUTDOWN_FUNCTION(v8js)
+{
+	UNREGISTER_INI_ENTRIES();
+
+	v8::V8::Dispose();
+
+	if (V8JSG(extensions)) {
+		zend_hash_destroy(V8JSG(extensions));
+		free(V8JSG(extensions));
+		V8JSG(extensions) = NULL;
+	}
+
+	return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_RSHUTDOWN_FUNCTION
+ */
+static PHP_RSHUTDOWN_FUNCTION(v8js)
+{
+#if V8JS_DEBUG
+	v8::HeapStatistics stats;
+	v8::V8::GetHeapStatistics(&stats);
+	float used = stats.used_heap_size() / 1024.0 / 1024.0;
+	float total = stats.total_heap_size() / 1024.0 / 1024.0;
+
+	fprintf(stderr, "### RSHUTDOWN ###\n");
+	fprintf(stderr, "############ Heap Used/Total %.2f/%.2f MB ############\n", used, total);
+	fflush(stderr);
+#endif
+
+	/* Force V8 to do as much GC as possible */
+	if (V8JSG(max_disposed_contexts) && (V8JSG(disposed_contexts) > V8JSG(max_disposed_contexts))) {
+		php_v8js_force_v8_gc();
+	}
+
+	return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION
+ */
+static PHP_MINFO_FUNCTION(v8js)
+{
+	php_info_print_table_start();
+	php_info_print_table_header(2, "V8 Javascript Engine", "enabled");
+	php_info_print_table_header(2, "V8 Engine Compiled Version", PHP_V8_VERSION);
+	php_info_print_table_header(2, "V8 Engine Linked Version", v8::V8::GetVersion());
+	php_info_print_table_header(2, "Version", V8JS_VERSION);
+	php_info_print_table_end();
+
+	DISPLAY_INI_ENTRIES();
+}
+/* }}} */
+
+/* {{{ PHP_GINIT_FUNCTION
+ */
+static PHP_GINIT_FUNCTION(v8js)
+{
+	v8js_globals->extensions = NULL;
+	v8js_globals->disposed_contexts = 0;
+	v8js_globals->v8_initialized = 0;
+	v8js_globals->v8_flags = NULL;
+}
+/* }}} */
+
+/* {{{ v8js_functions[] */
+static const zend_function_entry v8js_functions[] = {
+	{NULL, NULL, NULL}
+};
+/* }}} */
+
+/* {{{ v8js_module_entry
+ */
+zend_module_entry v8js_module_entry = {
+	STANDARD_MODULE_HEADER_EX,
+	NULL,
+	NULL,
+	"v8js",
+	v8js_functions,
+	PHP_MINIT(v8js),
+	PHP_MSHUTDOWN(v8js),
+	NULL,
+	PHP_RSHUTDOWN(v8js),
+	PHP_MINFO(v8js),
+	V8JS_VERSION,
+	PHP_MODULE_GLOBALS(v8js),
+	PHP_GINIT(v8js),
+	NULL,
+	NULL,
+	STANDARD_MODULE_PROPERTIES_EX
+};
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 595 - 0
v8js_convert.cc

@@ -0,0 +1,595 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include "php.h"
+#include "ext/date/php_date.h"
+#include "zend_interfaces.h"
+#include "zend_closures.h"
+}
+
+#include "php_v8js_macros.h"
+#include <v8.h>
+
+/* Callback for PHP methods and functions */
+static v8::Handle<v8::Value> php_v8js_php_callback(const v8::Arguments &args) /* {{{ */
+{
+	v8::Handle<v8::Value> return_value;
+	zval *value = reinterpret_cast<zval *>(args.This()->GetPointerFromInternalField(0));
+	zend_function *method_ptr;
+	zend_fcall_info fci;
+	zend_fcall_info_cache fcc;
+	zval fname, *retval_ptr = NULL, **argv = NULL;
+	zend_class_entry *ce = Z_OBJCE_P(value);
+	zend_uint argc = args.Length(), min_num_args = 0, max_num_args = 0;
+	char *error;
+	int error_len, i, flags = V8JS_FLAG_NONE;
+	TSRMLS_FETCH();
+
+	/* Set method_ptr from v8::External or fetch the closure invoker */
+	if (!args.Data().IsEmpty() && args.Data()->IsExternal()) {
+		method_ptr = static_cast<zend_function *>(v8::External::Unwrap(args.Data()));
+	} else {
+		method_ptr = zend_get_closure_invoke_method(value TSRMLS_CC);
+	}
+
+	/* Set parameter limits */
+	min_num_args = method_ptr->common.required_num_args;
+	max_num_args = method_ptr->common.num_args;
+
+	/* Function name to call */
+	ZVAL_STRING(&fname, method_ptr->common.function_name, 0);
+
+	/* zend_fcall_info */
+	fci.size = sizeof(fci);
+	fci.function_table = &ce->function_table;
+	fci.function_name = &fname;
+	fci.symbol_table = NULL;
+	fci.object_ptr = value;
+	fci.retval_ptr_ptr = &retval_ptr;
+	fci.param_count = 0;
+
+	/* Check for passed vs required number of arguments */
+	if (argc < min_num_args)
+	{
+		error_len = spprintf(&error, 0,
+			"%s::%s() expects %s %d parameter%s, %d given",
+				ce->name,
+				method_ptr->common.function_name,
+				min_num_args == max_num_args ? "exactly" : argc < min_num_args ? "at least" : "at most",
+				argc < min_num_args ? min_num_args : max_num_args,
+				(argc < min_num_args ? min_num_args : max_num_args) == 1 ? "" : "s",
+				argc);
+
+		return_value = V8JS_THROW(TypeError, error, error_len);
+		if (ce == zend_ce_closure) {
+			efree(method_ptr->internal_function.function_name);
+			efree(method_ptr);
+		}
+		efree(error);
+		return return_value;
+	}
+
+	/* Convert parameters passed from V8 */
+	if (argc) {
+		flags = V8JS_GLOBAL_GET_FLAGS();
+		fci.params = (zval ***) safe_emalloc(argc, sizeof(zval **), 0);
+		argv = (zval **) safe_emalloc(argc, sizeof(zval *), 0);
+		for (i = 0; i < argc; i++) {
+			MAKE_STD_ZVAL(argv[i]);
+			if (v8js_to_zval(args[i], argv[i], flags TSRMLS_CC) == FAILURE) {
+				fci.param_count++;
+				error_len = spprintf(&error, 0, "converting parameter #%d passed to %s() failed", i + 1, method_ptr->common.function_name);
+				return_value = V8JS_THROW(Error, error, error_len);
+				efree(error);
+				goto failure;
+			}
+			fci.params[fci.param_count++] = &argv[i];
+ 		}
+	} else {
+		fci.params = NULL;
+	}
+	fci.no_separation = 1;
+
+	/* zend_fcall_info_cache */
+	fcc.initialized = 1;
+	fcc.function_handler = method_ptr;
+	fcc.calling_scope = ce;
+	fcc.called_scope = ce;
+	fcc.object_ptr = value;
+
+	/* Call the method */
+	zend_call_function(&fci, &fcc TSRMLS_CC);
+
+failure:
+	/* Cleanup */
+	if (argc) {
+		for (i = 0; i < fci.param_count; i++) {
+			zval_ptr_dtor(&argv[i]);
+		}
+		efree(argv);
+		efree(fci.params);
+	}
+
+	if (retval_ptr != NULL) {
+		return_value = zval_to_v8js(retval_ptr TSRMLS_CC);
+		zval_ptr_dtor(&retval_ptr);
+	} else {
+		return_value = V8JS_NULL;
+	}
+
+	return return_value;
+}
+/* }}} */
+
+static int _php_v8js_is_assoc_array(HashTable *myht TSRMLS_DC) /* {{{ */
+{
+	int i;
+	char *key;
+	ulong index, idx = 0;
+	uint key_len;
+	HashPosition pos;
+
+	zend_hash_internal_pointer_reset_ex(myht, &pos);
+	for (;; zend_hash_move_forward_ex(myht, &pos)) {
+		i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
+		if (i == HASH_KEY_NON_EXISTANT)
+			break;
+		if (i == HASH_KEY_IS_STRING || index != idx) {
+			return 1;
+		}
+		idx++;
+	}
+	return 0;
+}
+/* }}} */
+
+static v8::Handle<v8::Value> php_v8js_property_caller(const v8::Arguments &args) /* {{{ */
+{
+	v8::Local<v8::Object> self = args.Holder();
+	v8::Local<v8::String> cname = args.Callee()->GetName()->ToString();
+	v8::Local<v8::Value> value;
+	v8::Local<v8::String> cb_func = v8::Local<v8::String>::Cast(args.Data());
+
+	value = self->GetHiddenValue(cb_func);
+
+	if (!value.IsEmpty() && value->IsFunction())
+	{
+		int argc = args.Length(), i = 0;
+		v8::Local<v8::Value> *argv = new v8::Local<v8::Value>[argc];
+		v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(value);
+
+		if (cb_func->Equals(V8JS_SYM(ZEND_INVOKE_FUNC_NAME))) {
+			for (; i < argc; ++i) {
+				argv[i] = args[i];
+			}
+			value = cb->Call(self, argc, argv);
+		} 
+		else /* __call() */
+		{ 
+			v8::Local<v8::Array> argsarr = v8::Array::New(argc);
+			for (; i < argc; ++i) {
+				argsarr->Set(i, args[i]);
+			}
+			v8::Local<v8::Value> argsv[2] = { cname, argsarr };
+			value = cb->Call(self, 2, argsv);
+		}
+	}
+
+	if (args.IsConstructCall()) {
+		if (!value.IsEmpty() && !value->IsNull()) {
+			return value;
+		}
+		return self;
+	}
+
+	return value;
+}
+/* }}} */
+
+static v8::Handle<v8::Value> php_v8js_property_getter(v8::Local<v8::String> property, const v8::AccessorInfo &info) /* {{{ */
+{
+	v8::Local<v8::Object> self = info.Holder();
+	v8::Local<v8::Value> value;
+	v8::Local<v8::Function> cb;
+
+	/* Check first if JS object has the named property */
+	value = self->GetRealNamedProperty(property);
+
+	if (!value.IsEmpty()) {
+		return value;
+	}
+
+	/* If __get() is set for PHP object, call it */
+	value = self->GetHiddenValue(V8JS_SYM(ZEND_GET_FUNC_NAME));
+	if (!value.IsEmpty() && value->IsFunction()) {
+		cb = v8::Local<v8::Function>::Cast(value);
+		v8::Local<v8::Value> argv[1] = {property};
+		value = cb->Call(self, 1, argv);
+	}
+
+	/* If __get() does not exist or returns NULL, create new function with callback for __call() */
+	if ((value.IsEmpty() || value->IsNull()) && info.Data()->IsTrue()) {
+		v8::Local<v8::FunctionTemplate> cb_t = v8::FunctionTemplate::New(php_v8js_property_caller, V8JS_SYM(ZEND_CALL_FUNC_NAME));
+		cb = cb_t->GetFunction();
+		cb->SetName(property);
+		return cb;
+	}
+
+	return value;
+}
+/* }}} */
+
+static v8::Handle<v8::Integer> php_v8js_property_query(v8::Local<v8::String> property, const v8::AccessorInfo &info) /* {{{ */
+{
+	v8::Local<v8::Object> self = info.Holder();
+	v8::Local<v8::Value> value;
+
+	/* Return early if property is set in JS object */
+	if (self->HasRealNamedProperty(property)) {
+		return V8JS_INT(v8::ReadOnly);
+	}
+
+	value = self->GetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME));
+	if (!value.IsEmpty() && value->IsFunction()) {
+		v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(value);
+		v8::Local<v8::Value> argv[1] = {property};
+		value = cb->Call(self, 1, argv);
+	}
+
+	return (!value.IsEmpty() && value->IsTrue()) ? V8JS_INT(v8::ReadOnly) : v8::Local<v8::Integer>();
+}
+/* }}} */
+
+/* These are not defined by Zend */
+#define ZEND_WAKEUP_FUNC_NAME    "__wakeup"
+#define ZEND_SLEEP_FUNC_NAME     "__sleep"
+#define ZEND_SET_STATE_FUNC_NAME "__set_state"
+
+#define IS_MAGIC_FUNC(mname) \
+	((key_len == sizeof(mname)) && \
+	!strncasecmp(key, mname, key_len - 1))
+
+#define PHP_V8JS_CALLBACK(mptr) \
+	v8::FunctionTemplate::New(php_v8js_php_callback, v8::External::New(mptr))->GetFunction()
+
+static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value TSRMLS_DC) /* {{{ */
+{
+	v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New();
+	v8::Local<v8::Object> newobj;
+	int i;
+	char *key = NULL;
+	ulong index;
+	uint key_len;
+	HashTable *myht;
+	HashPosition pos;
+	zend_class_entry *ce = NULL;
+	zend_function *method_ptr, *call_ptr = NULL, *get_ptr = NULL, *invoke_ptr = NULL, *isset_ptr = NULL;
+
+	if (Z_TYPE_P(value) == IS_ARRAY) {
+		myht = HASH_OF(value);
+	} else {
+		myht = Z_OBJPROP_P(value);
+		ce = Z_OBJCE_P(value);
+	}
+
+	/* Prevent recursion */
+	if (myht && myht->nApplyCount > 1) {
+		return V8JS_NULL;
+	}
+
+	/* Object methods */
+	if (ce) {
+		new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
+		new_tpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+		if (ce == zend_ce_closure) {
+			new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback);
+			newobj = new_tpl->InstanceTemplate()->NewInstance();
+		} else {
+			zend_hash_internal_pointer_reset_ex(&ce->function_table, &pos);
+			for (;; zend_hash_move_forward_ex(&ce->function_table, &pos)) {
+				if (zend_hash_get_current_key_ex(&ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING  ||
+					zend_hash_get_current_data_ex(&ce->function_table, (void **) &method_ptr, &pos) == FAILURE
+				) {
+					break;
+				}
+
+				if ((method_ptr->common.fn_flags & ZEND_ACC_PUBLIC)     && /* Allow only public methods */
+					(method_ptr->common.fn_flags & ZEND_ACC_CTOR) == 0  && /* ..and no __construct() */
+					(method_ptr->common.fn_flags & ZEND_ACC_DTOR) == 0  && /* ..or __destruct() */
+					(method_ptr->common.fn_flags & ZEND_ACC_CLONE) == 0 /* ..or __clone() functions */
+				) {
+					/* Override native toString() with __tostring() if it is set in passed object */
+					if (IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME)) {
+						new_tpl->InstanceTemplate()->Set(V8JS_SYM("toString"), PHP_V8JS_CALLBACK(method_ptr));
+					/* TODO: __set(), __unset() disabled as JS is not allowed to modify the passed PHP object yet.
+					 *  __sleep(), __wakeup(), __set_state() are always ignored */
+					} else if (
+						IS_MAGIC_FUNC(ZEND_CALLSTATIC_FUNC_NAME)|| /* TODO */
+						IS_MAGIC_FUNC(ZEND_SLEEP_FUNC_NAME)     ||
+						IS_MAGIC_FUNC(ZEND_WAKEUP_FUNC_NAME)    ||
+						IS_MAGIC_FUNC(ZEND_SET_STATE_FUNC_NAME) ||
+						IS_MAGIC_FUNC(ZEND_SET_FUNC_NAME)       ||
+						IS_MAGIC_FUNC(ZEND_UNSET_FUNC_NAME)
+					) {
+					/* Register all magic function as hidden with lowercase name */
+					} else if (IS_MAGIC_FUNC(ZEND_GET_FUNC_NAME)) {
+						get_ptr = method_ptr;
+					} else if (IS_MAGIC_FUNC(ZEND_CALL_FUNC_NAME)) {
+						call_ptr = method_ptr;
+					} else if (IS_MAGIC_FUNC(ZEND_INVOKE_FUNC_NAME)) {
+						invoke_ptr = method_ptr;
+					} else if (IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
+						isset_ptr = method_ptr;
+					} else {
+						new_tpl->InstanceTemplate()->Set(V8JS_STR(method_ptr->common.function_name), PHP_V8JS_CALLBACK(method_ptr), v8::ReadOnly);
+					}
+				}
+			}
+
+			/* Only register getter, etc. when they're set in PHP side */
+			if (call_ptr || get_ptr || isset_ptr)
+			{
+				/* Set __get() handler which acts also as __call() proxy */
+				new_tpl->InstanceTemplate()->SetNamedPropertyHandler(
+					php_v8js_property_getter,					/* getter */
+					0,											/* setter */
+					isset_ptr ? php_v8js_property_query : 0,	/* query */
+					0,											/* deleter */
+					0,											/* enumerator */
+					V8JS_BOOL(call_ptr ? true : false)
+				);
+			}
+
+			/* __invoke() handler */
+			if (invoke_ptr) {
+				new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_property_caller, V8JS_SYM(ZEND_INVOKE_FUNC_NAME));
+			}
+
+			newobj = new_tpl->InstanceTemplate()->NewInstance();
+
+			if (call_ptr) {
+				newobj->SetHiddenValue(V8JS_SYM(ZEND_CALL_FUNC_NAME), PHP_V8JS_CALLBACK(call_ptr));
+			}
+			if (get_ptr) {
+				newobj->SetHiddenValue(V8JS_SYM(ZEND_GET_FUNC_NAME), PHP_V8JS_CALLBACK(get_ptr));
+			}
+			if (invoke_ptr) {
+				newobj->SetHiddenValue(V8JS_SYM(ZEND_INVOKE_FUNC_NAME), PHP_V8JS_CALLBACK(invoke_ptr));
+			}
+			if (isset_ptr) {
+				newobj->SetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME), PHP_V8JS_CALLBACK(isset_ptr));
+			}
+		}
+		newobj->SetPointerInInternalField(0, (void *) value);
+	} else {
+		new_tpl->SetClassName(V8JS_SYM("Array"));
+		newobj = new_tpl->InstanceTemplate()->NewInstance();
+	}
+
+	/* Object properties */
+	i = myht ? zend_hash_num_elements(myht) : 0;
+
+	if (i > 0)
+	{
+		zval **data;
+		HashTable *tmp_ht;
+
+		zend_hash_internal_pointer_reset_ex(myht, &pos);
+		for (;; zend_hash_move_forward_ex(myht, &pos)) {
+			i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
+			if (i == HASH_KEY_NON_EXISTANT)
+				break;
+
+			if (zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS)
+			{
+				tmp_ht = HASH_OF(*data);
+
+				if (tmp_ht) {
+					tmp_ht->nApplyCount++;
+				}
+
+				if (i == HASH_KEY_IS_STRING)
+				{
+					if (key[0] == '\0' && Z_TYPE_P(value) == IS_OBJECT) {
+						/* Skip protected and private members. */
+						if (tmp_ht) {
+							tmp_ht->nApplyCount--;
+						}
+						continue;
+					}
+					newobj->Set(V8JS_STRL(key, key_len - 1), zval_to_v8js(*data TSRMLS_CC), v8::ReadOnly);
+				} else {
+					newobj->Set(index, zval_to_v8js(*data TSRMLS_CC));
+				}
+
+				if (tmp_ht) {
+					tmp_ht->nApplyCount--;
+				}
+			}
+		}
+	}
+	return newobj;
+}
+/* }}} */
+
+static v8::Handle<v8::Value> php_v8js_hash_to_jsarr(zval *value TSRMLS_DC) /* {{{ */
+{
+	HashTable *myht = HASH_OF(value);
+	int i = myht ? zend_hash_num_elements(myht) : 0;
+
+	/* Return object if dealing with assoc array */
+	if (i > 0 && _php_v8js_is_assoc_array(myht TSRMLS_CC)) {
+		return php_v8js_hash_to_jsobj(value TSRMLS_CC);
+	}
+
+	v8::Local<v8::Array> newarr;
+
+	/* Prevent recursion */
+	if (myht && myht->nApplyCount > 1) {
+		return V8JS_NULL;
+	}
+
+	newarr = v8::Array::New(i);
+
+	if (i > 0)
+	{
+		zval **data;
+		ulong index = 0;
+		HashTable *tmp_ht;
+		HashPosition pos;
+
+		for (zend_hash_internal_pointer_reset_ex(myht, &pos);
+			SUCCESS == zend_hash_get_current_data_ex(myht, (void **) &data, &pos);
+			zend_hash_move_forward_ex(myht, &pos)
+		) {
+			tmp_ht = HASH_OF(*data);
+
+			if (tmp_ht) {
+				tmp_ht->nApplyCount++;
+			}
+
+			newarr->Set(index++, zval_to_v8js(*data TSRMLS_CC));
+
+			if (tmp_ht) {
+				tmp_ht->nApplyCount--;
+			}
+		}
+	}
+	return newarr;
+}
+/* }}} */
+
+v8::Handle<v8::Value> zval_to_v8js(zval *value TSRMLS_DC) /* {{{ */
+{
+	v8::Handle<v8::Value> jsValue;
+
+	switch (Z_TYPE_P(value))
+	{
+		case IS_ARRAY:
+			jsValue = php_v8js_hash_to_jsarr(value TSRMLS_CC);
+			break;
+
+		case IS_OBJECT:
+			jsValue = php_v8js_hash_to_jsobj(value TSRMLS_CC);
+			break;
+
+		case IS_STRING:
+			jsValue = V8JS_STRL(Z_STRVAL_P(value), Z_STRLEN_P(value));
+			break;
+
+		case IS_LONG:
+			jsValue = V8JS_INT(Z_LVAL_P(value));
+			break;
+
+		case IS_DOUBLE:
+			jsValue = V8JS_FLOAT(Z_DVAL_P(value));
+			break;
+
+		case IS_BOOL:
+			jsValue = V8JS_BOOL(Z_BVAL_P(value));
+			break;
+
+		default:
+		case IS_NULL:
+			jsValue = V8JS_NULL;
+			break;
+	}
+	return jsValue; 
+}
+/* }}} */
+
+int v8js_to_zval(v8::Handle<v8::Value> jsValue, zval *return_value, int flags TSRMLS_DC) /* {{{ */
+{
+	if (jsValue->IsString())
+	{
+		v8::String::Utf8Value str(jsValue);
+		const char *cstr = ToCString(str);
+		RETVAL_STRING(cstr, 1);
+	}
+	else if (jsValue->IsBoolean())
+	{
+		RETVAL_BOOL(jsValue->Uint32Value());
+	}
+	else if (jsValue->IsInt32() || jsValue->IsUint32())
+	{
+		RETVAL_LONG((long) jsValue->IntegerValue());
+	}
+	else if (jsValue->IsNumber())
+	{
+		RETVAL_DOUBLE(jsValue->NumberValue());
+	}
+	else if (jsValue->IsDate())	/* Return as a PHP DateTime object */
+	{
+		v8::String::Utf8Value str(jsValue);
+		const char *cstr = ToCString(str);
+		zend_class_entry *ce = php_date_get_date_ce();
+#if PHP_VERSION_ID < 50304
+		zval *param;
+
+		MAKE_STD_ZVAL(param);
+		ZVAL_STRING(param, cstr, 1);
+
+		object_init_ex(return_value, ce TSRMLS_CC);
+		zend_call_method_with_1_params(&return_value, ce, &ce->constructor, "__construct", NULL, param);
+		zval_ptr_dtor(&param);
+
+		if (EG(exception)) {
+			return FAILURE;
+		}
+#else
+		php_date_instantiate(ce, return_value TSRMLS_CC);
+		if (!php_date_initialize((php_date_obj *) zend_object_store_get_object(return_value TSRMLS_CC), (char *) cstr, strlen(cstr), NULL, NULL, 0 TSRMLS_CC)) {
+			return FAILURE;
+		}
+#endif
+	}
+	else if (jsValue->IsObject())
+	{
+		if ((flags & V8JS_FLAG_FORCE_ARRAY) || jsValue->IsArray()) {
+			array_init(return_value TSRMLS_CC);
+			return php_v8js_v8_get_properties_hash(jsValue, Z_ARRVAL_P(return_value), flags TSRMLS_CC);
+		} else {
+			php_v8js_create_v8(return_value, jsValue, flags TSRMLS_CC);
+			return SUCCESS;
+		}
+	}
+	else /* types External, RegExp, Undefined and Null are considered NULL */
+	{
+		RETVAL_NULL();
+	}
+
+ 	return SUCCESS;
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 183 - 0
v8js_methods.cc

@@ -0,0 +1,183 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include "php.h"
+}
+
+#include "php_v8js_macros.h"
+#include <v8.h>
+
+/* global.exit - terminate execution */
+V8JS_METHOD(exit) /* {{{ */
+{
+	v8::V8::TerminateExecution();
+	return v8::Undefined();
+}
+/* }}} */
+
+/* global.sleep - sleep for passed seconds */
+V8JS_METHOD(sleep) /* {{{ */
+{
+	php_sleep(args[0]->Int32Value());
+	return v8::Undefined();
+}
+/* }}} */
+
+/* global.print - php print() */
+V8JS_METHOD(print) /* {{{ */
+{
+	int ret = 0;
+	TSRMLS_FETCH();
+
+	for (int i = 0; i < args.Length(); i++) {
+		v8::String::Utf8Value str(args[i]);
+		const char *cstr = ToCString(str);
+		ret = PHPWRITE(cstr, strlen(cstr));
+	}
+	return V8JS_INT(ret);
+}
+/* }}} */
+
+static void _php_v8js_dumper(v8::Local<v8::Value> var, int level TSRMLS_CC) /* {{{ */
+{
+	v8::String::Utf8Value str(var->ToDetailString());
+	const char *valstr = ToCString(str);
+	size_t valstr_len = (valstr) ? strlen(valstr) : 0;
+
+	if (level > 1) {
+		php_printf("%*c", (level - 1) * 2, ' ');
+	}
+
+	if (var->IsString())
+	{
+		php_printf("string(%d) \"%s\"\n", valstr_len, valstr);
+	}
+	else if (var->IsBoolean())
+	{
+		php_printf("bool(%s)\n", valstr);
+	}
+	else if (var->IsInt32() || var->IsUint32())
+	{
+		php_printf("int(%s)\n", valstr);
+	}
+	else if (var->IsNumber())
+	{
+		php_printf("float(%s)\n", valstr);
+	}
+	else if (var->IsDate())
+	{
+		php_printf("Date(%s)\n", valstr);
+	}
+	else if (var->IsRegExp())
+	{
+		php_printf("RegExp(%s)\n", valstr);
+	}
+	else if (var->IsArray())
+	{
+		v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(var);
+		uint32_t length = array->Length();
+
+		php_printf("array(%d) {\n", length);
+
+		for (unsigned i = 0; i < length; i++) {
+			php_printf("%*c[%d] =>\n", level * 2, ' ', i);
+			_php_v8js_dumper(array->Get(i), level + 1 TSRMLS_CC);
+		}
+
+		if (level > 1) {
+			php_printf("%*c", (level - 1) * 2, ' ');
+		}
+
+		ZEND_PUTS("}\n");
+	}
+	else if (var->IsObject())
+	{
+		v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(var);
+		V8JS_GET_CLASS_NAME(cname, object);
+
+		if (var->IsFunction())
+		{
+			v8::String::Utf8Value csource(object->ToString());
+			php_printf("object(%s)#%d {\n%*c%s\n", ToCString(cname), object->GetIdentityHash(), level * 2 + 2, ' ', ToCString(csource));
+		}
+		else
+		{
+			v8::Local<v8::Array> keys = object->GetPropertyNames();
+			uint32_t length = keys->Length();
+
+			php_printf("object(%s)#%d (%d) {\n", ToCString(cname), object->GetIdentityHash(), length);
+
+			for (unsigned i = 0; i < length; i++) {
+				v8::Local<v8::String> key = keys->Get(i)->ToString();
+				v8::String::Utf8Value kname(key);
+				php_printf("%*c[\"%s\"] =>\n", level * 2, ' ', ToCString(kname));
+				_php_v8js_dumper(object->Get(key), level + 1 TSRMLS_CC);
+			}
+		}
+
+		if (level > 1) {
+			php_printf("%*c", (level - 1) * 2, ' ');
+		}
+
+		ZEND_PUTS("}\n");
+	}
+	else /* null, undefined, etc. */
+	{
+		php_printf("<%s>\n", valstr);
+	}
+}
+/* }}} */
+
+/* global.var_dump - Dump JS values */
+V8JS_METHOD(var_dump) /* {{{ */
+{
+	int i;
+	TSRMLS_FETCH();
+
+	for (int i = 0; i < args.Length(); i++) {
+		_php_v8js_dumper(args[i], 1 TSRMLS_CC);
+	}
+	
+	return V8JS_NULL;
+}
+/* }}} */
+
+void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global) /* {{{ */
+{
+	global->Set(V8JS_SYM("exit"), v8::FunctionTemplate::New(V8JS_MN(exit)), v8::ReadOnly);
+	global->Set(V8JS_SYM("sleep"), v8::FunctionTemplate::New(V8JS_MN(sleep)), v8::ReadOnly);
+	global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(V8JS_MN(print)), v8::ReadOnly);
+	global->Set(V8JS_SYM("var_dump"), v8::FunctionTemplate::New(V8JS_MN(var_dump)), v8::ReadOnly);
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 91 - 0
v8js_variables.cc

@@ -0,0 +1,91 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2010 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Jani Taskinen <[email protected]>                         |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include "php.h"
+}
+
+#include "php_v8js_macros.h"
+#include <v8.h>
+
+static v8::Handle<v8::Value> php_v8js_fetch_php_variable(v8::Local<v8::String> name, const v8::AccessorInfo &info) /* {{{ */
+{
+	v8::String::Utf8Value variable_name(info.Data()->ToString());
+	const char *variable_name_string = ToCString(variable_name);
+	uint variable_name_string_len = strlen(variable_name_string);
+	zval **variable;
+
+	TSRMLS_FETCH();
+
+	zend_is_auto_global(variable_name_string, variable_name_string_len TSRMLS_CC);
+
+	if (zend_hash_find(&EG(symbol_table), variable_name_string, variable_name_string_len + 1, (void **) &variable) == SUCCESS) {
+		return zval_to_v8js(*variable TSRMLS_CC);
+	}
+	return v8::Undefined();
+}
+/* }}} */
+
+void php_v8js_register_accessors(v8::Local<v8::ObjectTemplate> php_obj, zval *array TSRMLS_DC) /* {{{ */
+{
+	char *property_name;
+	uint property_name_len;
+	ulong index;
+	zval **item;
+
+	for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(array));
+		zend_hash_get_current_data(Z_ARRVAL_P(array), (void **) &item) != FAILURE;
+		zend_hash_move_forward(Z_ARRVAL_P(array))
+	) {
+		switch (Z_TYPE_PP(item))
+		{
+			/*
+			case IS_OBJECT:
+			case IS_ARRAY:
+			*/
+			case IS_STRING:
+				break;
+
+			default:
+				continue; /* Ignore invalid values */
+		}
+
+		if (zend_hash_get_current_key_ex(Z_ARRVAL_P(array), &property_name, &property_name_len, &index, 0, NULL) != HASH_KEY_IS_STRING) {
+			continue; /* Ignore invalid property names */
+		}
+
+		/* Set the variable fetch callback for given symbol on named property */
+		php_obj->SetAccessor(V8JS_STRL(property_name, property_name_len - 1), php_v8js_fetch_php_variable, NULL, V8JS_STRL(Z_STRVAL_PP(item), Z_STRLEN_PP(item)), v8::PROHIBITS_OVERWRITING, v8::ReadOnly);
+	}
+}
+/* }}} */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */