Skip to content

Commit

Permalink
Add scripting
Browse files Browse the repository at this point in the history
  • Loading branch information
elgopher committed Aug 6, 2023
1 parent 0f0e765 commit 85f608e
Show file tree
Hide file tree
Showing 29 changed files with 1,293 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
go: [ 1.18 ]
go: [ 1.20 ]
env:
DISPLAY: ':99.0'
steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Pi is under development. Only limited functionality is provided. API is not stab
## How to get started?

1. Install dependencies
* [Go 1.18+](https://go.dev/dl/)
* [Go 1.20+](https://go.dev/dl/)
* If not on Windows, please install additional dependencies for [Linux](docs/install-linux.md) or [macOS](docs/install-macos.md).
2. Try examples from [examples](examples) directory.
3. Create a new game using provided [Github template](https://github.com/elgopher/pi-template).
Expand Down
6 changes: 6 additions & 0 deletions devtools/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package devtools

import (
"fmt"

"github.com/elgopher/pi"
"github.com/elgopher/pi/devtools/internal/snapshot"
"github.com/elgopher/pi/devtools/internal/terminal"
)

var (
Expand All @@ -17,10 +20,13 @@ func pauseGame() {
gamePaused = true
timeWhenPaused = pi.TimeSeconds
snapshot.Take()
terminal.StartReadingCommands()
}

func resumeGame() {
gamePaused = false
pi.TimeSeconds = timeWhenPaused
snapshot.Draw()
fmt.Println("Game resumed")
terminal.StopReadingCommandsFromStdin()
}
2 changes: 1 addition & 1 deletion devtools/internal/inspector/measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (m *Measure) Update() {
case pi.MouseBtnp(pi.MouseLeft) && !distance.measuring:
distance.measuring = true
distance.startX, distance.startY = x, y
fmt.Printf("Measuring started at (%d, %d)\n", x, y)
fmt.Printf("\nMeasuring started at (%d, %d)\n", x, y)
case !pi.MouseBtn(pi.MouseLeft) && distance.measuring:
distance.measuring = false
dist, width, height := calcDistance()
Expand Down
186 changes: 186 additions & 0 deletions devtools/internal/interpreter/interpreter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package interpreter

import (
"bufio"
"errors"
"fmt"
"go/build"
"os"
"os/exec"
"reflect"
"runtime"
"strings"

"github.com/traefik/yaegi/interp"

"github.com/elgopher/pi/devtools/internal/interpreter/lib"
"github.com/elgopher/pi/devtools/internal/snapshot"
"github.com/elgopher/pi/devtools/internal/terminal"
)

func ExportFunc(name string, f any) {
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: reflect.ValueOf(f),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting function %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`) // TODO Redeclaration?
if err != nil {
panic(fmt.Sprintf("problem exporting function %s: %s", name, err))
}
}

func ExportVar(name string, v any) {
value := reflect.ValueOf(v)
if value.Kind() != reflect.Ptr {
panic(fmt.Sprintf("ExportVar: you must pass pointer to variable '%s', but you passed %T. Please add '&' in front.", name, v))
}
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: value.Elem(),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting variable %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`)
if err != nil {
panic(fmt.Sprintf("problem exporting variable %s: %s", name, err))
}
}

// TODO Better read name from T
func ExportType[T any](name string) {
var nilValue *T
err := interpreter.Use(interp.Exports{
"main/main": map[string]reflect.Value{
name: reflect.ValueOf(nilValue),
},
})
if err != nil {
panic(fmt.Sprintf("problem exporting type %s: %s", name, err))
}

_, err = interpreter.Eval(`import . "main"`)
if err != nil {
panic(fmt.Sprintf("problem exporting type %s: %s", name, err))
}
}

var interpreter *interp.Interpreter

func init() {
interpreter = interp.New(interp.Options{
GoPath: gopath(), // if GoPath is set then Yaegi does not complain about setting GOPATH.
})
err := interpreter.Use(lib.Symbols)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("problem using Go interpreter symbols: %s", err))
}

_, err = interpreter.Eval(`
import (
"bytes"
"fmt"
"math"
"math/rand"
"sort"
"strconv"
"strings"
"github.com/elgopher/pi"
"github.com/elgopher/pi/font"
"github.com/elgopher/pi/image"
"github.com/elgopher/pi/key"
"github.com/elgopher/pi/snap"
"github.com/elgopher/pi/state"
)
`)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("importing interpreter packages failed: %s", err))
}
}

func gopath() string {
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = build.Default.GOPATH
}
return gopath
}

func EvaluateNextCommand() {
select {
case cmd := <-terminal.Commands:
defer func() {
terminal.CommandExecuted <- struct{}{}
}()
defer func() {
err := recover()
if err != nil {
fmt.Println("panic when running Yaegi", err)
}
}()
if helpCmd := strings.Trim(cmd, " "); strings.HasPrefix(helpCmd, "help ") || helpCmd == "help" {
printHelp(strings.Trim(strings.TrimLeft(helpCmd, "help"), " "))
} else {
runGoCode(cmd)
}
default:
return
}
}

func printHelp(topic string) {
switch topic {
case "":
println("Here you can write Go code. It will be run immediately in the running game.")
default:
if goDoc(topic) {
return
}
}
}

func goDoc(symbol string) bool {
command := exec.Command("go", "doc", "-all", symbol)
command.Stdout = bufio.NewWriter(os.Stdout)
if err := command.Run(); err != nil {
var exitErr *exec.ExitError
if isExitErr := errors.As(err, &exitErr); isExitErr && exitErr.ExitCode() == 1 {
fmt.Println("no help found")
return true
}
fmt.Println("problem getting help:", err)
}
return false
}

func runGoCode(source string) {
res, err := interpreter.Eval(source)
if err != nil {
fmt.Println(err)
}
snapshot.Take()

if res.IsValid() {
if res.Type().Kind() != reflect.Func {
kind := res.Type().Kind()
if kind == reflect.Struct {
structName := res.Type().PkgPath() + "." + res.Type().Name()
structName = structName[strings.LastIndex(structName, "/")+1:]
fmt.Printf("%s: %+v\n", structName, res)
} else {
fmt.Printf("%s: %+v\n", kind, res)
}
} else {
name := runtime.FuncForPC(res.Pointer()).Name()
printHelp(name)
}
}
}
15 changes: 15 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-font.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-image.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-key.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions devtools/internal/interpreter/lib/github_com-elgopher-pi-snap.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 85f608e

Please sign in to comment.