summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThomas Lively <7121787+tlively@users.noreply.github.com>2022-06-10 12:30:09 -0700
committerGitHub <noreply@github.com>2022-06-10 12:30:09 -0700
commit093bf06e1115e84c4300eca91197dc31336ee3e5 (patch)
tree9016245be8af54c8e47bdb3c0aaa0ed6f263b3c0 /src
parent9fbfe0f200f716a6c9a045c6a3f2606b99af8dea (diff)
downloadbinaryen-093bf06e1115e84c4300eca91197dc31336ee3e5.tar.gz
binaryen-093bf06e1115e84c4300eca91197dc31336ee3e5.tar.bz2
binaryen-093bf06e1115e84c4300eca91197dc31336ee3e5.zip
[Parser] Begin parsing modules (#4716)
Implement the basic infrastructure for the full WAT parser with just enough detail to parse basic modules that contain only imported globals. Parsing functions correspond to elements of the grammar in the text specification and are templatized over context types that correspond to each phase of parsing. Errors are explicitly propagated via `Result<T>` and `MaybeResult<T>` types. Follow-on PRs will implement additional phases of parsing and parsing for new elements in the grammar.
Diffstat (limited to 'src')
-rw-r--r--src/tools/wasm-opt.cpp6
-rw-r--r--src/wasm-io.h4
-rw-r--r--src/wasm/CMakeLists.txt1
-rw-r--r--src/wasm/wasm-io.cpp20
-rw-r--r--src/wasm/wat-parser.cpp651
-rw-r--r--src/wat-lexer.h17
-rw-r--r--src/wat-parser.h73
7 files changed, 763 insertions, 9 deletions
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp
index c026be21b..3927e513c 100644
--- a/src/tools/wasm-opt.cpp
+++ b/src/tools/wasm-opt.cpp
@@ -228,6 +228,12 @@ int main(int argc, const char* argv[]) {
[&outputSourceMapUrl](Options* o, const std::string& argument) {
outputSourceMapUrl = argument;
})
+ .add("--new-wat-parser",
+ "",
+ "Use the experimental new WAT parser",
+ WasmOptOption,
+ Options::Arguments::Zero,
+ [](Options*, const std::string&) { useNewWATParser = true; })
.add_positional("INFILE",
Options::Arguments::One,
[](Options* o, const std::string& argument) {
diff --git a/src/wasm-io.h b/src/wasm-io.h
index 1e28b5f92..ae66c3932 100644
--- a/src/wasm-io.h
+++ b/src/wasm-io.h
@@ -27,6 +27,10 @@
namespace wasm {
+// TODO: Remove this after switching to the new WAT parser by default and
+// removing the old one.
+extern bool useNewWATParser;
+
class ModuleIOBase {
protected:
bool debugInfo;
diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt
index 974f3cd10..7b7f85698 100644
--- a/src/wasm/CMakeLists.txt
+++ b/src/wasm/CMakeLists.txt
@@ -13,6 +13,7 @@ set(wasm_SOURCES
wasm-type.cpp
wasm-validator.cpp
wat-lexer.cpp
+ wat-parser.cpp
${wasm_HEADERS}
)
# wasm-debug.cpp includes LLVM header using std::iterator (deprecated in C++17)
diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp
index b94fc8470..55892975b 100644
--- a/src/wasm/wasm-io.cpp
+++ b/src/wasm/wasm-io.cpp
@@ -24,19 +24,31 @@
// binary.
//
-#include "wasm-io.h"
#include "support/debug.h"
#include "wasm-binary.h"
#include "wasm-s-parser.h"
+#include "wat-parser.h"
+
+#include "wasm-io.h"
namespace wasm {
+bool useNewWATParser = false;
+
#define DEBUG_TYPE "writer"
static void readTextData(std::string& input, Module& wasm, IRProfile profile) {
- SExpressionParser parser(const_cast<char*>(input.c_str()));
- Element& root = *parser.root;
- SExpressionWasmBuilder builder(wasm, *root[0], profile);
+ if (useNewWATParser) {
+ std::string_view in(input.c_str());
+ if (auto parsed = WATParser::parseModule(wasm, in);
+ auto err = parsed.getErr()) {
+ Fatal() << err->msg;
+ }
+ } else {
+ SExpressionParser parser(const_cast<char*>(input.c_str()));
+ Element& root = *parser.root;
+ SExpressionWasmBuilder builder(wasm, *root[0], profile);
+ }
}
void ModuleReader::readText(std::string filename, Module& wasm) {
diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp
new file mode 100644
index 000000000..254db79fd
--- /dev/null
+++ b/src/wasm/wat-parser.cpp
@@ -0,0 +1,651 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wat-parser.h"
+#include "ir/names.h"
+#include "support/name.h"
+#include "wasm-builder.h"
+#include "wasm-type.h"
+#include "wasm.h"
+#include "wat-lexer.h"
+
+// The WebAssembly text format is recursive in the sense that elements may be
+// referred to before they are declared. Furthermore, elements may be referred
+// to by index or by name. As a result, we need to parse text modules in
+// multiple phases.
+//
+// In the first phase, we find all of the module element declarations and
+// record, but do not interpret, the input spans of their corresponding
+// definitions. This phase establishes the indices and names of each module
+// element so that subsequent phases can look them up.
+//
+// The second phase, not yet implemented, parses type definitions to construct
+// the types used in the module. This has to be its own phase because we have no
+// way to refer to a type before it has been built along with all the other
+// types, unlike for other module elements that can be referred to by name
+// before their definitions have been parsed.
+//
+// The third phase, not yet implemented, further parses and constructs types
+// implicitly defined by type uses in functions, blocks, and call_indirect
+// instructions. These implicitly defined types may be referred to by index
+// elsewhere.
+//
+// The fourth phase, not yet implemented, parses and sets the types of globals,
+// functions, and other top-level module elements. These types need to be set
+// before we parse instructions because they determine the types of instructions
+// such as global.get and ref.func.
+//
+// In the fifth and final phase, not yet implemented, parses the remaining
+// contents of all module elements, including instructions.
+//
+// Each phase of parsing gets its own context type that is passed to the
+// individual parsing functions. There is a parsing function for each element of
+// the grammar given in the spec. Parsing functions are templatized so that they
+// may be passed the appropriate context type and return the correct result type
+// for each phase.
+
+#define RETURN_OR_OK(val) \
+ if constexpr (parsingDecls<Ctx>) { \
+ return Ok{}; \
+ } else { \
+ return val; \
+ }
+
+#define CHECK_ERR(val) \
+ if (auto _val = (val); auto err = _val.getErr()) { \
+ return Err{*err}; \
+ }
+
+using namespace std::string_view_literals;
+
+namespace wasm::WATParser {
+
+namespace {
+
+// ============
+// Parser Input
+// ============
+
+// Wraps a lexer and provides utilities for consuming tokens.
+struct ParseInput {
+ Lexer lexer;
+
+ ParseInput(std::string_view in) : lexer(in) {}
+
+ ParseInput(std::string_view in, size_t index) : lexer(in) {
+ lexer.setIndex(index);
+ }
+
+ bool empty() { return lexer == lexer.end(); }
+
+ std::optional<Token> peek() {
+ if (!empty()) {
+ return *lexer;
+ }
+ return {};
+ }
+
+ bool takeLParen() {
+ auto t = peek();
+ if (!t || !t->isLParen()) {
+ return false;
+ }
+ ++lexer;
+ return true;
+ }
+
+ bool takeRParen() {
+ auto t = peek();
+ if (!t || !t->isRParen()) {
+ return false;
+ }
+ ++lexer;
+ return true;
+ }
+
+ std::optional<Name> takeID() {
+ if (auto t = peek()) {
+ if (auto id = t->getID()) {
+ ++lexer;
+ // See comment on takeName.
+ return Name(std::string(*id));
+ }
+ }
+ return {};
+ }
+
+ bool takeKeyword(std::string_view expected) {
+ if (auto t = peek()) {
+ if (auto keyword = t->getKeyword()) {
+ if (*keyword == expected) {
+ ++lexer;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ std::optional<uint64_t> takeU64() {
+ if (auto t = peek()) {
+ if (auto n = t->getU64()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<int64_t> takeS64() {
+ if (auto t = peek()) {
+ if (auto n = t->getS64()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<int64_t> takeI64() {
+ if (auto t = peek()) {
+ if (auto n = t->getI64()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<uint32_t> takeU32() {
+ if (auto t = peek()) {
+ if (auto n = t->getU32()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<int32_t> takeS32() {
+ if (auto t = peek()) {
+ if (auto n = t->getS32()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<int32_t> takeI32() {
+ if (auto t = peek()) {
+ if (auto n = t->getI32()) {
+ ++lexer;
+ return n;
+ }
+ }
+ return {};
+ }
+
+ std::optional<double> takeF64() {
+ if (auto t = peek()) {
+ if (auto d = t->getF64()) {
+ ++lexer;
+ return d;
+ }
+ }
+ return {};
+ }
+
+ std::optional<float> takeF32() {
+ if (auto t = peek()) {
+ if (auto f = t->getF32()) {
+ ++lexer;
+ return f;
+ }
+ }
+ return {};
+ }
+
+ std::optional<std::string_view> takeString() {
+ if (auto t = peek()) {
+ if (auto s = t->getString()) {
+ ++lexer;
+ return s;
+ }
+ }
+ return {};
+ }
+
+ std::optional<Name> takeName() {
+ // TODO: Move this to lexer and validate UTF.
+ if (auto str = takeString()) {
+ // Copy to a std::string to make sure we have a null terminator, otherwise
+ // the `Name` constructor won't work correctly.
+ // TODO: Update `Name` to use string_view instead of char* and/or to take
+ // rvalue strings to avoid this extra copy.
+ return Name(std::string(*str));
+ }
+ return {};
+ }
+
+ bool takeSExprStart(std::string_view expected) {
+ auto original = lexer;
+ if (takeLParen() && takeKeyword(expected)) {
+ return true;
+ }
+ lexer = original;
+ return false;
+ }
+
+ Index getPos() {
+ if (auto t = peek()) {
+ return lexer.getIndex() - t->span.size();
+ }
+ return lexer.getIndex();
+ }
+
+ [[nodiscard]] Err err(std::string reason) {
+ std::stringstream msg;
+ msg << lexer.position(lexer.getIndex()) << ": error: " << reason;
+ return Err{msg.str()};
+ }
+};
+
+// ===================
+// POD Utility Structs
+// ===================
+
+// The location and possible name of a module-level definition in the input.
+struct DefPos {
+ Name name;
+ Index pos;
+};
+
+struct GlobalType {
+ Mutability mutability;
+ Type type;
+};
+
+struct ImportNames {
+ Name mod;
+ Name nm;
+};
+
+// ===============
+// Parser Contexts
+// ===============
+
+// Phase 1: Parse definition spans for top-level module elements and determine
+// their indices and names.
+struct ParseDeclsCtx {
+ // At this stage we only look at types to find implicit type definitions,
+ // which are inserted directly in to the context. We cannot materialize or
+ // validate any types because we don't know what types exist yet.
+ using ValTypeT = Ok;
+ using GlobalTypeT = Ok;
+
+ // Declared module elements are inserted into the module, but their bodies are
+ // not filled out until later parsing phases.
+ Module& wasm;
+
+ // The module element definitions we are parsing in this phase.
+ std::vector<DefPos> globalDefs;
+
+ // Counters used for generating names for module elements.
+ int globalCounter = 0;
+
+ // Used to verify that all imports come before all non-imports.
+ bool hasNonImport = false;
+
+ ParseDeclsCtx(Module& wasm) : wasm(wasm) {}
+};
+
+template<typename Ctx>
+inline constexpr bool parsingDecls = std::is_same_v<Ctx, ParseDeclsCtx>;
+
+// TODO: Phase 2: ParseTypeDefsCtx
+// TODO: Phase 3: ParseImplicitTypeDefsCtx
+
+// Phase 4: Parse and set the types of module elements.
+struct ParseModuleTypesCtx {
+ // In this phase we have constructed all the types, so we can materialize and
+ // validate them when they are used.
+ using ValTypeT = Type;
+ using GlobalTypeT = GlobalType;
+
+ Module& wasm;
+
+ // The index of the current element in its index space.
+ Index index = 0;
+
+ ParseModuleTypesCtx(Module& wasm) : wasm(wasm) {}
+};
+
+template<typename Ctx>
+inline constexpr bool parsingModuleTypes =
+ std::is_same_v<Ctx, ParseModuleTypesCtx>;
+
+// TODO: Phase 5: ParseDefsCtx
+
+// ================
+// Parser Functions
+// ================
+
+// Types
+template<typename Ctx>
+Result<typename Ctx::ValTypeT> valtype(Ctx&, ParseInput&);
+
+// Modules
+MaybeResult<ImportNames> inlineImport(ParseInput&);
+Result<std::vector<Name>> inlineExports(ParseInput&);
+template<typename Ctx> MaybeResult<> global(Ctx&, ParseInput&);
+MaybeResult<> modulefield(ParseDeclsCtx&, ParseInput&);
+Result<> module(ParseDeclsCtx&, ParseInput&);
+
+// Utilities
+template<typename T>
+void applyImportNames(Importable& item,
+ const std::optional<ImportNames>& names);
+Result<> addExports(ParseInput& in,
+ Module& wasm,
+ const Named* item,
+ const std::vector<Name>& exports,
+ ExternalKind kind);
+Result<Global*> addGlobalDecl(ParseDeclsCtx& ctx,
+ ParseInput& in,
+ Name name,
+ std::optional<ImportNames> importNames);
+template<typename Ctx>
+Result<> parseDefs(Ctx& ctx,
+ std::string_view input,
+ const std::vector<DefPos>& defs,
+ MaybeResult<> (*parser)(Ctx&, ParseInput&));
+
+// =====
+// Types
+// =====
+
+// numtype ::= 'i32' => i32
+// | 'i64' => i64
+// | 'f32' => f32
+// | 'f64' => f64
+// vectype ::= 'v128' => v128
+// valtype ::= t:numtype => t
+// | t:vectype => t
+// | t:reftype => t
+template<typename Ctx>
+Result<typename Ctx::ValTypeT> valtype(Ctx& ctx, ParseInput& in) {
+ if (in.takeKeyword("i32"sv)) {
+ RETURN_OR_OK(Type::i32);
+ } else if (in.takeKeyword("i64"sv)) {
+ RETURN_OR_OK(Type::i64);
+ } else if (in.takeKeyword("f32"sv)) {
+ RETURN_OR_OK(Type::f32);
+ } else if (in.takeKeyword("f64"sv)) {
+ RETURN_OR_OK(Type::f64);
+ } else if (in.takeKeyword("v128"sv)) {
+ RETURN_OR_OK(Type::v128);
+ } else {
+ // TODO: reftype
+ return in.err("expected valtype");
+ }
+}
+
+// globaltype ::= t:valtype => const t
+// | '(' 'mut' t:valtype ')' => var t
+template<typename Ctx>
+Result<typename Ctx::GlobalTypeT> globaltype(Ctx& ctx, ParseInput& in) {
+ // '(' 'mut' t:valtype ')'
+ if (in.takeSExprStart("mut"sv)) {
+ auto type = valtype(ctx, in);
+ CHECK_ERR(type);
+
+ if (!in.takeRParen()) {
+ return in.err("expected end of globaltype");
+ }
+ RETURN_OR_OK((GlobalType{Mutable, *type}));
+ }
+
+ // t:valtype
+ auto type = valtype(ctx, in);
+ CHECK_ERR(type);
+ RETURN_OR_OK((GlobalType{Immutable, *type}));
+}
+
+// =======
+// Modules
+// =======
+
+// ('(' 'import' mod:name nm:name ')')?
+MaybeResult<ImportNames> inlineImport(ParseInput& in) {
+ if (!in.takeSExprStart("import"sv)) {
+ return {};
+ }
+ auto mod = in.takeName();
+ if (!mod) {
+ return in.err("expected import module");
+ }
+ auto nm = in.takeName();
+ if (!nm) {
+ return in.err("expected import name");
+ }
+ if (!in.takeRParen()) {
+ return in.err("expected end of import");
+ }
+ return {{*mod, *nm}};
+}
+
+// ('(' 'export' name ')')*
+Result<std::vector<Name>> inlineExports(ParseInput& in) {
+ std::vector<Name> exports;
+ while (in.takeSExprStart("export"sv)) {
+ auto name = in.takeName();
+ if (!name) {
+ return in.err("expected export name");
+ }
+ if (!in.takeRParen()) {
+ return in.err("expected end of import");
+ }
+ exports.push_back(*name);
+ }
+ return exports;
+}
+
+// global ::= '(' 'global' id? ('(' 'export' name ')')* gt:globaltype e:expr ')'
+// | '(' 'global' id? '(' 'import' mod:name nm:name ')'
+// gt:globaltype ')'
+template<typename Ctx> MaybeResult<> global(Ctx& ctx, ParseInput& in) {
+ [[maybe_unused]] auto pos = in.getPos();
+ if (!in.takeSExprStart("global"sv)) {
+ return {};
+ }
+ Name name;
+ if (auto id = in.takeID()) {
+ name = *id;
+ }
+
+ auto exports = inlineExports(in);
+ CHECK_ERR(exports);
+
+ auto import = inlineImport(in);
+ CHECK_ERR(import);
+
+ auto type = globaltype(ctx, in);
+ CHECK_ERR(type);
+
+ if (import) {
+ if (!in.takeRParen()) {
+ return in.err("expected end of global");
+ }
+
+ if constexpr (parsingDecls<Ctx>) {
+ if (ctx.hasNonImport) {
+ return in.err("import after non-import");
+ }
+ auto g = addGlobalDecl(ctx, in, name, *import);
+ CHECK_ERR(g);
+ CHECK_ERR(addExports(in, ctx.wasm, *g, *exports, ExternalKind::Global));
+ ctx.globalDefs.push_back({name, pos});
+ } else if constexpr (parsingModuleTypes<Ctx>) {
+ auto& g = ctx.wasm.globals[ctx.index];
+ g->mutable_ = type->mutability;
+ g->type = type->type;
+ }
+ return Ok{};
+ }
+
+ return in.err("TODO: non-imported globals");
+}
+
+// modulefield ::= type
+// | import
+// | func
+// | table
+// | mem
+// | global
+// | export
+// | start
+// | elem
+// | data
+MaybeResult<> modulefield(ParseDeclsCtx& ctx, ParseInput& in) {
+ if (auto t = in.peek(); !t || t->isRParen()) {
+ return {};
+ }
+ if (auto res = global(ctx, in)) {
+ CHECK_ERR(res);
+ return Ok{};
+ }
+ return in.err("unrecognized module field");
+}
+
+// module ::= '(' 'module' id? (m:modulefield)* ')'
+// | (m:modulefield)* eof
+Result<> module(ParseDeclsCtx& ctx, ParseInput& in) {
+ bool outer = in.takeSExprStart("module"sv);
+
+ if (outer) {
+ if (auto id = in.takeID()) {
+ ctx.wasm.name = *id;
+ }
+ }
+
+ while (auto field = modulefield(ctx, in)) {
+ CHECK_ERR(field);
+ }
+
+ if (outer && !in.takeRParen()) {
+ return in.err("expected end of module");
+ }
+
+ return Ok{};
+}
+
+// =========
+// Utilities
+// =========
+
+void applyImportNames(Importable& item,
+ const std::optional<ImportNames>& names) {
+ if (names) {
+ item.module = names->mod;
+ item.base = names->nm;
+ }
+}
+
+Result<> addExports(ParseInput& in,
+ Module& wasm,
+ const Named* item,
+ const std::vector<Name>& exports,
+ ExternalKind kind) {
+ for (auto name : exports) {
+ if (wasm.getExportOrNull(name)) {
+ // TODO: Fix error location
+ return in.err("repeated export name");
+ }
+ wasm.addExport(Builder(wasm).makeExport(name, item->name, kind));
+ }
+ return Ok{};
+}
+
+Result<Global*> addGlobalDecl(ParseDeclsCtx& ctx,
+ ParseInput& in,
+ Name name,
+ std::optional<ImportNames> importNames) {
+ auto g = std::make_unique<Global>();
+ if (name.is()) {
+ if (ctx.wasm.getGlobalOrNull(name)) {
+ // TODO: if the existing global is not explicitly named, fix its name and
+ // continue.
+ // TODO: Fix error location to point to name.
+ return in.err("repeated global name");
+ }
+ g->setExplicitName(name);
+ } else {
+ name =
+ (importNames ? "gimport$" : "") + std::to_string(ctx.globalCounter++);
+ name = Names::getValidGlobalName(ctx.wasm, name);
+ g->name = name;
+ }
+ applyImportNames(*g, importNames);
+ return ctx.wasm.addGlobal(std::move(g));
+}
+
+template<typename Ctx>
+Result<> parseDefs(Ctx& ctx,
+ std::string_view input,
+ const std::vector<DefPos>& defs,
+ MaybeResult<> (*parser)(Ctx&, ParseInput&)) {
+ for (Index i = 0; i < defs.size(); ++i) {
+ ctx.index = i;
+ ParseInput in(input, defs[i].pos);
+ auto parsed = parser(ctx, in);
+ CHECK_ERR(parsed);
+ assert(parsed);
+ }
+ return Ok{};
+}
+
+} // anonymous namespace
+
+Result<> parseModule(Module& wasm, std::string_view input) {
+ // Parse module-level declarations.
+ ParseDeclsCtx decls(wasm);
+ {
+ ParseInput in(input);
+ CHECK_ERR(module(decls, in));
+ if (!in.empty()) {
+ return in.err("Unexpected tokens after module");
+ }
+ }
+
+ // TODO: Map names to indices.
+
+ // TODO: Parse type definitions.
+
+ // TODO: Parse implicit type definitions.
+
+ // Parse module-level types.
+ ParseModuleTypesCtx ctx(wasm);
+ CHECK_ERR(parseDefs(ctx, input, decls.globalDefs, global));
+
+ // TODO: Parse types of other module elements.
+
+ // TODO: Parse definitions.
+
+ return Ok{};
+}
+
+} // namespace wasm::WATParser
diff --git a/src/wat-lexer.h b/src/wat-lexer.h
index e4ba2efa8..30aa1e036 100644
--- a/src/wat-lexer.h
+++ b/src/wat-lexer.h
@@ -22,8 +22,8 @@
#include <string_view>
#include <variant>
-#ifndef wasm_wat_parser_h
-#define wasm_wat_parser_h
+#ifndef wasm_wat_lexer_h
+#define wasm_wat_lexer_h
namespace wasm::WATParser {
@@ -112,7 +112,8 @@ struct Token {
std::optional<std::string_view> getID() const {
if (std::get_if<IdTok>(&data)) {
- return span;
+ // Drop leading '$'.
+ return span.substr(1);
}
return {};
}
@@ -163,7 +164,12 @@ public:
// The end sentinel.
Lexer() = default;
- Lexer(std::string_view buffer) : buffer(buffer) {
+ Lexer(std::string_view buffer) : buffer(buffer) { setIndex(0); }
+
+ size_t getIndex() { return index; }
+
+ void setIndex(size_t i) {
+ index = i;
skipSpace();
lexToken();
skipSpace();
@@ -203,6 +209,7 @@ public:
Lexer end() { return Lexer(); }
TextPos position(const char* c);
+ TextPos position(size_t i) { return position(buffer.data() + i); }
TextPos position(std::string_view span) { return position(span.data()); }
TextPos position(Token tok) { return position(tok.span); }
@@ -213,4 +220,4 @@ private:
} // namespace wasm::WATParser
-#endif // wasm_wat_parser_h
+#endif // wasm_wat_lexer_h
diff --git a/src/wat-parser.h b/src/wat-parser.h
new file mode 100644
index 000000000..4bb9b62c8
--- /dev/null
+++ b/src/wat-parser.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_wat_parser_h
+#define wasm_wat_parser_h
+
+#include <optional>
+#include <string_view>
+
+#include "wasm.h"
+
+namespace wasm::WATParser {
+
+struct Ok {};
+
+struct None {};
+
+struct Err {
+ std::string msg;
+};
+
+template<typename T = Ok> struct Result {
+ std::variant<T, Err> val;
+
+ Result(Result<T>& other) = default;
+ Result(const Err& e) : val(std::in_place_type<Err>, e) {}
+ Result(Err&& e) : val(std::in_place_type<Err>, e) {}
+ template<typename U = T>
+ Result(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {}
+
+ Err* getErr() { return std::get_if<Err>(&val); }
+ T& operator*() { return *std::get_if<T>(&val); }
+ T* operator->() { return std::get_if<T>(&val); }
+};
+
+template<typename T = Ok> struct MaybeResult {
+ std::variant<T, None, Err> val;
+
+ MaybeResult() : val(None{}) {}
+ MaybeResult(MaybeResult<T>& other) = default;
+ MaybeResult(const Err& e) : val(std::in_place_type<Err>, e) {}
+ MaybeResult(Err&& e) : val(std::in_place_type<Err>, std::move(e)) {}
+ template<typename U = T>
+ MaybeResult(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {}
+
+ // Whether we have an error or a value. Useful for assignment in loops and if
+ // conditions where errors should not get lost.
+ operator bool() const { return !std::holds_alternative<None>(val); }
+
+ Err* getErr() { return std::get_if<Err>(&val); }
+ T& operator*() { return *std::get_if<T>(&val); }
+ T* operator->() { return std::get_if<T>(&val); }
+};
+
+// Parse a single WAT module.
+Result<> parseModule(Module& wasm, std::string_view in);
+
+} // namespace wasm::WATParser
+
+#endif // wasm_wat_parser.h