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 } // TODO create a CompileError that contains the error log text func newError(code C.int) error { if code != C.PH7_OK { return Error{ Code: ResultCode(code), } } return nil } func (e Error) Error() string { return fmt.Sprintf("ph7: %s", e.Code) } type Engine C.ph7 // TODO strip off the enclosing struct if possible after wrapping all engine methods. 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 } // TODO All the ph7_config verbs 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 := newError(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 } // TODO (*Engine).CompileFile 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) Output(s string) error { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) return newError(C.ph7_context_output((*C.ph7_context)(c), cs, C.int(len(s)))) } // TODO (*Context).OutputFormat maybe? // TODO (*Context).ThrowError func (c *Context) RandomNum() uint { return uint(C.ph7_context_random_num((*C.ph7_context)(c))) } // TODO (*Context).RandomString // 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 // TODO (*Value).ToInt // TODO (*Value).ToBool // TODO (*Value).ToInt64 // TODO (*Value).ToDouble // TODO (*Value).ToString // TODO (*Value).ToResource // TODO (*Value).Compare // 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 // TODO (*Value).IsInt // TODO (*Value).IsFloat // TODO (*Value).IsBool // TODO (*Value).IsString // TODO (*Value).IsNull // TODO (*Value).IsNumeric // TODO (*Value).IsCallable // TODO (*Value).IsScalar // TODO (*Value).IsArray // TODO (*Value).IsObject // TODO (*Value).IsResource // TODO (*Value).IsEmpty // TODO Init // 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 All the ph7_vm_config verbs // TODO (*VM).Dump(w io.Writer) func (vm *VM) OutputWriteError() error { return vm.outputWriteErr } 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) 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