*/
RplSymbolSpace::~RplSymbolSpace()
{
- QMap<QString, RplVariable*>::iterator it;
- for (it = m_variables.begin(); it != m_variables.end(); it++){
+ ClassMap::iterator it;
+ for (it = m_classes.begin(); it != m_classes.end(); it++){
delete it.value();
}
- for (it = m_variables.begin(); it != m_variables.end(); it++){
- delete it.value();
+ MethodMap::iterator it2;
+ for (it2 = m_methods.begin(); it2 != m_methods.end(); it2++){
+ delete it2.value();
}
}
clazz->dump(fp, indent);
}
- fprintf(fp, "%s== Variables:\n", tabs);
+ fprintf(fp, "%s== Methods:\n", tabs);
sorted.clear();
sorted.reserve(m_variables.size());
- VariableMap::iterator it3;
- for (it3 = m_variables.begin(); it3 != m_variables.end(); it3++){
+ MethodMap::iterator it3;
+ for (it3 = m_methods.begin(); it3 != m_methods.end(); it3++){
sorted.append(it3.key());
}
qSort(sorted.begin(), sorted.end(), qLess<QString>());
QList<QString>::iterator it4;
for (it4 = sorted.begin(); it4 != sorted.end(); it4++){
- RplVariable* var = m_variables[*it4];
+ RplASMethod* var = m_methods[*it4];
+ var->dump(fp, indent);
+ }
+
+ fprintf(fp, "%s== Variables:\n", tabs);
+ sorted.clear();
+ sorted.reserve(m_variables.size());
+ VariableMap::iterator it5;
+ for (it5 = m_variables.begin(); it5 != m_variables.end(); it5++){
+ sorted.append(it5.key());
+ }
+ qSort(sorted.begin(), sorted.end(), qLess<QString>());
+ QList<QString>::iterator it6;
+ for (it6 = sorted.begin(); it6 != sorted.end(); it6++){
+ RplASVarDefinition* var = m_variables[*it6];
var->dump(fp, indent);
}
+
fprintf(fp, "%s== Body:%s\n", tabs, m_body == NULL ? " <none>" : "");
RplASNode1::dumpStatements(fp, indent, m_body);
}
}
return rc;
}
+/**
+ * @brief Returns the parent of the symbol space.
+ *
+ * @return the symbolspace of the object (module, method, class..) containing
+ * the object belonging to the instance
+ */
+RplSymbolSpace* RplSymbolSpace::parent() const
+{
+ return m_parent;
+}
+
/**
* @brief Returns the body (a abstract syntax tree) of the symbol space.
*
m_body = body;
}
+/**
+ * @brief Adds a variable to the symbol space.
+ *
+ * @param variable the variable to add
+ * @return NULL: success<br>
+ * otherwise: the already defined variable/method
+ */
+RplASItem* RplSymbolSpace::addVariable(RplASVarDefinition* variable)
+{
+ RplASItem* rc = NULL;
+ const QString& name = variable->name();
+ if (m_variables.contains(name))
+ rc = m_variables[name];
+ else if (m_methods.contains(name))
+ rc = m_methods[name];
+ else {
+ m_variables[name] = variable;
+ }
+ return rc;
+}
+
+/**
+ * @brief Adds a method to the symbol space.
+ *
+ * @param method the method to add
+ * @return NULL: success<br>
+ * otherwise: the already defined variable/method
+ */
+RplASItem* RplSymbolSpace::addMethod(RplASMethod* method)
+{
+ RplASItem* rc = NULL;
+ const QString& name = method->name();
+ if (m_variables.contains(name))
+ rc = m_variables[name];
+ else if (! m_methods.contains(name)){
+ m_methods[name] = method;
+ } else {
+ RplASMethod* oldMethod = m_methods[name];
+ do {
+ if (oldMethod->equalSignature(*method))
+ rc = oldMethod;
+ else
+ oldMethod = oldMethod->sibling();
+ } while (rc == NULL && oldMethod != NULL);
+ if (rc == NULL)
+ m_methods[name] = method;
+ }
+ return rc;
+}
+
/**
* @brief Returns the name of the symbol space.
*
* @return the name
*/
-QString RplSymbolSpace::name() const
+const QString& RplSymbolSpace::name() const
{
return m_name;
}
};
public:
- typedef QMap<QString, RplVariable*> VariableMap;
+ typedef QMap<QString, RplASVarDefinition*> VariableMap;
typedef QMap<QString, RplASClass*> ClassMap;
+ typedef QMap<QString, RplASMethod*> MethodMap;
public:
RplSymbolSpace(SymbolSpaceType type, const QString& name,
RplSymbolSpace* parent);
- ~RplSymbolSpace();
+ virtual ~RplSymbolSpace();
public:
RplVariable* findVariable(const QString& name) const;
RplASClass* findClass(const QString& name) const;
void dump(FILE* fp, int indent, const char* header = NULL);
- QString name() const;
+ const QString& name() const;
RplASItem* body() const;
void setBody(RplASItem* body);
+ RplASItem* addVariable(RplASVarDefinition* variable);
+ RplASItem* addMethod(RplASMethod* method);
+ RplASClass* addClass(RplASClass* clazz);
public:
static void initGlobal(RplSymbolSpace& global);
static const char* spaceTypeName(SymbolSpaceType type);
QString m_name;
VariableMap m_variables;
ClassMap m_classes;
+ MethodMap m_methods;
RplSymbolSpace* m_parent;
RplASItem* m_body;
public:
static RplSymbolSpace m_global;
+ RplSymbolSpace* parent() const;
};
class RplASBoolean : public RplASClass {
*
* @return the name
*/
-QString RplASNamedValue::name() const
+const QString& RplASNamedValue::name() const
{
return m_name;
}
m_name.toUtf8().constData(), m_id, m_attributes,
positionStr(buffer, sizeof buffer));
}
+/**
+ * @brief Returns the data type (class) of the instance.
+ *
+ * @return the data type
+ */
+RplASClass* RplASNamedValue::dataType() const
+{
+ return m_dataType;
+}
/** @class RplASIndexedValue rplastree.hpp "rplexpr/rplastree.hpp"
*
m_child3->dump(fp, indent + 1);
}
+/**
+ * @brief Returns the name of the variable.
+ *
+ * @return the name
+ */
+const QString& RplASVarDefinition::name() const
+{
+ RplASNamedValue* namedValue = dynamic_cast<RplASNamedValue*>(m_child2);
+ const QString& rc = namedValue == NULL ? "?" : namedValue->name();
+ return rc;
+}
+
+/**
+ * @brief Returns the data type (class) of the variable.
+ *
+ * @return the data type
+ */
+RplASClass* RplASVarDefinition::datatype() const
+{
+ RplASNamedValue* namedValue = dynamic_cast<RplASNamedValue*>(m_child2);
+ RplASClass* rc = namedValue->dataType();
+ return rc;
+}
+
/**
* @brief Executes the statement.
*/
{
}
+/**
+ * @brief Destructor.
+ *
+ * Does nothing but forces a virtual destructor of all derived classes.
+ *
+ */
+RplASClass::~RplASClass()
+{
+
+}
+
/**
* @brief Return the class name.
*
/**
* @brief Handles the start of a new class definition.
- * @param name
+ * @param name name of the class/method
+ * @return the new symbol space
*/
-void RplASTree::startClassOrMethod(const QString& name,
+RplSymbolSpace* RplASTree::startClassOrMethod(const QString& name,
RplSymbolSpace::SymbolSpaceType type)
{
// the stack m_modules is never empty because of "global" and modules.
m_symbolSpaceHeap[fullName] = space;
m_symbolSpaces.append(space);
m_currentSpace = space;
+ return space;
}
/**
* @param name the method name
* @param type the method result type
*/
-RplASMethod::RplASMethod(const QString& name) :
+RplASMethod::RplASMethod(const QString& name, RplASTree& tree) :
RplASNode2(AST_METHOD),
m_name(name),
m_resultType(NULL),
- m_parent(NULL)
+ m_symbols(tree.startClassOrMethod(name, RplSymbolSpace::SST_METHOD)),
+ m_sibling(NULL),
+ m_tree(tree)
{
}
+
/**
* @brief Writes the internals of the instance into a file.
*
fprintf(fp, "%sMethod %s %s(", tabs,
m_resultType == NULL ? "<NoneType>" : m_resultType->name().toUtf8().constData(),
m_name.toUtf8().constData());
- fprintf(fp, ") id: %d parent: %d args: %d body: %d %s\n", m_id,
- m_parent == NULL ? 0 : m_parent->id(),
+ RplSymbolSpace* parent = m_symbols->parent();
+ fprintf(fp, ") id: %d parent: %s args: %d body: %d %s\n", m_id,
+ parent == NULL ? "" : parent->name().toUtf8().constData(),
m_child2 == NULL ? 0 : m_child2->id(),
m_child->id(),
positionStr(buffer, sizeof buffer));
if (m_child2 != NULL)
m_child2->dump(fp, indent + 1);
- m_child->dump(fp, indent + 1);
+ dumpStatements(fp, indent + 1, m_child);
+}
+RplSymbolSpace* RplASMethod::symbols() const
+{
+ return m_symbols;
}
/**
- * @brief Returns the parent of the method: a class or a method.
- * @return NULL: this is a function, not a method<br>
- * otherwise: the parent: a class or a method/func
+ * @brief Returns the name of the method
+ * @return the name
*/
-RplASItem* RplASMethod::parent() const
+
+const QString& RplASMethod::name() const
{
- return m_parent;
+ return m_name;
}
+
/**
- * @brief Sets the parent of the method.
+ * @brief Tests whether an other method has the same signature (parameterlist).
+ * @param other the method for comparison
+ * @return true: same signature<br>
+ * false: otherwise
+ */
+bool RplASMethod::equalSignature(RplASMethod& other) const
+{
+ bool rc = true;
+ RplASExprStatement* args = dynamic_cast<RplASExprStatement*>(m_child2);
+ RplASExprStatement* otherArgs = dynamic_cast<RplASExprStatement*>(other.child2());
+
+ while(rc && (args != NULL || otherArgs != NULL)){
+ if (args == NULL || otherArgs == NULL)
+ rc = false;
+ else {
+ RplASVarDefinition* def = dynamic_cast<RplASVarDefinition*>(args->child2());
+ RplASVarDefinition* defOther = dynamic_cast<RplASVarDefinition*>(otherArgs->child2());
+ if (def->datatype() != defOther->datatype())
+ rc = false;
+ }
+ }
+ return rc;
+}
+/**
+ * @brief Returns the next overloaded method.
*
- * @param parent the parent: NULL or a class or a method
+ * @return NULL: no other method available<br>
+ * otherwise: the next method with the same name but another signature
*/
-void RplASMethod::setParent(RplASItem* parent)
+RplASMethod* RplASMethod::sibling() const
{
- m_parent = parent;
+ return m_sibling;
}
+/**
+ * @brief Sets the next overloaded method.
+ *
+ * @param sibling another method with the same name but another signature
+ */
+void RplASMethod::setSibling(RplASMethod* sibling)
+{
+ m_sibling = sibling;
+}
/** @class RplASArgument rplastree.hpp "rplexpr/rplastree.hpp"
*
RplASNamedValue(RplASClass* dataType,
const QString& name, int attributes);
public:
- QString name() const;
+ const QString& name() const;
public:
virtual void calc(RplASVariant& value);
void dump(FILE* fp, int indent);
+ RplASClass* dataType() const;
+
protected:
QString m_name;
int m_attributes;
virtual void execute();
virtual void calc(RplASVariant& value);
void dump(FILE* fp, int indent);
+ const QString& name() const;
+ RplASClass* datatype() const;
};
class RplASExprStatement : public RplASNode2, public RplASStatement
class RplASClass;
+class RplSymbolSpace;
class RplASMethod : public RplASNode2
{
public:
- RplASMethod(const QString& name);
+ RplASMethod(const QString& name, RplASTree& tree);
public:
void execute();
void dump(FILE* fp, int indent);
- RplASItem* parent() const;
- void setParent(RplASItem* parent);
+ RplSymbolSpace* symbols() const;
+
+ const QString& name() const;
+ bool equalSignature(RplASMethod& other) const;
+ RplASMethod* sibling() const;
+ void setSibling(RplASMethod* sibling);
private:
QString m_name;
RplASClass* m_resultType;
- RplASItem* m_parent;
+ RplSymbolSpace* m_symbols;
+ // chain over all overloaded methods (same name, other signature):
+ RplASMethod* m_sibling;
+ RplASTree& m_tree;
};
class RplASClass {
typedef QMap<QString, RplASMethod*> MethodMap;
public:
RplASClass(const QString& name);
+ virtual ~RplASClass();
public:
/**
* @brief Creates a value object (used in RplASVariant).
public:
bool startModule(const QString& name);
void finishModule(const QString& name);
- void startClassOrMethod(const QString& name,
+ RplSymbolSpace* startClassOrMethod(const QString& name,
RplSymbolSpace::SymbolSpaceType type);
void finishClassOrMethod(const QString& name);
SymbolSpaceStack& symbolSpaces();
L_PARSE_PARAMLIST_NO_PARENTH,
L_PARSE_PARAMLIST_NO_PARENTH2,
L_PARSE_METH_NO_END = 2045,
- L_PARSE_METH_NO_END2
+ L_PARSE_METH_NO_END2,
+ L_PARSE_VAR_DEF_ALREADY_DEFINED,
+ L_PARSE_VAR_DEF_ALREADY_DEFINED2
};
/** @class RplMFParser rpllexer.hpp "rplexpr/rplmfparser.hpp"
*
* 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
+ * @post the token behind the do is read
+ * @return the abstract syntax tree of the for statement
*/
RplASItem* RplMFParser::parseFor()
{
rc->setChild3(value);
token = m_lexer.currentToken();
}
+ RplSymbolSpace* symbols = m_tree.currentSpace();
+ RplASItem* oldSymbol = symbols->addVariable(rc);
+ if (oldSymbol != NULL)
+ error(L_PARSE_VAR_DEF_ALREADY_DEFINED, oldSymbol->position(),
+ "symbol already defined", "previous definition");
return rc;
}
} else if (opId == O_LPARENTH){
rc = parseExpr(level + 1);
token = m_lexer.currentToken();
- if(! token->isOperator(O_RPARENT)){
+ if(! token->isOperator(O_RPARENTH)){
// this call never comes back (exception!)
syntaxError(L_PARSE_OPERAND_RPARENTH,
"')' expected", "(", startPosition);
rc = call;
token = m_lexer.nextNonSpaceToken();
- if (! token->isOperator(O_RPARENT)){
+ if (! token->isOperator(O_RPARENTH)){
m_lexer.undoLastToken();
RplASArgument* args = parseArguments();
call->setChild2(args);
item = parseFor();
break;
case K_CLASS:
- item = parseClass();
+ parseClass();
break;
case K_FUNCTION:
case K_GENERATOR:
- item = parseMethodDefinition(NULL);
+ parseMethod();
break;
case K_IMPORT:
parseImport();
default:
break;
}
- if (again){
+ if (again && item != NULL){
if (body == NULL){
body = item;
} else {
}
last = current;
} while(m_lexer.currentToken()->isOperator(O_COMMA));
- if (! m_lexer.currentToken()->isOperator(O_RPARENT))
+ if (! m_lexer.currentToken()->isOperator(O_RPARENTH))
syntaxError(L_PARSE_PARAMLIST_NO_PARENTH, ") expected", ")", startPos);
m_lexer.nextNonSpaceToken();
return rc;
/**
* @brief Parses a class definition.
*
- * @pre token "func" is read
- * @post token behind "endf" is read
- * @return the node in an abstract syntax tree
+ * The method definition will be stored in the symbol space of the parent
+ *
+ * @pre token "func" is read
+ * @post token behind "endf" is read
+ * @return NULL
*/
-RplASMethod*RplMFParser::parseMethodDefinition(RplASItem* parent)
+void RplMFParser::parseMethod()
{
- RplASMethod* rc = NULL;
+ RplASMethod* method = NULL;
const RplSourcePosition* startPos = m_lexer.currentPosition();
RplToken* token = m_lexer.nextNonSpaceToken();
if (! token->isTokenType(TOKEN_ID))
token = m_lexer.nextNonSpaceToken();
if (! token->isOperator(O_LPARENTH, O_COLON))
syntaxError(L_PARSE_METH_NO_LPARENTH, "'(' or ':' expected");
- RplASExprStatement* parameterList;
+
+ RplASExprStatement* parameterList = NULL;
+ method = new RplASMethod(name, m_tree);
+ method->setPosition(startPos);
+ RplSymbolSpace* symbols = m_tree.currentSpace();
+ symbols->addMethod(method);
+
if (token->isOperator(O_LPARENTH)){
token = m_lexer.nextNonSpaceToken();
- if (token->isOperator(O_RPARENT)){
+ if (token->isOperator(O_RPARENTH)){
token = m_lexer.nextNonSpaceToken();
} else {
parameterList = parseParameterList();
+ method->setChild2(parameterList);
}
}
if (! token->isOperator(O_COLON))
syntaxError(L_PARSE_METH_NO_COLON, "':' expected");
- rc = new RplASMethod(name);
- rc->setParent(parent);
- rc->setPosition(startPos);
- rc->setChild2(parameterList);
- rc->setChild(parseBody(K_ENDF));
+
+ method->setChild(parseBody(K_ENDF));
if (! m_lexer.currentToken()->isKeyword(K_ENDF))
syntaxError(L_PARSE_METH_NO_END, "end of function not found", "endf",
startPos);
m_lexer.nextNonSpaceToken();
- return rc;
+ m_tree.finishClassOrMethod(name);
}
/**
* @brief Parses a class definition.
* @return the node in an abstract syntax tree
*/
-RplASItem*RplMFParser::parseClass()
+void RplMFParser::parseClass()
{
- RplASItem* rc = NULL;
- return rc;
+ //RplASItem* clazz = NULL;
+ QString name = "";
+
+ m_tree.finishClassOrMethod(name);
}
/**
bool again = false;
do {
RplASItem* expr = parseExpr(0);
- if (! m_lexer.currentToken()->isOperator(O_COMMA, O_RPARENT))
+ if (! m_lexer.currentToken()->isOperator(O_COMMA, O_RPARENTH))
syntaxError(L_PARSE_ARGS_NO_COMMA_OR_PARENT, "',' or ')' expected");
again = m_lexer.currentToken()->isOperator(O_COMMA);
RplASArgument* current = new RplASArgument();
O_LSHIFT, O_RSHIFT, O_RSHIFT2, // 37
O_NOT, O_BIT_NOT, // 39
O_INC, O_DEC, // 41
- O_DOT, O_LPARENTH, O_RPARENT, O_LBRACKET, O_RBRACKET, O_LBRACE, O_RBRACE // 48
+ O_DOT, O_LPARENTH, O_RPARENTH, O_LBRACKET, O_RBRACKET, O_LBRACE, O_RBRACE // 48
};
#define IS_BINARY_OP(op) (Operator(op) >= O_ASSIGN && Operator(op) <= O_DOT)
#define IS_UNARY_OP(op) (op==O_PLUS || op==O_MINUS || (op>=O_NOT && op<=O_DEC))
RplASItem* parseExpr(int depth);
RplASItem* parseBody(Keyword keywordStop, Keyword keywordStop2 = K_UNDEF,
Operator opStop = O_UNDEF, Operator opStop2 = O_UNDEF);
- RplASMethod*parseMethodDefinition(RplASItem* parent);
- RplASItem* parseClass();
+ void parseMethod();
+ void parseClass();
void parseImport();
RplASItem* parseModule(const QString& name);
void parse();
if (++m_errors >= m_maxErrors)
throw RplParserStop("too many errors");
}
+/**
+ * @brief Adds an error message with an additional info message.
+ *
+ * If too much errors an exception will be thrown to stop parsing.
+ *
+ * @param location unique id of the error/warning message
+ * @param position source position of the additional message
+ * @param message describes the error
+ * @param message2 the additional message
+ * @param ... optional: the variable argument list
+ */
+void RplParser::error(int location, const RplSourcePosition* position,
+ const char* message, const char* message2)
+{
+ addSimpleMessage(LT_ERROR, location, m_lexer.currentPosition(), message);
+ addSimpleMessage(LT_INFO, location + 1, position, message2);
+ if (++m_errors >= m_maxErrors)
+ throw RplParserStop("too many errors");
+}
/**
* @brief Adds a warning message.
void syntaxError(int location, const char* message, const char* symbol,
const RplSourcePosition* position);
void error(int location, const char* format, ...);
+ void error(int location, const RplSourcePosition* position,
+ const char* message, const char* message2);
void warning(int location, const char* format, ...);
protected:
== Classes:
== Variables:
== Body:
-Method 1 <NoneType> pi() body: 4 args: 36816 <test>:0:0
-Method 4 <NoneType> delim() body: 0 args: 36816 <test>:1:28
+Method <NoneType> pi() id: 1 parent: 0 args: 0 body: 4 <test>:0:0
+ Method <NoneType> delim() id: 4 parent: 0 args: 0 body: 6 <test>:1:28
+ Expr id: 6 expr: 5 succ: 0 <test>:1:46
+ const id: 5 value: '/' <test>:1:46
+ Expr id: 6 expr: 5 succ: 0 <test>:1:46
+ const id: 5 value: '/' <test>:1:46
+Method <NoneType> delim() id: 4 parent: 0 args: 0 body: 6 <test>:1:28
+ Expr id: 6 expr: 5 succ: 0 <test>:1:46
+ const id: 5 value: '/' <test>:1:46
+Expr id: 6 expr: 5 succ: 0 <test>:1:46
+ const id: 5 value: '/' <test>:1:46
== Classes:
== Variables:
== Body:
-Method 4 <NoneType> fac() body: 0 args: 3 <test>:0:55
+Method <NoneType> fac() id: 4 parent: 0 args: 3 body: 6 <test>:0:55
+ Expr id: 3 expr: 2 succ: 0
+ varDef n id: 2 namedValue: 1 value: 0 succ: 0 <test>:1:23
+ namedValue n id: 1 attr: 0x22 <test>:1:23
+ varDef rc id: 6 namedValue: 5 value: 0 succ: 7 <test>:2:4
+ namedValue rc id: 5 attr: 0x0 <test>:2:4
+ If id: 7 condition: 9 then: 14 else: 24 succ: 26<test>:2:8
+ BinOp id: 9 op: <= (23) left: 8 right: 10 <test>:2:14
+ namedValue rc id: 8 attr: 0x0 <test>:2:14
+ const id: 10 value: 1 <test>:2:17
+ Expr id: 14 expr: 12 succ: 0 <test>:2:27
+ BinOp id: 12 op: = (5) left: 11 right: 13 <test>:2:27
+ namedValue rc id: 11 attr: 0x0 <test>:2:27
+ const id: 13 value: 1 <test>:2:29
+ Expr id: 24 expr: 16 succ: 0 <test>:2:39
+ BinOp id: 16 op: = (5) left: 15 right: 18 <test>:2:39
+ namedValue rc id: 15 attr: 0x0 <test>:2:39
+ BinOp id: 18 op: * (30) left: 17 right: 19 <test>:2:42
+ namedValue n id: 17 attr: 0x0 <test>:2:42
+ call fac Id: 19 args: 23 parent: 0 succ: 0 <test>:2:46
+ arg 1 id: 23 expr: 21 succ: 0
+ BinOp id: 21 op: - (27) left: 20 right: 22 <test>:2:48
+ namedValue n id: 20 attr: 0x0 <test>:2:48
+ const id: 22 value: 1 <test>:2:49
+ Expr id: 26 expr: 25 succ: 0 <test>:3:3
+ namedValue rc id: 25 attr: 0x0 <test>:3:3
+varDef rc id: 6 namedValue: 5 value: 0 succ: 7 <test>:2:4
+ namedValue rc id: 5 attr: 0x0 <test>:2:4
+If id: 7 condition: 9 then: 14 else: 24 succ: 26<test>:2:8
+ BinOp id: 9 op: <= (23) left: 8 right: 10 <test>:2:14
+ namedValue rc id: 8 attr: 0x0 <test>:2:14
+ const id: 10 value: 1 <test>:2:17
+ Expr id: 14 expr: 12 succ: 0 <test>:2:27
+ BinOp id: 12 op: = (5) left: 11 right: 13 <test>:2:27
+ namedValue rc id: 11 attr: 0x0 <test>:2:27
+ const id: 13 value: 1 <test>:2:29
+ Expr id: 24 expr: 16 succ: 0 <test>:2:39
+ BinOp id: 16 op: = (5) left: 15 right: 18 <test>:2:39
+ namedValue rc id: 15 attr: 0x0 <test>:2:39
+ BinOp id: 18 op: * (30) left: 17 right: 19 <test>:2:42
+ namedValue n id: 17 attr: 0x0 <test>:2:42
+ call fac Id: 19 args: 23 parent: 0 succ: 0 <test>:2:46
+ arg 1 id: 23 expr: 21 succ: 0
+ BinOp id: 21 op: - (27) left: 20 right: 22 <test>:2:48
+ namedValue n id: 20 attr: 0x0 <test>:2:48
+ const id: 22 value: 1 <test>:2:49
+Expr id: 26 expr: 25 succ: 0 <test>:3:3
+ namedValue rc id: 25 attr: 0x0 <test>:3:3