summaryrefslogtreecommitdiff
path: root/src/parser/parsers.h
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2023-11-30 02:52:01 +0100
committerGitHub <noreply@github.com>2023-11-29 17:52:01 -0800
commit71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95 (patch)
tree70a560571537f51d78b75f7804259b8881d368bc /src/parser/parsers.h
parent24742589f5471a5e72755d8fe1da9e49923a35ff (diff)
downloadbinaryen-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.h192
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");
}