| |
| /* 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; |
| } |