package ph7 /* #include "cgo.h" #include "ph7.h" */ import "C" import ( "fmt" "io" "unsafe" pointer "github.com/mattn/go-pointer" ) type ResultCode int const ( ResultCodeOK = ResultCode(C.PH7_OK) ResultCodeNoMem = ResultCode(C.PH7_NOMEM) ResultCodeAbort = ResultCode(C.PH7_ABORT) ResultCodeIOErr = ResultCode(C.PH7_IO_ERR) ResultCodeLooked = ResultCode(C.PH7_LOOKED) ResultCodeCorrupt = ResultCode(C.PH7_CORRUPT) ResultCodeCompileErr = ResultCode(C.PH7_COMPILE_ERR) ResultCodeVMErr = ResultCode(C.PH7_VM_ERR) ) func (r ResultCode) String() string { switch r { case ResultCodeOK: return "PH7_OK" case ResultCodeNoMem: return "PH7_NOMEM" case ResultCodeAbort: return "PH7_ABORT" case ResultCodeIOErr: return "PH7_IO_ERR" case ResultCodeLooked: return "PH7_LOOKED" case ResultCodeCorrupt: return "PH7_CORRUPT" case ResultCodeCompileErr: return "PH7_COMPILE_ERR" case ResultCodeVMErr: return "PH7_VM_ERR" default: return "UNKNOWN" } } type Error struct { Code ResultCode } func newError(code C.int) error { if code == C.PH7_OK { return nil } return Error{ Code: ResultCode(code), } } func (e Error) Error() string { return fmt.Sprintf("ph7: %s", e.Code.String()) } type CompileError struct { Msg string } func (e CompileError) Error() string { return fmt.Sprintf("ph7 compile error: %s", e.Msg) } type Engine C.ph7 func NewEngine() (*Engine, error) { var cengine *C.ph7 result := C.ph7_init(&cengine) if result != C.PH7_OK { return nil, newError(result) } return (*Engine)(cengine), nil } func (e *Engine) ErrLog() (string, error) { var s *C.char var n C.int err := newError(C.engine_config_err_log((*C.ph7)(e), &s, &n)) if err != nil { return "", err } return C.GoStringN(s, n), nil } /* We don't bother ph7_config verbs other than PH7_CONFIG_ERR_LOG. PH7_CONFIG_ERR_OUTPUT is another way of getting compile error messages, which we don't need separately from returning CompileError values. PH7_CONFIG_ERR_ABORT doesn't do anything. */ func (e *Engine) compileError(code C.int) error { switch code { case C.PH7_OK: return nil case C.PH7_COMPILE_ERR: errLog, errLogErr := e.ErrLog() if errLogErr != nil || errLog == "" { return newError(code) } return CompileError{ Msg: errLog, } default: return newError(code) } } func (e *Engine) Compile(source []byte, phpOnly bool) (*VM, error) { csource := C.CBytes(source) defer C.free(csource) var flags C.int if phpOnly { flags |= C.PH7_PHP_ONLY } var cvm *C.ph7_vm err := e.compileError(C.ph7_compile_v2((*C.ph7)(e), (*C.char)(csource), C.int(len(source)), &cvm, flags)) if err != nil { return nil, err } return newVM(cvm), nil } func (e *Engine) CompileFile(path string, phpOnly bool) (*VM, error) { cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) var flags C.int if phpOnly { flags |= C.PH7_PHP_ONLY } var cvm *C.ph7_vm err := e.compileError(C.ph7_compile_file((*C.ph7)(e), cpath, &cvm, flags)) if err != nil { return nil, err } return newVM(cvm), nil } func (e *Engine) Close() error { return newError(C.ph7_release((*C.ph7)(e))) } type Context C.ph7_context func (c *Context) ResultInt(i int) error { return newError(C.ph7_result_int((*C.ph7_context)(c), C.int(i))) } func (c *Context) ResultInt64(i int64) error { return newError(C.ph7_result_int64((*C.ph7_context)(c), C.ph7_int64(i))) } func (c *Context) ResultBool(b bool) error { var cb C.int if b { cb = 1 } return newError(C.ph7_result_bool((*C.ph7_context)(c), cb)) } func (c *Context) ResultDouble(d float64) error { return newError(C.ph7_result_double((*C.ph7_context)(c), C.double(d))) } func (c *Context) ResultNull() error { return newError(C.ph7_result_null((*C.ph7_context)(c))) } func (c *Context) ResultString(s string) error { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) return newError(C.ph7_result_string((*C.ph7_context)(c), cs, C.int(len(s)))) } // TODO (*Context).ResultStringFormat maybe? Or just use fmt.Sprint*. func (c *Context) ResultValue(value *Value) error { return newError(C.ph7_result_value((*C.ph7_context)(c), (*C.ph7_value)(value))) } // TODO (*Context).ResultResource func (c *Context) Write(p []byte) (int, error) { cp := C.CBytes(p) defer C.free(cp) err := newError(C.ph7_context_output((*C.ph7_context)(c), (*C.char)(cp), C.int(len(p)))) if err != nil { return 0, err } return len(p), nil } type Severity int const ( SeverityErr Severity = C.PH7_CTX_ERR SeverityWarning Severity = C.PH7_CTX_WARNING SeverityNotice Severity = C.PH7_CTX_NOTICE ) func (c *Context) ThrowError(severity Severity, msg string) error { cmsg := C.CString(msg) defer C.free(unsafe.Pointer(cmsg)) return newError(C.ph7_context_throw_error((*C.ph7_context)(c), C.int(severity), cmsg)) } // TODO (*Context).ThrowErrorFormat maybe? func (c *Context) RandomNum() uint { return uint(C.ph7_context_random_num((*C.ph7_context)(c))) } func (c *Context) RandomString(length int) (string, error) { buf := (*C.char)(C.malloc(C.size_t(length))) defer C.free(unsafe.Pointer(buf)) err := newError(C.ph7_context_random_string((*C.ph7_context)(c), nil, C.int(length))) if err != nil { return "", err } return C.GoStringN(buf, C.int(length)), nil } // TODO (*Context).UserData // TODO (*Context).PushAuxData // TODO (*Context).PeekAuxData // TODO (*Context).PopAuxData // TODO (*Context).ResultBufLength // TODO (*Context).FunctionName // TODO (*Context).AllocChunk // TODO (*Context).ReallocChunk // TODO (*Context).FreeChunk type Value C.ph7_value func (v *Value) ToInt() int { return int(C.ph7_value_to_int((*C.ph7_value)(v))) } func (v *Value) ToBool() bool { return C.ph7_value_to_bool((*C.ph7_value)(v)) != 0 } func (v *Value) ToInt64() int64 { return int64(C.ph7_value_to_int64((*C.ph7_value)(v))) } func (v *Value) ToDouble() float64 { return float64(C.ph7_value_to_double((*C.ph7_value)(v))) } func (v *Value) ToString() string { var strLen C.int cstr := C.ph7_value_to_string((*C.ph7_value)(v), &strLen) return C.GoStringN(cstr, strLen) } // TODO (*Value).ToResource func (v *Value) Compare(v2 *Value) int { return int(C.ph7_value_compare((*C.ph7_value)(v), (*C.ph7_value)(v2), 0)) } func (v *Value) CompareStrict(v2 *Value) int { return int(C.ph7_value_compare((*C.ph7_value)(v), (*C.ph7_value)(v2), 1)) } // TODO (*Value).Int // TODO (*Value).Int64 // TODO (*Value).Bool // TODO (*Value).Null // TODO (*Value).Double // TODO (*Value).String // TODO (*Value).StringFormat maybe? // TODO (*Value).ResetStringCursor // TODO (*Value).Resource // TODO (*Value).Release // TODO (*Value).ArrayFetch // TODO (*Value).ArrayWalk // TODO (*Value).ArrayAddElem // TODO (*Value).ArrayAddStrkeyElem // TODO (*Value).ArrayAddIntkeyElem // TODO (*Value).ArrayCount // TODO (*Value).ObjectWalk // TODO (*Value).ObjectFetchAttr // TODO (*Value).ObjectGetClassName func (v *Value) IsInt() bool { return C.ph7_value_is_int((*C.ph7_value)(v)) != 0 } func (v *Value) IsFloat() bool { return C.ph7_value_is_float((*C.ph7_value)(v)) != 0 } func (v *Value) IsBool() bool { return C.ph7_value_is_bool((*C.ph7_value)(v)) != 0 } func (v *Value) IsString() bool { return C.ph7_value_is_string((*C.ph7_value)(v)) != 0 } func (v *Value) IsNull() bool { return C.ph7_value_is_null((*C.ph7_value)(v)) != 0 } func (v *Value) IsNumeric() bool { return C.ph7_value_is_numeric((*C.ph7_value)(v)) != 0 } func (v *Value) IsCallable() bool { return C.ph7_value_is_callable((*C.ph7_value)(v)) != 0 } func (v *Value) IsScalar() bool { return C.ph7_value_is_scalar((*C.ph7_value)(v)) != 0 } func (v *Value) IsArray() bool { return C.ph7_value_is_array((*C.ph7_value)(v)) != 0 } func (v *Value) IsObject() bool { return C.ph7_value_is_object((*C.ph7_value)(v)) != 0 } func (v *Value) IsResource() bool { return C.ph7_value_is_resource((*C.ph7_value)(v)) != 0 } func (v *Value) IsEmpty() bool { return C.ph7_value_is_empty((*C.ph7_value)(v)) != 0 } // ph7_lib_init is unnecessary when single-threaded, and possibly always. // TODO Config // TODO Shutdown // TODO IsThreadsafe // TODO Version // TODO Signature // TODO Ident // TODO Copyright type VM struct { ptr *C.ph7_vm pointerKey unsafe.Pointer outputWriter io.Writer // TODO clear outputWriteError when resetting VM outputWriteErr error userFunctionKeys []unsafe.Pointer } func newVM(cvm *C.ph7_vm) *VM { vm := new(VM) vm.ptr = cvm vm.pointerKey = pointer.Save(vm) return vm } //export write_vm_output func write_vm_output(buf unsafe.Pointer, bufLen C.uint, userData unsafe.Pointer) C.int { vm := pointer.Restore(userData).(*VM) gobuf := C.GoBytes(buf, C.int(bufLen)) _, vm.outputWriteErr = vm.outputWriter.Write(gobuf) if vm.outputWriteErr != nil { return C.int(ResultCodeAbort) } return C.int(ResultCodeOK) } func (vm *VM) SetOutputWriter(w io.Writer) error { vm.outputWriter = w return newError(C.vm_set_output_callback(vm.ptr, vm.pointerKey)) } // TODO PH7_VM_CONFIG_IMPORT_PATH // TODO PH7_VM_CONFIG_RECURSION_DEPTH // TODO PH7_VM_CONFIG_CREATE_SUPER // TODO PH7_VM_CONFIG_CREATE_VAR // TODO PH7_VM_CONFIG_HTTP_REQUEST // TODO PH7_VM_CONFIG_SERVER_ATTR // TODO PH7_VM_CONFIG_ENV_ATTR // TODO PH7_VM_CONFIG_SESSION_ATTR // TODO PH7_VM_CONFIG_POST_ATTR // TODO PH7_VM_CONFIG_GET_ATTR // TODO PH7_VM_CONFIG_COOKIE_ATTR // TODO PH7_VM_CONFIG_HEADER_ATTR // TODO PH7_VM_CONFIG_EXEC_VALUE // TODO PH7_VM_CONFIG_IO_STREAM // TODO PH7_VM_CONFIG_ARGV_ENTRY // TODO PH7_VM_CONFIG_EXTRACT_OUTPUT // TODO PH7_VM_CONFIG_ERR_LOG_HANDLER // TODO (*VM).Dump(w io.Writer) func (vm *VM) OutputWriteError() error { return vm.outputWriteErr } func (vm *VM) ReportErrors() error { return newError(C.vm_config_err_report((*C.ph7_vm)(vm.ptr))) } func (vm *VM) ExtractOutput() (string, error) { var s *C.char var n C.int err := newError(C.vm_extract_output(vm.ptr, &s, &n)) if err != nil { return "", err } return C.GoStringN(s, n), nil } func (vm *VM) Exec() error { // TODO return exit status return newError(C.ph7_vm_exec(vm.ptr, (*C.int)(nil))) } func (vm *VM) Reset() error { return newError(C.ph7_vm_reset(vm.ptr)) } func (vm *VM) Close() error { pointer.Unref(vm.pointerKey) for _, key := range vm.userFunctionKeys { pointer.Unref(key) } return newError(C.ph7_vm_release(vm.ptr)) } type ForeignFunction interface { Call(ctx *Context, args []*Value) ResultCode } //export call_foreign_function func call_foreign_function(ctx *C.ph7_context, argc C.int, argv **C.ph7_value) C.int { fkey := C.ph7_context_user_data(ctx) // TODO it looks like ph7 looks for a VFS at the user data pointer. WTF? Should we use it for that? f := pointer.Restore(fkey).(ForeignFunction) args := make([]*Value, 0, argc) argvSlice := unsafe.Slice(argv, argc) for i := range argvSlice { args = append(args, (*Value)(argvSlice[i])) } return C.int(f.Call((*Context)(ctx), args)) } func (vm *VM) CreateFunction(name string, f ForeignFunction) error { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) fkey := pointer.Save(f) vm.userFunctionKeys = append(vm.userFunctionKeys, fkey) return newError(C.vm_create_function(vm.ptr, cname, fkey)) } // TODO (*VM).DeleteFunction // TODO (*VM).CreateConstant // TODO (*VM).DeleteConstant