mirror of
https://github.com/phpv8/v8js.git
synced 2024-12-22 14:01:53 +00:00
Initial import
This commit is contained in:
commit
1c23a38026
11
Makefile.frag
Normal file
11
Makefile.frag
Normal file
@ -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
README
Normal file
64
README
Normal file
@ -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
TODO
Normal file
5
TODO
Normal file
@ -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
config.m4
Normal file
73
config.m4
Normal file
@ -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
js/json-template.js
Normal file
839
js/json-template.js
Normal file
@ -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,'&').
|
||||
replace(/>/g,'>').
|
||||
replace(/</g,'<');
|
||||
}
|
||||
|
||||
function HtmlTagEscape(s) {
|
||||
return s.replace(/&/g,'&').
|
||||
replace(/>/g,'>').
|
||||
replace(/</g,'<').
|
||||
replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// 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
js/jstparser.js
Normal file
339
js/jstparser.js
Normal file
@ -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('&', '&');
|
||||
s = s.replaceAll('"', '"');
|
||||
s = s.replaceAll('<', '<');
|
||||
s = s.replaceAll('>', '>');
|
||||
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
package.xml
Normal file
41
package.xml
Normal file
@ -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's open source JavaScript engine V8 in PHP.
|
||||
</description>
|
||||
<maintainers>
|
||||
<maintainer>
|
||||
<user>jani</user>
|
||||
<name>Jani Taskinen</name>
|
||||
<email>jani@php.net</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
package2.xml
Normal file
73
package2.xml
Normal file
@ -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>jani@php.net</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
php_v8js.h
Normal file
36
php_v8js.h
Normal file
@ -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 |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/* $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
php_v8js_macros.h
Normal file
100
php_v8js_macros.h
Normal file
@ -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 |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/* $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
samples/dlopen.supp
Normal file
15
samples/dlopen.supp
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
Ignore dlopen bug #1.
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:_dl_open
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
Ignore dlopen bug #2.
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:_dl_close
|
||||
...
|
||||
}
|
74
samples/test_call.php
Normal file
74
samples/test_call.php
Normal file
@ -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
samples/test_callback.php
Normal file
32
samples/test_callback.php
Normal file
@ -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
samples/test_closure.php
Normal file
11
samples/test_closure.php
Normal file
@ -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
samples/test_crash.php
Normal file
5
samples/test_crash.php
Normal file
@ -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
samples/test_date.php
Normal file
9
samples/test_date.php
Normal file
@ -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
samples/test_dumper.php
Normal file
59
samples/test_dumper.php
Normal file
@ -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
samples/test_exception.php
Normal file
26
samples/test_exception.php
Normal file
@ -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
samples/test_exception2.php
Normal file
34
samples/test_exception2.php
Normal file
@ -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
samples/test_extend.php
Normal file
23
samples/test_extend.php
Normal file
@ -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
samples/test_extension.php
Normal file
9
samples/test_extension.php
Normal file
@ -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
samples/test_method.php
Normal file
48
samples/test_method.php
Normal file
@ -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
test.php
Normal file
173
test.php
Normal file
@ -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 & 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
tests/basic.phpt
Normal file
25
tests/basic.phpt
Normal file
@ -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
tests/callbacks.phpt
Normal file
48
tests/callbacks.phpt
Normal file
@ -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
tests/closures_basic.phpt
Normal file
21
tests/closures_basic.phpt
Normal file
@ -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
tests/closures_dynamic.phpt
Normal file
30
tests/closures_dynamic.phpt
Normal file
@ -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
tests/construct.phpt
Normal file
24
tests/construct.phpt
Normal file
@ -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
tests/context_preserving.phpt
Normal file
78
tests/context_preserving.phpt
Normal file
@ -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
tests/context_separation.phpt
Normal file
38
tests/context_separation.phpt
Normal file
@ -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
tests/exception.phpt
Normal file
68
tests/exception.phpt
Normal file
@ -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
tests/exception_propagation_1.phpt
Normal file
37
tests/exception_propagation_1.phpt
Normal file
@ -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
tests/exception_propagation_2.phpt
Normal file
101
tests/exception_propagation_2.phpt
Normal file
@ -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
tests/exception_propagation_3.phpt
Normal file
38
tests/exception_propagation_3.phpt
Normal file
@ -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
tests/execute_flags.phpt
Normal file
33
tests/execute_flags.phpt
Normal file
@ -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
tests/execute_flags_args.phpt
Normal file
75
tests/execute_flags_args.phpt
Normal file
@ -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
tests/extensions_basic.phpt
Normal file
35
tests/extensions_basic.phpt
Normal file
@ -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
tests/extensions_circular_dependency.phpt
Normal file
39
tests/extensions_circular_dependency.phpt
Normal file
@ -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
tests/get_accessor.phpt
Normal file
41
tests/get_accessor.phpt
Normal file
@ -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
tests/object.phpt
Normal file
50
tests/object.phpt
Normal file
@ -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
tests/object_method_call.phpt
Normal file
144
tests/object_method_call.phpt
Normal file
@ -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
tests/object_prototype.phpt
Normal file
82
tests/object_prototype.phpt
Normal file
@ -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
tests/object_reuse.phpt
Normal file
22
tests/object_reuse.phpt
Normal file
@ -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
tests/return_value.phpt
Normal file
82
tests/return_value.phpt
Normal file
@ -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
tests/skipif.inc
Normal file
5
tests/skipif.inc
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
if (!extension_loaded('v8js')) {
|
||||
die("skip");
|
||||
}
|
58
tests/variable_passing.phpt
Normal file
58
tests/variable_passing.phpt
Normal file
@ -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===
|
595
v8js_convert.cc
Normal file
595
v8js_convert.cc
Normal file
@ -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 |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/* $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(¶m);
|
||||
|
||||
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
v8js_methods.cc
Normal file
183
v8js_methods.cc
Normal file
@ -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 |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/* $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
v8js_variables.cc
Normal file
91
v8js_variables.cc
Normal file
@ -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 |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/* $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
|
||||
*/
|
Loading…
Reference in New Issue
Block a user