Go+ SSA 脚本引擎简介

七叶

2021.12.22

主题

一、gossa 简介

gossa 是一个基于 SSA 实现的 Go 语言解释器,可以直接从 Go/Go+ 源码运行程序。

为什么要开发 gossa

Go 版本 interface

go run main.go

package main

import "fmt"

type T struct {
    n int
}

func (t *T) String() string {
    return fmt.Sprintf("T:%v", t.n)
}
func main() {
    fmt.Println("Hello World", &T{100})
}

T 实现了 fmt.Stringer 接口

yaegi 版本

yaegi run main.go

package main

import "fmt"

type T struct {
    n int
}

func (t *T) String() string {
    return fmt.Sprintf("T:%v", t.n)
}
func main() {
    fmt.Println("Hello World", &T{100})
}

yaegi 中 T 不兼容 fmt.Stringer 接口,打印原始值。

gossa 版本

gossa run main.go

package main

import "fmt"

type T struct {
    n int
}

func (t *T) String() string {
    return fmt.Sprintf("T:%v", t.n)
}
func main() {
    fmt.Println("Hello World", &T{100})
}

gossa 中 T 兼容 fmt.Stringer 接口,正确输出。

gossa 主要功能

gossa 使用限制

二、gossa 使用示例

go run main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

gop run main.go

println "Hello, World"

go run 运行过程

go run -x main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

gossa 版 Hello World

gossa run main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

gossa run main.gop

println "Hello, World"

gossa 组件

gossa 使用示例

通常情况不会直接使用 cmd/gossa ,而是基于 github.com/goplus/gossa 做自定义应用程序。

import (
    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
    _ "github.com/goplus/gossa/pkg/io"
    _ "github.com/goplus/gossa/pkg/os"
    。。。
)

gossa demo1

package main

import (
    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}
`

func main() {
    _, err := gossa.RunFile("main.go", source, nil, 0)
    if err != nil {
        panic(err)
    }
}

gossa demo2

package main

import (
    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/gopbuild"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
println "Hello, World"
`

func main() {
    _, err := gossa.RunFile("main.gop", source, nil, 0)
    if err != nil {
        panic(err)
    }
}

gossa demo3

package main

import (
    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

type T struct {}

func (t T) String() string {
    return "Hello, World" 
}

func main() {
    fmt.Println(&T{})
}
`

func main() {
    _, err := gossa.RunFile("main.go", source, nil, 0)
    if err != nil {
        panic(err)
    }
}
type Stringer interface { String() string }

gossa demo4

package main

import (
    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

type M struct { N int}
type T struct { N int}
func (t T) String() string {
    return "T:String" 
}
type U struct {
    T
}
func (u U) GoString() string {
    return "U:GoString"
}

func main() {
    m := &M{100}
    t := &T{100}
    u := &U{T{100}}
    fmt.Printf("%v %#v\n",m,m)
    fmt.Printf("%v %#v\n",t,t)
    fmt.Printf("%v %#v\n",u,u)
}
`

func main() {
    _, err := gossa.RunFile("main.go", source, nil, 0)
    if err != nil {
        panic(err)
    }
}

gossa demo5

package main

import (
    "fmt"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
    _ "github.com/goplus/gossa/pkg/os"
)

var source = `
package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println("Hello, World")
    os.Exit(2)
}
`

func main() {
    code, err := gossa.RunFile("main.go", source, nil, 0)
    if err != nil {
        panic(err)
    }
    fmt.Println("exit", code)
}

gossa demo6

package main

import (
    "fmt"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/gopbuild"
    _ "github.com/goplus/gossa/pkg/os"
)

var source = `
import "os"
println "Hello, World"
os.exit(2)
`

func main() {
    code, err := gossa.RunFile("main.gop", source, nil, 0)
    if err != nil {
        panic(err)
    }
    fmt.Println("exit code", code)
}

gossa demo7

package main

import (
    "fmt"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
    panic("error")
}
`

func main() {
    _, err := gossa.RunFile("main.go", source, nil, 0)
    if err != nil {
        fmt.Println("PANIC:", err)
    }
}

gossa demo8

package main

import (
    "fmt"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/gopbuild"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
var i int
println 100/i
`

func main() {
    _, err := gossa.RunFile("main.gop", source, nil, 0)
    if err != nil {
        fmt.Println("PANIC:", err)
    }
}

github.com/goplus/gossa 底层控制

gossa demo9

package main

import (
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    _, err = ctx.RunPkg(pkg, "main.go", nil)
    if err != nil {
        log.Panicln("run", err)
    }
}

gossa demo10

package main

import (
    "go/parser"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}
`

func main() {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "main.go", source, parser.ParseComments)
    if err != nil {
        log.Panicln("parse", err)
    }
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadAstFile(fset, f)
    if err != nil {
        log.Panicln("load", err)
    }
    _, err = ctx.RunPkg(pkg, "main.go", nil)
    if err != nil {
        log.Panicln("run", err)
    }
}

gossa demo11

package main

import (
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    _, err = interp.Run("main")
    if err != nil {
        log.Panicln("run", err)
    }
}

gossa demo12

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

func add(i, j int) int {
    return i+j
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    v, err := interp.RunFunc("add", 100, 200)
    fmt.Println(v, err)
}

gossa demo13

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package bar

func sum(n ...int) (r int) {
    for _, v := range n {
        r += v
    }
    return
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    v1, err := interp.RunFunc("sum", []int{100, 200})
    fmt.Println(v1, err)
    v2, err := interp.RunFunc("sum", []int{})
    fmt.Println(v2, err)
    // error call
    v3, err := interp.RunFunc("sum")
    fmt.Println(v3, err)
}

gossa demo14

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package bar

func call(i, j int) (int,int) {
    return i+j, i*j
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    v, err := interp.RunFunc("call", 100, 200)
    fmt.Println(v, err)
    if t, ok := v.(gossa.Tuple); ok {
        fmt.Println(len(t), t[0], t[1])
    }
}

Interp 函数

gossa demo15

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package bar

func add(i, j int) int {
    return i+j
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    if v, ok := interp.GetFunc("add"); ok {
        if fn, ok := v.(func(int, int) int); ok {
            r := fn(100, 200)
            fmt.Println(r)
        }
    }
}

gossa demo16

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package bar

var index int = 100
var pindex *int = &index

func show() {
    println(index)
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    if v, ok := interp.GetVarAddr("index"); ok {
        if p, ok := v.(*int); ok {
            fmt.Println(*p)
            *p = 200
        }
    }
    if v, ok := interp.GetVarAddr("pindex"); ok {
        if p, ok := v.(**int); ok {
            fmt.Println(**p)
            **p = 300
        }
    }
    if fn, ok := interp.GetFunc("show"); ok {
        fn.(func())()
    }
}

gossa demo17

package main

import (
    "fmt"
    "go/token"
    "log"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
    _ "github.com/goplus/gossa/pkg/math"
)

var source = `
package bar

import "math"

const Pi = math.Pi

func add(i, j int) int {
    return i+j
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }

    // go/constant.Value
    if v, ok := interp.GetConst("Pi"); ok {
        fmt.Printf("%v %T\n", v, v)
        fmt.Println(v.ExactString())
    }
}

gossa demo18

package main

import (
    "fmt"
    "go/token"
    "log"
    "reflect"

    "github.com/goplus/gossa"
    _ "github.com/goplus/gossa/pkg/fmt"
)

var source = `
package bar

import "fmt"

type Point struct {
    x, y int
}

func (p *Point) Set(x, y int) {
    p.x,p.y = x,y
}

func (p *Point) String() string {
    return fmt.Sprintf("(%v,%v)",p.x,p.y)
}
`

func main() {
    fset := token.NewFileSet()
    ctx := gossa.NewContext(0)
    pkg, err := ctx.LoadFile(fset, "main.go", source)
    if err != nil {
        log.Panicln("load", err)
    }
    interp, err := ctx.NewInterp(pkg)
    if err != nil {
        log.Panicln("interp", err)
    }
    if typ, ok := interp.GetType("Point"); ok {
        v := reflect.New(typ)
        fn := v.MethodByName("Set")
        fn.Interface().(func(int, int))(100, 200)
        fmt.Println(v)
    }
}

gossa 实战

安装 ispx

$ go get github.com/goplus/ispx

下载 FlappyCalf

$ git clone https://github.com/goplus/FlappyCalf

运行方式 1

$ ispx FlappyCalf

运行方式 2

$ cd FlappyCalf
$ ispx .

ispx 的实现代码

package main

//go:generate qexp -outdir pkg github.com/goplus/spx

import (
    "flag"
    "fmt"
    "log"
    "os"
    "path/filepath"

    "github.com/goplus/gossa"
    "github.com/goplus/gossa/gopbuild"
    "github.com/goplus/spx"

    _ "github.com/goplus/gossa/pkg/fmt"
    _ "github.com/goplus/gossa/pkg/math"
    _ "github.com/goplus/ispx/pkg/github.com/goplus/spx"

    _ "github.com/goplus/reflectx/icall/icall8192"
)

var (
    flagDumpSrc bool
    flagDumpPkg bool
    flagDumpSSA bool
)

func init() {
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "ispc [-dumpsrc|-dumppkg|-dumpssa] dir\n")
        flag.PrintDefaults()
    }
    flag.BoolVar(&flagDumpSrc, "dumpsrc", false, "print source code")
    flag.BoolVar(&flagDumpPkg, "dumppkg", false, "print import packages")
    flag.BoolVar(&flagDumpSSA, "dumpssa", false, "print ssa code information")
}

func main() {
    flag.Parse()
    args := flag.Args()
    if len(args) != 1 {
        flag.Usage()
        return
    }
    path := args[0]
    var mode gossa.Mode
    if flagDumpPkg {
        mode |= gossa.EnableDumpPackage
    }
    if flagDumpSSA {
        mode |= gossa.EnableTracing
    }
    ctx := gossa.NewContext(mode)
    data, err := gopbuild.BuildDir(ctx, path)
    if err != nil {
        log.Panicln(err)
    }
    if flagDumpSrc {
        fmt.Println(string(data))
    }
    if !filepath.IsAbs(path) {
        dir, _ := os.Getwd()
        path = filepath.Join(dir, path)
    }
    gossa.RegisterExternal("github.com/goplus/spx.Gopt_Game_Run", func(game spx.Gamer, resource interface{}, gameConf ...*spx.Config) {
        os.Chdir(path)
        spx.Gopt_Game_Run(game, resource, gameConf...)
    })
    _, err = ctx.RunFile("main.go", data, nil)
    if err != nil {
        log.Panicln(err)
    }
}

ispx 依赖包

$ ispx -dumppkg .

$ ispx -dumppkg .
2021/12/23 08:27:56 imported package spx ("github.com/goplus/spx")
2021/12/23 08:27:56 imported package math ("math")
2021/12/23 08:27:56 indirect package camera ("github.com/goplus/spx/internal/camera")
2021/12/23 08:27:56 indirect package tools ("github.com/goplus/spx/internal/tools")
2021/12/23 08:27:56 indirect package atlas ("github.com/hajimehoshi/ebiten/v2/internal/atlas")
2021/12/23 08:27:56 indirect package graphicscommand ("github.com/hajimehoshi/ebiten/v2/internal/graphicscommand")
2021/12/23 08:27:56 indirect package constant ("go/constant")
2021/12/23 08:27:56 indirect package fs ("github.com/goplus/spx/fs")
2021/12/23 08:27:56 indirect package math32 ("github.com/goplus/spx/internal/math32")
2021/12/23 08:27:56 imported package fmt ("fmt")
2021/12/23 08:27:56 indirect package rand ("math/rand")
2021/12/23 08:27:56 indirect package audio ("github.com/hajimehoshi/ebiten/v2/audio")
2021/12/23 08:27:56 indirect package reflect ("reflect")
2021/12/23 08:27:56 indirect package ebiten ("github.com/hajimehoshi/ebiten/v2")
2021/12/23 08:27:56 indirect package mipmap ("github.com/hajimehoshi/ebiten/v2/internal/mipmap")
2021/12/23 08:27:57 indirect package restorable ("github.com/hajimehoshi/ebiten/v2/internal/restorable")
2021/12/23 08:27:57 imported package strconv ("strconv")
2021/12/23 08:27:57 indirect package io ("io")
2021/12/23 08:27:57 imported package builtin ("github.com/goplus/gop/builtin")
2021/12/23 08:27:57 indirect package driver ("github.com/hajimehoshi/ebiten/v2/internal/driver")
2021/12/23 08:27:57 indirect package v2 ("github.com/hajimehoshi/oto/v2")
2021/12/23 08:27:57 imported package unsafe ("unsafe")
2021/12/23 08:27:57 imported package strings ("strings")
2021/12/23 08:27:57 indirect package color ("image/color")
2021/12/23 08:27:57 indirect package time ("time")
2021/12/23 08:27:57 indirect package anim ("github.com/goplus/spx/internal/anim")
2021/12/23 08:27:57 indirect package buffered ("github.com/hajimehoshi/ebiten/v2/internal/buffered")
2021/12/23 08:27:57 indirect package affine ("github.com/hajimehoshi/ebiten/v2/internal/affine")
2021/12/23 08:27:57 indirect package shaderir ("github.com/hajimehoshi/ebiten/v2/internal/shaderir")
2021/12/23 08:27:57 indirect package packing ("github.com/hajimehoshi/ebiten/v2/internal/packing")
2021/12/23 08:27:57 indirect package gdi ("github.com/goplus/spx/internal/gdi")
2021/12/23 08:27:57 indirect package audiorecord ("github.com/goplus/spx/internal/audiorecord")
2021/12/23 08:27:57 imported package big ("math/big")
2021/12/23 08:27:57 indirect package sync ("sync")
2021/12/23 08:27:57 indirect package unicode ("unicode")
2021/12/23 08:27:57 indirect package image ("image")
2021/12/23 08:27:57 indirect package coroutine ("github.com/goplus/spx/internal/coroutine")

ispx 的 spx 导入包

// export by github.com/goplus/gossa/cmd/qexp

package spx

import (
    q "github.com/goplus/spx"

    "go/constant"
    "reflect"

    "github.com/goplus/gossa"
)

func init() {
    gossa.RegisterPackage(&gossa.Package{
        Name: "spx",
        Path: "github.com/goplus/spx",
        Deps: map[string]string{
            "encoding/json":                       "json",
            "errors":                              "errors",
            "flag":                                "flag",
            "fmt":                                 "fmt",
            "github.com/goplus/spx/fs":            "fs",
            "github.com/goplus/spx/fs/asset":      "asset",
            "github.com/goplus/spx/fs/zip":        "zip",
            "github.com/goplus/spx/internal/anim": "anim",
            "github.com/goplus/spx/internal/audiorecord": "audiorecord",
            "github.com/goplus/spx/internal/camera":      "camera",
            "github.com/goplus/spx/internal/coroutine":   "coroutine",
            "github.com/goplus/spx/internal/effect":      "effect",
            "github.com/goplus/spx/internal/gdi":         "gdi",
            "github.com/goplus/spx/internal/gdi/clrutil": "clrutil",
            "github.com/goplus/spx/internal/gdi/font":    "font",
            "github.com/goplus/spx/internal/math32":      "math32",
            "github.com/goplus/spx/internal/tools":       "tools",
            "github.com/hajimehoshi/ebiten/v2":           "ebiten",
            "github.com/hajimehoshi/ebiten/v2/audio":     "audio",
            "github.com/pkg/errors":                      "errors",
            "github.com/qiniu/audio":                     "audio",
            "github.com/qiniu/audio/convert":             "convert",
            "github.com/qiniu/audio/mp3":                 "mp3",
            "github.com/qiniu/audio/wav":                 "wav",
            "github.com/qiniu/audio/wav/adpcm":           "adpcm",
            "golang.org/x/image/colornames":              "colornames",
            "golang.org/x/image/font":                    "font",
            "image":                                      "image",
            "image/color":                                "color",
            "image/jpeg":                                 "jpeg",
            "image/png":                                  "png",
            "io":                                         "io",
            "log":                                        "log",
            "math":                                       "math",
            "math/rand":                                  "rand",
            "os":                                         "os",
            "path":                                       "path",
            "path/filepath":                              "filepath",
            "reflect":                                    "reflect",
            "strconv":                                    "strconv",
            "strings":                                    "strings",
            "sync":                                       "sync",
            "sync/atomic":                                "atomic",
            "syscall":                                    "syscall",
            "time":                                       "time",
            "unsafe":                                     "unsafe",
        },
        Interfaces: map[string]reflect.Type{
            "Gamer": reflect.TypeOf((*q.Gamer)(nil)).Elem(),
            "Shape": reflect.TypeOf((*q.Shape)(nil)).Elem(),
        },
        NamedTypes: map[string]gossa.NamedType{
            "Camera":        {reflect.TypeOf((*q.Camera)(nil)).Elem(), "", "ChangeXYpos,On,SetXYpos,init,isWorldRange,render,screenToWorld,updateOnObj"},
            "Config":        {reflect.TypeOf((*q.Config)(nil)).Elem(), "", ""},
            "EffectKind":    {reflect.TypeOf((*q.EffectKind)(nil)).Elem(), "String", ""},
            "Game":          {reflect.TypeOf((*q.Game)(nil)).Elem(), "", "Answer,Ask,Broadcast__0,Broadcast__1,Broadcast__2,ChangeEffect,ChangeVolume,ClearSoundEffects,Draw,EraseAll,HideVar,KeyPressed,Layout,Loudness,MouseHitItem,MousePressed,MouseX,MouseY,NextScene,Play__0,PrevScene,ResetTimer,SceneIndex,SceneName,SetEffect,SetVolume,ShowVar,StartScene,StopAllSounds,Timer,Update,Username,Volume,Wait,activateShape,addClonedShape,addShape,addSpecialShape,addStageSprite,addStageSprites,currentTPS,doBroadcast,doFindSprite,doWhenLeftButtonDown,doWindowSize,doWorldSize,drawBackground,endLoad,eventLoop,findSprite,fireEvent,getItems,getMousePos,getSharedImgs,getTurtle,getWidth,goBackByLayers,handleEvent,initEventLoop,initGame,loadIndex,loadSound,loadSprite,movePen,objectPos,onDraw,onHit,removeShape,reset,runLoop,setStageMonitor,stampCostume,startLoad,startTick,touchingPoint,touchingSpriteBy,updateMousePos,windowSize_,worldSize_"},
            "List":          {reflect.TypeOf((*q.List)(nil)).Elem(), "", "Append,At,Contains,Delete,Init,InitFrom,Insert,Len,Set,String"},
            "MovingInfo":    {reflect.TypeOf((*q.MovingInfo)(nil)).Elem(), "", "Dx,Dy,StopMoving"},
            "RotationStyle": {reflect.TypeOf((*q.RotationStyle)(nil)).Elem(), "", ""},
            "Sound":         {reflect.TypeOf((*q.Sound)(nil)).Elem(), "", ""},
            "Sprite":        {reflect.TypeOf((*q.Sprite)(nil)).Elem(), "", "Animate,Ask,BounceOffEdge,Bounds,ChangeEffect,ChangeHeading,ChangePenColor,ChangePenHue,ChangePenShade,ChangePenSize,ChangeSize,ChangeXYpos,ChangeXpos,ChangeYpos,ClearGraphEffects,CostumeHeight,CostumeIndex,CostumeName,CostumeWidth,Destroy,Die,DistanceTo,Glide__0,Glide__1,GoBackLayers,Goto,GotoBack,GotoFront,Heading,Hide,HideVar,InitFrom,IsCloned,Move__0,Move__1,NextCostume,OnCloned__0,OnCloned__1,OnMoving__0,OnMoving__1,OnTouched__0,OnTouched__1,OnTouched__2,OnTouched__3,OnTouched__4,OnTouched__5,OnTurning__0,OnTurning__1,Parent,PenDown,PenUp,Pixel,PrevCostume,Say,SetCostume,SetDying,SetEffect,SetHeading,SetPenColor,SetPenHue,SetPenShade,SetPenSize,SetRotationStyle,SetSize,SetXYpos,SetXpos,SetYpos,Show,ShowVar,Size,Stamp,Step__0,Step__1,Step__2,Think,Touching,TouchingColor,Turn,TurnTo,Visible,Xpos,Ypos,checkTouchingScreen,doDestroy,doMoveTo,doMoveToForAnim,doStopSay,doTurnTogether,doUpdatePenColor,draw,fireTouched,fixWorldRange,getDrawInfo,getFromAnToForAni,getRotatedRect,getTrackPos,getXY,goAnimate,goMoveForward,hit,init,requireGreffUniforms,sayOrThink,setDirection,setPenHue,setPenShade,setPenWidth,touchPoint,touchRotatedRect,touchedColor_,touchingSprite,waitStopSay"},
            "StopKind":      {reflect.TypeOf((*q.StopKind)(nil)).Elem(), "", ""},
            "TurningInfo":   {reflect.TypeOf((*q.TurningInfo)(nil)).Elem(), "", "Dir"},
            "Value":         {reflect.TypeOf((*q.Value)(nil)).Elem(), "Equal,Float,Int,String", ""},
        },
        AliasTypes: map[string]reflect.Type{
            "Color":   reflect.TypeOf((*q.Color)(nil)).Elem(),
            "Key":     reflect.TypeOf((*q.Key)(nil)).Elem(),
            "Spriter": reflect.TypeOf((*q.Spriter)(nil)).Elem(),
        },
        Vars: map[string]reflect.Value{},
        Funcs: map[string]reflect.Value{
            "Exit__0":              reflect.ValueOf(q.Exit__0),
            "Exit__1":              reflect.ValueOf(q.Exit__1),
            "Gopt_Game_Main":       reflect.ValueOf(q.Gopt_Game_Main),
            "Gopt_Game_Reload":     reflect.ValueOf(q.Gopt_Game_Reload),
            "Gopt_Game_Run":        reflect.ValueOf(q.Gopt_Game_Run),
            "Gopt_Sprite_Clone__0": reflect.ValueOf(q.Gopt_Sprite_Clone__0),
            "Gopt_Sprite_Clone__1": reflect.ValueOf(q.Gopt_Sprite_Clone__1),
            "Iround":               reflect.ValueOf(q.Iround),
            "RGB":                  reflect.ValueOf(q.RGB),
            "RGBA":                 reflect.ValueOf(q.RGBA),
            "Rand__0":              reflect.ValueOf(q.Rand__0),
            "Rand__1":              reflect.ValueOf(q.Rand__1),
            "Sched":                reflect.ValueOf(q.Sched),
            "SchedNow":             reflect.ValueOf(q.SchedNow),
            "SetDebug":             reflect.ValueOf(q.SetDebug),
        },
        TypedConsts: map[string]gossa.TypedConst{
            "AllOtherScripts":      {reflect.TypeOf(q.AllOtherScripts), constant.MakeInt64(int64(q.AllOtherScripts))},
            "AllSprites":           {reflect.TypeOf(q.AllSprites), constant.MakeInt64(int64(q.AllSprites))},
            "BrightnessEffect":     {reflect.TypeOf(q.BrightnessEffect), constant.MakeInt64(int64(q.BrightnessEffect))},
            "ColorEffect":          {reflect.TypeOf(q.ColorEffect), constant.MakeInt64(int64(q.ColorEffect))},
            "Down":                 {reflect.TypeOf(q.Down), constant.MakeInt64(int64(q.Down))},
            "Edge":                 {reflect.TypeOf(q.Edge), constant.MakeInt64(int64(q.Edge))},
            "EdgeBottom":           {reflect.TypeOf(q.EdgeBottom), constant.MakeInt64(int64(q.EdgeBottom))},
            "EdgeLeft":             {reflect.TypeOf(q.EdgeLeft), constant.MakeInt64(int64(q.EdgeLeft))},
            "EdgeRight":            {reflect.TypeOf(q.EdgeRight), constant.MakeInt64(int64(q.EdgeRight))},
            "EdgeTop":              {reflect.TypeOf(q.EdgeTop), constant.MakeInt64(int64(q.EdgeTop))},
            "Key0":                 {reflect.TypeOf(q.Key0), constant.MakeInt64(int64(q.Key0))},
            "Key1":                 {reflect.TypeOf(q.Key1), constant.MakeInt64(int64(q.Key1))},
            "Key2":                 {reflect.TypeOf(q.Key2), constant.MakeInt64(int64(q.Key2))},
            "Key3":                 {reflect.TypeOf(q.Key3), constant.MakeInt64(int64(q.Key3))},
            "Key4":                 {reflect.TypeOf(q.Key4), constant.MakeInt64(int64(q.Key4))},
            "Key5":                 {reflect.TypeOf(q.Key5), constant.MakeInt64(int64(q.Key5))},
            "Key6":                 {reflect.TypeOf(q.Key6), constant.MakeInt64(int64(q.Key6))},
            "Key7":                 {reflect.TypeOf(q.Key7), constant.MakeInt64(int64(q.Key7))},
            "Key8":                 {reflect.TypeOf(q.Key8), constant.MakeInt64(int64(q.Key8))},
            "Key9":                 {reflect.TypeOf(q.Key9), constant.MakeInt64(int64(q.Key9))},
            "KeyA":                 {reflect.TypeOf(q.KeyA), constant.MakeInt64(int64(q.KeyA))},
            "KeyAlt":               {reflect.TypeOf(q.KeyAlt), constant.MakeInt64(int64(q.KeyAlt))},
            "KeyAny":               {reflect.TypeOf(q.KeyAny), constant.MakeInt64(int64(q.KeyAny))},
            "KeyApostrophe":        {reflect.TypeOf(q.KeyApostrophe), constant.MakeInt64(int64(q.KeyApostrophe))},
            "KeyB":                 {reflect.TypeOf(q.KeyB), constant.MakeInt64(int64(q.KeyB))},
            "KeyBackslash":         {reflect.TypeOf(q.KeyBackslash), constant.MakeInt64(int64(q.KeyBackslash))},
            "KeyBackspace":         {reflect.TypeOf(q.KeyBackspace), constant.MakeInt64(int64(q.KeyBackspace))},
            "KeyC":                 {reflect.TypeOf(q.KeyC), constant.MakeInt64(int64(q.KeyC))},
            "KeyCapsLock":          {reflect.TypeOf(q.KeyCapsLock), constant.MakeInt64(int64(q.KeyCapsLock))},
            "KeyComma":             {reflect.TypeOf(q.KeyComma), constant.MakeInt64(int64(q.KeyComma))},
            "KeyControl":           {reflect.TypeOf(q.KeyControl), constant.MakeInt64(int64(q.KeyControl))},
            "KeyD":                 {reflect.TypeOf(q.KeyD), constant.MakeInt64(int64(q.KeyD))},
            "KeyDelete":            {reflect.TypeOf(q.KeyDelete), constant.MakeInt64(int64(q.KeyDelete))},
            "KeyDown":              {reflect.TypeOf(q.KeyDown), constant.MakeInt64(int64(q.KeyDown))},
            "KeyE":                 {reflect.TypeOf(q.KeyE), constant.MakeInt64(int64(q.KeyE))},
            "KeyEnd":               {reflect.TypeOf(q.KeyEnd), constant.MakeInt64(int64(q.KeyEnd))},
            "KeyEnter":             {reflect.TypeOf(q.KeyEnter), constant.MakeInt64(int64(q.KeyEnter))},
            "KeyEqual":             {reflect.TypeOf(q.KeyEqual), constant.MakeInt64(int64(q.KeyEqual))},
            "KeyEscape":            {reflect.TypeOf(q.KeyEscape), constant.MakeInt64(int64(q.KeyEscape))},
            "KeyF":                 {reflect.TypeOf(q.KeyF), constant.MakeInt64(int64(q.KeyF))},
            "KeyF1":                {reflect.TypeOf(q.KeyF1), constant.MakeInt64(int64(q.KeyF1))},
            "KeyF10":               {reflect.TypeOf(q.KeyF10), constant.MakeInt64(int64(q.KeyF10))},
            "KeyF11":               {reflect.TypeOf(q.KeyF11), constant.MakeInt64(int64(q.KeyF11))},
            "KeyF12":               {reflect.TypeOf(q.KeyF12), constant.MakeInt64(int64(q.KeyF12))},
            "KeyF2":                {reflect.TypeOf(q.KeyF2), constant.MakeInt64(int64(q.KeyF2))},
            "KeyF3":                {reflect.TypeOf(q.KeyF3), constant.MakeInt64(int64(q.KeyF3))},
            "KeyF4":                {reflect.TypeOf(q.KeyF4), constant.MakeInt64(int64(q.KeyF4))},
            "KeyF5":                {reflect.TypeOf(q.KeyF5), constant.MakeInt64(int64(q.KeyF5))},
            "KeyF6":                {reflect.TypeOf(q.KeyF6), constant.MakeInt64(int64(q.KeyF6))},
            "KeyF7":                {reflect.TypeOf(q.KeyF7), constant.MakeInt64(int64(q.KeyF7))},
            "KeyF8":                {reflect.TypeOf(q.KeyF8), constant.MakeInt64(int64(q.KeyF8))},
            "KeyF9":                {reflect.TypeOf(q.KeyF9), constant.MakeInt64(int64(q.KeyF9))},
            "KeyG":                 {reflect.TypeOf(q.KeyG), constant.MakeInt64(int64(q.KeyG))},
            "KeyGraveAccent":       {reflect.TypeOf(q.KeyGraveAccent), constant.MakeInt64(int64(q.KeyGraveAccent))},
            "KeyH":                 {reflect.TypeOf(q.KeyH), constant.MakeInt64(int64(q.KeyH))},
            "KeyHome":              {reflect.TypeOf(q.KeyHome), constant.MakeInt64(int64(q.KeyHome))},
            "KeyI":                 {reflect.TypeOf(q.KeyI), constant.MakeInt64(int64(q.KeyI))},
            "KeyInsert":            {reflect.TypeOf(q.KeyInsert), constant.MakeInt64(int64(q.KeyInsert))},
            "KeyJ":                 {reflect.TypeOf(q.KeyJ), constant.MakeInt64(int64(q.KeyJ))},
            "KeyK":                 {reflect.TypeOf(q.KeyK), constant.MakeInt64(int64(q.KeyK))},
            "KeyKP0":               {reflect.TypeOf(q.KeyKP0), constant.MakeInt64(int64(q.KeyKP0))},
            "KeyKP1":               {reflect.TypeOf(q.KeyKP1), constant.MakeInt64(int64(q.KeyKP1))},
            "KeyKP2":               {reflect.TypeOf(q.KeyKP2), constant.MakeInt64(int64(q.KeyKP2))},
            "KeyKP3":               {reflect.TypeOf(q.KeyKP3), constant.MakeInt64(int64(q.KeyKP3))},
            "KeyKP4":               {reflect.TypeOf(q.KeyKP4), constant.MakeInt64(int64(q.KeyKP4))},
            "KeyKP5":               {reflect.TypeOf(q.KeyKP5), constant.MakeInt64(int64(q.KeyKP5))},
            "KeyKP6":               {reflect.TypeOf(q.KeyKP6), constant.MakeInt64(int64(q.KeyKP6))},
            "KeyKP7":               {reflect.TypeOf(q.KeyKP7), constant.MakeInt64(int64(q.KeyKP7))},
            "KeyKP8":               {reflect.TypeOf(q.KeyKP8), constant.MakeInt64(int64(q.KeyKP8))},
            "KeyKP9":               {reflect.TypeOf(q.KeyKP9), constant.MakeInt64(int64(q.KeyKP9))},
            "KeyKPDecimal":         {reflect.TypeOf(q.KeyKPDecimal), constant.MakeInt64(int64(q.KeyKPDecimal))},
            "KeyKPDivide":          {reflect.TypeOf(q.KeyKPDivide), constant.MakeInt64(int64(q.KeyKPDivide))},
            "KeyKPEnter":           {reflect.TypeOf(q.KeyKPEnter), constant.MakeInt64(int64(q.KeyKPEnter))},
            "KeyKPEqual":           {reflect.TypeOf(q.KeyKPEqual), constant.MakeInt64(int64(q.KeyKPEqual))},
            "KeyKPMultiply":        {reflect.TypeOf(q.KeyKPMultiply), constant.MakeInt64(int64(q.KeyKPMultiply))},
            "KeyKPSubtract":        {reflect.TypeOf(q.KeyKPSubtract), constant.MakeInt64(int64(q.KeyKPSubtract))},
            "KeyL":                 {reflect.TypeOf(q.KeyL), constant.MakeInt64(int64(q.KeyL))},
            "KeyLeft":              {reflect.TypeOf(q.KeyLeft), constant.MakeInt64(int64(q.KeyLeft))},
            "KeyLeftBracket":       {reflect.TypeOf(q.KeyLeftBracket), constant.MakeInt64(int64(q.KeyLeftBracket))},
            "KeyM":                 {reflect.TypeOf(q.KeyM), constant.MakeInt64(int64(q.KeyM))},
            "KeyMax":               {reflect.TypeOf(q.KeyMax), constant.MakeInt64(int64(q.KeyMax))},
            "KeyMenu":              {reflect.TypeOf(q.KeyMenu), constant.MakeInt64(int64(q.KeyMenu))},
            "KeyMinus":             {reflect.TypeOf(q.KeyMinus), constant.MakeInt64(int64(q.KeyMinus))},
            "KeyN":                 {reflect.TypeOf(q.KeyN), constant.MakeInt64(int64(q.KeyN))},
            "KeyNumLock":           {reflect.TypeOf(q.KeyNumLock), constant.MakeInt64(int64(q.KeyNumLock))},
            "KeyO":                 {reflect.TypeOf(q.KeyO), constant.MakeInt64(int64(q.KeyO))},
            "KeyP":                 {reflect.TypeOf(q.KeyP), constant.MakeInt64(int64(q.KeyP))},
            "KeyPageDown":          {reflect.TypeOf(q.KeyPageDown), constant.MakeInt64(int64(q.KeyPageDown))},
            "KeyPageUp":            {reflect.TypeOf(q.KeyPageUp), constant.MakeInt64(int64(q.KeyPageUp))},
            "KeyPause":             {reflect.TypeOf(q.KeyPause), constant.MakeInt64(int64(q.KeyPause))},
            "KeyPeriod":            {reflect.TypeOf(q.KeyPeriod), constant.MakeInt64(int64(q.KeyPeriod))},
            "KeyPrintScreen":       {reflect.TypeOf(q.KeyPrintScreen), constant.MakeInt64(int64(q.KeyPrintScreen))},
            "KeyQ":                 {reflect.TypeOf(q.KeyQ), constant.MakeInt64(int64(q.KeyQ))},
            "KeyR":                 {reflect.TypeOf(q.KeyR), constant.MakeInt64(int64(q.KeyR))},
            "KeyRight":             {reflect.TypeOf(q.KeyRight), constant.MakeInt64(int64(q.KeyRight))},
            "KeyRightBracket":      {reflect.TypeOf(q.KeyRightBracket), constant.MakeInt64(int64(q.KeyRightBracket))},
            "KeyS":                 {reflect.TypeOf(q.KeyS), constant.MakeInt64(int64(q.KeyS))},
            "KeyScrollLock":        {reflect.TypeOf(q.KeyScrollLock), constant.MakeInt64(int64(q.KeyScrollLock))},
            "KeySemicolon":         {reflect.TypeOf(q.KeySemicolon), constant.MakeInt64(int64(q.KeySemicolon))},
            "KeyShift":             {reflect.TypeOf(q.KeyShift), constant.MakeInt64(int64(q.KeyShift))},
            "KeySlash":             {reflect.TypeOf(q.KeySlash), constant.MakeInt64(int64(q.KeySlash))},
            "KeySpace":             {reflect.TypeOf(q.KeySpace), constant.MakeInt64(int64(q.KeySpace))},
            "KeyT":                 {reflect.TypeOf(q.KeyT), constant.MakeInt64(int64(q.KeyT))},
            "KeyTab":               {reflect.TypeOf(q.KeyTab), constant.MakeInt64(int64(q.KeyTab))},
            "KeyU":                 {reflect.TypeOf(q.KeyU), constant.MakeInt64(int64(q.KeyU))},
            "KeyUp":                {reflect.TypeOf(q.KeyUp), constant.MakeInt64(int64(q.KeyUp))},
            "KeyV":                 {reflect.TypeOf(q.KeyV), constant.MakeInt64(int64(q.KeyV))},
            "KeyW":                 {reflect.TypeOf(q.KeyW), constant.MakeInt64(int64(q.KeyW))},
            "KeyX":                 {reflect.TypeOf(q.KeyX), constant.MakeInt64(int64(q.KeyX))},
            "KeyY":                 {reflect.TypeOf(q.KeyY), constant.MakeInt64(int64(q.KeyY))},
            "KeyZ":                 {reflect.TypeOf(q.KeyZ), constant.MakeInt64(int64(q.KeyZ))},
            "Left":                 {reflect.TypeOf(q.Left), constant.MakeInt64(int64(q.Left))},
            "Mouse":                {reflect.TypeOf(q.Mouse), constant.MakeInt64(int64(q.Mouse))},
            "Next":                 {reflect.TypeOf(q.Next), constant.MakeInt64(int64(q.Next))},
            "OtherScriptsInSprite": {reflect.TypeOf(q.OtherScriptsInSprite), constant.MakeInt64(int64(q.OtherScriptsInSprite))},
            "Prev":                 {reflect.TypeOf(q.Prev), constant.MakeInt64(int64(q.Prev))},
            "Right":                {reflect.TypeOf(q.Right), constant.MakeInt64(int64(q.Right))},
            "ThisScript":           {reflect.TypeOf(q.ThisScript), constant.MakeInt64(int64(q.ThisScript))},
            "ThisSprite":           {reflect.TypeOf(q.ThisSprite), constant.MakeInt64(int64(q.ThisSprite))},
            "Up":                   {reflect.TypeOf(q.Up), constant.MakeInt64(int64(q.Up))},
        },
        UntypedConsts: map[string]gossa.UntypedConst{
            "All":          {"untyped int", constant.MakeInt64(int64(q.All))},
            "DbgFlagAll":   {"untyped int", constant.MakeInt64(int64(q.DbgFlagAll))},
            "DbgFlagEvent": {"untyped int", constant.MakeInt64(int64(q.DbgFlagEvent))},
            "DbgFlagInstr": {"untyped int", constant.MakeInt64(int64(q.DbgFlagInstr))},
            "DbgFlagLoad":  {"untyped int", constant.MakeInt64(int64(q.DbgFlagLoad))},
            "GopPackage":   {"untyped bool", constant.MakeBool(bool(q.GopPackage))},
            "Gop_sched":    {"untyped string", constant.MakeString(string(q.Gop_sched))},
            "Invalid":      {"untyped int", constant.MakeInt64(int64(q.Invalid))},
            "Last":         {"untyped int", constant.MakeInt64(int64(q.Last))},
            "LeftRight":    {"untyped int", constant.MakeInt64(int64(q.LeftRight))},
            "None":         {"untyped int", constant.MakeInt64(int64(q.None))},
            "Normal":       {"untyped int", constant.MakeInt64(int64(q.Normal))},
            "Random":       {"untyped int", constant.MakeInt64(int64(q.Random))},
        },
    })
}

ispx 的 methods 设置

cannot alloc method 3522 > 256, import _ "github.com/goplus/reflectx/icall/icall[2^n]"

fatal error: runtime: text offset base pointer out of ranges

github.com/goplus/reflectx/icall/icall[2^n] 1024 2048 4096 。。。65536

$ go get github.com/goplus/reflectx/cmd/icall_gen

示例:生成 20000 个预分配表

//go:generate icall_gen -o icall.go -pkg main -size 20000

gossa 实现原理

Package ssa defines a representation of the elements of Go programs (packages, types, functions, variables and constants) using a static single-assignment (SSA) form intermediate representation (IR) for the bodies of functions.

gossa 执行流程

  1. 预注册导入包 imports -> reflect package -> register pkg

    import _ "github.com/goplus/gossa/pkg/fmt"

  2. Go+ Source -> Go Source

    import _ "github.com/goplus/gopbuild"

  3. Go Source -> ast.Package

  4. ast.Package -> types.Package

    查找和安装导入包 reflect package -> types.Package

    直接依赖包/间接依赖包 ( qexp 生成的导入包 )

  5. types.Package -> ssa.Program / ssa.Package

  6. gossa.Interp 加载 ssa.Program / ssa.Package

gossa.Interp 执行

gossa 改进空间,已知问题和解决方案

Go+ Playground

jsplay.goplus.org 实现

源码


   
module github.com/goplusjs/play

go 1.16

require (
    github.com/goplus/gop v1.0.33
    github.com/goplus/gossa v0.2.6
    github.com/goplus/gox v1.8.1
    github.com/goplus/reflectx v0.6.8
    github.com/goplusjs/gopherjs v1.2.5
    golang.org/x/tools v0.1.8
)

replace github.com/goplus/reflectx => github.com/goplusjs/reflectx v0.5.6

jsplay.goplus.org 引用库

练习

Thank you

七叶

2021.12.22