+static zend_object_handlers psi_object_handlers;
+static zend_class_entry *psi_class_entry;
+
+zend_class_entry *psi_object_get_class_entry()
+{
+ return psi_class_entry;
+}
+
+void psi_error_wrapper(void *context, struct psi_token *t, int type, const char *msg, ...)
+{
+ va_list argv;
+ const char *fn = NULL;
+ unsigned ln = 0;
+
+ if (context) {
+ if (PSI_DATA(context)->flags & PSI_PARSER_SILENT) {
+ return;
+ }
+ }
+
+ if (t) {
+ fn = t->file;
+ ln = t->line;
+ } else if (zend_is_executing()) {
+ fn = zend_get_executed_filename();
+ ln = zend_get_executed_lineno();
+ } else if (zend_is_compiling()) {
+ fn = zend_get_compiled_filename()->val;
+ ln = zend_get_compiled_lineno();
+ }
+
+ va_start(argv, msg);
+ psi_verror(type, fn, ln, msg, argv);
+ va_end(argv);
+}
+
+void psi_error(int type, const char *fn, unsigned ln, const char *msg, ...)
+{
+ va_list argv;
+
+ va_start(argv, msg);
+ psi_verror(type, fn, ln, msg, argv);
+ va_end(argv);
+}
+
+void psi_verror(int type, const char *fn, unsigned ln, const char *msg, va_list argv)
+{
+ zend_error_cb(type, fn, ln, msg, argv);
+}
+
+static void psi_object_free(zend_object *o)
+{
+ psi_object *obj = PSI_OBJ(NULL, o);
+
+ if (obj->data) {
+ /* FIXME: how about registering a destructor?
+ // free(obj->data); */
+ obj->data = NULL;
+ }
+ zend_object_std_dtor(o);
+}
+
+static zend_object *psi_object_init(zend_class_entry *ce)
+{
+ psi_object *o = ecalloc(1, sizeof(*o) + zend_object_properties_size(ce));
+
+ zend_object_std_init(&o->std, ce);
+ object_properties_init(&o->std, ce);
+ o->std.handlers = &psi_object_handlers;
+ return &o->std;
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_psi_dump, 0, 0, 0)
+ ZEND_ARG_INFO(0, stream)
+ZEND_END_ARG_INFO();
+static PHP_FUNCTION(psi_dump) {
+ php_stream *s;
+ zval *r = NULL;
+ int fd = STDOUT_FILENO;
+
+ if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "|r!", &r)) {
+ return;
+ }
+ if (r) {
+ php_stream_from_zval(s, r);
+
+ if (SUCCESS != php_stream_cast(s, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void **)&fd, 1)) {
+ RETURN_FALSE;
+ }
+ }
+ psi_context_dump(&PSI_G(context), fd);
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_psi_validate, 0, 0, 1)
+ ZEND_ARG_INFO(0, file)
+ZEND_END_ARG_INFO();
+static PHP_FUNCTION(psi_validate) {
+ zend_string *file;
+ struct psi_parser P;
+
+ if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "P", &file)) {
+ return;
+ }
+
+ if (!psi_parser_init(&P, file->val, psi_error_wrapper, 0)) {
+ RETURN_FALSE;
+ }
+
+ while (0 < psi_parser_scan(&P)) {
+ psi_parser_parse(&P, psi_token_alloc(&P));
+ if (P.num == PSI_T_EOF) {
+ break;
+ }
+ }
+ psi_parser_parse(&P, NULL);
+
+ if (0 == psi_context_validate_data(NULL, PSI_DATA(&P)) && !P.errors) {
+ RETVAL_TRUE;
+ } else {
+ RETVAL_FALSE;
+ }
+ psi_parser_dtor(&P);
+}
+
+static PHP_MINIT_FUNCTION(psi)