1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 12:32:36 +00:00
OpenMW/components/compiler/exprparser.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

762 lines
20 KiB
C++
Raw Normal View History

#include "exprparser.hpp"
2010-06-30 14:08:59 +02:00
#include <algorithm>
2010-07-01 16:40:03 +02:00
#include <cassert>
#include <iterator>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <components/misc/strings/lower.hpp>
#include "context.hpp"
#include "discardparser.hpp"
#include "errorhandler.hpp"
#include "extensions.hpp"
#include "generator.hpp"
#include "junkparser.hpp"
#include "locals.hpp"
#include "scanner.hpp"
2010-07-01 16:40:03 +02:00
#include "stringparser.hpp"
namespace Compiler
{
int ExprParser::getPriority(char op)
{
2010-06-29 16:11:19 +02:00
switch (op)
{
2010-06-29 16:24:54 +02:00
case '(':
2010-08-22 12:47:56 +02:00
2010-06-29 16:24:54 +02:00
return 0;
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
case 'e': // ==
case 'n': // !=
case 'l': // <
case 'L': // <=
case 'g': // <
case 'G': // >=
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
return 1;
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
case '+':
case '-':
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
return 2;
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
case '*':
case '/':
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
return 3;
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
case 'm':
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
return 4;
2010-06-29 16:11:19 +02:00
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
return 0;
}
char ExprParser::getOperandType(int Index) const
{
assert(!mOperands.empty());
assert(Index >= 0);
assert(Index < static_cast<int>(mOperands.size()));
return mOperands[mOperands.size() - 1 - Index];
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
char ExprParser::getOperator() const
{
assert(!mOperators.empty());
return mOperators[mOperators.size() - 1];
}
2010-06-30 14:08:59 +02:00
bool ExprParser::isOpen() const
{
return std::find(mOperators.begin(), mOperators.end(), '(') != mOperators.end();
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
void ExprParser::popOperator()
{
2010-06-29 16:11:19 +02:00
assert(!mOperators.empty());
2010-08-22 12:47:56 +02:00
mOperators.resize(mOperators.size() - 1);
2010-06-29 16:11:19 +02:00
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
void ExprParser::popOperand()
{
assert(!mOperands.empty());
2010-08-22 12:47:56 +02:00
mOperands.resize(mOperands.size() - 1);
2010-06-29 16:11:19 +02:00
}
void ExprParser::replaceBinaryOperands()
{
char t1 = getOperandType(1);
char t2 = getOperandType();
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
popOperand();
popOperand();
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
if (t1 == t2)
mOperands.push_back(t1);
else if (t1 == 'f' || t2 == 'f')
mOperands.push_back('f');
else
2019-03-29 00:59:26 +03:00
throw std::logic_error("Failed to determine result operand type");
2010-06-29 16:11:19 +02:00
}
void ExprParser::pop()
{
char op = getOperator();
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
switch (op)
{
case 'm':
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
Generator::negate(mCode, getOperandType());
popOperator();
break;
case '+':
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
Generator::add(mCode, getOperandType(1), getOperandType());
popOperator();
replaceBinaryOperands();
break;
case '-':
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
Generator::sub(mCode, getOperandType(1), getOperandType());
popOperator();
replaceBinaryOperands();
break;
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
case '*':
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
Generator::mul(mCode, getOperandType(1), getOperandType());
popOperator();
replaceBinaryOperands();
2010-08-22 12:47:56 +02:00
break;
2010-06-29 16:11:19 +02:00
case '/':
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
Generator::div(mCode, getOperandType(1), getOperandType());
popOperator();
replaceBinaryOperands();
break;
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
case 'e':
case 'n':
case 'l':
case 'L':
case 'g':
case 'G':
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
Generator::compare(mCode, op, getOperandType(1), getOperandType());
popOperator();
popOperand();
popOperand();
mOperands.push_back('l');
break;
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
default:
2010-08-22 12:47:56 +02:00
2019-03-29 00:59:26 +03:00
throw std::logic_error("Unknown operator");
2010-06-29 16:11:19 +02:00
}
}
2010-06-29 16:11:19 +02:00
void ExprParser::pushIntegerLiteral(int value)
{
mNextOperand = false;
mOperands.push_back('l');
Generator::pushInt(mCode, mLiterals, value);
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
void ExprParser::pushFloatLiteral(float value)
{
mNextOperand = false;
mOperands.push_back('f');
2010-08-22 12:47:56 +02:00
Generator::pushFloat(mCode, mLiterals, value);
2010-06-29 16:11:19 +02:00
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
void ExprParser::pushBinaryOperator(char c)
{
while (!mOperators.empty() && getPriority(getOperator()) >= getPriority(c))
pop();
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
mOperators.push_back(c);
mNextOperand = true;
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:24:54 +02:00
void ExprParser::close()
{
while (getOperator() != '(')
pop();
2010-08-22 12:47:56 +02:00
2010-06-29 16:24:54 +02:00
popOperator();
}
2010-08-22 12:47:56 +02:00
int ExprParser::parseArguments(const std::string& arguments, Scanner& scanner)
{
2010-08-22 12:47:56 +02:00
return parseArguments(arguments, scanner, mCode);
}
2010-08-22 12:47:56 +02:00
bool ExprParser::handleMemberAccess(const std::string& name)
{
mMemberOp = false;
2013-01-09 20:51:52 +01:00
std::string name2 = Misc::StringUtils::lowerCase(name);
std::string id = Misc::StringUtils::lowerCase(mExplicit);
std::pair<char, bool> type = getContext().getMemberType(name2, id);
if (type.first != ' ')
{
Generator::fetchMember(mCode, mLiterals, type.first, name2, id, !type.second);
mNextOperand = false;
mExplicit.clear();
mOperands.push_back(type.first == 'f' ? 'f' : 'l');
return true;
}
return false;
}
2014-02-14 12:23:00 +01:00
ExprParser::ExprParser(
ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, bool argument)
: Parser(errorHandler, context)
, mLocals(locals)
, mLiterals(literals)
, mNextOperand(true)
, mFirst(true)
, mArgument(argument)
, mRefOp(false)
, mMemberOp(false)
{
}
bool ExprParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner)
{
if (!mExplicit.empty())
return Parser::parseInt(value, loc, scanner);
2010-08-22 12:47:56 +02:00
mFirst = false;
2010-08-22 12:47:56 +02:00
if (mNextOperand)
2010-08-22 12:47:56 +02:00
{
start();
2010-06-29 16:11:19 +02:00
pushIntegerLiteral(value);
mTokenLoc = loc;
return true;
}
else
{
scanner.putbackInt(value, loc);
return false;
}
}
bool ExprParser::parseFloat(float value, const TokenLoc& loc, Scanner& scanner)
{
if (!mExplicit.empty())
return Parser::parseFloat(value, loc, scanner);
mFirst = false;
2010-08-22 12:47:56 +02:00
if (mNextOperand)
2010-08-22 12:47:56 +02:00
{
start();
2010-06-29 16:11:19 +02:00
pushFloatLiteral(value);
mTokenLoc = loc;
return true;
2010-08-22 12:47:56 +02:00
}
else
{
scanner.putbackFloat(value, loc);
return false;
}
}
bool ExprParser::parseName(const std::string& name, const TokenLoc& loc, Scanner& scanner)
{
if (!mExplicit.empty())
{
if (!mRefOp)
{
if (mMemberOp && handleMemberAccess(name))
return true;
return Parser::parseName(name, loc, scanner);
}
else
{
mExplicit.clear();
2019-03-29 00:59:26 +03:00
getErrorHandler().warning("Stray explicit reference", loc);
}
}
mFirst = false;
if (mNextOperand)
2010-08-22 12:47:56 +02:00
{
start();
2013-01-09 20:51:52 +01:00
std::string name2 = Misc::StringUtils::lowerCase(name);
2010-08-22 12:47:56 +02:00
char type = mLocals.getType(name2);
2010-08-22 12:47:56 +02:00
if (type != ' ')
{
Generator::fetchLocal(mCode, type, mLocals.getIndex(name2));
mNextOperand = false;
2010-08-22 12:47:56 +02:00
mOperands.push_back(type == 'f' ? 'f' : 'l');
return true;
}
2010-08-22 12:47:56 +02:00
type = getContext().getGlobalType(name2);
2010-08-22 12:47:56 +02:00
if (type != ' ')
{
Generator::fetchGlobal(mCode, mLiterals, type, name2);
mNextOperand = false;
2010-08-22 12:47:56 +02:00
mOperands.push_back(type == 'f' ? 'f' : 'l');
return true;
}
2010-08-22 12:47:56 +02:00
if (mExplicit.empty() && getContext().isId(name2))
{
2012-06-19 14:55:22 +02:00
mExplicit = name2;
return true;
}
// This is terrible, but of course we must have this for legacy content.
// Convert the string to a number even if it's impossible and use it as a number literal.
// Can't use stof/atof or to_string out of locale concerns.
float number;
std::stringstream stream(name2);
stream >> number;
stream.str(std::string());
stream.clear();
stream << number;
pushFloatLiteral(number);
mTokenLoc = loc;
getErrorHandler().warning("Parsing a non-variable string as a number: " + stream.str(), loc);
return true;
}
else
{
scanner.putbackName(name, loc);
return false;
}
}
bool ExprParser::parseKeyword(int keyword, const TokenLoc& loc, Scanner& scanner)
{
if (const Extensions* extensions = getContext().getExtensions())
{
char returnType; // ignored
std::string argumentType; // ignored
bool hasExplicit = false; // ignored
bool isInstruction = extensions->isInstruction(keyword, argumentType, hasExplicit);
if (isInstruction
|| (mExplicit.empty() && extensions->isFunction(keyword, returnType, argumentType, hasExplicit)))
{
std::string name = loc.mLiteral;
if (name.size() >= 2 && name[0] == '"' && name[name.size() - 1] == '"')
name = name.substr(1, name.size() - 2);
if (isInstruction || mLocals.getType(Misc::StringUtils::lowerCase(name)) != ' ')
{
// pretend this is not a keyword
return parseName(name, loc, scanner);
}
}
}
if (keyword == Scanner::K_end || keyword == Scanner::K_begin || keyword == Scanner::K_short
|| keyword == Scanner::K_long || keyword == Scanner::K_float || keyword == Scanner::K_if
|| keyword == Scanner::K_endif || keyword == Scanner::K_else || keyword == Scanner::K_elseif
|| keyword == Scanner::K_while || keyword == Scanner::K_endwhile || keyword == Scanner::K_return
|| keyword == Scanner::K_messagebox || keyword == Scanner::K_set || keyword == Scanner::K_to)
{
return parseName(loc.mLiteral, loc, scanner);
}
mFirst = false;
2010-08-22 12:47:56 +02:00
if (!mExplicit.empty())
{
if (mRefOp && mNextOperand)
{
// check for custom extensions
if (const Extensions* extensions = getContext().getExtensions())
{
char returnType;
std::string argumentType;
2010-08-22 12:47:56 +02:00
bool hasExplicit = true;
if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit))
{
if (!hasExplicit)
{
2019-03-29 00:59:26 +03:00
getErrorHandler().warning("Stray explicit reference", loc);
mExplicit.clear();
}
2010-08-22 12:47:56 +02:00
start();
mTokenLoc = loc;
int optionals = parseArguments(argumentType, scanner);
2010-08-22 12:47:56 +02:00
extensions->generateFunctionCode(keyword, mCode, mLiterals, mExplicit, optionals);
mOperands.push_back(returnType);
mExplicit.clear();
mRefOp = false;
2010-08-22 12:47:56 +02:00
mNextOperand = false;
return true;
}
}
}
2010-08-22 12:47:56 +02:00
return Parser::parseKeyword(keyword, loc, scanner);
}
2010-08-22 12:47:56 +02:00
if (mNextOperand)
2010-08-22 12:47:56 +02:00
{
// check for custom extensions
if (const Extensions* extensions = getContext().getExtensions())
{
2010-08-22 12:47:56 +02:00
start();
char returnType;
std::string argumentType;
bool hasExplicit = false;
2010-08-22 12:47:56 +02:00
if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit))
{
mTokenLoc = loc;
int optionals = parseArguments(argumentType, scanner);
2010-08-22 12:47:56 +02:00
extensions->generateFunctionCode(keyword, mCode, mLiterals, "", optionals);
mOperands.push_back(returnType);
2010-08-22 12:47:56 +02:00
mNextOperand = false;
return true;
}
}
}
else
{
scanner.putbackKeyword(keyword, loc);
return false;
}
2010-08-22 12:47:56 +02:00
return Parser::parseKeyword(keyword, loc, scanner);
}
bool ExprParser::parseSpecial(int code, const TokenLoc& loc, Scanner& scanner)
{
if (!mExplicit.empty())
{
2014-02-15 12:50:40 +01:00
if (mRefOp && code == Scanner::S_open)
{
/// \todo add option to disable this workaround
mOperators.push_back('(');
mTokenLoc = loc;
return true;
}
if (!mRefOp && code == Scanner::S_ref)
{
mRefOp = true;
return true;
}
2010-08-22 12:47:56 +02:00
if (!mMemberOp && code == Scanner::S_member)
{
mMemberOp = true;
return true;
}
return Parser::parseSpecial(code, loc, scanner);
}
2010-08-22 12:47:56 +02:00
mFirst = false;
2010-08-22 12:47:56 +02:00
if (code == Scanner::S_newline)
{
// end marker
if (mTokenLoc.mLiteral.empty())
mTokenLoc = loc;
scanner.putbackSpecial(code, loc);
return false;
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
if (code == Scanner::S_minus && mNextOperand)
{
// unary
mOperators.push_back('m');
mTokenLoc = loc;
return true;
}
if (code == Scanner::S_plus && mNextOperand)
{
// Also unary, but +, just ignore it
mTokenLoc = loc;
return true;
2010-06-29 16:11:19 +02:00
}
2010-08-22 12:47:56 +02:00
if (code == Scanner::S_open)
2010-06-29 16:24:54 +02:00
{
if (mNextOperand)
{
mOperators.push_back('(');
mTokenLoc = loc;
return true;
}
else
{
scanner.putbackSpecial(code, loc);
return false;
}
2010-06-29 16:24:54 +02:00
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:24:54 +02:00
if (code == Scanner::S_close && !mNextOperand)
{
2010-06-30 14:08:59 +02:00
if (isOpen())
{
close();
return true;
}
2010-08-22 12:47:56 +02:00
2010-06-30 14:08:59 +02:00
mTokenLoc = loc;
scanner.putbackSpecial(code, loc);
return false;
2010-06-29 16:24:54 +02:00
}
2010-08-22 12:47:56 +02:00
if (!mNextOperand)
{
mTokenLoc = loc;
2010-07-01 12:19:52 +02:00
char c = 0; // comparison
2010-08-22 12:47:56 +02:00
switch (code)
{
case Scanner::S_plus:
c = '+';
break;
case Scanner::S_minus:
c = '-';
break;
case Scanner::S_mult:
pushBinaryOperator('*');
return true;
case Scanner::S_div:
pushBinaryOperator('/');
return true;
2010-07-01 12:19:52 +02:00
case Scanner::S_cmpEQ:
c = 'e';
break;
case Scanner::S_cmpNE:
c = 'n';
break;
case Scanner::S_cmpLT:
c = 'l';
break;
case Scanner::S_cmpLE:
c = 'L';
break;
case Scanner::S_cmpGT:
c = 'g';
break;
case Scanner::S_cmpGE:
c = 'G';
break;
}
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
if (c)
{
if (mArgument && !isOpen())
{
// expression ends here
// Thank you Morrowind for this rotten syntax :(
scanner.putbackSpecial(code, loc);
return false;
}
2010-08-22 12:47:56 +02:00
2010-07-01 12:19:52 +02:00
pushBinaryOperator(c);
return true;
}
}
2010-08-22 12:47:56 +02:00
return Parser::parseSpecial(code, loc, scanner);
}
2010-08-22 12:47:56 +02:00
void ExprParser::reset()
{
mOperands.clear();
mOperators.clear();
mNextOperand = true;
2010-06-29 16:11:19 +02:00
mCode.clear();
mFirst = true;
mExplicit.clear();
mRefOp = false;
mMemberOp = false;
2010-08-22 12:47:56 +02:00
Parser::reset();
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
char ExprParser::append(std::vector<Interpreter::Type_Code>& code)
{
if (mOperands.empty() && mOperators.empty())
2010-06-29 16:11:19 +02:00
{
2019-03-29 00:59:26 +03:00
getErrorHandler().error("Missing expression", mTokenLoc);
2010-06-29 16:11:19 +02:00
return 'l';
}
2010-08-22 12:47:56 +02:00
2010-06-29 16:11:19 +02:00
if (mNextOperand || mOperands.empty())
{
2019-03-29 00:59:26 +03:00
getErrorHandler().error("Syntax error in expression", mTokenLoc);
2010-06-29 16:11:19 +02:00
return 'l';
}
2010-06-29 16:11:19 +02:00
while (!mOperators.empty())
pop();
2010-06-29 16:11:19 +02:00
std::copy(mCode.begin(), mCode.end(), std::back_inserter(code));
assert(mOperands.size() == 1);
return mOperands[0];
2010-08-22 12:47:56 +02:00
}
int ExprParser::parseArguments(const std::string& arguments, Scanner& scanner,
std::vector<Interpreter::Type_Code>& code, int ignoreKeyword, bool expectNames)
2010-07-01 16:40:03 +02:00
{
2010-08-22 12:47:56 +02:00
bool optional = false;
int optionalCount = 0;
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
ExprParser parser(getErrorHandler(), getContext(), mLocals, mLiterals, true);
StringParser stringParser(getErrorHandler(), getContext(), mLiterals);
DiscardParser discardParser(getErrorHandler(), getContext());
JunkParser junkParser(getErrorHandler(), getContext(), ignoreKeyword);
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
std::stack<std::vector<Interpreter::Type_Code>> stack;
2010-08-22 12:47:56 +02:00
for (char argument : arguments)
2010-07-01 16:40:03 +02:00
{
if (argument == '/')
2010-08-22 12:47:56 +02:00
{
optional = true;
}
else if (argument == 'S' || argument == 'c' || argument == 'x')
2010-07-01 16:40:03 +02:00
{
stringParser.reset();
2010-08-22 12:47:56 +02:00
if (optional || argument == 'x')
2010-08-22 12:47:56 +02:00
stringParser.setOptional(true);
if (argument == 'c')
stringParser.smashCase();
if (argument == 'x')
stringParser.discard();
scanner.enableExpectName();
2010-08-22 12:47:56 +02:00
scanner.scan(stringParser);
if ((optional || argument == 'x') && stringParser.isEmpty())
2010-08-22 12:47:56 +02:00
break;
if (argument != 'x')
2010-07-01 16:40:03 +02:00
{
std::vector<Interpreter::Type_Code> tmp;
stringParser.append(tmp);
2010-08-22 12:47:56 +02:00
stack.push(tmp);
2010-08-22 12:47:56 +02:00
if (optional)
++optionalCount;
}
else
2019-03-29 00:59:26 +03:00
getErrorHandler().warning("Extra argument", stringParser.getTokenLoc());
2010-07-01 16:40:03 +02:00
}
else if (argument == 'X')
2014-07-21 12:50:29 +02:00
{
parser.reset();
parser.setOptional(true);
scanner.scan(parser);
if (parser.isEmpty())
break;
else
2019-03-29 00:59:26 +03:00
getErrorHandler().warning("Extra argument", parser.getTokenLoc());
}
else if (argument == 'z')
{
discardParser.reset();
discardParser.setOptional(true);
scanner.scan(discardParser);
if (discardParser.isEmpty())
2014-07-21 12:50:29 +02:00
break;
else
2019-03-29 00:59:26 +03:00
getErrorHandler().warning("Extra argument", discardParser.getTokenLoc());
2014-07-21 12:50:29 +02:00
}
else if (argument == 'j')
{
/// \todo disable this when operating in strict mode
junkParser.reset();
scanner.scan(junkParser);
}
2010-07-01 16:40:03 +02:00
else
{
2010-08-22 12:47:56 +02:00
parser.reset();
2014-07-21 12:50:29 +02:00
if (optional)
2010-08-22 12:47:56 +02:00
parser.setOptional(true);
if (expectNames)
scanner.enableExpectName();
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
scanner.scan(parser);
2010-08-22 12:47:56 +02:00
if (optional && parser.isEmpty())
break;
2014-07-21 12:50:29 +02:00
std::vector<Interpreter::Type_Code> tmp;
2010-07-01 16:40:03 +02:00
2014-07-21 12:50:29 +02:00
char type = parser.append(tmp);
2010-07-01 16:40:03 +02:00
if (type != argument)
Generator::convert(tmp, type, argument);
2010-08-22 12:47:56 +02:00
2014-07-21 12:50:29 +02:00
stack.push(tmp);
2010-08-22 12:47:56 +02:00
2014-07-21 12:50:29 +02:00
if (optional)
++optionalCount;
2010-07-01 16:40:03 +02:00
}
}
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
while (!stack.empty())
{
std::vector<Interpreter::Type_Code>& tmp = stack.top();
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
std::copy(tmp.begin(), tmp.end(), std::back_inserter(code));
2010-08-22 12:47:56 +02:00
2010-07-01 16:40:03 +02:00
stack.pop();
}
2010-08-22 12:47:56 +02:00
return optionalCount;
}
const TokenLoc& ExprParser::getTokenLoc() const
{
return mTokenLoc;
}
2010-08-22 12:47:56 +02:00
}