diff --git a/Tag.cpp b/Tag.cpp index 2d5059b..46167ec 100644 --- a/Tag.cpp +++ b/Tag.cpp @@ -318,11 +318,28 @@ namespace jet { return false; } + bool Tag::findTagDefinition(coreutils::MString name, coreutils::ZString &result) { + Tag *scope = this; + while(scope) { + if(scope->isCustomTagBoundary && scope->localTags.count(name)) { + result = scope->localTags[name]; + return true; + } + scope = scope->parent; + } + if(global.tags.count(name)) { + result = global.tags[name]; + return true; + } + return false; + } + bool Tag::ifTagDefined(coreutils::ZString &in, coreutils::ZString &tag) { in.push(); if(in.ifNext("<")) { tag = in.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); - if(global.tags.count(tag)) { + coreutils::ZString definition; + if(findTagDefinition(tag, definition)) { if(in.ifNext(" ")) { in.pop(); return true; @@ -332,10 +349,10 @@ namespace jet { } else if(in.ifNext(">")) { in.pop(); return true; - } + } in.pop(); } - } + } in.pop(); return false; } diff --git a/Tag.h b/Tag.h index afaed7f..1a34b41 100644 --- a/Tag.h +++ b/Tag.h @@ -46,6 +46,32 @@ namespace jet { // the 22 tag classes' logic. void interpretSpan(coreutils::ZString span, coreutils::MString &out); + // Custom scoping: a Tag defined directly inside + // another custom tag's own invocation (i.e. inside a __dotag + // instance's expansion, not just nested inside //etc. that + // happen to sit there) registers into that invocation's own + // localTags instead of the single flat global.tags map, so it's + // only visible for the rest of that invocation - not the whole + // script. isCustomTagBoundary is true only on __dotag instances + // (set in __dotag's own constructor body); every other tag leaves + // it false and is transparently walked past when searching for an + // enclosing scope, exactly like // already are for + // parent-chain purposes elsewhere. Public (like variables/keywords/ + // name/etc. above) rather than protected - __tag.cpp needs to read + // and write these through an arbitrary ancestor Tag* found by + // walking parent, not just on its own `this`, and protected access + // in C++ only extends to a derived class's own (sub)object, not to + // another object reached through a base-class-typed pointer. + bool isCustomTagBoundary = false; + std::map localTags; + + // Walks from `this` up through `parent` looking for the nearest + // isCustomTagBoundary ancestor whose localTags has `name`, falling + // back to global.tags - used by both ifTagDefined() (is + // visible right here?) and __dotag's own constructor (fetch the + // actual definition to expand). + bool findTagDefinition(coreutils::MString name, coreutils::ZString &result); + protected: bool hasContainer = false; bool hasContainer2 = false; diff --git a/__dotag.cpp b/__dotag.cpp index 1bd3b9d..e81423c 100644 --- a/__dotag.cpp +++ b/__dotag.cpp @@ -2,9 +2,35 @@ #include "Exception.h" namespace jet { - + __dotag::__dotag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, local) { - coreutils::ZString container3 = global.tags[name]; + // Every invocation of a custom tag is itself a new scope boundary - + // a encountered while expanding this invocation's + // own template (below) registers into THIS instance's localTags, + // not the global map, so it's only visible for the rest of this + // expansion. See Tag.h's isCustomTagBoundary/localTags doc comment. + isCustomTagBoundary = true; + + // The base Tag constructor already set hasContainer from whether + // THIS INVOCATION was self-closing ( vs ...) - + // that's the right question for capturing this invocation's own + // nested content into `container` above, but the wrong one for + // whether the tag's DEFINITION (container3 below) should render: + // a self-closing invocation of a tag whose definition doesn't use + // at all is completely normal and should still + // render. Found this as a real, pre-existing bug while testing + // this scoping feature - always silently rendered nothing + // for ANY custom tag, only worked, since + // processContainer() gates on hasContainer. Force it true here: + // by this point a valid definition is guaranteed to exist (dispatch + // only constructs __dotag after ifTagDefined() already confirmed + // it), so there's always something to render regardless of how + // this invocation was written. + hasContainer = true; + + coreutils::ZString container3; + if(!findTagDefinition(name, container3)) + throw coreutils::Exception("tag definition not found."); processContainer(container3, container); } } diff --git a/__dump.cpp b/__dump.cpp index 7ef143f..477d939 100644 --- a/__dump.cpp +++ b/__dump.cpp @@ -2,6 +2,7 @@ #include "Exception.h" #include #include +#include namespace jet { @@ -33,14 +34,36 @@ namespace jet { for (auto i = global.cookies.data.begin(); i != global.cookies.data.end(); i++) outFile << i->first << "=[" << i->second << "]" << std::endl; - // Unlike variables (which have real global/local/parent scoping), - // custom definitions have only one level - __tag.cpp - // always registers into global.tags regardless of where in the - // script the definition sits, so there's nothing to break out by - // scope here. + // Custom definitions now have real scoping - one + // defined directly inside another custom tag's own invocation is + // only visible for the rest of that invocation (see Tag.h's + // isCustomTagBoundary/localTags doc comment). Render the actual + // tree currently in effect at this point in the script: global at + // the root, then each enclosing custom-tag invocation nested one + // level deeper, outermost first - if there's no active local scope + // (this isn't running inside any custom tag's expansion), + // that's just the root with nothing nested under it. outFile << "*** DEFINED TAGS ***" << std::endl; + outFile << "(global)" << std::endl; for (auto i = global.tags.begin(); i != global.tags.end(); i++) - outFile << i->first << std::endl; + outFile << " " << i->first << std::endl; + + std::vector scopeChain; + for (Tag *scope = this; scope; scope = scope->parent) + if (scope->isCustomTagBoundary) + scopeChain.push_back(scope); + + int depth = 1; + for (auto it = scopeChain.rbegin(); it != scopeChain.rend(); ++it) { + Tag *boundary = *it; + coreutils::MString indent; + for (int d = 0; d < depth; d++) + indent << " "; + outFile << indent << "(inside " << boundary->name << ")" << std::endl; + for (auto i = boundary->localTags.begin(); i != boundary->localTags.end(); i++) + outFile << indent << " " << i->first << std::endl; + depth++; + } outFile.close(); diff --git a/__tag.cpp b/__tag.cpp index 5930660..620bbb2 100644 --- a/__tag.cpp +++ b/__tag.cpp @@ -3,14 +3,36 @@ namespace jet { - __tag::__tag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, this, this) { + __tag::__tag(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, local) { evaluate = false; output = false; if(!keywordDefined("name")) throw coreutils::Exception("tag must have a name."); if(!hasContainer) throw coreutils::Exception("tag requires a container to process."); - global.tags[keywords["name"]] = container; + + // A defined directly inside another custom tag's own + // invocation (a __dotag instance's expansion) is scoped to that + // invocation - only visible for the rest of it, not the whole + // script. Control-flow tags like / that happen to sit in + // between are transparent (walked past, not boundaries themselves). + // Reaching parent==NULL (true top level) falls back to the original + // single flat global.tags map, unchanged from before. This relies + // on `parent` here actually being the real enclosing tag - the base + // Tag(...) call above used to pass (this, this) instead of the + // parent/local this constructor actually received, which would have + // made this walk loop on itself forever; fixed as part of adding + // this scoping (parent/local weren't needed anywhere else in this + // class - 's own container is never evaluated at definition + // time, evaluate=false above, so nothing depended on the old + // self-referential values). + Tag *scope = parent; + while(scope && !scope->isCustomTagBoundary) + scope = scope->parent; + if(scope) + scope->localTags[keywords["name"]] = container; + else + global.tags[keywords["name"]] = container; } }