
scope {

    var i = 0;
    foreach({a:1,b:2} => k){
        ++i;
        assert 'a'===k || 'b'===k;
    }
    assert 2 === i;
    var obj = {a:1,b:2};
    i = 0;
    foreach(obj => k,v){
        assert obj[k] === v;
        if(2===++i){
            assert 'CWAL_RC_IS_VISITING'===catch{
                obj[k] = 1 /* assignment fails while iterating
                              (limitation of cwal's properties
                              model).*/
            }.codeString();
        }
    }
    assert 2 === i;

    i = var j = 0;
    foreach(@[1,2,3] => ndx,v) i+=v, j+=ndx;
    assert 6 === i;
    assert 3 === j;
    unset j;

    i = 0;
    foreach(#{#a:1, b:2} => k,v){
        assert 'a'===k || 'b'===k;
        assert 'a'===k ? 1===v : 2===v;
        ++i;
    }
    assert 2 === i;

    i = 0;
    foreach({}.prototype => k,v) ++i;
    assert i > 10;

    assert undefined === foreach(@[]=>k) assert 0;
    assert undefined === foreach({}=>k) assert 0;
    assert undefined === foreach({#}=>k) assert 0;

    // Slightly special behaviour for enums, for consistency
    // in handling them regardless of their property storage type...
    // We "just happen to know" that enums switch from objects to hashes
    // if they get "big enough" (counting their reverse mappings)...
    // 2020-02-21: enums are now always hashes, to eliminate special-case
    // handling such as this.

    var myEnum = enum {a,b,c,d,e};
    i = 0;
    foreach(myEnum=>k){
        assert typeinfo(isstring k);
        assert typeinfo(isunique myEnum[k]);
        ++i;
    }
    assert 5 === i;
    assert 5 === myEnum.#;
    i = 0;
    foreach(myEnum=>k,v){
        assert typeinfo(isstring k);
        assert typeinfo(isunique v);
        assert myEnum[k] === v;
        assert myEnum[v] === k;
        ++i;
    }
    assert i === 5;
    unset myEnum;

    assert 3 === foreach(@[1,2,3] => v){
        v<3 ? continue : break v;
        throw "impossible";
    };
    assert 2 === foreach(@[1,2,3]=>v) v%2 || break v;

    /* Empty arrays and tuples must be a no-op: */
    i = 0;
    foreach(@[]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;
    foreach([#]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;

}

scope {
    /* foreach() in a ternary must not generate a syntax error... */
    assert 1 === 0 ? foreach(@[0]=>v) v : 1;
    assert 0 === 1 ? foreach(@[0]=>v) break v : 1;
}

scope {
    var a = [1], i =0;
    a[4] = 1;
    foreach( @a => v ) ++i;
    assert 5 === i /* array visitation now includes C-level NULL entries (as the undefined value). */;
}

scope { // foreach(string=>...)
    var n = 0;
    foreach('© © '=>i,v){
        assert v === i%2 ? ' ' : '©';
        ++n;
    }
    assert 4 === n;

    n = 0;
    var x = 'にちは';
    assert x[2] === foreach(x=>i,v){
        ++n;
        0===i && assert 'に'===v;
        1===i && assert 'ち'===v;
        2===i && break v;
        assert x[i] === v;
    };
    unset x;
    assert 3 === n;

    /* We have one slightly different code path (opimization) for ASCII strings: */
    n = 0;
    foreach('abcd'=>i,v){
        0===i && assert 'a' === v;
        1===i && assert 'b' === v;
        2===i && assert 'c' === v;
        3===i && assert 'd' === v;
        ++n;
    }
    assert 4 === n;

    /* For strings, if no key is provided we iterate over the values.
       Contrast with objects, where we iterate over keys in that case. */
    n = 0;
    foreach('にちは'=>v){
        assert 'に' === v || 'ち' === v || 'は' === v;
        ++n;
    }
    assert 3 === n;

    /* Empty strings must be a no-op: */
    n = 0;
    foreach(''=>i) assert false /*loop body is never eval'd*/;
    assert 0===n;
}

scope {
    /* 20181119: allow a break in the foreach operand expression... */

    assert 1 === foreach((0|||break 1)=>k){...};
    assert -1 === foreach(scope {break -1}=>k){...};
    assert catch { /* break from inside a call() must still fail */
        foreach(proc(){break}()=>k){...}
    }.message.indexOf("'break'") > 0;
}
