memset(tabs, '\t', sizeof tabs); \
tabs[(unsigned) indent < sizeof tabs ? indent : sizeof tabs - 1] = '\0'
+/**
+ * @brief Writes a map into a file.
+ *
+ * The output is sorted by key.
+ *
+ * @param fp target file
+ * @param map map to dump
+ * @param withEndOfLine true: '\n' will be written at the end
+ */
+void dumpMap(FILE* fp, RplASMapOfVariants& map, bool withEndOfLine)
+{
+ QList<QString> sorted;
+ sorted.reserve(map.size());
+ RplASMapOfVariants::iterator it;
+ for (it = map.begin(); it != map.end(); it++){
+ sorted.append(it.key());
+ }
+ qSort(sorted.begin(), sorted.end(), qLess<QString>());
+ QList<QString>::iterator it2;
+ bool first = true;
+ for (it2 = sorted.begin(); it2 != sorted.end(); it2++){
+ RplASVariant* value = map[*it2];
+ fprintf(fp, "%c'%s':%s", first ? '{' : ',', (*it2).toUtf8().constData(),
+ value->toString().toUtf8().constData());
+ first = false;
+ }
+ fprintf(fp, "%s}%s", first ? "{" : "", withEndOfLine ? "\n" : "");
+}
+
/** @class RplASException rplastree.hpp "rplexpr/rplastree.hpp"
*
* @brief Implements a specific exception for the Abstract Syntax Tree.
return m_value;
}
+/** @class RplASMapConstant rplastree.hpp "rplexpr/rplastree.hpp"
+ *
+ * @brief Implements a hash map for constant list entries.
+ *
+ */
+/**
+ * @brief RplASMapConstant::RplASMapConstant
+ */
+RplASMapConstant::RplASMapConstant() :
+ RplASNode1(AST_MAP_CONSTANT),
+ RplASCalculable(),
+ m_value()
+{
+ m_value.setObject(new RplASMapOfVariants, &RplASMap::m_instance);
+}
+
+/**
+ * @brief Calculates the value.
+ *
+ * @param value OUT: the value of the instance
+ */
+void RplASMapConstant::calc(RplASVariant&)
+{
+
+}
+/**
+ * @brief Writes the internals into a file.
+ *
+ * @param fp target file
+ * @param indent nesting level
+ */
+void RplASMapConstant::dump(FILE* fp, int indent)
+{
+ DEFINE_TABS(indent);
+ char buffer[256];
+ fprintf(fp, "%smapConst id: %d %s\n%s", tabs,
+ m_id, positionStr(buffer, sizeof buffer), tabs);
+ dumpMap(fp, *map(), true);
+}
+
+
+/**
+ * @brief Returns the value of the constant, containing a map.
+ *
+ * @return the variant value
+ */
+RplASVariant& RplASMapConstant::value()
+{
+ return m_value;
+}
+
+/**
+ * @brief Returns the (low level) map of the constant.
+ *
+ * @return the map of the constant
+ */
+RplASMapOfVariants* RplASMapConstant::map()
+{
+ RplASMapOfVariants* rc = static_cast<RplASMapOfVariants*>(
+ m_value.asObject(NULL));
+ return rc;
+}
/** @class RplASNamedValue rplastree.hpp "rplexpr/rplastree.hpp"
*
* @brief Implements a named values, a constant or a variable
*
+ * <code>m_child</code>: NULL or the index expression (map/list)
*/
/**
*
* @param value OUT: the value of the instance
*/
-void RplASNamedValue::calc(RplASVariant& value)
+void RplASNamedValue::calc(RplASVariant& )
{
}
*
* @param value OUT: the calculated value
*/
-void RplASExprStatement::calc(RplASVariant& value)
+void RplASExprStatement::calc(RplASVariant& )
{
}
if (m_child2 != NULL){
var = dynamic_cast<RplASNamedValue*>(m_child2);
}
- for(int ii = start; ii <= end; ii++){
+ for(int ii = start; ii <= end; ii += step){
body->execute();
}
}
RplASNode2(AST_ARGUMENT)
{
}
+
+
L_PARSE_REPEAT_NO_SEMI,
L_PARSE_BODY_WRONG_ITEM,
L_PARSE_FOR_NO_TO = 2020,
- L_PARSE_LIST_NO_COMMA
-
+ L_PARSE_LIST_NO_COMMA,
+ L_PARSE_MAP_BOOL,
+ L_PARSE_MAP_NONE,
+ L_PARSE_MAP_NUMERIC,
+ L_PARSE_MAP_EXPR = 2025,
+ L_PARSE_MAP_EXPR2,
+ L_PARSE_MAP_NO_COLON,
+ L_PARSE_MAP_NO_COMMA,
+ L_PARSE_OPERAND_NOT_OPERAND = 2030,
+ L_PARSE_BODY_NO_START,
+ L_PARSE_OPERAND_NO_BRACKET
};
/** @class RplMFParser rpllexer.hpp "rplexpr/rplmfparser.hpp"
}
if (! m_lexer.currentToken()->isKeyword(K_FI))
syntaxError(L_PARSE_IF_NO_FI, "'fi' expected");
+ m_lexer.nextNonSpaceToken();
return rc;
}
/**
rc->setChild(body);
if (! m_lexer.currentToken()->isKeyword(K_OD))
syntaxError(L_PARSE_WHILE_NO_OD, "'od' expected");
+ m_lexer.nextNonSpaceToken();
return rc;
}
if (! m_lexer.currentToken()->isOperator(O_SEMICOLON))
syntaxError(L_PARSE_REPEAT_NO_SEMI, "';' expected");
rc->setChild2(condition);
+ m_lexer.nextNonSpaceToken();
return rc;
}
*
* for <var> in <iterable_expr> do <body> od
*
+ * @post the token behind the do is read
+ * @return the abstract syntax tree of the for statement
*/
RplASItem* RplMFParser::parseFor()
{
if (token->isKeyword(K_FROM)){
node->setChild3(parseExpr(0));
}
- if (! token->isKeyword(K_TO)){
+ if (! m_lexer.currentToken()->isKeyword(K_TO)){
syntaxError(L_PARSE_FOR_NO_TO, "'to' expected");
}
node->setChild4(parseExpr(0));
- if (token->isKeyword(K_STEP)){
+ if (m_lexer.currentToken()->isKeyword(K_STEP)){
node->setChild5(parseExpr(0));
}
}
- if (! token->isKeyword(K_DO))
+ if (! m_lexer.currentToken()->isKeyword(K_DO))
syntaxError(L_PARSE_FOR_NO_TO, "'to' expected");
rc->setChild(parseBody(K_OD));
+ m_lexer.nextNonSpaceToken();
return rc;
}
if (! token->isOperator(O_SEMICOLON)){
syntaxError(L_DEFINITION_NO_SEMICOLON, "';' expected");
}
+ m_lexer.nextNonSpaceToken();
return rc;
}
+/**
+ * @brief Reads the current tokens as an formula and returns the variant.
+ *
+ * @post the token behind the formula is read
+ * @param parent the parent for the formula: because of the destroying
+ * the new expression is chained into the parent
+ * @return the variant containing the formula
+ */
+RplASVariant* RplMFParser::createFormula(RplASNode1* parent)
+{
+ RplASVariant* variant = NULL;
+ m_lexer.undoLastToken2();
+ RplASExprStatement* expr = dynamic_cast<RplASExprStatement*>
+ (parseExprStatement(false));
+ if (expr != NULL){
+ // chaining per m_successor is for freeing while destruction:
+ expr->setSuccessor(parent->child());
+ parent->setChild(expr);
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ variant->setObject(expr, &RplASFormula::m_instance);
+ }
+ return variant;
+}
+
+/**
+ * @brief Converts the current expression into a <code>RplASVariant</code>.
+ *
+ * If the expression is a constant, the constant value will be the content
+ * of the variant. Otherwise a formula will be stored.
+ *
+ * @pre the first token of the variant expression is read
+ * @post the token behind the variant expression is read
+ * @param token the token to convert
+ * @param endsWithComma true: the 2nd token is a ','
+ * @param parent the parent node of the expression
+ * @return the variant with the token's content
+ */
+RplASVariant* RplMFParser::tokenToVariant(RplToken* token,
+ bool endsWithComma, RplASNode1* parent)
+{
+ RplASVariant* variant = NULL;
+ if (endsWithComma){
+ switch(token->tokenType()){
+ case TOKEN_NUMBER:
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ variant->setInt(token->asInteger());
+ break;
+ case TOKEN_STRING:
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ variant->setString(token->toString());
+ break;
+ case TOKEN_REAL:
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ variant->setFloat(token->asReal());
+ break;
+ case TOKEN_KEYWORD:
+ switch(token->id()){
+ case K_TRUE:
+ case K_FALSE:
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ variant->setBool(token->id() == K_TRUE);
+ break;
+ case K_NONE:
+ // freed in the destructor of varList (~RplASVariant()):
+ variant = new RplASVariant();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (variant == NULL)
+ variant = createFormula(parent);
+ return variant;
+}
+
/**
* @brief Parses a list.
*
* Syntax:<br>
* '[' [<expr_1> [ ',' <expr_2> ...]] ']'
*
- * @precondition '[' is the current token
- * @postcondition the token behind the ']' is read
+ * @pre '[' is the current token
+ * @post the token behind the ']' is read
* @return a node of the abstract syntax tree
*/
RplASItem* RplMFParser::parseList()
bool again = true;
RplToken* token;
RplToken* token2;
- while(again){
- token = m_lexer.nextNonSpaceToken();
- if (token->isOperator(O_RBRACKET))
- again = false;
- else{
- variant = NULL;
+ // read the token behind '[':
+ token = m_lexer.nextNonSpaceToken();
+ if (token->isOperator(O_RBRACKET)){
+ // read token behind ']':
+ m_lexer.nextNonSpaceToken();
+ } else{
+ while(again){
m_lexer.saveLastToken();
token2 = m_lexer.nextNonSpaceToken();
- if (token2->isOperator(O_COMMA)){
- switch(token->tokenType()){
- case TOKEN_NUMBER:
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- variant->setInt(token->asInteger());
- break;
- case TOKEN_STRING:
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- variant->setString(token->toString());
- break;
- case TOKEN_REAL:
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- variant->setFloat(token->asReal());
- break;
- case TOKEN_KEYWORD:
- switch(token->id()){
- case K_TRUE:
- case K_FALSE:
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- variant->setBool(token->id() == K_TRUE);
- break;
- case K_NONE:
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- break;
- default:
- break;
- }
- break;
- default:
- break;
- }
- }
- if (variant == NULL && ! token->isOperator(O_RBRACKET)){
- m_lexer.undoLastToken2();
- RplASExprStatement* expr = dynamic_cast<RplASExprStatement*>
- (parseExprStatement());
- if (expr != NULL){
- // chaining per m_successor is for freeing in destruction:
- expr->setSuccessor(rc->child());
- rc->setChild(expr);
- // freed in the destructor of varList (~RplASVariant()):
- variant = new RplASVariant();
- variant->setObject(expr, &RplASFormula::m_instance);
- token = m_lexer.currentToken();
- if (token->isOperator(O_RBRACKET))
- again = false;
- else if (! token->isOperator(O_COMMA))
- syntaxError(L_PARSE_LIST_NO_COMMA, "',' or ']' expected");
- }
- }
+ variant = tokenToVariant(token, token2->isOperator(O_COMMA), rc);
+ token = m_lexer.currentToken();
+ if (token->isOperator(O_RBRACKET))
+ again = false;
+ else if (! token->isOperator(O_COMMA))
+ syntaxError(L_PARSE_LIST_NO_COMMA, "',' or ']' expected");
+ // read token behind ',' or ']'
+ token = m_lexer.nextNonSpaceToken();
if (variant != NULL)
list->append(variant);
}
return rc;
}
+
/**
* @brief Parses a map.
*
* Syntax:<br>
* '{' [ <string_expr1> ':' <expr_1> [ ',' <string_expr1> ': <expr_2> ...]] '}'
*
- * @precondition '{' is the current token
- * @postcondition the token behind the '}' is read
+ * @pre '{' is the current token
+ * @post the token behind the '}' is read
* @return a node of the abstract syntax tree
*/
RplASItem* RplMFParser::parseMap()
{
- return NULL;
+ RplASMapConstant* rc = new RplASMapConstant();
+ RplASVariant& varMap = rc->value();
+ RplASMapOfVariants* map = static_cast<RplASMapOfVariants*>
+ (varMap.asObject(NULL));
+ rc->setPosition(m_lexer.currentPosition());
+ RplASVariant* variant;
+ bool again = true;
+ RplToken* token;
+ RplToken* token2;
+ QString key;
+ while(again){
+ token = m_lexer.nextNonSpaceToken();
+ if (token->isOperator(O_RBRACE))
+ again = false;
+ else{
+ key.clear();
+ switch(token->tokenType()){
+ case TOKEN_STRING:
+ // freed in the destructor of varList (~RplASVariant()):
+ key = token->toString();
+ break;
+ case TOKEN_KEYWORD:
+ switch(token->id()){
+ case K_TRUE:
+ case K_FALSE:
+ syntaxError(L_PARSE_MAP_BOOL,
+ "boolean value not allowed as key type. Use a string");
+ break;
+ case K_NONE:
+ syntaxError(L_PARSE_MAP_NONE,
+ "'none' is not allowed as key type. Use a string");
+ break;
+ default:
+ syntaxError(L_PARSE_MAP_EXPR,
+ "a non constant expression is not allowed as key type. Use a string");
+ break;
+ }
+ break;
+ case TOKEN_NUMBER:
+ case TOKEN_REAL:
+ syntaxError(L_PARSE_MAP_NUMERIC,
+ "numeric values not allowed as key type. Use a string");
+ break;
+ default:
+ syntaxError(L_PARSE_MAP_EXPR2,
+ "a non constant expression is not allowed as key type. Use a string");
+ break;
+ }
+ token = m_lexer.nextNonSpaceToken();
+ if (! token->isOperator(O_COLON)){
+ syntaxError(L_PARSE_MAP_NO_COLON, "':' expected");
+ } else {
+ token = m_lexer.nextNonSpaceToken();
+ m_lexer.saveLastToken();
+ token2 = m_lexer.nextNonSpaceToken();
+ variant = tokenToVariant(token, token2->isOperator(O_COMMA), rc);
+ (*map)[key] = variant;
+ variant = NULL;
+ token = m_lexer.currentToken();
+ if (token->isOperator(O_RBRACE))
+ again = false;
+ else if (! token->isOperator(O_COMMA))
+ syntaxError(L_PARSE_MAP_NO_COMMA, "',' or '}' expected");
+ }
+ }
+ }
+ m_lexer.nextNonSpaceToken();
+ return rc;
}
/**
* An operand is the first and the third part of a binary operation.
* Examples: constant, variable, method call, an expression in parentheses
*
+ * @post the token behind the operand is read
* @return the node with the operand
*/
RplASItem* RplMFParser::parseOperand(int level)
RplToken* token = m_lexer.nextNonSpaceToken();
const RplSourcePosition* startPosition = m_lexer.currentPosition();
RplASItem* rc = NULL;
+ bool readNext = true;
switch(token->tokenType()){
case TOKEN_OPERATOR:
{
Operator opId = (Operator) token->id();
if (opId == O_LBRACKET){
rc = parseList();
+ readNext = false;
} else if (opId == O_LBRACE){
rc = parseMap();
+ readNext = false;
} else if (opId == O_LPARENTH){
rc = parseExpr(level + 1);
token = m_lexer.currentToken();
RplASUnaryOp* op = new RplASUnaryOp(token->id(), AST_PRE_UNARY_OP);
op->setPosition(m_lexer.currentPosition());
op->setChild(parseOperand(level));
+ readNext = false;
rc = op;
- }
+ } else
+ syntaxError(L_PARSE_OPERAND_NOT_OPERAND,
+ "operand expected, not an operator");
break;
}
case TOKEN_STRING:
if (token->tokenType() != TOKEN_OPERATOR){
RplASNamedValue* var = new RplASNamedValue(name);
rc = var;
- m_lexer.undoLastToken();
+ readNext = false;
} else {
if (token->id() == O_LPARENTH){
- RplASArgument* args = parseArguments();
RplASMethodCall* call = new RplASMethodCall();
+ call->setPosition(m_lexer.currentPosition());
rc = call;
+ RplASArgument* args = parseArguments();
call->setArg1(args);
token = m_lexer.nextNonSpaceToken();
- if (token->tokenType() != TOKEN_OPERATOR
- || token->id() != O_RPARENT){
+ if (! token->isOperator(O_RPARENT)){
QByteArray pos = startPosition->toString().toUtf8();
// this call never comes back (exception!)
syntaxError(L_PARSE_OPERAND_RPARENTH_FUNC,
"')' expected. '(' is at %s", pos.constData());
}
- } else if (token->id() == O_LBRACKET){
-
} else {
RplASNamedValue* var = new RplASNamedValue(name);
var->setPosition(startPosition);
rc = var;
- if (token->id() == O_INC || token->id() == O_DEC){
- RplASUnaryOp* op = new RplASUnaryOp(token->id(),
- AST_POST_UNARY_OP);
- op->setChild(var);
- rc = op;
+ if (token->id() == O_LBRACKET){
+ RplASItem indexExpr = parseExpr(0);
+ if (! m_lexer.currentToken()->isOperator(O_RBRACKET))
+ syntaxError(L_PARSE_OPERAND_NO_BRACKET, "']' expected");
+ var->setChild(indexExpr);
} else {
- m_lexer.undoLastToken();
+ if (token->id() == O_INC || token->id() == O_DEC){
+ RplASUnaryOp* op = new RplASUnaryOp(token->id(),
+ AST_POST_UNARY_OP);
+ op->setChild(var);
+ rc = op;
+ } else {
+ readNext = false;
+ }
}
}
}
break;
}
case TOKEN_END_OF_SOURCE:
+ readNext = false;
break;
default:
// this call never comes back (exception!)
"unexpected symbol detected. Operand expected");
break;
}
+ if (readNext)
+ m_lexer.nextNonSpaceToken();
return rc;
}
* expr with level 1: 3*7-2<br>
* expr with level 0: a + expr1<br>
*
- * @precond the nextNonSpaceToken() will return the first token of the expr.
- * @postcond the next token behind the expression is read
+ * @pre the nextNonSpaceToken() will return the first token of the expr.
+ * @post the token behind the expression is read
*
- * @param depth the level of the parenthesis
- * @return the abstract syntax tree representing the parsed expression
+ * @param depth the level of the parenthesis
+ * @return the abstract syntax tree representing the parsed expression
*/
RplASItem* RplMFParser::parseExpr(int depth){
RplToken* token;
int lastPrio = INT_MAX;
bool again = true;
do {
- token = m_lexer.nextNonSpaceToken();
- RplTokenType tokenType = token->tokenType();
- switch(tokenType){
+ token = m_lexer.currentToken();
+ switch(token->tokenType()){
case TOKEN_OPERATOR:
{
Operator opId = (Operator) token->id();
/**
* @brief Parses an expression as a statement.
*
- * @precond the nextNonSpaceToken() will return the first token of the expr.
- * @postcond all tokens belonging to the expr are read (not more!)
- *
- * @return the abstract syntax tree of the expression statement
+ * @pre the nextNonSpaceToken() will return the first token of the expr.<br>
+ * Note: a ';' is part of the expression statement
+ * @post the token behind the expression is read
+ * @param eatSemicolon true: a trailing ';' will be read
+ * @return the abstract syntax tree of the expression statement
*/
-RplASItem* RplMFParser::parseExprStatement()
+RplASItem* RplMFParser::parseExprStatement(bool eatSemicolon)
{
RplASItem* item = parseExpr(0);
RplASExprStatement* statement = NULL;
statement->setPosition(item->position());
statement->setChild(item);
}
+ if (eatSemicolon && m_lexer.currentToken()->isOperator(O_SEMICOLON))
+ m_lexer.nextNonSpaceToken();
return statement;
}
RplASItem* body = NULL;
RplASStatement* lastStatement = NULL;
bool again = true;
- while(again) {
+ const RplSourcePosition* lastPos = NULL;
+ do {
token = m_lexer.currentToken();
+ if (lastPos == m_lexer.currentPosition())
+ syntaxError(L_PARSE_BODY_NO_START, "no statement starts with this symbol");
+ lastPos = m_lexer.currentPosition();
// eat a superflous ';'
if (token->isOperator(O_SEMICOLON))
token = m_lexer.nextNonSpaceToken();
|| (opStop != O_UNDEF
&& token->isOperator(opStop, opStop2)))
again = false;
- else
- token = m_lexer.nextNonSpaceToken();
}
} catch(RplSyntaxError exc){
// we look for the end of the statement:
token = m_lexer.nextNonSpaceToken();
}
}
- }
+ } while(again);
return body;
}
/**
* @brief Parses a module.
*
- * @precond the first char of the module is the next char to read.
- * @postcond the total module is read
+ * @pre the first char of the module is the next char to read.
+ * @post the total module is read
*
* @param name the name of the module (without path)
*/