thalassa/lib/scriptpp/scrmacro.hpp
2026-03-19 06:23:52 +05:00

262 lines
12 KiB
C++

// +-------------------------------------------------------------------------+
// | Script Plus Plus vers. 0.3.72 |
// | Copyright (c) Andrey V. Stolyarov <croco at croco dot net> 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 !!! |
// +-------------------------------------------------------------------------+
#ifndef SCRIPTPP_SCRMACRO_HPP_SENTRY
#define SCRIPTPP_SCRMACRO_HPP_SENTRY
/*! \file scrmacro.hpp
\brief This file invents the ScriptMacroprocessor class which
expand predefined macros in a given string
*/
#include "scrvar.hpp"
#include "scrvect.hpp"
//! The Macro interface
/*! To create a macro, simply derive your class from this one,
implementing the Expand() virtual method, which does all the job.
*/
class ScriptMacroprocessorMacro {
ScriptVariable name;
bool dirty;
public:
ScriptMacroprocessorMacro(const ScriptVariable &n, bool d = false)
: name(n), dirty(d) {}
virtual ~ScriptMacroprocessorMacro() {}
const ScriptVariable &GetName() const { return name; }
bool IsDirty() const { return dirty; }
//! The method to be implemented
/*!
*/
virtual ScriptVariable Expand(const ScriptVector &args) const = 0;
//! The simplified case
/*! This version is called when the macro call specifies
no arguments (e.g., if it is a simple macrovariable).
By default, this method calls the previous form with
empty Vector but you can override it to gain efficiecny
*/
virtual ScriptVariable Expand() const;
};
//! Script Macroprocessor processes macros. Surprize, heh.
/*! Macro calls have a form of either %var%, %[var] or ${var}, where name
can consist of alphanumeric chars ('a'..'z', 'A'..'Z', '0'..'9'
ranges), and the characters '_' and '*'. Macro calls can have
parameters like $[func:arg1:arg2]; the ':' here can actually be any char
which is not allowed in names (not alnum, '_' nor '*'). Please note
that the library is (as a matter of principle) i18n-agnostic, so please
use ASCII symbols in macro names and punctuation, or else problems are
almost guaranteed. For each macro call its argument-delimiting char is
set to the first _byte_ inside the macro call which is outside of the
abovementioned ranges and is not the closing char for the particular
call form (%, ], }); for example, you can use $[func/arg1/arg2], or
$[func@arg1@arg2], or $[func#arg1#arg2].
You can also replace all the 'special' chars to anything else than
%,[,],{,} passing them to the constructor (as a string of exactly 5
chars). In all examples we assume the default chars are used.
The special case %0%, %1%, ..., %120%, etc., can (and perhaps should)
be used to access elements of a certain ScriptVector, as ``arguments''.
%*% gives all ``args'' except for the zeroth, joined with one space.
Another important special case is ``%%'', which produces a single '%'
char; %} and %] can be used to 'escape' the closing character, should
it happen to be needed within the call.
The ScriptMacroprocessor class object owns its collection of
``macros'' represented by objects of classes derived from
ScriptMacroprocessor::Macro, each having its name. In the present
version, the name search is done in a simple linear way; this works
perfectly until you've got a lot of macros (say, 100 and more of them).
The Macro class has a pure virtual method which takes ScriptVector
(formed out of all params except the macro name itself) and returns
the resulting string in form of ScriptVariable.
Any macro (including these 0, 1, ...) can be used in any of the
three ways -- as %name%, %[name] or %{name}. The difference between
%name% and %[name] is that in the former case variable references can
be nested, such as %[foo:%[bar]:bur]. The %[] macro expansions are
performed in 'eager' manner, that is, the inner macro call is expanded
first. However, the call is broken down to delimited parts _before_
any more calls are processed, and each part is processed separately.
The %{} references are processed in 'lazy' manner: first, the line is
searched to match the '{' with '}', ignoring 'escaped' instances
(``%}'', ``%]''), broken down to delimited arguments, and then the
arguments are passed to the Macro object without any processing;
afterwards, the resulting (substituted) string is processed again.
Please be WARNED that if your macro is replaced with some 'external'
data, such as received from the Net or read from a file, SUCH MACRO
MUST NEVER BE USED with %{}! Such usage would lead to obvious
injection vulnerability. From the other hand, there's nothing to worry
about if such a macro is used like %var% or %[var], even if it is
inside of %{}. Such macros must be marked as ``dirty'' so that the
macroprocessor will refuse to expand them in lazy manner.
Note that %x% generally performs faster than %[x] so if you don't
need them nested, then perhaps you should prefer the first form. %{x}
is the slowest thing; avoid it if possible.
If no suitable Macro object is found, the entry is silently left
untouched by default, except for the lazy calls, which are replaced
with empty strings. The same thing happens when your macro returns
ScriptVariableInv. In the present version this behavior can't
be changed; if you really need to, contact the author (or see the
ScriptMacroContext::HandleError method which is to be made more
flexible). It is also impossible to determine what caused the error
(unknown macro name or existing macro returning invalidated string).
*/
class ScriptMacroprocessor {
public:
typedef void *MacroId;
enum { default_recursion_limit = 20 };
private:
struct ScriptMacroRealm *base_realm;
int recursion_limit;
bool positionals_owned;
// do we own the positionals (the pointer is inside the base_realm)
public:
ScriptMacroprocessor(char ec = '%');
ScriptMacroprocessor(const char *chrs);
/*! \param parent is a (kinda) 'outer scope', local vars shadow
the names from the parent; the parent is checked
in case no match is found locally
\note make sure the parent object remains existing
until you stop using all objects that depend on it
*/
ScriptMacroprocessor(const ScriptMacroprocessor *parent);
~ScriptMacroprocessor();
void SetRecursionLimit(int lim) { recursion_limit = lim; }
/*! \note The class OWNS all the 'Macro' instances!
\note To increase performans, add the most often used
entries last, and the rarely used entries first,
as they are added to the front of a single-linked
list and are queried in the order of the list.
*/
MacroId AddMacro(ScriptMacroprocessorMacro* v);
/*! \note The Macro object is deleted, so don't try to reuse it */
bool RemoveMacro(MacroId id);
void SetPositionals(const ScriptVector &args, bool copy);
void SetPositionals(const ScriptVector &args, int idx, int cnt, bool copy);
ScriptVariable Process(const ScriptVariable &src) const;
ScriptVariable Process(const ScriptVariable &src,
const ScriptVector &argv,
int idx = 0, int count = -1) const;
/*! \note the *aux object is not deleted, it remains yours */
ScriptVariable Process(const ScriptVariable &src,
const ScriptMacroprocessorMacro *aux) const;
ScriptVariable operator()(const ScriptVariable &src) const
{ return Process(src); }
ScriptVariable operator()(const ScriptVariable &src,
const ScriptVector &argv,
int idx = 0, int count = -1) const
{ return Process(src, argv, idx, count); }
//! Apply a macro to the given arguments
/*! \warning Be sure to set force_no_dirty to true in case
you're going to process the result again (like for
lazy evaluations); however there's no reason to bother
in case you've got no ``dirty'' macros in your realm.
See the ScriptMacroprocessor documentation for details.
*/
ScriptVariable Apply(const ScriptVariable &name,
const ScriptVector &args,
bool force_no_dirty = false) const;
};
//! A single macro 'variable' without parameters
/*! The name of the substitution variable is given to the constructor,
e.g., to arrange a substitution for '%MYVAR%', give the string 'MYVAR'.
The method Text() must be implemented by your derived class; it
returns the string to be substituted, which can be different each time.
*/
class ScriptMacroVariable : public ScriptMacroprocessorMacro {
public:
ScriptMacroVariable(const ScriptVariable &a_name)
: ScriptMacroprocessorMacro(a_name) {}
~ScriptMacroVariable() {}
ScriptVariable Expand(const ScriptVector &args) const
{ return args.Length() == 0 ? Expand() : ScriptVariable(""); }
ScriptVariable Expand() const { return Text(); }
virtual ScriptVariable Text() const = 0;
};
//! The simplest possible case: fixed name, fixed value
class ScriptMacroConst : public ScriptMacroVariable {
ScriptVariable val;
public:
ScriptMacroConst(const ScriptVariable &name,
const ScriptVariable &value)
: ScriptMacroVariable(name), val(value) {}
~ScriptMacroConst() {}
private:
virtual ScriptVariable Text() const { return val; }
};
//! Substitute a var of given name with the value of a ScriptVariable
/*! The ScriptVariable you give here can change whenever you want, so
the value to be substituted will change, too.
\warning Make sure the object of ScriptVariable that you use here
remains existing all the time the object of ScriptMacroScrVar
exists, or else full degree of chaos is guaranteed.
*/
class ScriptMacroScrVar : public ScriptMacroVariable {
ScriptVariable *val;
public:
ScriptMacroScrVar(const ScriptVariable &name, ScriptVariable *pval)
: ScriptMacroVariable(name), val(pval) {}
~ScriptMacroScrVar() {}
private:
virtual ScriptVariable Text() const { return *val; }
};
//! The 'dictionary' substitution
/*! The dictionary is represented by a ScriptVector of even length,
the 0th, 2nd, 4th... items being names to substitute, and the
1st, 3rd, 5th... to be the text to substitute. The macro call
should have the form %name:key%, %[name:key] or ${name:key}.
The ScriptMacroDictionary object can either make a copy of
your vector, or (if you wish so) assume the vector remains existing
somewhere else, and there's no need for a copy.
*/
class ScriptMacroDictionary : public ScriptMacroprocessorMacro {
const class ScriptVector *the_dict;
bool vec_owned;
public:
ScriptMacroDictionary(const ScriptVariable &name,
const ScriptVector &dict, bool copy = true);
~ScriptMacroDictionary();
ScriptVariable Expand(const ScriptVector &args) const;
};
#endif