diff --git a/Makefile b/Makefile index 3110540..3401c7a 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ TEST := -v --run RegExp_exec TEST := -v --run _panic TEST := -v --run TransformRegExp TEST := -v --run Lexer +TEST := -v --run Reflect +TEST := -v --run _reflectSlice +TEST := -v --run _reflect TEST := . test: test-i diff --git a/README.markdown b/README.markdown index cdb3a89..0b25b11 100644 --- a/README.markdown +++ b/README.markdown @@ -52,7 +52,7 @@ Embedding a Go function in JavaScript: Otto.Set("twoPlus", func(call otto.FunctionCall) otto.Value { right, _ := call.Argument(0).ToInteger() - result, _ := otto.ToValue(2 + right) + result, _ := Otto.ToValue(2 + right) return result }) @@ -280,6 +280,12 @@ conversion failing), then an error is returned. If the top-level binding does not exist, it will be created. +#### func (Otto) ToValue + +```go +func (self Otto) ToValue(value interface{}) (Value, error) +``` + #### type Value ```go diff --git a/dbg.go b/dbg.go new file mode 100644 index 0000000..7a7dc1d --- /dev/null +++ b/dbg.go @@ -0,0 +1,7 @@ +package otto + +import ( + Dbg "github.com/robertkrimen/otto/dbg" +) + +var dbg, dbgf = Dbg.New() diff --git a/dbg/dbg.go b/dbg/dbg.go new file mode 100644 index 0000000..ee5f64d --- /dev/null +++ b/dbg/dbg.go @@ -0,0 +1,361 @@ +/* +Package dbg is a println/printf/log-debugging utility library. + + import ( + Dbg "github.com/robertkrimen/dbg" + ) + + dbg, dbgf := Dbg.New() + + dbg("Emit some debug stuff", []byte{120, 121, 122, 122, 121}, math.Pi) + # "2013/01/28 16:50:03 Emit some debug stuff [120 121 122 122 121] 3.141592653589793" + + dbgf("With a %s formatting %.2f", "little", math.Pi) + # "2013/01/28 16:51:55 With a little formatting (3.14)" + + dbgf("%/fatal//A fatal debug statement: should not be here") + # "A fatal debug statement: should not be here" + # ...and then, os.Exit(1) + + dbgf("%/panic//Can also panic %s", "this") + # "Can also panic this" + # ...as a panic, equivalent to: panic("Can also panic this") + + dbgf("Any %s arguments without a corresponding %%", "extra", "are treated like arguments to dbg()") + # "2013/01/28 17:14:40 Any extra arguments (without a corresponding %) are treated like arguments to dbg()" + + dbgf("%d %d", 1, 2, 3, 4, 5) + # "2013/01/28 17:16:32 Another example: 1 2 3 4 5" + + dbgf("%@: Include the function name for a little context (via %s)", "%@") + # "2013... github.com/robertkrimen/dbg.TestSynopsis: Include the function name for a little context (via %@)" + +By default, dbg uses log (log.Println, log.Printf, log.Panic, etc.) for output. +However, you can also provide your own output destination by invoking dbg.New with +a customization function: + + import ( + "bytes" + Dbg "github.com/robertkrimen/dbg" + "os" + ) + + # dbg to os.Stderr + dbg, dbgf := Dbg.New(func(dbgr *Dbgr) { + dbgr.SetOutput(os.Stderr) + }) + + # A slightly contrived example: + var buffer bytes.Buffer + dbg, dbgf := New(func(dbgr *Dbgr) { + dbgr.SetOutput(&buffer) + }) + +*/ +package dbg + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "regexp" + "runtime" + "strings" + "unicode" +) + +type _frmt struct { + ctl string + format string + operandCount int + panic bool + fatal bool +} + +var ( + ctlTest = regexp.MustCompile(`^\s*%/`) + ctlScan = regexp.MustCompile(`%?/(panic|fatal)(?:\s|$)`) +) + +func operandCount(format string) int { + count := 0 + end := len(format) + for at := 0; at < end; { + for at < end && format[at] != '%' { + at++ + } + at++ + if at < end { + if format[at] != '%' && format[at] != '@' { + count++ + } + at++ + } + } + return count +} + +func parseFormat(format string) (frmt _frmt) { + if ctlTest.MatchString(format) { + format = strings.TrimLeftFunc(format, unicode.IsSpace) + index := strings.Index(format, "//") + if index != -1 { + frmt.ctl = format[0:index] + format = format[index+2:] // Skip the second slash via +2 (instead of +1) + } else { + frmt.ctl = format + format = "" + } + for _, tmp := range ctlScan.FindAllStringSubmatch(frmt.ctl, -1) { + for _, value := range tmp[1:] { + switch value { + case "panic": + frmt.panic = true + case "fatal": + frmt.fatal = true + } + } + } + } + frmt.format = format + frmt.operandCount = operandCount(format) + return +} + +type Dbgr struct { + emit _emit +} + +type DbgFunction func(values ...interface{}) + +func NewDbgr() *Dbgr { + self := &Dbgr{} + return self +} + +/* +New will create and return a pair of debugging functions. You can customize where +they output to by passing in an (optional) customization function: + + import ( + Dbg "github.com/robertkrimen/dbg" + "os" + ) + + # dbg to os.Stderr + dbg, dbgf := Dbg.New(func(dbgr *Dbgr) { + dbgr.SetOutput(os.Stderr) + }) + +*/ +func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) { + dbgr := NewDbgr() + if len(options) > 0 { + if fn, ok := options[0].(func(*Dbgr)); ok { + fn(dbgr) + } + } + return dbgr.DbgDbgf() +} + +func (self Dbgr) Dbg(values ...interface{}) { + self.getEmit().emit(_frmt{}, "", values...) +} + +func (self Dbgr) Dbgf(values ...interface{}) { + self.dbgf(values...) +} + +func (self Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) { + dbg = func(vl ...interface{}) { + self.Dbg(vl...) + } + dbgf = func(vl ...interface{}) { + self.dbgf(vl...) + } + return dbg, dbgf // Redundant, but... +} + +func (self Dbgr) dbgf(values ...interface{}) { + var frmt _frmt + if len(values) > 0 { + tmp := fmt.Sprint(values[0]) + frmt = parseFormat(tmp) + values = values[1:] + } + + buffer_f := bytes.Buffer{} + format := frmt.format + end := len(format) + for at := 0; at < end; { + last := at + for at < end && format[at] != '%' { + at++ + } + if at > last { + buffer_f.WriteString(format[last:at]) + } + if at >= end { + break + } + // format[at] == '%' + at++ + // format[at] == ? + if format[at] == '@' { + depth := 2 + pc, _, _, _ := runtime.Caller(depth) + name := runtime.FuncForPC(pc).Name() + buffer_f.WriteString(name) + } else { + buffer_f.WriteString(format[at-1 : at+1]) + } + at++ + } + + values_f := values[0:frmt.operandCount] + values_dbg := values[frmt.operandCount:] + if len(values_dbg) > 0 { + // Adjust frmt.format: + { + tmp := format + if len(tmp) > 0 { + if unicode.IsSpace(rune(tmp[len(tmp)-1])) { + buffer_f.WriteString("%s") + } else { + buffer_f.WriteString(" %s") + } + } else { + buffer_f.WriteString("%s") + } + } + + // Adjust vl_f: + { + tmp := []string{} + for _, value := range values_dbg { + tmp = append(tmp, fmt.Sprintf("%v", value)) + } + values_f = append(values_f, strings.Join(tmp, " ")) + } + } + + format = buffer_f.String() + self.getEmit().emit(frmt, format, values_f...) +} + +// Idiot-proof &Dbgr{}, etc. +func (self *Dbgr) getEmit() _emit { + if self.emit == nil { + self.emit = standardEmit() + } + return self.emit +} + +// SetOutput will accept the following as a destination for output: +// +// *log.Logger Print*/Panic*/Fatal* of the logger +// io.Writer - +// nil Reset to the default output (os.Stderr) +// "log" Print*/Panic*/Fatal* via the "log" package +// +func (self *Dbgr) SetOutput(output interface{}) { + if output == nil { + self.emit = standardEmit() + return + } + switch output := output.(type) { + case *log.Logger: + self.emit = _emitLogger{ + logger: output, + } + return + case io.Writer: + self.emit = _emitWriter{ + writer: output, + } + return + case string: + if output == "log" { + self.emit = _emitLog{} + return + } + } + panic(output) +} + +// ======== // +// = emit = // +// ======== // + +func standardEmit() _emit { + return _emitWriter{ + writer: os.Stderr, + } +} + +func ln(tmp string) string { + length := len(tmp) + if length > 0 && tmp[length-1] != '\n' { + return tmp + "\n" + } + return tmp +} + +type _emit interface { + emit(_frmt, string, ...interface{}) +} + +type _emitWriter struct { + writer io.Writer +} + +func (self _emitWriter) emit(frmt _frmt, format string, values ...interface{}) { + if format == "" { + fmt.Fprintln(self.writer, values...) + } else { + if frmt.panic { + panic(fmt.Sprintf(format, values...)) + } + fmt.Fprintf(self.writer, ln(format), values...) + if frmt.fatal { + os.Exit(1) + } + } +} + +type _emitLogger struct { + logger *log.Logger +} + +func (self _emitLogger) emit(frmt _frmt, format string, values ...interface{}) { + if format == "" { + self.logger.Println(values...) + } else { + if frmt.panic { + self.logger.Panicf(format, values...) + } else if frmt.fatal { + self.logger.Fatalf(format, values...) + } else { + self.logger.Printf(format, values...) + } + } +} + +type _emitLog struct { +} + +func (self _emitLog) emit(frmt _frmt, format string, values ...interface{}) { + if format == "" { + log.Println(values...) + } else { + if frmt.panic { + log.Panicf(format, values...) + } else if frmt.fatal { + log.Fatalf(format, values...) + } else { + log.Printf(format, values...) + } + } +} diff --git a/otto.go b/otto.go index 84104f3..66b9192 100644 --- a/otto.go +++ b/otto.go @@ -49,7 +49,7 @@ Embedding a Go function in JavaScript: Otto.Set("twoPlus", func(call otto.FunctionCall) otto.Value { right, _ := call.Argument(0).ToInteger() - result, _ := otto.ToValue(2 + right) + result, _ := Otto.ToValue(2 + right) return result }) @@ -201,10 +201,16 @@ func (self Otto) getValue(name string) Value { // // If the top-level binding does not exist, it will be created. func (self Otto) Set(name string, value interface{}) error { - err := catchPanic(func() { - self.setValue(name, self.runtime.toValue(value)) - }) - return err + { + value, err := self.ToValue(value) + if err != nil { + return err + } + err = catchPanic(func() { + self.setValue(name, value) + }) + return err + } } func (self Otto) setValue(name string, value Value) { @@ -242,6 +248,10 @@ func (self Otto) Object(source string) (*Object, error) { return nil, fmt.Errorf("Result was not an object") } +func (self Otto) ToValue(value interface{}) (Value, error) { + return self.runtime.ToValue(value) +} + // Object{} // Object is the representation of a JavaScript object. @@ -296,10 +306,16 @@ func (self Object) Get(name string) (Value, error) { // An error will result if the setting the property triggers an exception (i.e. read-only), // or there is an error during conversion of the given value. func (self Object) Set(name string, value interface{}) error { - err := catchPanic(func() { - self.object.put(name, self.object.runtime.toValue(value), true) - }) - return err + { + value, err := self.object.runtime.ToValue(value) + if err != nil { + return err + } + err = catchPanic(func() { + self.object.put(name, value, true) + }) + return err + } } // Class will return the class string of the object. diff --git a/otto_.go b/otto_.go index 69607f3..e07d6d4 100644 --- a/otto_.go +++ b/otto_.go @@ -15,7 +15,7 @@ func isIdentifier(string_ string) bool { return isIdentifier_Regexp.MatchString(string_) } -func toValueArray(arguments ...interface{}) []Value { +func (self *_runtime) toValueArray(arguments ...interface{}) []Value { length := len(arguments) if length == 1 { if valueArray, ok := arguments[0].([]Value); ok { @@ -86,14 +86,6 @@ func valueToArrayIndex(indexValue Value, length uint, negativeIsZero bool) uint } } -func dbg(arguments ...interface{}) { - output := []string{} - for _, argument := range arguments { - output = append(output, fmt.Sprintf("%v", argument)) - } - fmt.Println(strings.Join(output, " ")) -} - func boolFields(input string) (result map[string]bool) { result = map[string]bool{} for _, word := range strings.Fields(input) { diff --git a/otto_error_test.go b/otto_error_test.go index 28c6353..cb60970 100644 --- a/otto_error_test.go +++ b/otto_error_test.go @@ -20,7 +20,7 @@ func TestOttoError(t *testing.T) { Is(err, "TypeError: Nothing happens.") _, err = ToValue([]byte{}) - Is(err, "TypeError: Unable to convert value: [] ([]uint8)") + Is(err, "TypeError: Invalid value (slice): Missing runtime: [] ([]uint8)") _, err = Otto.Run(` (function(){ diff --git a/otto_test.go b/otto_test.go index 4a64c81..704c0ad 100644 --- a/otto_test.go +++ b/otto_test.go @@ -10,8 +10,11 @@ import ( "unicode/utf8" ) +var testOtto *Otto + func runTestWithOtto() (*Otto, func(string, ...interface{}) Value) { Otto := New() + testOtto = Otto test := func(name string, expect ...interface{}) Value { raise := false defer func() { diff --git a/reflect_test.go b/reflect_test.go new file mode 100644 index 0000000..7575fff --- /dev/null +++ b/reflect_test.go @@ -0,0 +1,290 @@ +package otto + +import ( + . "github.com/robertkrimen/terst" + "math" + //"os" + "reflect" + "testing" +) + +func failSet(name string, value interface{}) { + err := testOtto.Set(name, value) + Is(err, nil) + if err != nil { + Terst().TestingT.FailNow() + } +} + +type testStruct struct { + Abc bool + Def int + Ghi string +} + +func TestReflect(t *testing.T) { + Terst(t) + + if false { + // Testing dbgf + // These should panic + toValue("Xyzzy").toReflectValue(reflect.Ptr) + stringToReflectValue("Xyzzy", reflect.Ptr) + } +} + +func Test_reflectStruct(t *testing.T) { + Terst(t) + + _, test := runTestWithOtto() + + // testStruct + { + abc := &testStruct{} + failSet("abc", abc) + + test(` + [ abc.Abc, abc.Ghi ] + `, "false,") + + abc.Abc = true + abc.Ghi = "Nothing happens." + + test(` + [ abc.Abc, abc.Ghi ] + `, "true,Nothing happens.") + + *abc = testStruct{} + + test(` + [ abc.Abc, abc.Ghi ] + `, "false,") + + abc.Abc = true + abc.Ghi = "Xyzzy" + failSet("abc", abc) + + test(` + [ abc.Abc, abc.Ghi ] + `, "true,Xyzzy") + + Is(abc.Abc, true) + test(` + abc.Abc = false; + abc.Def = 451; + abc.Ghi = "Nothing happens."; + abc.abc = "Something happens."; + [ abc.Def, abc.abc ]; + `, "451,Something happens.") + Is(abc.Abc, false) + Is(abc.Def, 451) + Is(abc.Ghi, "Nothing happens.") + + test(` + delete abc.Def; + delete abc.abc; + [ abc.Def, abc.abc ]; + `, "451,") + Is(abc.Def, 451) + } +} + +func Test_reflectMap(t *testing.T) { + Terst(t) + + _, test := runTestWithOtto() + + // map[string]string + { + abc := map[string]string{ + "Xyzzy": "Nothing happens.", + "def": "1", + } + failSet("abc", abc) + + test(` + abc.xyz = "pqr"; + [ abc.Xyzzy, abc.def, abc.ghi ]; + `, "Nothing happens.,1,") + + Is(abc["xyz"], "pqr") + } + + // map[string]float64 + { + abc := map[string]float64{ + "Xyzzy": math.Pi, + "def": 1, + } + failSet("abc", abc) + + test(` + abc.xyz = "pqr"; + abc.jkl = 10; + [ abc.Xyzzy, abc.def, abc.ghi ]; + `, "3.141592653589793,1,") + + Is(abc["xyz"], "NaN") + Is(abc["jkl"], float64(10)) + } + + // map[string]int32 + { + abc := map[string]int32{ + "Xyzzy": 3, + "def": 1, + } + failSet("abc", abc) + + test(` + abc.xyz = "pqr"; + abc.jkl = 10; + [ abc.Xyzzy, abc.def, abc.ghi ]; + `, "3,1,") + + Is(abc["xyz"], 0) + Is(abc["jkl"], int32(10)) + + test(` + delete abc["Xyzzy"]; + `) + + _, exists := abc["Xyzzy"] + IsFalse(exists) + Is(abc["Xyzzy"], 0) + } + + // map[int32]string + { + abc := map[int32]string{ + 0: "abc", + 1: "def", + } + failSet("abc", abc) + + test(` + abc[2] = "pqr"; + //abc.jkl = 10; + abc[3] = 10; + [ abc[0], abc[1], abc[2], abc[3] ] + `, "abc,def,pqr,10") + + Is(abc[2], "pqr") + Is(abc[3], "10") + + test(` + delete abc[2]; + `) + + _, exists := abc[2] + IsFalse(exists) + } +} + +func Test_reflectSlice(t *testing.T) { + Terst(t) + + _, test := runTestWithOtto() + + // []bool + { + abc := []bool{ + false, + true, + true, + false, + } + failSet("abc", abc) + + test(` + abc + `, "false,true,true,false") + + test(` + abc[0] = true + abc[abc.length-1] = true + abc + `, "true,true,true,true") + + Is(abc, []bool{true, true, true, true}) + Is(abc[len(abc)-1], true) + } + + // []int32 + { + abc := make([]int32, 4) + failSet("abc", abc) + + test(` + abc + `, "0,0,0,0") + + test(` + abc[0] = 4.2 + abc[1] = "42" + abc[2] = 3.14 + abc + `, "4,42,3,0") + + Is(abc, []int32{4, 42, 3, 0}) + + test(` + delete abc[1] + delete abc[2] + `) + Is(abc[1], 0) + Is(abc[2], 0) + } +} + +func Test_reflectArray(t *testing.T) { + Terst(t) + + _, test := runTestWithOtto() + + // []bool + { + abc := [4]bool{ + false, + true, + true, + false, + } + failSet("abc", abc) + + test(` + abc + `, "false,true,true,false") + // Unaddressable array + + test(` + abc[0] = true + abc[abc.length-1] = true + abc + `, "false,true,true,false") + // Again, unaddressable array + + Is(abc, [4]bool{false, true, true, false}) + Is(abc[len(abc)-1], false) + // ... + } + + // []int32 + { + abc := make([]int32, 4) + failSet("abc", &abc) // Addressable + + test(` + abc + `, "0,0,0,0") + + test(` + abc[0] = 4.2 + abc[1] = "42" + abc[2] = 3.14 + abc + `, "4,42,3,0") + + Is(abc, []int32{4, 42, 3, 0}) + } +} diff --git a/runtime.go b/runtime.go index b5e39a2..4aa2c43 100644 --- a/runtime.go +++ b/runtime.go @@ -1,8 +1,8 @@ package otto import ( + "reflect" "strconv" - //"fmt" ) type _runtime struct { @@ -320,12 +320,36 @@ func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) { return } +func (self *_runtime) ToValue(value interface{}) (Value, error) { + result := UndefinedValue() + err := catchPanic(func() { + result = self.toValue(value) + }) + return result, err +} + func (self *_runtime) toValue(value interface{}) Value { switch value := value.(type) { case func(FunctionCall) Value: return toValue(self.newNativeFunction(value, 0, "nativeFunction")) case _nativeFunction: return toValue(self.newNativeFunction(value, 0, "nativeFunction")) + case Object, *Object, _object, *_object: + // Nothing happens. + default: + { + value := reflect.Indirect(reflect.ValueOf(value)) + switch value.Kind() { + case reflect.Struct: + return toValue(self.newGoStructObject(value)) + case reflect.Map: + return toValue(self.newGoMapObject(value)) + case reflect.Slice, reflect.Array: + object := self.newGoArrayObject(value) + object.prototype = self.Global.ArrayPrototype + return toValue(object) + } + } } return toValue(value) } diff --git a/type_array.go b/type_array.go index 753e398..f3dfb83 100644 --- a/type_array.go +++ b/type_array.go @@ -64,18 +64,6 @@ func (self *_arrayStash) get(name string) Value { return self._stash.get(name) } -func (self *_arrayStash) enumerate(each func(string)) { - // .0, .1, .2, ... - for index, _ := range self.valueArray { - if self.valueArray[index]._valueType == valueEmpty { - continue // A sparse array - } - name := strconv.FormatInt(int64(index), 10) - each(name) - } - self._stash.enumerate(each) -} - func (self *_arrayStash) property(name string) *_property { // length if name == "length" { @@ -101,6 +89,18 @@ func (self *_arrayStash) property(name string) *_property { return self._stash.property(name) } +func (self *_arrayStash) enumerate(each func(string)) { + // .0, .1, .2, ... + for index, _ := range self.valueArray { + if self.valueArray[index]._valueType == valueEmpty { + continue // A sparse array + } + name := strconv.FormatInt(int64(index), 10) + each(name) + } + self._stash.enumerate(each) +} + // write func (self *_arrayStash) canPut(name string) bool { diff --git a/type_function.go b/type_function.go index f21c8fb..9f404a0 100644 --- a/type_function.go +++ b/type_function.go @@ -31,7 +31,7 @@ func (self *_object) Call(this Value, argumentList ...interface{}) Value { if self._Function == nil { panic(newTypeError("%v is not a function", toValue(self))) } - return self.runtime.Call(self, this, toValueArray(argumentList...)) + return self.runtime.Call(self, this, self.runtime.toValueArray(argumentList...)) // ... -> runtime -> self.Function.Call.Dispatch -> ... } @@ -39,7 +39,7 @@ func (self *_object) Construct(this Value, argumentList ...interface{}) Value { if self._Function == nil { panic(newTypeError("%v is not a function", toValue(self))) } - return self._Function.Construct(self, this, toValueArray(argumentList...)) + return self._Function.Construct(self, this, self.runtime.toValueArray(argumentList...)) } func defaultConstructFunction(self *_object, this Value, argumentList []Value) Value { diff --git a/type_go_array.go b/type_go_array.go new file mode 100644 index 0000000..7d17686 --- /dev/null +++ b/type_go_array.go @@ -0,0 +1,205 @@ +package otto + +import ( + "reflect" + "strconv" +) + +func (runtime *_runtime) newGoArrayObject(value reflect.Value) *_object { + self := runtime.newObject() + self.class = "Array" // TODO Should this be something else? + self.stash = newGoArrayStash(value, self.stash) + return self +} + +type _goArrayStash struct { + value reflect.Value + writable bool + propertyMode _propertyMode + _stash +} + +func newGoArrayStash(value reflect.Value, stash _stash) *_goArrayStash { + propertyMode := _propertyMode(0111) + writable := true + switch value.Kind() { + case reflect.Slice: + case reflect.Array: + // TODO We need SliceOf to exists (go1.1) + // TODO Mark as unwritable for now + propertyMode = 0010 + writable = false + default: + dbgf("%/panic//%@: %v != reflect.Slice", value.Kind()) + } + self := &_goArrayStash{ + value: value, + writable: writable, + propertyMode: propertyMode, + _stash: stash, + } + return self +} + +func (self _goArrayStash) getValue(index int) (reflect.Value, bool) { + if index < self.value.Len() { + return self.value.Index(index), true + } + return reflect.Value{}, false +} + +func (self _goArrayStash) setValue(index int, value Value) { + indexValue, exists := self.getValue(index) + if !exists { + return + } + reflectValue, err := value.toReflectValue(self.value.Type().Elem().Kind()) + if err != nil { + panic(err) + } + indexValue.Set(reflectValue) +} + +// read + +func (self *_goArrayStash) test(name string) bool { + // length + if name == "length" { + return true + } + + // .0, .1, .2, ... + index := stringToArrayIndex(name) + if index >= 0 { + _, exists := self.getValue(int(index)) + return exists + } + + return self._stash.test(name) +} + +func (self *_goArrayStash) get(name string) Value { + // length + if name == "length" { + return toValue(self.value.Len()) + } + + // .0, .1, .2, ... + index := stringToArrayIndex(name) + if index >= 0 { + value, exists := self.getValue(int(index)) + if !exists { + return UndefinedValue() + } + return toValue(value) + } + + return self._stash.get(name) +} + +func (self *_goArrayStash) property(name string) *_property { + // length + if name == "length" { + return &_property{ + value: toValue(self.value.Len()), + mode: 0000, // -Write -Enumerate -Configure + // -Write is different from the standard Array + } + } + + // .0, .1, .2, ... + index := stringToArrayIndex(name) + if index >= 0 { + value := UndefinedValue() + reflectValue, exists := self.getValue(int(index)) + if exists { + value = toValue(reflectValue) + } + return &_property{ + value: value, + mode: self.propertyMode, // If addressable or not + } + } + + return self._stash.property(name) +} + +func (self *_goArrayStash) enumerate(each func(string)) { + // .0, .1, .2, ... + + for index, length := 0, self.value.Len(); index < length; index++ { + name := strconv.FormatInt(int64(index), 10) + each(name) + } + + self._stash.enumerate(each) +} + +// write + +func (self *_goArrayStash) canPut(name string) bool { + // length + if name == "length" { + return false + } + + // .0, .1, .2, ... + index := int(stringToArrayIndex(name)) + if index >= 0 { + if self.writable { + length := self.value.Len() + if index < length { + return self.writable + } + } + return false + } + + return self._stash.canPut(name) +} + +func (self *_goArrayStash) put(name string, value Value) { + // length + if name == "length" { + return + } + + // .0, .1, .2, ... + index := int(stringToArrayIndex(name)) + if index >= 0 { + if self.writable { + length := self.value.Len() + if index < length { + self.setValue(index, value) + } + } + return + } + + self._stash.put(name, value) +} + +func (self *_goArrayStash) delete(name string) { + // length + if name == "length" { + return + } + + // .0, .1, .2, ... + index := int(stringToArrayIndex(name)) + if index >= 0 { + if self.writable { + length := self.value.Len() + if index < length { + indexValue, exists := self.getValue(index) + if !exists { + return + } + indexValue.Set(reflect.Zero(self.value.Type().Elem())) + } + } + return + } + + self._stash.delete(name) +} diff --git a/type_go_map.go b/type_go_map.go new file mode 100644 index 0000000..39a8f30 --- /dev/null +++ b/type_go_map.go @@ -0,0 +1,101 @@ +package otto + +import ( + "reflect" +) + +func (runtime *_runtime) newGoMapObject(value reflect.Value) *_object { + self := runtime.newObject() + self.class = "Object" // TODO Should this be something else? + self.stash = newGoMapStash(value, self.stash) + return self +} + +type _goMapStash struct { + value reflect.Value + keyKind reflect.Kind + valueKind reflect.Kind + _stash +} + +func newGoMapStash(value reflect.Value, stash _stash) *_goMapStash { + if value.Kind() != reflect.Map { + dbgf("%/panic//%@: %v != reflect.Map", value.Kind()) + } + self := &_goMapStash{ + value: value, + keyKind: value.Type().Key().Kind(), + valueKind: value.Type().Elem().Kind(), + _stash: stash, + } + return self +} + +func (self _goMapStash) toKey(name string) reflect.Value { + reflectValue, err := stringToReflectValue(name, self.keyKind) + if err != nil { + panic(err) + } + return reflectValue +} + +func (self _goMapStash) toValue(value Value) reflect.Value { + reflectValue, err := value.toReflectValue(self.valueKind) + if err != nil { + panic(err) + } + return reflectValue +} + +// read + +func (self *_goMapStash) test(name string) bool { + value := self.value.MapIndex(self.toKey(name)) + if value.IsValid() { + return true + } + return false +} + +func (self *_goMapStash) get(name string) Value { + value := self.value.MapIndex(self.toKey(name)) + if value.IsValid() { + return toValue(value) + } + + return UndefinedValue() +} + +func (self *_goMapStash) property(name string) *_property { + value := self.value.MapIndex(self.toKey(name)) + if value.IsValid() { + return &_property{ + toValue(value), + 0111, // +Write +Enumerate +Configure + } + } + + return nil +} + +func (self *_goMapStash) enumerate(each func(string)) { + keys := self.value.MapKeys() + for _, key := range keys { + each(key.String()) + } +} + +// write + +func (self *_goMapStash) canPut(name string) bool { + return true +} + +func (self *_goMapStash) put(name string, value Value) { + self.value.SetMapIndex(self.toKey(name), self.toValue(value)) +} + +func (self *_goMapStash) delete(name string) { + // Setting a zero Value will delete the key + self.value.SetMapIndex(self.toKey(name), reflect.Value{}) +} diff --git a/type_go_struct.go b/type_go_struct.go new file mode 100644 index 0000000..d58b5b8 --- /dev/null +++ b/type_go_struct.go @@ -0,0 +1,118 @@ +package otto + +import ( + "reflect" +) + +func (runtime *_runtime) newGoStructObject(value reflect.Value) *_object { + self := runtime.newObject() + self.class = "Object" // TODO Should this be something else? + self.stash = newGoStructStash(value, self.stash) + return self +} + +type _goStructStash struct { + value reflect.Value + _stash +} + +func newGoStructStash(value reflect.Value, stash _stash) *_goStructStash { + if value.Kind() != reflect.Struct { + dbgf("%/panic//%@: %v != reflect.Struct", value.Kind()) + } + self := &_goStructStash{ + value: value, + _stash: stash, + } + return self +} + +func (self _goStructStash) getValue(name string) reflect.Value { + return self.value.FieldByName(name) +} + +func (self _goStructStash) field(name string) (reflect.StructField, bool) { + return self.value.Type().FieldByName(name) +} + +func (self _goStructStash) setValue(name string, value Value) bool { + field, exists := self.field(name) + if !exists { + return false + } + fieldValue := self.getValue(name) + reflectValue, err := value.toReflectValue(field.Type.Kind()) + if err != nil { + panic(err) + } + fieldValue.Set(reflectValue) + return true +} + +// read + +func (self *_goStructStash) test(name string) bool { + value := self.getValue(name) + if value.IsValid() { + return true + } + return self._stash.test(name) +} + +func (self *_goStructStash) get(name string) Value { + value := self.getValue(name) + if value.IsValid() { + return toValue(value) + } + + return self._stash.get(name) +} + +func (self *_goStructStash) property(name string) *_property { + value := self.getValue(name) + if value.IsValid() { + return &_property{ + toValue(value), + 0111, // +Write +Enumerate +Configure + } + } + + return self._stash.property(name) +} + +func (self *_goStructStash) enumerate(each func(string)) { + count := self.value.NumField() + type_ := self.value.Type() + for index := 0; index < count; index++ { + each(type_.Field(index).Name) + } + + self._stash.enumerate(each) +} + +// write + +func (self *_goStructStash) canPut(name string) bool { + value := self.getValue(name) + if value.IsValid() { + return true + } + + return self._stash.canPut(name) +} + +func (self *_goStructStash) put(name string, value Value) { + if self.setValue(name, value) { + return + } + + self._stash.put(name, value) +} + +func (self *_goStructStash) delete(name string) { + if _, exists := self.field(name); exists { + return + } + + self._stash.delete(name) +} diff --git a/value.go b/value.go index acf592f..183c67f 100644 --- a/value.go +++ b/value.go @@ -3,6 +3,8 @@ package otto import ( "fmt" "math" + "reflect" + "strconv" ) type _valueType int @@ -59,8 +61,6 @@ func (value Value) IsUndefined() bool { return value._valueType == valueUndefined } -// Any nil will do -- we just make a new throwaway type here - // NullValue will return a Value representing null. func NullValue() Value { return Value{_valueType: valueNull} @@ -232,6 +232,17 @@ func (value Value) isError() bool { // --- +func toValue_reflectValuePanic(value interface{}, kind reflect.Kind) { + switch kind { + case reflect.Struct: + panic(newTypeError("Invalid value (struct): Missing runtime: %v (%T)", value, value)) + case reflect.Map: + panic(newTypeError("Invalid value (map): Missing runtime: %v (%T)", value, value)) + case reflect.Slice: + panic(newTypeError("Invalid value (slice): Missing runtime: %v (%T)", value, value)) + } +} + func toValue(value interface{}) Value { switch value := value.(type) { case Value: @@ -273,8 +284,47 @@ func toValue(value interface{}) Value { return Value{valueObject, value.object} case _reference: // reference is an interface (already a pointer) return Value{valueReference, value} + case reflect.Value: + value = reflect.Indirect(value) + switch value.Kind() { + case reflect.Bool: + return Value{valueBoolean, bool(value.Bool())} + case reflect.Int: + return Value{valueNumber, int(value.Int())} + case reflect.Int8: + return Value{valueNumber, int8(value.Int())} + case reflect.Int16: + return Value{valueNumber, int16(value.Int())} + case reflect.Int32: + return Value{valueNumber, int32(value.Int())} + case reflect.Int64: + return Value{valueNumber, int64(value.Int())} + case reflect.Uint: + return Value{valueNumber, uint(value.Uint())} + case reflect.Uint8: + return Value{valueNumber, uint8(value.Uint())} + case reflect.Uint16: + return Value{valueNumber, uint16(value.Uint())} + case reflect.Uint32: + return Value{valueNumber, uint32(value.Uint())} + case reflect.Uint64: + return Value{valueNumber, uint64(value.Uint())} + case reflect.Float32: + return Value{valueNumber, float32(value.Float())} + case reflect.Float64: + return Value{valueNumber, float64(value.Float())} + case reflect.String: + return Value{valueString, string(value.String())} + default: + toValue_reflectValuePanic(value.Interface(), value.Kind()) + } + default: + { + value := reflect.Indirect(reflect.ValueOf(value)) + toValue_reflectValuePanic(value.Interface(), value.Kind()) + } } - panic(newTypeError("Unable to convert value: %v (%T)", value, value)) + panic(newTypeError("Invalid value: Unsupported: %v (%T)", value, value)) } // String will return the value as a string. @@ -395,15 +445,13 @@ func (value Value) reference() _reference { return nil } -var __NaN__, __PositiveInfinity__, __NegativeInfinity__, __PositiveZero__, __NegativeZero__ float64 - -func init() { - __NaN__ = math.NaN() - __PositiveInfinity__ = math.Inf(+1) - __NegativeInfinity__ = math.Inf(-1) - __PositiveZero__ = 0 - __NegativeZero__ = math.Float64frombits(0 | (1 << 63)) -} +var ( + __NaN__ float64 = math.NaN() + __PositiveInfinity__ float64 = math.Inf(+1) + __NegativeInfinity__ float64 = math.Inf(-1) + __PositiveZero__ float64 = 0 + __NegativeZero__ float64 = math.Float64frombits(0 | (1 << 63)) +) func positiveInfinity() float64 { return __PositiveInfinity__ @@ -564,3 +612,187 @@ func (value Value) Export() (interface{}, error) { return nil, nil } + +func (value Value) toReflectValue(kind reflect.Kind) (reflect.Value, error) { + switch kind { + case reflect.Bool: + return reflect.ValueOf(value.toBoolean()), nil + case reflect.Int: + tmp := toIntegerFloat(value) + if tmp < _MinInt_ || tmp > _MaxInt_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to int", tmp, value) + } else { + return reflect.ValueOf(int(tmp)), nil + } + case reflect.Int8: + tmp := toInteger(value) + if tmp < _MinInt8_ || tmp > _MaxInt8_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to int8", tmp, value) + } else { + return reflect.ValueOf(int8(tmp)), nil + } + case reflect.Int16: + tmp := toInteger(value) + if tmp < _MinInt16_ || tmp > _MaxInt16_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to int16", tmp, value) + } else { + return reflect.ValueOf(int16(tmp)), nil + } + case reflect.Int32: + tmp := toInteger(value) + if tmp < _MinInt32_ || tmp > _MaxInt32_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to int32", tmp, value) + } else { + return reflect.ValueOf(int32(tmp)), nil + } + case reflect.Int64: + tmp := toIntegerFloat(value) + if tmp < _MinInt64_ || tmp > _MaxInt64_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to int", tmp, value) + } else { + return reflect.ValueOf(int64(tmp)), nil + } + case reflect.Uint: + tmp := toIntegerFloat(value) + if tmp < 0 || tmp > _MaxUint_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to uint", tmp, value) + } else { + return reflect.ValueOf(uint(tmp)), nil + } + case reflect.Uint8: + tmp := toInteger(value) + if tmp < 0 || tmp > _MaxUint8_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to uint8", tmp, value) + } else { + return reflect.ValueOf(uint8(tmp)), nil + } + case reflect.Uint16: + tmp := toInteger(value) + if tmp < 0 || tmp > _MaxUint16_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to uint16", tmp, value) + } else { + return reflect.ValueOf(uint16(tmp)), nil + } + case reflect.Uint32: + tmp := toInteger(value) + if tmp < 0 || tmp > _MaxUint32_ { + return reflect.Value{}, fmt.Errorf("RangeError: %d (%v) to uint32", tmp, value) + } else { + return reflect.ValueOf(uint32(tmp)), nil + } + case reflect.Uint64: + tmp := toIntegerFloat(value) + if tmp < 0 || tmp > _MaxUint64_ { + return reflect.Value{}, fmt.Errorf("RangeError: %f (%v) to uint64", tmp, value) + } else { + return reflect.ValueOf(uint64(tmp)), nil + } + case reflect.Float32: + tmp := toFloat(value) + tmp1 := tmp + if 0 > tmp1 { + tmp1 = -tmp1 + } + if tmp1 < math.SmallestNonzeroFloat32 || tmp1 > math.MaxFloat32 { + return reflect.Value{}, fmt.Errorf("RangeError: %f (%v) to float32", tmp, value) + } else { + return reflect.ValueOf(float32(tmp)), nil + } + case reflect.Float64: + value := toFloat(value) + return reflect.ValueOf(float64(value)), nil + case reflect.String: + return reflect.ValueOf(value.toString()), nil + } + + dbgf("%/panic//%@: Invalid: (%v) to reflect.Kind: %v", value, kind) + panic("") +} + +func stringToReflectValue(value string, kind reflect.Kind) (reflect.Value, error) { + switch kind { + case reflect.Bool: + value, err := strconv.ParseBool(value) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(value), nil + case reflect.Int: + value, err := strconv.ParseInt(value, 0, 0) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(int(value)), nil + case reflect.Int8: + value, err := strconv.ParseInt(value, 0, 8) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(int8(value)), nil + case reflect.Int16: + value, err := strconv.ParseInt(value, 0, 16) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(int16(value)), nil + case reflect.Int32: + value, err := strconv.ParseInt(value, 0, 32) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(int32(value)), nil + case reflect.Int64: + value, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(int64(value)), nil + case reflect.Uint: + value, err := strconv.ParseUint(value, 0, 0) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(uint(value)), nil + case reflect.Uint8: + value, err := strconv.ParseUint(value, 0, 8) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(uint8(value)), nil + case reflect.Uint16: + value, err := strconv.ParseUint(value, 0, 16) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(uint16(value)), nil + case reflect.Uint32: + value, err := strconv.ParseUint(value, 0, 32) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(uint32(value)), nil + case reflect.Uint64: + value, err := strconv.ParseUint(value, 0, 64) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(uint64(value)), nil + case reflect.Float32: + value, err := strconv.ParseFloat(value, 32) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(float32(value)), nil + case reflect.Float64: + value, err := strconv.ParseFloat(value, 64) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(float64(value)), nil + case reflect.String: + return reflect.ValueOf(value), nil + } + + dbgf("%/panic//%@: Invalid: \"%s\" to reflect.Kind: %v", value, kind) + panic("") +} diff --git a/value_number.go b/value_number.go index a551efe..57ad7b3 100644 --- a/value_number.go +++ b/value_number.go @@ -100,6 +100,46 @@ const ( sqrt1_2 float64 = math.Sqrt2 / 2 ) +const ( + _MaxInt8 = math.MaxInt8 + _MinInt8 = math.MinInt8 + _MaxInt16 = math.MaxInt16 + _MinInt16 = math.MinInt16 + _MaxInt32 = math.MaxInt32 + _MinInt32 = math.MinInt32 + _MaxInt64 = math.MaxInt64 + _MinInt64 = math.MinInt64 + _MaxUint8 = math.MaxUint8 + _MaxUint16 = math.MaxUint16 + _MaxUint32 = math.MaxUint32 + _MaxUint64 = math.MaxUint64 + _MaxUint = ^uint(0) + _MinUint = 0 + _MaxInt = int(^uint(0) >> 1) + _MinInt = -_MaxInt - 1 + + // int64 + _MaxInt8_ int64 = math.MaxInt8 + _MinInt8_ int64 = math.MinInt8 + _MaxInt16_ int64 = math.MaxInt16 + _MinInt16_ int64 = math.MinInt16 + _MaxInt32_ int64 = math.MaxInt32 + _MinInt32_ int64 = math.MinInt32 + _MaxUint8_ int64 = math.MaxUint8 + _MaxUint16_ int64 = math.MaxUint16 + _MaxUint32_ int64 = math.MaxUint32 + + // float64 + _MaxInt_ float64 = float64(int(^uint(0) >> 1)) + _MinInt_ float64 = float64(int(-_MaxInt - 1)) + _MinUint_ float64 = float64(0) + _MaxUint_ float64 = float64(uint(^uint(0))) + _MinUint64_ float64 = float64(0) + _MaxUint64_ float64 = math.MaxUint64 + _MaxInt64_ float64 = math.MaxInt64 + _MinInt64_ float64 = math.MinInt64 +) + func toIntegerFloat(value Value) float64 { floatValue := value.toFloat() if math.IsNaN(floatValue) { @@ -132,7 +172,8 @@ func toInteger(value Value) int64 { if math.IsNaN(floatValue) { return 0 } else if math.IsInf(floatValue, 0) { - panic(hereBeDragons()) + // FIXME + dbgf("%/panic//%@: %f %v to int64", floatValue, value) } if floatValue > 0 { return int64(math.Floor(floatValue))