From 6803234760a60d61e94782cd075bad3e4183d5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Dano?= Date: Thu, 27 Feb 2020 19:49:01 +0100 Subject: [PATCH] wast: first import of a wast format scanner+parser - `tokens.go`: Defines the Token type generated by the scanner and several utility functions around it - `test.wast`: A .wast test file extracted from [wasm's spec tests](https://github.com/WebAssembly/spec/blob/master/test/core/names.wast) - `scanner.go`: Implements the actual scanner and utilty functions regarding runes - `scanner_test.go`: A dead simple test case against the `test.wast` file. It doesn't verify correctness yet. - Added several new token kind constants to list different typed instructions such as `i64.reinterpret/f64` or `i32.add` - Implemented the `(*Scanner).scanTypedReserved()` method to identify well and ill-formed typed instructions - Better Scanner test interface, the test suite now takes a `-wast-file` flag that must point to the `.wast` file to Scan and test against. In order to test: - Create (or use a [`spec/test/core`](https://github.com/WebAssembly/spec/tree/master/test/core) ) wast file Updates go-interpreter/wagon#34. --- wast/doc.go | 8 + wast/scanner.go | 567 +++++++++++++++++++++++++++++++++++++++++++ wast/scanner_test.go | 66 +++++ wast/token.go | 474 ++++++++++++++++++++++++++++++++++++ wast/write.go | 3 - 5 files changed, 1115 insertions(+), 3 deletions(-) create mode 100644 wast/doc.go create mode 100644 wast/scanner.go create mode 100644 wast/scanner_test.go create mode 100644 wast/token.go diff --git a/wast/doc.go b/wast/doc.go new file mode 100644 index 00000000..ea68669c --- /dev/null +++ b/wast/doc.go @@ -0,0 +1,8 @@ +// Copyright 2017 The go-interpreter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package wast provides functions to handle wast files. +// +// See https://webassembly.github.io/spec/core/text/ +package wast diff --git a/wast/scanner.go b/wast/scanner.go new file mode 100644 index 00000000..c54cecfb --- /dev/null +++ b/wast/scanner.go @@ -0,0 +1,567 @@ +// Copyright 2017 The go-interpreter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wast + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/go-interpreter/wagon/wasm" +) + +type Scanner struct { + file string + inBuf *bytes.Buffer + + ch rune + eof bool + token *Token + + offset int + + Line int + Column int + + Errors []error +} + +func NewScanner(path string) *Scanner { + var s Scanner + + s.file = path + buf, err := ioutil.ReadFile(path) + if err != nil { + s.raise(err) + return &s + } + + s.inBuf = bytes.NewBuffer(buf) + + s.eof = false + s.Line = 1 + s.Column = 1 + s.offset = 0 + + return &s +} + +const ( + eofRune = -1 + errRune = -2 +) + +func (s *Scanner) peek() rune { + if s.eof { + return eofRune + } + + r, _, err := s.inBuf.ReadRune() + defer s.inBuf.UnreadRune() // rewind + + switch { + case err == io.EOF: + return eofRune + case err != nil: + s.raise(err) + return errRune + } + + return r +} + +func (s *Scanner) next() rune { + if s.eof { + return eofRune + } + + r, n, err := s.inBuf.ReadRune() + switch { + case err == io.EOF: + s.eof = true + s.ch = eofRune + s.offset += n + s.Column++ + return eofRune + case err != nil: + s.raise(err) + return errRune + } + + if r == '\n' { + s.Column = 0 + s.Line++ + } + + s.offset += n + s.Column++ + s.ch = r + + return r +} + +func (s *Scanner) match(r rune) bool { + if s.peek() == r { + s.next() + return true + } + return false +} + +func (s *Scanner) matchIf(f func(rune) bool) bool { + if f(s.peek()) { + s.next() + return true + } + return false +} + +func (s *Scanner) Next() (token *Token) { + s.token = &Token{ + Line: s.Line, + Column: s.Column, + } + token = s.token + + if s.match(eofRune) { + token.Kind = EOF + s.next() + return + } + + switch { + case s.matchIf(isSpace): // ignore spaces + case s.match('('): + if s.match(';') { + s.scanBlockComment() + return s.Next() + } + token.Text = "(" + token.Kind = LPAR + return + case s.match(')'): + token.Text = ")" + token.Kind = RPAR + return + case s.match(';'): + if s.match(';') { + s.scanLineComment() + return s.Next() + } + s.errorf("unexpected character ';'") + case s.match('$'): // names/vars + token.Kind = VAR + token.Text = "$" + s.scanVar() + return + case s.match('"'): + s.scanString() + return + case s.matchIf(isReserved): + s.scanReserved() + return + case s.matchIf(isUtf8): + s.errorf("malformed operator") + s.next() + default: + s.errorf("malformed UTF-8 encoding") + s.next() + } + + return s.Next() +} + +func (s *Scanner) scanString() { + s.token.Kind = STRING + s.token.Text = "" + for !s.eof { + switch { + case s.eof || s.match('\n'): + s.errorf("unclosed string literal") + return + case s.matchIf(unicode.IsControl): + s.errorf("illegal control character in string literal") + case s.match('"'): + return + case s.match('\\'): + s.scanEscape() + // case s.matchIf(isStringRune): + default: + s.next() + s.token.Text += string(s.ch) + } + } +} + +func (s *Scanner) scanEscape() bool { + // escape slash is already matched + switch s.next() { + case 'n': + s.token.Text += "\n" + case 'r': + s.token.Text += "\r" + case 't': + s.token.Text += "\t" + case '\\': + s.token.Text += "\\" + case '\'': + s.token.Text += "'" + case '"': + s.token.Text += "\"" + case 'u': // unicode + if !s.match('{') { + s.errorf("missing opening '{' in unicode escape sequence") + s.token.Text += "\\u" + return false + } + + esc := "" + for s.matchIf(isHexDigit) { + esc += string(s.ch) + } + + switch { + case len(esc) == 0 && s.match('}'): + s.errorf("empty unicode escape sequence") + s.token.Text += "\\u{}" + return false + case len(esc) == 0 && !s.match('}'): + rtext := safeRune(s.peek()) + s.errorf("unexpected character in unicode escape sequence '%s'", rtext) + s.token.Text += "\\u" + rtext + return false + case len(esc) > 0 && !s.match('}'): + s.errorf("missing closing '}' in unicode escape sequence") + s.token.Text += "\\u{" + esc + return false + } + + n, err := strconv.ParseInt(esc, 16, 0) + if err != nil { + s.raise(err) + s.token.Text += "\\u{" + esc + "}" + return false + } + + u := make([]byte, 4) + utf8.EncodeRune(u, rune(n)) + s.token.Text += string(bytes.Trim(u, "\x00")) // remove NULL bytes + default: // hexadecimal + if !isHexDigit(s.ch) { + rtext := safeRune(s.ch) + s.errorf("unexpected character in hexadecimal escape sequence '%s'", rtext) + s.token.Text += "\\" + rtext + return false + } + + esc := string(s.ch) + if !s.matchIf(isHexDigit) { + rtext := safeRune(s.peek()) + s.errorf("unexpected character in hexadecimal escape sequence '%s'", rtext) + s.token.Text += "\\" + esc + rtext + return false + } + esc += string(s.ch) + + n, err := strconv.ParseInt(esc, 16, 0) + if err != nil { + s.raise(err) + s.token.Text += "\\" + esc + return false + } + s.token.Text += string(n) + } + return true +} + +func (s *Scanner) scanVar() { + if s.match('_') || s.matchIf(isLetter) || s.matchIf(isDigit) || s.matchIf(isSymbol) { + s.token.Text += string(s.ch) + s.scanVar() + } + + if len(s.token.Text) == 1 { + s.errorf("empty $-name") + } +} + +func (s *Scanner) scanReserved() { + s.token.Text = string(s.ch) + for s.matchIf(isReserved) { + s.token.Text += string(s.ch) + + if isType(s.token.Text) && s.match('.') { + s.scanTypedReserved(s.token.Text) + return + } + } + + // isolated type token 'i32', 'f64', ... + if isType(s.token.Text) { + s.token.Kind = VALUE_TYPE + s.token.Data = valueTypeOf(s.token.Text) + return + } + + // Basic instruction / reserved word + if k, ok := tokenKindOf[s.token.Text]; ok { + s.token.Kind = k + } +} + +func (s *Scanner) scanTypedReserved(t string) { + var instr string + + s.token.Text += string(s.ch) // '.' + for s.matchIf(isReserved) { + instr += string(s.ch) + } + s.token.Text += instr + + s.token.Data = valueTypeOf(t) + if k, ok := typedKindOf[instr]; ok { + s.token.Kind = k + return + } + + s.errorf("unkown operator") +} + +func (s *Scanner) scanLineComment() { + for s.next() != '\n' { + } +} + +func (s *Scanner) scanBlockComment() { + for depth := 1; depth > 0; { + switch { + case s.eof: + s.errorf("unclosed comment") + return + case s.match('(') && s.match(';'): + depth++ + case s.match(';') && s.match(')'): + depth-- + default: + s.next() + } + } +} + +const ( + scanErrPrefix = "error: " + scanWarnPrefix = "warning: " +) + +// errorf generates a new scanner error appended to the scanner's Errors field +func (s *Scanner) errorf(fmtStr string, args ...interface{}) { + pfx := fmt.Sprintf("%s ~ line %d, column %d\n => ", s.file, s.token.Line, s.token.Column) + err := fmt.Errorf(scanErrPrefix+pfx+fmtStr+"\n", args...) + s.Errors = append(s.Errors, err) +} + +// raise directly promote any error to a printable Scanner error +func (s *Scanner) raise(err error) { + err2 := fmt.Errorf(scanErrPrefix+"%s\n %s\n", s.file, err.Error()) + s.Errors = append(s.Errors, err2) +} + +func safeRune(r rune) string { + switch r { + case '\n': + return "\\n" + case '\r': + return "\\r" + case '\t': + return "\\t" + case '\'': + return "\\'" + case '"': + return "\\\"" + default: + if unicode.IsPrint(r) { + return string(r) + } + return fmt.Sprintf("\\%x", r) + } +} + +func isUtf8(r rune) bool { + return utf8.ValidRune(r) +} + +func isReserved(r rune) bool { + return !isSpace(r) && strings.IndexRune("\"();", r) < 0 +} + +func isSpace(r rune) bool { + switch r { + case ' ', '\n', '\r', '\t': + return true + default: + return false + } +} + +func isSymbol(r rune) bool { + return strings.IndexRune("+-*/\\^~=<>!?@#$%&|:`.'", r) > 0 +} + +func isSign(r rune) bool { + return r == '-' || r == '+' +} + +func isDigit(r rune) bool { + return r >= '0' && r <= '9' +} + +func isHexDigit(r rune) bool { + if isDigit(r) { + return true + } + return (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') +} + +func isLowerLetter(r rune) bool { + return r >= 'a' && r <= 'z' +} + +func isUpperLetter(r rune) bool { + return r >= 'A' && r <= 'Z' +} + +func isLetter(r rune) bool { + return isLowerLetter(r) || isUpperLetter(r) +} + +func isType(s string) bool { + switch s { + case "i32", "i64", "f32", "f64": + return true + default: + return false + } +} + +func isNum(s string) bool { + if len(s) == 0 || !isDigit(rune(s[0])) { + return false + } + + for i := 0; i < len(s); i++ { + if s[i] == '_' { + i++ + if i >= len(s) { + return false + } + } + if !isDigit(rune(s[i])) { + return false + } + } + return true +} + +func isNat(s string) bool { + if strings.HasPrefix(s, "0x") { + return isHexNum(s[2:]) + } + return isNat(s) +} + +func isInt(s string) bool { + if len(s) == 0 || !isSign(rune(s[0])) { + return false + } + return isNat(s[1:]) +} + +func isFloat(s string) bool { + if len(s) == 0 { + return false + } + + switch s[0] { + case '+', '-': + s = s[1:] // strip the string from the sign + } + + switch { + case s == "inf": + return true + case s == "nan": + return true + case strings.HasPrefix(s, "nan:0x"): + return isHexNum(s[6:]) // len("nan:0x") == 6 + } + + if strings.HasPrefix(s, "0x") { + period := strings.IndexRune(s[2:], '.') + if period <= 1 { + // there must be an hexnum between the '0x' and the '.' + return false + } + + units := s[2 : 2+period] + if 2+period == len(s) { + return isHexNum(units) + } + + decimals := s[2+period+1:] + return isHexNum(units) && isHexNum(decimals) + } + + return false +} + +func isHexNum(s string) bool { + if len(s) == 0 || !isHexDigit(rune(s[0])) { + return false + } + + for i := 0; i < len(s); i++ { + if s[i] == '_' { + i++ + if i >= len(s) { + return false + } + } + if !isHexDigit(rune(s[i])) { + return false + } + } + return true +} + +func digitValue(r rune) int { + switch { + case isDigit(r): + return int(r) - '0' + case r >= 'a' && r <= 'f': + return int(r) - 'a' + 10 + case r >= 'A' && r <= 'F': + return int(r) - 'A' + 10 + } + return 16 // max +} + +func valueTypeOf(s string) wasm.ValueType { + switch s { + case "i32": + return wasm.ValueTypeI32 + case "i64": + return wasm.ValueTypeI64 + case "f32": + return wasm.ValueTypeF32 + case "f64": + return wasm.ValueTypeF64 + } + return 0 // TODO find a suitable error ValueType value +} diff --git a/wast/scanner_test.go b/wast/scanner_test.go new file mode 100644 index 00000000..7007e3ab --- /dev/null +++ b/wast/scanner_test.go @@ -0,0 +1,66 @@ +// Copyright 2017 The go-interpreter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wast + +import ( + "fmt" + "testing" +) + +func TestScanner(t *testing.T) { + for _, fname := range []string{ + // "../exec/testdata/spec/address.wast", // FIXME + "../exec/testdata/spec/block.wast", + "../exec/testdata/spec/break-drop.wast", + "../exec/testdata/spec/br_if.wast", + // "../exec/testdata/spec/br_table.wast", // FIXME + // "../exec/testdata/spec/br.wast", // FIXME + "../exec/testdata/spec/call_indirect.wast", + // "../exec/testdata/spec/endianness.wast", // FIXME + "../exec/testdata/spec/fac.wast", + "../exec/testdata/spec/forward.wast", + "../exec/testdata/spec/get_local.wast", + "../exec/testdata/spec/globals.wast", + "../exec/testdata/spec/if.wast", + // "../exec/testdata/spec/loop.wast", // FIXME + "../exec/testdata/spec/memory_redundancy.wast", + "../exec/testdata/spec/names.wast", + "../exec/testdata/spec/nop.wast", + "../exec/testdata/spec/resizing.wast", + // "../exec/testdata/spec/return.wast", // FIXME + "../exec/testdata/spec/select.wast", + "../exec/testdata/spec/switch.wast", + "../exec/testdata/spec/tee_local.wast", + "../exec/testdata/spec/traps_int_div.wast", + "../exec/testdata/spec/traps_int_rem.wast", + // "../exec/testdata/spec/traps_mem.wast", // FIXME + "../exec/testdata/spec/unwind.wast", + } { + t.Run(fname, func(t *testing.T) { + + s := NewScanner(fname) + if len(s.Errors) > 0 { + fmt.Println(s.Errors[0]) + return + } + + var tok *Token + tok = s.Next() + for tok.Kind != EOF { + fmt.Printf("%d:%d %s\n", tok.Line, tok.Column, tok.String()) + tok = s.Next() + } + fmt.Printf("%d:%d %s\n", tok.Line, tok.Column, tok.String()) + + for _, err := range s.Errors { + fmt.Print(err) + } + + if len(s.Errors) > 0 { + t.Errorf("wast: failed with %d errors", len(s.Errors)) + } + }) + } +} diff --git a/wast/token.go b/wast/token.go new file mode 100644 index 00000000..a9c94f8d --- /dev/null +++ b/wast/token.go @@ -0,0 +1,474 @@ +// Copyright 2017 The go-interpreter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wast + +import "fmt" + +type Token struct { + Kind TokenKind + Text string + Line int + Column int + Data interface{} // may be nil +} + +func (t *Token) Copy() *Token { + return &Token{ + Kind: t.Kind, + Text: t.Text, + Line: t.Line, + Column: t.Column, + Data: t.Data, + } +} + +func (t *Token) String() string { + // Don't print unwanted characters (eg. newline) + safe := "" + for _, r := range t.Text { + safe += safeRune(r) + } + + switch t.Kind { + case EOF: + return "" + case STRING: + return fmt.Sprintf("<%s \"%s\">", t.Kind, safe) + default: + return fmt.Sprintf("<%s '%s'>", t.Kind, safe) + } +} + +type TokenKind int + +const ( + NAT TokenKind = iota + INT + FLOAT + STRING + VAR + VALUE_TYPE + ANYFUNC + MUT + LPAR + RPAR + + NOP + DROP + BLOCK + END + IF + THEN + ELSE + SELECT + LOOP + BR + BR_IF + BR_TABLE + + CALL + CALL_INDIRECT + RETURN + GET_LOCAL + SET_LOCAL + TEE_LOCAL + GET_GLOBAL + SET_GLOBAL + + LOAD + STORE + OFFSET_EQ_NAT + ALIGN_EQ_NAT + + CONST + LOAD_8 + LOAD_16 + LOAD_32 + STORE_8 + STORE_16 + STORE_32 + + CLZ + CTZ + POPCNT + NEG + ABS + SQRT + CEIL + FLOOR + TRUNC + NEAREST + + ADD + SUB + MUL + DIV_S + DIV_U + REM_S + REM_U + AND + OR + XOR + SHL + SHR_S + SHR_U + ROTL + ROTR + MIN + MAX + COPYSIGN + + EQZ + EQ + NE + LT_S + LT_U + LE_S + LE_U + GT_S + GT_U + GE_S + GE_U + LT + LE + GT + GE + + WRAP + EXTEND_S + EXTEND_U + DEMOTE + PROMOTE + TRUNC_S_F32 + TRUNC_U_F32 + TRUNC_S_F64 + TRUNC_U_F64 + CONVERT_S_I32 + CONVERT_U_I32 + CONVERT_S_I64 + CONVERT_U_I64 + REINTERPRET_I32 + REINTERPRET_I64 + REINTERPRET_F32 + REINTERPRET_F64 + + UNREACHABLE + CURRENT_MEMORY + GROW_MEMORY + + FUNC + START + TYPE + PARAM + RESULT + LOCAL + GLOBAL + + TABLE + ELEM + MEMORY + DATA + OFFSET + IMPORT + EXPORT + + MODULE + BIN + QUOTE + + SCRIPT + REGISTER + INVOKE + GET + + ASSERT_MALFORMED + ASSERT_INVALID + ASSERT_SOFT_INVALID + ASSERT_UNLINKABLE + ASSERT_RETURN + ASSERT_RETURN_CANONICAL_NAN + ASSERT_RETURN_ARITHMETIC_NAN + ASSERT_TRAP + ASSERT_EXHAUSTION + INPUT + OUTPUT + EOF +) + +var tokenKindOf = map[string]TokenKind{ + "anyfunc": ANYFUNC, + "mut": MUT, + "nop": NOP, + "drop": DROP, + "block": BLOCK, + "end": END, + "if": IF, + "then": THEN, + "else": ELSE, + "select": SELECT, + "loop": LOOP, + "br": BR, + "br_if": BR_IF, + "br_table": BR_TABLE, + "call": CALL, + "call_indirect": CALL_INDIRECT, + "return": RETURN, + "get_local": GET_LOCAL, + "set_local": SET_LOCAL, + "tee_local": TEE_LOCAL, + "get_global": GET_GLOBAL, + "set_global": SET_GLOBAL, + "unreachable": UNREACHABLE, + "current_memory": CURRENT_MEMORY, + "grow_memory": GROW_MEMORY, + "func": FUNC, + "start": START, + "type": TYPE, + "param": PARAM, + "result": RESULT, + "local": LOCAL, + "global": GLOBAL, + "table": TABLE, + "elem": ELEM, + "memory": MEMORY, + "data": DATA, + "offset": OFFSET, + "import": IMPORT, + "export": EXPORT, + "module": MODULE, + "binary": BIN, + "quote": QUOTE, + "script": SCRIPT, + "register": REGISTER, + "invoke": INVOKE, + "get": GET, + "assert_malformed": ASSERT_MALFORMED, + "assert_invalid": ASSERT_INVALID, + "assert_soft_invalid": ASSERT_SOFT_INVALID, + "assert_unlinkabled": ASSERT_UNLINKABLE, + "assert_return": ASSERT_RETURN, + "assert_return_canonical_nan": ASSERT_RETURN_CANONICAL_NAN, + "assert_return_arithmetic_nan": ASSERT_RETURN_ARITHMETIC_NAN, + "assert_trap": ASSERT_TRAP, + "assert_exhaustion": ASSERT_EXHAUSTION, + "input": INPUT, + "output": OUTPUT, +} + +var typedKindOf = map[string]TokenKind{ + "const": CONST, + "load": LOAD, + "store": STORE, + "load8": LOAD_8, + "load16": LOAD_16, + "load32": LOAD_32, + "store8": STORE_8, + "store16": STORE_16, + "store32": STORE_32, + "clz": CLZ, + "ctz": CTZ, + "popcnt": POPCNT, + "neg": NEG, + "abs": ABS, + "sqrt": SQRT, + "ceil": CEIL, + "floor": FLOOR, + "trunc": TRUNC, + "nearest": NEAREST, + "add": ADD, + "sub": SUB, + "mul": MUL, + "div_s": DIV_S, + "div_u": DIV_U, + "rem_s": REM_S, + "rem_u": REM_U, + "and": AND, + "or": OR, + "xor": XOR, + "shl": SHL, + "shr_s": SHR_S, + "shr_u": SHR_U, + "rotl": ROTL, + "rort": ROTR, + "min": MIN, + "max": MAX, + "copysign": COPYSIGN, + "eqz": EQZ, + "eq": EQ, + "ne": NE, + "lt_s": LT_S, + "lt_u": LT_U, + "le_s": LE_S, + "le_u": LE_U, + "gt_s": GT_S, + "gt_u": GT_U, + "ge_s": GE_S, + "ge_u": GE_U, + "lt": LT, + "le": LE, + "gt": GT, + "ge": GE, + "wrap/i64": WRAP, + "extend_s/i32": EXTEND_S, + "extend_u/i32": EXTEND_U, + "demote/f64": DEMOTE, + "promote/f32": PROMOTE, + "trunc_s/f32": TRUNC_S_F32, + "trunc_u/f32": TRUNC_U_F32, + "trunc_s/f64": TRUNC_S_F64, + "trunc_u/f64": TRUNC_U_F64, + "convert_s/i32": CONVERT_S_I32, + "convert_u/i32": CONVERT_U_I32, + "convert_s/i64": CONVERT_S_I64, + "convert_u/i64": CONVERT_U_I64, + "reinterpret/i32": REINTERPRET_I32, + "reinterpret/i64": REINTERPRET_I64, + "reinterpret/f32": REINTERPRET_F32, + "reinterpret/f64": REINTERPRET_F64, +} + +var tokenStrings = [...]string{ + NAT: "NAT", + INT: "INT", + FLOAT: "FLOAT", + STRING: "STRING", + VAR: "VAR", + VALUE_TYPE: "VALUE_TYPE", + ANYFUNC: "ANYFUNC", + MUT: "MUT", + LPAR: "LPAR", + RPAR: "RPAR", + NOP: "NOP", + DROP: "DROP", + BLOCK: "BLOCK", + END: "END", + IF: "IF", + THEN: "THEN", + ELSE: "ELSE", + SELECT: "SELECT", + LOOP: "LOOP", + BR: "BR", + BR_IF: "BR_IF", + BR_TABLE: "BR_TABLE", + CALL: "CALL", + CALL_INDIRECT: "CALL_INDIRECT", + RETURN: "RETURN", + GET_LOCAL: "GET_LOCAL", + SET_LOCAL: "SET_LOCAL", + TEE_LOCAL: "TEE_LOCAL", + GET_GLOBAL: "GET_GLOBAL", + SET_GLOBAL: "SET_GLOBAL", + LOAD: "LOAD", + STORE: "STORE", + OFFSET_EQ_NAT: "OFFSET_EQ_NAT", + ALIGN_EQ_NAT: "ALIGN_EQ_NAT", + CONST: "CONST", + LOAD_8: "LOAD_8", + LOAD_16: "LOAD_16", + LOAD_32: "LOAD_32", + STORE_8: "STORE_8", + STORE_16: "STORE_16", + STORE_32: "STORE_32", + CLZ: "CLZ", + CTZ: "CTZ", + POPCNT: "POPCNT", + NEG: "NEG", + ABS: "ABS", + SQRT: "SQRT", + CEIL: "CEIL", + FLOOR: "FLOOR", + TRUNC: "TRUNC", + NEAREST: "NEAREST", + ADD: "ADD", + SUB: "SUB", + MUL: "MUL", + DIV_S: "DIV_S", + DIV_U: "DIV_U", + REM_S: "REM_S", + REM_U: "REM_U", + AND: "AND", + OR: "OR", + XOR: "XOR", + SHL: "SHL", + SHR_S: "SHR_S", + SHR_U: "SHR_U", + ROTL: "ROTL", + ROTR: "ROTR", + MIN: "MIN", + MAX: "MAX", + COPYSIGN: "COPYSIGN", + EQZ: "EQZ", + EQ: "EQ", + NE: "NE", + LT_S: "LT_S", + LT_U: "LT_U", + LE_S: "LE_S", + LE_U: "LE_U", + GT_S: "GT_S", + GT_U: "GT_U", + GE_S: "GE_S", + GE_U: "GE_U", + LT: "LT", + LE: "LE", + GT: "GT", + GE: "GE", + WRAP: "WRAP", + EXTEND_S: "EXTEND_S", + EXTEND_U: "EXTEND_U", + DEMOTE: "DEMOTE", + PROMOTE: "PROMOTE", + TRUNC_S_F32: "TRUNC_S_F32", + TRUNC_U_F32: "TRUNC_U_F32", + TRUNC_S_F64: "TRUNC_S_F64", + TRUNC_U_F64: "TRUNC_U_F64", + CONVERT_S_I32: "CONVERT_S_I32", + CONVERT_U_I32: "CONVERT_U_I32", + CONVERT_S_I64: "CONVERT_S_I64", + CONVERT_U_I64: "CONVERT_U_I64", + REINTERPRET_I32: "REINTERPRET_I32", + REINTERPRET_I64: "REINTERPRET_I64", + REINTERPRET_F32: "REINTERPRET_F32", + REINTERPRET_F64: "REINTERPRET_F64", + UNREACHABLE: "UNREACHABLE", + CURRENT_MEMORY: "CURRENT_MEMORY", + GROW_MEMORY: "GROW_MEMORY", + FUNC: "FUNC", + START: "START", + TYPE: "TYPE", + PARAM: "PARAM", + RESULT: "RESULT", + LOCAL: "LOCAL", + GLOBAL: "GLOBAL", + TABLE: "TABLE", + ELEM: "ELEM", + MEMORY: "MEMORY", + DATA: "DATA", + OFFSET: "OFFSET", + IMPORT: "IMPORT", + EXPORT: "EXPORT", + MODULE: "MODULE", + BIN: "BIN", + QUOTE: "QUOTE", + SCRIPT: "SCRIPT", + REGISTER: "REGISTER", + INVOKE: "INVOKE", + GET: "GET", + ASSERT_MALFORMED: "ASSERT_MALFORMED", + ASSERT_INVALID: "ASSERT_INVALID", + ASSERT_SOFT_INVALID: "ASSERT_SOFT_INVALID", + ASSERT_UNLINKABLE: "ASSERT_UNLINKABLE", + ASSERT_RETURN: "ASSERT_RETURN", + ASSERT_RETURN_CANONICAL_NAN: "ASSERT_RETURN_CANONICAL_NAN", + ASSERT_RETURN_ARITHMETIC_NAN: "ASSERT_RETURN_ARITHMETIC_NAN", + ASSERT_TRAP: "ASSERT_TRAP", + ASSERT_EXHAUSTION: "ASSERT_EXHAUSTION", + INPUT: "INPUT", + OUTPUT: "OUTPUT", + EOF: "EOF", +} + +func (t TokenKind) String() string { + return tokenStrings[t] +} diff --git a/wast/write.go b/wast/write.go index ba4821c9..864e63b7 100644 --- a/wast/write.go +++ b/wast/write.go @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package wast implements a WebAssembly text format. package wast -// See https://webassembly.github.io/spec/core/text/ - import ( "bufio" "bytes"