$print {Demonstrating Object support...}

/**
  TODO: figure out why this script, when run via the fossi1ish
  interpreter (which adds 80-100kb of memory in infrastructure)
  somehow needs 1.2MB instead of the 85kb it needs when run in
  the standalone th1ish on my 64-bit box? The main difference is
  that it is being import()ed there, but other scripts do not show
  such a drastic difference due just to an environment change.
*/

const magicValue 42
const obj object {
    a:7,
    b:9.2+3,
    f: proc {a} {
        $print "from member:" a
        //$print 'argv =' argv
        assert a === argv.0
        assert (undefined===a) || (1===[argv.length])
        return magicValue
    },
    o: object { "hi!": "world" }
}
assert magicValue === $(get obj 'f') "got"


1 && scope {
    var f obj.f
    assert 'function' === typename f
    //$print 'f =' f
    set f.foo 'bar'
    assert magicValue === $f "hi"
    //$print 'f =' f
    //$print 'obj =' obj
    //$print 'obj.a =' obj.a
    //$print 'obj.o."hi!" =' obj.o."hi!"
    assert "world" === obj.o."hi!"
}

1 && scope {
    set obj.p print
    set obj.o.p print
    $obj.o.p "via $obj.o.p"
}

1 && scope {
    var ar = array[1,2,3]
    assert 'function' === typename ar.length
    assert 3 === [ar.length]
    assert 3 === $ar.length
    assert 4 === [ar.length 4]
    set ar.6 '#6'
    assert '#6' === ar.(5+2-1)
    set ar.foo 'foobar'
    assert 7 === [ar.length]
    assert 'foobar' === ar.foo
    //$print 'ar =' ar
    assert unset ar.foo
    assert !unset ar.foo
    assert undefined === ar.foo
    var ex = catch { [ar.length "hi"] }
    $print typename ex ex
    assert 'exception' === typename ex
    $print "Caught expected exception:" ex
}

1 && scope {
    set obj.o2 obj
    assert obj === obj.o2
    set obj.o.'p' print
    assert print === obj.o.p

    set obj.o2.inner 17
    assert 17 === obj.o2.inner
    assert 7 === obj.o2.a
    set obj.(obj) obj
    assert obj === obj.(obj)
    assert 'object' === typename (obj.(obj))
    assert undefined === obj.(null)

    set obj.o.3 19
    assert 19 === obj.o.3

    assert 'double' === typename obj.(obj).b
    assert 'double' === typename obj.(obj).{b}
    //$print 'obj.(SELF).b =' obj.(obj).b
    //$print  'obj.(obj)."b" =' obj.(obj)."b"
    assert 42 === $obj.o2.f //via $obj.o2.f
}

1 && scope {
    assert undefined === obj.unknown // must not throw
    //$print 'obj.unknown =' obj.unknown
    var exp = catch { obj.unknown.foo }
    assert 'exception' === typename exp
    $print "Got expected exception:" exp
    set exp catch {
        /* Ensure that (...) disables unknown-identifier
           fudging. */
        obj.(unknown)
    }
    assert 'exception' === typename exp
    $print "Got expected exception:" exp
    //throw exp
    //throw exp.message
    //scope { throw exp }
    //not reached;
}

1 && scope {
    var o2 = object {
        a:3,
        f: proc{} {
            //$print 'this =' this
            //$print 'this.a =' this.a
            //$print 'argv =' argv
            assert argv.callee === this.f
            assert 3 === this.a
        },
        list: array[6,7,8,9]
    }
    [o2.f]
    assert 4 === $o2.list.length
    assert 9 === o2.list.3
    assert undefined === o2.list.4
    //$print 'o2.list =' o2.list
    set o2.list.4 12
    assert 12 === o2.list.4
    //$print 'o2.list =' o2.list
    assert 'exception' === typename var ex catch {
        set o2.f.ff o2.f
        $o2.f.ff // will fail on (argv.callee===this.f) check
    }
    assert 106 === ex.code
    $print "Got expected exception:" ex
}

1 && scope {
    var a array [007, 0xf, 1.3];
    set a.setLength proc {newLen} {
        assert 1 === $argv.length
        return $this.length newLen
    }
    set a.f function {} {
        //$print 'this =' this
        //$print 'this.prototype =' this.prototype
        assert 'object' === typename this.prototype
        assert 'object' === typename this.prototype.prototype
        assert 'world' === argv.callee.33
    }
    //$print '[a.propertyKeys] =' [a.propertyKeys]
    assert 2 === [[a.propertyKeys].length]
    //assert 2 === [[a.propertyKeys.apply a].length]
    assert 'world' === a.f.33 = 'world'
    [a.f]
    assert 'function' === typename a.length
    assert a.length === a.prototype.length
    assert 304 === catch { [a.prototype.length] }.code // b/c prototype is-not-a Array
    assert 5 === $a.setLength 5
    assert 5 === $a.length
    set a.([a.length]-1) 'TheEnd'
    assert 'TheEnd' === a.([a.length]-1)
    assert 'No, really' === [a.set [a.length] 'No, really']
    assert 'No, really' === a.([a.length]-1)
    assert [a.unset [a.length]-1]
    assert undefined === a.([a.length]-1)
    assert 303 === catch {[a.set -1 3]}.code
    //$print a
    var i 0, z = 0
    $a.eachIndex proc {v,i} {
        //$print '#' i '=' v
        z += 1
    }
    assert z === [a.length]

    set a.foo = "foo"
    set a.bar = "bar"
    set a.(a) = a
    set z 0
    $print "Property list:"
    $a.eachProperty proc {k,v}{
        $print '\t' k '=' v
        set z z + 1
    }
    assert z === 5 /* including the functions set above */

    var obj object {}
    set obj.eachIndex a.eachIndex
    assert 304 === catch{ $obj.eachIndex proc {} {} }.code // wrong type for eachIndex() impl
}

1 && scope {
    var max 3, count = 0
    var f proc {i=0} {
        set count i
        if {i<max} {
            //$print "Recursing..."
            [argv.callee i+1]
        }
    }
    $f
    assert max === count
}

1 && scope {
    const obj object{}
    obj .   foo = 1
    assert 1 === obj.foo
    obj.
       foo = 2
    assert 2 === obj.foo
    obj /* comments are
       ignored*/
        .foo = 3 // didn't expect this to work b/c of newline b/t them
    assert 3 === obj.foo // but hey, why not?
}


1 && assert 401 === catch {
    $print 1 2 obj // will fail due to cycles in obj
}.code
$print "As expected: previous output failed because of cycles in obj."

scope {
    const obj = object {
          p1: object{d: 4, a:1, c:2, b:3}
    }
    const sorter = proc(k,v){
        print("Sorting",k)
        const ce = argv.callee
        print("ce:",typename ce)
        assert 'function' === typename ce
        assert ce.isTheSorter
        if('object' === typename v){
            [v.sortProperties]
            [v.eachProperty ce]
            //v.eachProperty.call( v, ce )
            //v.eachProperty(ce)
            //$v.eachProperty ce
            /*
                This isa th1ish bug (in several places, it turns out):
                eachProperty() does not override the argv/this vars,
                so we're inheriting them (which is correct for 'this',
                but not argv.
    
                Would bite other algos similarly. Fixed now:
                cwal_callback_hook_set() was added to deal with this
                generically.
            */
        }
    }
    sorter.isTheSorter = true
    $obj.eachProperty sorter
    //[print obj]
    const keys = [obj.p1.propertyKeys]
    assert 4 === [keys.length]
    assert 'a' === keys.0
    assert 'd' === keys.3
}

$print "Done with script #7"
return obj.o
