diff --git a/Tag.cpp b/Tag.cpp index 45d0be0..2d5059b 100644 --- a/Tag.cpp +++ b/Tag.cpp @@ -25,6 +25,7 @@ #include "__dotag.h" #include "__stream.h" #include "__dump.h" +#include "__trytag.h" #include "Operand.h" #include @@ -90,8 +91,22 @@ namespace jet { } else if(hasSplitTag && ifSplitTagName(in)) { hasContainer2 = true; container = coreutils::ZString(start, end - start); - in.ifNext(""); - start = in.getCursor(); + // ifSplitTagName() is a pure lookahead (push()/pop()'s the + // cursor back regardless of match - see its own body), so + // the actual split-tag opening delimiter still has to be + // consumed here. This used to be hardcoded to the literal + // "" - fine for // (which all use + // splitTagName "else"), but wrong for any other split tag + // name (e.g. 's "catch") - a hardcoded literal that + // never matches means this ifNext() silently fails, the + // cursor never advances past , and the outer loop + // matches the same unconsumed on every subsequent + // iteration forever. Build the real expected delimiter + // from splitTagName itself so it works for any split tag. + coreutils::MString splitOpenTag; + splitOpenTag << "<" << splitTagName << ">"; + in.ifNext(splitOpenTag); + start = in.getCursor(); } else if(ifNested(in)) { } else in.nextChar(); @@ -211,6 +226,9 @@ namespace jet { } else if(ifTagName(in, "until")) { __until _until(in, out, global, this, local); continue; + } else if(ifTagName(in, "try")) { + __trytag _trytag(in, out, global, this, local); + continue; } else if(ifTagName(in, "header")) { __header _header(in, out, global, this, local); continue; diff --git a/__trytag.cpp b/__trytag.cpp new file mode 100644 index 0000000..12ef1a5 --- /dev/null +++ b/__trytag.cpp @@ -0,0 +1,41 @@ +#include "__trytag.h" +#include "Exception.h" +#include + +namespace jet { + + __trytag::__trytag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) + : Tag(in, parentOut, global, parent, this, "catch") { + + coreutils::MString errorText; + bool failed = false; + + try { + processContainer(container); + } catch(coreutils::Exception &e) { + errorText = e.text; + failed = true; + } catch(std::exception &e) { + errorText = e.what(); + failed = true; + } catch(...) { + errorText = "unknown error"; + failed = true; + } + + if(failed) { + // Discard whatever the failed attempt had already written before + // it threw - out is this tag's own accumulating output buffer + // (processContainer(container) above wrote into it directly), so + // without this a caught mid-render failure would leave partial + // try-block output mixed in ahead of the catch block's own. + out = ""; + if(keywordDefined("name")) + storeVariable(resolveKeyword("name"), errorText, + keywordDefined("scope") ? resolveKeyword("scope") : "global"); + if(hasContainer2) + processContainer(container2); + } + } + +} diff --git a/__trytag.h b/__trytag.h new file mode 100644 index 0000000..f06ab3d --- /dev/null +++ b/__trytag.h @@ -0,0 +1,46 @@ +#ifndef ____trytag_h__ +#define ____trytag_h__ + +#include "Tag.h" +#include "ZString.h" +#include "MString.h" + +namespace jet { + + /// + /// ...container......container2... + /// + /// Runs its own container the same as any other tag; if that raises an + /// error that would otherwise propagate up and halt the whole script - + /// a coreutils::Exception (what every other tag actually throws), any + /// other std::exception (e.g. the std::filesystem::filesystem_error + /// can raise), or anything else - the container (if + /// present) runs instead, with any partial output the failed attempt + /// had already written discarded first so the result is cleanly either + /// the try's output or the catch's, never a mix of both. + /// + /// Optional keywords, both meaningful only on catch: name (a variable + /// to receive the caught error's message text) and scope (global by + /// default, or local/parent - same convention as 's own name/scope + /// pair). + /// + /// Named __trytag rather than __try: transitively defines a + /// preprocessor macro "#define __try try" (an SEH-compatibility shim + /// pulled in on this toolchain) that silently rewrites the identifier + /// __try to the literal keyword try wherever it appears, breaking + /// compilation - confirmed via `g++ -E -dM` before picking this name, + /// not guessed. The JET tag name itself is still plain - this + /// only affects the internal C++ class name, matched by ifTagName(in, + /// "try") in Tag.cpp regardless of what the class is called. + /// + + class __trytag : public Tag { + + public: + __trytag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local); + + }; + +} + +#endif