| /* |
| * Copyright 2017 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 "src/wast-parser.h" |
| |
| #include "src/binary-reader-ir.h" |
| #include "src/binary-reader.h" |
| #include "src/cast.h" |
| #include "src/expr-visitor.h" |
| #include "src/make-unique.h" |
| #include "src/resolve-names.h" |
| #include "src/stream.h" |
| #include "src/utf8.h" |
| #include "src/validator.h" |
| |
| #define WABT_TRACING 0 |
| #include "src/tracing.h" |
| |
| #define EXPECT(token_type) CHECK_RESULT(Expect(TokenType::token_type)) |
| |
| namespace wabt { |
| |
| namespace { |
| |
| static const size_t kMaxErrorTokenLength = 80; |
| |
| bool IsPowerOfTwo(uint32_t x) { |
| return x && ((x & (x - 1)) == 0); |
| } |
| |
| template <typename OutputIter> |
| void RemoveEscapes(string_view text, OutputIter dest) { |
| // Remove surrounding quotes; if any. This may be empty if the string was |
| // invalid (e.g. if it contained a bad escape sequence). |
| if (text.size() <= 2) { |
| return; |
| } |
| |
| text = text.substr(1, text.size() - 2); |
| |
| const char* src = text.data(); |
| const char* end = text.data() + text.size(); |
| |
| while (src < end) { |
| if (*src == '\\') { |
| src++; |
| switch (*src) { |
| case 'n': |
| *dest++ = '\n'; |
| break; |
| case 'r': |
| *dest++ = '\r'; |
| break; |
| case 't': |
| *dest++ = '\t'; |
| break; |
| case '\\': |
| *dest++ = '\\'; |
| break; |
| case '\'': |
| *dest++ = '\''; |
| break; |
| case '\"': |
| *dest++ = '\"'; |
| break; |
| default: { |
| // The string should be validated already, so we know this is a hex |
| // sequence. |
| uint32_t hi; |
| uint32_t lo; |
| if (Succeeded(ParseHexdigit(src[0], &hi)) && |
| Succeeded(ParseHexdigit(src[1], &lo))) { |
| *dest++ = (hi << 4) | lo; |
| } else { |
| assert(0); |
| } |
| src++; |
| break; |
| } |
| } |
| src++; |
| } else { |
| *dest++ = *src++; |
| } |
| } |
| } |
| |
| typedef std::vector<string_view> TextVector; |
| |
| template <typename OutputIter> |
| void RemoveEscapes(const TextVector& texts, OutputIter out) { |
| for (string_view text : texts) |
| RemoveEscapes(text, out); |
| } |
| |
| bool IsPlainInstr(TokenType token_type) { |
| switch (token_type) { |
| case TokenType::Unreachable: |
| case TokenType::Nop: |
| case TokenType::Drop: |
| case TokenType::Select: |
| case TokenType::Br: |
| case TokenType::BrIf: |
| case TokenType::BrTable: |
| case TokenType::Return: |
| case TokenType::ReturnCall: |
| case TokenType::ReturnCallIndirect: |
| case TokenType::Call: |
| case TokenType::CallIndirect: |
| case TokenType::CallRef: |
| case TokenType::LocalGet: |
| case TokenType::LocalSet: |
| case TokenType::LocalTee: |
| case TokenType::GlobalGet: |
| case TokenType::GlobalSet: |
| case TokenType::Load: |
| case TokenType::Store: |
| case TokenType::Const: |
| case TokenType::Unary: |
| case TokenType::Binary: |
| case TokenType::Compare: |
| case TokenType::Convert: |
| case TokenType::MemoryCopy: |
| case TokenType::DataDrop: |
| case TokenType::MemoryFill: |
| case TokenType::MemoryGrow: |
| case TokenType::MemoryInit: |
| case TokenType::MemorySize: |
| case TokenType::TableCopy: |
| case TokenType::ElemDrop: |
| case TokenType::TableInit: |
| case TokenType::TableGet: |
| case TokenType::TableSet: |
| case TokenType::TableGrow: |
| case TokenType::TableSize: |
| case TokenType::TableFill: |
| case TokenType::Throw: |
| case TokenType::Rethrow: |
| case TokenType::RefFunc: |
| case TokenType::RefNull: |
| case TokenType::RefIsNull: |
| case TokenType::AtomicLoad: |
| case TokenType::AtomicStore: |
| case TokenType::AtomicRmw: |
| case TokenType::AtomicRmwCmpxchg: |
| case TokenType::AtomicNotify: |
| case TokenType::AtomicFence: |
| case TokenType::AtomicWait: |
| case TokenType::Ternary: |
| case TokenType::SimdLaneOp: |
| case TokenType::SimdLoadLane: |
| case TokenType::SimdStoreLane: |
| case TokenType::SimdShuffleOp: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsBlockInstr(TokenType token_type) { |
| switch (token_type) { |
| case TokenType::Block: |
| case TokenType::Loop: |
| case TokenType::If: |
| case TokenType::Try: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsPlainOrBlockInstr(TokenType token_type) { |
| return IsPlainInstr(token_type) || IsBlockInstr(token_type); |
| } |
| |
| bool IsExpr(TokenTypePair pair) { |
| return pair[0] == TokenType::Lpar && IsPlainOrBlockInstr(pair[1]); |
| } |
| |
| bool IsInstr(TokenTypePair pair) { |
| return IsPlainOrBlockInstr(pair[0]) || IsExpr(pair); |
| } |
| |
| bool IsCatch(TokenType token_type) { |
| return token_type == TokenType::Catch || token_type == TokenType::CatchAll; |
| } |
| |
| bool IsModuleField(TokenTypePair pair) { |
| if (pair[0] != TokenType::Lpar) { |
| return false; |
| } |
| |
| switch (pair[1]) { |
| case TokenType::Data: |
| case TokenType::Elem: |
| case TokenType::Tag: |
| case TokenType::Export: |
| case TokenType::Func: |
| case TokenType::Type: |
| case TokenType::Global: |
| case TokenType::Import: |
| case TokenType::Memory: |
| case TokenType::Start: |
| case TokenType::Table: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsCommand(TokenTypePair pair) { |
| if (pair[0] != TokenType::Lpar) { |
| return false; |
| } |
| |
| switch (pair[1]) { |
| case TokenType::AssertException: |
| case TokenType::AssertExhaustion: |
| case TokenType::AssertInvalid: |
| case TokenType::AssertMalformed: |
| case TokenType::AssertReturn: |
| case TokenType::AssertTrap: |
| case TokenType::AssertUnlinkable: |
| case TokenType::Get: |
| case TokenType::Invoke: |
| case TokenType::Input: |
| case TokenType::Module: |
| case TokenType::Output: |
| case TokenType::Register: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsEmptySignature(const FuncSignature& sig) { |
| return sig.result_types.empty() && sig.param_types.empty(); |
| } |
| |
| bool ResolveFuncTypeWithEmptySignature(const Module& module, |
| FuncDeclaration* decl) { |
| // Resolve func type variables where the signature was not specified |
| // explicitly, e.g.: (func (type 1) ...) |
| if (decl->has_func_type && IsEmptySignature(decl->sig)) { |
| const FuncType* func_type = module.GetFuncType(decl->type_var); |
| if (func_type) { |
| decl->sig = func_type->sig; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void ResolveTypeName( |
| const Module& module, |
| Type& type, |
| Index index, |
| const std::unordered_map<uint32_t, std::string>& bindings) { |
| if (type != Type::Reference || type.GetReferenceIndex() != kInvalidIndex) { |
| return; |
| } |
| |
| const auto name_iterator = bindings.find(index); |
| assert(name_iterator != bindings.cend()); |
| const auto type_index = module.type_bindings.FindIndex(name_iterator->second); |
| assert(type_index != kInvalidIndex); |
| type = Type(Type::Reference, type_index); |
| } |
| |
| void ResolveTypeNames(const Module& module, FuncDeclaration* decl) { |
| assert(decl); |
| auto& signature = decl->sig; |
| |
| for (uint32_t param_index = 0; param_index < signature.GetNumParams(); |
| ++param_index) { |
| ResolveTypeName(module, signature.param_types[param_index], param_index, |
| signature.param_type_names); |
| } |
| |
| for (uint32_t result_index = 0; result_index < signature.GetNumResults(); |
| ++result_index) { |
| ResolveTypeName(module, signature.result_types[result_index], result_index, |
| signature.result_type_names); |
| } |
| } |
| |
| void ResolveImplicitlyDefinedFunctionType(const Location& loc, |
| Module* module, |
| const FuncDeclaration& decl) { |
| // Resolve implicitly defined function types, e.g.: (func (param i32) ...) |
| if (!decl.has_func_type) { |
| Index func_type_index = module->GetFuncTypeIndex(decl.sig); |
| if (func_type_index == kInvalidIndex) { |
| auto func_type_field = MakeUnique<TypeModuleField>(loc); |
| auto func_type = MakeUnique<FuncType>(); |
| func_type->sig = decl.sig; |
| func_type_field->type = std::move(func_type); |
| module->AppendField(std::move(func_type_field)); |
| } |
| } |
| } |
| |
| Result CheckTypeIndex(const Location& loc, |
| Type actual, |
| Type expected, |
| const char* desc, |
| Index index, |
| const char* index_kind, |
| Errors* errors) { |
| // Types must match exactly; no subtyping should be allowed. |
| if (actual != expected) { |
| errors->emplace_back(ErrorLevel::Error, loc, |
| StringPrintf("type mismatch for %s %" PRIindex |
| " of %s. got %s, expected %s", |
| index_kind, index, desc, actual.GetName(), |
| expected.GetName())); |
| return Result::Error; |
| } |
| return Result::Ok; |
| } |
| |
| Result CheckTypes(const Location& loc, |
| const TypeVector& actual, |
| const TypeVector& expected, |
| const char* desc, |
| const char* index_kind, |
| Errors* errors) { |
| Result result = Result::Ok; |
| if (actual.size() == expected.size()) { |
| for (size_t i = 0; i < actual.size(); ++i) { |
| result |= CheckTypeIndex(loc, actual[i], expected[i], desc, i, index_kind, |
| errors); |
| } |
| } else { |
| errors->emplace_back( |
| ErrorLevel::Error, loc, |
| StringPrintf("expected %" PRIzd " %ss, got %" PRIzd, expected.size(), |
| index_kind, actual.size())); |
| result = Result::Error; |
| } |
| return result; |
| } |
| |
| Result CheckFuncTypeVarMatchesExplicit(const Location& loc, |
| const Module& module, |
| const FuncDeclaration& decl, |
| Errors* errors) { |
| Result result = Result::Ok; |
| if (decl.has_func_type) { |
| const FuncType* func_type = module.GetFuncType(decl.type_var); |
| if (func_type) { |
| result |= |
| CheckTypes(loc, decl.sig.result_types, func_type->sig.result_types, |
| "function", "result", errors); |
| result |= |
| CheckTypes(loc, decl.sig.param_types, func_type->sig.param_types, |
| "function", "argument", errors); |
| } else if (!(decl.sig.param_types.empty() && |
| decl.sig.result_types.empty())) { |
| // We want to check whether the function type at the explicit index |
| // matches the given param and result types. If they were omitted then |
| // they'll be resolved automatically (see |
| // ResolveFuncTypeWithEmptySignature), but if they are provided then we |
| // have to check. If we get here then the type var is invalid, so we |
| // can't check whether they match. |
| if (decl.type_var.is_index()) { |
| errors->emplace_back(ErrorLevel::Error, loc, |
| StringPrintf("invalid func type index %" PRIindex, |
| decl.type_var.index())); |
| } else { |
| errors->emplace_back(ErrorLevel::Error, loc, |
| StringPrintf("expected func type identifier %s", |
| decl.type_var.name().c_str())); |
| } |
| result = Result::Error; |
| } |
| } |
| return result; |
| } |
| |
| bool IsInlinableFuncSignature(const FuncSignature& sig) { |
| return sig.GetNumParams() == 0 && sig.GetNumResults() <= 1; |
| } |
| |
| class ResolveFuncTypesExprVisitorDelegate : public ExprVisitor::DelegateNop { |
| public: |
| explicit ResolveFuncTypesExprVisitorDelegate(Module* module, Errors* errors) |
| : module_(module), errors_(errors) {} |
| |
| void ResolveBlockDeclaration(const Location& loc, BlockDeclaration* decl) { |
| ResolveTypeNames(*module_, decl); |
| ResolveFuncTypeWithEmptySignature(*module_, decl); |
| if (!IsInlinableFuncSignature(decl->sig)) { |
| ResolveImplicitlyDefinedFunctionType(loc, module_, *decl); |
| } |
| } |
| |
| Result BeginBlockExpr(BlockExpr* expr) override { |
| ResolveBlockDeclaration(expr->loc, &expr->block.decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, |
| expr->block.decl, errors_); |
| } |
| |
| Result BeginIfExpr(IfExpr* expr) override { |
| ResolveBlockDeclaration(expr->loc, &expr->true_.decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, |
| expr->true_.decl, errors_); |
| } |
| |
| Result BeginLoopExpr(LoopExpr* expr) override { |
| ResolveBlockDeclaration(expr->loc, &expr->block.decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, |
| expr->block.decl, errors_); |
| } |
| |
| Result BeginTryExpr(TryExpr* expr) override { |
| ResolveBlockDeclaration(expr->loc, &expr->block.decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, |
| expr->block.decl, errors_); |
| } |
| |
| Result OnCallIndirectExpr(CallIndirectExpr* expr) override { |
| ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); |
| ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, |
| errors_); |
| } |
| |
| Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) override { |
| ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); |
| ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); |
| return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, |
| errors_); |
| } |
| |
| private: |
| Module* module_; |
| Errors* errors_; |
| }; |
| |
| Result ResolveFuncTypes(Module* module, Errors* errors) { |
| Result result = Result::Ok; |
| for (ModuleField& field : module->fields) { |
| Func* func = nullptr; |
| FuncDeclaration* decl = nullptr; |
| if (auto* func_field = dyn_cast<FuncModuleField>(&field)) { |
| func = &func_field->func; |
| decl = &func->decl; |
| } else if (auto* tag_field = dyn_cast<TagModuleField>(&field)) { |
| decl = &tag_field->tag.decl; |
| } else if (auto* import_field = dyn_cast<ImportModuleField>(&field)) { |
| if (auto* func_import = |
| dyn_cast<FuncImport>(import_field->import.get())) { |
| // Only check the declaration, not the function itself, since it is an |
| // import. |
| decl = &func_import->func.decl; |
| } else if (auto* tag_import = |
| dyn_cast<TagImport>(import_field->import.get())) { |
| decl = &tag_import->tag.decl; |
| } else { |
| continue; |
| } |
| } else { |
| continue; |
| } |
| |
| bool has_func_type_and_empty_signature = false; |
| |
| if (decl) { |
| ResolveTypeNames(*module, decl); |
| has_func_type_and_empty_signature = |
| ResolveFuncTypeWithEmptySignature(*module, decl); |
| ResolveImplicitlyDefinedFunctionType(field.loc, module, *decl); |
| result |= |
| CheckFuncTypeVarMatchesExplicit(field.loc, *module, *decl, errors); |
| } |
| |
| if (func) { |
| if (has_func_type_and_empty_signature) { |
| // The call to ResolveFuncTypeWithEmptySignature may have updated the |
| // function signature so there are parameters. Since parameters and |
| // local variables share the same index space, we need to increment the |
| // local indexes bound to a given name by the number of parameters in |
| // the function. |
| for (auto& pair: func->bindings) { |
| pair.second.index += func->GetNumParams(); |
| } |
| } |
| |
| ResolveFuncTypesExprVisitorDelegate delegate(module, errors); |
| ExprVisitor visitor(&delegate); |
| result |= visitor.VisitFunc(func); |
| } |
| } |
| return result; |
| } |
| |
| void AppendInlineExportFields(Module* module, |
| ModuleFieldList* fields, |
| Index index) { |
| Location last_field_loc = module->fields.back().loc; |
| |
| for (ModuleField& field : *fields) { |
| auto* export_field = cast<ExportModuleField>(&field); |
| export_field->export_.var = Var(index, last_field_loc); |
| } |
| |
| module->AppendFields(fields); |
| } |
| |
| } // End of anonymous namespace |
| |
| WastParser::WastParser(WastLexer* lexer, |
| Errors* errors, |
| WastParseOptions* options) |
| : lexer_(lexer), errors_(errors), options_(options) {} |
| |
| void WastParser::Error(Location loc, const char* format, ...) { |
| WABT_SNPRINTF_ALLOCA(buffer, length, format); |
| errors_->emplace_back(ErrorLevel::Error, loc, buffer); |
| } |
| |
| Token WastParser::GetToken() { |
| if (tokens_.empty()) { |
| tokens_.push_back(lexer_->GetToken(this)); |
| } |
| return tokens_.front(); |
| } |
| |
| Location WastParser::GetLocation() { |
| return GetToken().loc; |
| } |
| |
| TokenType WastParser::Peek(size_t n) { |
| while (tokens_.size() <= n) { |
| Token cur = lexer_->GetToken(this); |
| if (cur.token_type() != TokenType::LparAnn) { |
| tokens_.push_back(cur); |
| } else { |
| // Custom annotation. For now, discard until matching Rpar. |
| if (!options_->features.annotations_enabled()) { |
| Error(cur.loc, "annotations not enabled: %s", cur.to_string().c_str()); |
| tokens_.push_back(Token(cur.loc, TokenType::Invalid)); |
| continue; |
| } |
| int indent = 1; |
| while (indent > 0) { |
| cur = lexer_->GetToken(this); |
| switch (cur.token_type()) { |
| case TokenType::Lpar: |
| case TokenType::LparAnn: |
| indent++; |
| break; |
| |
| case TokenType::Rpar: |
| indent--; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return tokens_.at(n).token_type(); |
| } |
| |
| TokenTypePair WastParser::PeekPair() { |
| return TokenTypePair{{Peek(), Peek(1)}}; |
| } |
| |
| bool WastParser::PeekMatch(TokenType type) { |
| return Peek() == type; |
| } |
| |
| bool WastParser::PeekMatchLpar(TokenType type) { |
| return Peek() == TokenType::Lpar && Peek(1) == type; |
| } |
| |
| bool WastParser::PeekMatchExpr() { |
| return IsExpr(PeekPair()); |
| } |
| |
| bool WastParser::PeekMatchRefType() { |
| return options_->features.function_references_enabled() && |
| PeekMatchLpar(TokenType::Ref); |
| } |
| |
| bool WastParser::Match(TokenType type) { |
| if (PeekMatch(type)) { |
| Consume(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool WastParser::MatchLpar(TokenType type) { |
| if (PeekMatchLpar(type)) { |
| Consume(); |
| Consume(); |
| return true; |
| } |
| return false; |
| } |
| |
| Result WastParser::Expect(TokenType type) { |
| if (!Match(type)) { |
| Token token = Consume(); |
| Error(token.loc, "unexpected token %s, expected %s.", |
| token.to_string_clamp(kMaxErrorTokenLength).c_str(), |
| GetTokenTypeName(type)); |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Token WastParser::Consume() { |
| assert(!tokens_.empty()); |
| Token token = tokens_.front(); |
| tokens_.pop_front(); |
| return token; |
| } |
| |
| Result WastParser::Synchronize(SynchronizeFunc func) { |
| static const int kMaxConsumed = 10; |
| for (int i = 0; i < kMaxConsumed; ++i) { |
| if (func(PeekPair())) { |
| return Result::Ok; |
| } |
| |
| Token token = Consume(); |
| if (token.token_type() == TokenType::Reserved) { |
| Error(token.loc, "unexpected token %s.", |
| token.to_string_clamp(kMaxErrorTokenLength).c_str()); |
| } |
| } |
| |
| return Result::Error; |
| } |
| |
| void WastParser::ErrorUnlessOpcodeEnabled(const Token& token) { |
| Opcode opcode = token.opcode(); |
| if (!opcode.IsEnabled(options_->features)) { |
| Error(token.loc, "opcode not allowed: %s", opcode.GetName()); |
| } |
| } |
| |
| Result WastParser::ErrorExpected(const std::vector<std::string>& expected, |
| const char* example) { |
| Token token = Consume(); |
| std::string expected_str; |
| if (!expected.empty()) { |
| expected_str = ", expected "; |
| for (size_t i = 0; i < expected.size(); ++i) { |
| if (i != 0) { |
| if (i == expected.size() - 1) { |
| expected_str += " or "; |
| } else { |
| expected_str += ", "; |
| } |
| } |
| |
| expected_str += expected[i]; |
| } |
| |
| if (example) { |
| expected_str += " (e.g. "; |
| expected_str += example; |
| expected_str += ")"; |
| } |
| } |
| |
| Error(token.loc, "unexpected token \"%s\"%s.", |
| token.to_string_clamp(kMaxErrorTokenLength).c_str(), |
| expected_str.c_str()); |
| return Result::Error; |
| } |
| |
| Result WastParser::ErrorIfLpar(const std::vector<std::string>& expected, |
| const char* example) { |
| if (Match(TokenType::Lpar)) { |
| GetToken(); |
| return ErrorExpected(expected, example); |
| } |
| return Result::Ok; |
| } |
| |
| bool WastParser::ParseBindVarOpt(std::string* name) { |
| WABT_TRACE(ParseBindVarOpt); |
| if (!PeekMatch(TokenType::Var)) { |
| return false; |
| } |
| Token token = Consume(); |
| *name = token.text().to_string(); |
| return true; |
| } |
| |
| Result WastParser::ParseVar(Var* out_var) { |
| WABT_TRACE(ParseVar); |
| if (PeekMatch(TokenType::Nat)) { |
| Token token = Consume(); |
| string_view sv = token.literal().text; |
| uint64_t index = kInvalidIndex; |
| if (Failed(ParseUint64(sv.begin(), sv.end(), &index))) { |
| // Print an error, but don't fail parsing. |
| Error(token.loc, "invalid int \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(sv)); |
| } |
| |
| *out_var = Var(index, token.loc); |
| return Result::Ok; |
| } else if (PeekMatch(TokenType::Var)) { |
| Token token = Consume(); |
| *out_var = Var(token.text(), token.loc); |
| return Result::Ok; |
| } else { |
| return ErrorExpected({"a numeric index", "a name"}, "12 or $foo"); |
| } |
| } |
| |
| bool WastParser::ParseVarOpt(Var* out_var, Var default_var) { |
| WABT_TRACE(ParseVarOpt); |
| if (PeekMatch(TokenType::Nat) || PeekMatch(TokenType::Var)) { |
| Result result = ParseVar(out_var); |
| // Should always succeed, the only way it could fail is if the token |
| // doesn't match. |
| assert(Succeeded(result)); |
| WABT_USE(result); |
| return true; |
| } else { |
| *out_var = default_var; |
| return false; |
| } |
| } |
| |
| Result WastParser::ParseOffsetExpr(ExprList* out_expr_list) { |
| WABT_TRACE(ParseOffsetExpr); |
| if (!ParseOffsetExprOpt(out_expr_list)) { |
| return ErrorExpected({"an offset expr"}, "(i32.const 123)"); |
| } |
| return Result::Ok; |
| } |
| |
| bool WastParser::ParseOffsetExprOpt(ExprList* out_expr_list) { |
| WABT_TRACE(ParseOffsetExprOpt); |
| if (MatchLpar(TokenType::Offset)) { |
| CHECK_RESULT(ParseTerminatingInstrList(out_expr_list)); |
| EXPECT(Rpar); |
| return true; |
| } else if (PeekMatchExpr()) { |
| CHECK_RESULT(ParseExpr(out_expr_list)); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| Result WastParser::ParseTextList(std::vector<uint8_t>* out_data) { |
| WABT_TRACE(ParseTextList); |
| if (!ParseTextListOpt(out_data)) { |
| // TODO(binji): Add error message here. |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| bool WastParser::ParseTextListOpt(std::vector<uint8_t>* out_data) { |
| WABT_TRACE(ParseTextListOpt); |
| TextVector texts; |
| while (PeekMatch(TokenType::Text)) |
| texts.push_back(Consume().text()); |
| |
| RemoveEscapes(texts, std::back_inserter(*out_data)); |
| return !texts.empty(); |
| } |
| |
| Result WastParser::ParseVarList(VarVector* out_var_list) { |
| WABT_TRACE(ParseVarList); |
| Var var; |
| while (ParseVarOpt(&var)) { |
| out_var_list->emplace_back(var); |
| } |
| if (out_var_list->empty()) { |
| return ErrorExpected({"a var"}, "12 or $foo"); |
| } else { |
| return Result::Ok; |
| } |
| } |
| |
| bool WastParser::ParseElemExprOpt(ExprList* out_elem_expr) { |
| WABT_TRACE(ParseElemExprOpt); |
| bool item = MatchLpar(TokenType::Item); |
| ExprList exprs; |
| if (item) { |
| if (ParseTerminatingInstrList(&exprs) != Result::Ok) { |
| return false; |
| } |
| EXPECT(Rpar); |
| } else { |
| if (ParseExpr(&exprs) != Result::Ok) { |
| return false; |
| } |
| } |
| if (!exprs.size()) { |
| return false; |
| } |
| *out_elem_expr = std::move(exprs); |
| return true; |
| } |
| |
| bool WastParser::ParseElemExprListOpt(InitExprVector* out_list) { |
| InitExpr init_expr; |
| while (ParseElemExprOpt(&init_expr.exprs)) { |
| out_list->push_back(std::move(init_expr)); |
| } |
| return !out_list->empty(); |
| } |
| |
| bool WastParser::ParseElemExprVarListOpt(InitExprVector* out_list) { |
| WABT_TRACE(ParseElemExprVarListOpt); |
| Var var; |
| InitExpr init_expr; |
| while (ParseVarOpt(&var)) { |
| init_expr.exprs.push_back(MakeUnique<RefFuncExpr>(var)); |
| out_list->push_back(std::move(init_expr)); |
| } |
| return !out_list->empty(); |
| } |
| |
| Result WastParser::ParseValueType(Var* out_type) { |
| WABT_TRACE(ParseValueType); |
| |
| const bool is_ref_type = PeekMatchRefType(); |
| const bool is_value_type = PeekMatch(TokenType::ValueType); |
| |
| if (!is_value_type && !is_ref_type) { |
| return ErrorExpected({"i32", "i64", "f32", "f64", "v128", "externref"}); |
| } |
| |
| if (is_ref_type) { |
| EXPECT(Lpar); |
| EXPECT(Ref); |
| CHECK_RESULT(ParseVar(out_type)); |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Token token = Consume(); |
| Type type = token.type(); |
| bool is_enabled; |
| switch (type) { |
| case Type::V128: |
| is_enabled = options_->features.simd_enabled(); |
| break; |
| case Type::FuncRef: |
| case Type::ExternRef: |
| is_enabled = options_->features.reference_types_enabled(); |
| break; |
| default: |
| is_enabled = true; |
| break; |
| } |
| |
| if (!is_enabled) { |
| Error(token.loc, "value type not allowed: %s", type.GetName()); |
| return Result::Error; |
| } |
| |
| *out_type = Var(type); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseValueTypeList( |
| TypeVector* out_type_list, |
| std::unordered_map<uint32_t, std::string>* type_names) { |
| WABT_TRACE(ParseValueTypeList); |
| while (true) { |
| if (!PeekMatchRefType() && !PeekMatch(TokenType::ValueType)) { |
| break; |
| } |
| |
| Var type; |
| CHECK_RESULT(ParseValueType(&type)); |
| |
| if (type.is_index()) { |
| out_type_list->push_back(Type(type.index())); |
| } else { |
| assert(type.is_name()); |
| assert(options_->features.function_references_enabled()); |
| type_names->emplace(out_type_list->size(), type.name()); |
| out_type_list->push_back(Type(Type::Reference, kInvalidIndex)); |
| } |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseRefKind(Type* out_type) { |
| WABT_TRACE(ParseRefKind); |
| if (!IsTokenTypeRefKind(Peek())) { |
| return ErrorExpected({"func", "extern", "exn"}); |
| } |
| |
| Token token = Consume(); |
| Type type = token.type(); |
| |
| if ((type == Type::ExternRef && |
| !options_->features.reference_types_enabled()) || |
| ((type == Type::Struct || type == Type::Array) && |
| !options_->features.gc_enabled())) { |
| Error(token.loc, "value type not allowed: %s", type.GetName()); |
| return Result::Error; |
| } |
| |
| *out_type = type; |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseRefType(Type* out_type) { |
| WABT_TRACE(ParseRefType); |
| if (!PeekMatch(TokenType::ValueType)) { |
| return ErrorExpected({"funcref", "externref"}); |
| } |
| |
| Token token = Consume(); |
| Type type = token.type(); |
| if (type == Type::ExternRef && |
| !options_->features.reference_types_enabled()) { |
| Error(token.loc, "value type not allowed: %s", type.GetName()); |
| return Result::Error; |
| } |
| |
| *out_type = type; |
| return Result::Ok; |
| } |
| |
| bool WastParser::ParseRefTypeOpt(Type* out_type) { |
| WABT_TRACE(ParseRefTypeOpt); |
| if (!PeekMatch(TokenType::ValueType)) { |
| return false; |
| } |
| |
| Token token = Consume(); |
| Type type = token.type(); |
| if (type == Type::ExternRef && |
| !options_->features.reference_types_enabled()) { |
| return false; |
| } |
| |
| *out_type = type; |
| return true; |
| } |
| |
| Result WastParser::ParseQuotedText(std::string* text) { |
| WABT_TRACE(ParseQuotedText); |
| if (!PeekMatch(TokenType::Text)) { |
| return ErrorExpected({"a quoted string"}, "\"foo\""); |
| } |
| |
| Token token = Consume(); |
| RemoveEscapes(token.text(), std::back_inserter(*text)); |
| if (!IsValidUtf8(text->data(), text->length())) { |
| Error(token.loc, "quoted string has an invalid utf-8 encoding"); |
| } |
| return Result::Ok; |
| } |
| |
| bool WastParser::ParseOffsetOpt(Address* out_offset) { |
| WABT_TRACE(ParseOffsetOpt); |
| if (PeekMatch(TokenType::OffsetEqNat)) { |
| Token token = Consume(); |
| uint64_t offset64; |
| string_view sv = token.text(); |
| if (Failed(ParseInt64(sv.begin(), sv.end(), &offset64, |
| ParseIntType::SignedAndUnsigned))) { |
| Error(token.loc, "invalid offset \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(sv)); |
| } |
| // FIXME: make this depend on the current memory. |
| if (offset64 > UINT32_MAX) { |
| Error(token.loc, "offset must be less than or equal to 0xffffffff"); |
| } |
| *out_offset = offset64; |
| return true; |
| } else { |
| *out_offset = 0; |
| return false; |
| } |
| } |
| |
| bool WastParser::ParseAlignOpt(Address* out_align) { |
| WABT_TRACE(ParseAlignOpt); |
| if (PeekMatch(TokenType::AlignEqNat)) { |
| Token token = Consume(); |
| string_view sv = token.text(); |
| if (Failed(ParseInt64(sv.begin(), sv.end(), out_align, |
| ParseIntType::UnsignedOnly))) { |
| Error(token.loc, "invalid alignment \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(sv)); |
| } |
| |
| if (!IsPowerOfTwo(*out_align)) { |
| Error(token.loc, "alignment must be power-of-two"); |
| } |
| |
| return true; |
| } else { |
| *out_align = WABT_USE_NATURAL_ALIGNMENT; |
| return false; |
| } |
| } |
| |
| Result WastParser::ParseMemidx(Location loc, Var* out_memidx) { |
| WABT_TRACE(ParseMemidx); |
| if (PeekMatchLpar(TokenType::Memory)) { |
| if (!options_->features.multi_memory_enabled()) { |
| Error(loc, "Specifying memory variable is not allowed"); |
| return Result::Error; |
| } |
| EXPECT(Lpar); |
| EXPECT(Memory); |
| CHECK_RESULT(ParseVar(out_memidx)); |
| EXPECT(Rpar); |
| } else { |
| if (ParseVarOpt(out_memidx, Var(0, loc)) && |
| !options_->features.multi_memory_enabled()) { |
| Error(loc, "Specifying memory variable is not allowed"); |
| return Result::Error; |
| } |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseLimitsIndex(Limits* out_limits) { |
| WABT_TRACE(ParseLimitsIndex); |
| |
| if (PeekMatch(TokenType::ValueType)) { |
| if (GetToken().type() == Type::I64) { |
| Consume(); |
| out_limits->is_64 = true; |
| } else if (GetToken().type() == Type::I32) { |
| Consume(); |
| out_limits->is_64 = false; |
| } |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseLimits(Limits* out_limits) { |
| WABT_TRACE(ParseLimits); |
| |
| CHECK_RESULT(ParseNat(&out_limits->initial, out_limits->is_64)); |
| if (PeekMatch(TokenType::Nat)) { |
| CHECK_RESULT(ParseNat(&out_limits->max, out_limits->is_64)); |
| out_limits->has_max = true; |
| } else { |
| out_limits->has_max = false; |
| } |
| |
| if (Match(TokenType::Shared)) { |
| out_limits->is_shared = true; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseNat(uint64_t* out_nat, bool is_64) { |
| WABT_TRACE(ParseNat); |
| if (!PeekMatch(TokenType::Nat)) { |
| return ErrorExpected({"a natural number"}, "123"); |
| } |
| |
| Token token = Consume(); |
| string_view sv = token.literal().text; |
| if (Failed(ParseUint64(sv.begin(), sv.end(), out_nat)) || |
| (!is_64 && *out_nat > 0xffffffffu)) { |
| Error(token.loc, "invalid int \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(sv)); |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseModule(std::unique_ptr<Module>* out_module) { |
| WABT_TRACE(ParseModule); |
| auto module = MakeUnique<Module>(); |
| |
| if (PeekMatchLpar(TokenType::Module)) { |
| // Starts with "(module". Allow text and binary modules, but no quoted |
| // modules. |
| CommandPtr command; |
| CHECK_RESULT(ParseModuleCommand(nullptr, &command)); |
| auto module_command = cast<ModuleCommand>(std::move(command)); |
| *module = std::move(module_command->module); |
| } else if (IsModuleField(PeekPair())) { |
| // Parse an inline module (i.e. one with no surrounding (module)). |
| CHECK_RESULT(ParseModuleFieldList(module.get())); |
| } else { |
| ConsumeIfLpar(); |
| ErrorExpected({"a module field", "a module"}); |
| } |
| |
| EXPECT(Eof); |
| if (errors_->size() == 0) { |
| *out_module = std::move(module); |
| return Result::Ok; |
| } else { |
| return Result::Error; |
| } |
| } |
| |
| Result WastParser::ParseScript(std::unique_ptr<Script>* out_script) { |
| WABT_TRACE(ParseScript); |
| auto script = MakeUnique<Script>(); |
| |
| // Don't consume the Lpar yet, even though it is required. This way the |
| // sub-parser functions (e.g. ParseFuncModuleField) can consume it and keep |
| // the parsing structure more regular. |
| if (IsModuleField(PeekPair())) { |
| // Parse an inline module (i.e. one with no surrounding (module)). |
| auto command = MakeUnique<ModuleCommand>(); |
| command->module.loc = GetLocation(); |
| CHECK_RESULT(ParseModuleFieldList(&command->module)); |
| script->commands.emplace_back(std::move(command)); |
| } else if (IsCommand(PeekPair())) { |
| CHECK_RESULT(ParseCommandList(script.get(), &script->commands)); |
| } else { |
| ConsumeIfLpar(); |
| ErrorExpected({"a module field", "a command"}); |
| } |
| |
| EXPECT(Eof); |
| if (errors_->size() == 0) { |
| *out_script = std::move(script); |
| return Result::Ok; |
| } else { |
| return Result::Error; |
| } |
| } |
| |
| Result WastParser::ParseModuleFieldList(Module* module) { |
| WABT_TRACE(ParseModuleFieldList); |
| while (IsModuleField(PeekPair())) { |
| if (Failed(ParseModuleField(module))) { |
| CHECK_RESULT(Synchronize(IsModuleField)); |
| } |
| } |
| CHECK_RESULT(ResolveFuncTypes(module, errors_)); |
| CHECK_RESULT(ResolveNamesModule(module, errors_)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseModuleField(Module* module) { |
| WABT_TRACE(ParseModuleField); |
| switch (Peek(1)) { |
| case TokenType::Data: return ParseDataModuleField(module); |
| case TokenType::Elem: return ParseElemModuleField(module); |
| case TokenType::Tag: return ParseTagModuleField(module); |
| case TokenType::Export: return ParseExportModuleField(module); |
| case TokenType::Func: return ParseFuncModuleField(module); |
| case TokenType::Type: return ParseTypeModuleField(module); |
| case TokenType::Global: return ParseGlobalModuleField(module); |
| case TokenType::Import: return ParseImportModuleField(module); |
| case TokenType::Memory: return ParseMemoryModuleField(module); |
| case TokenType::Start: return ParseStartModuleField(module); |
| case TokenType::Table: return ParseTableModuleField(module); |
| default: |
| assert( |
| !"ParseModuleField should only be called if IsModuleField() is true"); |
| return Result::Error; |
| } |
| } |
| |
| Result WastParser::ParseDataModuleField(Module* module) { |
| WABT_TRACE(ParseDataModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Data); |
| std::string name; |
| ParseBindVarOpt(&name); |
| auto field = MakeUnique<DataSegmentModuleField>(loc, name); |
| |
| if (PeekMatchLpar(TokenType::Memory)) { |
| EXPECT(Lpar); |
| EXPECT(Memory); |
| CHECK_RESULT(ParseVar(&field->data_segment.memory_var)); |
| EXPECT(Rpar); |
| CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset.exprs)); |
| } else if (ParseVarOpt(&field->data_segment.memory_var, Var(0, loc))) { |
| CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset.exprs)); |
| } else if (!ParseOffsetExprOpt(&field->data_segment.offset.exprs)) { |
| if (!options_->features.bulk_memory_enabled()) { |
| Error(loc, "passive data segments are not allowed"); |
| return Result::Error; |
| } |
| |
| field->data_segment.kind = SegmentKind::Passive; |
| } |
| |
| ParseTextListOpt(&field->data_segment.data); |
| EXPECT(Rpar); |
| module->AppendField(std::move(field)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseElemModuleField(Module* module) { |
| WABT_TRACE(ParseElemModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Elem); |
| |
| // With MVP text format the name here was intended to refer to the table |
| // that the elem segment was part of, but we never did anything with this name |
| // since there was only one table anyway. |
| // With bulk-memory enabled this introduces a new name for the particular |
| // elem segment. |
| std::string initial_name; |
| bool has_name = ParseBindVarOpt(&initial_name); |
| |
| std::string segment_name = initial_name; |
| if (!options_->features.bulk_memory_enabled()) { |
| segment_name = ""; |
| } |
| auto field = MakeUnique<ElemSegmentModuleField>(loc, segment_name); |
| if (options_->features.reference_types_enabled() && |
| Match(TokenType::Declare)) { |
| field->elem_segment.kind = SegmentKind::Declared; |
| } |
| |
| // Optional table specifier |
| if (options_->features.bulk_memory_enabled()) { |
| if (PeekMatchLpar(TokenType::Table)) { |
| EXPECT(Lpar); |
| EXPECT(Table); |
| CHECK_RESULT(ParseVar(&field->elem_segment.table_var)); |
| EXPECT(Rpar); |
| } else { |
| ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)); |
| } |
| } else { |
| if (has_name) { |
| field->elem_segment.table_var = Var(initial_name, loc); |
| } else { |
| ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)); |
| } |
| } |
| |
| // Parse offset expression, if not declared/passive segment. |
| if (options_->features.bulk_memory_enabled()) { |
| if (field->elem_segment.kind != SegmentKind::Declared && |
| !ParseOffsetExprOpt(&field->elem_segment.offset.exprs)) { |
| field->elem_segment.kind = SegmentKind::Passive; |
| } |
| } else { |
| CHECK_RESULT(ParseOffsetExpr(&field->elem_segment.offset.exprs)); |
| } |
| |
| if (ParseRefTypeOpt(&field->elem_segment.elem_type)) { |
| ParseElemExprListOpt(&field->elem_segment.elem_exprs); |
| } else { |
| field->elem_segment.elem_type = Type::FuncRef; |
| if (PeekMatch(TokenType::Func)) { |
| EXPECT(Func); |
| } |
| ParseElemExprVarListOpt(&field->elem_segment.elem_exprs); |
| } |
| EXPECT(Rpar); |
| module->AppendField(std::move(field)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseTagModuleField(Module* module) { |
| WABT_TRACE(ParseTagModuleField); |
| EXPECT(Lpar); |
| EXPECT(Tag); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| ModuleFieldList export_fields; |
| CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Tag)); |
| |
| if (PeekMatchLpar(TokenType::Import)) { |
| CheckImportOrdering(module); |
| auto import = MakeUnique<TagImport>(name); |
| CHECK_RESULT(ParseInlineImport(import.get())); |
| CHECK_RESULT(ParseTypeUseOpt(&import->tag.decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&import->tag.decl.sig)); |
| auto field = |
| MakeUnique<ImportModuleField>(std::move(import), GetLocation()); |
| module->AppendField(std::move(field)); |
| } else { |
| auto field = MakeUnique<TagModuleField>(GetLocation(), name); |
| CHECK_RESULT(ParseTypeUseOpt(&field->tag.decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&field->tag.decl.sig)); |
| module->AppendField(std::move(field)); |
| } |
| |
| AppendInlineExportFields(module, &export_fields, module->tags.size() - 1); |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExportModuleField(Module* module) { |
| WABT_TRACE(ParseExportModuleField); |
| EXPECT(Lpar); |
| auto field = MakeUnique<ExportModuleField>(GetLocation()); |
| EXPECT(Export); |
| CHECK_RESULT(ParseQuotedText(&field->export_.name)); |
| CHECK_RESULT(ParseExportDesc(&field->export_)); |
| EXPECT(Rpar); |
| module->AppendField(std::move(field)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseFuncModuleField(Module* module) { |
| WABT_TRACE(ParseFuncModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Func); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| ModuleFieldList export_fields; |
| CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Func)); |
| |
| if (PeekMatchLpar(TokenType::Import)) { |
| CheckImportOrdering(module); |
| auto import = MakeUnique<FuncImport>(name); |
| Func& func = import->func; |
| CHECK_RESULT(ParseInlineImport(import.get())); |
| CHECK_RESULT(ParseTypeUseOpt(&func.decl)); |
| CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings)); |
| CHECK_RESULT(ErrorIfLpar({"type", "param", "result"})); |
| auto field = |
| MakeUnique<ImportModuleField>(std::move(import), GetLocation()); |
| module->AppendField(std::move(field)); |
| } else { |
| auto field = MakeUnique<FuncModuleField>(loc, name); |
| Func& func = field->func; |
| CHECK_RESULT(ParseTypeUseOpt(&func.decl)); |
| CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings)); |
| TypeVector local_types; |
| CHECK_RESULT(ParseBoundValueTypeList( |
| TokenType::Local, &local_types, &func.bindings, |
| &func.decl.sig.param_type_names, func.GetNumParams())); |
| func.local_types.Set(local_types); |
| CHECK_RESULT(ParseTerminatingInstrList(&func.exprs)); |
| module->AppendField(std::move(field)); |
| } |
| |
| AppendInlineExportFields(module, &export_fields, module->funcs.size() - 1); |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseTypeModuleField(Module* module) { |
| WABT_TRACE(ParseTypeModuleField); |
| EXPECT(Lpar); |
| auto field = MakeUnique<TypeModuleField>(GetLocation()); |
| EXPECT(Type); |
| |
| std::string name; |
| ParseBindVarOpt(&name); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| |
| if (Match(TokenType::Func)) { |
| auto func_type = MakeUnique<FuncType>(name); |
| BindingHash bindings; |
| CHECK_RESULT(ParseFuncSignature(&func_type->sig, &bindings)); |
| CHECK_RESULT(ErrorIfLpar({"param", "result"})); |
| field->type = std::move(func_type); |
| } else if (Match(TokenType::Struct)) { |
| if (!options_->features.gc_enabled()) { |
| Error(loc, "struct not allowed"); |
| return Result::Error; |
| } |
| auto struct_type = MakeUnique<StructType>(name); |
| CHECK_RESULT(ParseFieldList(&struct_type->fields)); |
| field->type = std::move(struct_type); |
| } else if (Match(TokenType::Array)) { |
| if (!options_->features.gc_enabled()) { |
| Error(loc, "array type not allowed"); |
| } |
| auto array_type = MakeUnique<ArrayType>(name); |
| CHECK_RESULT(ParseField(&array_type->field)); |
| field->type = std::move(array_type); |
| } else { |
| return ErrorExpected({"func", "struct", "array"}); |
| } |
| |
| EXPECT(Rpar); |
| EXPECT(Rpar); |
| module->AppendField(std::move(field)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseField(Field* field) { |
| WABT_TRACE(ParseField); |
| auto parse_mut_valuetype = [&]() -> Result { |
| // TODO: Share with ParseGlobalType? |
| if (MatchLpar(TokenType::Mut)) { |
| field->mutable_ = true; |
| Var type; |
| CHECK_RESULT(ParseValueType(&type)); |
| field->type = Type(type.index()); |
| EXPECT(Rpar); |
| } else { |
| field->mutable_ = false; |
| Var type; |
| CHECK_RESULT(ParseValueType(&type)); |
| field->type = Type(type.index()); |
| } |
| return Result::Ok; |
| }; |
| |
| if (MatchLpar(TokenType::Field)) { |
| ParseBindVarOpt(&field->name); |
| CHECK_RESULT(parse_mut_valuetype()); |
| EXPECT(Rpar); |
| } else { |
| CHECK_RESULT(parse_mut_valuetype()); |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseFieldList(std::vector<Field>* fields) { |
| WABT_TRACE(ParseFieldList); |
| while (PeekMatch(TokenType::ValueType) || PeekMatch(TokenType::Lpar)) { |
| Field field; |
| CHECK_RESULT(ParseField(&field)); |
| fields->push_back(field); |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseGlobalModuleField(Module* module) { |
| WABT_TRACE(ParseGlobalModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Global); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| ModuleFieldList export_fields; |
| CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Global)); |
| |
| if (PeekMatchLpar(TokenType::Import)) { |
| CheckImportOrdering(module); |
| auto import = MakeUnique<GlobalImport>(name); |
| CHECK_RESULT(ParseInlineImport(import.get())); |
| CHECK_RESULT(ParseGlobalType(&import->global)); |
| auto field = |
| MakeUnique<ImportModuleField>(std::move(import), GetLocation()); |
| module->AppendField(std::move(field)); |
| } else { |
| auto field = MakeUnique<GlobalModuleField>(loc, name); |
| CHECK_RESULT(ParseGlobalType(&field->global)); |
| CHECK_RESULT(ParseTerminatingInstrList(&field->global.init_expr.exprs)); |
| module->AppendField(std::move(field)); |
| } |
| |
| AppendInlineExportFields(module, &export_fields, module->globals.size() - 1); |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseImportModuleField(Module* module) { |
| WABT_TRACE(ParseImportModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| CheckImportOrdering(module); |
| EXPECT(Import); |
| std::string module_name; |
| std::string field_name; |
| CHECK_RESULT(ParseQuotedText(&module_name)); |
| CHECK_RESULT(ParseQuotedText(&field_name)); |
| EXPECT(Lpar); |
| |
| std::unique_ptr<ImportModuleField> field; |
| std::string name; |
| |
| switch (Peek()) { |
| case TokenType::Func: { |
| Consume(); |
| ParseBindVarOpt(&name); |
| auto import = MakeUnique<FuncImport>(name); |
| if (PeekMatchLpar(TokenType::Type)) { |
| import->func.decl.has_func_type = true; |
| CHECK_RESULT(ParseTypeUseOpt(&import->func.decl)); |
| EXPECT(Rpar); |
| } else { |
| CHECK_RESULT( |
| ParseFuncSignature(&import->func.decl.sig, &import->func.bindings)); |
| CHECK_RESULT(ErrorIfLpar({"param", "result"})); |
| EXPECT(Rpar); |
| } |
| field = MakeUnique<ImportModuleField>(std::move(import), loc); |
| break; |
| } |
| |
| case TokenType::Table: { |
| Consume(); |
| ParseBindVarOpt(&name); |
| auto import = MakeUnique<TableImport>(name); |
| CHECK_RESULT(ParseLimits(&import->table.elem_limits)); |
| CHECK_RESULT(ParseRefType(&import->table.elem_type)); |
| EXPECT(Rpar); |
| field = MakeUnique<ImportModuleField>(std::move(import), loc); |
| break; |
| } |
| |
| case TokenType::Memory: { |
| Consume(); |
| ParseBindVarOpt(&name); |
| auto import = MakeUnique<MemoryImport>(name); |
| CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); |
| CHECK_RESULT(ParseLimits(&import->memory.page_limits)); |
| EXPECT(Rpar); |
| field = MakeUnique<ImportModuleField>(std::move(import), loc); |
| break; |
| } |
| |
| case TokenType::Global: { |
| Consume(); |
| ParseBindVarOpt(&name); |
| auto import = MakeUnique<GlobalImport>(name); |
| CHECK_RESULT(ParseGlobalType(&import->global)); |
| EXPECT(Rpar); |
| field = MakeUnique<ImportModuleField>(std::move(import), loc); |
| break; |
| } |
| |
| case TokenType::Tag: { |
| Consume(); |
| ParseBindVarOpt(&name); |
| auto import = MakeUnique<TagImport>(name); |
| CHECK_RESULT(ParseTypeUseOpt(&import->tag.decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&import->tag.decl.sig)); |
| EXPECT(Rpar); |
| field = MakeUnique<ImportModuleField>(std::move(import), loc); |
| break; |
| } |
| |
| default: |
| return ErrorExpected({"an external kind"}); |
| } |
| |
| field->import->module_name = module_name; |
| field->import->field_name = field_name; |
| |
| module->AppendField(std::move(field)); |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseMemoryModuleField(Module* module) { |
| WABT_TRACE(ParseMemoryModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Memory); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| ModuleFieldList export_fields; |
| CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Memory)); |
| |
| if (PeekMatchLpar(TokenType::Import)) { |
| CheckImportOrdering(module); |
| auto import = MakeUnique<MemoryImport>(name); |
| CHECK_RESULT(ParseInlineImport(import.get())); |
| CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); |
| CHECK_RESULT(ParseLimits(&import->memory.page_limits)); |
| auto field = |
| MakeUnique<ImportModuleField>(std::move(import), GetLocation()); |
| module->AppendField(std::move(field)); |
| } else { |
| auto field = MakeUnique<MemoryModuleField>(loc, name); |
| CHECK_RESULT(ParseLimitsIndex(&field->memory.page_limits)); |
| if (MatchLpar(TokenType::Data)) { |
| auto data_segment_field = MakeUnique<DataSegmentModuleField>(loc); |
| DataSegment& data_segment = data_segment_field->data_segment; |
| data_segment.memory_var = Var(module->memories.size()); |
| data_segment.offset.exprs.push_back(MakeUnique<ConstExpr>( |
| field->memory.page_limits.is_64 ? Const::I64(0) : Const::I32(0))); |
| data_segment.offset.exprs.back().loc = loc; |
| ParseTextListOpt(&data_segment.data); |
| EXPECT(Rpar); |
| |
| uint32_t byte_size = WABT_ALIGN_UP_TO_PAGE(data_segment.data.size()); |
| uint32_t page_size = WABT_BYTES_TO_PAGES(byte_size); |
| field->memory.page_limits.initial = page_size; |
| field->memory.page_limits.max = page_size; |
| field->memory.page_limits.has_max = true; |
| |
| module->AppendField(std::move(field)); |
| module->AppendField(std::move(data_segment_field)); |
| } else { |
| CHECK_RESULT(ParseLimits(&field->memory.page_limits)); |
| module->AppendField(std::move(field)); |
| } |
| } |
| |
| AppendInlineExportFields(module, &export_fields, module->memories.size() - 1); |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseStartModuleField(Module* module) { |
| WABT_TRACE(ParseStartModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| if (module->starts.size() > 0) { |
| Error(loc, "multiple start sections"); |
| return Result::Error; |
| } |
| EXPECT(Start); |
| Var var; |
| CHECK_RESULT(ParseVar(&var)); |
| EXPECT(Rpar); |
| module->AppendField(MakeUnique<StartModuleField>(var, loc)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseTableModuleField(Module* module) { |
| WABT_TRACE(ParseTableModuleField); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Table); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| ModuleFieldList export_fields; |
| CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Table)); |
| |
| if (PeekMatchLpar(TokenType::Import)) { |
| CheckImportOrdering(module); |
| auto import = MakeUnique<TableImport>(name); |
| CHECK_RESULT(ParseInlineImport(import.get())); |
| CHECK_RESULT(ParseLimits(&import->table.elem_limits)); |
| CHECK_RESULT(ParseRefType(&import->table.elem_type)); |
| auto field = |
| MakeUnique<ImportModuleField>(std::move(import), GetLocation()); |
| module->AppendField(std::move(field)); |
| } else if (PeekMatch(TokenType::ValueType)) { |
| Type elem_type; |
| CHECK_RESULT(ParseRefType(&elem_type)); |
| |
| EXPECT(Lpar); |
| EXPECT(Elem); |
| |
| auto elem_segment_field = MakeUnique<ElemSegmentModuleField>(loc); |
| ElemSegment& elem_segment = elem_segment_field->elem_segment; |
| elem_segment.table_var = Var(module->tables.size()); |
| elem_segment.offset.exprs.push_back(MakeUnique<ConstExpr>(Const::I32(0))); |
| elem_segment.offset.exprs.back().loc = loc; |
| elem_segment.elem_type = elem_type; |
| // Syntax is either an optional list of var (legacy), or a non-empty list |
| // of elem expr. |
| InitExpr elem_expr; |
| if (ParseElemExprOpt(&elem_expr.exprs)) { |
| elem_segment.elem_exprs.push_back(std::move(elem_expr)); |
| // Parse the rest. |
| ParseElemExprListOpt(&elem_segment.elem_exprs); |
| } else { |
| ParseElemExprVarListOpt(&elem_segment.elem_exprs); |
| } |
| EXPECT(Rpar); |
| |
| auto table_field = MakeUnique<TableModuleField>(loc, name); |
| table_field->table.elem_limits.initial = elem_segment.elem_exprs.size(); |
| table_field->table.elem_limits.max = elem_segment.elem_exprs.size(); |
| table_field->table.elem_limits.has_max = true; |
| table_field->table.elem_type = elem_type; |
| module->AppendField(std::move(table_field)); |
| module->AppendField(std::move(elem_segment_field)); |
| } else { |
| auto field = MakeUnique<TableModuleField>(loc, name); |
| CHECK_RESULT(ParseLimits(&field->table.elem_limits)); |
| CHECK_RESULT(ParseRefType(&field->table.elem_type)); |
| module->AppendField(std::move(field)); |
| } |
| |
| AppendInlineExportFields(module, &export_fields, module->tables.size() - 1); |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExportDesc(Export* export_) { |
| WABT_TRACE(ParseExportDesc); |
| EXPECT(Lpar); |
| switch (Peek()) { |
| case TokenType::Func: export_->kind = ExternalKind::Func; break; |
| case TokenType::Table: export_->kind = ExternalKind::Table; break; |
| case TokenType::Memory: export_->kind = ExternalKind::Memory; break; |
| case TokenType::Global: export_->kind = ExternalKind::Global; break; |
| case TokenType::Tag: export_->kind = ExternalKind::Tag; break; |
| default: |
| return ErrorExpected({"an external kind"}); |
| } |
| Consume(); |
| CHECK_RESULT(ParseVar(&export_->var)); |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseInlineExports(ModuleFieldList* fields, |
| ExternalKind kind) { |
| WABT_TRACE(ParseInlineExports); |
| while (PeekMatchLpar(TokenType::Export)) { |
| EXPECT(Lpar); |
| auto field = MakeUnique<ExportModuleField>(GetLocation()); |
| field->export_.kind = kind; |
| EXPECT(Export); |
| CHECK_RESULT(ParseQuotedText(&field->export_.name)); |
| EXPECT(Rpar); |
| fields->push_back(std::move(field)); |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseInlineImport(Import* import) { |
| WABT_TRACE(ParseInlineImport); |
| EXPECT(Lpar); |
| EXPECT(Import); |
| CHECK_RESULT(ParseQuotedText(&import->module_name)); |
| CHECK_RESULT(ParseQuotedText(&import->field_name)); |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseTypeUseOpt(FuncDeclaration* decl) { |
| WABT_TRACE(ParseTypeUseOpt); |
| if (MatchLpar(TokenType::Type)) { |
| decl->has_func_type = true; |
| CHECK_RESULT(ParseVar(&decl->type_var)); |
| EXPECT(Rpar); |
| } else { |
| decl->has_func_type = false; |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseFuncSignature(FuncSignature* sig, |
| BindingHash* param_bindings) { |
| WABT_TRACE(ParseFuncSignature); |
| CHECK_RESULT(ParseBoundValueTypeList(TokenType::Param, &sig->param_types, |
| param_bindings, &sig->param_type_names)); |
| CHECK_RESULT(ParseResultList(&sig->result_types, &sig->result_type_names)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseUnboundFuncSignature(FuncSignature* sig) { |
| WABT_TRACE(ParseUnboundFuncSignature); |
| CHECK_RESULT(ParseUnboundValueTypeList(TokenType::Param, &sig->param_types, |
| &sig->param_type_names)); |
| CHECK_RESULT(ParseResultList(&sig->result_types, &sig->result_type_names)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseBoundValueTypeList( |
| TokenType token, |
| TypeVector* types, |
| BindingHash* bindings, |
| std::unordered_map<uint32_t, std::string>* type_names, |
| Index binding_index_offset) { |
| WABT_TRACE(ParseBoundValueTypeList); |
| while (MatchLpar(token)) { |
| if (PeekMatch(TokenType::Var)) { |
| std::string name; |
| Var type; |
| Location loc = GetLocation(); |
| ParseBindVarOpt(&name); |
| CHECK_RESULT(ParseValueType(&type)); |
| bindings->emplace(name, |
| Binding(loc, binding_index_offset + types->size())); |
| if (type.is_index()) { |
| types->push_back(Type(type.index())); |
| } else { |
| assert(type.is_name()); |
| assert(options_->features.function_references_enabled()); |
| type_names->emplace(binding_index_offset + types->size(), type.name()); |
| types->push_back(Type(Type::Reference, kInvalidIndex)); |
| } |
| } else { |
| CHECK_RESULT(ParseValueTypeList(types, type_names)); |
| } |
| EXPECT(Rpar); |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseUnboundValueTypeList( |
| TokenType token, |
| TypeVector* types, |
| std::unordered_map<uint32_t, std::string>* type_names) { |
| WABT_TRACE(ParseUnboundValueTypeList); |
| while (MatchLpar(token)) { |
| CHECK_RESULT(ParseValueTypeList(types, type_names)); |
| EXPECT(Rpar); |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseResultList( |
| TypeVector* result_types, |
| std::unordered_map<uint32_t, std::string>* type_names) { |
| WABT_TRACE(ParseResultList); |
| return ParseUnboundValueTypeList(TokenType::Result, result_types, type_names); |
| } |
| |
| Result WastParser::ParseInstrList(ExprList* exprs) { |
| WABT_TRACE(ParseInstrList); |
| ExprList new_exprs; |
| while (IsInstr(PeekPair())) { |
| if (Succeeded(ParseInstr(&new_exprs))) { |
| exprs->splice(exprs->end(), new_exprs); |
| } else { |
| CHECK_RESULT(Synchronize(IsInstr)); |
| } |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseTerminatingInstrList(ExprList* exprs) { |
| WABT_TRACE(ParseTerminatingInstrList); |
| Result result = ParseInstrList(exprs); |
| // An InstrList often has no further Lpar following it, because it would have |
| // gobbled it up. So if there is a following Lpar it is an error. If we |
| // handle it here we can produce a nicer error message. |
| CHECK_RESULT(ErrorIfLpar({"an instr"})); |
| return result; |
| } |
| |
| Result WastParser::ParseInstr(ExprList* exprs) { |
| WABT_TRACE(ParseInstr); |
| if (IsPlainInstr(Peek())) { |
| std::unique_ptr<Expr> expr; |
| CHECK_RESULT(ParsePlainInstr(&expr)); |
| exprs->push_back(std::move(expr)); |
| return Result::Ok; |
| } else if (IsBlockInstr(Peek())) { |
| std::unique_ptr<Expr> expr; |
| CHECK_RESULT(ParseBlockInstr(&expr)); |
| exprs->push_back(std::move(expr)); |
| return Result::Ok; |
| } else if (PeekMatchExpr()) { |
| return ParseExpr(exprs); |
| } else { |
| assert(!"ParseInstr should only be called when IsInstr() is true"); |
| return Result::Error; |
| } |
| } |
| |
| template <typename T> |
| Result WastParser::ParsePlainInstrVar(Location loc, |
| std::unique_ptr<Expr>* out_expr) { |
| Var var; |
| CHECK_RESULT(ParseVar(&var)); |
| out_expr->reset(new T(var, loc)); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseMemoryInstrVar(Location loc, |
| std::unique_ptr<Expr>* out_expr) { |
| Var memidx; |
| Var var; |
| if (PeekMatchLpar(TokenType::Memory)) { |
| if (!options_->features.multi_memory_enabled()) { |
| Error(loc, "Specifying memory variable is not allowed"); |
| return Result::Error; |
| } |
| CHECK_RESULT(ParseMemidx(loc, &memidx)); |
| CHECK_RESULT(ParseVar(&var)); |
| out_expr->reset(new T(var, memidx, loc)); |
| } else { |
| CHECK_RESULT(ParseVar(&memidx)); |
| if (ParseVarOpt(&var, Var(0, loc))) { |
| if (!options_->features.multi_memory_enabled()) { |
| Error(loc, "Specifiying memory variable is not allowed"); |
| return Result::Error; |
| } |
| out_expr->reset(new T(var, memidx, loc)); |
| } else { |
| out_expr->reset(new T(memidx, var, loc)); |
| } |
| } |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParsePlainLoadStoreInstr(Location loc, |
| Token token, |
| std::unique_ptr<Expr>* out_expr) { |
| Opcode opcode = token.opcode(); |
| Address offset; |
| Address align; |
| ParseOffsetOpt(&offset); |
| ParseAlignOpt(&align); |
| out_expr->reset(new T(opcode, align, offset, loc)); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseMemoryLoadStoreInstr(Location loc, |
| Token token, |
| std::unique_ptr<Expr>* out_expr) { |
| Opcode opcode = token.opcode(); |
| Var memidx; |
| Address offset; |
| Address align; |
| CHECK_RESULT(ParseMemidx(loc, &memidx)); |
| ParseOffsetOpt(&offset); |
| ParseAlignOpt(&align); |
| out_expr->reset(new T(opcode, memidx, align, offset, loc)); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseMemoryExpr(Location loc, |
| std::unique_ptr<Expr>* out_expr) { |
| Var memidx; |
| CHECK_RESULT(ParseMemidx(loc, &memidx)); |
| out_expr->reset(new T(memidx, loc)); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseMemoryBinaryExpr(Location loc, |
| std::unique_ptr<Expr>* out_expr) { |
| Var srcmemidx; |
| Var destmemidx; |
| CHECK_RESULT(ParseMemidx(loc, &srcmemidx)); |
| CHECK_RESULT(ParseMemidx(loc, &destmemidx)); |
| out_expr->reset(new T(srcmemidx, destmemidx, loc)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseSimdLane(Location loc, uint64_t* lane_idx) { |
| if (!PeekMatch(TokenType::Nat) && !PeekMatch(TokenType::Int)) { |
| return ErrorExpected({"a natural number in range [0, 32)"}); |
| } |
| |
| Literal literal = Consume().literal(); |
| |
| Result result = ParseInt64(literal.text.begin(), literal.text.end(), |
| lane_idx, ParseIntType::UnsignedOnly); |
| |
| if (Failed(result)) { |
| Error(loc, "invalid literal \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(literal.text)); |
| return Result::Error; |
| } |
| |
| // The valid range is only [0, 32), but it's only malformed if it can't |
| // fit in a byte. |
| if (*lane_idx > 255) { |
| Error(loc, "lane index \"" PRIstringview "\" out-of-range [0, 32)", |
| WABT_PRINTF_STRING_VIEW_ARG(literal.text)); |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParsePlainInstr(std::unique_ptr<Expr>* out_expr) { |
| WABT_TRACE(ParsePlainInstr); |
| Location loc = GetLocation(); |
| switch (Peek()) { |
| case TokenType::Unreachable: |
| Consume(); |
| out_expr->reset(new UnreachableExpr(loc)); |
| break; |
| |
| case TokenType::Nop: |
| Consume(); |
| out_expr->reset(new NopExpr(loc)); |
| break; |
| |
| case TokenType::Drop: |
| Consume(); |
| out_expr->reset(new DropExpr(loc)); |
| break; |
| |
| case TokenType::Select: { |
| Consume(); |
| TypeVector result; |
| if (options_->features.reference_types_enabled() && |
| MatchLpar(TokenType::Result)) { |
| CHECK_RESULT(ParseValueTypeList(&result, nullptr)); |
| EXPECT(Rpar); |
| } |
| out_expr->reset(new SelectExpr(result, loc)); |
| break; |
| } |
| |
| case TokenType::Br: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<BrExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::BrIf: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<BrIfExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::BrTable: { |
| Consume(); |
| auto expr = MakeUnique<BrTableExpr>(loc); |
| CHECK_RESULT(ParseVarList(&expr->targets)); |
| expr->default_target = expr->targets.back(); |
| expr->targets.pop_back(); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::Return: |
| Consume(); |
| out_expr->reset(new ReturnExpr(loc)); |
| break; |
| |
| case TokenType::Call: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<CallExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::CallIndirect: { |
| Consume(); |
| auto expr = MakeUnique<CallIndirectExpr>(loc); |
| ParseVarOpt(&expr->table, Var(0, loc)); |
| CHECK_RESULT(ParseTypeUseOpt(&expr->decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::CallRef: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| out_expr->reset(new CallRefExpr(loc)); |
| break; |
| } |
| |
| case TokenType::ReturnCall: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<ReturnCallExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::ReturnCallIndirect: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| auto expr = MakeUnique<ReturnCallIndirectExpr>(loc); |
| CHECK_RESULT(ParseTypeUseOpt(&expr->decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig)); |
| ParseVarOpt(&expr->table, Var(0, loc)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::LocalGet: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<LocalGetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::LocalSet: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<LocalSetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::LocalTee: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<LocalTeeExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::GlobalGet: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<GlobalGetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::GlobalSet: |
| Consume(); |
| CHECK_RESULT(ParsePlainInstrVar<GlobalSetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::Load: |
| CHECK_RESULT( |
| ParseMemoryLoadStoreInstr<LoadExpr>(loc, Consume(), out_expr)); |
| break; |
| |
| case TokenType::Store: |
| CHECK_RESULT( |
| ParseMemoryLoadStoreInstr<StoreExpr>(loc, Consume(), out_expr)); |
| break; |
| |
| case TokenType::Const: { |
| Const const_; |
| CHECK_RESULT(ParseConst(&const_, ConstType::Normal)); |
| out_expr->reset(new ConstExpr(const_, loc)); |
| break; |
| } |
| |
| case TokenType::Unary: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| out_expr->reset(new UnaryExpr(token.opcode(), loc)); |
| break; |
| } |
| |
| case TokenType::Binary: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| out_expr->reset(new BinaryExpr(token.opcode(), loc)); |
| break; |
| } |
| |
| case TokenType::Compare: |
| out_expr->reset(new CompareExpr(Consume().opcode(), loc)); |
| break; |
| |
| case TokenType::Convert: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| out_expr->reset(new ConvertExpr(token.opcode(), loc)); |
| break; |
| } |
| |
| case TokenType::MemoryCopy: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParseMemoryBinaryExpr<MemoryCopyExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::MemoryFill: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParseMemoryExpr<MemoryFillExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::DataDrop: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<DataDropExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::MemoryInit: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParseMemoryInstrVar<MemoryInitExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::MemorySize: |
| Consume(); |
| CHECK_RESULT(ParseMemoryExpr<MemorySizeExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::MemoryGrow: |
| Consume(); |
| CHECK_RESULT(ParseMemoryExpr<MemoryGrowExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableCopy: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| Var dst(0, loc); |
| Var src(0, loc); |
| if (options_->features.reference_types_enabled()) { |
| ParseVarOpt(&dst, dst); |
| ParseVarOpt(&src, src); |
| } |
| out_expr->reset(new TableCopyExpr(dst, src, loc)); |
| break; |
| } |
| |
| case TokenType::ElemDrop: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<ElemDropExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableInit: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| Var segment_index(0, loc); |
| CHECK_RESULT(ParseVar(&segment_index)); |
| Var table_index(0, loc); |
| if (ParseVarOpt(&table_index, table_index)) { |
| // Here are the two forms: |
| // |
| // table.init $elemidx ... |
| // table.init $tableidx $elemidx ... |
| // |
| // So if both indexes are provided, we need to swap them. |
| std::swap(segment_index, table_index); |
| } |
| out_expr->reset(new TableInitExpr(segment_index, table_index, loc)); |
| break; |
| } |
| |
| case TokenType::TableGet: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<TableGetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableSet: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| // TODO: Table index. |
| CHECK_RESULT(ParsePlainInstrVar<TableSetExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableGrow: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| // TODO: Table index. |
| CHECK_RESULT(ParsePlainInstrVar<TableGrowExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableSize: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| // TODO: Table index. |
| CHECK_RESULT(ParsePlainInstrVar<TableSizeExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::TableFill: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| // TODO: Table index. |
| CHECK_RESULT(ParsePlainInstrVar<TableFillExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::RefFunc: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<RefFuncExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::RefNull: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| Type type; |
| CHECK_RESULT(ParseRefKind(&type)); |
| out_expr->reset(new RefNullExpr(type, loc)); |
| break; |
| } |
| |
| case TokenType::RefIsNull: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| out_expr->reset(new RefIsNullExpr(loc)); |
| break; |
| |
| case TokenType::Throw: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<ThrowExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::Rethrow: |
| ErrorUnlessOpcodeEnabled(Consume()); |
| CHECK_RESULT(ParsePlainInstrVar<RethrowExpr>(loc, out_expr)); |
| break; |
| |
| case TokenType::AtomicNotify: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicNotifyExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::AtomicFence: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| uint32_t consistency_model = 0x0; |
| out_expr->reset(new AtomicFenceExpr(consistency_model, loc)); |
| break; |
| } |
| |
| case TokenType::AtomicWait: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicWaitExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::AtomicLoad: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicLoadExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::AtomicStore: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicStoreExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::AtomicRmw: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicRmwExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::AtomicRmwCmpxchg: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| CHECK_RESULT( |
| ParsePlainLoadStoreInstr<AtomicRmwCmpxchgExpr>(loc, token, out_expr)); |
| break; |
| } |
| |
| case TokenType::Ternary: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| out_expr->reset(new TernaryExpr(token.opcode(), loc)); |
| break; |
| } |
| |
| case TokenType::SimdLaneOp: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| |
| uint64_t lane_idx = 0; |
| Result result = ParseSimdLane(loc, &lane_idx); |
| |
| if (Failed(result)) { |
| return Result::Error; |
| } |
| |
| out_expr->reset(new SimdLaneOpExpr(token.opcode(), lane_idx, loc)); |
| break; |
| } |
| |
| case TokenType::SimdLoadLane: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| |
| Address offset; |
| Address align; |
| ParseOffsetOpt(&offset); |
| ParseAlignOpt(&align); |
| |
| uint64_t lane_idx = 0; |
| Result result = ParseSimdLane(loc, &lane_idx); |
| |
| if (Failed(result)) { |
| return Result::Error; |
| } |
| |
| out_expr->reset(new SimdLoadLaneExpr(token.opcode(), align, offset, lane_idx, loc)); |
| break; |
| } |
| |
| case TokenType::SimdStoreLane: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| |
| Address offset; |
| Address align; |
| ParseOffsetOpt(&offset); |
| ParseAlignOpt(&align); |
| |
| uint64_t lane_idx = 0; |
| Result result = ParseSimdLane(loc, &lane_idx); |
| |
| if (Failed(result)) { |
| return Result::Error; |
| } |
| |
| out_expr->reset(new SimdStoreLaneExpr(token.opcode(), align, offset, lane_idx, loc)); |
| break; |
| } |
| |
| case TokenType::SimdShuffleOp: { |
| Token token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| v128 values; |
| for (int lane = 0; lane < 16; ++lane) { |
| Location loc = GetLocation(); |
| uint64_t lane_idx; |
| Result result = ParseSimdLane(loc, &lane_idx); |
| if (Failed(result)) { |
| return Result::Error; |
| } |
| |
| values.set_u8(lane, static_cast<uint8_t>(lane_idx)); |
| } |
| |
| out_expr->reset( |
| new SimdShuffleOpExpr(token.opcode(), values, loc)); |
| break; |
| } |
| |
| default: |
| assert( |
| !"ParsePlainInstr should only be called when IsPlainInstr() is true"); |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseSimdV128Const(Const* const_, |
| TokenType token_type, |
| ConstType const_type) { |
| WABT_TRACE(ParseSimdV128Const); |
| |
| uint8_t lane_count = 0; |
| bool integer = true; |
| switch (token_type) { |
| case TokenType::I8X16: { lane_count = 16; break; } |
| case TokenType::I16X8: { lane_count = 8; break; } |
| case TokenType::I32X4: { lane_count = 4; break; } |
| case TokenType::I64X2: { lane_count = 2; break; } |
| case TokenType::F32X4: { lane_count = 4; integer = false; break; } |
| case TokenType::F64X2: { lane_count = 2; integer = false; break; } |
| default: { |
| Error( |
| const_->loc, |
| "Unexpected type at start of simd constant. " |
| "Expected one of: i8x16, i16x8, i32x4, i64x2, f32x4, f64x2. " |
| "Found \"%s\".", |
| GetTokenTypeName(token_type) |
| ); |
| return Result::Error; |
| } |
| } |
| Consume(); |
| |
| const_->loc = GetLocation(); |
| |
| for (int lane = 0; lane < lane_count; ++lane) { |
| Location loc = GetLocation(); |
| |
| // Check that the lane literal type matches the element type of the v128: |
| Token token = GetToken(); |
| switch (token.token_type()) { |
| case TokenType::Nat: |
| case TokenType::Int: |
| // OK. |
| break; |
| |
| case TokenType::Float: |
| case TokenType::NanArithmetic: |
| case TokenType::NanCanonical: |
| if (integer) { |
| goto error; |
| } |
| break; |
| |
| error: |
| default: |
| if (integer) { |
| return ErrorExpected({"a Nat or Integer literal"}, "123"); |
| } else { |
| return ErrorExpected({"a Float literal"}, "42.0"); |
| } |
| } |
| |
| Result result; |
| |
| // For each type, parse the next literal, bound check it, and write it to |
| // the array of bytes: |
| if (integer) { |
| string_view sv = Consume().literal().text; |
| const char* s = sv.begin(); |
| const char* end = sv.end(); |
| |
| switch (lane_count) { |
| case 16: { |
| uint8_t value = 0; |
| result = ParseInt8(s, end, &value, ParseIntType::SignedAndUnsigned); |
| const_->set_v128_u8(lane, value); |
| break; |
| } |
| case 8: { |
| uint16_t value = 0; |
| result = ParseInt16(s, end, &value, ParseIntType::SignedAndUnsigned); |
| const_->set_v128_u16(lane, value); |
| break; |
| } |
| case 4: { |
| uint32_t value = 0; |
| result = ParseInt32(s, end, &value, ParseIntType::SignedAndUnsigned); |
| const_->set_v128_u32(lane, value); |
| break; |
| } |
| case 2: { |
| uint64_t value = 0; |
| result = ParseInt64(s, end, &value, ParseIntType::SignedAndUnsigned); |
| const_->set_v128_u64(lane, value); |
| break; |
| } |
| } |
| } else { |
| Const lane_const_; |
| switch (lane_count) { |
| case 4: |
| result = ParseF32(&lane_const_, const_type); |
| const_->set_v128_f32(lane, lane_const_.f32_bits()); |
| break; |
| |
| case 2: |
| result = ParseF64(&lane_const_, const_type); |
| const_->set_v128_f64(lane, lane_const_.f64_bits()); |
| break; |
| } |
| |
| const_->set_expected_nan(lane, lane_const_.expected_nan()); |
| } |
| |
| if (Failed(result)) { |
| Error(loc, "invalid literal \"%s\"", token.to_string().c_str()); |
| return Result::Error; |
| } |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExpectedNan(ExpectedNan* expected) { |
| WABT_TRACE(ParseExpectedNan); |
| TokenType token_type = Peek(); |
| switch (token_type) { |
| case TokenType::NanArithmetic: |
| *expected = ExpectedNan::Arithmetic; |
| break; |
| case TokenType::NanCanonical: |
| *expected = ExpectedNan::Canonical; |
| break; |
| default: |
| return Result::Error; |
| } |
| Consume(); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseF32(Const* const_, ConstType const_type) { |
| ExpectedNan expected; |
| if (const_type == ConstType::Expectation && |
| Succeeded(ParseExpectedNan(&expected))) { |
| const_->set_f32(expected); |
| return Result::Ok; |
| } |
| |
| auto token = Consume(); |
| if (!token.HasLiteral()) { |
| return Result::Error; |
| } |
| |
| auto literal = token.literal(); |
| uint32_t f32_bits; |
| Result result = ParseFloat(literal.type, literal.text.begin(), |
| literal.text.end(), &f32_bits); |
| const_->set_f32(f32_bits); |
| return result; |
| } |
| |
| Result WastParser::ParseF64(Const* const_, ConstType const_type) { |
| ExpectedNan expected; |
| if (const_type == ConstType::Expectation && |
| Succeeded(ParseExpectedNan(&expected))) { |
| const_->set_f64(expected); |
| return Result::Ok; |
| } |
| |
| auto token = Consume(); |
| if (!token.HasLiteral()) { |
| return Result::Error; |
| } |
| |
| auto literal = token.literal(); |
| uint64_t f64_bits; |
| Result result = ParseDouble(literal.type, literal.text.begin(), |
| literal.text.end(), &f64_bits); |
| const_->set_f64(f64_bits); |
| return result; |
| } |
| |
| Result WastParser::ParseConst(Const* const_, ConstType const_type) { |
| WABT_TRACE(ParseConst); |
| Token opcode_token = Consume(); |
| Opcode opcode = opcode_token.opcode(); |
| const_->loc = GetLocation(); |
| Token token = GetToken(); |
| |
| // V128 is fully handled by ParseSimdV128Const: |
| if (opcode != Opcode::V128Const) { |
| switch (token.token_type()) { |
| case TokenType::Nat: |
| case TokenType::Int: |
| case TokenType::Float: |
| // OK. |
| break; |
| case TokenType::NanArithmetic: |
| case TokenType::NanCanonical: |
| break; |
| default: |
| return ErrorExpected({"a numeric literal"}, "123, -45, 6.7e8"); |
| } |
| } |
| |
| Result result; |
| switch (opcode) { |
| case Opcode::I32Const: { |
| auto token = Consume(); |
| if (!token.HasLiteral()) { |
| return Result::Error; |
| } |
| auto sv = token.literal().text; |
| uint32_t u32; |
| result = ParseInt32(sv.begin(), sv.end(), &u32, |
| ParseIntType::SignedAndUnsigned); |
| const_->set_u32(u32); |
| break; |
| } |
| |
| case Opcode::I64Const: { |
| auto token = Consume(); |
| if (!token.HasLiteral()) { |
| return Result::Error; |
| } |
| auto sv = token.literal().text; |
| uint64_t u64; |
| result = ParseInt64(sv.begin(), sv.end(), &u64, |
| ParseIntType::SignedAndUnsigned); |
| const_->set_u64(u64); |
| break; |
| } |
| |
| case Opcode::F32Const: |
| result = ParseF32(const_, const_type); |
| break; |
| |
| case Opcode::F64Const: |
| result = ParseF64(const_, const_type); |
| break; |
| |
| case Opcode::V128Const: |
| ErrorUnlessOpcodeEnabled(opcode_token); |
| // Parse V128 Simd Const (16 bytes). |
| result = ParseSimdV128Const(const_, token.token_type(), const_type); |
| // ParseSimdV128Const report error already, just return here if parser get |
| // errors. |
| if (Failed(result)) { |
| return Result::Error; |
| } |
| break; |
| |
| default: |
| assert(!"ParseConst called with invalid opcode"); |
| return Result::Error; |
| } |
| |
| if (Failed(result)) { |
| Error(const_->loc, "invalid literal \"%s\"", token.to_string().c_str()); |
| // Return if parser get errors. |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExternref(Const* const_) { |
| WABT_TRACE(ParseExternref); |
| Token token = Consume(); |
| if (!options_->features.reference_types_enabled()) { |
| Error(token.loc, "externref not allowed"); |
| return Result::Error; |
| } |
| |
| Literal literal; |
| string_view sv; |
| const char* s; |
| const char* end; |
| const_->loc = GetLocation(); |
| TokenType token_type = Peek(); |
| |
| switch (token_type) { |
| case TokenType::Nat: |
| case TokenType::Int: { |
| literal = Consume().literal(); |
| sv = literal.text; |
| s = sv.begin(); |
| end = sv.end(); |
| break; |
| } |
| default: |
| return ErrorExpected({"a numeric literal"}, "123"); |
| } |
| |
| uint64_t ref_bits; |
| Result result = ParseInt64(s, end, &ref_bits, ParseIntType::UnsignedOnly); |
| |
| const_->set_externref(static_cast<uintptr_t>(ref_bits)); |
| |
| if (Failed(result)) { |
| Error(const_->loc, "invalid literal \"" PRIstringview "\"", |
| WABT_PRINTF_STRING_VIEW_ARG(literal.text)); |
| // Return if parser get errors. |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseConstList(ConstVector* consts, ConstType type) { |
| WABT_TRACE(ParseConstList); |
| while (PeekMatchLpar(TokenType::Const) || PeekMatchLpar(TokenType::RefNull) || |
| PeekMatchLpar(TokenType::RefExtern) || |
| PeekMatchLpar(TokenType::RefFunc)) { |
| Consume(); |
| Const const_; |
| switch (Peek()) { |
| case TokenType::Const: |
| CHECK_RESULT(ParseConst(&const_, type)); |
| break; |
| case TokenType::RefNull: { |
| auto token = Consume(); |
| Type type; |
| CHECK_RESULT(ParseRefKind(&type)); |
| ErrorUnlessOpcodeEnabled(token); |
| const_.loc = GetLocation(); |
| const_.set_null(type); |
| break; |
| } |
| case TokenType::RefFunc: { |
| auto token = Consume(); |
| ErrorUnlessOpcodeEnabled(token); |
| const_.loc = GetLocation(); |
| const_.set_funcref(); |
| break; |
| } |
| case TokenType::RefExtern: |
| CHECK_RESULT(ParseExternref(&const_)); |
| break; |
| default: |
| assert(!"unreachable"); |
| return Result::Error; |
| } |
| EXPECT(Rpar); |
| consts->push_back(const_); |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseBlockInstr(std::unique_ptr<Expr>* out_expr) { |
| WABT_TRACE(ParseBlockInstr); |
| Location loc = GetLocation(); |
| |
| switch (Peek()) { |
| case TokenType::Block: { |
| Consume(); |
| auto expr = MakeUnique<BlockExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlock(&expr->block)); |
| EXPECT(End); |
| CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::Loop: { |
| Consume(); |
| auto expr = MakeUnique<LoopExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlock(&expr->block)); |
| EXPECT(End); |
| CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::If: { |
| Consume(); |
| auto expr = MakeUnique<IfExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->true_.label)); |
| CHECK_RESULT(ParseBlock(&expr->true_)); |
| if (Match(TokenType::Else)) { |
| CHECK_RESULT(ParseEndLabelOpt(expr->true_.label)); |
| CHECK_RESULT(ParseTerminatingInstrList(&expr->false_)); |
| expr->false_end_loc = GetLocation(); |
| } |
| EXPECT(End); |
| CHECK_RESULT(ParseEndLabelOpt(expr->true_.label)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| case TokenType::Try: { |
| ErrorUnlessOpcodeEnabled(Consume()); |
| auto expr = MakeUnique<TryExpr>(loc); |
| CatchVector catches; |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlock(&expr->block)); |
| if (IsCatch(Peek())) { |
| CHECK_RESULT(ParseCatchInstrList(&expr->catches)); |
| expr->kind = TryKind::Catch; |
| } else if (PeekMatch(TokenType::Delegate)) { |
| Consume(); |
| Var var; |
| CHECK_RESULT(ParseVar(&var)); |
| expr->delegate_target = var; |
| expr->kind = TryKind::Delegate; |
| } |
| CHECK_RESULT(ErrorIfLpar({"a valid try clause"})); |
| expr->block.end_loc = GetLocation(); |
| if (expr->kind != TryKind::Delegate) { |
| EXPECT(End); |
| } |
| CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); |
| *out_expr = std::move(expr); |
| break; |
| } |
| |
| default: |
| assert( |
| !"ParseBlockInstr should only be called when IsBlockInstr() is true"); |
| return Result::Error; |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseLabelOpt(std::string* out_label) { |
| WABT_TRACE(ParseLabelOpt); |
| if (PeekMatch(TokenType::Var)) { |
| *out_label = Consume().text().to_string(); |
| } else { |
| out_label->clear(); |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseEndLabelOpt(const std::string& begin_label) { |
| WABT_TRACE(ParseEndLabelOpt); |
| Location loc = GetLocation(); |
| std::string end_label; |
| CHECK_RESULT(ParseLabelOpt(&end_label)); |
| if (!end_label.empty()) { |
| if (begin_label.empty()) { |
| Error(loc, "unexpected label \"%s\"", end_label.c_str()); |
| } else if (begin_label != end_label) { |
| Error(loc, "mismatching label \"%s\" != \"%s\"", begin_label.c_str(), |
| end_label.c_str()); |
| } |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseBlockDeclaration(BlockDeclaration* decl) { |
| WABT_TRACE(ParseBlockDeclaration); |
| FuncDeclaration func_decl; |
| CHECK_RESULT(ParseTypeUseOpt(&func_decl)); |
| CHECK_RESULT(ParseUnboundFuncSignature(&func_decl.sig)); |
| decl->has_func_type = func_decl.has_func_type; |
| decl->type_var = func_decl.type_var; |
| decl->sig = func_decl.sig; |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseBlock(Block* block) { |
| WABT_TRACE(ParseBlock); |
| CHECK_RESULT(ParseBlockDeclaration(&block->decl)); |
| CHECK_RESULT(ParseInstrList(&block->exprs)); |
| block->end_loc = GetLocation(); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExprList(ExprList* exprs) { |
| WABT_TRACE(ParseExprList); |
| ExprList new_exprs; |
| while (PeekMatchExpr()) { |
| if (Succeeded(ParseExpr(&new_exprs))) { |
| exprs->splice(exprs->end(), new_exprs); |
| } else { |
| CHECK_RESULT(Synchronize(IsExpr)); |
| } |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseExpr(ExprList* exprs) { |
| WABT_TRACE(ParseExpr); |
| if (!PeekMatch(TokenType::Lpar)) { |
| return Result::Error; |
| } |
| |
| if (IsPlainInstr(Peek(1))) { |
| Consume(); |
| std::unique_ptr<Expr> expr; |
| CHECK_RESULT(ParsePlainInstr(&expr)); |
| CHECK_RESULT(ParseExprList(exprs)); |
| CHECK_RESULT(ErrorIfLpar({"an expr"})); |
| exprs->push_back(std::move(expr)); |
| } else { |
| Location loc = GetLocation(); |
| |
| switch (Peek(1)) { |
| case TokenType::Block: { |
| Consume(); |
| Consume(); |
| auto expr = MakeUnique<BlockExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlock(&expr->block)); |
| exprs->push_back(std::move(expr)); |
| break; |
| } |
| |
| case TokenType::Loop: { |
| Consume(); |
| Consume(); |
| auto expr = MakeUnique<LoopExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlock(&expr->block)); |
| exprs->push_back(std::move(expr)); |
| break; |
| } |
| |
| case TokenType::If: { |
| Consume(); |
| Consume(); |
| auto expr = MakeUnique<IfExpr>(loc); |
| |
| CHECK_RESULT(ParseLabelOpt(&expr->true_.label)); |
| CHECK_RESULT(ParseBlockDeclaration(&expr->true_.decl)); |
| |
| if (PeekMatchExpr()) { |
| ExprList cond; |
| CHECK_RESULT(ParseExpr(&cond)); |
| exprs->splice(exprs->end(), cond); |
| } |
| |
| if (MatchLpar(TokenType::Then)) { |
| CHECK_RESULT(ParseTerminatingInstrList(&expr->true_.exprs)); |
| expr->true_.end_loc = GetLocation(); |
| EXPECT(Rpar); |
| |
| if (MatchLpar(TokenType::Else)) { |
| CHECK_RESULT(ParseTerminatingInstrList(&expr->false_)); |
| EXPECT(Rpar); |
| } else if (PeekMatchExpr()) { |
| CHECK_RESULT(ParseExpr(&expr->false_)); |
| } |
| expr->false_end_loc = GetLocation(); |
| } else if (PeekMatchExpr()) { |
| CHECK_RESULT(ParseExpr(&expr->true_.exprs)); |
| expr->true_.end_loc = GetLocation(); |
| if (PeekMatchExpr()) { |
| CHECK_RESULT(ParseExpr(&expr->false_)); |
| expr->false_end_loc = GetLocation(); |
| } |
| } else { |
| ConsumeIfLpar(); |
| return ErrorExpected({"then block"}, "(then ...)"); |
| } |
| |
| exprs->push_back(std::move(expr)); |
| break; |
| } |
| |
| case TokenType::Try: { |
| Consume(); |
| ErrorUnlessOpcodeEnabled(Consume()); |
| |
| auto expr = MakeUnique<TryExpr>(loc); |
| CHECK_RESULT(ParseLabelOpt(&expr->block.label)); |
| CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl)); |
| EXPECT(Lpar); |
| EXPECT(Do); |
| CHECK_RESULT(ParseInstrList(&expr->block.exprs)); |
| EXPECT(Rpar); |
| if (PeekMatch(TokenType::Lpar)) { |
| Consume(); |
| TokenType type = Peek(); |
| switch (type) { |
| case TokenType::Catch: |
| case TokenType::CatchAll: |
| CHECK_RESULT(ParseCatchExprList(&expr->catches)); |
| expr->kind = TryKind::Catch; |
| break; |
| case TokenType::Delegate: { |
| Consume(); |
| Var var; |
| CHECK_RESULT(ParseVar(&var)); |
| expr->delegate_target = var; |
| expr->kind = TryKind::Delegate; |
| EXPECT(Rpar); |
| break; |
| } |
| default: |
| ErrorExpected({"catch", "catch_all", "delegate"}); |
| break; |
| } |
| } |
| CHECK_RESULT(ErrorIfLpar({"a valid try clause"})); |
| expr->block.end_loc = GetLocation(); |
| exprs->push_back(std::move(expr)); |
| break; |
| } |
| |
| default: |
| assert(!"ParseExpr should only be called when IsExpr() is true"); |
| return Result::Error; |
| } |
| } |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseCatchInstrList(CatchVector* catches) { |
| WABT_TRACE(ParseCatchInstrList); |
| bool parsedCatch = false; |
| bool parsedCatchAll = false; |
| |
| while (IsCatch(Peek())) { |
| Catch catch_(GetLocation()); |
| |
| auto token = Consume(); |
| if (token.token_type() == TokenType::Catch) { |
| CHECK_RESULT(ParseVar(&catch_.var)); |
| } else { |
| if (parsedCatchAll) { |
| Error(token.loc, "multiple catch_all clauses not allowed"); |
| return Result::Error; |
| } |
| parsedCatchAll = true; |
| } |
| |
| CHECK_RESULT(ParseInstrList(&catch_.exprs)); |
| catches->push_back(std::move(catch_)); |
| parsedCatch = true; |
| } |
| |
| if (!parsedCatch) { |
| return ErrorExpected({"catch"}); |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseCatchExprList(CatchVector* catches) { |
| WABT_TRACE(ParseCatchExprList); |
| bool parsedCatchAll = false; |
| |
| do { |
| Catch catch_(GetLocation()); |
| |
| auto token = Consume(); |
| if (token.token_type() == TokenType::Catch) { |
| CHECK_RESULT(ParseVar(&catch_.var)); |
| } else { |
| if (parsedCatchAll) { |
| Error(token.loc, "multiple catch_all clauses not allowed"); |
| return Result::Error; |
| } |
| parsedCatchAll = true; |
| } |
| |
| CHECK_RESULT(ParseTerminatingInstrList(&catch_.exprs)); |
| EXPECT(Rpar); |
| catches->push_back(std::move(catch_)); |
| } while (Match(TokenType::Lpar) && IsCatch(Peek())); |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseGlobalType(Global* global) { |
| WABT_TRACE(ParseGlobalType); |
| if (MatchLpar(TokenType::Mut)) { |
| global->mutable_ = true; |
| Var type; |
| CHECK_RESULT(ParseValueType(&type)); |
| global->type = Type(type.index()); |
| CHECK_RESULT(ErrorIfLpar({"i32", "i64", "f32", "f64"})); |
| EXPECT(Rpar); |
| } else { |
| Var type; |
| CHECK_RESULT(ParseValueType(&type)); |
| global->type = Type(type.index()); |
| } |
| |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseCommandList(Script* script, |
| CommandPtrVector* commands) { |
| WABT_TRACE(ParseCommandList); |
| while (IsCommand(PeekPair())) { |
| CommandPtr command; |
| if (Succeeded(ParseCommand(script, &command))) { |
| commands->push_back(std::move(command)); |
| } else { |
| CHECK_RESULT(Synchronize(IsCommand)); |
| } |
| } |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseCommand(Script* script, CommandPtr* out_command) { |
| WABT_TRACE(ParseCommand); |
| switch (Peek(1)) { |
| case TokenType::AssertException: |
| return ParseAssertExceptionCommand(out_command); |
| |
| case TokenType::AssertExhaustion: |
| return ParseAssertExhaustionCommand(out_command); |
| |
| case TokenType::AssertInvalid: |
| return ParseAssertInvalidCommand(out_command); |
| |
| case TokenType::AssertMalformed: |
| return ParseAssertMalformedCommand(out_command); |
| |
| case TokenType::AssertReturn: |
| return ParseAssertReturnCommand(out_command); |
| |
| case TokenType::AssertTrap: |
| return ParseAssertTrapCommand(out_command); |
| |
| case TokenType::AssertUnlinkable: |
| return ParseAssertUnlinkableCommand(out_command); |
| |
| case TokenType::Get: |
| case TokenType::Invoke: |
| return ParseActionCommand(out_command); |
| |
| case TokenType::Module: |
| return ParseModuleCommand(script, out_command); |
| |
| case TokenType::Register: |
| return ParseRegisterCommand(out_command); |
| |
| case TokenType::Input: |
| return ParseInputCommand(out_command); |
| |
| case TokenType::Output: |
| return ParseOutputCommand(out_command); |
| |
| default: |
| assert(!"ParseCommand should only be called when IsCommand() is true"); |
| return Result::Error; |
| } |
| } |
| |
| Result WastParser::ParseAssertExceptionCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertExceptionCommand); |
| return ParseAssertActionCommand<AssertExceptionCommand>( |
| TokenType::AssertException, out_command); |
| } |
| |
| Result WastParser::ParseAssertExhaustionCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertExhaustionCommand); |
| return ParseAssertActionTextCommand<AssertExhaustionCommand>( |
| TokenType::AssertExhaustion, out_command); |
| } |
| |
| Result WastParser::ParseAssertInvalidCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertInvalidCommand); |
| return ParseAssertScriptModuleCommand<AssertInvalidCommand>( |
| TokenType::AssertInvalid, out_command); |
| } |
| |
| Result WastParser::ParseAssertMalformedCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertMalformedCommand); |
| return ParseAssertScriptModuleCommand<AssertMalformedCommand>( |
| TokenType::AssertMalformed, out_command); |
| } |
| |
| Result WastParser::ParseAssertReturnCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertReturnCommand); |
| EXPECT(Lpar); |
| EXPECT(AssertReturn); |
| auto command = MakeUnique<AssertReturnCommand>(); |
| CHECK_RESULT(ParseAction(&command->action)); |
| CHECK_RESULT(ParseConstList(&command->expected, ConstType::Expectation)); |
| EXPECT(Rpar); |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseAssertTrapCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertTrapCommand); |
| EXPECT(Lpar); |
| EXPECT(AssertTrap); |
| if (PeekMatchLpar(TokenType::Module)) { |
| auto command = MakeUnique<AssertUninstantiableCommand>(); |
| CHECK_RESULT(ParseScriptModule(&command->module)); |
| CHECK_RESULT(ParseQuotedText(&command->text)); |
| *out_command = std::move(command); |
| } else { |
| auto command = MakeUnique<AssertTrapCommand>(); |
| CHECK_RESULT(ParseAction(&command->action)); |
| CHECK_RESULT(ParseQuotedText(&command->text)); |
| *out_command = std::move(command); |
| } |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseAssertUnlinkableCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertUnlinkableCommand); |
| return ParseAssertScriptModuleCommand<AssertUnlinkableCommand>( |
| TokenType::AssertUnlinkable, out_command); |
| } |
| |
| Result WastParser::ParseActionCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseActionCommand); |
| auto command = MakeUnique<ActionCommand>(); |
| CHECK_RESULT(ParseAction(&command->action)); |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseModuleCommand(Script* script, CommandPtr* out_command) { |
| WABT_TRACE(ParseModuleCommand); |
| std::unique_ptr<ScriptModule> script_module; |
| CHECK_RESULT(ParseScriptModule(&script_module)); |
| |
| auto command = MakeUnique<ModuleCommand>(); |
| Module& module = command->module; |
| |
| switch (script_module->type()) { |
| case ScriptModuleType::Text: |
| module = std::move(cast<TextScriptModule>(script_module.get())->module); |
| break; |
| |
| case ScriptModuleType::Binary: { |
| auto* bsm = cast<BinaryScriptModule>(script_module.get()); |
| ReadBinaryOptions options; |
| #if WABT_TRACING |
| auto log_stream = FileStream::CreateStdout(); |
| options.log_stream = log_stream.get(); |
| #endif |
| options.features = options_->features; |
| Errors errors; |
| const char* filename = "<text>"; |
| ReadBinaryIr(filename, bsm->data.data(), bsm->data.size(), options, |
| &errors, &module); |
| module.name = bsm->name; |
| module.loc = bsm->loc; |
| for (const auto& error: errors) { |
| assert(error.error_level == ErrorLevel::Error); |
| if (error.loc.offset == kInvalidOffset) { |
| Error(bsm->loc, "error in binary module: %s", error.message.c_str()); |
| } else { |
| Error(bsm->loc, "error in binary module: @0x%08" PRIzx ": %s", |
| error.loc.offset, error.message.c_str()); |
| } |
| } |
| break; |
| } |
| |
| case ScriptModuleType::Quoted: |
| return ErrorExpected({"a binary module", "a text module"}); |
| } |
| |
| // script is nullptr when ParseModuleCommand is called from ParseModule. |
| if (script) { |
| Index command_index = script->commands.size(); |
| |
| if (!module.name.empty()) { |
| script->module_bindings.emplace(module.name, |
| Binding(module.loc, command_index)); |
| } |
| |
| last_module_index_ = command_index; |
| } |
| |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseRegisterCommand(CommandPtr* out_command) { |
| WABT_TRACE(ParseRegisterCommand); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Register); |
| std::string text; |
| Var var; |
| CHECK_RESULT(ParseQuotedText(&text)); |
| ParseVarOpt(&var, Var(last_module_index_, loc)); |
| EXPECT(Rpar); |
| out_command->reset(new RegisterCommand(text, var)); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseInputCommand(CommandPtr*) { |
| // Parse the input command, but always fail since this command is not |
| // actually supported. |
| WABT_TRACE(ParseInputCommand); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Input); |
| Error(loc, "input command is not supported"); |
| Var var; |
| std::string text; |
| ParseVarOpt(&var); |
| CHECK_RESULT(ParseQuotedText(&text)); |
| EXPECT(Rpar); |
| return Result::Error; |
| } |
| |
| Result WastParser::ParseOutputCommand(CommandPtr*) { |
| // Parse the output command, but always fail since this command is not |
| // actually supported. |
| WABT_TRACE(ParseOutputCommand); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Output); |
| Error(loc, "output command is not supported"); |
| Var var; |
| std::string text; |
| ParseVarOpt(&var); |
| if (Peek() == TokenType::Text) { |
| CHECK_RESULT(ParseQuotedText(&text)); |
| } |
| EXPECT(Rpar); |
| return Result::Error; |
| } |
| |
| Result WastParser::ParseAction(ActionPtr* out_action) { |
| WABT_TRACE(ParseAction); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| |
| switch (Peek()) { |
| case TokenType::Invoke: { |
| Consume(); |
| auto action = MakeUnique<InvokeAction>(loc); |
| ParseVarOpt(&action->module_var, Var(last_module_index_, loc)); |
| CHECK_RESULT(ParseQuotedText(&action->name)); |
| CHECK_RESULT(ParseConstList(&action->args, ConstType::Normal)); |
| *out_action = std::move(action); |
| break; |
| } |
| |
| case TokenType::Get: { |
| Consume(); |
| auto action = MakeUnique<GetAction>(loc); |
| ParseVarOpt(&action->module_var, Var(last_module_index_, loc)); |
| CHECK_RESULT(ParseQuotedText(&action->name)); |
| *out_action = std::move(action); |
| break; |
| } |
| |
| default: |
| return ErrorExpected({"invoke", "get"}); |
| } |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| Result WastParser::ParseScriptModule( |
| std::unique_ptr<ScriptModule>* out_module) { |
| WABT_TRACE(ParseScriptModule); |
| EXPECT(Lpar); |
| Location loc = GetLocation(); |
| EXPECT(Module); |
| std::string name; |
| ParseBindVarOpt(&name); |
| |
| switch (Peek()) { |
| case TokenType::Bin: { |
| Consume(); |
| std::vector<uint8_t> data; |
| // TODO(binji): The spec allows this to be empty, switch to |
| // ParseTextListOpt. |
| CHECK_RESULT(ParseTextList(&data)); |
| |
| auto bsm = MakeUnique<BinaryScriptModule>(); |
| bsm->name = name; |
| bsm->loc = loc; |
| bsm->data = std::move(data); |
| *out_module = std::move(bsm); |
| break; |
| } |
| |
| case TokenType::Quote: { |
| Consume(); |
| std::vector<uint8_t> data; |
| // TODO(binji): The spec allows this to be empty, switch to |
| // ParseTextListOpt. |
| CHECK_RESULT(ParseTextList(&data)); |
| |
| auto qsm = MakeUnique<QuotedScriptModule>(); |
| qsm->name = name; |
| qsm->loc = loc; |
| qsm->data = std::move(data); |
| *out_module = std::move(qsm); |
| break; |
| } |
| |
| default: { |
| auto tsm = MakeUnique<TextScriptModule>(); |
| tsm->module.name = name; |
| tsm->module.loc = loc; |
| if (IsModuleField(PeekPair())) { |
| CHECK_RESULT(ParseModuleFieldList(&tsm->module)); |
| } else if (!PeekMatch(TokenType::Rpar)) { |
| ConsumeIfLpar(); |
| return ErrorExpected({"a module field"}); |
| } |
| *out_module = std::move(tsm); |
| break; |
| } |
| } |
| |
| EXPECT(Rpar); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseAssertActionCommand(TokenType token_type, |
| CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertActionCommand); |
| EXPECT(Lpar); |
| CHECK_RESULT(Expect(token_type)); |
| auto command = MakeUnique<T>(); |
| CHECK_RESULT(ParseAction(&command->action)); |
| EXPECT(Rpar); |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseAssertActionTextCommand(TokenType token_type, |
| CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertActionTextCommand); |
| EXPECT(Lpar); |
| CHECK_RESULT(Expect(token_type)); |
| auto command = MakeUnique<T>(); |
| CHECK_RESULT(ParseAction(&command->action)); |
| CHECK_RESULT(ParseQuotedText(&command->text)); |
| EXPECT(Rpar); |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| template <typename T> |
| Result WastParser::ParseAssertScriptModuleCommand(TokenType token_type, |
| CommandPtr* out_command) { |
| WABT_TRACE(ParseAssertScriptModuleCommand); |
| EXPECT(Lpar); |
| CHECK_RESULT(Expect(token_type)); |
| auto command = MakeUnique<T>(); |
| CHECK_RESULT(ParseScriptModule(&command->module)); |
| CHECK_RESULT(ParseQuotedText(&command->text)); |
| EXPECT(Rpar); |
| *out_command = std::move(command); |
| return Result::Ok; |
| } |
| |
| void WastParser::CheckImportOrdering(Module* module) { |
| if (module->funcs.size() != module->num_func_imports || |
| module->tables.size() != module->num_table_imports || |
| module->memories.size() != module->num_memory_imports || |
| module->globals.size() != module->num_global_imports || |
| module->tags.size() != module->num_tag_imports) { |
| Error(GetLocation(), |
| "imports must occur before all non-import definitions"); |
| } |
| } |
| |
| Result ParseWatModule(WastLexer* lexer, |
| std::unique_ptr<Module>* out_module, |
| Errors* errors, |
| WastParseOptions* options) { |
| assert(out_module != nullptr); |
| assert(options != nullptr); |
| WastParser parser(lexer, errors, options); |
| CHECK_RESULT(parser.ParseModule(out_module)); |
| return Result::Ok; |
| } |
| |
| Result ParseWastScript(WastLexer* lexer, |
| std::unique_ptr<Script>* out_script, |
| Errors* errors, |
| WastParseOptions* options) { |
| assert(out_script != nullptr); |
| assert(options != nullptr); |
| WastParser parser(lexer, errors, options); |
| CHECK_RESULT(parser.ParseScript(out_script)); |
| CHECK_RESULT(ResolveNamesScript(out_script->get(), errors)); |
| return Result::Ok; |
| } |
| |
| } // namespace wabt |