2013年5月29日

harmony:spread (3)

一旦寝てから考えたらもっとシンプルな解決策を思いついたので修正。

バイトコードを生成する段階で配列であるかのように生成する、っていうんじゃあ配列のバイトコードの修正があった場合のメンテナンス性が下がるし、同じようなコードが二箇所にあるっていうのもアレ。

という事で、パーサの方で spread operator 付きの引数列を配列に変換して、更に spread operator を付けた 1 引数として返す事にする。

[js/src/frontend/Parser.cpp]

bool
Parser<ParseHandler>::argumentList(Node listNode)
{
...
    bool spreadcall = false;
    Vector<Node, 8, SystemAllocPolicy> args;

    do {
        bool spread = false;
        if (tokenStream.matchToken(TOK_TRIPLEDOT, TSF_OPERAND)) {
            if (tokenStream.matchToken(TOK_RP, TSF_OPERAND)) {
                report(ParseError, false, null(), JSMSG_SYNTAX_ERROR);
                return null();
            }
            spread = true;
            spreadcall = true;
        }

        Node argNode = assignExpr();
        if (!argNode)
            return false;
        if (spread) {
            argNode = handler.newUnary(PNK_SPREAD, argNode);
            if (!argNode)
                return null();
        }
...
#if JS_HAS_GENERATOR_EXPRS
        if (!spread && tokenStream.matchToken(TOK_FOR)) {
...
        } else
#endif
...
        args.append(argNode);
    } while (tokenStream.matchToken(TOK_COMMA));

    if (!spreadcall) {
        for (size_t i = 0, len = args.length(); i < len; i ++) {
            handler.addList(listNode, args[i]);
        }
    } else {
        size_t len = args.length();
        if (len == 1) {
            handler.addList(listNode, args[0]);
        } else {
            Node pn = handler.newList(PNK_ARRAY, args[0], JSOP_NEWINIT);
            if (!pn)
                return null();
            handler.setListFlag(pn, PNX_SPECIALARRAYINIT | PNX_NONCONST);
            for (size_t i = 1; i < len; i ++) {
                handler.addList(pn, args[i]);
            }
            pn = handler.newUnary(PNK_SPREAD, pn);
            if (!pn)
                return null();
            handler.addList(listNode, pn);
        }
    }
...
}

バイトコード生成の段階では、引数が 1 つで、かつ最初の引数に spread operator が付いているなら特別扱いする事にする。具体的には argc を -1 にして、JSOP_NOTEARG を吐かない。

[js/src/frontend/BytecodeEmitter.cpp]

static bool
EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
...
    bool spread = false;
    if (pn->pn_count == 2 && pn2->pn_next->isKind(PNK_SPREAD)) {
        spread = true;
        argc = -1;
    }
...
        if (bce->selfHostingMode && pn2->name() == cx->names().callFunction &&
            !spread)
        {
...
        }
...
        for (ParseNode *pn3 = spread ? pn2->pn_next->pn_kid : pn2->pn_next; pn3; pn3 = pn3->pn_next) {
            if (!EmitTree(cx, bce, pn3))
                return false;
            if (spread)
                continue;
            if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
                return false;
        }
...
}

例によって argc が -1 の場合は 1 って事にする。

[js/src/jsopcode.cpp]

unsigned
js::StackUses(JSScript *script, jsbytecode *pc)
{
...
      default:
        /* stack: fun, this, [argc arguments] */
        JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
                  op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
        if (GET_ARGC(pc) == (uint16_t)-1) {
            return 2 + 1;
        }
        return 2 + GET_ARGC(pc);
...
}


で、実行する時には、argc が -1 の場合スタックに乗っている配列をスタックに展開する。

[js/src/jsinterp.cpp]

BEGIN_CASE(JSOP_NEW)
BEGIN_CASE(JSOP_CALL)
BEGIN_CASE(JSOP_FUNCALL)
{
...
    CallArgs args;
    if (GET_ARGC(regs.pc) != (uint16_t)-1) {
        JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
        args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
    } else {
        JS_ASSERT(regs.stackDepth() >= 3);
        RootedObject &arr = rootObject0;
        arr = &regs.sp[-1].toObject();

        uint32_t argc;
        if (!GetLengthProperty(cx, arr, &argc))
            goto error;
        regs.sp --;

        GetElements(cx, arr, argc, regs.sp);
        regs.sp += argc;

        args = CallArgsFromSp(argc, regs.sp);
    }
...
}

という事で、

js> ((...x) => x)(10, 20, ...[30, 40, 50])
[10, 20, 30, 40, 50]
js> [].concat(...[ [10, 20, 30], [40, 50, 60], [70, 80, 90] ])
[10, 20, 30, 40, 50, 60, 70, 80, 90]

今度こそパッチを投げるのだー。
その前にドキュメント読まなくちゃさん。



…… -1 とかマジックナンバーはダメよね。
という事で定数を定義してついでに ARGC の最大値を 1 減らす修正を加える事に。

0 件のコメント:

コメントを投稿