Skip to content

Commit 8e30f05

Browse files
working on getting the runner hooked up.
1 parent c8eeeb8 commit 8e30f05

File tree

6 files changed

+280
-297
lines changed

6 files changed

+280
-297
lines changed
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package common
22

3-
// Runner is the interface describing the functions to compile and run Go code
3+
// Runner is the interface describing the functions to compile Go code
44
// via GopherJS in the playground.
55
type Runner interface {
6-
76
// Preload asynchronously starts loading standard library packages needed
8-
// to run the given code. A running preload will be cancelled when Run,
9-
// Stop, or another Preload is called.
10-
Preload(code string)
7+
// to run the Go given code.
8+
Preload(goCode string)
9+
10+
// Compile asynchronously compiles the given Go code.
11+
// When done the the then callback is invoked with the resulting
12+
// JavaScript code or an error if the compilation failed.
13+
Compile(goCode string, then func(string, error))
1114

12-
// Run asynchronously compiles and runs the given code.
15+
// Run executes the given JavaScript code.
16+
//
17+
// TODO(grantnelson-wf): Currently, there is no way to cancel a previous Run.
1318
// If this is called again while a previous Run is still in progress,
14-
// the previous Run is cancelled.
15-
Run(code string)
19+
// the previous Run should be cancelled.
20+
Run(jsCode string)
1621
}

playground/internal/page/banner.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package page
22

33
import (
44
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
5+
"github.com/gopherjs/gopherjs/js"
56
)
67

78
func Banner(urlHash string, onRun, onFormat, onShare, onSnippetSelected react.Func) *react.Element {
@@ -48,3 +49,15 @@ func bannerComponent(props react.Props) *react.Element {
4849
),
4950
)
5051
}
52+
53+
func getDefaultToLightTheme() bool {
54+
return js.Global.Get(`window`).Call(`matchMedia`, `(prefers-color-scheme: light)`).Get(`matches`).Bool()
55+
}
56+
57+
func setDataTheme(lightTheme bool) {
58+
theme := `dark`
59+
if lightTheme {
60+
theme = `light`
61+
}
62+
js.Global.Get(`document`).Get(`documentElement`).Call(`setAttribute`, `data-theme`, theme)
63+
}
Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package page
22

33
import (
4-
"fmt"
5-
"strings"
64
"sync"
75

86
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
9-
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
107
"github.com/gopherjs/gopherjs.github.io/playground/internal/runner"
118
"github.com/gopherjs/gopherjs.github.io/playground/internal/snippets"
129
"github.com/gopherjs/gopherjs.github.io/playground/internal/undoRedo"
13-
"github.com/gopherjs/gopherjs/js"
1410
)
1511

1612
// globals are the global objects used by the react components.
@@ -50,59 +46,3 @@ func OnceValue[T any](init func() T) func() T {
5046
return value
5147
}
5248
}
53-
54-
func getTopWindow() *js.Object { return js.Global.Get(`window`).Get(`top`) }
55-
func getLocation() *js.Object { return getTopWindow().Get(`location`) }
56-
57-
func getUrlWithoutHash() string {
58-
href := getLocation().Get(`href`).String()
59-
index := strings.Index(href, `#`)
60-
if index == -1 {
61-
return href
62-
}
63-
return href[:index]
64-
}
65-
66-
func getUrlHash() string {
67-
return getLocation().Get(`hash`).String()
68-
}
69-
70-
func setUrlHash(hash string) {
71-
if hash != `` && !strings.HasPrefix(hash, `#`) {
72-
panic(fmt.Errorf(`invalid hash to set. Must be empty or start with a "#": %q`, hash))
73-
}
74-
75-
if history := getTopWindow().Get(`history`); history != js.Undefined {
76-
if pushState := history.Get(`pushState`); pushState != js.Undefined {
77-
newUrl := getUrlWithoutHash()
78-
if hash != `` {
79-
newUrl += hash
80-
}
81-
history.Call(`pushState`, nil, ``, newUrl)
82-
return
83-
}
84-
}
85-
86-
// Fallback to setting location.hash directly
87-
getLocation().Set(`hash`, hash)
88-
}
89-
90-
func addEventListener(event string, handler react.Func) {
91-
getTopWindow().Call(`addEventListener`, event, handler)
92-
}
93-
94-
func removeEventListener(event string, handler react.Func) {
95-
getTopWindow().Call(`removeEventListener`, event, handler)
96-
}
97-
98-
func getDefaultToLightTheme() bool {
99-
return js.Global.Get(`window`).Call(`matchMedia`, `(prefers-color-scheme: light)`).Get(`matches`).Bool()
100-
}
101-
102-
func setDataTheme(lightTheme bool) {
103-
theme := `dark`
104-
if lightTheme {
105-
theme = `light`
106-
}
107-
js.Global.Get(`document`).Get(`documentElement`).Call(`setAttribute`, `data-theme`, theme)
108-
}

playground/internal/page/playground.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package page
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/gopherjs/gopherjs/js"
58

69
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
@@ -26,6 +29,7 @@ func playgroundComponent(props react.Props) *react.Element {
2629

2730
asyncOpInProgress.SetCurrent(true)
2831
globals.SnippetsStore().Read(hash, func(snippet string, err error) {
32+
defer asyncOpInProgress.SetCurrent(false)
2933
o := Output(setOutput)
3034
o.Clear()
3135
if err != nil {
@@ -36,7 +40,6 @@ func playgroundComponent(props react.Props) *react.Element {
3640
}
3741
// even on error, set the code so the default code is shown.
3842
setCode.Invoke(snippet)
39-
asyncOpInProgress.SetCurrent(false)
4043
})
4144
}, []any{setCode, setShareHash, setOutput, asyncOpInProgress})
4245

@@ -85,6 +88,7 @@ func playgroundComponent(props react.Props) *react.Element {
8588

8689
asyncOpInProgress.SetCurrent(true)
8790
globals.SnippetsStore().Write(code, func(hash string, err error) {
91+
defer asyncOpInProgress.SetCurrent(false)
8892
output := Output(setOutput)
8993
output.Clear()
9094
if err != nil {
@@ -93,7 +97,6 @@ func playgroundComponent(props react.Props) *react.Element {
9397
return
9498
}
9599
setShareHash.Invoke(hash)
96-
asyncOpInProgress.SetCurrent(false)
97100
})
98101
}, []any{code, setOutput, setShareHash, asyncOpInProgress})
99102

@@ -119,9 +122,22 @@ func playgroundComponent(props react.Props) *react.Element {
119122
println("Save key pressed") // TODO(grantnelson-wf): Implement by running format or just do nothing?
120123
}, []any{})
121124

125+
// TODO(grantnelson-wf): Implement a periodic read of the code after a change to preload packages.
126+
122127
onRun := react.UseCallback(func() {
123-
println("Run clicked") // TODO(grantnelson-wf): Implement
124-
}, []any{})
128+
asyncOpInProgress.SetCurrent(true)
129+
// TODO(grantnelson-wf): Finish implementing and debugging.
130+
globals.Runner().Compile(code, func(jsCode string, err error) {
131+
defer asyncOpInProgress.SetCurrent(false)
132+
output := Output(setOutput)
133+
output.Clear()
134+
if err != nil {
135+
output.AddError(err)
136+
return
137+
}
138+
globals.Runner().Run(jsCode)
139+
})
140+
}, []any{code, setOutput, asyncOpInProgress})
125141

126142
onFormat := react.UseCallback(func(fmtImports bool) {
127143
println("Format clicked", fmtImports) // TODO(grantnelson-wf): Implement
@@ -137,3 +153,47 @@ func playgroundComponent(props react.Props) *react.Element {
137153
),
138154
)
139155
}
156+
157+
func getTopWindow() *js.Object { return js.Global.Get(`window`).Get(`top`) }
158+
func getLocation() *js.Object { return getTopWindow().Get(`location`) }
159+
160+
func getUrlWithoutHash() string {
161+
href := getLocation().Get(`href`).String()
162+
index := strings.Index(href, `#`)
163+
if index == -1 {
164+
return href
165+
}
166+
return href[:index]
167+
}
168+
169+
func getUrlHash() string {
170+
return getLocation().Get(`hash`).String()
171+
}
172+
173+
func setUrlHash(hash string) {
174+
if hash != `` && !strings.HasPrefix(hash, `#`) {
175+
panic(fmt.Errorf(`invalid hash to set. Must be empty or start with a "#": %q`, hash))
176+
}
177+
178+
if history := getTopWindow().Get(`history`); history != js.Undefined {
179+
if pushState := history.Get(`pushState`); pushState != js.Undefined {
180+
newUrl := getUrlWithoutHash()
181+
if hash != `` {
182+
newUrl += hash
183+
}
184+
history.Call(`pushState`, nil, ``, newUrl)
185+
return
186+
}
187+
}
188+
189+
// Fallback to setting location.hash directly
190+
getLocation().Set(`hash`, hash)
191+
}
192+
193+
func addEventListener(event string, handler react.Func) {
194+
getTopWindow().Call(`addEventListener`, event, handler)
195+
}
196+
197+
func removeEventListener(event string, handler react.Func) {
198+
getTopWindow().Call(`removeEventListener`, event, handler)
199+
}

playground/internal/runner/runner.go

Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import (
2020
const pseudoFileName = `prog.go`
2121

2222
type runner struct {
23-
cache *packageCache
24-
output common.Output
23+
cache *packageCache
2524
}
2625

2726
func New(fetcher common.Fetcher) common.Runner {
@@ -30,24 +29,9 @@ func New(fetcher common.Fetcher) common.Runner {
3029
}
3130
}
3231

33-
// SetOutput sets the output to write errors to.
34-
func (r *runner) SetOutput(output common.Output) {
35-
r.output = output
36-
}
37-
38-
func (r *runner) writeError(err error) {
39-
if r.output != nil {
40-
r.output.AddError(err)
41-
} else {
42-
fmt.Printf("Runner error: %v\n", err)
43-
}
44-
}
45-
46-
// Preload asynchronously starts loading standard library packages needed
47-
// to run the given code.
48-
func (r *runner) Preload(code string) {
32+
func (r *runner) Preload(goCode string) {
4933
fileSet := token.NewFileSet()
50-
file, err := parser.ParseFile(fileSet, pseudoFileName, code, parser.ImportsOnly)
34+
file, err := parser.ParseFile(fileSet, pseudoFileName, goCode, parser.ImportsOnly)
5135
if err != nil {
5236
// Ignore errors here. They will be reported during actual compilation.
5337
return
@@ -74,15 +58,15 @@ func (r *runner) preloadImports(srcs *sources.Sources) {
7458
}
7559
}
7660

77-
// Run compiles and runs the given code.
78-
// If this is called again while a previous Run is still in progress,
79-
// the previous Run is cancelled.
80-
func (r *runner) Run(code string) {
61+
func (r *runner) Compile(goCode string, then func(string, error)) {
62+
go func() { then(r.syncCompile(goCode)) }()
63+
}
64+
65+
func (r *runner) syncCompile(goCode string) (string, error) {
8166
fileSet := token.NewFileSet()
82-
file, err := parser.ParseFile(fileSet, pseudoFileName, code, parser.ParseComments)
67+
file, err := parser.ParseFile(fileSet, pseudoFileName, goCode, parser.ParseComments)
8368
if err != nil {
84-
r.writeError(err)
85-
return
69+
return ``, err
8670
}
8771

8872
root := &sources.Sources{
@@ -96,24 +80,24 @@ func (r *runner) Run(code string) {
9680
// and we can synchronously wait for them later during actual compilation.
9781
r.preloadImports(root)
9882

99-
allSources, ok := r.collectAllSources(root)
100-
if !ok {
101-
return // Errors have already been reported.
83+
allSources, err := r.collectAllSources(root)
84+
if err != nil {
85+
return ``, err
10286
}
10387
archives, err := r.prepareAndCompilePackages(allSources)
10488
if err != nil {
105-
r.writeError(err)
106-
return
89+
return ``, err
10790
}
10891

10992
jsCode := r.write(archives)
110-
r.eval(jsCode)
93+
return jsCode, nil
11194
}
11295

113-
func (r *runner) collectAllSources(root *sources.Sources) ([]*sources.Sources, bool) {
96+
func (r *runner) collectAllSources(root *sources.Sources) ([]*sources.Sources, error) {
11497
allSrcs := map[string]*sources.Sources{}
115-
var collect func(srcs *sources.Sources) bool
116-
collect = func(srcs *sources.Sources) bool {
98+
99+
var collect func(srcs *sources.Sources) error
100+
collect = func(srcs *sources.Sources) error {
117101
allSrcs[srcs.ImportPath] = srcs
118102
for _, path := range srcs.UnresolvedImports() {
119103
if _, has := allSrcs[path]; has {
@@ -123,24 +107,23 @@ func (r *runner) collectAllSources(root *sources.Sources) ([]*sources.Sources, b
123107
srcs, _, err := r.cache.Load(path)
124108
if err != nil {
125109
// Failed to load an import.
126-
r.writeError(fmt.Errorf(`failed to load package %q: %w`, path, err))
127-
return false
110+
return fmt.Errorf(`failed to load package %q: %w`, path, err)
128111
}
129-
if !collect(srcs) {
130-
return false
112+
if err := collect(srcs); err != nil {
113+
return err
131114
}
132115
}
133-
return true
116+
return nil
134117
}
135-
if !collect(root) {
136-
return nil, false
118+
if err := collect(root); err != nil {
119+
return nil, err
137120
}
138121

139122
sourcesSlice := make([]*sources.Sources, 0, len(allSrcs))
140123
for _, srcs := range allSrcs {
141124
sourcesSlice = append(sourcesSlice, srcs)
142125
}
143-
return sourcesSlice, true
126+
return sourcesSlice, nil
144127
}
145128

146129
func (r *runner) sourcesForImport(path, _ string) (*sources.Sources, error) {
@@ -180,7 +163,7 @@ func (r *runner) write(allPkgs []*compiler.Archive) string {
180163
return jsCode.String()
181164
}
182165

183-
func (r *runner) eval(jsCode string) {
166+
func (r *runner) Run(jsCode string) {
184167
js.Global.Set("$checkForDeadlock", true)
185168
js.Global.Call("eval", js.InternalObject(jsCode))
186169
}

0 commit comments

Comments
 (0)