diff options
author | Thomas Lively <tlively@google.com> | 2023-11-30 02:52:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-29 17:52:01 -0800 |
commit | 71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95 (patch) | |
tree | 70a560571537f51d78b75f7804259b8881d368bc /src/parser/parsers.h | |
parent | 24742589f5471a5e72755d8fe1da9e49923a35ff (diff) | |
download | binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.tar.gz binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.tar.bz2 binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.zip |
[Parser] Parse try/catch/catch_all/delegate (#6128)
Parse the legacy v3 syntax for try/catch/catch_all/delegate in both its folded
and unfolded forms.
The first sources of significant complexity is the optional IDs after `catch`
and `catch_all` in the unfolded form, which can be confused for tag indices and
require backtracking to parse correctly.
The second source of complexity is the handling of delegate labels, which are
relative to the try's parent scope despite being parsed after the try's scope
has already started. Handling this correctly requires punching a whole big
enough to drive a truck through through both the parser and IRBuilder
abstractions.
Diffstat (limited to 'src/parser/parsers.h')
-rw-r--r-- | src/parser/parsers.h | 192 |
1 files changed, 174 insertions, 18 deletions
diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 9201314dc..f16cca2b5 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -56,6 +56,7 @@ template<typename Ctx> Result<typename Ctx::BlockTypeT> blocktype(Ctx&); template<typename Ctx> MaybeResult<> block(Ctx&, bool); template<typename Ctx> MaybeResult<> ifelse(Ctx&, bool); template<typename Ctx> MaybeResult<> loop(Ctx&, bool); +template<typename Ctx> MaybeResult<> trycatch(Ctx&, bool); template<typename Ctx> Result<> makeUnreachable(Ctx&, Index); template<typename Ctx> Result<> makeNop(Ctx&, Index); template<typename Ctx> Result<> makeBinary(Ctx&, Index, BinaryOp op); @@ -113,9 +114,6 @@ template<typename Ctx> Result<> makeTableSize(Ctx&, Index); template<typename Ctx> Result<> makeTableGrow(Ctx&, Index); template<typename Ctx> Result<> makeTableFill(Ctx&, Index); template<typename Ctx> Result<> makeTableCopy(Ctx&, Index); -template<typename Ctx> Result<> makeTry(Ctx&, Index); -template<typename Ctx> -Result<> makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry); template<typename Ctx> Result<> makeThrow(Ctx&, Index); template<typename Ctx> Result<> makeRethrow(Ctx&, Index); template<typename Ctx> Result<> makeTupleMake(Ctx&, Index); @@ -173,7 +171,8 @@ template<typename Ctx> Result<typename Ctx::MemoryIdxT> memidx(Ctx&); template<typename Ctx> MaybeResult<typename Ctx::MemoryIdxT> maybeMemuse(Ctx&); template<typename Ctx> Result<typename Ctx::GlobalIdxT> globalidx(Ctx&); template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx&); -template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx&); +template<typename Ctx> +Result<typename Ctx::LabelIdxT> labelidx(Ctx&, bool inDelegate = false); template<typename Ctx> Result<typename Ctx::TagIdxT> tagidx(Ctx&); template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx&); MaybeResult<ImportNames> inlineImport(ParseInput&); @@ -573,6 +572,9 @@ template<typename Ctx> MaybeResult<> foldedBlockinstr(Ctx& ctx) { if (auto i = loop(ctx, true)) { return i; } + if (auto i = trycatch(ctx, true)) { + return i; + } // TODO: Other block instructions return {}; } @@ -587,6 +589,9 @@ template<typename Ctx> MaybeResult<> unfoldedBlockinstr(Ctx& ctx) { if (auto i = loop(ctx, false)) { return i; } + if (auto i = trycatch(ctx, false)) { + return i; + } // TODO: Other block instructions return {}; } @@ -619,7 +624,9 @@ template<typename Ctx> MaybeResult<> instr(Ctx& ctx) { // Check for valid strings that are not instructions. if (auto tok = ctx.in.peek()) { if (auto keyword = tok->getKeyword()) { - if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv) { + if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv || + keyword == "catch"sv || keyword == "catch_all"sv || + keyword == "delegate"sv) { return {}; } } @@ -860,7 +867,7 @@ template<typename Ctx> MaybeResult<> ifelse(Ctx& ctx, bool folded) { return ctx.visitEnd(); } -// loop ::= 'loop' label blocktype instr* end id? +// loop ::= 'loop' label blocktype instr* 'end' id? // | '(' 'loop' label blocktype instr* ')' template<typename Ctx> MaybeResult<> loop(Ctx& ctx, bool folded) { auto pos = ctx.in.getPos(); @@ -895,6 +902,163 @@ template<typename Ctx> MaybeResult<> loop(Ctx& ctx, bool folded) { return ctx.visitEnd(); } +// trycatch ::= 'try' label blocktype instr* ('catch' id? tagidx instr*)* +// ('catch_all' id? instr*)? 'end' id? +// | '(' 'try' label blocktype '(' 'do' instr* ')' +// ('(' 'catch' tagidx instr* ')')* +// ('(' 'catch_all' instr* ')')? ')' +// | 'try' label blocktype instr* 'deledate' label +// | '(' 'try' label blocktype '(' 'do' instr* ')' +// '(' 'delegate' label ')' ')' +template<typename Ctx> MaybeResult<> trycatch(Ctx& ctx, bool folded) { + auto pos = ctx.in.getPos(); + + if ((folded && !ctx.in.takeSExprStart("try"sv)) || + (!folded && !ctx.in.takeKeyword("try"sv))) { + return {}; + } + + auto label = ctx.in.takeID(); + + auto type = blocktype(ctx); + CHECK_ERR(type); + + CHECK_ERR(ctx.makeTry(pos, label, *type)); + + if (folded) { + if (!ctx.in.takeSExprStart("do"sv)) { + return ctx.in.err("expected 'do' in try"); + } + } + + CHECK_ERR(instrs(ctx)); + + if (folded) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of do"); + } + } + + if ((folded && ctx.in.takeSExprStart("delegate")) || + (!folded && ctx.in.takeKeyword("delegate"))) { + auto delegatePos = ctx.in.getPos(); + + auto label = labelidx(ctx, true); + CHECK_ERR(label); + + if (folded) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of delegate"); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of try"); + } + } + + CHECK_ERR(ctx.visitDelegate(delegatePos, *label)); + return Ok{}; + } + + while (true) { + auto catchPos = ctx.in.getPos(); + + if ((folded && !ctx.in.takeSExprStart("catch"sv)) || + (!folded && !ctx.in.takeKeyword("catch"sv))) { + break; + } + + // It can be ambiguous whether the name after `catch` is intended to be the + // optional ID or the tag identifier. For example: + // + // (tag $t) + // (func $ambiguous + // try $t + // catch $t + // end + // ) + // + // When parsing the `catch`, the parser first tries to parse an optional ID + // that must match the label of the `try`, and it succeeds because it sees + // `$t` after the catch. However, when it then tries to parse the mandatory + // tag index, it fails because the next token is `end`. The problem is that + // the `$t` after the `catch` was the tag name and there was no optional ID + // after all. The parser sets `parseID = false` and resets to just after the + // `catch`, and now it skips parsing the optional ID so it correctly parses + // the `$t` as a tag name. + bool parseID = !folded; + auto afterCatchPos = ctx.in.getPos(); + while (true) { + if (!folded && parseID) { + auto id = ctx.in.takeID(); + if (id && id != label) { + // Instead of returning an error, retry without the ID. + parseID = false; + ctx.in.lexer.setIndex(afterCatchPos); + continue; + } + } + + auto tag = tagidx(ctx); + if (parseID && tag.getErr()) { + // Instead of returning an error, retry without the ID. + parseID = false; + ctx.in.lexer.setIndex(afterCatchPos); + continue; + } + CHECK_ERR(tag); + + CHECK_ERR(ctx.visitCatch(catchPos, *tag)); + + CHECK_ERR(instrs(ctx)); + + if (folded) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of catch"); + } + } + break; + } + } + + if ((folded && ctx.in.takeSExprStart("catch_all"sv)) || + (!folded && ctx.in.takeKeyword("catch_all"sv))) { + auto catchPos = ctx.in.getPos(); + + if (!folded) { + auto id = ctx.in.takeID(); + if (id && id != label) { + return ctx.in.err("catch_all label does not match try label"); + } + } + + CHECK_ERR(ctx.visitCatchAll(catchPos)); + + CHECK_ERR(instrs(ctx)); + + if (folded) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of catch_all"); + } + } + } + + if (folded) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of try"); + } + } else { + if (!ctx.in.takeKeyword("end"sv)) { + return ctx.in.err("expected 'end' at end of try"); + } + + auto id = ctx.in.takeID(); + if (id && id != label) { + return ctx.in.err("end label does not match try label"); + } + } + return ctx.visitEnd(); +} + template<typename Ctx> Result<> makeUnreachable(Ctx& ctx, Index pos) { return ctx.makeUnreachable(pos); } @@ -1266,15 +1430,6 @@ template<typename Ctx> Result<> makeTableCopy(Ctx& ctx, Index pos) { return ctx.in.err("unimplemented instruction"); } -template<typename Ctx> Result<> makeTry(Ctx& ctx, Index pos) { - return ctx.in.err("unimplemented instruction"); -} - -template<typename Ctx> -Result<> makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) { - return ctx.in.err("unimplemented instruction"); -} - template<typename Ctx> Result<> makeThrow(Ctx& ctx, Index pos) { auto tag = tagidx(ctx); CHECK_ERR(tag); @@ -1621,12 +1776,13 @@ template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx& ctx) { // labelidx ::= x:u32 => x // | v:id => x (if labels[x] = v) -template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx& ctx) { +template<typename Ctx> +Result<typename Ctx::LabelIdxT> labelidx(Ctx& ctx, bool inDelegate) { if (auto x = ctx.in.takeU32()) { - return ctx.getLabelFromIdx(*x); + return ctx.getLabelFromIdx(*x, inDelegate); } if (auto id = ctx.in.takeID()) { - return ctx.getLabelFromName(*id); + return ctx.getLabelFromName(*id, inDelegate); } return ctx.in.err("expected label index or identifier"); } |