mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-12 02:28:11 +02:00
* a bit more of ERM/VERM interpreter
This commit is contained in:
parent
f12aeb7fa4
commit
382e239e47
@ -15,153 +15,7 @@
|
||||
*/
|
||||
|
||||
namespace spirit = boost::spirit;
|
||||
|
||||
namespace VERMInterpreter
|
||||
{
|
||||
using namespace ERM;
|
||||
|
||||
//different exceptions that can be thrown during interpreting
|
||||
class EInterpreterProblem : public std::exception
|
||||
{};
|
||||
|
||||
class ESymbolNotFound : public EInterpreterProblem
|
||||
{
|
||||
std::string problem;
|
||||
public:
|
||||
ESymbolNotFound(const std::string & sym) : problem(std::string("Symbol ") + sym + std::string(" not found!"))
|
||||
{}
|
||||
~ESymbolNotFound() throw();
|
||||
const char * what() const throw() OVERRIDE
|
||||
{
|
||||
return problem.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///main environment class, manages symbols
|
||||
class Environment
|
||||
{
|
||||
private:
|
||||
std::map<std::string, TVOption> symbols;
|
||||
Environment * lexicalParent;
|
||||
|
||||
public:
|
||||
bool isBound(const std::string & name, bool globalOnly) const
|
||||
{
|
||||
std::map<std::string, TVOption>::const_iterator it = symbols.find(name);
|
||||
if(globalOnly && lexicalParent)
|
||||
{
|
||||
return lexicalParent->isBound(name, globalOnly);
|
||||
}
|
||||
|
||||
//we have it; if globalOnly is true, lexical parent is false here so we are global env
|
||||
if(it != symbols.end())
|
||||
return true;
|
||||
|
||||
//here, we don;t have it; but parent can have
|
||||
if(lexicalParent)
|
||||
return lexicalParent->isBound(name, globalOnly);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TVOption retrieveValue(const std::string & name) const
|
||||
{
|
||||
std::map<std::string, TVOption>::const_iterator it = symbols.find(name);
|
||||
if(it == symbols.end())
|
||||
{
|
||||
if(lexicalParent)
|
||||
{
|
||||
return lexicalParent->retrieveValue(name);
|
||||
}
|
||||
|
||||
throw ESymbolNotFound(name);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
///returns true if symbols was really unbound
|
||||
enum EUnbindMode{LOCAL, RECURSIVE_UNTIL_HIT, FULLY_RECURSIVE};
|
||||
bool unbind(const std::string & name, EUnbindMode mode)
|
||||
{
|
||||
if(isBound(name, false))
|
||||
{
|
||||
if(symbols.find(name) != symbols.end()) //result of isBound could be from higher lexical env
|
||||
symbols.erase(symbols.find(name));
|
||||
|
||||
if(mode == FULLY_RECURSIVE && lexicalParent)
|
||||
lexicalParent->unbind(name, mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
if(lexicalParent && (mode == RECURSIVE_UNTIL_HIT || mode == FULLY_RECURSIVE))
|
||||
return lexicalParent->unbind(name, mode);
|
||||
|
||||
//neither bound nor have lexical parent
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// All numeric variables are integer variables and have a range of -2147483647...+2147483647
|
||||
// c stores game active day number //indirect variable
|
||||
// d current value //not an actual variable but a modifier
|
||||
// e1..e100 Function floating point variables //local
|
||||
// e-1..e-100 Trigger local floating point variables //local
|
||||
// 'f'..'t' Standard variables ('quick variables') //global
|
||||
// v1..v1000 Standard variables //global
|
||||
// w1..w100 Hero variables
|
||||
// w101..w200 Hero variables
|
||||
// x1..x16 Function parameters //local
|
||||
// y1..y100 Function local variables //local
|
||||
// y-1..y-100 Trigger-based local integer variables //local
|
||||
// z1..z1000 String variables //global
|
||||
// z-1..z-10 Function local string variables //local
|
||||
|
||||
struct TriggerLocalVars
|
||||
{
|
||||
static const int EVAR_NUM = 100; //number of evar locals
|
||||
double evar[EVAR_NUM]; //negative indices
|
||||
|
||||
static const int YVAR_NUM = 100; //number of yvar locals
|
||||
int yvar[YVAR_NUM];
|
||||
};
|
||||
|
||||
struct FunctionLocalVars
|
||||
{
|
||||
static const int NUM_PARAMETERS = 16; //number of function parameters
|
||||
int params[NUM_PARAMETERS]; //x-vars
|
||||
|
||||
static const int NUM_LOCALS = 100;
|
||||
int locals[NUM_LOCALS]; //y-vars
|
||||
|
||||
static const int NUM_STRINGS = 10;
|
||||
std::string strings[NUM_STRINGS]; //z-vars (negative indices)
|
||||
|
||||
static const int NUM_FLOATINGS = 100;
|
||||
double floats[NUM_FLOATINGS]; //e-vars (positive indices)
|
||||
};
|
||||
|
||||
struct ERMEnvironment
|
||||
{
|
||||
static const int NUM_QUICKS = 't' - 'f' + 1; //it should be 15
|
||||
int quickVars[NUM_QUICKS]; //referenced by letter ('f' to 't' inclusive)
|
||||
|
||||
static const int NUM_STANDARDS = 1000;
|
||||
int standardVars[NUM_STANDARDS]; //v-vars
|
||||
|
||||
static const int NUM_STRINGS = 1000;
|
||||
std::string strings[NUM_STRINGS]; //z-vars (positive indices)
|
||||
};
|
||||
|
||||
//call stack
|
||||
class Stack
|
||||
{
|
||||
std::vector<int> entryPoints; //defines how to pass to current location
|
||||
Environment * env; //most nested VERM environment
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
using namespace VERMInterpreter;
|
||||
|
||||
namespace ERMPrinter
|
||||
{
|
||||
@ -517,7 +371,17 @@ void ERMInterpreter::scanForScripts()
|
||||
boost::algorithm::ends_with(name, ".verm") )
|
||||
{
|
||||
ERMParser ep(dir->path().string());
|
||||
scripts[name] = ep.parseFile();
|
||||
FileInfo * finfo = new FileInfo;
|
||||
finfo->filename = dir->path().string();
|
||||
|
||||
std::vector<ERM::TLine> buf = ep.parseFile();
|
||||
finfo->length = buf.size();
|
||||
files.push_back(finfo);
|
||||
|
||||
for(int g=0; g<buf.size(); ++g)
|
||||
{
|
||||
scripts[LinePointer(finfo, g)] = buf[g];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -525,12 +389,124 @@ void ERMInterpreter::scanForScripts()
|
||||
|
||||
void ERMInterpreter::printScripts( EPrintMode mode /*= EPrintMode::ALL*/ )
|
||||
{
|
||||
for(std::map<std::string, std::vector<ERM::TLine> >::const_iterator it = scripts.begin(); it != scripts.end(); ++it)
|
||||
std::map< LinePointer, ERM::TLine >::const_iterator prevIt;
|
||||
for(std::map< LinePointer, ERM::TLine >::const_iterator it = scripts.begin(); it != scripts.end(); ++it)
|
||||
{
|
||||
tlog2 << "----------------- script " << it->first << " ------------------\n";
|
||||
for(int i=0; i<it->second.size(); ++i)
|
||||
if(it == scripts.begin() || it->first.file != prevIt->first.file)
|
||||
{
|
||||
ERMPrinter::printAST(it->second[i]);
|
||||
tlog2 << "----------------- script " << it->first.file->filename << " ------------------\n";
|
||||
}
|
||||
|
||||
ERMPrinter::printAST(it->second);
|
||||
prevIt = it;
|
||||
}
|
||||
}
|
||||
|
||||
void ERMInterpreter::scanScripts()
|
||||
{
|
||||
for(std::map< LinePointer, ERM::TLine >::const_iterator it = scripts.begin(); it != scripts.end(); ++it)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ERMInterpreter::ERMInterpreter()
|
||||
{
|
||||
globalEnv = new Environment();
|
||||
}
|
||||
|
||||
void ERMInterpreter::executeTrigger( Trigger & trig )
|
||||
{
|
||||
for(LinePointer lp = trig.line; lp.isValid(); ++lp)
|
||||
{
|
||||
ERM::TLine curLine = retrieveLine(lp);
|
||||
if(isATrigger(curLine))
|
||||
break;
|
||||
|
||||
executeLine(lp);
|
||||
}
|
||||
}
|
||||
|
||||
bool ERMInterpreter::isATrigger( const ERM::TLine & line )
|
||||
{
|
||||
switch(line.which())
|
||||
{
|
||||
case 0: //v-exp
|
||||
{
|
||||
TVExp vexp = boost::get<TVExp>(line);
|
||||
if(vexp.children.size() == 0)
|
||||
return false;
|
||||
|
||||
switch (getExpType(vexp.children[0]))
|
||||
{
|
||||
case SYMBOL:
|
||||
{
|
||||
//TODO: what about sym modifiers?
|
||||
//TOOD: macros
|
||||
ERM::TSymbol sym = boost::get<ERM::TSymbol>(vexp.children[0]);
|
||||
return sym.sym == triggerSymbol || sym.sym == postTriggerSymbol;
|
||||
}
|
||||
break;
|
||||
case TCMD:
|
||||
return isCMDATrigger( boost::get<ERM::Tcommand>(vexp.children[0]) );
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: //erm
|
||||
{
|
||||
TERMline line = boost::get<TERMline>(line);
|
||||
switch(line.which())
|
||||
{
|
||||
case 0: //tcmd
|
||||
return isCMDATrigger( boost::get<ERM::Tcommand>(line) );
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0); //it should never happen
|
||||
break;
|
||||
}
|
||||
assert(0);
|
||||
}
|
||||
|
||||
ERM::EVOtions ERMInterpreter::getExpType( const ERM::TVOption & opt )
|
||||
{
|
||||
//MAINTENANCE: keep it correct!
|
||||
return static_cast<ERM::EVOtions>(opt.which());
|
||||
}
|
||||
|
||||
bool ERMInterpreter::isCMDATrigger( const ERM::Tcommand & cmd )
|
||||
{
|
||||
switch (cmd.cmd.which())
|
||||
{
|
||||
case 0: //trigger
|
||||
case 3: //post trigger
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERM::TLine ERMInterpreter::retrieveLine( LinePointer linePtr ) const
|
||||
{
|
||||
return *scripts.find(linePtr);
|
||||
}
|
||||
|
||||
void ERMInterpreter::executeLine( const LinePointer & lp )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const std::string ERMInterpreter::triggerSymbol = "trigger";
|
||||
const std::string ERMInterpreter::postTriggerSymbol = "postTrigger";
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "../global.h"
|
||||
#include "ERMParser.h"
|
||||
#include <boost/smart_ptr.hpp>
|
||||
|
||||
/*
|
||||
* ERMInterpreter.h, part of VCMI engine
|
||||
@ -12,12 +13,285 @@
|
||||
*
|
||||
*/
|
||||
|
||||
namespace VERMInterpreter
|
||||
{
|
||||
using namespace ERM;
|
||||
|
||||
//different exceptions that can be thrown during interpreting
|
||||
class EInterpreterProblem : public std::exception
|
||||
{
|
||||
std::string problem;
|
||||
public:
|
||||
const char * what() const throw() OVERRIDE
|
||||
{
|
||||
return problem.c_str();
|
||||
}
|
||||
~EInterpreterProblem() throw();
|
||||
EInterpreterProblem(const std::string & problemDesc) : problem(problemDesc)
|
||||
{}
|
||||
};
|
||||
|
||||
struct ESymbolNotFound : public EInterpreterProblem
|
||||
{
|
||||
ESymbolNotFound(const std::string & sym) :
|
||||
EInterpreterProblem(std::string("Symbol \"") + sym + std::string("\" not found!"))
|
||||
{}
|
||||
};
|
||||
|
||||
struct EInvalidTrigger : public EInterpreterProblem
|
||||
{
|
||||
EInvalidTrigger(const std::string & sym) :
|
||||
EInterpreterProblem(std::string("Trigger \"") + sym + std::string("\" is invalid!"))
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
///main environment class, manages symbols
|
||||
class Environment
|
||||
{
|
||||
private:
|
||||
std::map<std::string, TVOption> symbols;
|
||||
Environment * parent;
|
||||
|
||||
public:
|
||||
bool isBound(const std::string & name, bool globalOnly) const
|
||||
{
|
||||
std::map<std::string, TVOption>::const_iterator it = symbols.find(name);
|
||||
if(globalOnly && parent)
|
||||
{
|
||||
return parent->isBound(name, globalOnly);
|
||||
}
|
||||
|
||||
//we have it; if globalOnly is true, lexical parent is false here so we are global env
|
||||
if(it != symbols.end())
|
||||
return true;
|
||||
|
||||
//here, we don;t have it; but parent can have
|
||||
if(parent)
|
||||
return parent->isBound(name, globalOnly);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TVOption retrieveValue(const std::string & name) const
|
||||
{
|
||||
std::map<std::string, TVOption>::const_iterator it = symbols.find(name);
|
||||
if(it == symbols.end())
|
||||
{
|
||||
if(parent)
|
||||
{
|
||||
return parent->retrieveValue(name);
|
||||
}
|
||||
|
||||
throw ESymbolNotFound(name);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
enum EUnbindMode{LOCAL, RECURSIVE_UNTIL_HIT, FULLY_RECURSIVE};
|
||||
///returns true if symbols was really unbound
|
||||
bool unbind(const std::string & name, EUnbindMode mode)
|
||||
{
|
||||
if(isBound(name, false))
|
||||
{
|
||||
if(symbols.find(name) != symbols.end()) //result of isBound could be from higher lexical env
|
||||
symbols.erase(symbols.find(name));
|
||||
|
||||
if(mode == FULLY_RECURSIVE && parent)
|
||||
parent->unbind(name, mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
if(parent && (mode == RECURSIVE_UNTIL_HIT || mode == FULLY_RECURSIVE))
|
||||
return parent->unbind(name, mode);
|
||||
|
||||
//neither bound nor have lexical parent
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// All numeric variables are integer variables and have a range of -2147483647...+2147483647
|
||||
// c stores game active day number //indirect variable
|
||||
// d current value //not an actual variable but a modifier
|
||||
// e1..e100 Function floating point variables //local
|
||||
// e-1..e-100 Trigger local floating point variables //local
|
||||
// 'f'..'t' Standard variables ('quick variables') //global
|
||||
// v1..v1000 Standard variables //global
|
||||
// w1..w100 Hero variables
|
||||
// w101..w200 Hero variables
|
||||
// x1..x16 Function parameters //local
|
||||
// y1..y100 Function local variables //local
|
||||
// y-1..y-100 Trigger-based local integer variables //local
|
||||
// z1..z1000 String variables //global
|
||||
// z-1..z-10 Function local string variables //local
|
||||
|
||||
struct TriggerLocalVars
|
||||
{
|
||||
static const int EVAR_NUM = 100; //number of evar locals
|
||||
double evar[EVAR_NUM]; //negative indices
|
||||
|
||||
static const int YVAR_NUM = 100; //number of yvar locals
|
||||
int yvar[YVAR_NUM];
|
||||
};
|
||||
|
||||
struct FunctionLocalVars
|
||||
{
|
||||
static const int NUM_PARAMETERS = 16; //number of function parameters
|
||||
int params[NUM_PARAMETERS]; //x-vars
|
||||
|
||||
static const int NUM_LOCALS = 100;
|
||||
int locals[NUM_LOCALS]; //y-vars
|
||||
|
||||
static const int NUM_STRINGS = 10;
|
||||
std::string strings[NUM_STRINGS]; //z-vars (negative indices)
|
||||
|
||||
static const int NUM_FLOATINGS = 100;
|
||||
double floats[NUM_FLOATINGS]; //e-vars (positive indices)
|
||||
};
|
||||
|
||||
struct ERMEnvironment
|
||||
{
|
||||
static const int NUM_QUICKS = 't' - 'f' + 1; //it should be 15
|
||||
int quickVars[NUM_QUICKS]; //referenced by letter ('f' to 't' inclusive)
|
||||
int & getQuickVar(const char letter)
|
||||
{
|
||||
assert(letter >= 'f' && letter <= 't'); //it should be check by another function, just makign sure here
|
||||
return quickVars[letter - 'f'];
|
||||
}
|
||||
|
||||
static const int NUM_STANDARDS = 1000;
|
||||
int standardVars[NUM_STANDARDS]; //v-vars
|
||||
|
||||
static const int NUM_STRINGS = 1000;
|
||||
std::string strings[NUM_STRINGS]; //z-vars (positive indices)
|
||||
};
|
||||
|
||||
struct TriggerType
|
||||
{
|
||||
//the same order of trigger types in this enum and in validTriggers array is obligatory!
|
||||
enum ETrigType{AE, BA, BF, BG, BR, CM, CO, FU, GE, GM, HE, HL, HM, IP, LE, MF, MG, MM, MR,
|
||||
MW, OB, PI, SN, TH, TM} type;
|
||||
static ETrigType convertTrigger(const std::string & trig)
|
||||
{
|
||||
static const std::string validTriggers[] = {"AE", "BA", "BF", "BG", "BR", "CM", "CO", "FU",
|
||||
"GE", "GM", "HE", "HL", "HM", "IP", "LE", "MF", "MG", "MM", "MR", "MW", "OB", "PI", "SN",
|
||||
"TH", "TM"};
|
||||
|
||||
for(int i=0; i<ARRAY_COUNT(validTriggers); ++i)
|
||||
{
|
||||
if(validTriggers[i] == trig)
|
||||
return static_cast<ETrigType>(i);
|
||||
}
|
||||
throw EInvalidTrigger(trig);
|
||||
}
|
||||
|
||||
TriggerType(const std::string & sym)
|
||||
{
|
||||
type = convertTrigger(sym);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct FileInfo
|
||||
{
|
||||
std::string filename;
|
||||
int length;
|
||||
};
|
||||
|
||||
struct LinePointer
|
||||
{
|
||||
const FileInfo * file; //non-owning
|
||||
int lineNum;
|
||||
|
||||
LinePointer(const FileInfo * finfo, int line) : file(finfo), lineNum(line)
|
||||
{}
|
||||
|
||||
//lexicographical order
|
||||
bool operator<(const LinePointer & rhs) const
|
||||
{
|
||||
if(file->filename != rhs.file->filename)
|
||||
return file->filename < rhs.file->filename;
|
||||
|
||||
return lineNum < rhs.lineNum;
|
||||
}
|
||||
|
||||
bool operator!=(const LinePointer & rhs) const
|
||||
{
|
||||
return file->filename != rhs.file->filename || lineNum != rhs.lineNum;
|
||||
}
|
||||
LinePointer & operator++()
|
||||
{
|
||||
++lineNum;
|
||||
return *this;
|
||||
}
|
||||
bool isValid() const
|
||||
{
|
||||
return lineNum < file->length;
|
||||
}
|
||||
};
|
||||
|
||||
struct LexicalPtr
|
||||
{
|
||||
LinePointer line; //where to start
|
||||
std::vector<int> entryPoints; //defines how to pass to current location
|
||||
|
||||
bool operator<(const LexicalPtr & sec) const
|
||||
{
|
||||
if(line != sec.line)
|
||||
return line < sec.line;
|
||||
|
||||
if(entryPoints.size() != sec.entryPoints.size())
|
||||
return entryPoints.size() < sec.entryPoints.size();
|
||||
|
||||
for(int g=0; g<entryPoints.size(); ++g)
|
||||
{
|
||||
if(entryPoints[g] < sec.entryPoints[g])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//call stack, represents dynamic range
|
||||
struct Stack
|
||||
{
|
||||
std::vector<LexicalPtr> stack;
|
||||
};
|
||||
|
||||
struct Trigger
|
||||
{
|
||||
LinePointer line;
|
||||
TriggerLocalVars ermLocalVars;
|
||||
Stack * stack; //where we are stuck at execution
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class ERMInterpreter
|
||||
{
|
||||
std::map<std::string, std::vector<ERM::TLine> > scripts;
|
||||
std::vector<VERMInterpreter::FileInfo*> files;
|
||||
std::vector< std::shared_ptr<VERMInterpreter::FileInfo> > fileInfos;
|
||||
std::map<VERMInterpreter::LinePointer, ERM::TLine> scripts;
|
||||
std::map<VERMInterpreter::LexicalPtr, VERMInterpreter::Environment> lexicalEnvs;
|
||||
ERM::TLine retrieveLine(VERMInterpreter::LinePointer linePtr) const;
|
||||
|
||||
VERMInterpreter::Environment * globalEnv;
|
||||
std::map<VERMInterpreter::TriggerType, std::vector<VERMInterpreter::Trigger> > triggers;
|
||||
|
||||
static const std::string triggerSymbol, postTriggerSymbol;
|
||||
|
||||
void executeLine(const VERMInterpreter::LinePointer & lp);
|
||||
void executeTrigger(VERMInterpreter::Trigger & trig);
|
||||
static bool isCMDATrigger(const ERM::Tcommand & cmd);
|
||||
static bool isATrigger(const ERM::TLine & line);
|
||||
static ERM::EVOtions getExpType(const ERM::TVOption & opt);
|
||||
public:
|
||||
void scanForScripts();
|
||||
|
||||
enum EPrintMode{ALL, ERM_ONLY, VERM_ONLY};
|
||||
void printScripts(EPrintMode mode = ALL);
|
||||
void startExecution();
|
||||
void scanScripts(); //scans for functions, triggers etc.
|
||||
|
||||
ERMInterpreter();
|
||||
};
|
@ -217,6 +217,7 @@ namespace ERM
|
||||
|
||||
//for #'symbol expression
|
||||
|
||||
enum EVOtions{VEXP, SYMBOL, CHAR, DOUBLE, INT, TCMD, STRINGC};
|
||||
struct TVExp;
|
||||
typedef boost::variant<boost::recursive_wrapper<TVExp>, TSymbol, char, double, int, Tcommand, TStringConstant > TVOption; //options in v-expression
|
||||
//v-expression
|
||||
|
@ -52,9 +52,9 @@ DLL_EXPORT void initDLL(CConsoleHandler *Console, std::ostream *Logfile)
|
||||
} HANDLE_EXCEPTION;
|
||||
|
||||
|
||||
// ERMInterpreter ei;
|
||||
// ei.scanForScripts();
|
||||
// ei.printScripts();
|
||||
ERMInterpreter ei;
|
||||
ei.scanForScripts();
|
||||
ei.printScripts();
|
||||
}
|
||||
|
||||
DLL_EXPORT void loadToIt(std::string &dest, const std::string &src, int &iter, int mode)
|
||||
|
Loading…
Reference in New Issue
Block a user