diff --git a/README.md b/README.md index 8d05c871c..3575af4f7 100644 --- a/README.md +++ b/README.md @@ -20,19 +20,6 @@ Q Language - A script language for Go * 作为编译原理的教学语言。由于 qlang 的 Compiler 代码极短,便于阅读和理解,非常方便教学实践之用。 -## 不兼容调整 - -qlang.v3 在 [qlang.v2](https://github.com/qiniu/qlang/tree/qlang.v2) 基础上,有如下不兼容调整: - -1. exec.Context: Var(name) 改名为 GetVar(name); -2. 采用 vendor 机制,放弃手工在包名上带上版本号(这意味着我们不再支持 go1.4 及以下版本); -3. 目录结构调整: - - 语言规范:qlang.io/qlang.spec.v1 => qlang.io/spec - - 编译器:qlang.io/qlang.v2/qlang => qlang.io/cl/qlang - - 运行时:qlang.io/exec.v2 => qlang.io/exec - - 可执行程序:qlang.io/app/* => qlang.io/cmd/* - - ## 快速入门 ### 在您的 Go 代码中整合 qlang @@ -104,6 +91,20 @@ QLANG_DUMPCODE=1 qlang * [Q 语言手册](README_QL.md): 这里有语言特性的详细介绍。 * [Qlang Tutorials](tutorial): 这里是一些 qlang 的样例代码,供您学习 qlang 时参考。 + +## 不兼容调整 + +qlang.v3 在 [qlang.v2](https://github.com/qiniu/qlang/tree/qlang.v2) 基础上,有如下不兼容调整: + +1. exec.Context: Var(name) 改名为 GetVar(name); +2. 采用 vendor 机制,放弃手工在包名上带上版本号(这意味着我们不再支持 go1.4 及以下版本); +3. 目录结构调整: + - 语言规范:qlang.io/qlang.spec.v1 => qlang.io/spec + - 编译器:qlang.io/qlang.v2/qlang => qlang.io/cl/qlang + - 运行时:qlang.io/exec.v2 => qlang.io/exec + - 可执行程序:qlang.io/app/* => qlang.io/cmd/* + + ## 下载 ### 发行版本 @@ -119,9 +120,7 @@ go get -u -insecure qlang.io/qlang 或者在 src 目录执行如下命令: ``` -mkdir qiniupkg.com git clone https://github.com/qiniu/qlang.git qlang.io -git clone https://github.com/qiniu/text.git qiniupkg.com/text ``` ## 社区资源 diff --git a/cl/interpreter/interpret2.go b/cl/interpreter/interpret2.go index c9db6e847..d4c27585c 100644 --- a/cl/interpreter/interpret2.go +++ b/cl/interpreter/interpret2.go @@ -49,29 +49,29 @@ func lastWord(s string) string { return "" } -func call(ip *exec.Object, name string) interface{} { +func call(estk *exec.Stack, ip *exec.Object, name string) interface{} { if fn, ok := ip.Cls.Fns[name]; ok { - return fn.Call(ip) + return fn.Call(estk, ip) } panic("method not found: " + name) } -func callN(ip *exec.Object, name string, args ...interface{}) interface{} { +func callN(estk *exec.Stack, ip *exec.Object, name string, args ...interface{}) interface{} { if fn, ok := ip.Cls.Fns[name]; ok { args1 := make([]interface{}, 1, len(args)+1) args1[0] = ip - return fn.Call(append(args1, args...)...) + return fn.Call(estk, append(args1, args...)...) } panic("method not found: " + name) } -func callFn(stk *exec.Object, fn interface{}, arity int) { +func callFn(estk *exec.Stack, stk *exec.Object, fn interface{}, arity int) { var in []reflect.Value if arity > 0 { - ret := reflect.ValueOf(callN(stk, "popArgs", arity)) + ret := reflect.ValueOf(callN(estk, stk, "popArgs", arity)) if ret.Kind() != reflect.Slice { panic("method stack.popArgs doesn't return a slice") } @@ -84,17 +84,17 @@ func callFn(stk *exec.Object, fn interface{}, arity int) { vfn := reflect.ValueOf(fn) out := vfn.Call(in) if len(out) == 0 { - callN(stk, "push", nil) + callN(estk, stk, "push", nil) } else { - callN(stk, "push", out[0].Interface()) + callN(estk, stk, "push", out[0].Interface()) } } -func callVfn(stk *exec.Object, fn *exec.Function, arity int) { +func callVfn(estk *exec.Stack, stk *exec.Object, fn *exec.Function, arity int) { - v := callN(stk, "popArgs", arity) + v := callN(estk, stk, "popArgs", arity) if args, ok := v.([]interface{}); ok { - callN(stk, "push", fn.Call(args...)) + callN(estk, stk, "push", fn.Call(estk, args...)) return } @@ -107,7 +107,7 @@ func callVfn(stk *exec.Object, fn *exec.Function, arity int) { for i := 0; i < n; i++ { in[i] = ret.Index(i).Interface() } - callN(stk, "push", fn.Call(in...)) + callN(estk, stk, "push", fn.Call(estk, in...)) } func New(ip interface{}, options *Options) (p *Engine, err error) { @@ -125,7 +125,8 @@ func New(ip interface{}, options *Options) (p *Engine, err error) { } }() - p = newEngine(ip, options) + estk := exec.NewStack() + p = newEngine(estk, ip, options) return } @@ -153,7 +154,7 @@ func (p *Engine) Eval(expr string) (err error) { return p.Exec([]byte(expr), "") } -func newEngine(ip1 interface{}, options *Options) (p *Engine) { +func newEngine(estk *exec.Stack, ip1 interface{}, options *Options) (p *Engine) { if options == nil { options = &optionsDef @@ -165,13 +166,13 @@ func newEngine(ip1 interface{}, options *Options) (p *Engine) { } p = new(Engine) - vstk := call(ip, "stack") + vstk := call(estk, ip, "stack") stk, ok := vstk.(*exec.Object) if !ok { panic("stack isn't an object") } - vfntable := call(ip, "fntable") + vfntable := call(estk, ip, "fntable") fntable, ok := vfntable.(map[string]interface{}) if !ok { panic("fntable isn't a map[string]interface{} object") @@ -220,7 +221,7 @@ func newEngine(ip1 interface{}, options *Options) (p *Engine) { return } } - callFn(stk, fn, n) + callFn(estk, stk, fn, n) } return tpl.Action(g, action) } @@ -288,15 +289,15 @@ func newEngine(ip1 interface{}, options *Options) (p *Engine) { } } } - vfn.Call(args...) + vfn.Call(estk, args...) } else { - callVfn(stk, vfn, n) + callVfn(estk, stk, vfn, n) } } return tpl.Action(g, action) } - vgrammar := call(ip, "grammar") + vgrammar := call(estk, ip, "grammar") grammar, ok := vgrammar.(string) if !ok { panic("grammar isn't a string object") diff --git a/cl/qlang/engine2.go b/cl/qlang/engine2.go index d04bfa8be..fd641c2ef 100644 --- a/cl/qlang/engine2.go +++ b/cl/qlang/engine2.go @@ -89,13 +89,6 @@ func New() *Qlang { return &Qlang{ctx, cl} } -// NewEx returns a new qlang instance. -// -func NewEx(options ...*Options) (lang *Qlang, err error) { - - return New(), nil -} - // SetLibs sets lib paths for searching modules. // func (p *Qlang) SetLibs(libs string) { diff --git a/cmd/qexport/api.go b/cmd/qexport/api.go index 28dc2a603..f287c658f 100644 --- a/cmd/qexport/api.go +++ b/cmd/qexport/api.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" ) @@ -12,10 +13,20 @@ func apipath(base string) string { return filepath.Join(os.Getenv("GOROOT"), "api", base) } +//pkg syscall (windows-386), const CERT_E_CN_NO_MATCH = 2148204815 var sym = regexp.MustCompile(`^pkg (\S+)\s?(.*)?, (?:(var|func|type|const)) ([A-Z]\w*)`) +var num = regexp.MustCompile(`^\-?[0-9]+$`) + +type KeyType int + +const ( + Normal KeyType = 1 + ConstInt64 KeyType = 2 + ConstUnit64 KeyType = 3 +) type GoApi struct { - Keys map[string]bool + Keys map[string]KeyType Ver string } @@ -25,7 +36,7 @@ func LoadApi(ver string) (*GoApi, error) { return nil, err } sc := bufio.NewScanner(f) - keys := make(map[string]bool) + keys := make(map[string]KeyType) for sc.Scan() { l := sc.Text() has := func(v string) bool { return strings.Contains(l, v) } @@ -38,20 +49,38 @@ func LoadApi(ver string) (*GoApi, error) { // 3 var|func|type|const // 4 name key := m[1] + "." + m[4] - keys[key] = true + if _, ok := keys[key]; ok { + continue + } + keys[key] = Normal + if m[3] == "const" { + if pos := strings.LastIndex(l, "="); pos != -1 { + value := strings.TrimSpace(l[pos+1:]) + if num.MatchString(value) { + _, err := strconv.ParseInt(l[pos+2:], 10, 32) + if err != nil { + if value[0] == '-' { + keys[key] = ConstInt64 + } else { + keys[key] = ConstUnit64 + } + } + } + } + } } } return &GoApi{Ver: ver, Keys: keys}, nil } type ApiCheck struct { - Base map[string]bool + Base map[string]KeyType Apis []*GoApi } func NewApiCheck() *ApiCheck { ac := &ApiCheck{} - ac.Base = make(map[string]bool) + ac.Base = make(map[string]KeyType) return ac } @@ -75,7 +104,7 @@ func (ac *ApiCheck) LoadApi(vers ...string) error { return err } for k, _ := range api.Keys { - if ac.Base[k] { + if _, ok := ac.Base[k]; ok { delete(api.Keys, k) } } @@ -86,7 +115,7 @@ func (ac *ApiCheck) LoadApi(vers ...string) error { func (ac *ApiCheck) FincApis(name string) (vers []string) { for _, api := range ac.Apis { - if api.Keys[name] { + if _, ok := api.Keys[name]; ok { vers = append(vers, api.Ver) } } @@ -99,3 +128,15 @@ func (ac *ApiCheck) ApiVers() (vers []string) { } return } + +func (ac *ApiCheck) CheckConstType(name string) KeyType { + if typ, ok := ac.Base[name]; ok { + return typ + } + for _, api := range ac.Apis { + if typ, ok := api.Keys[name]; ok { + return typ + } + } + return Normal +} diff --git a/cmd/qexport/context.go b/cmd/qexport/context.go index 07ccf7c27..f266e6ced 100644 --- a/cmd/qexport/context.go +++ b/cmd/qexport/context.go @@ -10,35 +10,20 @@ import ( // overridden by the -contexts flag. var contexts = []*build.Context{ {GOOS: "linux", GOARCH: "386", CgoEnabled: true}, - {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "linux", GOARCH: "amd64"}, {GOOS: "linux", GOARCH: "arm", CgoEnabled: true}, - {GOOS: "linux", GOARCH: "arm"}, {GOOS: "darwin", GOARCH: "386", CgoEnabled: true}, - {GOOS: "darwin", GOARCH: "386"}, {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "386", CgoEnabled: true}, - {GOOS: "windows", GOARCH: "386"}, {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true}, - {GOOS: "freebsd", GOARCH: "386"}, {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "freebsd", GOARCH: "amd64"}, {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true}, - {GOOS: "freebsd", GOARCH: "arm"}, {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true}, - {GOOS: "netbsd", GOARCH: "386"}, {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "netbsd", GOARCH: "amd64"}, {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true}, - {GOOS: "netbsd", GOARCH: "arm"}, {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true}, - {GOOS: "openbsd", GOARCH: "386"}, {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true}, - {GOOS: "openbsd", GOARCH: "amd64"}, } func contextName(c *build.Context) string { diff --git a/cmd/qexport/main.go b/cmd/qexport/main.go index 8246ae585..1868c1baa 100644 --- a/cmd/qexport/main.go +++ b/cmd/qexport/main.go @@ -109,23 +109,6 @@ func main() { } } -var ( - uint64_const_keys = []string{ - "crc64.ECMA", - "crc64.ISO", - "math.MaxUint64", - } -) - -func isUint64Const(key string) bool { - for _, k := range uint64_const_keys { - if key == k { - return true - } - } - return false -} - func export(pkg string, outpath string, skipOSArch bool) error { p, err := NewPackage(pkg, flagDefaultContext) if err != nil { @@ -157,6 +140,8 @@ func export(pkg string, outpath string, skipOSArch bool) error { for _, path := range strings.Split(bp.ImportPath, "/") { if path == "internal" { return errors.New("skip internal pkg") + } else if path == "vendor" { + return errors.New("skip vendor pkg") } } @@ -173,6 +158,9 @@ func export(pkg string, outpath string, skipOSArch bool) error { } return "", false } + checkConst := func(key string) KeyType { + return ac.CheckConstType(bp.ImportPath + "." + key) + } // go ver map verMap := make(map[string][]string) @@ -196,7 +184,10 @@ var Exports = map[string]interface{}{ for _, v := range keys { name := v fn := pkgName + "." + v - if isUint64Const(fn) { + typ := checkConst(v) + if typ == ConstInt64 { + fn = "int64(" + fn + ")" + } else if typ == ConstUnit64 { fn = "uint64(" + fn + ")" } if vers, ok := checkVer(v); ok { @@ -344,11 +335,11 @@ var Exports = map[string]interface{}{ } } - //export type, qlang.NewType(reflect.TypeOf((*http.Client)(nil)).Elem()) - //export type, qlang.StructOf((*strings.Reader)(nil)) + //export type, spec.NewType(reflect.TypeOf((*http.Client)(nil)).Elem()) + //export type, spec.StructOf((*strings.Reader)(nil)) if ast.IsExported(v) { name := v - fn := fmt.Sprintf("qlang.StructOf((*%s.%s)(nil))", pkgName, v) + fn := fmt.Sprintf("spec.StructOf((*%s.%s)(nil))", pkgName, v) if vers, ok := checkVer(v); ok { verHasTypeExport[vers] = true outfv(vers, name, fn) @@ -405,7 +396,7 @@ var Exports = map[string]interface{}{ outHeadf("import (\n") outHeadf("\t%q\n", pkg) if hasTypeExport { - outHeadf("\n\t\"qlang.io/qlang.spec.v1\"\n") + outHeadf("\n\t\"qlang.io/spec\"\n") } outHeadf(")\n\n") } @@ -438,7 +429,7 @@ var Exports = map[string]interface{}{ if verHasTypeExport[ver] { buf.WriteString("import (\n") buf.WriteString(fmt.Sprintf("\t%q\n\n", bp.ImportPath)) - buf.WriteString(fmt.Sprintf("\t%q\n", "qlang.io/qlang.spec.v1")) + buf.WriteString(fmt.Sprintf("\t%q\n", "qlang.io/spec")) buf.WriteString(")\n") } else { buf.WriteString(fmt.Sprintf("import %q\n", bp.ImportPath)) diff --git a/exec/call.go b/exec/call.go index 6caaa6af1..c4b68c601 100644 --- a/exec/call.go +++ b/exec/call.go @@ -72,7 +72,11 @@ func function2Func(in *reflect.Value, t reflect.Type) { fn := in.MethodByName("Call") wrap := func(args []reflect.Value) (results []reflect.Value) { - ret := fn.Call(args)[0] + argsWithStk := make([]reflect.Value, len(args)+1) + stk := NewStack() // issue #127: 使用新的stack,因为回调过程有可能是从新的goroutine发起的 + argsWithStk[0] = reflect.ValueOf(stk) + copy(argsWithStk[1:], args) + ret := fn.Call(argsWithStk)[0] n := t.NumOut() if n == 0 { return @@ -194,6 +198,9 @@ func Call(fn interface{}, varity ...int) Instr { // CallFn type iCallFn int +type iCaller interface { + Call(stk *Stack, args ...interface{}) interface{} +} func (arity iCallFn) OptimizableGetArity() int { @@ -212,13 +219,19 @@ func (arity iCallFn) Exec(stk *Stack, ctx *Context) { var tfn0 reflect.Type if vfn.Kind() != reflect.Func { // 这不是func,而是Function/其他可调用对象 tfn0 = tfn - vfnt := vfn.MethodByName("Call") - if vfnt.IsValid() { - vfn = vfnt + if caller, ok := vfn.Interface().(iCaller); ok { + vfn = reflect.ValueOf(func(args ...interface{}) interface{} { + return caller.Call(stk, args...) + }) } else { - vfn = reflect.Indirect(vfn).FieldByName("Call") - if vfn.Kind() == reflect.Interface { - vfn = vfn.Elem() + vfnt := vfn.MethodByName("Call") + if vfnt.IsValid() { + vfn = vfnt + } else { + vfn = reflect.Indirect(vfn).FieldByName("Call") + if vfn.Kind() == reflect.Interface { + vfn = vfn.Elem() + } } } tfn = vfn.Type() diff --git a/exec/class.go b/exec/class.go index dc1a5835c..9f584b3a7 100644 --- a/exec/class.go +++ b/exec/class.go @@ -49,14 +49,14 @@ func (p *Class) GoType() reflect.Type { // NewInstance creates a new instance of a qlang type. required by `qlang type` spec. // -func (p *Class) NewInstance(args ...interface{}) interface{} { +func (p *Class) NewInstance(stk *Stack, args ...interface{}) interface{} { - return p.New(args...) + return p.New(stk, args...) } // New creates a new instance of this class. // -func (p *Class) New(args ...interface{}) *Object { +func (p *Class) New(stk *Stack, args ...interface{}) *Object { obj := &Object{ vars: make(map[string]interface{}), @@ -67,7 +67,7 @@ func (p *Class) New(args ...interface{}) *Object { this: obj, fn: init, } - closure.Call(args...) + closure.Call(stk, args...) } else if len(args) > 0 { panic("constructor `_init` not found") } @@ -151,14 +151,14 @@ type Method struct { // Call calls this method with arguments. // -func (p *Method) Call(a ...interface{}) interface{} { +func (p *Method) Call(stk *Stack, a ...interface{}) interface{} { args := make([]interface{}, len(a)+1) args[0] = p.this for i, v := range a { args[i+1] = v } - return p.fn.Call(args...) + return p.fn.Call(stk, args...) } // ----------------------------------------------------------------------------- @@ -167,6 +167,10 @@ type newTyper interface { NewInstance(args ...interface{}) interface{} } +type newTyperEx interface { + NewInstance(stk *Stack, args ...interface{}) interface{} +} + type iNew int func (nArgs iNew) Exec(stk *Stack, ctx *Context) { @@ -178,6 +182,11 @@ func (nArgs iNew) Exec(stk *Stack, ctx *Context) { } if v, ok := stk.Pop(); ok { + if cls, ok := v.(newTyperEx); ok { + obj := cls.NewInstance(stk, args...) + stk.Push(obj) + return + } if cls, ok := v.(newTyper); ok { obj := cls.NewInstance(args...) stk.Push(obj) diff --git a/exec/code.go b/exec/code.go index 10eab0dc6..bb5d1cece 100644 --- a/exec/code.go +++ b/exec/code.go @@ -158,9 +158,9 @@ type variables struct { symtbl map[string]int // symbol table } -// InitVars initializes the variable table. +// initVars initializes the variable table. // -func (p *variables) InitVars(symtbl map[string]int) { +func (p *variables) initVars(symtbl map[string]int) { n := len(symtbl) if n > 0 { @@ -169,7 +169,7 @@ func (p *variables) InitVars(symtbl map[string]int) { p.symtbl = symtbl } -// ResizeVars resizes variable table. +// ResizeVars is reserved for internal use. // func (p *variables) ResizeVars() { @@ -307,7 +307,7 @@ func NewContextEx(symtbl map[string]int) *Context { mods: mods, } p := &Context{modmgr: modmgr} - p.InitVars(symtbl) + p.initVars(symtbl) return p } diff --git a/exec/function.go b/exec/function.go index 731500fd4..23678546d 100644 --- a/exec/function.go +++ b/exec/function.go @@ -131,9 +131,18 @@ func NewFunction(cls *Class, start, end int, symtbl map[string]int, args []strin // Call calls this function with default context. // -func (p *Function) Call(args ...interface{}) (ret interface{}) { - - return p.ExtCall(nil, args...) +func (p *Function) Call(stk *Stack, args ...interface{}) (ret interface{}) { + + parent := p.Parent + ctx := &Context{ + parent: parent, + Stack: stk, + Code: parent.Code, + modmgr: parent.modmgr, + base: stk.BaseFrame(), + } + ctx.initVars(p.symtbl) + return p.ExtCall(ctx, args...) } // ExtCall calls this function with a specified context. @@ -155,26 +164,7 @@ func (p *Function) ExtCall(ctx *Context, args ...interface{}) (ret interface{}) return nil } - var base int - var stk *Stack - - if ctx == nil { - parent := p.Parent - stk = parent.Stack - base = stk.BaseFrame() - ctx = &Context{ - parent: parent, - Stack: stk, - Code: parent.Code, - modmgr: parent.modmgr, - base: base, - } - ctx.InitVars(p.symtbl) - } else { - stk = ctx.Stack - base = stk.BaseFrame() - } - + stk := ctx.Stack vars := ctx.Vars() if p.Variadic { for i := 0; i < n-1; i++ { @@ -193,7 +183,7 @@ func (p *Function) ExtCall(ctx *Context, args ...interface{}) (ret interface{}) } ret = ctx.ret ctx.ExecDefers() - stk.SetFrame(base) + stk.SetFrame(ctx.base) if ctx.Recov != nil { panic(ctx.Recov) } diff --git a/exec/module.go b/exec/module.go index d4918fad6..565b055ac 100644 --- a/exec/module.go +++ b/exec/module.go @@ -17,7 +17,7 @@ func (p *iAnonymFn) Exec(stk *Stack, ctx *Context) { fn := NewFunction(nil, p.start, p.end, p.symtbl, nil, false) fn.Parent = ctx - stk.Push(fn.ExtCall(nil)) + stk.Push(fn.Call(stk)) } // AnonymFn returns an instruction that creates an anonymous function. @@ -81,7 +81,7 @@ func (p *iModule) Exec(stk *Stack, ctx *Context) { Stack: ctx.Stack, modmgr: ctx.modmgr, } - modCtx.InitVars(p.symtbl) + modCtx.initVars(p.symtbl) modFn := NewFunction(nil, p.start, p.end, p.symtbl, nil, false) modFn.ExtCall(modCtx) exports = modCtx.Exports()