LSVariable.h
// LSVariable.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006
#pragma once
#ifndef Ceda_cxMacroExpander_LSVariable_H
#define Ceda_cxMacroExpander_LSVariable_H
#include "cxMacroExpander.h"
#include "Ceda/cxUtils/xvector.h"
#include "Ceda/cxUtils/xstring.h"
#include "Ceda/cxUtils/SubString.h"
#include "Ceda/cxUtils/xostream.h"
#include <map>
#include <set>
namespace ceda
{
class VarFrame;
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSVariable
enum ELSVariable
{
LSV_UNDEF,
LSV_MACRO,
LSV_BOOL,
LSV_INT,
LSV_DOUBLE,
LSV_CHAR,
LSV_STRING
};
class cxMacroExpander_API LSVariable
{
cxNotCloneable(LSVariable)
public:
LSVariable(SubString name);
virtual ~LSVariable() {}
SubString GetName() const { return m_name; }
virtual ELSVariable GetType() const = 0;
virtual void WriteToStream(xostream& os) const = 0;
virtual void WriteValue(xostream& os) const = 0;
protected:
// Name of this variable
SubString m_name;
// Next variable in the forward linked list
LSVariable* m_next;
friend class LSVariableList;
};
inline xostream& operator<<(xostream& os, const LSVariable& x)
{
x.WriteToStream(os);
return os;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSFormalArg
struct cxMacroExpander_API LSFormalArg
{
LSFormalArg() : m_type(LSV_UNDEF) {}
LSFormalArg(SubString name, ELSVariable type, bool isFnArg) : m_name(name), m_type(type), m_isFnArg(isFnArg) {}
SubString m_name;
ELSVariable m_type;
bool m_isFnArg;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSMacroVariable
class cxMacroExpander_API LSMacroVariable : public LSVariable
{
cxNotCloneable(LSMacroVariable)
public:
LSMacroVariable(ELSVariable returnType, SubString name, VarFrame* context,
bool isPython, bool naked, bool isFnArg);
virtual ELSVariable GetType() const { return LSV_MACRO; }
virtual void WriteToStream(xostream& os) const;
virtual void WriteValue(xostream& os) const;
bool IsPython() const { return m_isPython; }
ELSVariable GetReturnType() const { return m_returnType; }
ssize_t GetNumArgs() const { return m_argNames.size(); }
const LSFormalArg& GetArg(ssize_t i) const { return m_argNames[i]; }
void AddArgName(SubString name, ELSVariable type, bool isFnArg) { m_argNames.push_back(LSFormalArg(name,type,isFnArg)); }
ssize_t FindArgName(SubString name) const;
void WriteNameAndFormalParameterList(xostream& os) const;
// Set the substitution string to the given string.
// If makeIndependentCopy then make an independent copy of the given string. The copy is owned
// by this LSVarValue
void SetSubstitutionString(SubString s, bool makeIndependentCopy, ssize_t indentation);
VarFrame* GetContext() const { return m_context; }
SubString GetSubstitutionString() const { return m_substitutionString; }
ssize_t GetIndent() const { return m_indentation; }
void ClearArgs() { m_argNames.clear(); }
bool IsNaked() const { return m_naked; }
bool IsFnArg() const { return m_isFnArg; }
private:
bool m_isPython;
bool m_naked;
bool m_isFnArg;
ELSVariable m_returnType;
// Names of formal arguments
xvector<LSFormalArg> m_argNames;
/*
m_context is only set for a LSMacroVariable of type LSV_MACRO which is used to record the value
of an untyped formal argument variable of a macro invocation. Formal arguments are treated
specially because they use lazy evaluation (i.e. macro expansion of the value of the formal
argument variable is deferred until the formal argument is invoked), and yet we want eager
bindings to names in the VarFrame. To achieve eager bindings we record the VarFrame of the
context in which the value of the formal argument is to be evaluated.
Consider a macro definition
@def m(v) =
{
@def n = 2
v
}
'v' is the name of a formal argument to macro 'm'.
Consider that 'm' is invoked as follows:
@def n = 1
@assert(m(n) == 1)
The invocation of 'm' involves the creation of a local VarFrame. 'v' is inserted into the
VarFrame using a LSMacroVariable of type LSV_MACRO. The value of 'v' is the substitution string
'n'. The value of v is evaluated lazily (i.e. not now, but later when/if v is invoked).
Therefore the fact that v is actually '1' is only determined later. Therefore v must be
evaluated in the context of where m was involved, not in the context of where v is invoked.
*/
VarFrame* m_context;
ssize_t m_indentation;
// TODO: Don't need StringX here. Rather, just a SubString will do?
StringX m_substitutionString;
};
inline xostream& operator<<(xostream& os, const LSMacroVariable& v)
{
v.WriteNameAndFormalParameterList(os);
return os;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSValueVariable
class cxMacroExpander_API LSValueVariable : public LSVariable
{
cxNotCloneable(LSValueVariable)
public:
LSValueVariable(ELSVariable type, SubString name, SubString value);
virtual ELSVariable GetType() const { return m_type; }
virtual void WriteToStream(xostream& os) const;
virtual void WriteValue(xostream& os) const;
public:
ELSVariable m_type;
xstring m_value;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSVar
/*
LSVar is a hack. LSValueVariable only holds an xstring for the value, but not for
the name. So it's no good if the name string isn't recorded somewhere where it can
be safely referenced by a SubString.
It is necessary to inherit from LSValueVariable because the macro expander
uses static casts to LSValueVariable! Therefore we inherit from LSValueVariable so
we can add an xstring member to hold the name. However, we must fix the inherited
SubString m_name member - to point at the local xstring not the SubString passed in
which may not be valid at a later time.
Deletions occurs correctly because LSVar inherits virtual destruction.
*/
class cxMacroExpander_API LSVar : public LSValueVariable
{
cxNotCloneable(LSVar)
public:
LSVar(SubString name, SubString value);
public:
xstring m_nameStr;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// LSVariableList
// 0 = never, 1 = conditionally, 2 = always
#define CEDA_USE_MAP_FOR_LSVARS 2
class cxMacroExpander_API LSVariableList
{
public:
LSVariableList() : m_first(NULL)
#if CEDA_USE_MAP_FOR_LSVARS == 1
,m_count(0)
#endif
{}
// Deletes all the variables in the list
~LSVariableList();
void Add(/*takes*/ LSVariable* v);
LSVariable* Find(SubString name);
void WriteToStream(xostream& os) const;
private:
// First variable in a forward linked list. Each variable is allocated on the heap
LSVariable* m_first;
#if CEDA_USE_MAP_FOR_LSVARS == 1
ssize_t m_count;
#endif
#if CEDA_USE_MAP_FOR_LSVARS != 0
// Cache the variables in a map for fast lookup
typedef std::map<SubString, LSVariable*> MAP;
MAP m_map;
#endif
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// VarFrame
class FileScopeVarFrame;
#define CEDA_CALC_SET_OF_IMPORTED_FRAMES 1
#define CEDA_VALIDATE_SET_OF_IMPORTED_FRAMES 0
/*
A frame represents a dictionary for macro variables. It is the context for performing a macro
expansion of a given string.
A local variable is recorded directly in the map (keyed by the name of the variable).
Frames can nest - a frame stores a pointer to the parent frame. A dictionary look-up involves
searching the local variables first, then searching up through the parent frames until a match is
found or we run out of frames. This has the effect of implementing the "hiding rule" - where a
variable can mask or hide variables of the same name in ancestor frames.
A frame also supports a set of local frames that contribute to the local namespace. During name
look-up, the local variable map as well as all the local frames are searched. If more than one
of these "namespaces" contains the name then an ambiguity error is thrown.
*/
class cxMacroExpander_API VarFrame
{
public:
explicit VarFrame(VarFrame* parent) : m_parent(parent)
{
cxAssert(parent != this);
if (parent)
{
m_fileScopeFrame = parent->m_fileScopeFrame;
cxAssert(m_fileScopeFrame);
}
else
{
m_fileScopeFrame = nullptr;
}
}
FileScopeVarFrame* GetFileScopeVarFrame() const
{
return m_fileScopeFrame;
}
void AddVariable(/*takes*/ LSVariable* var)
{
m_localVariables.Add(var);
}
// Search for a variable with the given name. Searches the local variables as well as the
// local frames for the given name. Ignores the parent frame.
// 'pos' is the error position used if this function throws an exception because of an
// ambiguity in the name look up.
// Returns NULL if not found.
LSVariable* FindLocal(SubString name, const xchar* pos);
// Find variable with the given name. 'pos' is the error position used if this
// function throws an exception because of an ambiguity in the name look up.
// Returns NULL if not found.
// This involves calls to FindLocal() in this frame then parent frames recursively until
// a match is found. When a match is found, ancestor frames are not searched any further.
LSVariable* Find(SubString name, const xchar* pos);
// Like Find() except only searches the local variables in each frame as is recurses through
// the parents. (i.e. always ignores the imported frames).
LSVariable* Find2(SubString name);
VarFrame* GetParent() const { return m_parent; }
void WriteToStream(xostream& os) const;
#if CEDA_CALC_SET_OF_IMPORTED_FRAMES
void ImportFrame(VarFrame* vf);
private:
#if CEDA_VALIDATE_SET_OF_IMPORTED_FRAMES
void AppendImportedFramesIntoSet(std::set<VarFrame*>& s);
xvector<VarFrame*> m_localFrames; // Frames that are imported directly into the local namespace
#endif
LSVariable* FindInDirectImports(SubString name, const xchar* pos);
// The set of frames that are imported either directly or indirectly
// Being a set, we avoid repeats for files that are imported indirectly more than once.
// This provides significant performance improvements.
std::set<VarFrame*> m_setOfFrames;
// The set of frames that are imported directly.
std::set<VarFrame*> m_setOfDirectFrames;
#else
void ImportFrame(VarFrame* vf) { m_localFrames.push_back(vf); }
private:
xvector<VarFrame*> m_localFrames; // Frames that are imported directly into the local namespace
#endif
protected:
LSVariableList m_localVariables;
VarFrame* m_parent;
FileScopeVarFrame* m_fileScopeFrame;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Proposal to allow for $macros, and @def+ to define macros at file scope
In the following cases the macro is added at file scope.
@$PLATFORM = "Win32"
@def+ x = {1}
So the file scope VarFrame is special. --> FileScopeVarFrame is a subtype of VarFrame.
We can use virtual functions to redefine behaviour as required
*/
class cxMacroExpander_API FileScopeVarFrame : public VarFrame
{
public:
FileScopeVarFrame();
// todo: Does this make the need to import frames into a VarFrame redundant?
void AddImport(FileScopeVarFrame* md);
bool ApplyDollarMacros(xstring& s) const;
// Write src to os, applying macros that are of the form $(macroname).
// Returns true if any macros were applied
bool ApplyDollarMacros(xostream& os, SubString src) const;
// Find the macro with the given name, looking locally then the imports
// returns NULL if not found, or else points at the mapped value
const xstring* FindDollarMacro(const xstring& name) const;
// Insert macro $name = value
void InsertDollarMacro(const xstring& name, const xstring& value);
private:
typedef xvector<const FileScopeVarFrame*> IMPORTS;
IMPORTS m_imports;
// Represents macros of the form $name = "value"
typedef std::map<xstring,xstring> MAP;
MAP m_dollarMacrosMap;
//LSVariableList m_privateVariables;
};
} // namespace ceda
#endif // include guard
//#define CEDA_CALC_IMPORTED_NAMESPACE 0
/*
#if CEDA_CALC_IMPORTED_NAMESPACE
void ImportFrame(VarFrame* vf);
private:
friend class LSVariableList;
std::set<VarFrame*> m_importedFrames;
std::map<SubString, LSVariable*> m_importedVarsMap;
std::set<SubString> m_ambiguousImportedNames;
#if CEDA_CALC_IMPORTED_NAMESPACE
void VarFrame::ImportFrame(VarFrame* vf)
{
cxAssert(vf);
std::set<VarFrame*>::iterator i = m_importedFrames.find(vf);
if (i == m_importedFrames.end())
{
// new, so add it, and what's in it. No recursion is needed since vf->m_importedFrames already accounts for
// what's imported directly or indirectly.
m_importedFrames.insert(vf);
m_importedFrames.insert(vf->m_importedFrames.begin(), vf->m_importedFrames.end());
for (std::map<SubString, LSVariable*>::iterator j = vf->m_localVariables.m_map.begin(); j != vf->m_localVariables.m_map.end() ; ++j)
{
if (!m_importedVarsMap.insert(*j).first)
{
m_ambiguousImportedNames.insert(j->first);
}
}
}
}
#endif
*/