/*****
* exp.h
* Andy Hammerlindl 2002/8/19
*
* Represents the abstract syntax tree for the expressions in the
* language. this is translated into virtual machine code using trans()
* and with the aid of the environment class.
*****/
using trans::coenv;
using trans::label;
using trans::application;
using trans::access;
using sym::symbol;
using types::record;
using types::array;
class exp : public varinit {
protected:
// The cached type (from a call to cgetType).
types::ty *ct;
public:
exp(position pos)
: varinit(pos), ct(0) {}
void prettyprint(ostream &out, Int indent) = 0;
// When reporting errors with function calls, it is nice to say "no
// function f(int)" instead of "no function matching signature
// (int)." Hence, this method returns the name of the expression if
// there is one.
virtual symbol getName()
{
return symbol::nullsym;
}
// Checks if the expression can be used as the right side of a scale
// expression. ie. 3sin(x)
// If a "non-scalable" expression is scaled a warning is issued.
virtual bool scalable() { return true; }
// Specifies if the value of the expression should be written to interactive
// prompt if typed as a stand-alone expression. For example:
// > 2+3;
// should write 5, but
// > x=2+3;
// shouldn't. (These choices are largely aesthetic)
virtual bool writtenToPrompt() { return true; }
// Translates the expression to the given target type. This should only be
// called with a type returned by getType(). It does not perform implicit
// casting.
virtual void transAsType(coenv &e, types::ty *target);
// Translates the expression to the given target type, possibly using an
// implicit cast.
void transToType(coenv &e, types::ty *target);
// Translates the expression and returns the resultant type.
// For some expressions, this will be ambiguous and return an error.
// Trans may only return ty_error, if it (or one of its recursively
// called children in the syntax tree) reported an error to em.
virtual types::ty *trans(coenv &) = 0;
// getType() figures out the type of the expression without translating
// the code into the virtual machine language or reporting errors to em.
// This must follow a few rules to ensure proper translation:
// 1. If this returns a valid type, t, trans(e) must return t or
// report an error, and transToType(e, t) must run either reporting
// an error or reporting no error and yielding the same result as
// trans(e).
// 2. If this returns a superposition of types (ie. for overloaded
// functions), trans must not return a singular type, and every
// type in the superposition must run without error properly
// if fed to transAsType(e, t).
// 3. If this returns ty_error, then so must a call to trans(e) and any
// call to trans, transAsType, or transToType must report an error
// to em.
// 4. Any call to transAsType(e, t) with a type that is not returned by
// getType() (or one of the subtypes in case of a superposition)
// must report an error.
// Any call to transToType(e, t) with a type that is not returned by
// getType() (or one of the subtypes in case of a superposition)
// or any type not implicitly castable from the above must report an
// error.
virtual types::ty *getType(coenv &) = 0;
// This is an optimization which works in some cases to by-pass the slow
// overloaded function resolution provided by the application class.
//
// If an expression is called with arguments given by sig, getCallee must
// either return 0 (the default), or if it returns a varEntry, the varEntry
// must correspond to the function which would be called after normal
// function resolution.
//
// The callee must produce no side effects as there are no guarantees when
// the varEntry will be translated.
virtual trans::varEntry *getCallee(coenv &e, types::signature *sig) {
//#define DEBUG_GETAPP
#if DEBUG_GETAPP
cout << "exp fail" << endl;
cout << "exp fail at " << getPos() << endl;
prettyprint(cout, 2);
#endif
return 0;
}
// Same result as getType, but caches the result so that subsequent
// calls are faster. For this to work correctly, the expression should
// only be used in one place, so the environment doesn't change between
// calls.
virtual types::ty *cgetType(coenv &e) {
#ifdef DEBUG_CACHE
testCachedType(e);
#endif
return ct ? ct : ct = getType(e);
}
void testCachedType(coenv &e);
// The expression is being written. Translate code such that the value
// (represented by the exp value) is stored into the address represented by
// this expression.
// In terms of side-effects, this expression must be evaluated (once) before
// value is evaluated (once).
virtual void transWrite(coenv &e, types::ty *t, exp *value) {
em.error(getPos());
em << "expression cannot be used as an address";
// Translate the value for errors.
value->transToType(e, t);
}
// Translates code for calling a function. The arguments, in the order they
// appear in the function's signature, must all be on the stack.
virtual void transCall(coenv &e, types::ty *target);
// transConditionalJump must produce code equivalent to the following:
// Evaluate the expression as a boolean. If the result equals cond, jump to
// the label dest, otherwise do not jump. In either case, no value is left
// on the stack.
virtual void transConditionalJump(coenv &e, bool cond, label dest);
// This is used to ensure the proper order and number of evaluations. When
// called, it immediately translates code to perform the side-effects
// consistent with a corresponding call to transAsType(e, target).
//
// The return value, called an evaluation for lack of a better name, is
// another expression that responds to the trans methods exactly as would the
// original expression, but without producing side-effects. It is also no
// longer overloaded, due to the resolution effected by giving a target type
// to evaluate().
//
// The methods transAsType, transWrite, and transCall of the evaluation must
// be called with the same target type as the original call to evaluate.
// When evaluate() is called during the translation of a function, that
// function must still be in translation when the evaluation is translated.
//
// The base implementation uses a tempExp (see below). This is
// sufficient for most expressions.
virtual exp *evaluate(coenv &e, types::ty *target);
// NOTE: could add a "side-effects" method which says if the expression has
// side-effects. This might allow some small optimizations in translating.
};
class tempExp : public exp {
access *a;
types::ty *t;
// Wrap a varEntry so that it can be used as an expression.
// Translating the varEntry must cause no side-effects.
class varEntryExp : public exp {
trans::varEntry *v;
public:
varEntryExp(position pos, trans::varEntry *v)
: exp(pos), v(v) {}
varEntryExp(position pos, types::ty *t, access *a);
varEntryExp(position pos, types::ty *t, vm::bltin f);
void prettyprint(ostream &out, Int indent) override;
void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
symbol getName() override
{
return value->getName();
}
void transAsType(coenv &e, types::ty *target) override {
value->varTrans(trans::READ, e, target);
// After translation, the cached type is no longer needed and should be
// garbage collected. This could presumably be done in every class derived
// from exp, but here it is most important as nameExp can have heavily
// overloaded types cached.
ct=0;
}
types::ty *trans(coenv &e) override {
types::ty *t=cgetType(e);
if (t->kind == types::ty_error) {
em.error(getPos());
em << "no matching variable \'" << *value << "\'";
return types::primError();
}
if (t->kind == types::ty_overloaded) {
em.error(getPos());
em << "use of variable \'" << *value << "\' is ambiguous";
return types::primError();
}
else {
transAsType(e, t);
return t;
}
}
types::ty *getType(coenv &e) override {
types::ty *t=value->varGetType(e);
return t ? t : types::primError();
}
void transCall(coenv &e, types::ty *target) override {
value->varTrans(trans::CALL, e, target);
ct=0; // See note in transAsType.
}
exp *evaluate(coenv &, types::ty *) override {
// Names have no side-effects.
return this;
}
};
// Most fields accessed are handled as parts of qualified names, but in cases
// like f().x or (new t).x, a separate expression is needed.
class fieldExp : public nameExp {
exp *object;
symbol field;
// fieldExp has a lot of common functionality with qualifiedName, so we
// essentially hack qualifiedName, by making our object expression look
// like a name.
class pseudoName : public name {
exp *object;
// As a type:
types::ty *typeTrans(coenv &, bool tacit = false) {
if (!tacit) {
em.error(getPos());
em << "expression is not a type";
}
return types::primError();
}
trans::varEntry *getVarEntry(coenv &) {
em.compiler(getPos());
em << "expression cannot be used as part of a type";
return 0;
}
trans::tyEntry *tyEntryTrans(coenv &) {
em.compiler(getPos());
em << "expression cannot be used as part of a type";
return 0;
}
trans::frame *tyFrameTrans(coenv &) {
return 0;
}
void prettyprint(ostream &out, Int indent);
void print(ostream& out) const {
out << "<exp>";
}
symbol getName() const {
return object->getName();
}
// Translates code to put the left and right expressions on the stack (in that
// order). If left is omitted, zero is pushed on the stack in it's place. If
// right is omitted, nothing is pushed in its place.
void trans(coenv &e);
slice *evaluate(coenv &e) {
return new slice(getPos(),
left ? new tempExp(e, left, types::primInt()) : 0,
right ? new tempExp(e, right, types::primInt()) : 0);
}
};
// No constructor due to the union in camp.y
#if 0
argument(exp *val=0, symbol name=0)
: val(val), name(name) {}
#endif
void prettyprint(ostream &out, Int indent);
void createSymMap(AsymptoteLsp::SymbolContext* symContext);
};
class arglist : public gc {
public:
typedef mem::vector<argument> argvector;
argvector args;
argument rest;
// As the language allows named arguments after rest arguments, store the
// index of the rest argument in order to ensure proper left-to-right
// execution.
static const size_t DUMMY_REST_POSITION = 9999;
size_t restPosition;
// callExp has a global cache of resolved overloaded functions. This clears
// this cache so the associated data can be garbage collected.
void clearCachedCalls();
class callExp : public exp {
protected:
exp *callee;
arglist *args;
private:
// Per object caching - Cache the application when it's determined.
application *cachedApp;
// In special cases, no application object is needed and we can store the
// varEntry used in advance.
trans::varEntry *cachedVarEntry;
// Caches either the application object used to apply the function to the
// arguments, or in cases where the arguments match the function perfectly,
// the varEntry of the callee (or neither in case of an error). Returns
// what getType should return.
types::ty *cacheAppOrVarEntry(coenv &e, bool tacit);
void prettyprint(ostream &out, Int indent) override;
void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
using colorInfo = std::tuple<double, double, double>;
/**
* @return nullopt if callExp is not a color, pair<color, nullopt> if color is RGB,
* and pair<color, alpha> if color is RGBA.
*/
optional<std::tuple<colorInfo, optional<double>,
AsymptoteLsp::posInFile, AsymptoteLsp::posInFile>> getColorInformation();
// Returns true if the function call resolves uniquely without error. Used
// in implementing the special == and != operators for functions.
virtual bool resolved(coenv &e);
};
// Used for tension, which takes two real values, and a boolean to denote if it
// is a tension atleast case.
class ternaryExp : public callExp {
public:
ternaryExp(position pos, exp *left, symbol op, exp *right, exp *last)
: callExp(pos, new nameExp(pos, op), left, right, last) {}
};
// The a ? b : c ternary operator.
class conditionalExp : public exp {
exp *test;
exp *onTrue;
exp *onFalse;
class assignExp : public exp {
protected:
exp *dest;
exp *value;
// This is basically a hook to facilitate selfExp. dest is given as an
// argument since it will be a temporary in translation in order to avoid
// multiple evaluation.
virtual exp *ultimateValue(exp *) {
return value;
}
// Postfix expresions are illegal. This is caught here as we can give a
// more meaningful error message to the user, rather than a "parse
// error."
class postfixExp : public exp {
exp *dest;
symbol op;