// +-------------------------------------------------------------------------+ // | Script Plus Plus vers. 0.3.72 | // | Copyright (c) Andrey V. Stolyarov 2003--2023 | // | ----------------------------------------------------------------------- | // | This is free software. Permission is granted to everyone to use, copy | // | or modify this software under the terms and conditions of | // | GNU LESSER GENERAL PUBLIC LICENSE, v. 2.1 | // | as published by Free Software Foundation (see the file LGPL.txt) | // | | // | Please visit http://www.croco.net/software/scriptpp to get a fresh copy | // | ----------------------------------------------------------------------- | // | This code is provided strictly and exclusively on the "AS IS" basis. | // | !!! THERE IS NO WARRANTY OF ANY KIND, NEITHER EXPRESSED NOR IMPLIED !!! | // +-------------------------------------------------------------------------+ #include "scrvect.hpp" #include "scrmacro.hpp" /* TODO: The method named FetchArgs actually scans AND EVALUATES the arguments (of an eager macro), so using it from within PerformSimple method (which evaluates things like %f:x:y%, where there can't be anything nested) seems to be overkill. Perhaps FetchArgs should be renamed into smth. like ProcessEagerArguments; for PerformSimple, there should be smth. simpler than current FetchArgs. After that, there will only be ONE call to FetchArgs (or whatever we rename it to), so arguments for it can be reduced, and maybe its body can be simplified. Actually, that's no strange to encounter this crap. Reusing methods in a recursive descent, well, /doubtful/. */ struct macro_item { const ScriptMacroprocessorMacro *macro; macro_item *next; } *first; /* These two structures are operated by ScriptMacroprocessor which is responsible for them; they don't own anything, including the list of macros */ struct ScriptMacroRealm { char esc_char, left_br, right_br, left_lazy, right_lazy; macro_item *first; const ScriptMacroRealm *parent; const ScriptVector *positionals; int pos_idx, pos_count; explicit ScriptMacroRealm(char ec) : esc_char(ec), left_br('['), right_br(']'), left_lazy('{'), right_lazy('}'), first(0), parent(0), positionals(0) {} explicit ScriptMacroRealm(const char *chrs) : esc_char(chrs[0]), left_br(chrs[1]), right_br(chrs[2]), left_lazy(chrs[3]), right_lazy(chrs[4]), first(0), parent(0), positionals(0) {} explicit ScriptMacroRealm(const ScriptMacroRealm *par) // this isn't copy! : esc_char(par->esc_char), left_br(par->left_br), right_br(par->right_br), left_lazy(par->left_lazy), right_lazy(par->right_lazy), first(0), parent(par), positionals(0) {} /* There's no explicit copy contstructor! the structure is copied byte to byte and this is exaclty what we want. There's also no destructor here. The master is responsible for the destruction. */ }; struct ScriptMacroContext : public ScriptMacroRealm { int recursion_limit; const char *rest; ScriptVariable result; ScriptMacroContext(const ScriptMacroRealm &realm, const char *s, int lim) : ScriptMacroRealm(realm), recursion_limit(lim), rest(s) {} /* for the Realm, we use implicit copy-constructor here */ void DoProcess(); private: void Next() { if(*rest) rest++; } void Scan(const char *stoppers = 0); // recursive descent is implemented here void PerformSimple(); // called for rest pointing after '%' void PerformNesting(); // ---- '%[' void PerformLazy(); // ---- '%{' ScriptVariable FetchName(); void FetchArgs(char delim, char closing, ScriptVector &args); const ScriptMacroprocessorMacro* FindMacro(const ScriptVariable &name) const; ScriptVariable GetValue(const ScriptVariable &name) const; void LazyComputation(const ScriptVariable &name, const ScriptVector &args); void HandleError(const char *start_err_pos); public: ScriptVariable GetValue(const ScriptVariable &name, const ScriptVector &args, bool lazy = false) const; }; static bool char_ok_in_name(int c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '*' || c == '_'; } ScriptVariable ScriptMacroContext::FetchName() { ScriptVariable res = ""; while(*rest && char_ok_in_name(*rest)) { res += *rest; Next(); } return res; } void ScriptMacroContext:: FetchArgs(char delim, char closing, ScriptVector &args) { char stoppers[3] = { delim, closing, 0 }; ScriptVariable save_res = result; args.Clear(); char c; do { result = ""; Scan(stoppers); args.AddItem(result); c = *rest; Next(); } while(*rest && c != closing); result = save_res; } void ScriptMacroContext::DoProcess() { if(!rest) { result.Invalidate(); return; } if(recursion_limit <= 0) { result = rest; return; } result = ""; Scan(); } static bool char_in_str(char c, const char *s) { while(*s) { if(c == *s) return true; s++; } return false; } void ScriptMacroContext::Scan(const char *stoppers) { while(*rest) { if(stoppers && char_in_str(*rest, stoppers)) break; if(*rest == esc_char) { Next(); char c = *rest; if(!c) { break; } else if(c == left_br) { Next(); PerformNesting(); } else if(c == left_lazy) { Next(); PerformLazy(); } else if(c == esc_char || c == right_br || c == right_lazy) { result += c; Next(); } else { PerformSimple(); } } else { result += *rest; Next(); } } } void ScriptMacroContext::PerformSimple() { const char *startpos = rest - 1; ScriptVariable name = FetchName(); if(!*rest) /* XXX error handling to be performed here */ return; if(*rest == esc_char) { Next(); ScriptVariable r = GetValue(name); if(r.IsInvalid()) { HandleError(startpos); return; } result += r; return; } char delim = *rest; Next(); ScriptVector args; FetchArgs(delim, esc_char, args); // please note it is impossible to have anyting nested here // because esc_char is used as the closer // FetchArgs is 'too good' here, as it actually evaluates the // encountered macros, but here we can't have any of them! ScriptVariable r = GetValue(name, args); if(r.IsInvalid()) { HandleError(startpos); return; } result += r; } void ScriptMacroContext::PerformNesting() { const char *startpos = rest - 2; ScriptVariable name = FetchName(); if(!*rest) /* XXX error handling to be performed here */ return; if(*rest == right_br) { Next(); // no nesting is allowed within the name ScriptVariable r = GetValue(name); if(r.IsInvalid()) { HandleError(startpos); return; } result += r; return; } char delim = *rest; Next(); ScriptVector args; FetchArgs(delim, right_br, args); #if 0 // now we need to process every arg in a sub-context // then pass them to the macro // NO WE DON'T!!! FetchArgs already did that for us! int i; for(i = 0; i < args.Length(); i++) { if(args[i].IsInvalid()) // invalidated string remains invalidated continue; ScriptMacroContext cnt(*this, args[i].c_str(), recursion_limit-1); cnt.DoProcess(); args[i] = cnt.result; } #endif ScriptVariable r = GetValue(name, args); if(r.IsInvalid()) { HandleError(startpos); return; } result += r; } void ScriptMacroContext::PerformLazy() { const char *startpos = rest - 2; ScriptVariable name = FetchName(); if(!*rest) { HandleError(startpos); return; } if(*rest == right_lazy) { Next(); ScriptVector dummy_args; // lazy comp. always done with args LazyComputation(name, dummy_args); return; } char delim = *rest; Next(); ScriptVector args; // we can't use Scan, it is for eager strategy ScriptVariable unclosed; unclosed += left_lazy; args[0] = ""; while(*rest) { if(unclosed.length() == 1 && *rest == delim) { Next(); args.AddItem(""); continue; } if((*rest == right_br && unclosed[unclosed.length()-1] == left_br) || (*rest == esc_char && unclosed[unclosed.length()-1] == esc_char)) { args[args.Length()-1] += *rest; Next(); unclosed.Range(-1, 1).Erase(); continue; } if(*rest == right_lazy && unclosed[unclosed.length()-1] == left_lazy) { unclosed.Range(-1, 1).Erase(); if(unclosed == "") { Next(); LazyComputation(name, args); return; } else { args[args.Length()-1] += *rest; Next(); } continue; } // all other chars go to the args! args[args.Length()-1] += *rest; if(*rest == esc_char) { Next(); char c = *rest; if(!c) break; if(c == left_lazy || c == left_br) { unclosed += c; args[args.Length()-1] += c; Next(); } else if(c == right_lazy || c == right_br) { // so they will not act as special chars this time args[args.Length()-1] += c; Next(); } else { // otherwise, leave it where it is, don't consume! // it is a start of simple call like %macro%, so // add the % to the unclosed stack unclosed += esc_char; } } else { Next(); } } /* unclosed lazy call */ HandleError(startpos); } void ScriptMacroContext::LazyComputation(const ScriptVariable &name, const ScriptVector &args) { ScriptVariable s = GetValue(name, args, true); if(s.IsInvalid()) // XXXXXXX not found, error? return; ScriptMacroContext cnt(*this, s.c_str(), recursion_limit-1); cnt.DoProcess(); result += cnt.result; } const ScriptMacroprocessorMacro* ScriptMacroContext::FindMacro(const ScriptVariable &name) const { const ScriptMacroRealm *r; for(r = this; r; r = r->parent) { macro_item *p; for(p = r->first; p; p = p->next) if(p->macro->GetName() == name) return p->macro; } return 0; } ScriptVariable ScriptMacroContext::GetValue(const ScriptVariable &name) const { // this form is never called for lazy computation if(positionals) { long pos; if(name.GetLong(pos)) { if(pos_count != -1 && pos > pos_count) return ScriptVariable(""); return (*positionals)[pos + pos_idx]; } if(name == "*") return (*positionals).Join(" ", pos_idx+1, pos_count == -1 ? -1 : pos_count-1); } const ScriptMacroprocessorMacro *m = FindMacro(name); if(!m) return ScriptVariableInv(); return m->Expand(); } ScriptVariable ScriptMacroContext::GetValue(const ScriptVariable &name, const ScriptVector &args, bool lazy) const { // no check for positional here, don't waste the time // in future we might want to add %*:separator% here const ScriptMacroprocessorMacro *m = FindMacro(name); if(!m) return ScriptVariableInv(); if(lazy && m->IsDirty()) // prohibited! return ScriptVariableInv(); return m->Expand(args); } void ScriptMacroContext::HandleError(const char *start_err_pos) { const char *p; for(p = start_err_pos; p < rest; p++) result += *p; } //////////////////////////////////////////////////////////////////////// ScriptMacroprocessor::ScriptMacroprocessor(char ec) : base_realm(new ScriptMacroRealm(ec)), recursion_limit(default_recursion_limit), positionals_owned(false) { } ScriptMacroprocessor::ScriptMacroprocessor(const char *chrs) : base_realm(new ScriptMacroRealm(chrs)), recursion_limit(default_recursion_limit), positionals_owned(false) { } ScriptMacroprocessor::ScriptMacroprocessor(const ScriptMacroprocessor *parent) : base_realm(new ScriptMacroRealm(parent->base_realm)), // not a copy! recursion_limit(default_recursion_limit), positionals_owned(false) { } ScriptMacroprocessor::~ScriptMacroprocessor() { while(base_realm->first) { macro_item *p = base_realm->first; base_realm->first = p->next; delete p->macro; delete p; } if(base_realm->positionals && positionals_owned) delete base_realm->positionals; delete base_realm; } ScriptMacroprocessor::MacroId ScriptMacroprocessor::AddMacro(ScriptMacroprocessorMacro* v) { macro_item *p = new macro_item; p->macro = v; p->next = base_realm->first; base_realm->first = p; return base_realm->first; } bool ScriptMacroprocessor::RemoveMacro(ScriptMacroprocessor::MacroId id) { for(macro_item **p = &base_realm->first; *p; p = &(*p)->next) { if(*p == id) { macro_item *tmp = *p; *p = tmp->next; delete tmp->macro; delete tmp; return true; } } return false; } void ScriptMacroprocessor::SetPositionals(const ScriptVector &args, bool copy) { SetPositionals(args, 0, -1, copy); } void ScriptMacroprocessor::SetPositionals(const ScriptVector &args, int idx, int cnt, bool copy) { if(positionals_owned && base_realm->positionals) delete base_realm->positionals; if(copy) { base_realm->positionals = new ScriptVector(args, idx, cnt); positionals_owned = true; base_realm->pos_idx = 0; base_realm->pos_count = -1; } else { base_realm->positionals = &args; positionals_owned = false; base_realm->pos_idx = idx; base_realm->pos_count = cnt; } } ScriptVariable ScriptMacroprocessor::Process(const ScriptVariable &src) const { ScriptMacroContext context(*base_realm, src.c_str(), recursion_limit); context.DoProcess(); return context.result; } ScriptVariable ScriptMacroprocessor::Process(const ScriptVariable &src, const ScriptVector &argv, int idx, int count) const { ScriptMacroContext context(*base_realm, src.c_str(), recursion_limit); context.positionals = &argv; context.pos_idx = idx; context.pos_count = count; context.DoProcess(); return context.result; } ScriptVariable ScriptMacroprocessor:: Process(const ScriptVariable &src, const ScriptMacroprocessorMacro *aux) const { ScriptMacroContext context(*base_realm, src.c_str(), recursion_limit); macro_item it; it.next = context.first; it.macro = aux; context.first = ⁢ context.DoProcess(); return context.result; } ScriptVariable ScriptMacroprocessor:: Apply(const ScriptVariable &name, const ScriptVector &args, bool force_no_dirty) const { ScriptMacroContext context(*base_realm, 0, recursion_limit); return context.GetValue(name, args, force_no_dirty); } ScriptVariable ScriptMacroprocessorMacro::Expand() const { ScriptVector dummy; return Expand(dummy); } ScriptMacroDictionary::ScriptMacroDictionary(const ScriptVariable &name, const ScriptVector &dict, bool copy) : ScriptMacroprocessorMacro(name), vec_owned(copy) { if(copy) the_dict = new ScriptVector(dict); else the_dict = &dict; } ScriptMacroDictionary::~ScriptMacroDictionary() { if(vec_owned) delete the_dict; } ScriptVariable ScriptMacroDictionary::Expand(const ScriptVector &args) const { int i; for(i = 0; i < the_dict->Length() - 1; i += 2) { if((*the_dict)[i] == args[0]) return (*the_dict)[i+1]; } return ""; }