Value OrExpr::Evaluate(SymbolTable *scope, EvalContext& context, bool asbool) { // Scope override; must do this for every expression that might contain an identifier if(this->scope != NULL) scope = this->scope; // Lowering of A or B: // [A] // [iftrue goto end] // [B] // label end: String* value = new String(); string labelbase = context.GetUniqueLabelName(); Anchor* endanchor = new Anchor(labelbase + ".end"); // a value->Append( a->Evaluate(scope, context, true).ToCodeString() ); // iftrue goto end: value->Code("1B 03 FF FF FF FF"); value->AddReference(value->GetPos()-4, endanchor); // b value->Append( b->Evaluate(scope, context, true).ToCodeString() ); // end: value->AddAnchor(endanchor); return Value(value); }
Value AndExpr::Evaluate(SymbolTable *scope, EvalContext& context, bool asbool) { // Scope override; must do this for every expression that might contain an identifier if(this->scope != NULL) scope = this->scope; // Lowering A and B: // [A] // [iffalse goto end] // [B] // label end: String* value = new String(); // Create internal label string labelbase = context.GetUniqueLabelName(); Anchor* endanchor = new Anchor(labelbase + ".end"); // Evaluate the first operand value->Append( a->Evaluate(scope, context, true).ToCodeString() ); // Add a jump to the end if the first operand is false value->Code("1B 02 FF FF FF FF"); value->AddReference(value->GetPos()-4, endanchor); // TODO: // Hm. I just realized that some boolean expressions (and and or) rely on reference // resolution to operate correctly. Thus, it doesn't make sense to use them in ROM // write statements at the moment, because ROM write statements occur after normal // resolution, but without doing any resolution themselves. Perhaps ROM write statements // should have a special resolution step to take care of stuff like this. (Perhaps // the ROM data itself should be represented as a ByteChunk, with refs?) // Anyway, don't worry about this for now, since using boolean expressions in a ROM // write statement is not a very likely usage scenario. // UPDATE: 11/11/2008 // This issue has been fixed by moving the evaluation of ROM write subexpressions // back to the evaluation pass of the compiler, and simply caching the results and // resolving references in a later pass. However, it still might be worthwhile to // consider alternative solutions; whole-ROM resolution seems interesting for example. // Evaluate the second operand value->Append( b->Evaluate(scope, context, true).ToCodeString() ); // Set the position of the end label value->AddAnchor(endanchor); return Value(value); }
Value MenuExpr::Evaluate(SymbolTable* scope, EvalContext& context, bool asbool) { if(this->scope != NULL) scope = this->scope; // Lowering a menu statement: // [19 02][option][02] - for each option // [1C 0C $cols][11][12] // [09 $num (statementjmps)] // [goto end] // [statement][goto end] - for each statement // label end: String* value = new String(); // Create internal labels vector<Anchor*> anchors; string labelbase = context.GetUniqueLabelName(); for(unsigned int i = 0; i < options.size(); ++i) { std::stringstream ss; ss << ".opt" << i; Anchor* a = new Anchor(labelbase + ss.str()); anchors.push_back(a); } Anchor* endanchor = new Anchor(labelbase + ".end"); // First, append the options between [19 02] and [02] codes for(unsigned int i = 0; i < options.size(); ++i) { value->Code("19 02"); value->Append( options[i]->Evaluate(scope, context).ToCodeString() ); value->Code("02"); } // Next, append the option display commands // If we're only using two options, and no number of columns was specified, // use "1C 07", otherwise, use "1C 0C". if(options.size() == 2 && defcolumns) value->Code("1C 07"); else value->Code("1C 0C"); value->Byte(columns);// write exactly one byte for the column count value->Code("11 12"); // Next, the multi-jump code value->Code("09"); value->Byte(results.size());// write exactly one byte for the option count for(unsigned int i = 0; i < results.size(); ++i) { value->Code("FF FF FF FF"); value->AddReference(value->GetPos() - 4, anchors[i]); } // Add a jump to the "default" option after the multi-jump, or end if no default value->Code("0A FF FF FF FF"); if(defaultopt != -1) value->AddReference(value->GetPos() - 4, anchors[defaultopt]); else value->AddReference(value->GetPos() - 4, endanchor); // Finally, write out all the options, with a "goto end" after each // At each point we set the position of the relevant label. for(unsigned int i = 0; i < results.size(); ++i) { value->AddAnchor(anchors[i]); value->Append( results[i]->Evaluate(scope, context).ToCodeString() ); // Add a "goto end" after every statement, in case it falls through value->Code("0A FF FF FF FF"); value->AddReference(value->GetPos() - 4, endanchor); } // Last step: set position of the "end" label value->AddAnchor(endanchor); return Value(value); }
Value IdentExpr::Evaluate(SymbolTable* scope, EvalContext& context, bool asbool) { /// Scope override; must do this for every expression that might contain an identifier if(this->scope != NULL) scope = this->scope; //context.file = this->file; //context.line = this->linenumber; // To evaluate an identifier expression: // First, we have to look up the symbol and check the type of its value. // If it's a constant, we just evaluate the constant (provided there are no // parameters given; if there are parameters, we should report an error.) // If it's a command, we need to: // - bind each argument expression to the corresponding symbol in the commands // parameter list. // - invoke the command. Module* module = context.module; SymbolTable* lookupScope = scope; // If the ident expr's "file" field is not empty, we'll look it up in a different module if(!file.empty()) { Module* mod = module->GetSiblingContext(file); if(!mod) { Error("reference to nonexistent module '" + file + "'"); return Value::Null; } lookupScope = mod->GetRootTable(); } Value found = lookupScope->Lookup(name); if(found != Value::Undefined) { // In most cases, we just return the value. if(found.GetType() != Type::Macro) { // However, evaluated vars are not importable. if(lookupScope != scope) { Error("cannot access local variable declaration '" + name + "' in module '" + file + "'"); return Value::Null; } return found; } Node* node = found.GetNode(); Value result; if(node->GetType() == conststmt) { if(hasparens) { Error("'" + GetFullName() + "' refers to a constant; cannot use parentheses"); return Value(); } result = dynamic_cast<ConstDef*>(node)->EvaluateExpr(scope, context, asbool); } else if(node->GetType() == commandstmt) { for(unsigned int i = 0; i < args.size(); ++i) args[i]->scope = scope; CommandDef* cmd = dynamic_cast<CommandDef*>(found.GetNode()); if(cmd->GetArgCount() != args.size()) Error("incorrect number of parameters to command '" + GetFullName() + "'"); else result = cmd->Invoke(context, args); } else if(node->GetType() == ambiguousid) { AmbiguousID* ambig = dynamic_cast<AmbiguousID*>(found.GetNode()); Error(ambig->ToString("")); result = Value::Null; } else if(node->IsExpression()) { result = dynamic_cast<Expression*>(node)->Evaluate(scope, context, asbool); } else { Error("invalid type"); } return result; } // Didn't find it in the symbol table, check the jumps table Anchor* foundanchor = lookupScope->LookupAnchor(name); if(foundanchor) { if(hasparens) { Error("'" + GetFullName() + "' refers to a label; cannot use parentheses"); return Value(); } String* val = new String(); val->Long(foundanchor->GetTarget()); // The targeted label might not have its address computed yet, so we register a // reference to the target label (unless refs are forbidden by the context) if(!context.norefs) val->AddReference(val->GetPos()-4, foundanchor); return Value(val); } Error("use of undefined identifier '" + GetFullName() + "'"); return Value(); }
Value IfExpr::Evaluate(SymbolTable *env, EvalContext& context, bool asbool) { if(this->scope != NULL) env = this->scope; /* * Lowering an if statement: * * [condition] * [iffalse goto falselbl] * [thenstmt] * [goto endlbl] * falselbl: * [elsestmt] * endlbl: */ String* value = new String(); // Create internal labels string labelbase = context.GetUniqueLabelName(); Anchor* endanchor = new Anchor(labelbase + ".end"); Anchor* falseanchor = new Anchor(labelbase + ".false"); // First, we evaluate the condition Value cond_val = condition->Evaluate(env, context, true); // TODO: this might be an opportunity to do some typechecking on the returned value, // instead of just converting it to a string. Maybe some warnings would be appropriate? // (i.e., conditioning on a number value, which is almost always meaningless) // append cond_val to the output: value->Append(cond_val.ToCodeString()); // Then, we output an "iffalse goto false" instruction, and register a jump reference value->Code("1B 02 FF FF FF FF"); value->AddReference(value->GetSize() - 4, falseanchor); // Evaluate the "then" statement Value then_val = thenexpr->Evaluate(env, context); value->Append(then_val.ToCodeString()); // Add a "goto end" // TODO: strictly speaking, we can dispense with this last goto when // there is no 'else' clause. We'll leave it here for now until we // get the first round of regression tests in place, and then we'll // update it along with the other evaluation refactoring. value->Code("0A FF FF FF FF"); value->AddReference(value->GetPos() - 4, endanchor); // Set the position of the false anchor within the string value->AddAnchor(falseanchor); // Evaluate the "else" statement if(elseexpr) { Value else_val = elseexpr->Evaluate(env, context); value->Append(else_val.ToCodeString()); } // Set the position of the "end" label value->AddAnchor(endanchor); return Value(value); }