blob: 859e964b1561c6a97cd29e06ca4344f82461ac7d [file] [log] [blame]
/* Compiler implementation of the D programming language
* Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved
* written by Walter Bright
* http://www.digitalmars.com
* Distributed under the Boost Software License, Version 1.0.
* http://www.boost.org/LICENSE_1_0.txt
* https://github.com/D-Programming-Language/dmd/blob/master/src/arrayop.c
*/
#include "root/dsystem.h"
#include "root/rmem.h"
#include "root/aav.h"
#include "mars.h"
#include "expression.h"
#include "statement.h"
#include "mtype.h"
#include "declaration.h"
#include "scope.h"
#include "id.h"
#include "module.h"
#include "init.h"
#include "tokens.h"
void buildArrayIdent(Expression *e, OutBuffer *buf, Expressions *arguments);
Expression *buildArrayLoop(Expression *e, Parameters *fparams);
Expression *semantic(Expression *e, Scope *sc);
/**************************************
* Hash table of array op functions already generated or known about.
*/
AA *arrayfuncs;
/**************************************
* Structure to contain information needed to insert an array op call
*/
FuncDeclaration *buildArrayOp(Identifier *ident, BinExp *exp, Scope *sc)
{
Parameters *fparams = new Parameters();
Expression *loopbody = buildArrayLoop(exp, fparams);
/* Construct the function body:
* foreach (i; 0 .. p.length) for (size_t i = 0; i < p.length; i++)
* loopbody;
* return p;
*/
Parameter *p = (*fparams)[0];
// foreach (i; 0 .. p.length)
Statement *s1 = new ForeachRangeStatement(Loc(), TOKforeach,
new Parameter(0, NULL, Id::p, NULL),
new IntegerExp(Loc(), 0, Type::tsize_t),
new ArrayLengthExp(Loc(), new IdentifierExp(Loc(), p->ident)),
new ExpStatement(Loc(), loopbody),
Loc());
//printf("%s\n", s1->toChars());
Statement *s2 = new ReturnStatement(Loc(), new IdentifierExp(Loc(), p->ident));
//printf("s2: %s\n", s2->toChars());
Statement *fbody = new CompoundStatement(Loc(), s1, s2);
// Built-in array ops should be @trusted, pure, nothrow and nogc
StorageClass stc = STCtrusted | STCpure | STCnothrow | STCnogc;
/* Construct the function
*/
TypeFunction *ftype = new TypeFunction(fparams, exp->e1->type, 0, LINKc, stc);
//printf("fd: %s %s\n", ident->toChars(), ftype->toChars());
FuncDeclaration *fd = new FuncDeclaration(Loc(), Loc(), ident, STCundefined, ftype);
fd->fbody = fbody;
fd->protection = Prot(PROTpublic);
fd->linkage = LINKc;
fd->isArrayOp = 1;
sc->_module->importedFrom->members->push(fd);
sc = sc->push();
sc->parent = sc->_module->importedFrom;
sc->stc = 0;
sc->linkage = LINKc;
fd->semantic(sc);
fd->semantic2(sc);
unsigned errors = global.startGagging();
fd->semantic3(sc);
if (global.endGagging(errors))
{
fd->type = Type::terror;
fd->errors = true;
fd->fbody = NULL;
}
sc->pop();
return fd;
}
/**********************************************
* Check that there are no uses of arrays without [].
*/
bool isArrayOpValid(Expression *e)
{
if (e->op == TOKslice)
return true;
if (e->op == TOKarrayliteral)
{
Type *t = e->type->toBasetype();
while (t->ty == Tarray || t->ty == Tsarray)
t = t->nextOf()->toBasetype();
return (t->ty != Tvoid);
}
Type *tb = e->type->toBasetype();
if (tb->ty == Tarray || tb->ty == Tsarray)
{
if (isUnaArrayOp(e->op))
{
return isArrayOpValid(((UnaExp *)e)->e1);
}
if (isBinArrayOp(e->op) ||
isBinAssignArrayOp(e->op) ||
e->op == TOKassign)
{
BinExp *be = (BinExp *)e;
return isArrayOpValid(be->e1) && isArrayOpValid(be->e2);
}
if (e->op == TOKconstruct)
{
BinExp *be = (BinExp *)e;
return be->e1->op == TOKslice && isArrayOpValid(be->e2);
}
if (e->op == TOKcall)
{
return false; // TODO: Decide if [] is required after arrayop calls.
}
else
{
return false;
}
}
return true;
}
bool isNonAssignmentArrayOp(Expression *e)
{
if (e->op == TOKslice)
return isNonAssignmentArrayOp(((SliceExp *)e)->e1);
Type *tb = e->type->toBasetype();
if (tb->ty == Tarray || tb->ty == Tsarray)
{
return (isUnaArrayOp(e->op) || isBinArrayOp(e->op));
}
return false;
}
bool checkNonAssignmentArrayOp(Expression *e, bool suggestion)
{
if (isNonAssignmentArrayOp(e))
{
const char *s = "";
if (suggestion)
s = " (possible missing [])";
e->error("array operation %s without destination memory not allowed%s", e->toChars(), s);
return true;
}
return false;
}
/***********************************
* Construct the array operation expression.
*/
Expression *arrayOp(BinExp *e, Scope *sc)
{
//printf("BinExp::arrayOp() %s\n", toChars());
Type *tb = e->type->toBasetype();
assert(tb->ty == Tarray || tb->ty == Tsarray);
Type *tbn = tb->nextOf()->toBasetype();
if (tbn->ty == Tvoid)
{
e->error("cannot perform array operations on void[] arrays");
return new ErrorExp();
}
if (!isArrayOpValid(e))
{
e->error("invalid array operation %s (possible missing [])", e->toChars());
return new ErrorExp();
}
Expressions *arguments = new Expressions();
/* The expression to generate an array operation for is mangled
* into a name to use as the array operation function name.
* Mangle in the operands and operators in RPN order, and type.
*/
OutBuffer buf;
buf.writestring("_array");
buildArrayIdent(e, &buf, arguments);
buf.writeByte('_');
/* Append deco of array element type
*/
buf.writestring(e->type->toBasetype()->nextOf()->toBasetype()->mutableOf()->deco);
char *name = buf.peekString();
Identifier *ident = Identifier::idPool(name);
FuncDeclaration **pFd = (FuncDeclaration **)dmd_aaGet(&arrayfuncs, (void *)ident);
FuncDeclaration *fd = *pFd;
if (!fd)
fd = buildArrayOp(ident, e, sc);
if (fd && fd->errors)
{
const char *fmt;
if (tbn->ty == Tstruct || tbn->ty == Tclass)
fmt = "invalid array operation '%s' because %s doesn't support necessary arithmetic operations";
else if (!tbn->isscalar())
fmt = "invalid array operation '%s' because %s is not a scalar type";
else
fmt = "invalid array operation '%s' for element type %s";
e->error(fmt, e->toChars(), tbn->toChars());
return new ErrorExp();
}
*pFd = fd;
Expression *ev = new VarExp(e->loc, fd);
Expression *ec = new CallExp(e->loc, ev, arguments);
return semantic(ec, sc);
}
Expression *arrayOp(BinAssignExp *e, Scope *sc)
{
//printf("BinAssignExp::arrayOp() %s\n", toChars());
/* Check that the elements of e1 can be assigned to
*/
Type *tn = e->e1->type->toBasetype()->nextOf();
if (tn && (!tn->isMutable() || !tn->isAssignable()))
{
e->error("slice %s is not mutable", e->e1->toChars());
return new ErrorExp();
}
if (e->e1->op == TOKarrayliteral)
{
return e->e1->modifiableLvalue(sc, e->e1);
}
return arrayOp((BinExp *)e, sc);
}
/******************************************
* Construct the identifier for the array operation function,
* and build the argument list to pass to it.
*/
void buildArrayIdent(Expression *e, OutBuffer *buf, Expressions *arguments)
{
class BuildArrayIdentVisitor : public Visitor
{
OutBuffer *buf;
Expressions *arguments;
public:
BuildArrayIdentVisitor(OutBuffer *buf, Expressions *arguments)
: buf(buf), arguments(arguments)
{
}
void visit(Expression *e)
{
buf->writestring("Exp");
arguments->shift(e);
}
void visit(CastExp *e)
{
Type *tb = e->type->toBasetype();
if (tb->ty == Tarray || tb->ty == Tsarray)
{
e->e1->accept(this);
}
else
visit((Expression *)e);
}
void visit(ArrayLiteralExp *e)
{
buf->writestring("Slice");
arguments->shift(e);
}
void visit(SliceExp *e)
{
buf->writestring("Slice");
arguments->shift(e);
}
void visit(AssignExp *e)
{
/* Evaluate assign expressions right to left
*/
e->e2->accept(this);
e->e1->accept(this);
buf->writestring("Assign");
}
void visit(BinAssignExp *e)
{
/* Evaluate assign expressions right to left
*/
e->e2->accept(this);
e->e1->accept(this);
const char *s;
switch(e->op)
{
case TOKaddass: s = "Addass"; break;
case TOKminass: s = "Minass"; break;
case TOKmulass: s = "Mulass"; break;
case TOKdivass: s = "Divass"; break;
case TOKmodass: s = "Modass"; break;
case TOKxorass: s = "Xorass"; break;
case TOKandass: s = "Andass"; break;
case TOKorass: s = "Orass"; break;
case TOKpowass: s = "Powass"; break;
default: assert(0);
}
buf->writestring(s);
}
void visit(NegExp *e)
{
e->e1->accept(this);
buf->writestring("Neg");
}
void visit(ComExp *e)
{
e->e1->accept(this);
buf->writestring("Com");
}
void visit(BinExp *e)
{
/* Evaluate assign expressions left to right
*/
const char *s = NULL;
switch(e->op)
{
case TOKadd: s = "Add"; break;
case TOKmin: s = "Min"; break;
case TOKmul: s = "Mul"; break;
case TOKdiv: s = "Div"; break;
case TOKmod: s = "Mod"; break;
case TOKxor: s = "Xor"; break;
case TOKand: s = "And"; break;
case TOKor: s = "Or"; break;
case TOKpow: s = "Pow"; break;
default: break;
}
if (s)
{
Type *tb = e->type->toBasetype();
Type *t1 = e->e1->type->toBasetype();
Type *t2 = e->e2->type->toBasetype();
e->e1->accept(this);
if (t1->ty == Tarray &&
((t2->ty == Tarray && !t1->equivalent(tb)) ||
(t2->ty != Tarray && !t1->nextOf()->equivalent(e->e2->type))))
{
// Bugzilla 12780: if A is narrower than B
// A[] op B[]
// A[] op B
buf->writestring("Of");
buf->writestring(t1->nextOf()->mutableOf()->deco);
}
e->e2->accept(this);
if (t2->ty == Tarray &&
((t1->ty == Tarray && !t2->equivalent(tb)) ||
(t1->ty != Tarray && !t2->nextOf()->equivalent(e->e1->type))))
{
// Bugzilla 12780: if B is narrower than A:
// A[] op B[]
// A op B[]
buf->writestring("Of");
buf->writestring(t2->nextOf()->mutableOf()->deco);
}
buf->writestring(s);
}
else
visit((Expression *)e);
}
};
BuildArrayIdentVisitor v(buf, arguments);
e->accept(&v);
}
/******************************************
* Construct the inner loop for the array operation function,
* and build the parameter list.
*/
Expression *buildArrayLoop(Expression *e, Parameters *fparams)
{
class BuildArrayLoopVisitor : public Visitor
{
Parameters *fparams;
Expression *result;
public:
BuildArrayLoopVisitor(Parameters *fparams)
: fparams(fparams), result(NULL)
{
}
void visit(Expression *e)
{
Identifier *id = Identifier::generateId("c", fparams->dim);
Parameter *param = new Parameter(0, e->type, id, NULL);
fparams->shift(param);
result = new IdentifierExp(Loc(), id);
}
void visit(CastExp *e)
{
Type *tb = e->type->toBasetype();
if (tb->ty == Tarray || tb->ty == Tsarray)
{
e->e1->accept(this);
}
else
visit((Expression *)e);
}
void visit(ArrayLiteralExp *e)
{
Identifier *id = Identifier::generateId("p", fparams->dim);
Parameter *param = new Parameter(STCconst, e->type, id, NULL);
fparams->shift(param);
Expression *ie = new IdentifierExp(Loc(), id);
Expression *index = new IdentifierExp(Loc(), Id::p);
result = new ArrayExp(Loc(), ie, index);
}
void visit(SliceExp *e)
{
Identifier *id = Identifier::generateId("p", fparams->dim);
Parameter *param = new Parameter(STCconst, e->type, id, NULL);
fparams->shift(param);
Expression *ie = new IdentifierExp(Loc(), id);
Expression *index = new IdentifierExp(Loc(), Id::p);
result = new ArrayExp(Loc(), ie, index);
}
void visit(AssignExp *e)
{
/* Evaluate assign expressions right to left
*/
Expression *ex2 = buildArrayLoop(e->e2);
/* Need the cast because:
* b = c + p[i];
* where b is a byte fails because (c + p[i]) is an int
* which cannot be implicitly cast to byte.
*/
ex2 = new CastExp(Loc(), ex2, e->e1->type->nextOf());
Expression *ex1 = buildArrayLoop(e->e1);
Parameter *param = (*fparams)[0];
param->storageClass = 0;
result = new AssignExp(Loc(), ex1, ex2);
}
void visit(BinAssignExp *e)
{
/* Evaluate assign expressions right to left
*/
Expression *ex2 = buildArrayLoop(e->e2);
Expression *ex1 = buildArrayLoop(e->e1);
Parameter *param = (*fparams)[0];
param->storageClass = 0;
switch(e->op)
{
case TOKaddass: result = new AddAssignExp(e->loc, ex1, ex2); return;
case TOKminass: result = new MinAssignExp(e->loc, ex1, ex2); return;
case TOKmulass: result = new MulAssignExp(e->loc, ex1, ex2); return;
case TOKdivass: result = new DivAssignExp(e->loc, ex1, ex2); return;
case TOKmodass: result = new ModAssignExp(e->loc, ex1, ex2); return;
case TOKxorass: result = new XorAssignExp(e->loc, ex1, ex2); return;
case TOKandass: result = new AndAssignExp(e->loc, ex1, ex2); return;
case TOKorass: result = new OrAssignExp(e->loc, ex1, ex2); return;
case TOKpowass: result = new PowAssignExp(e->loc, ex1, ex2); return;
default:
assert(0);
}
}
void visit(NegExp *e)
{
Expression *ex1 = buildArrayLoop(e->e1);
result = new NegExp(Loc(), ex1);
}
void visit(ComExp *e)
{
Expression *ex1 = buildArrayLoop(e->e1);
result = new ComExp(Loc(), ex1);
}
void visit(BinExp *e)
{
if (isBinArrayOp(e->op))
{
/* Evaluate assign expressions left to right
*/
BinExp *be = (BinExp *)e->copy();
be->e1 = buildArrayLoop(be->e1);
be->e2 = buildArrayLoop(be->e2);
be->type = NULL;
result = be;
return;
}
else
{
visit((Expression *)e);
return;
}
}
Expression *buildArrayLoop(Expression *e)
{
e->accept(this);
return result;
}
};
BuildArrayLoopVisitor v(fparams);
return v.buildArrayLoop(e);
}
/***********************************************
* Test if expression is a unary array op.
*/
bool isUnaArrayOp(TOK op)
{
switch (op)
{
case TOKneg:
case TOKtilde:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if expression is a binary array op.
*/
bool isBinArrayOp(TOK op)
{
switch (op)
{
case TOKadd:
case TOKmin:
case TOKmul:
case TOKdiv:
case TOKmod:
case TOKxor:
case TOKand:
case TOKor:
case TOKpow:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if expression is a binary assignment array op.
*/
bool isBinAssignArrayOp(TOK op)
{
switch (op)
{
case TOKaddass:
case TOKminass:
case TOKmulass:
case TOKdivass:
case TOKmodass:
case TOKxorass:
case TOKandass:
case TOKorass:
case TOKpowass:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if operand is a valid array op operand.
*/
bool isArrayOpOperand(Expression *e)
{
//printf("Expression::isArrayOpOperand() %s\n", e->toChars());
if (e->op == TOKslice)
return true;
if (e->op == TOKarrayliteral)
{
Type *t = e->type->toBasetype();
while (t->ty == Tarray || t->ty == Tsarray)
t = t->nextOf()->toBasetype();
return (t->ty != Tvoid);
}
Type *tb = e->type->toBasetype();
if (tb->ty == Tarray)
{
return (isUnaArrayOp(e->op) ||
isBinArrayOp(e->op) ||
isBinAssignArrayOp(e->op) ||
e->op == TOKassign);
}
return false;
}