File: WastParser.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (303 lines) | stat: -rw-r--r-- 12,806 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
import WasmParser
import WasmTypes

protocol WastConstInstructionVisitor: InstructionVisitor {
    mutating func visitRefExtern(value: UInt32) throws
}

/// A parser for WAST format.
/// You can find its grammar definition in the [WebAssembly spec repository](https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/README.md#scripts)
struct WastParser {
    var parser: Parser
    let features: WasmFeatureSet

    init(_ input: String, features: WasmFeatureSet) {
        self.parser = Parser(input)
        self.features = features
    }

    mutating func nextDirective() throws -> WastDirective? {
        var originalParser = parser
        guard (try parser.peek(.leftParen)) != nil else { return nil }
        try parser.consume()
        guard try WastDirective.peek(wastParser: self) else {
            if try peekModuleField() {
                // Parse inline module, which doesn't include surrounding (module)
                let location = originalParser.lexer.location()
                return .module(
                    ModuleDirective(
                        source: .text(try parseWAT(&originalParser, features: features)), id: nil, location: location
                    ))
            }
            throw WatParserError("unexpected wast directive token", location: parser.lexer.location())
        }
        let directive = try WastDirective.parse(wastParser: &self)
        return directive
    }

    private func peekModuleField() throws -> Bool {
        guard let keyword = try parser.peekKeyword() else { return false }
        switch keyword {
        case "data", "elem", "tag", "export", "func",
            "type", "global", "import", "memory",
            "start", "table":
            return true
        default:
            return false
        }
    }

    mutating func parens<T>(_ body: (inout WastParser) throws -> T) throws -> T {
        try parser.expect(.leftParen)
        let result = try body(&self)
        return result
    }

    struct ConstExpressionCollector: WastConstInstructionVisitor {
        let addValue: (Value) -> Void

        mutating func visitI32Const(value: Int32) throws { addValue(.i32(UInt32(bitPattern: value))) }
        mutating func visitI64Const(value: Int64) throws { addValue(.i64(UInt64(bitPattern: value))) }
        mutating func visitF32Const(value: IEEE754.Float32) throws { addValue(.f32(value.bitPattern)) }
        mutating func visitF64Const(value: IEEE754.Float64) throws { addValue(.f64(value.bitPattern)) }
        mutating func visitRefFunc(functionIndex: UInt32) throws {
            addValue(.ref(.function(FunctionAddress(functionIndex))))
        }
        mutating func visitRefNull(type: ReferenceType) throws {
            let value: Reference
            switch type {
            case .externRef: value = .extern(nil)
            case .funcRef: value = .function(nil)
            }
            addValue(.ref(value))
        }

        mutating func visitRefExtern(value: UInt32) throws {
            addValue(.ref(.extern(ExternAddress(value))))
        }
    }

    mutating func constExpression() throws -> [Value] {
        var values: [Value] = []
        var collector = ConstExpressionCollector(addValue: { values.append($0) })
        var exprParser = ExpressionParser<ConstExpressionCollector>(lexer: parser.lexer, features: features)
        while try exprParser.parseWastConstInstruction(visitor: &collector) {}
        parser = exprParser.parser
        return values
    }

    mutating func expectationValues() throws -> [WastExpectValue] {
        var values: [WastExpectValue] = []
        var collector = ConstExpressionCollector(addValue: { values.append(.value($0)) })
        var exprParser = ExpressionParser<ConstExpressionCollector>(lexer: parser.lexer, features: features)
        while true {
            if let expectValue = try exprParser.parseWastExpectValue() {
                values.append(expectValue)
            }
            if try exprParser.parseWastConstInstruction(visitor: &collector) {
                continue
            }
            break
        }
        parser = exprParser.parser
        return values
    }
}

public enum WastExecute {
    case invoke(WastInvoke)
    case wat(Wat)
    case get(module: String?, globalName: String)

    static func parse(wastParser: inout WastParser) throws -> WastExecute {
        let keyword = try wastParser.parser.peekKeyword()
        let execute: WastExecute
        switch keyword {
        case "invoke":
            execute = .invoke(try WastInvoke.parse(wastParser: &wastParser))
        case "module":
            try wastParser.parser.consume()
            execute = .wat(try parseWAT(&wastParser.parser, features: wastParser.features))
            try wastParser.parser.skipParenBlock()
        case "get":
            try wastParser.parser.consume()
            let module = try wastParser.parser.takeId()
            let globalName = try wastParser.parser.expectString()
            execute = .get(module: module?.value, globalName: globalName)
            try wastParser.parser.expect(.rightParen)
        case let keyword?:
            throw WatParserError(
                "unexpected wast execute \(keyword)",
                location: wastParser.parser.lexer.location()
            )
        case nil:
            throw WatParserError("unexpected eof", location: wastParser.parser.lexer.location())
        }
        return execute
    }
}

public struct WastInvoke {
    public let module: String?
    public let name: String
    public let args: [Value]

    static func parse(wastParser: inout WastParser) throws -> WastInvoke {
        try wastParser.parser.expectKeyword("invoke")
        let module = try wastParser.parser.takeId()
        let name = try wastParser.parser.expectString()
        let args = try wastParser.constExpression()
        try wastParser.parser.expect(.rightParen)
        let invoke = WastInvoke(module: module?.value, name: name, args: args)
        return invoke
    }
}

public enum WastExpectValue {
    /// A concrete value that is expected to be returned.
    case value(Value)
    /// A value that is expected to be a canonical NaN.
    /// Corresponds to `f32.const nan:canonical` in WAST.
    case f32CanonicalNaN
    /// A value that is expected to be an arithmetic NaN.
    /// Corresponds to `f32.const nan:arithmetic` in WAST.
    case f32ArithmeticNaN
    /// A value that is expected to be a canonical NaN.
    /// Corresponds to `f64.const nan:canonical` in WAST.
    case f64CanonicalNaN
    /// A value that is expected to be an arithmetic NaN.
    /// Corresponds to `f64.const nan:arithmetic` in WAST.
    case f64ArithmeticNaN
}

/// A directive in a WAST script.
public enum WastDirective {
    case module(ModuleDirective)
    case assertInvalid(module: ModuleDirective, message: String)
    case assertMalformed(module: ModuleDirective, message: String)
    case assertReturn(execute: WastExecute, results: [WastExpectValue])
    case assertTrap(execute: WastExecute, message: String)
    case assertExhaustion(call: WastInvoke, message: String)
    case assertUnlinkable(module: Wat, message: String)
    case register(name: String, moduleId: String?)
    case invoke(WastInvoke)

    static func peek(wastParser: WastParser) throws -> Bool {
        guard let keyword = try wastParser.parser.peekKeyword() else { return false }
        return keyword.starts(with: "assert_") || keyword == "module" || keyword == "register" || keyword == "invoke"
    }

    /// Parse a directive in a WAST script from "keyword ...)" form.
    /// Leading left parenthesis is already consumed, and the trailing right parenthesis should be consumed by this function.
    static func parse(wastParser: inout WastParser) throws -> WastDirective {
        let keyword = try wastParser.parser.peekKeyword()
        switch keyword {
        case "module":
            return .module(try ModuleDirective.parse(wastParser: &wastParser))
        case "assert_invalid":
            try wastParser.parser.consume()
            let module = try wastParser.parens { try ModuleDirective.parse(wastParser: &$0) }
            let message = try wastParser.parser.expectString()
            try wastParser.parser.expect(.rightParen)
            return .assertInvalid(module: module, message: message)
        case "assert_malformed":
            try wastParser.parser.consume()
            let module = try wastParser.parens { try ModuleDirective.parse(wastParser: &$0) }
            let message = try wastParser.parser.expectString()
            try wastParser.parser.expect(.rightParen)
            return .assertMalformed(module: module, message: message)
        case "assert_return":
            try wastParser.parser.consume()
            let execute = try wastParser.parens { try WastExecute.parse(wastParser: &$0) }
            let results = try wastParser.expectationValues()
            try wastParser.parser.expect(.rightParen)
            return .assertReturn(execute: execute, results: results)
        case "assert_trap":
            try wastParser.parser.consume()
            let execute = try wastParser.parens { try WastExecute.parse(wastParser: &$0) }
            let message = try wastParser.parser.expectString()
            try wastParser.parser.expect(.rightParen)
            return .assertTrap(execute: execute, message: message)
        case "assert_exhaustion":
            try wastParser.parser.consume()
            let call = try wastParser.parens { try WastInvoke.parse(wastParser: &$0) }
            let message = try wastParser.parser.expectString()
            try wastParser.parser.expect(.rightParen)
            return .assertExhaustion(call: call, message: message)
        case "assert_unlinkable":
            try wastParser.parser.consume()
            let features = wastParser.features
            let module = try wastParser.parens {
                try $0.parser.expectKeyword("module")
                let wat = try parseWAT(&$0.parser, features: features)
                try $0.parser.skipParenBlock()
                return wat
            }
            let message = try wastParser.parser.expectString()
            try wastParser.parser.expect(.rightParen)
            return .assertUnlinkable(module: module, message: message)
        case "register":
            try wastParser.parser.consume()
            let name = try wastParser.parser.expectString()
            let module = try wastParser.parser.takeId()
            try wastParser.parser.expect(.rightParen)
            return .register(name: name, moduleId: module?.value)
        case "invoke":
            let invoke = try WastInvoke.parse(wastParser: &wastParser)
            return .invoke(invoke)
        case let keyword?:
            throw WatParserError(
                "unexpected wast directive \(keyword)",
                location: wastParser.parser.lexer.location()
            )
        case nil:
            throw WatParserError("unexpected eof", location: wastParser.parser.lexer.location())
        }
    }
}

/// A module representation in "(module ...)" form in WAST.
public struct ModuleDirective {
    /// The source of the module
    public let source: ModuleSource
    /// The name of the module specified in $id form
    public let id: String?
    /// The location of the module in the source
    public let location: Location

    static func parse(wastParser: inout WastParser) throws -> ModuleDirective {
        let location = wastParser.parser.lexer.location()
        try wastParser.parser.expectKeyword("module")
        let id = try wastParser.parser.takeId()
        let source = try ModuleSource.parse(wastParser: &wastParser)
        return ModuleDirective(source: source, id: id?.value, location: location)
    }
}

/// The source of a module in WAST.
public enum ModuleSource {
    /// A parsed WAT module
    case text(Wat)
    /// A text form of WAT module
    case quote([UInt8])
    /// A binary form of WebAssembly module
    case binary([UInt8])

    static func parse(wastParser: inout WastParser) throws -> ModuleSource {
        if let headKeyword = try wastParser.parser.peekKeyword() {
            if headKeyword == "binary" {
                // (module binary "..." "..." ...)
                try wastParser.parser.consume()
                return .binary(try wastParser.parser.expectStringList())
            } else if headKeyword == "quote" {
                // (module quote "..." "..." ...)
                try wastParser.parser.consume()
                return .quote(try wastParser.parser.expectStringList())
            }
        }

        let watModule = try parseWAT(&wastParser.parser, features: wastParser.features)
        try wastParser.parser.skipParenBlock()
        return .text(watModule)
    }
}