├── .gitignore ├── README.md ├── cmd ├── compile.wasm ├── gofmt.wasm └── link.wasm ├── experimental ├── generics │ ├── README.md │ ├── cmd │ │ ├── compile.wasm │ │ ├── gofmt.wasm │ │ ├── link.wasm │ │ └── preprocess.wasm │ ├── index.html │ ├── prebuilt │ │ ├── internal │ │ │ ├── bytealg.a │ │ │ └── cpu.a │ │ ├── runtime.a │ │ └── runtime │ │ │ └── internal │ │ │ ├── atomic.a │ │ │ ├── math.a │ │ │ └── sys.a │ ├── preprocessor │ │ ├── .gitignore │ │ ├── astcopy │ │ │ └── astcopy.go │ │ ├── main.go │ │ └── testdata │ │ │ ├── contracts.go2 │ │ │ ├── function-values.go2 │ │ │ ├── functions.go2 │ │ │ ├── nested.go2 │ │ │ ├── nested_types.go2 │ │ │ ├── playground-default.go2 │ │ │ └── types.go2 │ └── wasm_exec.js └── try-builtin │ ├── README.md │ ├── cmd │ ├── compile.wasm │ ├── gofmt.wasm │ └── link.wasm │ ├── index.html │ ├── prebuilt │ ├── internal │ │ ├── bytealg.a │ │ └── cpu.a │ ├── runtime.a │ └── runtime │ │ └── internal │ │ ├── atomic.a │ │ ├── math.a │ │ └── sys.a │ └── wasm_exec.js ├── index.html ├── jquery-linedtextarea.js ├── main.go ├── playground.js ├── prebuilt ├── internal │ ├── bytealg.a │ └── cpu.a ├── runtime.a └── runtime │ └── internal │ ├── atomic.a │ ├── math.a │ └── sys.a ├── sharing.js ├── style.css └── wasm_exec.js /.gitignore: -------------------------------------------------------------------------------- 1 | /main 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasm-go-playground 2 | 3 | This is the Go compiler ("gc") compiled for WASM, running in your browser! It can be used to run a simple playground, à la [play.golang.org](https://play.golang.org/) entirely in your browser! 4 | 5 | You can try it out here: https://ccbrown.github.io/wasm-go-playground 6 | 7 | #### ⚠️ Important ⚠️ 8 | 9 | * Safari works, but is unbearably slow. **Chrome or Firefox for desktop is highly recommended.** 10 | * Imports other than "runtime" are not supported. Making them work would be straightforward, but I'm not compelled to spend time on it right now since this probably has no practical uses. 11 | 12 | ## Experimental Playgrounds 13 | 14 | One potential use-case for this is making compiler changes easily and freely available for people to try out. 15 | 16 | If you're interested in trying out the `try` [proposal](https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md), you can do so here: https://ccbrown.github.io/wasm-go-playground/experimental/try-builtin/ 17 | 18 | If you're interested in trying out generics [draft design](https://go.googlesource.com/proposal/+/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-contracts.md), you can do so here: https://ccbrown.github.io/wasm-go-playground/experimental/generics/ 19 | 20 | ## Code 21 | 22 | * ./cmd – These are Go commands compiled for WASM. They were all produced by running commands such as `GOOS=js GOARCH=wasm go build .` from the Go source directories. 23 | * ./experimental – This directory contains experimental playgrounds, for testing out modified compilers. 24 | * ./prebuilt – These are prebuilt runtime WASM files. These were produced by copying them from Go's cache after compiling anything for WASM. 25 | * . – The top level directory contains all the static files for the in-browser Go playground. Most of the files are lightly modified copies of bits and pieces from [play.golang.org](https://play.golang.org/). The most substantial work here is in index.html and wasm_exec.js as wasm_exec.js needed a virtual filesystem implementation. 26 | -------------------------------------------------------------------------------- /cmd/compile.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/cmd/compile.wasm -------------------------------------------------------------------------------- /cmd/gofmt.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/cmd/gofmt.wasm -------------------------------------------------------------------------------- /cmd/link.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/cmd/link.wasm -------------------------------------------------------------------------------- /experimental/generics/README.md: -------------------------------------------------------------------------------- 1 | # generics 2 | 3 | This is an experimental playground for the generics draft design: 4 | 5 | https://go.googlesource.com/proposal/+/4a54a00950b56dd0096482d0edae46969d7432a6/design/go2draft-contracts.md 6 | 7 | It is based on the prototype source translation tool here: 8 | 9 | https://go-review.googlesource.com/c/go/+/187317 10 | 11 | You can play with it here: 12 | 13 | https://ccbrown.github.io/wasm-go-playground/experimental/generics/ 14 | -------------------------------------------------------------------------------- /experimental/generics/cmd/compile.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/cmd/compile.wasm -------------------------------------------------------------------------------- /experimental/generics/cmd/gofmt.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/cmd/gofmt.wasm -------------------------------------------------------------------------------- /experimental/generics/cmd/link.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/cmd/link.wasm -------------------------------------------------------------------------------- /experimental/generics/cmd/preprocess.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/cmd/preprocess.wasm -------------------------------------------------------------------------------- /experimental/generics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The WebAssembly Go Playground 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 139 | 140 | 141 | 150 |
151 | 192 |
193 |
194 | 195 | 196 | -------------------------------------------------------------------------------- /experimental/generics/prebuilt/internal/bytealg.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/internal/bytealg.a -------------------------------------------------------------------------------- /experimental/generics/prebuilt/internal/cpu.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/internal/cpu.a -------------------------------------------------------------------------------- /experimental/generics/prebuilt/runtime.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/runtime.a -------------------------------------------------------------------------------- /experimental/generics/prebuilt/runtime/internal/atomic.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/runtime/internal/atomic.a -------------------------------------------------------------------------------- /experimental/generics/prebuilt/runtime/internal/math.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/runtime/internal/math.a -------------------------------------------------------------------------------- /experimental/generics/prebuilt/runtime/internal/sys.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/generics/prebuilt/runtime/internal/sys.a -------------------------------------------------------------------------------- /experimental/generics/preprocessor/.gitignore: -------------------------------------------------------------------------------- 1 | /preprocessor 2 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/astcopy/astcopy.go: -------------------------------------------------------------------------------- 1 | // From https://github.com/go-toolsmith/astcopy/blob/master/astcopy.go with slight edits. 2 | package astcopy 3 | 4 | import ( 5 | "go/ast" 6 | ) 7 | 8 | // Node returns x node deep copy. 9 | // Copy of nil argument is nil. 10 | func Node(x ast.Node) ast.Node { 11 | return copyNode(x) 12 | } 13 | 14 | // NodeList returns xs node slice deep copy. 15 | // Copy of nil argument is nil. 16 | func NodeList(xs []ast.Node) []ast.Node { 17 | if xs == nil { 18 | return nil 19 | } 20 | cp := make([]ast.Node, len(xs)) 21 | for i := range xs { 22 | cp[i] = copyNode(xs[i]) 23 | } 24 | return cp 25 | } 26 | 27 | // Expr returns x expression deep copy. 28 | // Copy of nil argument is nil. 29 | func Expr(x ast.Expr) ast.Expr { 30 | return copyExpr(x) 31 | } 32 | 33 | // ExprList returns xs expression slice deep copy. 34 | // Copy of nil argument is nil. 35 | func ExprList(xs []ast.Expr) []ast.Expr { 36 | if xs == nil { 37 | return nil 38 | } 39 | cp := make([]ast.Expr, len(xs)) 40 | for i := range xs { 41 | cp[i] = copyExpr(xs[i]) 42 | } 43 | return cp 44 | } 45 | 46 | // Stmt returns x statement deep copy. 47 | // Copy of nil argument is nil. 48 | func Stmt(x ast.Stmt) ast.Stmt { 49 | return copyStmt(x) 50 | } 51 | 52 | // StmtList returns xs statement slice deep copy. 53 | // Copy of nil argument is nil. 54 | func StmtList(xs []ast.Stmt) []ast.Stmt { 55 | if xs == nil { 56 | return nil 57 | } 58 | cp := make([]ast.Stmt, len(xs)) 59 | for i := range xs { 60 | cp[i] = copyStmt(xs[i]) 61 | } 62 | return cp 63 | } 64 | 65 | // Decl returns x declaration deep copy. 66 | // Copy of nil argument is nil. 67 | func Decl(x ast.Decl) ast.Decl { 68 | return copyDecl(x) 69 | } 70 | 71 | // DeclList returns xs declaration slice deep copy. 72 | // Copy of nil argument is nil. 73 | func DeclList(xs []ast.Decl) []ast.Decl { 74 | if xs == nil { 75 | return nil 76 | } 77 | cp := make([]ast.Decl, len(xs)) 78 | for i := range xs { 79 | cp[i] = copyDecl(xs[i]) 80 | } 81 | return cp 82 | } 83 | 84 | // BadExpr returns x deep copy. 85 | // Copy of nil argument is nil. 86 | func BadExpr(x *ast.BadExpr) *ast.BadExpr { 87 | if x == nil { 88 | return nil 89 | } 90 | cp := *x 91 | return &cp 92 | } 93 | 94 | // Ident returns x deep copy. 95 | // Copy of nil argument is nil. 96 | func Ident(x *ast.Ident) *ast.Ident { 97 | // XXX: Don't copy idents. We need them to stay the same. 98 | return x 99 | } 100 | 101 | // IdentList returns xs identifier slice deep copy. 102 | // Copy of nil argument is nil. 103 | func IdentList(xs []*ast.Ident) []*ast.Ident { 104 | if xs == nil { 105 | return nil 106 | } 107 | cp := make([]*ast.Ident, len(xs)) 108 | for i := range xs { 109 | cp[i] = Ident(xs[i]) 110 | } 111 | return cp 112 | } 113 | 114 | // Ellipsis returns x deep copy. 115 | // Copy of nil argument is nil. 116 | func Ellipsis(x *ast.Ellipsis) *ast.Ellipsis { 117 | if x == nil { 118 | return nil 119 | } 120 | cp := *x 121 | cp.Elt = copyExpr(x.Elt) 122 | return &cp 123 | } 124 | 125 | // BasicLit returns x deep copy. 126 | // Copy of nil argument is nil. 127 | func BasicLit(x *ast.BasicLit) *ast.BasicLit { 128 | if x == nil { 129 | return nil 130 | } 131 | cp := *x 132 | return &cp 133 | } 134 | 135 | // FuncLit returns x deep copy. 136 | // Copy of nil argument is nil. 137 | func FuncLit(x *ast.FuncLit) *ast.FuncLit { 138 | if x == nil { 139 | return nil 140 | } 141 | cp := *x 142 | cp.Type = FuncType(x.Type) 143 | cp.Body = BlockStmt(x.Body) 144 | return &cp 145 | } 146 | 147 | // CompositeLit returns x deep copy. 148 | // Copy of nil argument is nil. 149 | func CompositeLit(x *ast.CompositeLit) *ast.CompositeLit { 150 | if x == nil { 151 | return nil 152 | } 153 | cp := *x 154 | cp.Type = copyExpr(x.Type) 155 | cp.Elts = ExprList(x.Elts) 156 | return &cp 157 | } 158 | 159 | // ParenExpr returns x deep copy. 160 | // Copy of nil argument is nil. 161 | func ParenExpr(x *ast.ParenExpr) *ast.ParenExpr { 162 | if x == nil { 163 | return nil 164 | } 165 | cp := *x 166 | cp.X = copyExpr(x.X) 167 | return &cp 168 | } 169 | 170 | // SelectorExpr returns x deep copy. 171 | // Copy of nil argument is nil. 172 | func SelectorExpr(x *ast.SelectorExpr) *ast.SelectorExpr { 173 | if x == nil { 174 | return nil 175 | } 176 | cp := *x 177 | cp.X = copyExpr(x.X) 178 | cp.Sel = Ident(x.Sel) 179 | return &cp 180 | } 181 | 182 | // IndexExpr returns x deep copy. 183 | // Copy of nil argument is nil. 184 | func IndexExpr(x *ast.IndexExpr) *ast.IndexExpr { 185 | if x == nil { 186 | return nil 187 | } 188 | cp := *x 189 | cp.X = copyExpr(x.X) 190 | cp.Index = copyExpr(x.Index) 191 | return &cp 192 | } 193 | 194 | // SliceExpr returns x deep copy. 195 | // Copy of nil argument is nil. 196 | func SliceExpr(x *ast.SliceExpr) *ast.SliceExpr { 197 | if x == nil { 198 | return nil 199 | } 200 | cp := *x 201 | cp.X = copyExpr(x.X) 202 | cp.Low = copyExpr(x.Low) 203 | cp.High = copyExpr(x.High) 204 | cp.Max = copyExpr(x.Max) 205 | return &cp 206 | } 207 | 208 | // TypeAssertExpr returns x deep copy. 209 | // Copy of nil argument is nil. 210 | func TypeAssertExpr(x *ast.TypeAssertExpr) *ast.TypeAssertExpr { 211 | if x == nil { 212 | return nil 213 | } 214 | cp := *x 215 | cp.X = copyExpr(x.X) 216 | cp.Type = copyExpr(x.Type) 217 | return &cp 218 | } 219 | 220 | // CallExpr returns x deep copy. 221 | // Copy of nil argument is nil. 222 | func CallExpr(x *ast.CallExpr) *ast.CallExpr { 223 | if x == nil { 224 | return nil 225 | } 226 | cp := *x 227 | cp.Fun = copyExpr(x.Fun) 228 | cp.Args = ExprList(x.Args) 229 | return &cp 230 | } 231 | 232 | // StarExpr returns x deep copy. 233 | // Copy of nil argument is nil. 234 | func StarExpr(x *ast.StarExpr) *ast.StarExpr { 235 | if x == nil { 236 | return nil 237 | } 238 | cp := *x 239 | cp.X = copyExpr(x.X) 240 | return &cp 241 | } 242 | 243 | // UnaryExpr returns x deep copy. 244 | // Copy of nil argument is nil. 245 | func UnaryExpr(x *ast.UnaryExpr) *ast.UnaryExpr { 246 | if x == nil { 247 | return nil 248 | } 249 | cp := *x 250 | cp.X = copyExpr(x.X) 251 | return &cp 252 | } 253 | 254 | // BinaryExpr returns x deep copy. 255 | // Copy of nil argument is nil. 256 | func BinaryExpr(x *ast.BinaryExpr) *ast.BinaryExpr { 257 | if x == nil { 258 | return nil 259 | } 260 | cp := *x 261 | cp.X = copyExpr(x.X) 262 | cp.Y = copyExpr(x.Y) 263 | return &cp 264 | } 265 | 266 | // KeyValueExpr returns x deep copy. 267 | // Copy of nil argument is nil. 268 | func KeyValueExpr(x *ast.KeyValueExpr) *ast.KeyValueExpr { 269 | if x == nil { 270 | return nil 271 | } 272 | cp := *x 273 | cp.Key = copyExpr(x.Key) 274 | cp.Value = copyExpr(x.Value) 275 | return &cp 276 | } 277 | 278 | // ArrayType returns x deep copy. 279 | // Copy of nil argument is nil. 280 | func ArrayType(x *ast.ArrayType) *ast.ArrayType { 281 | if x == nil { 282 | return nil 283 | } 284 | cp := *x 285 | cp.Len = copyExpr(x.Len) 286 | cp.Elt = copyExpr(x.Elt) 287 | return &cp 288 | } 289 | 290 | // StructType returns x deep copy. 291 | // Copy of nil argument is nil. 292 | func StructType(x *ast.StructType) *ast.StructType { 293 | if x == nil { 294 | return nil 295 | } 296 | cp := *x 297 | cp.Fields = FieldList(x.Fields) 298 | return &cp 299 | } 300 | 301 | // Field returns x deep copy. 302 | // Copy of nil argument is nil. 303 | func Field(x *ast.Field) *ast.Field { 304 | if x == nil { 305 | return nil 306 | } 307 | cp := *x 308 | cp.Names = IdentList(x.Names) 309 | cp.Type = copyExpr(x.Type) 310 | cp.Tag = BasicLit(x.Tag) 311 | cp.Doc = CommentGroup(x.Doc) 312 | cp.Comment = CommentGroup(x.Comment) 313 | return &cp 314 | } 315 | 316 | // FieldList returns x deep copy. 317 | // Copy of nil argument is nil. 318 | func FieldList(x *ast.FieldList) *ast.FieldList { 319 | if x == nil { 320 | return nil 321 | } 322 | cp := *x 323 | if x.List != nil { 324 | cp.List = make([]*ast.Field, len(x.List)) 325 | for i := range x.List { 326 | cp.List[i] = Field(x.List[i]) 327 | } 328 | } 329 | return &cp 330 | } 331 | 332 | // FuncType returns x deep copy. 333 | // Copy of nil argument is nil. 334 | func FuncType(x *ast.FuncType) *ast.FuncType { 335 | if x == nil { 336 | return nil 337 | } 338 | cp := *x 339 | cp.Params = FieldList(x.Params) 340 | cp.Results = FieldList(x.Results) 341 | return &cp 342 | } 343 | 344 | // InterfaceType returns x deep copy. 345 | // Copy of nil argument is nil. 346 | func InterfaceType(x *ast.InterfaceType) *ast.InterfaceType { 347 | if x == nil { 348 | return nil 349 | } 350 | cp := *x 351 | cp.Methods = FieldList(x.Methods) 352 | return &cp 353 | } 354 | 355 | // MapType returns x deep copy. 356 | // Copy of nil argument is nil. 357 | func MapType(x *ast.MapType) *ast.MapType { 358 | if x == nil { 359 | return nil 360 | } 361 | cp := *x 362 | cp.Key = copyExpr(x.Key) 363 | cp.Value = copyExpr(x.Value) 364 | return &cp 365 | } 366 | 367 | // ChanType returns x deep copy. 368 | // Copy of nil argument is nil. 369 | func ChanType(x *ast.ChanType) *ast.ChanType { 370 | if x == nil { 371 | return nil 372 | } 373 | cp := *x 374 | cp.Value = copyExpr(x.Value) 375 | return &cp 376 | } 377 | 378 | // BlockStmt returns x deep copy. 379 | // Copy of nil argument is nil. 380 | func BlockStmt(x *ast.BlockStmt) *ast.BlockStmt { 381 | if x == nil { 382 | return nil 383 | } 384 | cp := *x 385 | cp.List = StmtList(x.List) 386 | return &cp 387 | } 388 | 389 | // ImportSpec returns x deep copy. 390 | // Copy of nil argument is nil. 391 | func ImportSpec(x *ast.ImportSpec) *ast.ImportSpec { 392 | if x == nil { 393 | return nil 394 | } 395 | cp := *x 396 | cp.Name = Ident(x.Name) 397 | cp.Path = BasicLit(x.Path) 398 | cp.Doc = CommentGroup(x.Doc) 399 | cp.Comment = CommentGroup(x.Comment) 400 | return &cp 401 | } 402 | 403 | // ValueSpec returns x deep copy. 404 | // Copy of nil argument is nil. 405 | func ValueSpec(x *ast.ValueSpec) *ast.ValueSpec { 406 | if x == nil { 407 | return nil 408 | } 409 | cp := *x 410 | cp.Names = IdentList(x.Names) 411 | cp.Values = ExprList(x.Values) 412 | cp.Type = copyExpr(x.Type) 413 | cp.Doc = CommentGroup(x.Doc) 414 | cp.Comment = CommentGroup(x.Comment) 415 | return &cp 416 | } 417 | 418 | // TypeSpec returns x deep copy. 419 | // Copy of nil argument is nil. 420 | func TypeSpec(x *ast.TypeSpec) *ast.TypeSpec { 421 | if x == nil { 422 | return nil 423 | } 424 | cp := *x 425 | cp.Name = Ident(x.Name) 426 | cp.Type = copyExpr(x.Type) 427 | cp.Doc = CommentGroup(x.Doc) 428 | cp.Comment = CommentGroup(x.Comment) 429 | return &cp 430 | } 431 | 432 | // Spec returns x deep copy. 433 | // Copy of nil argument is nil. 434 | func Spec(x ast.Spec) ast.Spec { 435 | if x == nil { 436 | return nil 437 | } 438 | 439 | switch x := x.(type) { 440 | case *ast.ImportSpec: 441 | return ImportSpec(x) 442 | case *ast.ValueSpec: 443 | return ValueSpec(x) 444 | case *ast.TypeSpec: 445 | return TypeSpec(x) 446 | default: 447 | panic("unhandled spec") 448 | } 449 | } 450 | 451 | // SpecList returns xs spec slice deep copy. 452 | // Copy of nil argument is nil. 453 | func SpecList(xs []ast.Spec) []ast.Spec { 454 | if xs == nil { 455 | return nil 456 | } 457 | cp := make([]ast.Spec, len(xs)) 458 | for i := range xs { 459 | cp[i] = Spec(xs[i]) 460 | } 461 | return cp 462 | } 463 | 464 | // BadStmt returns x deep copy. 465 | // Copy of nil argument is nil. 466 | func BadStmt(x *ast.BadStmt) *ast.BadStmt { 467 | if x == nil { 468 | return nil 469 | } 470 | cp := *x 471 | return &cp 472 | } 473 | 474 | // DeclStmt returns x deep copy. 475 | // Copy of nil argument is nil. 476 | func DeclStmt(x *ast.DeclStmt) *ast.DeclStmt { 477 | if x == nil { 478 | return nil 479 | } 480 | cp := *x 481 | cp.Decl = copyDecl(x.Decl) 482 | return &cp 483 | } 484 | 485 | // EmptyStmt returns x deep copy. 486 | // Copy of nil argument is nil. 487 | func EmptyStmt(x *ast.EmptyStmt) *ast.EmptyStmt { 488 | if x == nil { 489 | return nil 490 | } 491 | cp := *x 492 | return &cp 493 | } 494 | 495 | // LabeledStmt returns x deep copy. 496 | // Copy of nil argument is nil. 497 | func LabeledStmt(x *ast.LabeledStmt) *ast.LabeledStmt { 498 | if x == nil { 499 | return nil 500 | } 501 | cp := *x 502 | cp.Label = Ident(x.Label) 503 | cp.Stmt = copyStmt(x.Stmt) 504 | return &cp 505 | } 506 | 507 | // ExprStmt returns x deep copy. 508 | // Copy of nil argument is nil. 509 | func ExprStmt(x *ast.ExprStmt) *ast.ExprStmt { 510 | if x == nil { 511 | return nil 512 | } 513 | cp := *x 514 | cp.X = copyExpr(x.X) 515 | return &cp 516 | } 517 | 518 | // SendStmt returns x deep copy. 519 | // Copy of nil argument is nil. 520 | func SendStmt(x *ast.SendStmt) *ast.SendStmt { 521 | if x == nil { 522 | return nil 523 | } 524 | cp := *x 525 | cp.Chan = copyExpr(x.Chan) 526 | cp.Value = copyExpr(x.Value) 527 | return &cp 528 | } 529 | 530 | // IncDecStmt returns x deep copy. 531 | // Copy of nil argument is nil. 532 | func IncDecStmt(x *ast.IncDecStmt) *ast.IncDecStmt { 533 | if x == nil { 534 | return nil 535 | } 536 | cp := *x 537 | cp.X = copyExpr(x.X) 538 | return &cp 539 | } 540 | 541 | // AssignStmt returns x deep copy. 542 | // Copy of nil argument is nil. 543 | func AssignStmt(x *ast.AssignStmt) *ast.AssignStmt { 544 | if x == nil { 545 | return nil 546 | } 547 | cp := *x 548 | cp.Lhs = ExprList(x.Lhs) 549 | cp.Rhs = ExprList(x.Rhs) 550 | return &cp 551 | } 552 | 553 | // GoStmt returns x deep copy. 554 | // Copy of nil argument is nil. 555 | func GoStmt(x *ast.GoStmt) *ast.GoStmt { 556 | if x == nil { 557 | return nil 558 | } 559 | cp := *x 560 | cp.Call = CallExpr(x.Call) 561 | return &cp 562 | } 563 | 564 | // DeferStmt returns x deep copy. 565 | // Copy of nil argument is nil. 566 | func DeferStmt(x *ast.DeferStmt) *ast.DeferStmt { 567 | if x == nil { 568 | return nil 569 | } 570 | cp := *x 571 | cp.Call = CallExpr(x.Call) 572 | return &cp 573 | } 574 | 575 | // ReturnStmt returns x deep copy. 576 | // Copy of nil argument is nil. 577 | func ReturnStmt(x *ast.ReturnStmt) *ast.ReturnStmt { 578 | if x == nil { 579 | return nil 580 | } 581 | cp := *x 582 | cp.Results = ExprList(x.Results) 583 | return &cp 584 | } 585 | 586 | // BranchStmt returns x deep copy. 587 | // Copy of nil argument is nil. 588 | func BranchStmt(x *ast.BranchStmt) *ast.BranchStmt { 589 | if x == nil { 590 | return nil 591 | } 592 | cp := *x 593 | cp.Label = Ident(x.Label) 594 | return &cp 595 | } 596 | 597 | // IfStmt returns x deep copy. 598 | // Copy of nil argument is nil. 599 | func IfStmt(x *ast.IfStmt) *ast.IfStmt { 600 | if x == nil { 601 | return nil 602 | } 603 | cp := *x 604 | cp.Init = copyStmt(x.Init) 605 | cp.Cond = copyExpr(x.Cond) 606 | cp.Body = BlockStmt(x.Body) 607 | cp.Else = copyStmt(x.Else) 608 | return &cp 609 | } 610 | 611 | // CaseClause returns x deep copy. 612 | // Copy of nil argument is nil. 613 | func CaseClause(x *ast.CaseClause) *ast.CaseClause { 614 | if x == nil { 615 | return nil 616 | } 617 | cp := *x 618 | cp.List = ExprList(x.List) 619 | cp.Body = StmtList(x.Body) 620 | return &cp 621 | } 622 | 623 | // SwitchStmt returns x deep copy. 624 | // Copy of nil argument is nil. 625 | func SwitchStmt(x *ast.SwitchStmt) *ast.SwitchStmt { 626 | if x == nil { 627 | return nil 628 | } 629 | cp := *x 630 | cp.Init = copyStmt(x.Init) 631 | cp.Tag = copyExpr(x.Tag) 632 | cp.Body = BlockStmt(x.Body) 633 | return &cp 634 | } 635 | 636 | // TypeSwitchStmt returns x deep copy. 637 | // Copy of nil argument is nil. 638 | func TypeSwitchStmt(x *ast.TypeSwitchStmt) *ast.TypeSwitchStmt { 639 | if x == nil { 640 | return nil 641 | } 642 | cp := *x 643 | cp.Init = copyStmt(x.Init) 644 | cp.Assign = copyStmt(x.Assign) 645 | cp.Body = BlockStmt(x.Body) 646 | return &cp 647 | } 648 | 649 | // CommClause returns x deep copy. 650 | // Copy of nil argument is nil. 651 | func CommClause(x *ast.CommClause) *ast.CommClause { 652 | if x == nil { 653 | return nil 654 | } 655 | cp := *x 656 | cp.Comm = copyStmt(x.Comm) 657 | cp.Body = StmtList(x.Body) 658 | return &cp 659 | } 660 | 661 | // SelectStmt returns x deep copy. 662 | // Copy of nil argument is nil. 663 | func SelectStmt(x *ast.SelectStmt) *ast.SelectStmt { 664 | if x == nil { 665 | return nil 666 | } 667 | cp := *x 668 | cp.Body = BlockStmt(x.Body) 669 | return &cp 670 | } 671 | 672 | // ForStmt returns x deep copy. 673 | // Copy of nil argument is nil. 674 | func ForStmt(x *ast.ForStmt) *ast.ForStmt { 675 | if x == nil { 676 | return nil 677 | } 678 | cp := *x 679 | cp.Init = copyStmt(x.Init) 680 | cp.Cond = copyExpr(x.Cond) 681 | cp.Post = copyStmt(x.Post) 682 | cp.Body = BlockStmt(x.Body) 683 | return &cp 684 | } 685 | 686 | // RangeStmt returns x deep copy. 687 | // Copy of nil argument is nil. 688 | func RangeStmt(x *ast.RangeStmt) *ast.RangeStmt { 689 | if x == nil { 690 | return nil 691 | } 692 | cp := *x 693 | cp.Key = copyExpr(x.Key) 694 | cp.Value = copyExpr(x.Value) 695 | cp.X = copyExpr(x.X) 696 | cp.Body = BlockStmt(x.Body) 697 | return &cp 698 | } 699 | 700 | // Comment returns x deep copy. 701 | // Copy of nil argument is nil. 702 | func Comment(x *ast.Comment) *ast.Comment { 703 | if x == nil { 704 | return nil 705 | } 706 | cp := *x 707 | return &cp 708 | } 709 | 710 | // CommentGroup returns x deep copy. 711 | // Copy of nil argument is nil. 712 | func CommentGroup(x *ast.CommentGroup) *ast.CommentGroup { 713 | if x == nil { 714 | return nil 715 | } 716 | cp := *x 717 | if x.List != nil { 718 | cp.List = make([]*ast.Comment, len(x.List)) 719 | for i := range x.List { 720 | cp.List[i] = Comment(x.List[i]) 721 | } 722 | } 723 | return &cp 724 | } 725 | 726 | // File returns x deep copy. 727 | // Copy of nil argument is nil. 728 | func File(x *ast.File) *ast.File { 729 | if x == nil { 730 | return nil 731 | } 732 | cp := *x 733 | cp.Doc = CommentGroup(x.Doc) 734 | cp.Name = Ident(x.Name) 735 | cp.Decls = DeclList(x.Decls) 736 | cp.Imports = make([]*ast.ImportSpec, len(x.Imports)) 737 | for i := range x.Imports { 738 | cp.Imports[i] = ImportSpec(x.Imports[i]) 739 | } 740 | cp.Unresolved = IdentList(x.Unresolved) 741 | cp.Comments = make([]*ast.CommentGroup, len(x.Comments)) 742 | for i := range x.Comments { 743 | cp.Comments[i] = CommentGroup(x.Comments[i]) 744 | } 745 | return &cp 746 | } 747 | 748 | // Package returns x deep copy. 749 | // Copy of nil argument is nil. 750 | func Package(x *ast.Package) *ast.Package { 751 | if x == nil { 752 | return nil 753 | } 754 | cp := *x 755 | cp.Files = make(map[string]*ast.File) 756 | for filename, f := range x.Files { 757 | cp.Files[filename] = f 758 | } 759 | return &cp 760 | } 761 | 762 | // BadDecl returns x deep copy. 763 | // Copy of nil argument is nil. 764 | func BadDecl(x *ast.BadDecl) *ast.BadDecl { 765 | if x == nil { 766 | return nil 767 | } 768 | cp := *x 769 | return &cp 770 | } 771 | 772 | // GenDecl returns x deep copy. 773 | // Copy of nil argument is nil. 774 | func GenDecl(x *ast.GenDecl) *ast.GenDecl { 775 | if x == nil { 776 | return nil 777 | } 778 | cp := *x 779 | cp.Specs = SpecList(x.Specs) 780 | cp.Doc = CommentGroup(x.Doc) 781 | return &cp 782 | } 783 | 784 | // FuncDecl returns x deep copy. 785 | // Copy of nil argument is nil. 786 | func FuncDecl(x *ast.FuncDecl) *ast.FuncDecl { 787 | if x == nil { 788 | return nil 789 | } 790 | cp := *x 791 | cp.Recv = FieldList(x.Recv) 792 | cp.Name = Ident(x.Name) 793 | cp.Type = FuncType(x.Type) 794 | cp.Body = BlockStmt(x.Body) 795 | cp.Doc = CommentGroup(x.Doc) 796 | return &cp 797 | } 798 | 799 | func copyNode(x ast.Node) ast.Node { 800 | switch x := x.(type) { 801 | case ast.Expr: 802 | return copyExpr(x) 803 | case ast.Stmt: 804 | return copyStmt(x) 805 | case ast.Decl: 806 | return copyDecl(x) 807 | 808 | case ast.Spec: 809 | return Spec(x) 810 | case *ast.FieldList: 811 | return FieldList(x) 812 | case *ast.Comment: 813 | return Comment(x) 814 | case *ast.CommentGroup: 815 | return CommentGroup(x) 816 | case *ast.File: 817 | return File(x) 818 | case *ast.Package: 819 | return Package(x) 820 | 821 | default: 822 | panic("unhandled node") 823 | } 824 | } 825 | 826 | func copyExpr(x ast.Expr) ast.Expr { 827 | if x == nil { 828 | return nil 829 | } 830 | 831 | switch x := x.(type) { 832 | case *ast.BadExpr: 833 | return BadExpr(x) 834 | case *ast.Ident: 835 | return Ident(x) 836 | case *ast.Ellipsis: 837 | return Ellipsis(x) 838 | case *ast.BasicLit: 839 | return BasicLit(x) 840 | case *ast.FuncLit: 841 | return FuncLit(x) 842 | case *ast.CompositeLit: 843 | return CompositeLit(x) 844 | case *ast.ParenExpr: 845 | return ParenExpr(x) 846 | case *ast.SelectorExpr: 847 | return SelectorExpr(x) 848 | case *ast.IndexExpr: 849 | return IndexExpr(x) 850 | case *ast.SliceExpr: 851 | return SliceExpr(x) 852 | case *ast.TypeAssertExpr: 853 | return TypeAssertExpr(x) 854 | case *ast.CallExpr: 855 | return CallExpr(x) 856 | case *ast.StarExpr: 857 | return StarExpr(x) 858 | case *ast.UnaryExpr: 859 | return UnaryExpr(x) 860 | case *ast.BinaryExpr: 861 | return BinaryExpr(x) 862 | case *ast.KeyValueExpr: 863 | return KeyValueExpr(x) 864 | case *ast.ArrayType: 865 | return ArrayType(x) 866 | case *ast.StructType: 867 | return StructType(x) 868 | case *ast.FuncType: 869 | return FuncType(x) 870 | case *ast.InterfaceType: 871 | return InterfaceType(x) 872 | case *ast.MapType: 873 | return MapType(x) 874 | case *ast.ChanType: 875 | return ChanType(x) 876 | 877 | default: 878 | panic("unhandled expr") 879 | } 880 | } 881 | 882 | func copyStmt(x ast.Stmt) ast.Stmt { 883 | if x == nil { 884 | return nil 885 | } 886 | 887 | switch x := x.(type) { 888 | case *ast.BadStmt: 889 | return BadStmt(x) 890 | case *ast.DeclStmt: 891 | return DeclStmt(x) 892 | case *ast.EmptyStmt: 893 | return EmptyStmt(x) 894 | case *ast.LabeledStmt: 895 | return LabeledStmt(x) 896 | case *ast.ExprStmt: 897 | return ExprStmt(x) 898 | case *ast.SendStmt: 899 | return SendStmt(x) 900 | case *ast.IncDecStmt: 901 | return IncDecStmt(x) 902 | case *ast.AssignStmt: 903 | return AssignStmt(x) 904 | case *ast.GoStmt: 905 | return GoStmt(x) 906 | case *ast.DeferStmt: 907 | return DeferStmt(x) 908 | case *ast.ReturnStmt: 909 | return ReturnStmt(x) 910 | case *ast.BranchStmt: 911 | return BranchStmt(x) 912 | case *ast.BlockStmt: 913 | return BlockStmt(x) 914 | case *ast.IfStmt: 915 | return IfStmt(x) 916 | case *ast.CaseClause: 917 | return CaseClause(x) 918 | case *ast.SwitchStmt: 919 | return SwitchStmt(x) 920 | case *ast.TypeSwitchStmt: 921 | return TypeSwitchStmt(x) 922 | case *ast.CommClause: 923 | return CommClause(x) 924 | case *ast.SelectStmt: 925 | return SelectStmt(x) 926 | case *ast.ForStmt: 927 | return ForStmt(x) 928 | case *ast.RangeStmt: 929 | return RangeStmt(x) 930 | 931 | default: 932 | panic("unhandled stmt") 933 | } 934 | } 935 | 936 | func copyDecl(x ast.Decl) ast.Decl { 937 | if x == nil { 938 | return nil 939 | } 940 | 941 | switch x := x.(type) { 942 | case *ast.BadDecl: 943 | return BadDecl(x) 944 | case *ast.GenDecl: 945 | return GenDecl(x) 946 | case *ast.FuncDecl: 947 | return FuncDecl(x) 948 | 949 | default: 950 | panic("unhandled decl") 951 | } 952 | } 953 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/go2go" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | source, err := ioutil.ReadFile(os.Args[1]) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | fout := os.Stdout 17 | if len(os.Args) > 2 { 18 | f, err := os.Create(os.Args[2]) 19 | if err != nil { 20 | panic(err) 21 | } 22 | defer f.Close() 23 | fout = f 24 | } 25 | 26 | importerTmpdir, err := ioutil.TempDir("", "go2go") 27 | if err != nil { 28 | panic(err) 29 | } 30 | defer os.RemoveAll(importerTmpdir) 31 | 32 | importer := go2go.NewImporter(importerTmpdir) 33 | 34 | buf, err := go2go.RewriteBuffer(importer, os.Args[1], source) 35 | if err != nil { 36 | fmt.Fprintf(os.Stderr, "%s", err) 37 | os.Exit(2) 38 | } 39 | 40 | if _, err := fout.Write(buf); err != nil { 41 | panic(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/contracts.go2: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file shows examples of contract declarations. 6 | // They are not type-checked at the moment. 7 | 8 | package p 9 | 10 | // A contract declaration, like any other declaration, 11 | // starts with a keyword, followed by the contract name 12 | // and the contract type parameters, and then the contract 13 | // specification. 14 | contract C(T) { 15 | T m() 16 | } 17 | 18 | // Contracts may be empty. 19 | contract Empty() {} 20 | 21 | // Contracts may be grouped. 22 | contract ( 23 | C1(T) {} 24 | C2(T) {} 25 | C3(T) {} 26 | ) 27 | 28 | // A contract specifies methods and types for each of the 29 | // type parameters it constrains. 30 | contract Stringer(T) { 31 | T String() string 32 | } 33 | 34 | contract Sequence(T) { 35 | T string, []byte 36 | } 37 | 38 | // Contracts may constrain multiple type parameters 39 | // in mutually recursive ways. 40 | contract G(Node, Edge) { 41 | Node Edges() []Edge 42 | Edge Nodes() (from Node, to Node) 43 | } 44 | 45 | type Graph (type Node, Edge G) struct { /* ... */ } 46 | 47 | func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) { panic("unimplemented") } 48 | 49 | func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { panic("unimplemented") } 50 | 51 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/function-values.go2: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func Nop (type T) (x T) T { 4 | return x 5 | } 6 | 7 | var _ = Nop(int) 8 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/functions.go2: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file shows some examples of type-parameterized functions. 6 | 7 | package p 8 | 9 | // Reverse is a function that takes a []T argument and 10 | // reverses that slice in place. 11 | func Reverse (type T) (list []T) { 12 | i := 0 13 | j := len(list)-1 14 | for i < j { 15 | list[i], list[j] = list[j], list[i] 16 | i++ 17 | j-- 18 | } 19 | } 20 | 21 | func _() { 22 | // Reverse can be called with an explicit type argument. 23 | Reverse(int)(nil) 24 | Reverse(string)([]string{"foo", "bar"}) 25 | Reverse(struct{x, y int})([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) 26 | 27 | // Since the type parameter is used for an incoming argument, 28 | // it can be inferred from the provided argument's type. 29 | Reverse([]string{"foo", "bar"}) 30 | Reverse([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) 31 | 32 | // But the incoming argument must have a type, even if it's a 33 | // default type. An untyped nil won't work. 34 | // Reverse(nil) // this won't type-check 35 | 36 | // A typed nil will work, though. 37 | Reverse([]int(nil)) 38 | } 39 | 40 | // Certain functions, such as the built-in `new` could be written using 41 | // type parameters. 42 | func new (type T) () *T { 43 | var x T 44 | return &x 45 | } 46 | 47 | // When calling our own new, we need to pass the type parameter 48 | // explicitly since there is no (value) argument from which the 49 | // result type could be inferred. We doen't try to infer the 50 | // result type from the assignment to keep things simple and 51 | // easy to understand. 52 | var _ = new(int)() 53 | var _ *float64 = new(float64)() // the result type is indeed *float64 54 | 55 | // A function may have multiple type parameters, of course. 56 | func foo (type A, B, C) (a A, b []B, c *C) B { 57 | // do something here 58 | return b[0] 59 | } 60 | 61 | // As before, we can pass type parameters explicitly. 62 | var s = foo(int, string, float64)(1, []string{"first"}, new(float64)()) 63 | 64 | // Or we can use type inference. 65 | var _ float64 = foo(42, []float64{1.0}, &s) 66 | 67 | // Type inference works in a straight-forward manner even 68 | // for variadic functions. 69 | func variadic(type A, B)(A, B, ...B) int { panic("unimplemented") } 70 | 71 | // var _ = variadic(1) // ERROR not enough arguments 72 | var _ = variadic(1, 2.3) 73 | var _ = variadic(1, 2.3, 3.4, 4.5) 74 | var _ = variadic(int, float64)(1, 2.3, 3.4, 4) 75 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/nested.go2: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func Foo (type T) (x T) int { 4 | return 1 5 | } 6 | 7 | func Bar (type U) (x U) int { 8 | return Foo(U)(x) 9 | } 10 | 11 | func Baz (type V) (x V) int { 12 | return Bar(V)(x) 13 | } 14 | 15 | func Recursive (type T) (x T) T { 16 | Recursive(int)(1) 17 | return x 18 | } 19 | 20 | var _ = Baz(1) 21 | var _ = Recursive(int)(1) 22 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/nested_types.go2: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Foo(type T) struct { 4 | bar Bar(T, T) 5 | } 6 | 7 | type Bar(type T, U) struct { 8 | v U 9 | } 10 | 11 | func main() { 12 | f := Foo(int){} 13 | println(f.bar.v) 14 | } 15 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/playground-default.go2: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // XXX: This is highly experimental. Don't expect correctness or stability. 4 | 5 | // Takes a slice of any type and reverses it. 6 | func Reverse (type T) (list []T) { 7 | i := 0 8 | j := len(list) - 1 9 | for i < j { 10 | list[i], list[j] = list[j], list[i] 11 | i++ 12 | j-- 13 | } 14 | } 15 | 16 | // Requires T to be a string or byte slice. 17 | // 18 | // Note that at the moment, contracts are not actually type-checked. 19 | contract Sequence(T) { 20 | T string, []byte 21 | } 22 | 23 | // Defines a tree where each node can hold a value that satisfies Sequence. 24 | type Tree (type T Sequence) struct { 25 | Left *Tree(T) 26 | Right *Tree(T) 27 | Value T 28 | } 29 | 30 | func main() { 31 | s := []string{"a", "b", "c"} 32 | Reverse(s) 33 | println(s[0], s[1], s[2]) 34 | 35 | tree := Tree(string){ 36 | Value: "foo", 37 | Left: &Tree(string){ 38 | Value: "bar", 39 | }, 40 | } 41 | println(tree.Left.Value) 42 | } 43 | -------------------------------------------------------------------------------- /experimental/generics/preprocessor/testdata/types.go2: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file shows some examples of type-parameterized types. 6 | 7 | package p 8 | 9 | // List is just what it says - a slice of E elements. 10 | type List(type E) []E 11 | 12 | // A parameterized (generic) type must always be instantiated 13 | // before it can be used to designate the type of a variable 14 | // (including a struct field, or function parameter); though 15 | // for the latter cases, the provided type may be another type 16 | // parameter. So: 17 | var _ List(byte) = []byte{} 18 | 19 | // A generic binary tree might be declared as follows. 20 | type Tree(type E) struct { 21 | left, right *Tree(E) 22 | payload E 23 | } 24 | 25 | // A simple instantiation of Tree: 26 | var root1 Tree(int) 27 | 28 | // The actual type parameter provided may be a parameterized 29 | // type itself: 30 | var root2 Tree(List(int)) 31 | 32 | // A couple of more complex examples. 33 | // Here, we need extra parentheses around the element type of the slices on the right 34 | // to resolve the parsing ambiguity between the conversion []List(int) and the slice 35 | // type with a parameterized elements type [](List(int)). 36 | var _ List(List(int)) = [](List(int)){} 37 | -------------------------------------------------------------------------------- /experimental/generics/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | if (typeof global !== "undefined") { 7 | // global already exists 8 | } else if (typeof window !== "undefined") { 9 | window.global = window; 10 | } else if (typeof self !== "undefined") { 11 | self.global = self; 12 | } else { 13 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 14 | } 15 | 16 | const encoder = new TextEncoder('utf-8'); 17 | const decoder = new TextDecoder('utf-8'); 18 | 19 | const filesystem = {}; 20 | 21 | let workingDirectory = '/'; 22 | 23 | let absPath = (path) => { 24 | if (path[0] == '/') { 25 | return path; 26 | } 27 | return workingDirectory + path.replace(/^\.\/?/, ''); 28 | }; 29 | 30 | global.readFromGoFilesystem = (path) => filesystem[absPath(path)]; 31 | global.writeToGoFilesystem = (path, content) => { 32 | if (typeof content === 'string') { 33 | filesystem[absPath(path)] = encoder.encode(content); 34 | } else { 35 | filesystem[absPath(path)] = content; 36 | } 37 | }; 38 | global.goStdout = (buf) => {}; 39 | global.goStderr = (buf) => {}; 40 | 41 | const openFiles = new Map(); 42 | let nextFd = 1000; 43 | 44 | let stat = (path, callback) => { 45 | let mode = 0; 46 | if (path === '/') { 47 | mode |= 0x80000000; 48 | } else if (filesystem[path] === undefined) { 49 | const err = new Error('no such file'); 50 | err.code = 'ENOENT'; 51 | callback(err); 52 | return; 53 | } 54 | callback(null, { 55 | mode, 56 | dev: 0, 57 | ino: 0, 58 | nlink: 0, 59 | uid: 0, 60 | gid: 0, 61 | rdev: 0, 62 | size: 0, 63 | blksize: 0, 64 | blocks: 0, 65 | atimeMs: 0, 66 | mtimeMs: 0, 67 | ctimeMs: 0, 68 | isDirectory: () => !!(mode & 0x80000000), 69 | }); 70 | }; 71 | 72 | const constants = { 73 | O_WRONLY: 1 << 0, 74 | O_RDWR: 1 << 1, 75 | O_CREAT: 1 << 2, 76 | O_TRUNC: 1 << 3, 77 | O_APPEND: 1 << 4, 78 | O_EXCL: 1 << 5, 79 | }; 80 | 81 | let outputBuf = ""; 82 | global.fs = { 83 | constants, 84 | writeSync(fd, buf) { 85 | if (fd === 1) { 86 | global.goStdout(buf); 87 | } else if (fd === 2) { 88 | global.goStderr(buf); 89 | } else { 90 | const file = openFiles[fd]; 91 | const source = filesystem[file.path]; 92 | let destLength = source.length + buf.length; 93 | if (file.offset < source.length) { 94 | destLength = file.offset + buf.length; 95 | if (destLength < source.length) { 96 | destLength = source.length; 97 | } 98 | } 99 | const dest = new Uint8Array(destLength); 100 | for (let i = 0; i < source.length; ++i) { 101 | dest[i] = source[i]; 102 | } 103 | for (let i = 0; i < buf.length; ++i) { 104 | dest[file.offset + i] = buf[i]; 105 | } 106 | openFiles[fd].offset += buf.length; 107 | filesystem[file.path] = dest; 108 | } 109 | }, 110 | write(fd, buf, offset, length, position, callback) { 111 | if (offset !== 0 || length !== buf.length) { 112 | throw new Error('write not fully implemented: ' + offset + ', ' + length + '/' + buf.length); 113 | } 114 | if (position !== null) { 115 | openFiles[fd].offset = position; 116 | } 117 | this.writeSync(fd, buf); 118 | callback(null, length); 119 | }, 120 | open(path, flags, mode, callback) { 121 | console.log('open(' + path + ', ' + mode + ')'); 122 | path = absPath(path); 123 | if (!filesystem[path]) { 124 | if (flags & constants.O_CREAT) { 125 | filesystem[path] = new Uint8Array(0); 126 | } else { 127 | const err = new Error('no such file'); 128 | err.code = 'ENOENT'; 129 | callback(err); 130 | return; 131 | } 132 | } 133 | if (flags & constants.O_TRUNC) { 134 | filesystem[path] = new Uint8Array(0); 135 | } 136 | const fd = nextFd++; 137 | openFiles[fd] = { 138 | offset: 0, 139 | path, 140 | }; 141 | callback(null, fd); 142 | }, 143 | read(fd, buffer, offset, length, position, callback) { 144 | if (offset !== 0) { 145 | throw new Error('read not fully implemented: ' + offset); 146 | } 147 | if (position !== null) { 148 | openFiles[fd].offset = position; 149 | } 150 | const file = openFiles[fd]; 151 | const source = filesystem[file.path]; 152 | let n = length; 153 | if (file.offset + length > source.length) { 154 | n = source.length - file.offset; 155 | } 156 | for (let i = 0; i < n; ++i) { 157 | buffer[i] = source[file.offset + i]; 158 | } 159 | openFiles[fd].offset += n; 160 | callback(null, n); 161 | }, 162 | mkdir(path, perm, callback) { 163 | console.log('mkdir(' + path + ', ' + perm + ')'); 164 | callback(null); 165 | }, 166 | close(fd, callback) { 167 | console.log('close(' + fd + ')'); 168 | openFiles.delete(fd); 169 | callback(null); 170 | }, 171 | fsync(fd, callback) { 172 | callback(null); 173 | }, 174 | unlink(path, callback) { 175 | console.log('unlink(' + path + ')'); 176 | callback(null); 177 | }, 178 | fstat(fd, callback) { 179 | console.log('fstat(' + fd + ')'); 180 | stat(openFiles[fd].path, callback); 181 | }, 182 | stat(path, callback) { 183 | console.log('stat(' + path + ')'); 184 | stat(absPath(path), callback); 185 | }, 186 | lstat(path, callback) { 187 | console.log('lstat(' + path + ')'); 188 | stat(absPath(path), callback); 189 | }, 190 | fchmod(fd, mode, callback) { 191 | console.log('fchmod(' + fd + ', ' + mode + ')'); 192 | callback(null); 193 | }, 194 | }; 195 | 196 | global.process = { 197 | pid: 1, 198 | cwd() { 199 | console.log('cwd()'); 200 | return workingDirectory; 201 | }, 202 | }; 203 | 204 | global.Go = class { 205 | constructor() { 206 | this.argv = ["js"]; 207 | this.env = {}; 208 | this.exit = (code) => { 209 | if (code !== 0) { 210 | console.warn("exit code:", code); 211 | } 212 | }; 213 | this._exitPromise = new Promise((resolve) => { 214 | this._resolveExitPromise = resolve; 215 | }); 216 | this._pendingEvent = null; 217 | this._scheduledTimeouts = new Map(); 218 | this._nextCallbackTimeoutID = 1; 219 | 220 | const setInt64 = (addr, v) => { 221 | this.mem.setUint32(addr + 0, v, true); 222 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 223 | } 224 | 225 | const getInt64 = (addr) => { 226 | const low = this.mem.getUint32(addr + 0, true); 227 | const high = this.mem.getInt32(addr + 4, true); 228 | return low + high * 4294967296; 229 | } 230 | 231 | const loadValue = (addr) => { 232 | const f = this.mem.getFloat64(addr, true); 233 | if (f === 0) { 234 | return undefined; 235 | } 236 | if (!isNaN(f)) { 237 | return f; 238 | } 239 | 240 | const id = this.mem.getUint32(addr, true); 241 | return this._values[id]; 242 | } 243 | 244 | const storeValue = (addr, v) => { 245 | const nanHead = 0x7FF80000; 246 | 247 | if (typeof v === "number") { 248 | if (isNaN(v)) { 249 | this.mem.setUint32(addr + 4, nanHead, true); 250 | this.mem.setUint32(addr, 0, true); 251 | return; 252 | } 253 | if (v === 0) { 254 | this.mem.setUint32(addr + 4, nanHead, true); 255 | this.mem.setUint32(addr, 1, true); 256 | return; 257 | } 258 | this.mem.setFloat64(addr, v, true); 259 | return; 260 | } 261 | 262 | switch (v) { 263 | case undefined: 264 | this.mem.setFloat64(addr, 0, true); 265 | return; 266 | case null: 267 | this.mem.setUint32(addr + 4, nanHead, true); 268 | this.mem.setUint32(addr, 2, true); 269 | return; 270 | case true: 271 | this.mem.setUint32(addr + 4, nanHead, true); 272 | this.mem.setUint32(addr, 3, true); 273 | return; 274 | case false: 275 | this.mem.setUint32(addr + 4, nanHead, true); 276 | this.mem.setUint32(addr, 4, true); 277 | return; 278 | } 279 | 280 | let id = this._ids.get(v); 281 | if (id === undefined) { 282 | id = this._idPool.pop(); 283 | if (id === undefined) { 284 | id = this._values.length; 285 | } 286 | this._values[id] = v; 287 | this._goRefCounts[id] = 0; 288 | this._ids.set(v, id); 289 | } 290 | this._goRefCounts[id]++; 291 | let typeFlag = 1; 292 | switch (typeof v) { 293 | case "string": 294 | typeFlag = 2; 295 | break; 296 | case "symbol": 297 | typeFlag = 3; 298 | break; 299 | case "function": 300 | typeFlag = 4; 301 | break; 302 | } 303 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 304 | this.mem.setUint32(addr, id, true); 305 | } 306 | 307 | const loadSlice = (addr) => { 308 | const array = getInt64(addr + 0); 309 | const len = getInt64(addr + 8); 310 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 311 | } 312 | 313 | const loadSliceOfValues = (addr) => { 314 | const array = getInt64(addr + 0); 315 | const len = getInt64(addr + 8); 316 | const a = new Array(len); 317 | for (let i = 0; i < len; i++) { 318 | a[i] = loadValue(array + i * 8); 319 | } 320 | return a; 321 | } 322 | 323 | const loadString = (addr) => { 324 | const saddr = getInt64(addr + 0); 325 | const len = getInt64(addr + 8); 326 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 327 | } 328 | 329 | const timeOrigin = Date.now() - performance.now(); 330 | this.importObject = { 331 | go: { 332 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 333 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 334 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 335 | // This changes the SP, thus we have to update the SP used by the imported function. 336 | 337 | // func wasmExit(code int32) 338 | "runtime.wasmExit": (sp) => { 339 | const code = this.mem.getInt32(sp + 8, true); 340 | this.exited = true; 341 | delete this._inst; 342 | delete this._values; 343 | delete this._goRefCounts; 344 | delete this._ids; 345 | delete this._idPool; 346 | this.exit(code); 347 | }, 348 | 349 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 350 | "runtime.wasmWrite": (sp) => { 351 | const fd = getInt64(sp + 8); 352 | const p = getInt64(sp + 16); 353 | const n = this.mem.getInt32(sp + 24, true); 354 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 355 | }, 356 | 357 | // func resetMemoryDataView() 358 | "runtime.resetMemoryDataView": (sp) => { 359 | this.mem = new DataView(this._inst.exports.mem.buffer); 360 | }, 361 | 362 | // func nanotime1() int64 363 | "runtime.nanotime1": (sp) => { 364 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 365 | }, 366 | 367 | // func nanotime() int64 368 | "runtime.nanotime": (sp) => { 369 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 370 | }, 371 | 372 | // func walltime1() (sec int64, nsec int32) 373 | "runtime.walltime1": (sp) => { 374 | const msec = (new Date).getTime(); 375 | setInt64(sp + 8, msec / 1000); 376 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 377 | }, 378 | 379 | // func walltime() (sec int64, nsec int32) 380 | "runtime.walltime": (sp) => { 381 | const msec = (new Date).getTime(); 382 | setInt64(sp + 8, msec / 1000); 383 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 384 | }, 385 | 386 | // func scheduleTimeoutEvent(delay int64) int32 387 | "runtime.scheduleTimeoutEvent": (sp) => { 388 | const id = this._nextCallbackTimeoutID; 389 | this._nextCallbackTimeoutID++; 390 | this._scheduledTimeouts.set(id, setTimeout( 391 | () => { 392 | this._resume(); 393 | while (this._scheduledTimeouts.has(id)) { 394 | // for some reason Go failed to register the timeout event, log and try again 395 | // (temporary workaround for https://github.com/golang/go/issues/28975) 396 | console.warn("scheduleTimeoutEvent: missed timeout event"); 397 | this._resume(); 398 | } 399 | }, 400 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 401 | )); 402 | this.mem.setInt32(sp + 16, id, true); 403 | }, 404 | 405 | // func clearTimeoutEvent(id int32) 406 | "runtime.clearTimeoutEvent": (sp) => { 407 | const id = this.mem.getInt32(sp + 8, true); 408 | clearTimeout(this._scheduledTimeouts.get(id)); 409 | this._scheduledTimeouts.delete(id); 410 | }, 411 | 412 | // func getRandomData(r []byte) 413 | "runtime.getRandomData": (sp) => { 414 | crypto.getRandomValues(loadSlice(sp + 8)); 415 | }, 416 | 417 | // func finalizeRef(v ref) 418 | "syscall/js.finalizeRef": (sp) => { 419 | const id = this.mem.getUint32(sp + 8, true); 420 | this._goRefCounts[id]--; 421 | if (this._goRefCounts[id] === 0) { 422 | const v = this._values[id]; 423 | this._values[id] = null; 424 | this._ids.delete(v); 425 | this._idPool.push(id); 426 | } 427 | }, 428 | 429 | // func stringVal(value string) ref 430 | "syscall/js.stringVal": (sp) => { 431 | storeValue(sp + 24, loadString(sp + 8)); 432 | }, 433 | 434 | // func valueGet(v ref, p string) ref 435 | "syscall/js.valueGet": (sp) => { 436 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 437 | sp = this._inst.exports.getsp(); // see comment above 438 | storeValue(sp + 32, result); 439 | }, 440 | 441 | // func valueSet(v ref, p string, x ref) 442 | "syscall/js.valueSet": (sp) => { 443 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 444 | }, 445 | 446 | // func valueDelete(v ref, p string) 447 | "syscall/js.valueDelete": (sp) => { 448 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 449 | }, 450 | 451 | // func valueIndex(v ref, i int) ref 452 | "syscall/js.valueIndex": (sp) => { 453 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 454 | }, 455 | 456 | // valueSetIndex(v ref, i int, x ref) 457 | "syscall/js.valueSetIndex": (sp) => { 458 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 459 | }, 460 | 461 | // func valueCall(v ref, m string, args []ref) (ref, bool) 462 | "syscall/js.valueCall": (sp) => { 463 | try { 464 | const v = loadValue(sp + 8); 465 | const m = Reflect.get(v, loadString(sp + 16)); 466 | const args = loadSliceOfValues(sp + 32); 467 | const result = Reflect.apply(m, v, args); 468 | sp = this._inst.exports.getsp(); // see comment above 469 | storeValue(sp + 56, result); 470 | this.mem.setUint8(sp + 64, 1); 471 | } catch (err) { 472 | storeValue(sp + 56, err); 473 | this.mem.setUint8(sp + 64, 0); 474 | } 475 | }, 476 | 477 | // func valueInvoke(v ref, args []ref) (ref, bool) 478 | "syscall/js.valueInvoke": (sp) => { 479 | try { 480 | const v = loadValue(sp + 8); 481 | const args = loadSliceOfValues(sp + 16); 482 | const result = Reflect.apply(v, undefined, args); 483 | sp = this._inst.exports.getsp(); // see comment above 484 | storeValue(sp + 40, result); 485 | this.mem.setUint8(sp + 48, 1); 486 | } catch (err) { 487 | storeValue(sp + 40, err); 488 | this.mem.setUint8(sp + 48, 0); 489 | } 490 | }, 491 | 492 | // func valueNew(v ref, args []ref) (ref, bool) 493 | "syscall/js.valueNew": (sp) => { 494 | try { 495 | const v = loadValue(sp + 8); 496 | const args = loadSliceOfValues(sp + 16); 497 | const result = Reflect.construct(v, args); 498 | sp = this._inst.exports.getsp(); // see comment above 499 | storeValue(sp + 40, result); 500 | this.mem.setUint8(sp + 48, 1); 501 | } catch (err) { 502 | storeValue(sp + 40, err); 503 | this.mem.setUint8(sp + 48, 0); 504 | } 505 | }, 506 | 507 | // func valueLength(v ref) int 508 | "syscall/js.valueLength": (sp) => { 509 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 510 | }, 511 | 512 | // valuePrepareString(v ref) (ref, int) 513 | "syscall/js.valuePrepareString": (sp) => { 514 | const str = encoder.encode(String(loadValue(sp + 8))); 515 | storeValue(sp + 16, str); 516 | setInt64(sp + 24, str.length); 517 | }, 518 | 519 | // valueLoadString(v ref, b []byte) 520 | "syscall/js.valueLoadString": (sp) => { 521 | const str = loadValue(sp + 8); 522 | loadSlice(sp + 16).set(str); 523 | }, 524 | 525 | // func valueInstanceOf(v ref, t ref) bool 526 | "syscall/js.valueInstanceOf": (sp) => { 527 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 528 | }, 529 | 530 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 531 | "syscall/js.copyBytesToGo": (sp) => { 532 | const dst = loadSlice(sp + 8); 533 | const src = loadValue(sp + 32); 534 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 535 | this.mem.setUint8(sp + 48, 0); 536 | return; 537 | } 538 | const toCopy = src.subarray(0, dst.length); 539 | dst.set(toCopy); 540 | setInt64(sp + 40, toCopy.length); 541 | this.mem.setUint8(sp + 48, 1); 542 | }, 543 | 544 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 545 | "syscall/js.copyBytesToJS": (sp) => { 546 | const dst = loadValue(sp + 8); 547 | const src = loadSlice(sp + 16); 548 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 549 | this.mem.setUint8(sp + 48, 0); 550 | return; 551 | } 552 | const toCopy = src.subarray(0, dst.length); 553 | dst.set(toCopy); 554 | setInt64(sp + 40, toCopy.length); 555 | this.mem.setUint8(sp + 48, 1); 556 | }, 557 | 558 | "debug": (value) => { 559 | console.log(value); 560 | }, 561 | } 562 | }; 563 | } 564 | 565 | async run(instance) { 566 | this._inst = instance; 567 | this.mem = new DataView(this._inst.exports.mem.buffer); 568 | this._values = [ // JS values that Go currently has references to, indexed by reference id 569 | NaN, 570 | 0, 571 | null, 572 | true, 573 | false, 574 | global, 575 | this, 576 | ]; 577 | this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 578 | this._ids = new Map(); // mapping from JS values to reference ids 579 | this._idPool = []; // unused ids that have been garbage collected 580 | this.exited = false; // whether the Go program has exited 581 | 582 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 583 | let offset = 4096; 584 | 585 | const strPtr = (str) => { 586 | const ptr = offset; 587 | const bytes = encoder.encode(str + "\0"); 588 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 589 | offset += bytes.length; 590 | if (offset % 8 !== 0) { 591 | offset += 8 - (offset % 8); 592 | } 593 | return ptr; 594 | }; 595 | 596 | const argc = this.argv.length; 597 | 598 | const argvPtrs = []; 599 | this.argv.forEach((arg) => { 600 | argvPtrs.push(strPtr(arg)); 601 | }); 602 | argvPtrs.push(0); 603 | 604 | const keys = Object.keys(this.env).sort(); 605 | keys.forEach((key) => { 606 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 607 | }); 608 | argvPtrs.push(0); 609 | 610 | const argv = offset; 611 | argvPtrs.forEach((ptr) => { 612 | this.mem.setUint32(offset, ptr, true); 613 | this.mem.setUint32(offset + 4, 0, true); 614 | offset += 8; 615 | }); 616 | 617 | this._inst.exports.run(argc, argv); 618 | if (this.exited) { 619 | this._resolveExitPromise(); 620 | } 621 | await this._exitPromise; 622 | } 623 | 624 | _resume() { 625 | if (this.exited) { 626 | throw new Error("Go program has already exited"); 627 | } 628 | this._inst.exports.resume(); 629 | if (this.exited) { 630 | this._resolveExitPromise(); 631 | } 632 | } 633 | 634 | _makeFuncWrapper(id) { 635 | const go = this; 636 | return function () { 637 | const event = { id: id, this: this, args: arguments }; 638 | go._pendingEvent = event; 639 | go._resume(); 640 | return event.result; 641 | }; 642 | } 643 | } 644 | 645 | if ( 646 | global.require && 647 | global.require.main === module && 648 | global.process && 649 | global.process.versions && 650 | !global.process.versions.electron 651 | ) { 652 | if (process.argv.length < 3) { 653 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 654 | process.exit(1); 655 | } 656 | 657 | const go = new Go(); 658 | go.argv = process.argv.slice(2); 659 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); 660 | go.exit = process.exit; 661 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 662 | process.on("exit", (code) => { // Node.js exits if no event handler is pending 663 | if (code === 0 && !go.exited) { 664 | // deadlock, make Go print error and stack traces 665 | go._pendingEvent = { id: 0 }; 666 | go._resume(); 667 | } 668 | }); 669 | return go.run(result.instance); 670 | }).catch((err) => { 671 | console.error(err); 672 | process.exit(1); 673 | }); 674 | } 675 | })(); 676 | -------------------------------------------------------------------------------- /experimental/try-builtin/README.md: -------------------------------------------------------------------------------- 1 | # try-builtin 2 | 3 | This is an experimental playground for this proposal: 4 | 5 | https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md 6 | 7 | You can play with it here: 8 | 9 | https://ccbrown.github.io/wasm-go-playground/experimental/try-builtin/ 10 | 11 | You can find the Go compiler changes that it uses here: 12 | 13 | https://github.com/ccbrown/go/pull/1 14 | -------------------------------------------------------------------------------- /experimental/try-builtin/cmd/compile.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/cmd/compile.wasm -------------------------------------------------------------------------------- /experimental/try-builtin/cmd/gofmt.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/cmd/gofmt.wasm -------------------------------------------------------------------------------- /experimental/try-builtin/cmd/link.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/cmd/link.wasm -------------------------------------------------------------------------------- /experimental/try-builtin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The WebAssembly Go Playground 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 154 | 155 | 156 | 166 |
167 | 188 |
189 |
190 | 191 | 192 | -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/internal/bytealg.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/internal/bytealg.a -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/internal/cpu.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/internal/cpu.a -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/runtime.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/runtime.a -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/runtime/internal/atomic.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/runtime/internal/atomic.a -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/runtime/internal/math.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/runtime/internal/math.a -------------------------------------------------------------------------------- /experimental/try-builtin/prebuilt/runtime/internal/sys.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/experimental/try-builtin/prebuilt/runtime/internal/sys.a -------------------------------------------------------------------------------- /experimental/try-builtin/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | if (typeof global !== "undefined") { 7 | // global already exists 8 | } else if (typeof window !== "undefined") { 9 | window.global = window; 10 | } else if (typeof self !== "undefined") { 11 | self.global = self; 12 | } else { 13 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 14 | } 15 | 16 | const encoder = new TextEncoder('utf-8'); 17 | const decoder = new TextDecoder('utf-8'); 18 | 19 | const filesystem = {}; 20 | 21 | let workingDirectory = '/'; 22 | 23 | let absPath = (path) => { 24 | if (path[0] == '/') { 25 | return path; 26 | } 27 | return workingDirectory + path.replace(/^\.\/?/, ''); 28 | }; 29 | 30 | global.readFromGoFilesystem = (path) => filesystem[absPath(path)]; 31 | global.writeToGoFilesystem = (path, content) => { 32 | if (typeof content === 'string') { 33 | filesystem[absPath(path)] = encoder.encode(content); 34 | } else { 35 | filesystem[absPath(path)] = content; 36 | } 37 | }; 38 | global.goStdout = (buf) => {}; 39 | global.goStderr = (buf) => {}; 40 | 41 | const openFiles = new Map(); 42 | let nextFd = 1000; 43 | 44 | let stat = (path, callback) => { 45 | let mode = 0; 46 | if (path === '/') { 47 | mode |= 0x80000000; 48 | } else if (filesystem[path] === undefined) { 49 | const err = new Error('no such file'); 50 | err.code = 'ENOENT'; 51 | callback(err); 52 | return; 53 | } 54 | callback(null, { 55 | mode, 56 | dev: 0, 57 | ino: 0, 58 | nlink: 0, 59 | uid: 0, 60 | gid: 0, 61 | rdev: 0, 62 | size: 0, 63 | blksize: 0, 64 | blocks: 0, 65 | atimeMs: 0, 66 | mtimeMs: 0, 67 | ctimeMs: 0, 68 | isDirectory: () => !!(mode & 0x80000000), 69 | }); 70 | }; 71 | 72 | const constants = { 73 | O_WRONLY: 1 << 0, 74 | O_RDWR: 1 << 1, 75 | O_CREAT: 1 << 2, 76 | O_TRUNC: 1 << 3, 77 | O_APPEND: 1 << 4, 78 | O_EXCL: 1 << 5, 79 | }; 80 | 81 | let outputBuf = ""; 82 | global.fs = { 83 | constants, 84 | writeSync(fd, buf) { 85 | if (fd === 1) { 86 | global.goStdout(buf); 87 | } else if (fd === 2) { 88 | global.goStderr(buf); 89 | } else { 90 | const file = openFiles[fd]; 91 | const source = filesystem[file.path]; 92 | let destLength = source.length + buf.length; 93 | if (file.offset < source.length) { 94 | destLength = file.offset + buf.length; 95 | if (destLength < source.length) { 96 | destLength = source.length; 97 | } 98 | } 99 | const dest = new Uint8Array(destLength); 100 | for (let i = 0; i < source.length; ++i) { 101 | dest[i] = source[i]; 102 | } 103 | for (let i = 0; i < buf.length; ++i) { 104 | dest[file.offset + i] = buf[i]; 105 | } 106 | openFiles[fd].offset += buf.length; 107 | filesystem[file.path] = dest; 108 | } 109 | }, 110 | write(fd, buf, offset, length, position, callback) { 111 | if (offset !== 0 || length !== buf.length) { 112 | throw new Error('write not fully implemented: ' + offset + ', ' + length + '/' + buf.length); 113 | } 114 | if (position !== null) { 115 | openFiles[fd].offset = position; 116 | } 117 | this.writeSync(fd, buf); 118 | callback(null, length); 119 | }, 120 | open(path, flags, mode, callback) { 121 | console.log('open(' + path + ', ' + mode + ')'); 122 | path = absPath(path); 123 | if (!filesystem[path]) { 124 | if (flags & constants.O_CREAT) { 125 | filesystem[path] = new Uint8Array(0); 126 | } else { 127 | const err = new Error('no such file'); 128 | err.code = 'ENOENT'; 129 | callback(err); 130 | return; 131 | } 132 | } 133 | if (flags & constants.O_TRUNC) { 134 | filesystem[path] = new Uint8Array(0); 135 | } 136 | const fd = nextFd++; 137 | openFiles[fd] = { 138 | offset: 0, 139 | path, 140 | }; 141 | callback(null, fd); 142 | }, 143 | read(fd, buffer, offset, length, position, callback) { 144 | if (offset !== 0) { 145 | throw new Error('read not fully implemented: ' + offset); 146 | } 147 | if (position !== null) { 148 | openFiles[fd].offset = position; 149 | } 150 | const file = openFiles[fd]; 151 | const source = filesystem[file.path]; 152 | let n = length; 153 | if (file.offset + length > source.length) { 154 | n = source.length - file.offset; 155 | } 156 | for (let i = 0; i < n; ++i) { 157 | buffer[i] = source[file.offset + i]; 158 | } 159 | openFiles[fd].offset += n; 160 | callback(null, n); 161 | }, 162 | close(fd, callback) { 163 | console.log('close(' + fd + ')'); 164 | openFiles.delete(fd); 165 | callback(null); 166 | }, 167 | fsync(fd, callback) { 168 | callback(null); 169 | }, 170 | unlink(path, callback) { 171 | console.log('unlink(' + path + ')'); 172 | callback(null); 173 | }, 174 | fstat(fd, callback) { 175 | console.log('fstat(' + fd + ')'); 176 | stat(openFiles[fd].path, callback); 177 | }, 178 | stat(path, callback) { 179 | console.log('stat(' + path + ')'); 180 | stat(absPath(path), callback); 181 | }, 182 | lstat(path, callback) { 183 | console.log('lstat(' + path + ')'); 184 | stat(absPath(path), callback); 185 | }, 186 | fchmod(fd, mode, callback) { 187 | console.log('fchmod(' + fd + ', ' + mode + ')'); 188 | callback(null); 189 | }, 190 | }; 191 | 192 | global.process = { 193 | pid: 1, 194 | cwd() { 195 | console.log('cwd()'); 196 | return workingDirectory; 197 | }, 198 | }; 199 | 200 | global.Go = class { 201 | constructor() { 202 | this.argv = ["js"]; 203 | this.env = {}; 204 | this.exit = (code) => { 205 | if (code !== 0) { 206 | console.warn("exit code:", code); 207 | } 208 | }; 209 | this._exitPromise = new Promise((resolve) => { 210 | this._resolveExitPromise = resolve; 211 | }); 212 | this._pendingEvent = null; 213 | this._scheduledTimeouts = new Map(); 214 | this._nextCallbackTimeoutID = 1; 215 | 216 | const mem = () => { 217 | // The buffer may change when requesting more memory. 218 | return new DataView(this._inst.exports.mem.buffer); 219 | } 220 | 221 | const setInt64 = (addr, v) => { 222 | mem().setUint32(addr + 0, v, true); 223 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 224 | } 225 | 226 | const getInt64 = (addr) => { 227 | const low = mem().getUint32(addr + 0, true); 228 | const high = mem().getInt32(addr + 4, true); 229 | return low + high * 4294967296; 230 | } 231 | 232 | const loadValue = (addr) => { 233 | const f = mem().getFloat64(addr, true); 234 | if (f === 0) { 235 | return undefined; 236 | } 237 | if (!isNaN(f)) { 238 | return f; 239 | } 240 | 241 | const id = mem().getUint32(addr, true); 242 | return this._values[id]; 243 | } 244 | 245 | const storeValue = (addr, v) => { 246 | const nanHead = 0x7FF80000; 247 | 248 | if (typeof v === "number") { 249 | if (isNaN(v)) { 250 | mem().setUint32(addr + 4, nanHead, true); 251 | mem().setUint32(addr, 0, true); 252 | return; 253 | } 254 | if (v === 0) { 255 | mem().setUint32(addr + 4, nanHead, true); 256 | mem().setUint32(addr, 1, true); 257 | return; 258 | } 259 | mem().setFloat64(addr, v, true); 260 | return; 261 | } 262 | 263 | switch (v) { 264 | case undefined: 265 | mem().setFloat64(addr, 0, true); 266 | return; 267 | case null: 268 | mem().setUint32(addr + 4, nanHead, true); 269 | mem().setUint32(addr, 2, true); 270 | return; 271 | case true: 272 | mem().setUint32(addr + 4, nanHead, true); 273 | mem().setUint32(addr, 3, true); 274 | return; 275 | case false: 276 | mem().setUint32(addr + 4, nanHead, true); 277 | mem().setUint32(addr, 4, true); 278 | return; 279 | } 280 | 281 | let ref = this._refs.get(v); 282 | if (ref === undefined) { 283 | ref = this._values.length; 284 | this._values.push(v); 285 | this._refs.set(v, ref); 286 | } 287 | let typeFlag = 0; 288 | switch (typeof v) { 289 | case "string": 290 | typeFlag = 1; 291 | break; 292 | case "symbol": 293 | typeFlag = 2; 294 | break; 295 | case "function": 296 | typeFlag = 3; 297 | break; 298 | } 299 | mem().setUint32(addr + 4, nanHead | typeFlag, true); 300 | mem().setUint32(addr, ref, true); 301 | } 302 | 303 | const loadSlice = (addr) => { 304 | const array = getInt64(addr + 0); 305 | const len = getInt64(addr + 8); 306 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 307 | } 308 | 309 | const loadSliceOfValues = (addr) => { 310 | const array = getInt64(addr + 0); 311 | const len = getInt64(addr + 8); 312 | const a = new Array(len); 313 | for (let i = 0; i < len; i++) { 314 | a[i] = loadValue(array + i * 8); 315 | } 316 | return a; 317 | } 318 | 319 | const loadString = (addr) => { 320 | const saddr = getInt64(addr + 0); 321 | const len = getInt64(addr + 8); 322 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 323 | } 324 | 325 | const timeOrigin = Date.now() - performance.now(); 326 | this.importObject = { 327 | go: { 328 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 329 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 330 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 331 | // This changes the SP, thus we have to update the SP used by the imported function. 332 | 333 | // func wasmExit(code int32) 334 | "runtime.wasmExit": (sp) => { 335 | const code = mem().getInt32(sp + 8, true); 336 | this.exited = true; 337 | delete this._inst; 338 | delete this._values; 339 | delete this._refs; 340 | this.exit(code); 341 | }, 342 | 343 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 344 | "runtime.wasmWrite": (sp) => { 345 | const fd = getInt64(sp + 8); 346 | const p = getInt64(sp + 16); 347 | const n = mem().getInt32(sp + 24, true); 348 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 349 | }, 350 | 351 | // func nanotime() int64 352 | "runtime.nanotime": (sp) => { 353 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 354 | }, 355 | 356 | // func walltime() (sec int64, nsec int32) 357 | "runtime.walltime": (sp) => { 358 | const msec = (new Date).getTime(); 359 | setInt64(sp + 8, msec / 1000); 360 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 361 | }, 362 | 363 | // func scheduleTimeoutEvent(delay int64) int32 364 | "runtime.scheduleTimeoutEvent": (sp) => { 365 | const id = this._nextCallbackTimeoutID; 366 | this._nextCallbackTimeoutID++; 367 | this._scheduledTimeouts.set(id, setTimeout( 368 | () => { this._resume(); }, 369 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 370 | )); 371 | mem().setInt32(sp + 16, id, true); 372 | }, 373 | 374 | // func clearTimeoutEvent(id int32) 375 | "runtime.clearTimeoutEvent": (sp) => { 376 | const id = mem().getInt32(sp + 8, true); 377 | clearTimeout(this._scheduledTimeouts.get(id)); 378 | this._scheduledTimeouts.delete(id); 379 | }, 380 | 381 | // func getRandomData(r []byte) 382 | "runtime.getRandomData": (sp) => { 383 | crypto.getRandomValues(loadSlice(sp + 8)); 384 | }, 385 | 386 | // func stringVal(value string) ref 387 | "syscall/js.stringVal": (sp) => { 388 | storeValue(sp + 24, loadString(sp + 8)); 389 | }, 390 | 391 | // func valueGet(v ref, p string) ref 392 | "syscall/js.valueGet": (sp) => { 393 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 394 | sp = this._inst.exports.getsp(); // see comment above 395 | storeValue(sp + 32, result); 396 | }, 397 | 398 | // func valueSet(v ref, p string, x ref) 399 | "syscall/js.valueSet": (sp) => { 400 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 401 | }, 402 | 403 | // func valueIndex(v ref, i int) ref 404 | "syscall/js.valueIndex": (sp) => { 405 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 406 | }, 407 | 408 | // valueSetIndex(v ref, i int, x ref) 409 | "syscall/js.valueSetIndex": (sp) => { 410 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 411 | }, 412 | 413 | // func valueCall(v ref, m string, args []ref) (ref, bool) 414 | "syscall/js.valueCall": (sp) => { 415 | try { 416 | const v = loadValue(sp + 8); 417 | const m = Reflect.get(v, loadString(sp + 16)); 418 | const args = loadSliceOfValues(sp + 32); 419 | const result = Reflect.apply(m, v, args); 420 | sp = this._inst.exports.getsp(); // see comment above 421 | storeValue(sp + 56, result); 422 | mem().setUint8(sp + 64, 1); 423 | } catch (err) { 424 | storeValue(sp + 56, err); 425 | mem().setUint8(sp + 64, 0); 426 | } 427 | }, 428 | 429 | // func valueInvoke(v ref, args []ref) (ref, bool) 430 | "syscall/js.valueInvoke": (sp) => { 431 | try { 432 | const v = loadValue(sp + 8); 433 | const args = loadSliceOfValues(sp + 16); 434 | const result = Reflect.apply(v, undefined, args); 435 | sp = this._inst.exports.getsp(); // see comment above 436 | storeValue(sp + 40, result); 437 | mem().setUint8(sp + 48, 1); 438 | } catch (err) { 439 | storeValue(sp + 40, err); 440 | mem().setUint8(sp + 48, 0); 441 | } 442 | }, 443 | 444 | // func valueNew(v ref, args []ref) (ref, bool) 445 | "syscall/js.valueNew": (sp) => { 446 | try { 447 | const v = loadValue(sp + 8); 448 | const args = loadSliceOfValues(sp + 16); 449 | const result = Reflect.construct(v, args); 450 | sp = this._inst.exports.getsp(); // see comment above 451 | storeValue(sp + 40, result); 452 | mem().setUint8(sp + 48, 1); 453 | } catch (err) { 454 | storeValue(sp + 40, err); 455 | mem().setUint8(sp + 48, 0); 456 | } 457 | }, 458 | 459 | // func valueLength(v ref) int 460 | "syscall/js.valueLength": (sp) => { 461 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 462 | }, 463 | 464 | // valuePrepareString(v ref) (ref, int) 465 | "syscall/js.valuePrepareString": (sp) => { 466 | const str = encoder.encode(String(loadValue(sp + 8))); 467 | storeValue(sp + 16, str); 468 | setInt64(sp + 24, str.length); 469 | }, 470 | 471 | // valueLoadString(v ref, b []byte) 472 | "syscall/js.valueLoadString": (sp) => { 473 | const str = loadValue(sp + 8); 474 | loadSlice(sp + 16).set(str); 475 | }, 476 | 477 | // func valueInstanceOf(v ref, t ref) bool 478 | "syscall/js.valueInstanceOf": (sp) => { 479 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 480 | }, 481 | 482 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 483 | "syscall/js.copyBytesToGo": (sp) => { 484 | const dst = loadSlice(sp + 8); 485 | const src = loadValue(sp + 32); 486 | if (!(src instanceof Uint8Array)) { 487 | mem().setUint8(sp + 48, 0); 488 | return; 489 | } 490 | const toCopy = src.subarray(0, dst.length); 491 | dst.set(toCopy); 492 | setInt64(sp + 40, toCopy.length); 493 | mem().setUint8(sp + 48, 1); 494 | }, 495 | 496 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 497 | "syscall/js.copyBytesToJS": (sp) => { 498 | const dst = loadValue(sp + 8); 499 | const src = loadSlice(sp + 16); 500 | if (!(dst instanceof Uint8Array)) { 501 | mem().setUint8(sp + 48, 0); 502 | return; 503 | } 504 | const toCopy = src.subarray(0, dst.length); 505 | dst.set(toCopy); 506 | setInt64(sp + 40, toCopy.length); 507 | mem().setUint8(sp + 48, 1); 508 | }, 509 | 510 | "debug": (value) => { 511 | console.log(value); 512 | }, 513 | } 514 | }; 515 | } 516 | 517 | async run(instance) { 518 | this._inst = instance; 519 | this._values = [ // TODO: garbage collection 520 | NaN, 521 | 0, 522 | null, 523 | true, 524 | false, 525 | global, 526 | this, 527 | ]; 528 | this._refs = new Map(); 529 | this.exited = false; 530 | 531 | const mem = new DataView(this._inst.exports.mem.buffer) 532 | 533 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 534 | let offset = 4096; 535 | 536 | const strPtr = (str) => { 537 | const ptr = offset; 538 | const bytes = encoder.encode(str + "\0"); 539 | new Uint8Array(mem.buffer, offset, bytes.length).set(bytes); 540 | offset += bytes.length; 541 | if (offset % 8 !== 0) { 542 | offset += 8 - (offset % 8); 543 | } 544 | return ptr; 545 | }; 546 | 547 | const argc = this.argv.length; 548 | 549 | const argvPtrs = []; 550 | this.argv.forEach((arg) => { 551 | argvPtrs.push(strPtr(arg)); 552 | }); 553 | 554 | const keys = Object.keys(this.env).sort(); 555 | argvPtrs.push(keys.length); 556 | keys.forEach((key) => { 557 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 558 | }); 559 | 560 | const argv = offset; 561 | argvPtrs.forEach((ptr) => { 562 | mem.setUint32(offset, ptr, true); 563 | mem.setUint32(offset + 4, 0, true); 564 | offset += 8; 565 | }); 566 | 567 | this._inst.exports.run(argc, argv); 568 | if (this.exited) { 569 | this._resolveExitPromise(); 570 | } 571 | await this._exitPromise; 572 | } 573 | 574 | _resume() { 575 | if (this.exited) { 576 | throw new Error("Go program has already exited"); 577 | } 578 | this._inst.exports.resume(); 579 | if (this.exited) { 580 | this._resolveExitPromise(); 581 | } 582 | } 583 | 584 | _makeFuncWrapper(id) { 585 | const go = this; 586 | return function () { 587 | const event = { id: id, this: this, args: arguments }; 588 | go._pendingEvent = event; 589 | go._resume(); 590 | return event.result; 591 | }; 592 | } 593 | } 594 | })(); 595 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The WebAssembly Go Playground 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 154 | 155 | 156 | 166 |
167 | 173 |
174 |
175 | 176 | 177 | -------------------------------------------------------------------------------- /jquery-linedtextarea.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from jQuery Lined Textarea Plugin 3 | * http://alan.blog-city.com/jquerylinedtextarea.htm 4 | * 5 | * Released under the MIT License: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | (function($) { 9 | $.fn.linedtextarea = function() { 10 | /* 11 | * Helper function to make sure the line numbers are always kept up to 12 | * the current system 13 | */ 14 | var fillOutLines = function(linesDiv, h, lineNo) { 15 | while (linesDiv.height() < h) { 16 | linesDiv.append("
" + lineNo + "
"); 17 | lineNo++; 18 | } 19 | return lineNo; 20 | }; 21 | 22 | return this.each(function() { 23 | var lineNo = 1; 24 | var textarea = $(this); 25 | 26 | /* Wrap the text area in the elements we need */ 27 | textarea.wrap("
"); 28 | textarea.width("97%"); 29 | textarea.parent().prepend("
"); 30 | var linesDiv = textarea.parent().find(".lines"); 31 | 32 | var scroll = function(tn) { 33 | var domTextArea = $(this)[0]; 34 | var scrollTop = domTextArea.scrollTop; 35 | var clientHeight = domTextArea.clientHeight; 36 | linesDiv.css({ 37 | 'margin-top' : (-scrollTop) + "px" 38 | }); 39 | lineNo = fillOutLines(linesDiv, scrollTop + clientHeight, 40 | lineNo); 41 | }; 42 | /* React to the scroll event */ 43 | textarea.scroll(scroll); 44 | $(window).resize(function() { textarea.scroll(); }); 45 | /* We call scroll once to add the line numbers */ 46 | textarea.scroll(); 47 | }); 48 | }; 49 | 50 | })(jQuery); 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | http.Handle("/", http.FileServer(http.Dir("."))) 10 | log.Fatal(http.ListenAndServe(":8080", nil)) 11 | } 12 | -------------------------------------------------------------------------------- /playground.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | In the absence of any formal way to specify interfaces in JavaScript, 7 | here's a skeleton implementation of a playground transport. 8 | 9 | function Transport() { 10 | // Set up any transport state (eg, make a websocket connection). 11 | return { 12 | Run: function(body, output, options) { 13 | // Compile and run the program 'body' with 'options'. 14 | // Call the 'output' callback to display program output. 15 | return { 16 | Kill: function() { 17 | // Kill the running program. 18 | } 19 | }; 20 | } 21 | }; 22 | } 23 | 24 | // The output callback is called multiple times, and each time it is 25 | // passed an object of this form. 26 | var write = { 27 | Kind: 'string', // 'start', 'stdout', 'stderr', 'end' 28 | Body: 'string' // content of write or end status message 29 | } 30 | 31 | // The first call must be of Kind 'start' with no body. 32 | // Subsequent calls may be of Kind 'stdout' or 'stderr' 33 | // and must have a non-null Body string. 34 | // The final call should be of Kind 'end' with an optional 35 | // Body string, signifying a failure ("killed", for example). 36 | 37 | // The output callback must be of this form. 38 | // See PlaygroundOutput (below) for an implementation. 39 | function outputCallback(write) { 40 | } 41 | */ 42 | 43 | // HTTPTransport is the default transport. 44 | // enableVet enables running vet if a program was compiled and ran successfully. 45 | // If vet returned any errors, display them before the output of a program. 46 | function HTTPTransport(enableVet) { 47 | 'use strict'; 48 | 49 | function playback(output, data) { 50 | // Backwards compatibility: default values do not affect the output. 51 | var events = data.Events || []; 52 | var errors = data.Errors || ""; 53 | var status = data.Status || 0; 54 | var isTest = data.IsTest || false; 55 | var testsFailed = data.TestsFailed || 0; 56 | 57 | var timeout; 58 | output({Kind: 'start'}); 59 | function next() { 60 | if (!events || events.length === 0) { 61 | if (isTest) { 62 | if (testsFailed > 0) { 63 | output({Kind: 'system', Body: '\n'+testsFailed+' test'+(testsFailed>1?'s':'')+' failed.'}); 64 | } else { 65 | output({Kind: 'system', Body: '\nAll tests passed.'}); 66 | } 67 | } else { 68 | if (status > 0) { 69 | output({Kind: 'end', Body: 'status ' + status + '.'}); 70 | } else { 71 | if (errors !== "") { 72 | // errors are displayed only in the case of timeout. 73 | output({Kind: 'end', Body: errors + '.'}); 74 | } else { 75 | output({Kind: 'end'}); 76 | } 77 | } 78 | } 79 | return; 80 | } 81 | var e = events.shift(); 82 | if (e.Delay === 0) { 83 | output({Kind: e.Kind, Body: e.Message}); 84 | next(); 85 | return; 86 | } 87 | timeout = setTimeout(function() { 88 | output({Kind: e.Kind, Body: e.Message}); 89 | next(); 90 | }, e.Delay / 1000000); 91 | } 92 | next(); 93 | return { 94 | Stop: function() { 95 | clearTimeout(timeout); 96 | } 97 | }; 98 | } 99 | 100 | function error(output, msg) { 101 | output({Kind: 'start'}); 102 | output({Kind: 'stderr', Body: msg}); 103 | output({Kind: 'end'}); 104 | } 105 | 106 | function buildFailed(output, msg) { 107 | output({Kind: 'start'}); 108 | output({Kind: 'stderr', Body: msg}); 109 | output({Kind: 'system', Body: '\nGo build failed.'}); 110 | } 111 | 112 | var seq = 0; 113 | return { 114 | Run: function(body, output, options) { 115 | seq++; 116 | var cur = seq; 117 | var playing; 118 | $.ajax('/compile', { 119 | type: 'POST', 120 | data: {'version': 2, 'body': body, 'withVet': enableVet}, 121 | dataType: 'json', 122 | success: function(data) { 123 | if (seq != cur) return; 124 | if (!data) return; 125 | if (playing != null) playing.Stop(); 126 | if (data.Errors) { 127 | if (data.Errors === 'process took too long') { 128 | // Playback the output that was captured before the timeout. 129 | playing = playback(output, data); 130 | } else { 131 | buildFailed(output, data.Errors); 132 | } 133 | return; 134 | } 135 | if (!data.Events) { 136 | data.Events = []; 137 | } 138 | if (data.VetErrors) { 139 | // Inject errors from the vet as the first events in the output. 140 | data.Events.unshift({Message: 'Go vet exited.\n\n', Kind: 'system', Delay: 0}); 141 | data.Events.unshift({Message: data.VetErrors, Kind: 'stderr', Delay: 0}); 142 | } 143 | 144 | if (!enableVet || data.VetOK || data.VetErrors) { 145 | playing = playback(output, data); 146 | return; 147 | } 148 | 149 | // In case the server support doesn't support 150 | // compile+vet in same request signaled by the 151 | // 'withVet' parameter above, also try the old way. 152 | // TODO: remove this when it falls out of use. 153 | // It is 2019-05-13 now. 154 | $.ajax("/vet", { 155 | data: {"body": body}, 156 | type: "POST", 157 | dataType: "json", 158 | success: function(dataVet) { 159 | if (dataVet.Errors) { 160 | // inject errors from the vet as the first events in the output 161 | data.Events.unshift({Message: 'Go vet exited.\n\n', Kind: 'system', Delay: 0}); 162 | data.Events.unshift({Message: dataVet.Errors, Kind: 'stderr', Delay: 0}); 163 | } 164 | playing = playback(output, data); 165 | }, 166 | error: function() { 167 | playing = playback(output, data); 168 | } 169 | }); 170 | }, 171 | error: function() { 172 | error(output, 'Error communicating with remote server.'); 173 | } 174 | }); 175 | return { 176 | Kill: function() { 177 | if (playing != null) playing.Stop(); 178 | output({Kind: 'end', Body: 'killed'}); 179 | } 180 | }; 181 | } 182 | }; 183 | } 184 | 185 | function SocketTransport() { 186 | 'use strict'; 187 | 188 | var id = 0; 189 | var outputs = {}; 190 | var started = {}; 191 | var websocket; 192 | if (window.location.protocol == "http:") { 193 | websocket = new WebSocket('ws://' + window.location.host + '/socket'); 194 | } else if (window.location.protocol == "https:") { 195 | websocket = new WebSocket('wss://' + window.location.host + '/socket'); 196 | } 197 | 198 | websocket.onclose = function() { 199 | console.log('websocket connection closed'); 200 | }; 201 | 202 | websocket.onmessage = function(e) { 203 | var m = JSON.parse(e.data); 204 | var output = outputs[m.Id]; 205 | if (output === null) 206 | return; 207 | if (!started[m.Id]) { 208 | output({Kind: 'start'}); 209 | started[m.Id] = true; 210 | } 211 | output({Kind: m.Kind, Body: m.Body}); 212 | }; 213 | 214 | function send(m) { 215 | websocket.send(JSON.stringify(m)); 216 | } 217 | 218 | return { 219 | Run: function(body, output, options) { 220 | var thisID = id+''; 221 | id++; 222 | outputs[thisID] = output; 223 | send({Id: thisID, Kind: 'run', Body: body, Options: options}); 224 | return { 225 | Kill: function() { 226 | send({Id: thisID, Kind: 'kill'}); 227 | } 228 | }; 229 | } 230 | }; 231 | } 232 | 233 | function PlaygroundOutput(el) { 234 | 'use strict'; 235 | 236 | return function(write) { 237 | if (write.Kind == 'start') { 238 | el.innerHTML = ''; 239 | return; 240 | } 241 | 242 | var cl = 'system'; 243 | if (write.Kind == 'stdout' || write.Kind == 'stderr') 244 | cl = write.Kind; 245 | 246 | var m = write.Body; 247 | if (write.Kind == 'end') { 248 | m = '\nProgram exited' + (m?(': '+m):'.'); 249 | } 250 | 251 | if (m.indexOf('IMAGE:') === 0) { 252 | // TODO(adg): buffer all writes before creating image 253 | var url = 'data:image/png;base64,' + m.substr(6); 254 | var img = document.createElement('img'); 255 | img.src = url; 256 | el.appendChild(img); 257 | return; 258 | } 259 | 260 | // ^L clears the screen. 261 | var s = m.split('\x0c'); 262 | if (s.length > 1) { 263 | el.innerHTML = ''; 264 | m = s.pop(); 265 | } 266 | 267 | m = m.replace(/&/g, '&'); 268 | m = m.replace(//g, '>'); 270 | 271 | var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight; 272 | 273 | var span = document.createElement('span'); 274 | span.className = cl; 275 | span.innerHTML = m; 276 | el.appendChild(span); 277 | 278 | if (needScroll) 279 | el.scrollTop = el.scrollHeight - el.offsetHeight; 280 | }; 281 | } 282 | 283 | (function() { 284 | function lineHighlight(error) { 285 | var regex = /prog.go:([0-9]+)/g; 286 | var r = regex.exec(error); 287 | while (r) { 288 | $(".lines div").eq(r[1]-1).addClass("lineerror"); 289 | r = regex.exec(error); 290 | } 291 | } 292 | function highlightOutput(wrappedOutput) { 293 | return function(write) { 294 | if (write.Body) lineHighlight(write.Body); 295 | wrappedOutput(write); 296 | }; 297 | } 298 | function lineClear() { 299 | $(".lineerror").removeClass("lineerror"); 300 | } 301 | 302 | // opts is an object with these keys 303 | // codeEl - code editor element 304 | // outputEl - program output element 305 | // runEl - run button element 306 | // fmtEl - fmt button element (optional) 307 | // fmtImportEl - fmt "imports" checkbox element (optional) 308 | // shareEl - share button element (optional) 309 | // shareURLEl - share URL text input element (optional) 310 | // shareRedirect - base URL to redirect to on share (optional) 311 | // toysEl - toys select element (optional) 312 | // enableHistory - enable using HTML5 history API (optional) 313 | // transport - playground transport to use (default is HTTPTransport) 314 | // enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false) 315 | // enableVet - enable running vet and displaying its errors 316 | function playground(opts) { 317 | var code = $(opts.codeEl); 318 | var transport = opts['transport'] || new HTTPTransport(opts['enableVet']); 319 | var running; 320 | 321 | // autoindent helpers. 322 | function insertTabs(n) { 323 | // find the selection start and end 324 | var start = code[0].selectionStart; 325 | var end = code[0].selectionEnd; 326 | // split the textarea content into two, and insert n tabs 327 | var v = code[0].value; 328 | var u = v.substr(0, start); 329 | for (var i=0; i 0) { 343 | curpos--; 344 | if (el.value[curpos] == "\t") { 345 | tabs++; 346 | } else if (tabs > 0 || el.value[curpos] == "\n") { 347 | break; 348 | } 349 | } 350 | setTimeout(function() { 351 | insertTabs(tabs); 352 | }, 1); 353 | } 354 | 355 | // NOTE(cbro): e is a jQuery event, not a DOM event. 356 | function handleSaveShortcut(e) { 357 | if (e.isDefaultPrevented()) return false; 358 | if (!e.metaKey && !e.ctrlKey) return false; 359 | if (e.key != "S" && e.key != "s") return false; 360 | 361 | e.preventDefault(); 362 | 363 | // Share and save 364 | share(function(url) { 365 | window.location.href = url + ".go?download=true"; 366 | }); 367 | 368 | return true; 369 | } 370 | 371 | function keyHandler(e) { 372 | if (opts.enableShortcuts && handleSaveShortcut(e)) return; 373 | 374 | if (e.keyCode == 9 && !e.ctrlKey) { // tab (but not ctrl-tab) 375 | insertTabs(1); 376 | e.preventDefault(); 377 | return false; 378 | } 379 | if (e.keyCode == 13) { // enter 380 | if (e.shiftKey) { // +shift 381 | run(); 382 | e.preventDefault(); 383 | return false; 384 | } if (e.ctrlKey) { // +control 385 | fmt(); 386 | e.preventDefault(); 387 | } else { 388 | autoindent(e.target); 389 | } 390 | } 391 | return true; 392 | } 393 | code.unbind('keydown').bind('keydown', keyHandler); 394 | var outdiv = $(opts.outputEl).empty(); 395 | var output = $('
').appendTo(outdiv);
396 | 
397 |     function body() {
398 |       return $(opts.codeEl).val();
399 |     }
400 |     function setBody(text) {
401 |       $(opts.codeEl).val(text);
402 |     }
403 |     function origin(href) {
404 |       return (""+href).split("/").slice(0, 3).join("/");
405 |     }
406 | 
407 |     var pushedEmpty = (window.location.pathname == "/");
408 |     function inputChanged() {
409 |       if (pushedEmpty) {
410 |         return;
411 |       }
412 |       pushedEmpty = true;
413 |       $(opts.shareURLEl).hide();
414 |       window.history.pushState(null, "", "/");
415 |     }
416 |     function popState(e) {
417 |       if (e === null) {
418 |         return;
419 |       }
420 |       if (e && e.state && e.state.code) {
421 |         setBody(e.state.code);
422 |       }
423 |     }
424 |     var rewriteHistory = false;
425 |     if (window.history && window.history.pushState && window.addEventListener && opts.enableHistory) {
426 |       rewriteHistory = true;
427 |       code[0].addEventListener('input', inputChanged);
428 |       window.addEventListener('popstate', popState);
429 |     }
430 | 
431 |     function setError(error) {
432 |       if (running) running.Kill();
433 |       lineClear();
434 |       lineHighlight(error);
435 |       output.empty().addClass("error").text(error);
436 |     }
437 |     function loading() {
438 |       lineClear();
439 |       if (running) running.Kill();
440 |       output.removeClass("error").text('Waiting for remote server...');
441 |     }
442 |     function run() {
443 |       loading();
444 |       running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
445 |     }
446 | 
447 |     function fmt() {
448 |       loading();
449 |       var data = {"body": body()};
450 |       if ($(opts.fmtImportEl).is(":checked")) {
451 |         data["imports"] = "true";
452 |       }
453 |       $.ajax("/fmt", {
454 |         data: data,
455 |         type: "POST",
456 |         dataType: "json",
457 |         success: function(data) {
458 |           if (data.Error) {
459 |             setError(data.Error);
460 |           } else {
461 |             setBody(data.Body);
462 |             setError("");
463 |           }
464 |         }
465 |       });
466 |     }
467 | 
468 |     var shareURL; // jQuery element to show the shared URL.
469 |     var sharing = false; // true if there is a pending request.
470 |     var shareCallbacks = [];
471 |     function share(opt_callback) {
472 |       if (opt_callback) shareCallbacks.push(opt_callback);
473 | 
474 |       if (sharing) return;
475 |       sharing = true;
476 | 
477 |       var sharingData = body();
478 |       $.ajax("/share", {
479 |         processData: false,
480 |         data: sharingData,
481 |         type: "POST",
482 |         contentType: "text/plain; charset=utf-8",
483 |         complete: function(xhr) {
484 |           sharing = false;
485 |           if (xhr.status != 200) {
486 |             alert("Server error; try again.");
487 |             return;
488 |           }
489 |           if (opts.shareRedirect) {
490 |             window.location = opts.shareRedirect + xhr.responseText;
491 |           }
492 |           var path = "/p/" + xhr.responseText;
493 |           var url = origin(window.location) + path;
494 | 
495 |           for (var i = 0; i < shareCallbacks.length; i++) {
496 |             shareCallbacks[i](url);
497 |           }
498 |           shareCallbacks = [];
499 | 
500 |           if (shareURL) {
501 |             shareURL.show().val(url).focus().select();
502 | 
503 |             if (rewriteHistory) {
504 |               var historyData = {"code": sharingData};
505 |               window.history.pushState(historyData, "", path);
506 |               pushedEmpty = false;
507 |             }
508 |           }
509 |         }
510 |       });
511 |     }
512 | 
513 |     $(opts.runEl).click(run);
514 |     $(opts.fmtEl).click(fmt);
515 | 
516 |     if (opts.shareEl !== null && (opts.shareURLEl !== null || opts.shareRedirect !== null)) {
517 |       if (opts.shareURLEl) {
518 |         shareURL = $(opts.shareURLEl).hide();
519 |       }
520 |       $(opts.shareEl).click(function() {
521 |         share();
522 |       });
523 |     }
524 | 
525 |     if (opts.toysEl !== null) {
526 |       $(opts.toysEl).bind('change', function() {
527 |         var toy = $(this).val();
528 |         $.ajax("/doc/play/"+toy, {
529 |           processData: false,
530 |           type: "GET",
531 |           complete: function(xhr) {
532 |             if (xhr.status != 200) {
533 |               alert("Server error; try again.");
534 |               return;
535 |             }
536 |             setBody(xhr.responseText);
537 |           }
538 |         });
539 |       });
540 |     }
541 |   }
542 | 
543 |   window.playground = playground;
544 | })();
545 | 


--------------------------------------------------------------------------------
/prebuilt/internal/bytealg.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/internal/bytealg.a


--------------------------------------------------------------------------------
/prebuilt/internal/cpu.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/internal/cpu.a


--------------------------------------------------------------------------------
/prebuilt/runtime.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/runtime.a


--------------------------------------------------------------------------------
/prebuilt/runtime/internal/atomic.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/runtime/internal/atomic.a


--------------------------------------------------------------------------------
/prebuilt/runtime/internal/math.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/runtime/internal/math.a


--------------------------------------------------------------------------------
/prebuilt/runtime/internal/sys.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ccbrown/wasm-go-playground/6c970507c13ae3f062fd94ea3e7dd1abc1b02303/prebuilt/runtime/internal/sys.a


--------------------------------------------------------------------------------
/sharing.js:
--------------------------------------------------------------------------------
 1 | function initSharing(opts) {
 2 |     var code = $(opts.codeEl);
 3 |     var share = $(opts.shareEl);
 4 |     var shareURL = $(opts.shareURLEl);
 5 | 
 6 |     var encodeHash = () => {
 7 |         var encoded = LZString.compressToBase64(code.val());
 8 |         window.location.hash = encoded;
 9 |     };
10 | 
11 |     var decodeHash = () => {
12 |         if (window.location.hash && window.location.hash.length > 1) {
13 |             var decoded = LZString.decompressFromBase64(window.location.hash.substr(1));
14 |             if (decoded) {
15 |                 code.val(decoded);
16 |             } else {
17 |                 window.location.hash = '';
18 |             }
19 |         }
20 |     };
21 | 
22 |     code[0].addEventListener('input', () => {
23 |         window.location.hash = '';
24 |         shareURL.hide();
25 |     });
26 | 
27 |     decodeHash();
28 |     window.addEventListener('hashchange', () => {
29 |         decodeHash();
30 |     });
31 | 
32 |     share.click(() => {
33 |         encodeHash();
34 |         shareURL.show().val(window.location).focus().select();
35 |     });
36 | }
37 | 


--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
  1 | html {
  2 | 	height: 100%;
  3 | }
  4 | body {
  5 | 	color: black;
  6 | 	padding: 0;
  7 | 	margin: 0;
  8 | 	width: 100%;
  9 | 	height: 100%;
 10 | }
 11 | a {
 12 | 	color: #009;
 13 | }
 14 | #wrap,
 15 | #about {
 16 | 	padding: 5px;
 17 | 	margin: 0;
 18 | 
 19 | 	position: absolute;
 20 | 	top: 50px;
 21 | 	bottom: 25%;
 22 | 	left: 0;
 23 | 	right: 0;
 24 | 
 25 | 	background: #FFD;
 26 | }
 27 | #about {
 28 | 	display: none;
 29 | 	z-index: 1;
 30 | 	padding: 10px 40px;
 31 | 	font-size: 16px;
 32 | 	font-family: sans-serif;
 33 | 	overflow: auto;
 34 | }
 35 | #about p {
 36 | 	max-width: 520px;
 37 | }
 38 | #about ul {
 39 | 	max-width: 480px;
 40 | }
 41 | #about li {
 42 | 	margin-bottom: 1em;
 43 | }
 44 | #code, #output, pre, .lines {
 45 | 	/* The default monospace font on OS X is ugly, so specify Menlo
 46 | 	 * instead. On other systems the default monospace font will be used. */
 47 | 	font-family: Menlo, monospace;
 48 | 	font-size: 11pt;
 49 | }
 50 | 
 51 | #code {
 52 | 	color: black;
 53 | 	background: inherit;
 54 | 
 55 | 	width: 100%;
 56 | 	height: 100%;
 57 | 	padding: 0; margin: 0;
 58 | 	border: none;
 59 | 	outline: none;
 60 | 	resize: none;
 61 | 	wrap: off;
 62 | 	float: right;
 63 | }
 64 | #output {
 65 | 	position: absolute;
 66 | 	top: 75%;
 67 | 	bottom: 0;
 68 | 	left: 0;
 69 | 	right: 0;
 70 | 	padding: 8px;
 71 | }
 72 | #output .system, #output .loading {
 73 | 	color: #999;
 74 | }
 75 | #output .stderr, #output .error {
 76 | 	color: #900;
 77 | }
 78 | #output pre {
 79 | 	margin: 0;
 80 | }
 81 | #banner {
 82 | 	position: absolute;
 83 | 	left: 0;
 84 | 	right: 0;
 85 | 	top: 0;
 86 | 	height: 50px;
 87 | 	background-color: #E0EBF5;
 88 | }
 89 | #head {
 90 | 	float: left;
 91 | 	padding: 15px 10px;
 92 | 
 93 | 	font-size: 20px;
 94 | 	font-family: sans-serif;
 95 | }
 96 | #controls {
 97 | 	float: left;
 98 | 	padding: 10px 15px;
 99 | 	min-width: 245px;
100 | }
101 | #controls > input {
102 | 	border-radius: 5px;
103 | }
104 | #aboutControls {
105 | 	float: right;
106 | 	padding: 10px 15px;
107 | }
108 | input[type=button],
109 | #importsBox {
110 | 	height: 30px;
111 | 	border: 1px solid #375EAB;
112 | 	font-size: 16px;
113 | 	font-family: sans-serif;
114 | 	background: #375EAB;
115 | 	color: white;
116 | 	position: static;
117 | 	top: 1px;
118 | 	border-radius: 5px;
119 | }
120 | input[type=button]:disabled {
121 | 	background: #C0C0C0;
122 | }
123 | #importsBox input {
124 | 	position: relative;
125 | 	top: -2px;
126 | 	left: 1px;
127 | 	height: 10px;
128 | 	width: 10px;
129 | }
130 | #shareURL {
131 | 	width: 280px;
132 | 	font-size: 16px;
133 | 	border: 1px solid #ccc;
134 | 	background: #eee;
135 | 	color: black;
136 | 	height: 23px;
137 | }
138 | #embedLabel {
139 | 	font-family: sans-serif;
140 | }
141 | .lines {
142 | 	float: left;
143 | 	overflow: hidden;
144 | 	text-align: right;
145 | }
146 | .lines div {
147 | 	padding-right: 5px;
148 | 	color: lightgray;
149 | }
150 | .lineerror {
151 | 	color: red;
152 | 	background: #FDD;
153 | }
154 | .exit {
155 | 	color: lightgray;
156 | }
157 | 
158 | .embedded #banner {
159 | 	display: none;
160 | }
161 | .embedded #wrap {
162 | 	top: 0;
163 | }
164 | #embedRun {
165 | 	display: none;
166 | }
167 | .embedded #embedRun {
168 | 	display: block;
169 | 	position: absolute;
170 | 	z-index: 1;
171 | 	top: 10px;
172 | 	right: 10px;
173 | }
174 | 


--------------------------------------------------------------------------------
/wasm_exec.js:
--------------------------------------------------------------------------------
  1 | // Copyright 2018 The Go Authors. All rights reserved.
  2 | // Use of this source code is governed by a BSD-style
  3 | // license that can be found in the LICENSE file.
  4 | 
  5 | (() => {
  6 | 	if (typeof global !== "undefined") {
  7 | 		// global already exists
  8 | 	} else if (typeof window !== "undefined") {
  9 | 		window.global = window;
 10 | 	} else if (typeof self !== "undefined") {
 11 | 		self.global = self;
 12 | 	} else {
 13 | 		throw new Error("cannot export Go (neither global, window nor self is defined)");
 14 | 	}
 15 | 
 16 | 	const encoder = new TextEncoder('utf-8');
 17 | 	const decoder = new TextDecoder('utf-8');
 18 | 
 19 |     const filesystem = {};
 20 | 
 21 |     let workingDirectory = '/';
 22 | 
 23 |     let absPath = (path) => {
 24 |         if (path[0] == '/') {
 25 |             return path;
 26 |         }
 27 |         return workingDirectory + path.replace(/^\.\/?/, '');
 28 |     };
 29 | 
 30 |     global.readFromGoFilesystem = (path) => filesystem[absPath(path)];
 31 |     global.writeToGoFilesystem = (path, content) => {
 32 |         if (typeof content === 'string') {
 33 |             filesystem[absPath(path)] = encoder.encode(content);
 34 |         } else {
 35 |             filesystem[absPath(path)] = content;
 36 |         }
 37 |     };
 38 |     global.goStdout = (buf) => {};
 39 |     global.goStderr = (buf) => {};
 40 | 
 41 |     const openFiles = new Map();
 42 |     let nextFd = 1000;
 43 | 
 44 |     let stat = (path, callback) => {
 45 |         let mode = 0;
 46 |         if (path === '/') {
 47 |             mode |= 0x80000000;
 48 |         } else if (filesystem[path] === undefined) {
 49 |             const err = new Error('no such file');
 50 |             err.code = 'ENOENT';
 51 |             callback(err);
 52 |             return;
 53 |         }
 54 |         callback(null, {
 55 |             mode,
 56 |             dev: 0,
 57 |             ino: 0,
 58 |             nlink: 0,
 59 |             uid: 0,
 60 |             gid: 0,
 61 |             rdev: 0,
 62 |             size: 0,
 63 |             blksize: 0,
 64 |             blocks: 0,
 65 |             atimeMs: 0,
 66 |             mtimeMs: 0,
 67 |             ctimeMs: 0,
 68 |             isDirectory: () => !!(mode & 0x80000000),
 69 |         });
 70 |     };
 71 | 
 72 |     const constants = {
 73 |         O_WRONLY: 1 << 0,
 74 |         O_RDWR: 1 << 1,
 75 |         O_CREAT: 1 << 2,
 76 |         O_TRUNC: 1 << 3,
 77 |         O_APPEND: 1 << 4,
 78 |         O_EXCL: 1 << 5,
 79 |     };
 80 | 
 81 |     let outputBuf = "";
 82 |     global.fs = {
 83 |         constants,
 84 |         writeSync(fd, buf) {
 85 |             if (fd === 1) {
 86 |                 global.goStdout(buf);
 87 |             } else if (fd === 2) {
 88 |                 global.goStderr(buf);
 89 |             } else {
 90 |                 const file = openFiles[fd];
 91 |                 const source = filesystem[file.path];
 92 |                 let destLength = source.length + buf.length;
 93 |                 if (file.offset < source.length) {
 94 |                     destLength = file.offset + buf.length;
 95 |                     if (destLength < source.length) {
 96 |                         destLength = source.length;
 97 |                     }
 98 |                 }
 99 |                 const dest = new Uint8Array(destLength);
100 |                 for (let i = 0; i < source.length; ++i) {
101 |                     dest[i] = source[i];
102 |                 }
103 |                 for (let i = 0; i < buf.length; ++i) {
104 |                     dest[file.offset + i] = buf[i];
105 |                 }
106 |                 openFiles[fd].offset += buf.length;
107 |                 filesystem[file.path] = dest;
108 |             }
109 |         },
110 |         write(fd, buf, offset, length, position, callback) {
111 |             if (offset !== 0 || length !== buf.length) {
112 |                 throw new Error('write not fully implemented: ' + offset + ', ' + length + '/' + buf.length);
113 |             }
114 |             if (position !== null) {
115 |                 openFiles[fd].offset = position;
116 |             }
117 |             this.writeSync(fd, buf);
118 |             callback(null, length);
119 |         },
120 |         open(path, flags, mode, callback) {
121 |             console.log('open(' + path + ', ' + mode + ')');
122 |             path = absPath(path);
123 |             if (!filesystem[path]) {
124 |                 if (flags & constants.O_CREAT) {
125 |                     filesystem[path] = new Uint8Array(0);
126 |                 } else {
127 |                     const err = new Error('no such file');
128 |                     err.code = 'ENOENT';
129 |                     callback(err);
130 |                     return;
131 |                 }
132 |             }
133 |             if (flags & constants.O_TRUNC) {
134 |                 filesystem[path] = new Uint8Array(0);
135 |             }
136 |             const fd = nextFd++;
137 |             openFiles[fd] = {
138 |                 offset: 0,
139 |                 path,
140 |             };
141 |             callback(null, fd);
142 |         },
143 |         read(fd, buffer, offset, length, position, callback) {
144 |             if (offset !== 0) {
145 |                 throw new Error('read not fully implemented: ' + offset);
146 |             }
147 |             if (position !== null) {
148 |                 openFiles[fd].offset = position;
149 |             }
150 |             const file = openFiles[fd];
151 |             const source = filesystem[file.path];
152 |             let n = length;
153 |             if (file.offset + length > source.length) {
154 |                 n = source.length - file.offset;
155 |             }
156 |             for (let i = 0; i < n; ++i) {
157 |                 buffer[i] = source[file.offset + i];
158 |             }
159 |             openFiles[fd].offset += n;
160 |             callback(null, n);
161 |         },
162 |         close(fd, callback) {
163 |             console.log('close(' + fd + ')');
164 |             openFiles.delete(fd);
165 |             callback(null);
166 |         },
167 |         fsync(fd, callback) {
168 |             callback(null);
169 |         },
170 |         unlink(path, callback) {
171 |             console.log('unlink(' + path + ')');
172 |             callback(null);
173 |         },
174 |         fstat(fd, callback) {
175 |             console.log('fstat(' + fd + ')');
176 |             stat(openFiles[fd].path, callback);
177 |         },
178 |         stat(path, callback) {
179 |             console.log('stat(' + path + ')');
180 |             stat(absPath(path), callback);
181 |         },
182 |         lstat(path, callback) {
183 |             console.log('lstat(' + path + ')');
184 |             stat(absPath(path), callback);
185 |         },
186 |         fchmod(fd, mode, callback) {
187 |             console.log('fchmod(' + fd + ', ' + mode + ')');
188 |             callback(null);
189 |         },
190 |     };
191 | 
192 |     global.process = {
193 |         cwd() {
194 |             console.log('cwd()');
195 |             return workingDirectory;
196 |         },
197 |     };
198 | 
199 | 	global.Go = class {
200 | 		constructor() {
201 | 			this.argv = ["js"];
202 | 			this.env = {};
203 | 			this.exit = (code) => {
204 | 				if (code !== 0) {
205 | 					console.warn("exit code:", code);
206 | 				}
207 | 			};
208 | 			this._exitPromise = new Promise((resolve) => {
209 | 				this._resolveExitPromise = resolve;
210 | 			});
211 | 			this._pendingEvent = null;
212 | 			this._scheduledTimeouts = new Map();
213 | 			this._nextCallbackTimeoutID = 1;
214 | 
215 | 			const mem = () => {
216 | 				// The buffer may change when requesting more memory.
217 | 				return new DataView(this._inst.exports.mem.buffer);
218 | 			}
219 | 
220 | 			const setInt64 = (addr, v) => {
221 | 				mem().setUint32(addr + 0, v, true);
222 | 				mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
223 | 			}
224 | 
225 | 			const getInt64 = (addr) => {
226 | 				const low = mem().getUint32(addr + 0, true);
227 | 				const high = mem().getInt32(addr + 4, true);
228 | 				return low + high * 4294967296;
229 | 			}
230 | 
231 | 			const loadValue = (addr) => {
232 | 				const f = mem().getFloat64(addr, true);
233 | 				if (f === 0) {
234 | 					return undefined;
235 | 				}
236 | 				if (!isNaN(f)) {
237 | 					return f;
238 | 				}
239 | 
240 | 				const id = mem().getUint32(addr, true);
241 | 				return this._values[id];
242 | 			}
243 | 
244 | 			const storeValue = (addr, v) => {
245 | 				const nanHead = 0x7FF80000;
246 | 
247 | 				if (typeof v === "number") {
248 | 					if (isNaN(v)) {
249 | 						mem().setUint32(addr + 4, nanHead, true);
250 | 						mem().setUint32(addr, 0, true);
251 | 						return;
252 | 					}
253 | 					if (v === 0) {
254 | 						mem().setUint32(addr + 4, nanHead, true);
255 | 						mem().setUint32(addr, 1, true);
256 | 						return;
257 | 					}
258 | 					mem().setFloat64(addr, v, true);
259 | 					return;
260 | 				}
261 | 
262 | 				switch (v) {
263 | 					case undefined:
264 | 						mem().setFloat64(addr, 0, true);
265 | 						return;
266 | 					case null:
267 | 						mem().setUint32(addr + 4, nanHead, true);
268 | 						mem().setUint32(addr, 2, true);
269 | 						return;
270 | 					case true:
271 | 						mem().setUint32(addr + 4, nanHead, true);
272 | 						mem().setUint32(addr, 3, true);
273 | 						return;
274 | 					case false:
275 | 						mem().setUint32(addr + 4, nanHead, true);
276 | 						mem().setUint32(addr, 4, true);
277 | 						return;
278 | 				}
279 | 
280 | 				let ref = this._refs.get(v);
281 | 				if (ref === undefined) {
282 | 					ref = this._values.length;
283 | 					this._values.push(v);
284 | 					this._refs.set(v, ref);
285 | 				}
286 | 				let typeFlag = 0;
287 | 				switch (typeof v) {
288 | 					case "string":
289 | 						typeFlag = 1;
290 | 						break;
291 | 					case "symbol":
292 | 						typeFlag = 2;
293 | 						break;
294 | 					case "function":
295 | 						typeFlag = 3;
296 | 						break;
297 | 				}
298 | 				mem().setUint32(addr + 4, nanHead | typeFlag, true);
299 | 				mem().setUint32(addr, ref, true);
300 | 			}
301 | 
302 | 			const loadSlice = (addr) => {
303 | 				const array = getInt64(addr + 0);
304 | 				const len = getInt64(addr + 8);
305 | 				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
306 | 			}
307 | 
308 | 			const loadSliceOfValues = (addr) => {
309 | 				const array = getInt64(addr + 0);
310 | 				const len = getInt64(addr + 8);
311 | 				const a = new Array(len);
312 | 				for (let i = 0; i < len; i++) {
313 | 					a[i] = loadValue(array + i * 8);
314 | 				}
315 | 				return a;
316 | 			}
317 | 
318 | 			const loadString = (addr) => {
319 | 				const saddr = getInt64(addr + 0);
320 | 				const len = getInt64(addr + 8);
321 | 				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
322 | 			}
323 | 
324 | 			const timeOrigin = Date.now() - performance.now();
325 | 			this.importObject = {
326 | 				go: {
327 | 					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
328 | 					// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
329 | 					// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
330 | 					// This changes the SP, thus we have to update the SP used by the imported function.
331 | 
332 | 					// func wasmExit(code int32)
333 | 					"runtime.wasmExit": (sp) => {
334 | 						const code = mem().getInt32(sp + 8, true);
335 | 						this.exited = true;
336 | 						delete this._inst;
337 | 						delete this._values;
338 | 						delete this._refs;
339 | 						this.exit(code);
340 | 					},
341 | 
342 | 					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
343 | 					"runtime.wasmWrite": (sp) => {
344 | 						const fd = getInt64(sp + 8);
345 | 						const p = getInt64(sp + 16);
346 | 						const n = mem().getInt32(sp + 24, true);
347 | 						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
348 | 					},
349 | 
350 | 					// func nanotime() int64
351 | 					"runtime.nanotime": (sp) => {
352 | 						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
353 | 					},
354 | 
355 | 					// func walltime() (sec int64, nsec int32)
356 | 					"runtime.walltime": (sp) => {
357 | 						const msec = (new Date).getTime();
358 | 						setInt64(sp + 8, msec / 1000);
359 | 						mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
360 | 					},
361 | 
362 | 					// func scheduleTimeoutEvent(delay int64) int32
363 | 					"runtime.scheduleTimeoutEvent": (sp) => {
364 | 						const id = this._nextCallbackTimeoutID;
365 | 						this._nextCallbackTimeoutID++;
366 | 						this._scheduledTimeouts.set(id, setTimeout(
367 | 							() => { this._resume(); },
368 | 							getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
369 | 						));
370 | 						mem().setInt32(sp + 16, id, true);
371 | 					},
372 | 
373 | 					// func clearTimeoutEvent(id int32)
374 | 					"runtime.clearTimeoutEvent": (sp) => {
375 | 						const id = mem().getInt32(sp + 8, true);
376 | 						clearTimeout(this._scheduledTimeouts.get(id));
377 | 						this._scheduledTimeouts.delete(id);
378 | 					},
379 | 
380 | 					// func getRandomData(r []byte)
381 | 					"runtime.getRandomData": (sp) => {
382 | 						crypto.getRandomValues(loadSlice(sp + 8));
383 | 					},
384 | 
385 | 					// func stringVal(value string) ref
386 | 					"syscall/js.stringVal": (sp) => {
387 | 						storeValue(sp + 24, loadString(sp + 8));
388 | 					},
389 | 
390 | 					// func valueGet(v ref, p string) ref
391 | 					"syscall/js.valueGet": (sp) => {
392 | 						const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
393 | 						sp = this._inst.exports.getsp(); // see comment above
394 | 						storeValue(sp + 32, result);
395 | 					},
396 | 
397 | 					// func valueSet(v ref, p string, x ref)
398 | 					"syscall/js.valueSet": (sp) => {
399 | 						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
400 | 					},
401 | 
402 | 					// func valueIndex(v ref, i int) ref
403 | 					"syscall/js.valueIndex": (sp) => {
404 | 						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
405 | 					},
406 | 
407 | 					// valueSetIndex(v ref, i int, x ref)
408 | 					"syscall/js.valueSetIndex": (sp) => {
409 | 						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
410 | 					},
411 | 
412 | 					// func valueCall(v ref, m string, args []ref) (ref, bool)
413 | 					"syscall/js.valueCall": (sp) => {
414 | 						try {
415 | 							const v = loadValue(sp + 8);
416 | 							const m = Reflect.get(v, loadString(sp + 16));
417 | 							const args = loadSliceOfValues(sp + 32);
418 | 							const result = Reflect.apply(m, v, args);
419 | 							sp = this._inst.exports.getsp(); // see comment above
420 | 							storeValue(sp + 56, result);
421 | 							mem().setUint8(sp + 64, 1);
422 | 						} catch (err) {
423 | 							storeValue(sp + 56, err);
424 | 							mem().setUint8(sp + 64, 0);
425 | 						}
426 | 					},
427 | 
428 | 					// func valueInvoke(v ref, args []ref) (ref, bool)
429 | 					"syscall/js.valueInvoke": (sp) => {
430 | 						try {
431 | 							const v = loadValue(sp + 8);
432 | 							const args = loadSliceOfValues(sp + 16);
433 | 							const result = Reflect.apply(v, undefined, args);
434 | 							sp = this._inst.exports.getsp(); // see comment above
435 | 							storeValue(sp + 40, result);
436 | 							mem().setUint8(sp + 48, 1);
437 | 						} catch (err) {
438 | 							storeValue(sp + 40, err);
439 | 							mem().setUint8(sp + 48, 0);
440 | 						}
441 | 					},
442 | 
443 | 					// func valueNew(v ref, args []ref) (ref, bool)
444 | 					"syscall/js.valueNew": (sp) => {
445 | 						try {
446 | 							const v = loadValue(sp + 8);
447 | 							const args = loadSliceOfValues(sp + 16);
448 | 							const result = Reflect.construct(v, args);
449 | 							sp = this._inst.exports.getsp(); // see comment above
450 | 							storeValue(sp + 40, result);
451 | 							mem().setUint8(sp + 48, 1);
452 | 						} catch (err) {
453 | 							storeValue(sp + 40, err);
454 | 							mem().setUint8(sp + 48, 0);
455 | 						}
456 | 					},
457 | 
458 | 					// func valueLength(v ref) int
459 | 					"syscall/js.valueLength": (sp) => {
460 | 						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
461 | 					},
462 | 
463 | 					// valuePrepareString(v ref) (ref, int)
464 | 					"syscall/js.valuePrepareString": (sp) => {
465 | 						const str = encoder.encode(String(loadValue(sp + 8)));
466 | 						storeValue(sp + 16, str);
467 | 						setInt64(sp + 24, str.length);
468 | 					},
469 | 
470 | 					// valueLoadString(v ref, b []byte)
471 | 					"syscall/js.valueLoadString": (sp) => {
472 | 						const str = loadValue(sp + 8);
473 | 						loadSlice(sp + 16).set(str);
474 | 					},
475 | 
476 | 					// func valueInstanceOf(v ref, t ref) bool
477 | 					"syscall/js.valueInstanceOf": (sp) => {
478 | 						mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
479 | 					},
480 | 
481 | 					"debug": (value) => {
482 | 						console.log(value);
483 | 					},
484 | 				}
485 | 			};
486 | 		}
487 | 
488 | 		async run(instance) {
489 | 			this._inst = instance;
490 | 			this._values = [ // TODO: garbage collection
491 | 				NaN,
492 | 				0,
493 | 				null,
494 | 				true,
495 | 				false,
496 | 				global,
497 | 				this._inst.exports.mem,
498 | 				this,
499 | 			];
500 | 			this._refs = new Map();
501 | 			this.exited = false;
502 | 
503 | 			const mem = new DataView(this._inst.exports.mem.buffer)
504 | 
505 | 			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
506 | 			let offset = 4096;
507 | 
508 | 			const strPtr = (str) => {
509 | 				let ptr = offset;
510 | 				new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
511 | 				offset += str.length + (8 - (str.length % 8));
512 | 				return ptr;
513 | 			};
514 | 
515 | 			const argc = this.argv.length;
516 | 
517 | 			const argvPtrs = [];
518 | 			this.argv.forEach((arg) => {
519 | 				argvPtrs.push(strPtr(arg));
520 | 			});
521 | 
522 | 			const keys = Object.keys(this.env).sort();
523 | 			argvPtrs.push(keys.length);
524 | 			keys.forEach((key) => {
525 | 				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
526 | 			});
527 | 
528 | 			const argv = offset;
529 | 			argvPtrs.forEach((ptr) => {
530 | 				mem.setUint32(offset, ptr, true);
531 | 				mem.setUint32(offset + 4, 0, true);
532 | 				offset += 8;
533 | 			});
534 | 
535 | 			this._inst.exports.run(argc, argv);
536 | 			if (this.exited) {
537 | 				this._resolveExitPromise();
538 | 			}
539 | 			await this._exitPromise;
540 | 		}
541 | 
542 | 		_resume() {
543 | 			if (this.exited) {
544 | 				throw new Error("Go program has already exited");
545 | 			}
546 | 			this._inst.exports.resume();
547 | 			if (this.exited) {
548 | 				this._resolveExitPromise();
549 | 			}
550 | 		}
551 | 
552 | 		_makeFuncWrapper(id) {
553 | 			const go = this;
554 | 			return function () {
555 | 				const event = { id: id, this: this, args: arguments };
556 | 				go._pendingEvent = event;
557 | 				go._resume();
558 | 				return event.result;
559 | 			};
560 | 		}
561 | 	}
562 | })();
563 | 


--------------------------------------------------------------------------------