Unverified Commit 86fae6a8 authored by 33cn's avatar 33cn Committed by GitHub

Merge pull request #192 from vipwzw/js

Js
parents edd90338 8a87b8ec
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
_ "github.com/33cn/plugin/plugin/dapp/evm" //auto gen _ "github.com/33cn/plugin/plugin/dapp/evm" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/game" //auto gen _ "github.com/33cn/plugin/plugin/dapp/game" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/hashlock" //auto gen _ "github.com/33cn/plugin/plugin/dapp/hashlock" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/js" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen _ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/multisig" //auto gen _ "github.com/33cn/plugin/plugin/dapp/multisig" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/norm" //auto gen _ "github.com/33cn/plugin/plugin/dapp/norm" //auto gen
......
all:
chmod +x ./build.sh
./build.sh $(OUT) $(FLAG)
\ No newline at end of file
#!/bin/sh
strpwd=$(pwd)
strcmd=${strpwd##*dapp/}
strapp=${strcmd%/cmd*}
OUT_DIR="${1}/$strapp"
#FLAG=$2
mkdir -p "${OUT_DIR}"
cp ./build/* "${OUT_DIR}"
package executor
var callcode = `
var tojson = JSON.stringify
function kvcreator(dbtype) {
this.data = {}
this.kvs = []
this.logs = []
this.type = dbtype
this.getstate = getstatedb
this.getloal = getlocaldb
this.list = listdb
if (dbtype == "exec" || dbtype == "init") {
this.get = getstatedb
} else if (dbtype == "local") {
this.get = getlocaldb
} else if (dbtype == "query") {
this.get = getlocaldb
} else {
throw new Error("chain33.js: dbtype error")
}
}
kvcreator.prototype.add = function(k, v) {
if (typeof v != "string") {
v = JSON.stringify(v)
}
this.data[k] = v
this.kvs.push({key:k, value: v})
}
kvcreator.prototype.get = function(k) {
var v
if (this.data[k]) {
v = this.data[k]
} else {
var dbvalue = this.get(k)
if (dbvalue.err != "") {
return null
}
v = dbvalue.value
}
if (!v) {
return null
}
return JSON.parse(v)
}
kvcreator.prototype.listvalue = function(prefix, key, count, direction) {
var dbvalues = this.list(prefix, key, count, direction)
if (dbvalues.err != "") {
return []
}
var values = dbvalues.value
if (!values || values.length == 0) {
return []
}
var objlist = []
for (var i = 0; i < values.length; i++) {
objlist.push(JSON.parse(values[i]))
}
return objlist
}
kvcreator.prototype.addlog = function(log) {
if (this.type != "exec") {
throw new Error("local or query can't set log")
}
if (typeof v != "string") {
log = JSON.stringify(log)
}
this.logs.push(log)
}
kvcreator.prototype.receipt = function() {
return {kvs: this.kvs, logs: this.logs}
}
function callcode(context, f, args, loglist) {
if (f == "init") {
return Init(JSON.parse(context))
}
var farr = f.split("_", 2)
if (farr.length != 2) {
throw new Error("chain33.js: invalid function name format")
}
var prefix = farr[0]
var funcname = farr[1]
var runobj = {}
var logs = []
if (!Array.isArray(loglist)) {
throw new Error("chain33.js: loglist must be array")
}
for (var i = 0; i < loglist.length; i++) {
logs.push(JSON.parse(loglist[i]))
}
if (prefix == "exec") {
runobj = new Exec(JSON.parse(context))
} else if (prefix == "execlocal") {
runobj = new ExecLocal(JSON.parse(context), logs)
} else if (prefix == "query") {
runobj = new Query(JSON.parse(context))
} else {
throw new Error("chain33.js: invalid function prefix format")
}
var arg = JSON.parse(args)
if (typeof runobj[funcname] != "function") {
throw new Error("chain33.js: invalid function name not found")
}
return runobj[funcname](arg)
}
`
package executor
import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
)
func (c *js) Exec_Create(payload *jsproto.Create, tx *types.Transaction, index int) (*types.Receipt, error) {
execer := types.ExecName("user.js." + payload.Name)
c.prefix = calcStatePrefix([]byte(execer))
kvc := dapp.NewKVCreator(c.GetStateDB(), c.prefix, nil)
_, err := kvc.GetNoPrefix(calcCodeKey(payload.Name))
if err != nil && err != types.ErrNotFound {
return nil, err
}
if err == nil {
return nil, ptypes.ErrDupName
}
kvc.AddNoPrefix(calcCodeKey(payload.Name), []byte(payload.Code))
jsvalue, err := c.callVM("init", &jsproto.Call{Name: payload.Name}, tx, index, nil)
if err != nil {
return nil, err
}
kvs, logs, err := parseJsReturn(jsvalue)
if err != nil {
return nil, err
}
kvc.AddList(kvs)
r := &types.Receipt{Ty: types.ExecOk, KV: kvc.KVList(), Logs: logs}
return r, nil
}
func (c *js) Exec_Call(payload *jsproto.Call, tx *types.Transaction, index int) (*types.Receipt, error) {
execer := types.ExecName("user.js." + payload.Name)
c.prefix = calcStatePrefix([]byte(execer))
kvc := dapp.NewKVCreator(c.GetStateDB(), c.prefix, nil)
jsvalue, err := c.callVM("exec", payload, tx, index, nil)
if err != nil {
return nil, err
}
kvs, logs, err := parseJsReturn(jsvalue)
if err != nil {
return nil, err
}
kvc.AddList(kvs)
r := &types.Receipt{Ty: types.ExecOk, KV: kvc.KVList(), Logs: logs}
return r, nil
}
package executor
import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
)
func (c *js) ExecDelLocal_Create(payload *jsproto.Create, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return &types.LocalDBSet{}, nil
}
func (c *js) ExecDelLocal_Call(payload *jsproto.Call, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
krollback := calcRollbackKey(tx.Hash())
execer := types.ExecName("user.js." + payload.Name)
c.prefix = calcLocalPrefix([]byte(execer))
kvc := dapp.NewKVCreator(c.GetLocalDB(), c.prefix, krollback)
kvs, err := kvc.GetRollbackKVList()
if err != nil {
return nil, err
}
for _, kv := range kvs {
kvc.AddNoPrefix(kv.Key, kv.Value)
}
kvc.DelRollbackKV()
r := &types.LocalDBSet{}
r.KV = kvc.KVList()
return r, nil
}
package executor
import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
)
func (c *js) ExecLocal_Create(payload *jsproto.Create, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return &types.LocalDBSet{}, nil
}
func (c *js) ExecLocal_Call(payload *jsproto.Call, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
k := calcRollbackKey(tx.Hash())
execer := types.ExecName("user.js." + payload.Name)
c.prefix = calcLocalPrefix([]byte(execer))
kvc := dapp.NewKVCreator(c.GetLocalDB(), c.prefix, k)
jsvalue, err := c.callVM("execlocal", payload, tx, index, receiptData)
if err != nil {
return nil, err
}
kvs, _, err := parseJsReturn(jsvalue)
if err != nil {
return nil, err
}
kvc.AddList(kvs)
kvc.AddRollbackKV()
r := &types.LocalDBSet{}
r.KV = kvc.KVList()
return r, nil
}
package executor
import (
"encoding/json"
"github.com/33cn/chain33/common"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
"github.com/robertkrimen/otto"
)
var driverName = ptypes.JsX
func init() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&js{}))
}
//Init 插件初始化
func Init(name string, sub []byte) {
drivers.Register(GetName(), newjs, 0)
}
type js struct {
drivers.DriverBase
prefix []byte
}
func newjs() drivers.Driver {
t := &js{}
t.SetChild(t)
t.SetExecutorType(types.LoadExecutorType(driverName))
return t
}
//GetName 获取名字
func GetName() string {
return newjs().GetName()
}
//GetDriverName 获取插件的名字
func (u *js) GetDriverName() string {
return driverName
}
func (u *js) callVM(prefix string, payload *jsproto.Call, tx *types.Transaction,
index int, receiptData *types.ReceiptData) (*otto.Object, error) {
vm, err := u.createVM(payload.Name, tx, index)
if err != nil {
return nil, err
}
db := u.GetStateDB()
code, err := db.Get(calcCodeKey(payload.Name))
if err != nil {
return nil, err
}
loglist, err := jslogs(receiptData)
if err != nil {
return nil, err
}
vm.Set("loglist", loglist)
vm.Set("code", code)
if prefix == "init" {
vm.Set("f", "init")
} else {
vm.Set("f", prefix+"_"+payload.Funcname)
}
vm.Set("args", payload.Args)
callfunc := "callcode(context, f, args, loglist)"
jsvalue, err := vm.Run(callcode + string(code) + "\n" + callfunc)
if err != nil {
return nil, err
}
if prefix == "query" {
s, err := jsvalue.ToString()
if err != nil {
return nil, err
}
return newObject(vm).setValue("result", s).object(), nil
}
if !jsvalue.IsObject() {
return nil, ptypes.ErrJsReturnNotObject
}
return jsvalue.Object(), nil
}
func jslogs(receiptData *types.ReceiptData) ([]string, error) {
data := make([]string, 0)
if receiptData == nil {
return data, nil
}
for i := 0; i < len(receiptData.Logs); i++ {
logitem := receiptData.Logs[i]
if logitem.Ty != ptypes.TyLogJs {
continue
}
var jslog jsproto.JsLog
err := types.Decode(logitem.Log, &jslog)
if err != nil {
return nil, err
}
data = append(data, jslog.Data)
}
return data, nil
}
func (u *js) getContext(tx *types.Transaction, index int64) *blockContext {
var hash [32]byte
if tx != nil {
copy(hash[:], tx.Hash())
}
return &blockContext{
Height: u.GetHeight(),
Name: u.GetName(),
Blocktime: u.GetBlockTime(),
Curname: u.GetCurrentExecName(),
DriverName: u.GetDriverName(),
Difficulty: u.GetDifficulty(),
TxHash: common.ToHex(hash[:]),
Index: index,
}
}
func (u *js) getstatedbFunc(vm *otto.Otto, name string) {
prefix, _ := calcAllPrefix(name)
vm.Set("getstatedb", func(call otto.FunctionCall) otto.Value {
key, err := call.Argument(0).ToString()
if err != nil {
return errReturn(vm, err)
}
v, err := u.getstatedb(string(prefix) + key)
if err != nil {
return errReturn(vm, err)
}
return okReturn(vm, v)
})
}
func (u *js) getlocaldbFunc(vm *otto.Otto, name string) {
_, prefix := calcAllPrefix(name)
vm.Set("getlocaldb", func(call otto.FunctionCall) otto.Value {
key, err := call.Argument(0).ToString()
if err != nil {
return errReturn(vm, err)
}
v, err := u.getlocaldb(string(prefix) + key)
if err != nil {
return errReturn(vm, err)
}
return okReturn(vm, v)
})
}
func (u *js) listdbFunc(vm *otto.Otto, name string) {
//List(prefix, key []byte, count, direction int32) ([][]byte, error)
_, plocal := calcAllPrefix(name)
vm.Set("listdb", func(call otto.FunctionCall) otto.Value {
prefix, err := call.Argument(0).ToString()
if err != nil {
return errReturn(vm, err)
}
key, err := call.Argument(1).ToString()
if err != nil {
return errReturn(vm, err)
}
count, err := call.Argument(2).ToInteger()
if err != nil {
return errReturn(vm, err)
}
direction, err := call.Argument(3).ToInteger()
if err != nil {
return errReturn(vm, err)
}
v, err := u.listdb(string(plocal)+prefix, key, int32(count), int32(direction))
if err != nil {
return errReturn(vm, err)
}
return listReturn(vm, v)
})
}
func (u *js) createVM(name string, tx *types.Transaction, index int) (*otto.Otto, error) {
data, err := json.Marshal(u.getContext(tx, int64(index)))
if err != nil {
return nil, err
}
vm := otto.New()
vm.Set("context", string(data))
u.getstatedbFunc(vm, name)
u.getlocaldbFunc(vm, name)
u.listdbFunc(vm, name)
return vm, nil
}
func errReturn(vm *otto.Otto, err error) otto.Value {
return newObject(vm).setErr(err).value()
}
func okReturn(vm *otto.Otto, value string) otto.Value {
return newObject(vm).setValue("value", value).value()
}
func listReturn(vm *otto.Otto, value []string) otto.Value {
return newObject(vm).setValue("value", value).value()
}
type object struct {
vm *otto.Otto
obj *otto.Object
}
func newObject(vm *otto.Otto) *object {
obj, err := vm.Object("({})")
if err != nil {
panic(err)
}
return &object{vm: vm, obj: obj}
}
func (o *object) setErr(err error) *object {
if err != nil {
o.obj.Set("err", err.Error())
}
return o
}
func (o *object) setValue(key string, value interface{}) *object {
o.obj.Set(key, value)
return o
}
func (o *object) object() *otto.Object {
return o.obj
}
func (o *object) value() otto.Value {
v, err := otto.ToValue(o.obj)
if err != nil {
panic(err)
}
return v
}
func (u *js) getstatedb(key string) (value string, err error) {
s, err := u.GetStateDB().Get([]byte(key))
value = string(s)
return value, err
}
func (u *js) getlocaldb(key string) (value string, err error) {
s, err := u.GetLocalDB().Get([]byte(key))
value = string(s)
return value, err
}
func (u *js) listdb(prefix, key string, count, direction int32) (value []string, err error) {
values, err := u.GetLocalDB().List([]byte(prefix), []byte(key), count, direction)
for _, v := range values {
value = append(value, string(v))
}
return value, err
}
package executor
import (
"errors"
"fmt"
"github.com/33cn/chain33/types"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
"github.com/robertkrimen/otto"
)
type blockContext struct {
Height int64 `json:"height"`
Blocktime int64 `json:"blocktime"`
DriverName string `json:"driverName"`
Name string `json:"name"`
Curname string `json:"curname"`
Difficulty uint64 `json:"difficulty"`
TxHash string `json:"txhash"`
Index int64 `json:"index"`
}
func parseJsReturn(jsvalue *otto.Object) (kvlist []*types.KeyValue, logs []*types.ReceiptLog, err error) {
//kvs
obj, err := getObject(jsvalue, "kvs")
if err != nil {
return nil, nil, ptypes.ErrJsReturnKVSFormat
}
if obj.Class() != "Array" {
return nil, nil, ptypes.ErrJsReturnKVSFormat
}
size, err := getInt(obj, "length")
if err != nil {
return nil, nil, err
}
for i := 0; i < int(size); i++ {
data, err := getObject(obj, fmt.Sprint(i))
if err != nil {
return nil, nil, err
}
kv, err := parseKV(data)
if err != nil {
return nil, nil, err
}
kvlist = append(kvlist, kv)
}
//logs
obj, err = getObject(jsvalue, "logs")
if err != nil {
return nil, nil, ptypes.ErrJsReturnLogsFormat
}
if obj.Class() != "Array" {
return nil, nil, ptypes.ErrJsReturnLogsFormat
}
size, err = getInt(obj, "length")
if err != nil {
return nil, nil, err
}
for i := 0; i < int(size); i++ {
data, err := getString(obj, fmt.Sprint(i))
if err != nil {
return nil, nil, err
}
l := &types.ReceiptLog{
Ty: ptypes.TyLogJs, Log: types.Encode(&jsproto.JsLog{Data: data})}
logs = append(logs, l)
}
return kvlist, logs, nil
}
func getString(data *otto.Object, key string) (string, error) {
v, err := data.Get(key)
if err != nil {
return "", err
}
return v.ToString()
}
func getInt(data *otto.Object, key string) (int64, error) {
v, err := data.Get(key)
if err != nil {
return 0, err
}
return v.ToInteger()
}
func getObject(data *otto.Object, key string) (*otto.Object, error) {
v, err := data.Get(key)
if err != nil {
return nil, err
}
if !v.IsObject() {
return nil, errors.New("chain33.js object get key " + key + " is not object")
}
return v.Object(), nil
}
func parseKV(data *otto.Object) (kv *types.KeyValue, err error) {
key, err := getString(data, "key")
if err != nil {
return nil, err
}
value, err := getString(data, "value")
if err != nil {
return nil, err
}
return &types.KeyValue{Key: []byte(key), Value: []byte(value)}, nil
}
package executor
import (
"encoding/json"
"strings"
"testing"
"time"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
"github.com/robertkrimen/otto"
"github.com/stretchr/testify/assert"
)
var jscode = `
//数据结构设计
//kvlist [{key:"key1", value:"value1"},{key:"key2", value:"value2"}]
//log 设计 {json data}
function Init(context) {
this.kvc = new kvcreator("init")
this.context = context
this.kvc.add("action", "init")
this.kvc.add("context", this.context)
return this.kvc.receipt()
}
function Exec(context) {
this.kvc = new kvcreator("exec")
this.context = context
}
function ExecLocal(context, logs) {
this.kvc = new kvcreator("local")
this.context = context
this.logs = logs
}
function Query(context) {
this.kvc = new kvcreator("query")
this.context = context
}
Exec.prototype.hello = function(args) {
this.kvc.add("args", args)
this.kvc.add("action", "exec")
this.kvc.add("context", this.context)
this.kvc.addlog({"key1": "value1"})
this.kvc.addlog({"key2": "value2"})
return this.kvc.receipt()
}
ExecLocal.prototype.hello = function(args) {
this.kvc.add("args", args)
this.kvc.add("action", "execlocal")
this.kvc.add("log", this.logs)
this.kvc.add("context", this.context)
return this.kvc.receipt()
}
//return a json string
Query.prototype.hello = function(args) {
var obj = getlocaldb("context")
return tojson(obj)
}
`
func initExec(ldb db.DB, kvdb db.KVDB, t *testing.T) *js {
e := newjs().(*js)
e.SetEnv(1, time.Now().Unix(), 1)
e.SetLocalDB(kvdb)
e.SetStateDB(kvdb)
c, tx := createCodeTx("test", jscode)
receipt, err := e.Exec_Create(c, tx, 0)
assert.Nil(t, err)
util.SaveKVList(ldb, receipt.KV)
return e
}
func createCodeTx(name, jscode string) (*jsproto.Create, *types.Transaction) {
data := &jsproto.Create{
Code: jscode,
Name: name,
}
return data, &types.Transaction{Execer: []byte("js"), Payload: types.Encode(data)}
}
func callCodeTx(name, f, args string) (*jsproto.Call, *types.Transaction) {
data := &jsproto.Call{
Funcname: f,
Name: name,
Args: args,
}
return data, &types.Transaction{Execer: []byte("js"), Payload: types.Encode(data)}
}
func TestCallcode(t *testing.T) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
e := initExec(ldb, kvdb, t)
call, tx := callCodeTx("test", "hello", `{"hello":"world"}`)
receipt, err := e.Exec_Call(call, tx, 0)
assert.Nil(t, err)
util.SaveKVList(ldb, receipt.KV)
assert.Equal(t, string(receipt.KV[0].Value), `{"hello":"world"}`)
assert.Equal(t, string(receipt.KV[1].Value), "exec")
var data blockContext
err = json.Unmarshal(receipt.KV[2].Value, &data)
assert.Nil(t, err)
assert.Equal(t, uint64(1), data.Difficulty)
assert.Equal(t, "js", data.DriverName)
assert.Equal(t, int64(1), data.Height)
assert.Equal(t, int64(0), data.Index)
kvset, err := e.ExecLocal_Call(call, tx, &types.ReceiptData{Logs: receipt.Logs}, 0)
assert.Nil(t, err)
util.SaveKVList(ldb, kvset.KV)
assert.Equal(t, string(kvset.KV[0].Value), `{"hello":"world"}`)
assert.Equal(t, string(kvset.KV[1].Value), "execlocal")
//test log is ok
assert.Equal(t, string(kvset.KV[2].Value), `[{"key1":"value1"},{"key2":"value2"}]`)
//test context
err = json.Unmarshal(kvset.KV[3].Value, &data)
assert.Nil(t, err)
assert.Equal(t, uint64(1), data.Difficulty)
assert.Equal(t, "js", data.DriverName)
assert.Equal(t, int64(1), data.Height)
assert.Equal(t, int64(0), data.Index)
//call query
jsondata, err := e.Query_Query(call)
assert.Nil(t, err)
err = json.Unmarshal([]byte(jsondata.(*jsproto.QueryResult).Data), &data)
assert.Nil(t, err)
assert.Equal(t, uint64(1), data.Difficulty)
assert.Equal(t, "js", data.DriverName)
assert.Equal(t, int64(1), data.Height)
assert.Equal(t, int64(0), data.Index)
//call rollback
kvset, err = e.ExecDelLocal_Call(call, tx, &types.ReceiptData{Logs: receipt.Logs}, 0)
assert.Nil(t, err)
util.SaveKVList(ldb, kvset.KV)
assert.Equal(t, 5, len(kvset.KV))
for i := 0; i < len(kvset.KV); i++ {
assert.Equal(t, kvset.KV[i].Value, []byte(nil))
}
}
func TestCallError(t *testing.T) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
e := initExec(ldb, kvdb, t)
//test call error(invalid json input)
call, tx := callCodeTx("test", "hello", `{hello":"world"}`)
_, err := e.callVM("exec", call, tx, 0, nil)
_, ok := err.(*otto.Error)
assert.Equal(t, true, ok)
assert.Equal(t, true, strings.Contains(err.Error(), "SyntaxError"))
call, tx = callCodeTx("test", "hello", `{"hello":"world"}`)
_, err = e.callVM("hello", call, tx, 0, nil)
_, ok = err.(*otto.Error)
assert.Equal(t, true, ok)
assert.Equal(t, true, strings.Contains(err.Error(), ptypes.ErrInvalidFuncPrefix.Error()))
call, tx = callCodeTx("test", "hello2", `{"hello":"world"}`)
_, err = e.callVM("exec", call, tx, 0, nil)
_, ok = err.(*otto.Error)
assert.Equal(t, true, ok)
assert.Equal(t, true, strings.Contains(err.Error(), ptypes.ErrFuncNotFound.Error()))
}
func TestCalcLocalPrefix(t *testing.T) {
assert.Equal(t, calcLocalPrefix([]byte("a")), []byte("LODB-a-"))
assert.Equal(t, calcStatePrefix([]byte("a")), []byte("mavl-a-"))
assert.Equal(t, calcCodeKey("a"), []byte("mavl-js-code-a"))
assert.Equal(t, calcRollbackKey([]byte("a")), []byte("LODB-js-rollback-a"))
}
package executor
import "github.com/33cn/chain33/types"
func calcLocalPrefix(execer []byte) []byte {
s := append([]byte("LODB-"), execer...)
s = append(s, byte('-'))
return s
}
func calcStatePrefix(execer []byte) []byte {
s := append([]byte("mavl-"), execer...)
s = append(s, byte('-'))
return s
}
func calcAllPrefix(name string) ([]byte, []byte) {
execer := types.ExecName("user.js." + name)
state := calcStatePrefix([]byte(execer))
local := calcLocalPrefix([]byte(execer))
return state, local
}
func calcCodeKey(name string) []byte {
return append([]byte("mavl-js-code-"), []byte(name)...)
}
func calcRollbackKey(hash []byte) []byte {
return append([]byte("LODB-js-rollback-"), hash...)
}
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
)
func (c *js) Query_Query(payload *jsproto.Call) (types.Message, error) {
jsvalue, err := c.callVM("query", payload, nil, 0, nil)
if err != nil {
fmt.Println("query", err)
return nil, err
}
str, err := getString(jsvalue, "result")
if err != nil {
fmt.Println("result", err)
return nil, err
}
return &jsproto.QueryResult{Data: str}, nil
}
package unfreeze
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/js/executor"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
)
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: ptypes.JsX,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: nil,
RPC: nil,
})
}
all:
./create_protobuf.sh
#!/bin/sh
protoc --go_out=plugins=grpc:../types/jsproto/ ./*.proto
syntax = "proto3";
package jsproto;
// create action
message Create {
string code = 1;
string name = 2;
}
// call action
message Call {
string name = 1; //exec name
string funcname = 2; //call function name
string args = 3; //json args
}
message JsAction {
oneof value {
Create create = 1;
Call call = 2;
}
int32 ty = 3;
}
message JsLog {
string data = 1;
}
message QueryResult {
string data = 1;
}
\ No newline at end of file
package types
import (
"errors"
"reflect"
"github.com/33cn/chain33/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
)
// action for executor
const (
jsActionCreate = 0
jsActionCall = 1
)
//日志类型
const (
TyLogJs = 10000
)
var (
typeMap = map[string]int32{
"Create": jsActionCreate,
"Call": jsActionCall,
}
logMap = map[int64]*types.LogInfo{
TyLogJs: {Ty: reflect.TypeOf(jsproto.JsLog{}), Name: "TyLogJs"},
}
)
//JsX 插件名字
var JsX = "js"
//错误常量
var (
ErrDupName = errors.New("ErrDupName")
ErrJsReturnNotObject = errors.New("ErrJsReturnNotObject")
ErrJsReturnKVSFormat = errors.New("ErrJsReturnKVSFormat")
ErrJsReturnLogsFormat = errors.New("ErrJsReturnLogsFormat")
//ErrInvalidFuncFormat 错误的函数调用格式(没有_)
ErrInvalidFuncFormat = errors.New("chain33.js: invalid function name format")
//ErrInvalidFuncPrefix not exec_ execloal_ query_
ErrInvalidFuncPrefix = errors.New("chain33.js: invalid function prefix format")
//ErrFuncNotFound 函数没有找到
ErrFuncNotFound = errors.New("chain33.js: invalid function name not found")
)
func init() {
types.AllowUserExec = append(types.AllowUserExec, []byte(JsX))
types.RegistorExecutor(JsX, NewType())
}
//JsType 类型
type JsType struct {
types.ExecTypeBase
}
//NewType 新建一个plugin 类型
func NewType() *JsType {
c := &JsType{}
c.SetChild(c)
return c
}
//GetPayload 获取 交易构造
func (t *JsType) GetPayload() types.Message {
return &jsproto.JsAction{}
}
//GetTypeMap 获取类型映射
func (t *JsType) GetTypeMap() map[string]int32 {
return typeMap
}
//GetLogMap 获取日志映射
func (t *JsType) GetLogMap() map[int64]*types.LogInfo {
return logMap
}
This diff is collapsed.
...@@ -7,12 +7,13 @@ package table ...@@ -7,12 +7,13 @@ package table
import ( import (
"testing" "testing"
"github.com/33cn/chain33/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCount(t *testing.T) { func TestCount(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
count := NewCount("prefix", "name#hello", kvdb) count := NewCount("prefix", "name#hello", kvdb)
count.Inc() count.Inc()
count.Dec() count.Dec()
...@@ -22,7 +23,7 @@ func TestCount(t *testing.T) { ...@@ -22,7 +23,7 @@ func TestCount(t *testing.T) {
assert.Equal(t, i, int64(1)) assert.Equal(t, i, int64(1))
kvs, err := count.Save() kvs, err := count.Save()
assert.Nil(t, err) assert.Nil(t, err)
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
count = NewCount("prefix", "name#hello", kvdb) count = NewCount("prefix", "name#hello", kvdb)
i, err = count.Get() i, err = count.Get()
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"testing" "testing"
protodata "github.com/33cn/chain33/common/db/table/proto" protodata "github.com/33cn/chain33/common/db/table/proto"
"github.com/33cn/chain33/util"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
...@@ -12,8 +13,8 @@ import ( ...@@ -12,8 +13,8 @@ import (
) )
func TestJoin(t *testing.T) { func TestJoin(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
table1, err := NewTable(NewGameRow(), kvdb, optgame) table1, err := NewTable(NewGameRow(), kvdb, optgame)
assert.Nil(t, err) assert.Nil(t, err)
table2, err := NewTable(NewGameAddrRow(), kvdb, optgameaddr) table2, err := NewTable(NewGameAddrRow(), kvdb, optgameaddr)
...@@ -32,7 +33,7 @@ func TestJoin(t *testing.T) { ...@@ -32,7 +33,7 @@ func TestJoin(t *testing.T) {
kvs, err := tablejoin.Save() kvs, err := tablejoin.Save()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 7, len(kvs)) assert.Equal(t, 7, len(kvs))
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
//query table //query table
//每个表的查询,用 tablejoin.MustGetTable("gameaddr") //每个表的查询,用 tablejoin.MustGetTable("gameaddr")
//join query 用 tablejoin.Query //join query 用 tablejoin.Query
...@@ -53,7 +54,7 @@ func TestJoin(t *testing.T) { ...@@ -53,7 +54,7 @@ func TestJoin(t *testing.T) {
kvs, err = tablejoin.Save() kvs, err = tablejoin.Save()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 7, len(kvs)) assert.Equal(t, 7, len(kvs))
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
rows, err = tablejoin.ListIndex("addr#status", JoinKey([]byte("addr1"), []byte("2")), nil, 0, 0) rows, err = tablejoin.ListIndex("addr#status", JoinKey([]byte("addr1"), []byte("2")), nil, 0, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(rows)) assert.Equal(t, 1, len(rows))
...@@ -77,7 +78,7 @@ func TestJoin(t *testing.T) { ...@@ -77,7 +78,7 @@ func TestJoin(t *testing.T) {
kvs, err = tablejoin.Save() kvs, err = tablejoin.Save()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, len(kvs)) assert.Equal(t, 5, len(kvs))
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
//改回到全部是1的情况 //改回到全部是1的情况
rightdata = &protodata.Game{GameID: "gameid1", Status: 1} rightdata = &protodata.Game{GameID: "gameid1", Status: 1}
...@@ -87,7 +88,7 @@ func TestJoin(t *testing.T) { ...@@ -87,7 +88,7 @@ func TestJoin(t *testing.T) {
kvs, err = tablejoin.Save() kvs, err = tablejoin.Save()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 10, len(kvs)) assert.Equal(t, 10, len(kvs))
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
rows, err = tablejoin.ListIndex("addr#status", JoinKey([]byte("addr1"), []byte("1")), nil, 0, 0) rows, err = tablejoin.ListIndex("addr#status", JoinKey([]byte("addr1"), []byte("1")), nil, 0, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(rows)) assert.Equal(t, 1, len(rows))
......
...@@ -6,11 +6,8 @@ package table ...@@ -6,11 +6,8 @@ package table
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os"
"testing" "testing"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/db" "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
"github.com/33cn/chain33/util" "github.com/33cn/chain33/util"
...@@ -19,8 +16,8 @@ import ( ...@@ -19,8 +16,8 @@ import (
) )
func TestTransactinList(t *testing.T) { func TestTransactinList(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
opt := &Option{ opt := &Option{
Prefix: "prefix", Prefix: "prefix",
Name: "name", Name: "name",
...@@ -51,7 +48,7 @@ func TestTransactinList(t *testing.T) { ...@@ -51,7 +48,7 @@ func TestTransactinList(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, len(kvs), 12) assert.Equal(t, len(kvs), 12)
//save to database //save to database
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
//测试查询 //测试查询
query := table.GetQuery(kvdb) query := table.GetQuery(kvdb)
...@@ -157,8 +154,8 @@ func TestTransactinList(t *testing.T) { ...@@ -157,8 +154,8 @@ func TestTransactinList(t *testing.T) {
} }
func TestTransactinListAuto(t *testing.T) { func TestTransactinListAuto(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
opt := &Option{ opt := &Option{
Prefix: "prefix", Prefix: "prefix",
Name: "name", Name: "name",
...@@ -189,7 +186,7 @@ func TestTransactinListAuto(t *testing.T) { ...@@ -189,7 +186,7 @@ func TestTransactinListAuto(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, len(kvs), 13) assert.Equal(t, len(kvs), 13)
//save to database //save to database
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
//测试查询 //测试查询
query := table.GetQuery(kvdb) query := table.GetQuery(kvdb)
...@@ -257,28 +254,6 @@ func mergeDup(kvs []*types.KeyValue) (kvset []*types.KeyValue) { ...@@ -257,28 +254,6 @@ func mergeDup(kvs []*types.KeyValue) (kvset []*types.KeyValue) {
return kvset return kvset
} }
func setKV(kvdb db.DB, kvs []*types.KeyValue) {
//printKV(kvs)
batch := kvdb.NewBatch(true)
for i := 0; i < len(kvs); i++ {
if kvs[i].Value == nil {
batch.Delete(kvs[i].Key)
continue
}
batch.Set(kvs[i].Key, kvs[i].Value)
}
err := batch.Write()
if err != nil {
panic(err)
}
}
func printKV(kvs []*types.KeyValue) {
for i := 0; i < len(kvs); i++ {
fmt.Printf("KV %d %s(%s)\n", i, string(kvs[i].Key), common.ToHex(kvs[i].Value))
}
}
func TestRow(t *testing.T) { func TestRow(t *testing.T) {
rowmeta := NewTransactionRow() rowmeta := NewTransactionRow()
row := rowmeta.CreateRow() row := rowmeta.CreateRow()
...@@ -298,8 +273,8 @@ func TestRow(t *testing.T) { ...@@ -298,8 +273,8 @@ func TestRow(t *testing.T) {
} }
func TestDel(t *testing.T) { func TestDel(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
opt := &Option{ opt := &Option{
Prefix: "prefix", Prefix: "prefix",
Name: "name", Name: "name",
...@@ -327,7 +302,7 @@ func TestDel(t *testing.T) { ...@@ -327,7 +302,7 @@ func TestDel(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, len(kvs), 6) assert.Equal(t, len(kvs), 6)
//save to database //save to database
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
//printKV(kvs) //printKV(kvs)
query := table.GetQuery(kvdb) query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0) rows, err := query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0)
...@@ -344,8 +319,8 @@ func printAllKey(db db.DB) { ...@@ -344,8 +319,8 @@ func printAllKey(db db.DB) {
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
opt := &Option{ opt := &Option{
Prefix: "prefix", Prefix: "prefix",
Name: "name", Name: "name",
...@@ -369,7 +344,7 @@ func TestUpdate(t *testing.T) { ...@@ -369,7 +344,7 @@ func TestUpdate(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, len(kvs), 3) assert.Equal(t, len(kvs), 3)
//save to database //save to database
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
query := table.GetQuery(kvdb) query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(tx1.From()), nil, 0, 0) rows, err := query.ListIndex("From", []byte(tx1.From()), nil, 0, 0)
assert.Nil(t, err) assert.Nil(t, err)
...@@ -377,8 +352,8 @@ func TestUpdate(t *testing.T) { ...@@ -377,8 +352,8 @@ func TestUpdate(t *testing.T) {
} }
func TestReplace(t *testing.T) { func TestReplace(t *testing.T) {
dir, leveldb, kvdb := getdb() dir, ldb, kvdb := util.CreateTestDB()
defer dbclose(dir, leveldb) defer util.CloseTestDB(dir, ldb)
opt := &Option{ opt := &Option{
Prefix: "prefix", Prefix: "prefix",
Name: "name", Name: "name",
...@@ -405,7 +380,7 @@ func TestReplace(t *testing.T) { ...@@ -405,7 +380,7 @@ func TestReplace(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(kvs)) assert.Equal(t, 3, len(kvs))
//save to database //save to database
setKV(leveldb, kvs) util.SaveKVList(ldb, kvs)
query := table.GetQuery(kvdb) query := table.GetQuery(kvdb)
_, err = query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0) _, err = query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0)
assert.Equal(t, err, types.ErrNotFound) assert.Equal(t, err, types.ErrNotFound)
...@@ -445,20 +420,3 @@ func (tx *TransactionRow) Get(key string) ([]byte, error) { ...@@ -445,20 +420,3 @@ func (tx *TransactionRow) Get(key string) ([]byte, error) {
} }
return nil, types.ErrNotFound return nil, types.ErrNotFound
} }
func getdb() (string, db.DB, db.KVDB) {
dir, err := ioutil.TempDir("", "goleveldb")
if err != nil {
panic(err)
}
leveldb, err := db.NewGoLevelDB("goleveldb", dir, 128)
if err != nil {
panic(err)
}
return dir, leveldb, db.NewKVDB(leveldb)
}
func dbclose(dir string, dbm db.DB) {
os.RemoveAll(dir)
dbm.Close()
}
package dapp
import (
"testing"
"github.com/33cn/chain33/types"
"github.com/stretchr/testify/assert"
)
type demoApp struct {
*DriverBase
}
func newdemoApp() Driver {
demo := &demoApp{DriverBase: &DriverBase{}}
demo.SetChild(demo)
return demo
}
func (demo *demoApp) GetDriverName() string {
return "demo"
}
type noneApp struct {
*DriverBase
}
func newnoneApp() Driver {
none := &noneApp{DriverBase: &DriverBase{}}
none.SetChild(none)
return none
}
func (none *noneApp) GetDriverName() string {
return "none"
}
func TestReigister(t *testing.T) {
Register("none", newnoneApp, 0)
Register("demo", newdemoApp, 1)
_, err := LoadDriver("demo", 0)
assert.Equal(t, err, types.ErrUnknowDriver)
_, err = LoadDriver("demo", 1)
assert.Equal(t, err, nil)
tx := &types.Transaction{Execer: []byte("demo")}
driver := LoadDriverAllow(tx, 0, 0)
assert.Equal(t, "none", driver.GetDriverName())
driver = LoadDriverAllow(tx, 0, 1)
assert.Equal(t, "demo", driver.GetDriverName())
types.SetTitleOnlyForTest("user.p.hello.")
tx = &types.Transaction{Execer: []byte("demo")}
driver = LoadDriverAllow(tx, 0, 0)
assert.Equal(t, "none", driver.GetDriverName())
driver = LoadDriverAllow(tx, 0, 1)
assert.Equal(t, "demo", driver.GetDriverName())
tx.Execer = []byte("user.p.hello.demo")
driver = LoadDriverAllow(tx, 0, 1)
assert.Equal(t, "demo", driver.GetDriverName())
tx.Execer = []byte("user.p.hello2.demo")
driver = LoadDriverAllow(tx, 0, 1)
assert.Equal(t, "none", driver.GetDriverName())
}
func TestExecAddress(t *testing.T) {
assert.Equal(t, "16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp", ExecAddress("ticket"))
}
...@@ -7,6 +7,7 @@ package dapp ...@@ -7,6 +7,7 @@ package dapp
import ( import (
"fmt" "fmt"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
) )
...@@ -15,3 +16,183 @@ func HeightIndexStr(height, index int64) string { ...@@ -15,3 +16,183 @@ func HeightIndexStr(height, index int64) string {
v := height*types.MaxTxsPerBlock + index v := height*types.MaxTxsPerBlock + index
return fmt.Sprintf("%018d", v) return fmt.Sprintf("%018d", v)
} }
//KVCreator 创建KV的辅助工具
type KVCreator struct {
kvs []*types.KeyValue
kvdb db.KV
autorollback bool
prefix []byte
rollbackkey []byte
rollbackkvs []*types.KeyValue
}
//NewKVCreator 创建创建者
//注意: 自动回滚可能会严重影响系统性能
func NewKVCreator(kv db.KV, prefix []byte, rollbackkey []byte) *KVCreator {
return &KVCreator{
kvdb: kv,
prefix: prefix,
rollbackkey: rollbackkey,
autorollback: rollbackkey != nil,
}
}
func (c *KVCreator) addPrefix(key []byte) []byte {
newkey := append([]byte{}, c.prefix...)
return append(newkey, key...)
}
func (c *KVCreator) add(key, value []byte, set bool) *KVCreator {
if c.prefix != nil {
key = c.addPrefix(key)
}
return c.addnoprefix(key, value, set)
}
func (c *KVCreator) addnoprefix(key, value []byte, set bool) *KVCreator {
if c.autorollback {
prev, err := c.kvdb.Get(key)
//数据库发生错误,直接panic (再执行器中会recover)
if err != nil && err != types.ErrNotFound {
panic(err)
}
if value == nil { //del
//不需要做任何处理, 也不用加入 kvs
if err == types.ErrNotFound {
return c
}
rb := &types.KeyValue{Key: key, Value: prev}
c.rollbackkvs = append(c.rollbackkvs, rb)
} else {
if err == types.ErrNotFound { //add
rb := &types.KeyValue{Key: key}
c.rollbackkvs = append(c.rollbackkvs, rb)
} else { //update
rb := &types.KeyValue{Key: key, Value: prev}
c.rollbackkvs = append(c.rollbackkvs, rb)
}
}
}
c.kvs = append(c.kvs, &types.KeyValue{Key: key, Value: value})
if set {
c.kvdb.Set(key, value)
}
return c
}
//Get 从KV中获取 value
func (c *KVCreator) Get(key []byte) ([]byte, error) {
if c.prefix != nil {
newkey := c.addPrefix(key)
return c.kvdb.Get(newkey)
}
return c.kvdb.Get(key)
}
//GetNoPrefix 从KV中获取 value, 不自动添加前缀
func (c *KVCreator) GetNoPrefix(key []byte) ([]byte, error) {
return c.kvdb.Get(key)
}
//Add add and set to kvdb
func (c *KVCreator) Add(key, value []byte) *KVCreator {
return c.add(key, value, true)
}
//AddNoPrefix 不自动添加prefix
func (c *KVCreator) AddNoPrefix(key, value []byte) *KVCreator {
return c.addnoprefix(key, value, true)
}
//AddList only add KVList
func (c *KVCreator) AddList(list []*types.KeyValue) *KVCreator {
for _, kv := range list {
c.add(kv.Key, kv.Value, true)
}
return c
}
//AddKVOnly only add KV(can't auto rollback)
func (c *KVCreator) AddKVOnly(key, value []byte) *KVCreator {
if c.autorollback {
panic("autorollback open, AddKVOnly not allow")
}
return c.add(key, value, false)
}
//AddKVListOnly only add KVList (can't auto rollback)
func (c *KVCreator) AddKVListOnly(list []*types.KeyValue) *KVCreator {
if c.autorollback {
panic("autorollback open, AddKVListOnly not allow")
}
for _, kv := range list {
c.add(kv.Key, kv.Value, false)
}
return c
}
//KVList 读取所有的kv列表
func (c *KVCreator) KVList() []*types.KeyValue {
return c.kvs
}
//AddRollbackKV 添加回滚数据到 KV
func (c *KVCreator) AddRollbackKV() {
v := types.Encode(c.rollbackLog())
c.kvs = append(c.kvs, &types.KeyValue{Key: c.rollbackkey, Value: v})
}
//DelRollbackKV 删除rollback kv
func (c *KVCreator) DelRollbackKV() {
c.kvs = append(c.kvs, &types.KeyValue{Key: c.rollbackkey})
}
//GetRollbackKVList 获取 rollback 到 Key and Vaue
func (c *KVCreator) GetRollbackKVList() ([]*types.KeyValue, error) {
data, err := c.kvdb.Get(c.rollbackkey)
if err != nil {
return nil, err
}
var rollbacklog types.ReceiptLog
err = types.Decode(data, &rollbacklog)
if err != nil {
return nil, err
}
kvs, err := c.parseRollback(&rollbacklog)
if err != nil {
return nil, err
}
//reverse kvs
for left, right := 0, len(kvs)-1; left < right; left, right = left+1, right-1 {
kvs[left], kvs[right] = kvs[right], kvs[left]
}
return kvs, nil
}
//rollbackLog rollback log
func (c *KVCreator) rollbackLog() *types.ReceiptLog {
data := types.Encode(&types.LocalDBSet{KV: c.rollbackkvs})
return &types.ReceiptLog{Ty: types.TyLogRollback, Log: data}
}
//ParseRollback 解析rollback的数据
func (c *KVCreator) parseRollback(log *types.ReceiptLog) ([]*types.KeyValue, error) {
var data types.LocalDBSet
if log.Ty != types.TyLogRollback {
return nil, types.ErrInvalidParam
}
err := types.Decode(log.Log, &data)
if err != nil {
return nil, err
}
return data.KV, nil
}
//AddToLogs add not empty log to logs
func (c *KVCreator) AddToLogs(logs []*types.ReceiptLog) []*types.ReceiptLog {
if len(c.rollbackkvs) == 0 {
return logs
}
return append(logs, c.rollbackLog())
}
package dapp
import (
"testing"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/stretchr/testify/assert"
)
func TestKVCreator(t *testing.T) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
creator := NewKVCreator(kvdb, []byte("prefix-"), nil)
creator.AddKVOnly([]byte("a"), []byte("b"))
_, err := kvdb.Get([]byte("prefix-a"))
assert.Equal(t, err, types.ErrNotFound)
creator.Add([]byte("a"), []byte("b"))
value, err := kvdb.Get([]byte("prefix-a"))
assert.Equal(t, err, nil)
assert.Equal(t, value, []byte("b"))
creator = NewKVCreator(kvdb, []byte("prefix-"), []byte("rollback"))
creator.Add([]byte("a"), []byte("b"))
creator.Add([]byte("a1"), []byte("b1"))
creator.AddNoPrefix([]byte("np"), []byte("np-value"))
creator.AddList([]*types.KeyValue{
{Key: []byte("l1"), Value: []byte("vl1")},
{Key: []byte("l2"), Value: []byte("vl2")},
})
creator.Add([]byte("c1"), nil)
creator.Add([]byte("l2"), nil)
creator.AddRollbackKV()
assert.Equal(t, 7, len(creator.KVList()))
util.SaveKVList(ldb, creator.KVList())
kvs, err := creator.GetRollbackKVList()
assert.Nil(t, err)
assert.Equal(t, 6, len(kvs))
assert.Equal(t, []byte("b"), kvs[5].Value)
assert.Equal(t, []byte(nil), kvs[4].Value)
assert.Equal(t, []byte(nil), kvs[3].Value)
assert.Equal(t, []byte(nil), kvs[2].Value)
assert.Equal(t, []byte(nil), kvs[1].Value)
assert.Equal(t, []byte("vl2"), kvs[0].Value)
//current: a = b
//set data:
//a -> b (a -> b)
//a1 -> b1 (a1 -> nil)
//l1 -> vl1 (l1 -> nil)
//l2 -> vl2 (l2->nil)
//c1 -> nil (ignore)
//l2 -> nil (l2 -> vl2)
//rollback 的过程实际上是 set 的逆过程,就像时间倒流一样
//save rollback kvs
_, err = creator.Get([]byte("np"))
assert.Equal(t, types.ErrNotFound, err)
v, _ := creator.GetNoPrefix([]byte("np"))
assert.Equal(t, []byte("np-value"), v)
util.SaveKVList(ldb, kvs)
v, _ = kvdb.Get([]byte("prefix-a"))
assert.Equal(t, []byte("b"), v)
v, _ = creator.Get([]byte("a"))
assert.Equal(t, []byte("b"), v)
_, err = creator.Get([]byte("a1"))
assert.Equal(t, types.ErrNotFound, err)
_, err = creator.Get([]byte("l1"))
assert.Equal(t, types.ErrNotFound, err)
_, err = creator.Get([]byte("l2"))
assert.Equal(t, types.ErrNotFound, err)
_, err = creator.Get([]byte("c1"))
assert.Equal(t, types.ErrNotFound, err)
}
func TestHeightIndexStr(t *testing.T) {
assert.Equal(t, "000000000000100001", HeightIndexStr(1, 1))
}
...@@ -10,12 +10,37 @@ import ( ...@@ -10,12 +10,37 @@ import (
"github.com/33cn/chain33/common" "github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/crypto" "github.com/33cn/chain33/common/crypto"
"github.com/golang/protobuf/proto" proto "github.com/golang/protobuf/proto"
) )
// Hash 获取block的hash值 // Hash 获取block的hash值
func (block *Block) Hash() []byte { func (block *Block) Hash() []byte {
data, err := proto.Marshal(block.GetHeader()) if IsFork(block.Height, "ForkBlockHash") {
return block.HashNew()
}
return block.HashOld()
}
//HashByForkHeight hash 通过自己设置的fork 高度计算 hash
func (block *Block) HashByForkHeight(forkheight int64) []byte {
if block.Height >= forkheight {
return block.HashNew()
}
return block.HashOld()
}
//HashNew 新版本的Hash
func (block *Block) HashNew() []byte {
data, err := proto.Marshal(block.getHeaderHashNew())
if err != nil {
panic(err)
}
return common.Sha256(data)
}
//HashOld 老版本的hash
func (block *Block) HashOld() []byte {
data, err := proto.Marshal(block.getHeaderHashOld())
if err != nil { if err != nil {
panic(err) panic(err)
} }
...@@ -35,11 +60,32 @@ func (block *Block) GetHeader() *Header { ...@@ -35,11 +60,32 @@ func (block *Block) GetHeader() *Header {
head.TxHash = block.TxHash head.TxHash = block.TxHash
head.BlockTime = block.BlockTime head.BlockTime = block.BlockTime
head.Height = block.Height head.Height = block.Height
if IsFork(head.Height, "ForkBlockHash") { head.Difficulty = block.Difficulty
head.Difficulty = block.Difficulty head.StateHash = block.StateHash
head.StateHash = block.StateHash head.TxCount = int64(len(block.Txs))
head.TxCount = int64(len(block.Txs)) return head
} }
func (block *Block) getHeaderHashOld() *Header {
head := &Header{}
head.Version = block.Version
head.ParentHash = block.ParentHash
head.TxHash = block.TxHash
head.BlockTime = block.BlockTime
head.Height = block.Height
return head
}
func (block *Block) getHeaderHashNew() *Header {
head := &Header{}
head.Version = block.Version
head.ParentHash = block.ParentHash
head.TxHash = block.TxHash
head.BlockTime = block.BlockTime
head.Height = block.Height
head.Difficulty = block.Difficulty
head.StateHash = block.StateHash
head.TxCount = int64(len(block.Txs))
return head return head
} }
......
package types
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBlock(t *testing.T) {
b := &Block{}
assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hex.EncodeToString(b.Hash()))
assert.Equal(t, b.HashOld(), b.HashNew())
assert.Equal(t, b.HashOld(), b.Hash())
b.Height = 10
b.Difficulty = 1
assert.NotEqual(t, b.HashOld(), b.HashNew())
assert.NotEqual(t, b.HashOld(), b.HashNew())
assert.Equal(t, b.HashNew(), b.HashByForkHeight(10))
assert.Equal(t, b.HashOld(), b.HashByForkHeight(11))
assert.Equal(t, true, b.CheckSign())
b.Txs = append(b.Txs, &Transaction{})
assert.Equal(t, false, b.CheckSign())
b.Txs = append(b.Txs, &Transaction{})
b.Txs = append(b.Txs, &Transaction{})
b.Txs = append(b.Txs, &Transaction{})
b.Txs = append(b.Txs, &Transaction{})
assert.Equal(t, false, b.CheckSign())
}
...@@ -100,6 +100,7 @@ const ( ...@@ -100,6 +100,7 @@ const (
TyLogExecActive = 10 TyLogExecActive = 10
TyLogGenesisTransfer = 11 TyLogGenesisTransfer = 11
TyLogGenesisDeposit = 12 TyLogGenesisDeposit = 12
TyLogRollback = 13
) )
//SystemLog 系统log日志 //SystemLog 系统log日志
...@@ -116,6 +117,7 @@ var SystemLog = map[int64]*LogInfo{ ...@@ -116,6 +117,7 @@ var SystemLog = map[int64]*LogInfo{
TyLogExecActive: {reflect.TypeOf(ReceiptExecAccountTransfer{}), "LogExecActive"}, TyLogExecActive: {reflect.TypeOf(ReceiptExecAccountTransfer{}), "LogExecActive"},
TyLogGenesisTransfer: {reflect.TypeOf(ReceiptAccountTransfer{}), "LogGenesisTransfer"}, TyLogGenesisTransfer: {reflect.TypeOf(ReceiptAccountTransfer{}), "LogGenesisTransfer"},
TyLogGenesisDeposit: {reflect.TypeOf(ReceiptAccountTransfer{}), "LogGenesisDeposit"}, TyLogGenesisDeposit: {reflect.TypeOf(ReceiptAccountTransfer{}), "LogGenesisDeposit"},
TyLogRollback: {reflect.TypeOf(LocalDBSet{}), "LogRollback"},
} }
//exec type //exec type
......
...@@ -262,7 +262,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) { ...@@ -262,7 +262,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) {
value := Encode(acc) value := Encode(acc)
fmt.Println("TestIterateCallBack_PrefixWithoutExecAddr--test case 1---")
bRet := reply.IterateCallBack([]byte(key), value) bRet := reply.IterateCallBack([]byte(key), value)
assert.Equal(t, false, bRet) assert.Equal(t, false, bRet)
assert.Equal(t, 1, len(reply.Keys)) assert.Equal(t, 1, len(reply.Keys))
...@@ -270,7 +269,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) { ...@@ -270,7 +269,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) {
assert.Equal(t, int64(1), reply.Num) assert.Equal(t, int64(1), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithoutExecAddr--test case 2---")
bRet = reply.IterateCallBack([]byte(key), value) bRet = reply.IterateCallBack([]byte(key), value)
assert.Equal(t, false, bRet) assert.Equal(t, false, bRet)
assert.Equal(t, 2, len(reply.Keys)) assert.Equal(t, 2, len(reply.Keys))
...@@ -278,7 +276,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) { ...@@ -278,7 +276,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) {
assert.Equal(t, int64(2), reply.Num) assert.Equal(t, int64(2), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithoutExecAddr--test case 3---")
key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP" key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP"
bRet = reply.IterateCallBack([]byte(key2), value) bRet = reply.IterateCallBack([]byte(key2), value)
assert.Equal(t, false, bRet) assert.Equal(t, false, bRet)
...@@ -287,7 +284,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) { ...@@ -287,7 +284,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) {
assert.Equal(t, int64(2), reply.Num) assert.Equal(t, int64(2), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithoutExecAddr--test case 4---")
key3 := "mavl-coins-bty-exec-26htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:1JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP" key3 := "mavl-coins-bty-exec-26htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:1JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP"
bRet = reply.IterateCallBack([]byte(key3), value) bRet = reply.IterateCallBack([]byte(key3), value)
assert.Equal(t, false, bRet) assert.Equal(t, false, bRet)
...@@ -296,7 +292,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) { ...@@ -296,7 +292,6 @@ func TestIterateCallBack_PrefixWithoutExecAddr(t *testing.T) {
assert.Equal(t, int64(3), reply.Num) assert.Equal(t, int64(3), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithoutExecAddr--test case 5---")
reply.Count = int64(4) reply.Count = int64(4)
bRet = reply.IterateCallBack([]byte(key3), value) bRet = reply.IterateCallBack([]byte(key3), value)
...@@ -331,7 +326,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) { ...@@ -331,7 +326,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) {
value := Encode(acc) value := Encode(acc)
fmt.Println("TestIterateCallBack_PrefixWithExecAddr--test case 1---")
key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP" key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP"
bRet := reply.IterateCallBack([]byte(key2), value) bRet := reply.IterateCallBack([]byte(key2), value)
assert.Equal(t, false, bRet) assert.Equal(t, false, bRet)
...@@ -340,7 +334,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) { ...@@ -340,7 +334,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) {
assert.Equal(t, int64(0), reply.Num) assert.Equal(t, int64(0), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithExecAddr--test case 2---")
bRet = reply.IterateCallBack([]byte(key), value) bRet = reply.IterateCallBack([]byte(key), value)
assert.Equal(t, true, bRet) assert.Equal(t, true, bRet)
assert.Equal(t, 1, len(reply.Keys)) assert.Equal(t, 1, len(reply.Keys))
...@@ -348,7 +341,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) { ...@@ -348,7 +341,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) {
assert.Equal(t, int64(1), reply.Num) assert.Equal(t, int64(1), reply.Num)
assert.Equal(t, len(key), len(reply.NextKey)) assert.Equal(t, len(key), len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithExecAddr--test case 3---")
//key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP" //key2 := "mavl-coins-bty-exec-16htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:2JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP"
reply.NextKey = nil reply.NextKey = nil
reply.Count = int64(2) reply.Count = int64(2)
...@@ -359,7 +351,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) { ...@@ -359,7 +351,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) {
assert.Equal(t, int64(1), reply.Num) assert.Equal(t, int64(1), reply.Num)
assert.Equal(t, 0, len(reply.NextKey)) assert.Equal(t, 0, len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithExecAddr--test case 4---")
reply.NextKey = nil reply.NextKey = nil
key3 := "mavl-coins-bty-exec-26htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:1JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP" key3 := "mavl-coins-bty-exec-26htvcBNSEA7fZhAdLJphDwQRQJaHpyHTp:1JmFaA6unrCFYEWPGRi7uuXY1KthTJxJEP"
bRet = reply.IterateCallBack([]byte(key3), value) bRet = reply.IterateCallBack([]byte(key3), value)
...@@ -369,7 +360,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) { ...@@ -369,7 +360,6 @@ func TestIterateCallBack_PrefixWithExecAddr(t *testing.T) {
assert.Equal(t, int64(2), reply.Num) assert.Equal(t, int64(2), reply.Num)
assert.Equal(t, len(key3), len(reply.NextKey)) assert.Equal(t, len(key3), len(reply.NextKey))
fmt.Println("TestIterateCallBack_PrefixWithExecAddr--test case 5---")
bRet = reply.IterateCallBack([]byte(key), value) bRet = reply.IterateCallBack([]byte(key), value)
assert.Equal(t, true, bRet) assert.Equal(t, true, bRet)
assert.Equal(t, 3, len(reply.Keys)) assert.Equal(t, 3, len(reply.Keys))
......
...@@ -10,14 +10,17 @@ import ( ...@@ -10,14 +10,17 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"unicode" "unicode"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/address" "github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/common/crypto" "github.com/33cn/chain33/common/crypto"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/log/log15" "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/common/merkle" "github.com/33cn/chain33/common/merkle"
"github.com/33cn/chain33/queue" "github.com/33cn/chain33/queue"
...@@ -407,3 +410,46 @@ func ResetDatadir(cfg *types.Config, datadir string) string { ...@@ -407,3 +410,46 @@ func ResetDatadir(cfg *types.Config, datadir string) string {
cfg.Store.DbPath = filepath.Join(datadir, cfg.Store.DbPath) cfg.Store.DbPath = filepath.Join(datadir, cfg.Store.DbPath)
return datadir return datadir
} }
//CreateTestDB 创建一个测试数据库
func CreateTestDB() (string, db.DB, db.KVDB) {
dir, err := ioutil.TempDir("", "goleveldb")
if err != nil {
panic(err)
}
leveldb, err := db.NewGoLevelDB("goleveldb", dir, 128)
if err != nil {
panic(err)
}
return dir, leveldb, db.NewKVDB(leveldb)
}
//CloseTestDB 创建一个测试数据库
func CloseTestDB(dir string, dbm db.DB) {
os.RemoveAll(dir)
dbm.Close()
}
//SaveKVList 保存kvs to database
func SaveKVList(kvdb db.DB, kvs []*types.KeyValue) {
//printKV(kvs)
batch := kvdb.NewBatch(true)
for i := 0; i < len(kvs); i++ {
if kvs[i].Value == nil {
batch.Delete(kvs[i].Key)
continue
}
batch.Set(kvs[i].Key, kvs[i].Value)
}
err := batch.Write()
if err != nil {
panic(err)
}
}
//PrintKV 打印KVList
func PrintKV(kvs []*types.KeyValue) {
for i := 0; i < len(kvs); i++ {
fmt.Printf("KV %d %s(%s)\n", i, string(kvs[i].Key), common.ToHex(kvs[i].Value))
}
}
...@@ -177,3 +177,13 @@ func TestDelDupTx(t *testing.T) { ...@@ -177,3 +177,13 @@ func TestDelDupTx(t *testing.T) {
txcache = DelDupTx(txcache) txcache = DelDupTx(txcache)
assert.Equal(t, txcache, txcacheresult) assert.Equal(t, txcache, txcacheresult)
} }
func TestDB(t *testing.T) {
dir, db, kvdb := CreateTestDB()
defer CloseTestDB(dir, db)
err := kvdb.Set([]byte("a"), []byte("b"))
assert.Nil(t, err)
value, err := kvdb.Get([]byte("a"))
assert.Nil(t, err)
assert.Equal(t, value, []byte("b"))
}
/.test
/otto/otto
/otto/otto-*
/test/test-*.js
/test/tester
* Designate the filename of "anonymous" source code by the hash (md5/sha1, etc.)
Copyright (c) 2012 Robert Krimen
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 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.
.PHONY: test test-race test-release release release-check test-262
.PHONY: parser
.PHONY: otto assets underscore
TESTS := \
~
TEST := -v --run
TEST := -v
TEST := -v --run Test\($(subst $(eval) ,\|,$(TESTS))\)
TEST := .
test: parser inline.go
go test -i
go test $(TEST)
@echo PASS
parser:
$(MAKE) -C parser
inline.go: inline.pl
./$< > $@
#################
# release, test #
#################
release: test-race test-release
for package in . parser token ast file underscore registry; do (cd $$package && godocdown --signature > README.markdown); done
@echo \*\*\* make release-check
@echo PASS
release-check: .test
$(MAKE) -C test build test
$(MAKE) -C .test/test262 build test
@echo PASS
test-262: .test
$(MAKE) -C .test/test262 build test
@echo PASS
test-release:
go test -i
go test
test-race:
go test -race -i
go test -race
#################################
# otto, assets, underscore, ... #
#################################
otto:
$(MAKE) -C otto
assets:
mkdir -p .assets
for file in underscore/test/*.js; do tr "\`" "_" < $$file > .assets/`basename $$file`; done
underscore:
$(MAKE) -C $@
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package ast
import (
"fmt"
"github.com/robertkrimen/otto/file"
)
// CommentPosition determines where the comment is in a given context
type CommentPosition int
const (
_ CommentPosition = iota
LEADING // Before the pertinent expression
TRAILING // After the pertinent expression
KEY // Before a key in an object
COLON // After a colon in a field declaration
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal
IF // After an if keyword
WHILE // After a while keyword
DO // After do keyword
FOR // After a for keyword
WITH // After a with keyword
TBD
)
// Comment contains the data of the comment
type Comment struct {
Begin file.Idx
Text string
Position CommentPosition
}
// NewComment creates a new comment
func NewComment(text string, idx file.Idx) *Comment {
comment := &Comment{
Begin: idx,
Text: text,
Position: TBD,
}
return comment
}
// String returns a stringified version of the position
func (cp CommentPosition) String() string {
switch cp {
case LEADING:
return "Leading"
case TRAILING:
return "Trailing"
case KEY:
return "Key"
case COLON:
return "Colon"
case FINAL:
return "Final"
case IF:
return "If"
case WHILE:
return "While"
case DO:
return "Do"
case FOR:
return "For"
case WITH:
return "With"
default:
return "???"
}
}
// String returns a stringified version of the comment
func (c Comment) String() string {
return fmt.Sprintf("Comment: %v", c.Text)
}
// Comments defines the current view of comments from the parser
type Comments struct {
// CommentMap is a reference to the parser comment map
CommentMap CommentMap
// Comments lists the comments scanned, not linked to a node yet
Comments []*Comment
// future lists the comments after a line break during a sequence of comments
future []*Comment
// Current is node for which comments are linked to
Current Expression
// wasLineBreak determines if a line break occured while scanning for comments
wasLineBreak bool
// primary determines whether or not processing a primary expression
primary bool
// afterBlock determines whether or not being after a block statement
afterBlock bool
}
func NewComments() *Comments {
comments := &Comments{
CommentMap: CommentMap{},
}
return comments
}
func (c *Comments) String() string {
return fmt.Sprintf("NODE: %v, Comments: %v, Future: %v(LINEBREAK:%v)", c.Current, len(c.Comments), len(c.future), c.wasLineBreak)
}
// FetchAll returns all the currently scanned comments,
// including those from the next line
func (c *Comments) FetchAll() []*Comment {
defer func() {
c.Comments = nil
c.future = nil
}()
return append(c.Comments, c.future...)
}
// Fetch returns all the currently scanned comments
func (c *Comments) Fetch() []*Comment {
defer func() {
c.Comments = nil
}()
return c.Comments
}
// ResetLineBreak marks the beginning of a new statement
func (c *Comments) ResetLineBreak() {
c.wasLineBreak = false
}
// MarkPrimary will mark the context as processing a primary expression
func (c *Comments) MarkPrimary() {
c.primary = true
c.wasLineBreak = false
}
// AfterBlock will mark the context as being after a block.
func (c *Comments) AfterBlock() {
c.afterBlock = true
}
// AddComment adds a comment to the view.
// Depending on the context, comments are added normally or as post line break.
func (c *Comments) AddComment(comment *Comment) {
if c.primary {
if !c.wasLineBreak {
c.Comments = append(c.Comments, comment)
} else {
c.future = append(c.future, comment)
}
} else {
if !c.wasLineBreak || (c.Current == nil && !c.afterBlock) {
c.Comments = append(c.Comments, comment)
} else {
c.future = append(c.future, comment)
}
}
}
// MarkComments will mark the found comments as the given position.
func (c *Comments) MarkComments(position CommentPosition) {
for _, comment := range c.Comments {
if comment.Position == TBD {
comment.Position = position
}
}
for _, c := range c.future {
if c.Position == TBD {
c.Position = position
}
}
}
// Unset the current node and apply the comments to the current expression.
// Resets context variables.
func (c *Comments) Unset() {
if c.Current != nil {
c.applyComments(c.Current, c.Current, TRAILING)
c.Current = nil
}
c.wasLineBreak = false
c.primary = false
c.afterBlock = false
}
// SetExpression sets the current expression.
// It is applied the found comments, unless the previous expression has not been unset.
// It is skipped if the node is already set or if it is a part of the previous node.
func (c *Comments) SetExpression(node Expression) {
// Skipping same node
if c.Current == node {
return
}
if c.Current != nil && c.Current.Idx1() == node.Idx1() {
c.Current = node
return
}
previous := c.Current
c.Current = node
// Apply the found comments and futures to the node and the previous.
c.applyComments(node, previous, TRAILING)
}
// PostProcessNode applies all found comments to the given node
func (c *Comments) PostProcessNode(node Node) {
c.applyComments(node, nil, TRAILING)
}
// applyComments applies both the comments and the future comments to the given node and the previous one,
// based on the context.
func (c *Comments) applyComments(node, previous Node, position CommentPosition) {
if previous != nil {
c.CommentMap.AddComments(previous, c.Comments, position)
c.Comments = nil
} else {
c.CommentMap.AddComments(node, c.Comments, position)
c.Comments = nil
}
// Only apply the future comments to the node if the previous is set.
// This is for detecting end of line comments and which node comments on the following lines belongs to
if previous != nil {
c.CommentMap.AddComments(node, c.future, position)
c.future = nil
}
}
// AtLineBreak will mark a line break
func (c *Comments) AtLineBreak() {
c.wasLineBreak = true
}
// CommentMap is the data structure where all found comments are stored
type CommentMap map[Node][]*Comment
// AddComment adds a single comment to the map
func (cm CommentMap) AddComment(node Node, comment *Comment) {
list := cm[node]
list = append(list, comment)
cm[node] = list
}
// AddComments adds a slice of comments, given a node and an updated position
func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
for _, comment := range comments {
if comment.Position == TBD {
comment.Position = position
}
cm.AddComment(node, comment)
}
}
// Size returns the size of the map
func (cm CommentMap) Size() int {
size := 0
for _, comments := range cm {
size += len(comments)
}
return size
}
// MoveComments moves comments with a given position from a node to another
func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
for i, c := range cm[from] {
if c.Position == position {
cm.AddComment(to, c)
// Remove the comment from the "from" slice
cm[from][i] = cm[from][len(cm[from])-1]
cm[from][len(cm[from])-1] = nil
cm[from] = cm[from][:len(cm[from])-1]
}
}
}
package ast
import (
"github.com/robertkrimen/otto/file"
"testing"
)
func TestCommentMap(t *testing.T) {
statement := &EmptyStatement{file.Idx(1)}
comment := &Comment{1, "test", LEADING}
cm := CommentMap{}
cm.AddComment(statement, comment)
if cm.Size() != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if len(cm[statement]) != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if cm[statement][0].Text != "test" {
t.Errorf("the text is %v, not \"test\"", cm[statement][0].Text)
}
}
func TestCommentMap_move(t *testing.T) {
statement1 := &EmptyStatement{file.Idx(1)}
statement2 := &EmptyStatement{file.Idx(2)}
comment := &Comment{1, "test", LEADING}
cm := CommentMap{}
cm.AddComment(statement1, comment)
if cm.Size() != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if len(cm[statement1]) != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if len(cm[statement2]) != 0 {
t.Errorf("the number of comments is %v, not 0", cm.Size())
}
cm.MoveComments(statement1, statement2, LEADING)
if cm.Size() != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if len(cm[statement2]) != 1 {
t.Errorf("the number of comments is %v, not 1", cm.Size())
}
if len(cm[statement1]) != 0 {
t.Errorf("the number of comments is %v, not 0", cm.Size())
}
}
This diff is collapsed.
package ast
import "fmt"
// Visitor Enter method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children
// of node with the visitor v, followed by a call of the Exit method.
type Visitor interface {
Enter(n Node) (v Visitor)
Exit(n Node)
}
// Walk traverses an AST in depth-first order: It starts by calling
// v.Enter(node); node must not be nil. If the visitor v returned by
// v.Enter(node) is not nil, Walk is invoked recursively with visitor
// v for each of the non-nil children of node, followed by a call
// of v.Exit(node).
func Walk(v Visitor, n Node) {
if n == nil {
return
}
if v = v.Enter(n); v == nil {
return
}
defer v.Exit(n)
switch n := n.(type) {
case *ArrayLiteral:
if n != nil {
for _, ex := range n.Value {
Walk(v, ex)
}
}
case *AssignExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Right)
}
case *BadExpression:
case *BinaryExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Right)
}
case *BlockStatement:
if n != nil {
for _, s := range n.List {
Walk(v, s)
}
}
case *BooleanLiteral:
case *BracketExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Member)
}
case *BranchStatement:
if n != nil {
Walk(v, n.Label)
}
case *CallExpression:
if n != nil {
Walk(v, n.Callee)
for _, a := range n.ArgumentList {
Walk(v, a)
}
}
case *CaseStatement:
if n != nil {
Walk(v, n.Test)
for _, c := range n.Consequent {
Walk(v, c)
}
}
case *CatchStatement:
if n != nil {
Walk(v, n.Parameter)
Walk(v, n.Body)
}
case *ConditionalExpression:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Consequent)
Walk(v, n.Alternate)
}
case *DebuggerStatement:
case *DoWhileStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Body)
}
case *DotExpression:
if n != nil {
Walk(v, n.Left)
}
case *EmptyExpression:
case *EmptyStatement:
case *ExpressionStatement:
if n != nil {
Walk(v, n.Expression)
}
case *ForInStatement:
if n != nil {
Walk(v, n.Into)
Walk(v, n.Source)
Walk(v, n.Body)
}
case *ForStatement:
if n != nil {
Walk(v, n.Initializer)
Walk(v, n.Update)
Walk(v, n.Test)
Walk(v, n.Body)
}
case *FunctionLiteral:
if n != nil {
Walk(v, n.Name)
for _, p := range n.ParameterList.List {
Walk(v, p)
}
Walk(v, n.Body)
}
case *FunctionStatement:
if n != nil {
Walk(v, n.Function)
}
case *Identifier:
case *IfStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Consequent)
Walk(v, n.Alternate)
}
case *LabelledStatement:
if n != nil {
Walk(v, n.Statement)
}
case *NewExpression:
if n != nil {
Walk(v, n.Callee)
for _, a := range n.ArgumentList {
Walk(v, a)
}
}
case *NullLiteral:
case *NumberLiteral:
case *ObjectLiteral:
if n != nil {
for _, p := range n.Value {
Walk(v, p.Value)
}
}
case *Program:
if n != nil {
for _, b := range n.Body {
Walk(v, b)
}
}
case *RegExpLiteral:
case *ReturnStatement:
if n != nil {
Walk(v, n.Argument)
}
case *SequenceExpression:
if n != nil {
for _, e := range n.Sequence {
Walk(v, e)
}
}
case *StringLiteral:
case *SwitchStatement:
if n != nil {
Walk(v, n.Discriminant)
for _, c := range n.Body {
Walk(v, c)
}
}
case *ThisExpression:
case *ThrowStatement:
if n != nil {
Walk(v, n.Argument)
}
case *TryStatement:
if n != nil {
Walk(v, n.Body)
Walk(v, n.Catch)
Walk(v, n.Finally)
}
case *UnaryExpression:
if n != nil {
Walk(v, n.Operand)
}
case *VariableExpression:
if n != nil {
Walk(v, n.Initializer)
}
case *VariableStatement:
if n != nil {
for _, e := range n.List {
Walk(v, e)
}
}
case *WhileStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Body)
}
case *WithStatement:
if n != nil {
Walk(v, n.Object)
Walk(v, n.Body)
}
default:
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
}
package ast_test
import (
"fmt"
"log"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/parser"
)
type walkExample struct {
source string
shift file.Idx
}
func (w *walkExample) Enter(n ast.Node) ast.Visitor {
if id, ok := n.(*ast.Identifier); ok && id != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "new_" + w.source[idx:]
w.source = s
w.shift += 4
}
if v, ok := n.(*ast.VariableExpression); ok && v != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "varnew_" + w.source[idx:]
w.source = s
w.shift += 7
}
return w
}
func (w *walkExample) Exit(n ast.Node) {
// AST node n has had all its children walked. Pop it out of your
// stack, or do whatever processing you need to do, if any.
}
func ExampleVisitor_codeRewrite() {
source := `var b = function() {test(); try {} catch(e) {} var test = "test(); var test = 1"} // test`
program, err := parser.ParseFile(nil, "", source, 0)
if err != nil {
log.Fatal(err)
}
w := &walkExample{source: source}
ast.Walk(w, program)
fmt.Println(w.source)
// Output: var varnew_b = function() {new_test(); try {} catch(new_e) {} var varnew_test = "test(); var test = 1"} // test
}
package ast_test
import (
"log"
"testing"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/parser"
)
type walker struct {
stack []ast.Node
source string
shift file.Idx
}
// push and pop below are to prove the symmetry of Enter/Exit calls
func (w *walker) push(n ast.Node) {
w.stack = append(w.stack, n)
}
func (w *walker) pop(n ast.Node) {
size := len(w.stack)
if size <= 0 {
panic("pop of empty stack")
}
toPop := w.stack[size-1]
if toPop != n {
panic("pop: nodes do not equal")
}
w.stack[size-1] = nil
w.stack = w.stack[:size-1]
}
func (w *walker) Enter(n ast.Node) ast.Visitor {
w.push(n)
if id, ok := n.(*ast.Identifier); ok && id != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "new_" + w.source[idx:]
w.source = s
w.shift += 4
}
if v, ok := n.(*ast.VariableExpression); ok && v != nil {
idx := n.Idx0() + w.shift - 1
s := w.source[:idx] + "varnew_" + w.source[idx:]
w.source = s
w.shift += 7
}
return w
}
func (w *walker) Exit(n ast.Node) {
w.pop(n)
}
func TestVisitorRewrite(t *testing.T) {
source := `var b = function() {test(); try {} catch(e) {} var test = "test(); var test = 1"} // test`
program, err := parser.ParseFile(nil, "", source, 0)
if err != nil {
log.Fatal(err)
}
w := &walker{source: source}
ast.Walk(w, program)
xformed := `var varnew_b = function() {new_test(); try {} catch(new_e) {} var varnew_test = "test(); var test = 1"} // test`
if w.source != xformed {
t.Errorf("source is `%s` not `%s`", w.source, xformed)
}
if len(w.stack) != 0 {
t.Errorf("stack should be empty, but is length: %d", len(w.stack))
}
}
This diff is collapsed.
package otto
import (
"encoding/hex"
"math"
"net/url"
"regexp"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
)
// Global
func builtinGlobal_eval(call FunctionCall) Value {
src := call.Argument(0)
if !src.IsString() {
return src
}
runtime := call.runtime
program := runtime.cmpl_parseOrThrow(src.string(), nil)
if !call.eval {
// Not a direct call to eval, so we enter the global ExecutionContext
runtime.enterGlobalScope()
defer runtime.leaveScope()
}
returnValue := runtime.cmpl_evaluate_nodeProgram(program, true)
if returnValue.isEmpty() {
return Value{}
}
return returnValue
}
func builtinGlobal_isNaN(call FunctionCall) Value {
value := call.Argument(0).float64()
return toValue_bool(math.IsNaN(value))
}
func builtinGlobal_isFinite(call FunctionCall) Value {
value := call.Argument(0).float64()
return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0))
}
// radix 3 => 2 (ASCII 50) +47
// radix 11 => A/a (ASCII 65/97) +54/+86
var parseInt_alphabetTable = func() []string {
table := []string{"", "", "01"}
for radix := 3; radix <= 36; radix += 1 {
alphabet := table[radix-1]
if radix <= 10 {
alphabet += string(radix + 47)
} else {
alphabet += string(radix+54) + string(radix+86)
}
table = append(table, alphabet)
}
return table
}()
func digitValue(chr rune) int {
switch {
case '0' <= chr && chr <= '9':
return int(chr - '0')
case 'a' <= chr && chr <= 'z':
return int(chr - 'a' + 10)
case 'A' <= chr && chr <= 'Z':
return int(chr - 'A' + 10)
}
return 36 // Larger than any legal digit value
}
func builtinGlobal_parseInt(call FunctionCall) Value {
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
if len(input) == 0 {
return NaNValue()
}
radix := int(toInt32(call.Argument(1)))
negative := false
switch input[0] {
case '+':
input = input[1:]
case '-':
negative = true
input = input[1:]
}
strip := true
if radix == 0 {
radix = 10
} else {
if radix < 2 || radix > 36 {
return NaNValue()
} else if radix != 16 {
strip = false
}
}
switch len(input) {
case 0:
return NaNValue()
case 1:
default:
if strip {
if input[0] == '0' && (input[1] == 'x' || input[1] == 'X') {
input = input[2:]
radix = 16
}
}
}
base := radix
index := 0
for ; index < len(input); index++ {
digit := digitValue(rune(input[index])) // If not ASCII, then an error anyway
if digit >= base {
break
}
}
input = input[0:index]
value, err := strconv.ParseInt(input, radix, 64)
if err != nil {
if err.(*strconv.NumError).Err == strconv.ErrRange {
base := float64(base)
// Could just be a very large number (e.g. 0x8000000000000000)
var value float64
for _, chr := range input {
digit := float64(digitValue(chr))
if digit >= base {
goto error
}
value = value*base + digit
}
if negative {
value *= -1
}
return toValue_float64(value)
}
error:
return NaNValue()
}
if negative {
value *= -1
}
return toValue_int64(value)
}
var parseFloat_matchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`)
var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
func builtinGlobal_parseFloat(call FunctionCall) Value {
// Caveat emptor: This implementation does NOT match the specification
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
if parseFloat_matchBadSpecial.MatchString(input) {
return NaNValue()
}
value, err := strconv.ParseFloat(input, 64)
if err != nil {
for end := len(input); end > 0; end-- {
input := input[0:end]
if !parseFloat_matchValid.MatchString(input) {
return NaNValue()
}
value, err = strconv.ParseFloat(input, 64)
if err == nil {
break
}
}
if err != nil {
return NaNValue()
}
}
return toValue_float64(value)
}
// encodeURI/decodeURI
func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
value := call.Argument(0)
var input []uint16
switch vl := value.value.(type) {
case []uint16:
input = vl
default:
input = utf16.Encode([]rune(value.string()))
}
if len(input) == 0 {
return toValue_string("")
}
output := []byte{}
length := len(input)
encode := make([]byte, 4)
for index := 0; index < length; {
value := input[index]
decode := utf16.Decode(input[index : index+1])
if value >= 0xDC00 && value <= 0xDFFF {
panic(call.runtime.panicURIError("URI malformed"))
}
if value >= 0xD800 && value <= 0xDBFF {
index += 1
if index >= length {
panic(call.runtime.panicURIError("URI malformed"))
}
// input = ..., value, value1, ...
value1 := input[index]
if value1 < 0xDC00 || value1 > 0xDFFF {
panic(call.runtime.panicURIError("URI malformed"))
}
decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
}
index += 1
size := utf8.EncodeRune(encode, decode[0])
encode := encode[0:size]
output = append(output, encode...)
}
{
value := escape.ReplaceAllFunc(output, func(target []byte) []byte {
// Probably a better way of doing this
if target[0] == ' ' {
return []byte("%20")
}
return []byte(url.QueryEscape(string(target)))
})
return toValue_string(string(value))
}
}
var encodeURI_Regexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`)
func builtinGlobal_encodeURI(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURI_Regexp)
}
var encodeURIComponent_Regexp = regexp.MustCompile(`([^~!*()'])`)
func builtinGlobal_encodeURIComponent(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURIComponent_Regexp)
}
// 3B/2F/3F/3A/40/26/3D/2B/24/2C/23
var decodeURI_guard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`)
func _decodeURI(input string, reserve bool) (string, bool) {
if reserve {
input = decodeURI_guard.ReplaceAllString(input, "%25$1")
}
input = strings.Replace(input, "+", "%2B", -1) // Ugly hack to make QueryUnescape work with our use case
output, err := url.QueryUnescape(input)
if err != nil || !utf8.ValidString(output) {
return "", true
}
return output, false
}
func builtinGlobal_decodeURI(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), true)
if err {
panic(call.runtime.panicURIError("URI malformed"))
}
return toValue_string(output)
}
func builtinGlobal_decodeURIComponent(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), false)
if err {
panic(call.runtime.panicURIError("URI malformed"))
}
return toValue_string(output)
}
// escape/unescape
func builtin_shouldEscape(chr byte) bool {
if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' {
return false
}
return !strings.ContainsRune("*_+-./", rune(chr))
}
const escapeBase16 = "0123456789ABCDEF"
func builtin_escape(input string) string {
output := make([]byte, 0, len(input))
length := len(input)
for index := 0; index < length; {
if builtin_shouldEscape(input[index]) {
chr, width := utf8.DecodeRuneInString(input[index:])
chr16 := utf16.Encode([]rune{chr})[0]
if 256 > chr16 {
output = append(output, '%',
escapeBase16[chr16>>4],
escapeBase16[chr16&15],
)
} else {
output = append(output, '%', 'u',
escapeBase16[chr16>>12],
escapeBase16[(chr16>>8)&15],
escapeBase16[(chr16>>4)&15],
escapeBase16[chr16&15],
)
}
index += width
} else {
output = append(output, input[index])
index += 1
}
}
return string(output)
}
func builtin_unescape(input string) string {
output := make([]rune, 0, len(input))
length := len(input)
for index := 0; index < length; {
if input[index] == '%' {
if index <= length-6 && input[index+1] == 'u' {
byte16, err := hex.DecodeString(input[index+2 : index+6])
if err == nil {
value := uint16(byte16[0])<<8 + uint16(byte16[1])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 6
continue
}
}
if index <= length-3 {
byte8, err := hex.DecodeString(input[index+1 : index+3])
if err == nil {
value := uint16(byte8[0])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 3
continue
}
}
}
output = append(output, rune(input[index]))
index += 1
}
return string(output)
}
func builtinGlobal_escape(call FunctionCall) Value {
return toValue_string(builtin_escape(call.Argument(0).string()))
}
func builtinGlobal_unescape(call FunctionCall) Value {
return toValue_string(builtin_unescape(call.Argument(0).string()))
}
This diff is collapsed.
package otto
// Boolean
func builtinBoolean(call FunctionCall) Value {
return toValue_bool(call.Argument(0).bool())
}
func builtinNewBoolean(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
}
func builtinBoolean_toString(call FunctionCall) Value {
value := call.This
if !value.IsBoolean() {
// Will throw a TypeError if ThisObject is not a Boolean
value = call.thisClassObject("Boolean").primitiveValue()
}
return toValue_string(value.string())
}
func builtinBoolean_valueOf(call FunctionCall) Value {
value := call.This
if !value.IsBoolean() {
value = call.thisClassObject("Boolean").primitiveValue()
}
return value
}
This diff is collapsed.
package otto
import (
"fmt"
)
func builtinError(call FunctionCall) Value {
return toValue_object(call.runtime.newError("Error", call.Argument(0), 1))
}
func builtinNewError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newError("Error", valueOfArrayIndex(argumentList, 0), 0))
}
func builtinError_toString(call FunctionCall) Value {
thisObject := call.thisObject()
if thisObject == nil {
panic(call.runtime.panicTypeError())
}
name := "Error"
nameValue := thisObject.get("name")
if nameValue.IsDefined() {
name = nameValue.string()
}
message := ""
messageValue := thisObject.get("message")
if messageValue.IsDefined() {
message = messageValue.string()
}
if len(name) == 0 {
return toValue_string(message)
}
if len(message) == 0 {
return toValue_string(name)
}
return toValue_string(fmt.Sprintf("%s: %s", name, message))
}
func (runtime *_runtime) newEvalError(message Value) *_object {
self := runtime.newErrorObject("EvalError", message, 0)
self.prototype = runtime.global.EvalErrorPrototype
return self
}
func builtinEvalError(call FunctionCall) Value {
return toValue_object(call.runtime.newEvalError(call.Argument(0)))
}
func builtinNewEvalError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newEvalError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newTypeError(message Value) *_object {
self := runtime.newErrorObject("TypeError", message, 0)
self.prototype = runtime.global.TypeErrorPrototype
return self
}
func builtinTypeError(call FunctionCall) Value {
return toValue_object(call.runtime.newTypeError(call.Argument(0)))
}
func builtinNewTypeError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newTypeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newRangeError(message Value) *_object {
self := runtime.newErrorObject("RangeError", message, 0)
self.prototype = runtime.global.RangeErrorPrototype
return self
}
func builtinRangeError(call FunctionCall) Value {
return toValue_object(call.runtime.newRangeError(call.Argument(0)))
}
func builtinNewRangeError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRangeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newURIError(message Value) *_object {
self := runtime.newErrorObject("URIError", message, 0)
self.prototype = runtime.global.URIErrorPrototype
return self
}
func (runtime *_runtime) newReferenceError(message Value) *_object {
self := runtime.newErrorObject("ReferenceError", message, 0)
self.prototype = runtime.global.ReferenceErrorPrototype
return self
}
func builtinReferenceError(call FunctionCall) Value {
return toValue_object(call.runtime.newReferenceError(call.Argument(0)))
}
func builtinNewReferenceError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newReferenceError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newSyntaxError(message Value) *_object {
self := runtime.newErrorObject("SyntaxError", message, 0)
self.prototype = runtime.global.SyntaxErrorPrototype
return self
}
func builtinSyntaxError(call FunctionCall) Value {
return toValue_object(call.runtime.newSyntaxError(call.Argument(0)))
}
func builtinNewSyntaxError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newSyntaxError(valueOfArrayIndex(argumentList, 0)))
}
func builtinURIError(call FunctionCall) Value {
return toValue_object(call.runtime.newURIError(call.Argument(0)))
}
func builtinNewURIError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newURIError(valueOfArrayIndex(argumentList, 0)))
}
package otto
import (
"fmt"
"regexp"
"strings"
"unicode"
"github.com/robertkrimen/otto/parser"
)
// Function
func builtinFunction(call FunctionCall) Value {
return toValue_object(builtinNewFunctionNative(call.runtime, call.ArgumentList))
}
func builtinNewFunction(self *_object, argumentList []Value) Value {
return toValue_object(builtinNewFunctionNative(self.runtime, argumentList))
}
func argumentList2parameterList(argumentList []Value) []string {
parameterList := make([]string, 0, len(argumentList))
for _, value := range argumentList {
tmp := strings.FieldsFunc(value.string(), func(chr rune) bool {
return chr == ',' || unicode.IsSpace(chr)
})
parameterList = append(parameterList, tmp...)
}
return parameterList
}
var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object {
var parameterList, body string
count := len(argumentList)
if count > 0 {
tmp := make([]string, 0, count-1)
for _, value := range argumentList[0 : count-1] {
tmp = append(tmp, value.string())
}
parameterList = strings.Join(tmp, ",")
body = argumentList[count-1].string()
}
// FIXME
function, err := parser.ParseFunction(parameterList, body)
runtime.parseThrow(err) // Will panic/throw appropriately
cmpl := _compiler{}
cmpl_function := cmpl.parseExpression(function)
return runtime.newNodeFunction(cmpl_function.(*_nodeFunctionLiteral), runtime.globalStash)
}
func builtinFunction_toString(call FunctionCall) Value {
object := call.thisClassObject("Function") // Should throw a TypeError unless Function
switch fn := object.value.(type) {
case _nativeFunctionObject:
return toValue_string(fmt.Sprintf("function %s() { [native code] }", fn.name))
case _nodeFunctionObject:
return toValue_string(fn.node.source)
case _bindFunctionObject:
return toValue_string("function () { [native code] }")
}
panic(call.runtime.panicTypeError("Function.toString()"))
}
func builtinFunction_apply(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
}
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
}
argumentList := call.Argument(1)
switch argumentList.kind {
case valueUndefined, valueNull:
return call.thisObject().call(this, nil, false, nativeFrame)
case valueObject:
default:
panic(call.runtime.panicTypeError())
}
arrayObject := argumentList._object()
thisObject := call.thisObject()
length := int64(toUint32(arrayObject.get("length")))
valueArray := make([]Value, length)
for index := int64(0); index < length; index++ {
valueArray[index] = arrayObject.get(arrayIndexToString(index))
}
return thisObject.call(this, valueArray, false, nativeFrame)
}
func builtinFunction_call(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
}
thisObject := call.thisObject()
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
}
if len(call.ArgumentList) >= 1 {
return thisObject.call(this, call.ArgumentList[1:], false, nativeFrame)
}
return thisObject.call(this, nil, false, nativeFrame)
}
func builtinFunction_bind(call FunctionCall) Value {
target := call.This
if !target.isCallable() {
panic(call.runtime.panicTypeError())
}
targetObject := target._object()
this := call.Argument(0)
argumentList := call.slice(1)
if this.IsUndefined() {
// FIXME Do this elsewhere?
this = toValue_object(call.runtime.globalObject)
}
return toValue_object(call.runtime.newBoundFunction(targetObject, this, argumentList))
}
package otto
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type _builtinJSON_parseContext struct {
call FunctionCall
reviver Value
}
func builtinJSON_parse(call FunctionCall) Value {
ctx := _builtinJSON_parseContext{
call: call,
}
revive := false
if reviver := call.Argument(1); reviver.isCallable() {
revive = true
ctx.reviver = reviver
}
var root interface{}
err := json.Unmarshal([]byte(call.Argument(0).string()), &root)
if err != nil {
panic(call.runtime.panicSyntaxError(err.Error()))
}
value, exists := builtinJSON_parseWalk(ctx, root)
if !exists {
value = Value{}
}
if revive {
root := ctx.call.runtime.newObject()
root.put("", value, false)
return builtinJSON_reviveWalk(ctx, root, "")
}
return value
}
func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value {
value := holder.get(name)
if object := value._object(); object != nil {
if isArray(object) {
length := int64(objectLength(object))
for index := int64(0); index < length; index += 1 {
name := arrayIndexToString(index)
value := builtinJSON_reviveWalk(ctx, object, name)
if value.IsUndefined() {
object.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
}
}
} else {
object.enumerate(false, func(name string) bool {
value := builtinJSON_reviveWalk(ctx, object, name)
if value.IsUndefined() {
object.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
}
return true
})
}
}
return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value)
}
func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) {
switch value := rawValue.(type) {
case nil:
return nullValue, true
case bool:
return toValue_bool(value), true
case string:
return toValue_string(value), true
case float64:
return toValue_float64(value), true
case []interface{}:
arrayValue := make([]Value, len(value))
for index, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
arrayValue[index] = value
}
}
return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true
case map[string]interface{}:
object := ctx.call.runtime.newObject()
for name, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
object.put(name, value, false)
}
}
return toValue_object(object), true
}
return Value{}, false
}
type _builtinJSON_stringifyContext struct {
call FunctionCall
stack []*_object
propertyList []string
replacerFunction *Value
gap string
}
func builtinJSON_stringify(call FunctionCall) Value {
ctx := _builtinJSON_stringifyContext{
call: call,
stack: []*_object{nil},
}
replacer := call.Argument(1)._object()
if replacer != nil {
if isArray(replacer) {
length := objectLength(replacer)
seen := map[string]bool{}
propertyList := make([]string, length)
length = 0
for index, _ := range propertyList {
value := replacer.get(arrayIndexToString(int64(index)))
switch value.kind {
case valueObject:
switch value.value.(*_object).class {
case "String":
case "Number":
default:
continue
}
case valueString:
case valueNumber:
default:
continue
}
name := value.string()
if seen[name] {
continue
}
seen[name] = true
length += 1
propertyList[index] = name
}
ctx.propertyList = propertyList[0:length]
} else if replacer.class == "Function" {
value := toValue_object(replacer)
ctx.replacerFunction = &value
}
}
if spaceValue, exists := call.getArgument(2); exists {
if spaceValue.kind == valueObject {
switch spaceValue.value.(*_object).class {
case "String":
spaceValue = toValue_string(spaceValue.string())
case "Number":
spaceValue = spaceValue.numberValue()
}
}
switch spaceValue.kind {
case valueString:
value := spaceValue.string()
if len(value) > 10 {
ctx.gap = value[0:10]
} else {
ctx.gap = value
}
case valueNumber:
value := spaceValue.number().int64
if value > 10 {
value = 10
} else if value < 0 {
value = 0
}
ctx.gap = strings.Repeat(" ", int(value))
}
}
holder := call.runtime.newObject()
holder.put("", call.Argument(0), false)
value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
if !exists {
return Value{}
}
valueJSON, err := json.Marshal(value)
if err != nil {
panic(call.runtime.panicTypeError(err.Error()))
}
if ctx.gap != "" {
valueJSON1 := bytes.Buffer{}
json.Indent(&valueJSON1, valueJSON, "", ctx.gap)
valueJSON = valueJSON1.Bytes()
}
return toValue_string(string(valueJSON))
}
func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) {
value := holder.get(key)
if value.IsObject() {
object := value._object()
if toJSON := object.get("toJSON"); toJSON.IsFunction() {
value = toJSON.call(ctx.call.runtime, value, key)
} else {
// If the object is a GoStruct or something that implements json.Marshaler
if object.objectClass.marshalJSON != nil {
marshaler := object.objectClass.marshalJSON(object)
if marshaler != nil {
return marshaler, true
}
}
}
}
if ctx.replacerFunction != nil {
value = (*ctx.replacerFunction).call(ctx.call.runtime, toValue_object(holder), key, value)
}
if value.kind == valueObject {
switch value.value.(*_object).class {
case "Boolean":
value = value._object().value.(Value)
case "String":
value = toValue_string(value.string())
case "Number":
value = value.numberValue()
}
}
switch value.kind {
case valueBoolean:
return value.bool(), true
case valueString:
return value.string(), true
case valueNumber:
integer := value.number()
switch integer.kind {
case numberInteger:
return integer.int64, true
case numberFloat:
return integer.float64, true
default:
return nil, true
}
case valueNull:
return nil, true
case valueObject:
holder := value._object()
if value := value._object(); nil != value {
for _, object := range ctx.stack {
if holder == object {
panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON"))
}
}
ctx.stack = append(ctx.stack, value)
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
}
if isArray(holder) {
var length uint32
switch value := holder.get("length").value.(type) {
case uint32:
length = value
case int:
if value >= 0 {
length = uint32(value)
}
default:
panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value)))
}
array := make([]interface{}, length)
for index, _ := range array {
name := arrayIndexToString(int64(index))
value, _ := builtinJSON_stringifyWalk(ctx, name, holder)
array[index] = value
}
return array, true
} else if holder.class != "Function" {
object := map[string]interface{}{}
if ctx.propertyList != nil {
for _, name := range ctx.propertyList {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
if exists {
object[name] = value
}
}
} else {
// Go maps are without order, so this doesn't conform to the ECMA ordering
// standard, but oh well...
holder.enumerate(false, func(name string) bool {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
if exists {
object[name] = value
}
return true
})
}
return object, true
}
}
return nil, false
}
package otto
import (
"math"
"math/rand"
)
// Math
func builtinMath_abs(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Abs(number))
}
func builtinMath_acos(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Acos(number))
}
func builtinMath_asin(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Asin(number))
}
func builtinMath_atan(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Atan(number))
}
func builtinMath_atan2(call FunctionCall) Value {
y := call.Argument(0).float64()
if math.IsNaN(y) {
return NaNValue()
}
x := call.Argument(1).float64()
if math.IsNaN(x) {
return NaNValue()
}
return toValue_float64(math.Atan2(y, x))
}
func builtinMath_cos(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Cos(number))
}
func builtinMath_ceil(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Ceil(number))
}
func builtinMath_exp(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Exp(number))
}
func builtinMath_floor(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Floor(number))
}
func builtinMath_log(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Log(number))
}
func builtinMath_max(call FunctionCall) Value {
switch len(call.ArgumentList) {
case 0:
return negativeInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
return NaNValue()
}
for _, value := range call.ArgumentList[1:] {
value := value.float64()
if math.IsNaN(value) {
return NaNValue()
}
result = math.Max(result, value)
}
return toValue_float64(result)
}
func builtinMath_min(call FunctionCall) Value {
switch len(call.ArgumentList) {
case 0:
return positiveInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
return NaNValue()
}
for _, value := range call.ArgumentList[1:] {
value := value.float64()
if math.IsNaN(value) {
return NaNValue()
}
result = math.Min(result, value)
}
return toValue_float64(result)
}
func builtinMath_pow(call FunctionCall) Value {
// TODO Make sure this works according to the specification (15.8.2.13)
x := call.Argument(0).float64()
y := call.Argument(1).float64()
if math.Abs(x) == 1 && math.IsInf(y, 0) {
return NaNValue()
}
return toValue_float64(math.Pow(x, y))
}
func builtinMath_random(call FunctionCall) Value {
var v float64
if call.runtime.random != nil {
v = call.runtime.random()
} else {
v = rand.Float64()
}
return toValue_float64(v)
}
func builtinMath_round(call FunctionCall) Value {
number := call.Argument(0).float64()
value := math.Floor(number + 0.5)
if value == 0 {
value = math.Copysign(0, number)
}
return toValue_float64(value)
}
func builtinMath_sin(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Sin(number))
}
func builtinMath_sqrt(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Sqrt(number))
}
func builtinMath_tan(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Tan(number))
}
package otto
import (
"math"
"strconv"
)
// Number
func numberValueFromNumberArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 {
return argumentList[0].numberValue()
}
return toValue_int(0)
}
func builtinNumber(call FunctionCall) Value {
return numberValueFromNumberArgumentList(call.ArgumentList)
}
func builtinNewNumber(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList)))
}
func builtinNumber_toString(call FunctionCall) Value {
// Will throw a TypeError if ThisObject is not a Number
value := call.thisClassObject("Number").primitiveValue()
radix := 10
radixArgument := call.Argument(0)
if radixArgument.IsDefined() {
integer := toIntegerFloat(radixArgument)
if integer < 2 || integer > 36 {
panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36"))
}
radix = int(integer)
}
if radix == 10 {
return toValue_string(value.string())
}
return toValue_string(numberToStringRadix(value, radix))
}
func builtinNumber_valueOf(call FunctionCall) Value {
return call.thisClassObject("Number").primitiveValue()
}
func builtinNumber_toFixed(call FunctionCall) Value {
precision := toIntegerFloat(call.Argument(0))
if 20 < precision || 0 > precision {
panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20"))
}
if call.This.IsNaN() {
return toValue_string("NaN")
}
value := call.This.float64()
if math.Abs(value) >= 1e21 {
return toValue_string(floatToString(value, 64))
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'f', int(precision), 64))
}
func builtinNumber_toExponential(call FunctionCall) Value {
if call.This.IsNaN() {
return toValue_string("NaN")
}
precision := float64(-1)
if value := call.Argument(0); value.IsDefined() {
precision = toIntegerFloat(value)
if 0 > precision {
panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36"))
}
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'e', int(precision), 64))
}
func builtinNumber_toPrecision(call FunctionCall) Value {
if call.This.IsNaN() {
return toValue_string("NaN")
}
value := call.Argument(0)
if value.IsUndefined() {
return toValue_string(call.This.string())
}
precision := toIntegerFloat(value)
if 1 > precision {
panic(call.runtime.panicRangeError("toPrecision() precision must be greater than 1"))
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'g', int(precision), 64))
}
func builtinNumber_toLocaleString(call FunctionCall) Value {
return builtinNumber_toString(call)
}
package otto
import (
"fmt"
)
// Object
func builtinObject(call FunctionCall) Value {
value := call.Argument(0)
switch value.kind {
case valueUndefined, valueNull:
return toValue_object(call.runtime.newObject())
}
return toValue_object(call.runtime.toObject(value))
}
func builtinNewObject(self *_object, argumentList []Value) Value {
value := valueOfArrayIndex(argumentList, 0)
switch value.kind {
case valueNull, valueUndefined:
case valueNumber, valueString, valueBoolean:
return toValue_object(self.runtime.toObject(value))
case valueObject:
return value
default:
}
return toValue_object(self.runtime.newObject())
}
func builtinObject_valueOf(call FunctionCall) Value {
return toValue_object(call.thisObject())
}
func builtinObject_hasOwnProperty(call FunctionCall) Value {
propertyName := call.Argument(0).string()
thisObject := call.thisObject()
return toValue_bool(thisObject.hasOwnProperty(propertyName))
}
func builtinObject_isPrototypeOf(call FunctionCall) Value {
value := call.Argument(0)
if !value.IsObject() {
return falseValue
}
prototype := call.toObject(value).prototype
thisObject := call.thisObject()
for prototype != nil {
if thisObject == prototype {
return trueValue
}
prototype = prototype.prototype
}
return falseValue
}
func builtinObject_propertyIsEnumerable(call FunctionCall) Value {
propertyName := call.Argument(0).string()
thisObject := call.thisObject()
property := thisObject.getOwnProperty(propertyName)
if property != nil && property.enumerable() {
return trueValue
}
return falseValue
}
func builtinObject_toString(call FunctionCall) Value {
result := ""
if call.This.IsUndefined() {
result = "[object Undefined]"
} else if call.This.IsNull() {
result = "[object Null]"
} else {
result = fmt.Sprintf("[object %s]", call.thisObject().class)
}
return toValue_string(result)
}
func builtinObject_toLocaleString(call FunctionCall) Value {
toString := call.thisObject().get("toString")
if !toString.isCallable() {
panic(call.runtime.panicTypeError())
}
return toString.call(call.runtime, call.This)
}
func builtinObject_getPrototypeOf(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
if object.prototype == nil {
return nullValue
}
return toValue_object(object.prototype)
}
func builtinObject_getOwnPropertyDescriptor(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
name := call.Argument(1).string()
descriptor := object.getOwnProperty(name)
if descriptor == nil {
return Value{}
}
return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor))
}
func builtinObject_defineProperty(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
name := call.Argument(1).string()
descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
object.defineOwnProperty(name, descriptor, true)
return objectValue
}
func builtinObject_defineProperties(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
properties := call.runtime.toObject(call.Argument(1))
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
return true
})
return objectValue
}
func builtinObject_create(call FunctionCall) Value {
prototypeValue := call.Argument(0)
if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
panic(call.runtime.panicTypeError())
}
object := call.runtime.newObject()
object.prototype = prototypeValue._object()
propertiesValue := call.Argument(1)
if propertiesValue.IsDefined() {
properties := call.runtime.toObject(propertiesValue)
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
return true
})
}
return toValue_object(object)
}
func builtinObject_isExtensible(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
return toValue_bool(object.extensible)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_preventExtensions(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_isSealed(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() {
result = false
}
return true
})
return toValue_bool(result)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_seal(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property := object.getOwnProperty(name); nil != property && property.configurable() {
property.configureOff()
object.defineOwnProperty(name, *property, true)
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_isFrozen(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() || property.writable() {
result = false
}
return true
})
return toValue_bool(result)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_freeze(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property, update := object.getOwnProperty(name), false; nil != property {
if property.isDataDescriptor() && property.writable() {
property.writeOff()
update = true
}
if property.configurable() {
property.configureOff()
update = true
}
if update {
object.defineOwnProperty(name, *property, true)
}
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_keys(call FunctionCall) Value {
if object, keys := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(false, func(name string) bool {
keys = append(keys, toValue_string(name))
return true
})
return toValue_object(call.runtime.newArrayOf(keys))
}
panic(call.runtime.panicTypeError())
}
func builtinObject_getOwnPropertyNames(call FunctionCall) Value {
if object, propertyNames := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(true, func(name string) bool {
if object.hasOwnProperty(name) {
propertyNames = append(propertyNames, toValue_string(name))
}
return true
})
return toValue_object(call.runtime.newArrayOf(propertyNames))
}
panic(call.runtime.panicTypeError())
}
package otto
import (
"fmt"
)
// RegExp
func builtinRegExp(call FunctionCall) Value {
pattern := call.Argument(0)
flags := call.Argument(1)
if object := pattern._object(); object != nil {
if object.class == "RegExp" && flags.IsUndefined() {
return pattern
}
}
return toValue_object(call.runtime.newRegExp(pattern, flags))
}
func builtinNewRegExp(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRegExp(
valueOfArrayIndex(argumentList, 0),
valueOfArrayIndex(argumentList, 1),
))
}
func builtinRegExp_toString(call FunctionCall) Value {
thisObject := call.thisObject()
source := thisObject.get("source").string()
flags := []byte{}
if thisObject.get("global").bool() {
flags = append(flags, 'g')
}
if thisObject.get("ignoreCase").bool() {
flags = append(flags, 'i')
}
if thisObject.get("multiline").bool() {
flags = append(flags, 'm')
}
return toValue_string(fmt.Sprintf("/%s/%s", source, flags))
}
func builtinRegExp_exec(call FunctionCall) Value {
thisObject := call.thisObject()
target := call.Argument(0).string()
match, result := execRegExp(thisObject, target)
if !match {
return nullValue
}
return toValue_object(execResultToArray(call.runtime, target, result))
}
func builtinRegExp_test(call FunctionCall) Value {
thisObject := call.thisObject()
target := call.Argument(0).string()
match, _ := execRegExp(thisObject, target)
return toValue_bool(match)
}
func builtinRegExp_compile(call FunctionCall) Value {
// This (useless) function is deprecated, but is here to provide some
// semblance of compatibility.
// Caveat emptor: it may not be around for long.
return Value{}
}
This diff is collapsed.
package otto
import (
"testing"
)
func TestString_substr(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
[
"abc".substr(0,1), // "a"
"abc".substr(0,2), // "ab"
"abc".substr(0,3), // "abc"
"abc".substr(0,4), // "abc"
"abc".substr(0,9), // "abc"
];
`, "a,ab,abc,abc,abc")
test(`
[
"abc".substr(1,1), // "b"
"abc".substr(1,2), // "bc"
"abc".substr(1,3), // "bc"
"abc".substr(1,4), // "bc"
"abc".substr(1,9), // "bc"
];
`, "b,bc,bc,bc,bc")
test(`
[
"abc".substr(2,1), // "c"
"abc".substr(2,2), // "c"
"abc".substr(2,3), // "c"
"abc".substr(2,4), // "c"
"abc".substr(2,9), // "c"
];
`, "c,c,c,c,c")
test(`
[
"abc".substr(3,1), // ""
"abc".substr(3,2), // ""
"abc".substr(3,3), // ""
"abc".substr(3,4), // ""
"abc".substr(3,9), // ""
];
`, ",,,,")
test(`
[
"abc".substr(0), // "abc"
"abc".substr(1), // "bc"
"abc".substr(2), // "c"
"abc".substr(3), // ""
"abc".substr(9), // ""
];
`, "abc,bc,c,,")
test(`
[
"abc".substr(-9), // "abc"
"abc".substr(-3), // "abc"
"abc".substr(-2), // "bc"
"abc".substr(-1), // "c"
];
`, "abc,abc,bc,c")
test(`
[
"abc".substr(-9, 1), // "a"
"abc".substr(-3, 1), // "a"
"abc".substr(-2, 1), // "b"
"abc".substr(-1, 1), // "c"
"abc".substr(-1, 2), // "c"
];
`, "a,a,b,c,c")
test(`"abcd".substr(3, 5)`, "d")
})
}
func Test_builtin_escape(t *testing.T) {
tt(t, func() {
is(builtin_escape("abc"), "abc")
is(builtin_escape("="), "%3D")
is(builtin_escape("abc=%+32"), "abc%3D%25+32")
is(builtin_escape("世界"), "%u4E16%u754C")
})
}
func Test_builtin_unescape(t *testing.T) {
tt(t, func() {
is(builtin_unescape("abc"), "abc")
is(builtin_unescape("=%3D"), "==")
is(builtin_unescape("abc%3D%25+32"), "abc=%+32")
is(builtin_unescape("%u4E16%u754C"), "世界")
})
}
func TestGlobal_escape(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
[
escape("abc"), // "abc"
escape("="), // "%3D"
escape("abc=%+32"), // "abc%3D%25+32"
escape("\u4e16\u754c"), // "%u4E16%u754C"
];
`, "abc,%3D,abc%3D%25+32,%u4E16%u754C")
})
}
func TestGlobal_unescape(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
[
unescape("abc"), // "abc"
unescape("=%3D"), // "=="
unescape("abc%3D%25+32"), // "abc=%+32"
unescape("%u4E16%u754C"), // "世界"
];
`, "abc,==,abc=%+32,世界")
})
}
This diff is collapsed.
package otto
import (
"fmt"
)
type _clone struct {
runtime *_runtime
_object map[*_object]*_object
_objectStash map[*_objectStash]*_objectStash
_dclStash map[*_dclStash]*_dclStash
_fnStash map[*_fnStash]*_fnStash
}
func (in *_runtime) clone() *_runtime {
in.lck.Lock()
defer in.lck.Unlock()
out := &_runtime{
debugger: in.debugger,
random: in.random,
stackLimit: in.stackLimit,
traceLimit: in.traceLimit,
}
clone := _clone{
runtime: out,
_object: make(map[*_object]*_object),
_objectStash: make(map[*_objectStash]*_objectStash),
_dclStash: make(map[*_dclStash]*_dclStash),
_fnStash: make(map[*_fnStash]*_fnStash),
}
globalObject := clone.object(in.globalObject)
out.globalStash = out.newObjectStash(globalObject, nil)
out.globalObject = globalObject
out.global = _global{
clone.object(in.global.Object),
clone.object(in.global.Function),
clone.object(in.global.Array),
clone.object(in.global.String),
clone.object(in.global.Boolean),
clone.object(in.global.Number),
clone.object(in.global.Math),
clone.object(in.global.Date),
clone.object(in.global.RegExp),
clone.object(in.global.Error),
clone.object(in.global.EvalError),
clone.object(in.global.TypeError),
clone.object(in.global.RangeError),
clone.object(in.global.ReferenceError),
clone.object(in.global.SyntaxError),
clone.object(in.global.URIError),
clone.object(in.global.JSON),
clone.object(in.global.ObjectPrototype),
clone.object(in.global.FunctionPrototype),
clone.object(in.global.ArrayPrototype),
clone.object(in.global.StringPrototype),
clone.object(in.global.BooleanPrototype),
clone.object(in.global.NumberPrototype),
clone.object(in.global.DatePrototype),
clone.object(in.global.RegExpPrototype),
clone.object(in.global.ErrorPrototype),
clone.object(in.global.EvalErrorPrototype),
clone.object(in.global.TypeErrorPrototype),
clone.object(in.global.RangeErrorPrototype),
clone.object(in.global.ReferenceErrorPrototype),
clone.object(in.global.SyntaxErrorPrototype),
clone.object(in.global.URIErrorPrototype),
}
out.eval = out.globalObject.property["eval"].value.(Value).value.(*_object)
out.globalObject.prototype = out.global.ObjectPrototype
// Not sure if this is necessary, but give some help to the GC
clone.runtime = nil
clone._object = nil
clone._objectStash = nil
clone._dclStash = nil
clone._fnStash = nil
return out
}
func (clone *_clone) object(in *_object) *_object {
if out, exists := clone._object[in]; exists {
return out
}
out := &_object{}
clone._object[in] = out
return in.objectClass.clone(in, out, clone)
}
func (clone *_clone) dclStash(in *_dclStash) (*_dclStash, bool) {
if out, exists := clone._dclStash[in]; exists {
return out, true
}
out := &_dclStash{}
clone._dclStash[in] = out
return out, false
}
func (clone *_clone) objectStash(in *_objectStash) (*_objectStash, bool) {
if out, exists := clone._objectStash[in]; exists {
return out, true
}
out := &_objectStash{}
clone._objectStash[in] = out
return out, false
}
func (clone *_clone) fnStash(in *_fnStash) (*_fnStash, bool) {
if out, exists := clone._fnStash[in]; exists {
return out, true
}
out := &_fnStash{}
clone._fnStash[in] = out
return out, false
}
func (clone *_clone) value(in Value) Value {
out := in
switch value := in.value.(type) {
case *_object:
out.value = clone.object(value)
}
return out
}
func (clone *_clone) valueArray(in []Value) []Value {
out := make([]Value, len(in))
for index, value := range in {
out[index] = clone.value(value)
}
return out
}
func (clone *_clone) stash(in _stash) _stash {
if in == nil {
return nil
}
return in.clone(clone)
}
func (clone *_clone) property(in _property) _property {
out := in
switch value := in.value.(type) {
case Value:
out.value = clone.value(value)
case _propertyGetSet:
p := _propertyGetSet{}
if value[0] != nil {
p[0] = clone.object(value[0])
}
if value[1] != nil {
p[1] = clone.object(value[1])
}
out.value = p
default:
panic(fmt.Errorf("in.value.(Value) != true; in.value is %T", in.value))
}
return out
}
func (clone *_clone) dclProperty(in _dclProperty) _dclProperty {
out := in
out.value = clone.value(in.value)
return out
}
package otto
import (
"testing"
)
func TestCloneGetterSetter(t *testing.T) {
vm := New()
vm.Run(`var x = Object.create(null, {
x: {
get: function() {},
set: function() {},
},
})`)
vm.Copy()
}
package otto
import (
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
)
type _file struct {
name string
src string
base int // This will always be 1 or greater
}
type _compiler struct {
file *file.File
program *ast.Program
}
func (cmpl *_compiler) parse() *_nodeProgram {
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl._parse(cmpl.program)
}
package otto
import (
"strconv"
)
func (self *_runtime) cmpl_evaluate_nodeProgram(node *_nodeProgram, eval bool) Value {
if !eval {
self.enterGlobalScope()
defer func() {
self.leaveScope()
}()
}
self.cmpl_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList)
self.scope.frame.file = node.file
return self.cmpl_evaluate_nodeStatementList(node.body)
}
func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash, node *_nodeFunctionLiteral, this Value, argumentList []Value) Value {
indexOfParameterName := make([]string, len(argumentList))
// function(abc, def, ghi)
// indexOfParameterName[0] = "abc"
// indexOfParameterName[1] = "def"
// indexOfParameterName[2] = "ghi"
// ...
argumentsFound := false
for index, name := range node.parameterList {
if name == "arguments" {
argumentsFound = true
}
value := Value{}
if index < len(argumentList) {
value = argumentList[index]
indexOfParameterName[index] = name
}
// strict = false
self.scope.lexical.setValue(name, value, false)
}
if !argumentsFound {
arguments := self.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", toValue_object(function), 0101, false)
stash.arguments = arguments
// strict = false
self.scope.lexical.setValue("arguments", toValue_object(arguments), false)
for index, _ := range argumentList {
if index < len(node.parameterList) {
continue
}
indexAsString := strconv.FormatInt(int64(index), 10)
arguments.defineProperty(indexAsString, argumentList[index], 0111, false)
}
}
self.cmpl_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList)
result := self.cmpl_evaluate_nodeStatement(node.body)
if result.kind == valueResult {
return result
}
return Value{}
}
func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) {
executionContext := self.scope
eval := executionContext.eval
stash := executionContext.variable
for _, function := range list {
name := function.name
value := self.cmpl_evaluate_nodeExpression(function)
if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, value)
} else {
// TODO 10.5.5.e
stash.setBinding(name, value, false) // TODO strict
}
}
}
func (self *_runtime) cmpl_variableDeclaration(list []string) {
executionContext := self.scope
eval := executionContext.eval
stash := executionContext.variable
for _, name := range list {
if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, Value{}) // TODO strict?
}
}
}
This diff is collapsed.
This diff is collapsed.
package otto
import (
"testing"
"github.com/robertkrimen/otto/parser"
)
func Test_cmpl(t *testing.T) {
tt(t, func() {
vm := New()
test := func(src string, expect ...interface{}) {
program, err := parser.ParseFile(nil, "", src, 0)
is(err, nil)
{
program := cmpl_parse(program)
value := vm.runtime.cmpl_evaluate_nodeProgram(program, false)
if len(expect) > 0 {
is(value, expect[0])
}
}
}
test(``, Value{})
test(`var abc = 1; abc;`, 1)
test(`var abc = 1 + 1; abc;`, 2)
test(`1 + 2;`, 3)
})
}
func TestParse_cmpl(t *testing.T) {
tt(t, func() {
test := func(src string) {
program, err := parser.ParseFile(nil, "", src, 0)
is(err, nil)
is(cmpl_parse(program), "!=", nil)
}
test(``)
test(`var abc = 1; abc;`)
test(`
function abc() {
return;
}
`)
})
}
package otto
import (
"fmt"
"os"
"strings"
)
func formatForConsole(argumentList []Value) string {
output := []string{}
for _, argument := range argumentList {
output = append(output, fmt.Sprintf("%v", argument))
}
return strings.Join(output, " ")
}
func builtinConsole_log(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{}
}
func builtinConsole_error(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{}
}
// Nothing happens.
func builtinConsole_dir(call FunctionCall) Value {
return Value{}
}
func builtinConsole_time(call FunctionCall) Value {
return Value{}
}
func builtinConsole_timeEnd(call FunctionCall) Value {
return Value{}
}
func builtinConsole_trace(call FunctionCall) Value {
return Value{}
}
func builtinConsole_assert(call FunctionCall) Value {
return Value{}
}
func (runtime *_runtime) newConsole() *_object {
return newConsoleObject(runtime)
}
This diff is collapsed.
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) for github.com/robertkrimen/dbg
package otto
import (
Dbg "github.com/robertkrimen/otto/dbg"
)
var dbg, dbgf = Dbg.New()
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) from github.com/robertkrimen/dbg
/*
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
check bool
}
var (
ctlTest = regexp.MustCompile(`^\s*%/`)
ctlScan = regexp.MustCompile(`%?/(panic|fatal|check)(?:\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
case "check":
frmt.check = 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 := append([]interface{}{}, values[0:frmt.operandCount]...)
values_f := values[0:frmt.operandCount]
values_dbg := values[frmt.operandCount:]
if len(values_dbg) > 0 {
// Adjust frmt.format:
// (%v instead of %s because: frmt.check)
{
tmp := format
if len(tmp) > 0 {
if unicode.IsSpace(rune(tmp[len(tmp)-1])) {
buffer_f.WriteString("%v")
} else {
buffer_f.WriteString(" %v")
}
} else if frmt.check {
// Performing a check, so no output
} else {
buffer_f.WriteString("%v")
}
}
// Adjust values_f:
if !frmt.check {
tmp := []string{}
for _, value := range values_dbg {
tmp = append(tmp, fmt.Sprintf("%v", value))
}
// First, make a copy of values_f, so we avoid overwriting values_dbg when appending
values_f = append([]interface{}{}, values_f...)
values_f = append(values_f, strings.Join(tmp, " "))
}
}
format = buffer_f.String()
if frmt.check {
// We do not actually emit to the log, but panic if
// a non-nil value is detected (e.g. a non-nil error)
for _, value := range values_dbg {
if value != nil {
if format == "" {
panic(value)
} else {
panic(fmt.Sprintf(format, append(values_f, value)...))
}
}
}
} else {
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...)
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment