def nothing():
	pass
assert.equal(nothing(), undefined)

add = def(a, b):
	return a+b
def sub(a, b):
	return a-b

mul = None
(def():
	nonlocal mul
	mul = def(a, b):
		return a*b

	div = def(a, b):
		return a/b
)()

assert.equal(add(1,2), 3)
assert.equal(sub(1,2), -1)
assert.equal(mul(2,2), 4)
# for some reason input to throws must be of type block, hence the 'def' wrapper
assert.throws(
	def():
		div(6,3)
	,
	/div is not defined/
)

arr = [8,4]
assert.equal(add(*arr), 12)
assert.ok(Array.isArray(arr))

def sum(*args):
	ttl = 0
	for i in args:
		ttl += i
	return ttl
assert.equal(sum(1,2,3), 6)
assert.equal(sum(1,*[2,3]), 6)

num = 4
def():
	nonlocal num
	num = 5
.call(this)

assert.equal(num, 5)

x = "foo"
y = 5
def swap(x, y):
	return y, x
x, y = swap(x, y)
assert.equal(x, 5)
assert.equal(y, "foo")

count = 0
f, r = (def():
	def fake_increment():
		# this pattern no longer allowed, compiler throws an error since you probably forgot nonlocal definition
		# count += 1
		pass
	def real_increment():
		nonlocal count
		count += 1
	return fake_increment, real_increment
)()

f()
assert.equal(count, 0)
r()
assert.equal(count, 1)

st = "this is a string"
st1 = String('another string')
assert.equal(type(st), type(st1))

# testing inlined functions
foo = {bar: 'bar'}
inlined = [
	def(x): return x+1;, def(x): return x+2;,
	def(x): return x+3
	,
	def(x): return x+4;,

    # test auto-prepend by 'return' one-statement function
    # if statement is in parens or one of:
    #   Array, ObjectLiteral, Dot (e.g. foo.bar), PropAccess (e.g. foo['bar'])
	def(x): (x+4);,
	def(x): [0, x+4];,
	def(x): {x:x+4};,
	def(x): x.bar;,
	def(x): foo[x];
]
assert.equal(inlined[0](1), 2)
assert.equal(inlined[1](1), 3)
assert.equal(inlined[2](1), 4)
assert.equal(inlined[3](1), 5)

assert.equal(inlined[4](1), 5)
assert.equal(inlined[5](1)[1], 5)
assert.equal(inlined[6](1).x, 5)
assert.equal(inlined[7](foo), 'bar')
assert.equal(inlined[8]('bar'), 'bar')

# decorators
def makebold(fn):
	def wrapped(arg):
		return "<b>" + fn(arg) + "</b>"
	return wrapped

def makeitalic(fn):
	def wrapped(arg):
		return "<i>" + fn(arg) + "</i>"
	return wrapped

@makebold
@makeitalic
def hello(something):
	return "hello " + something

assert.equal(hello("world"), "<b><i>hello world</i></b>")

# just because something is a reserved keyword in RapydScript, doesn't mean other libraries won't attempt to use it
# let's make sure we parse that correctly
five = {}
JS("five.is = function(n) { return 5 == n };")
assert.ok(five.is(5))

# function assignment via conditional
foo = 0 ? def(): return 5; : def(): return 6
bar = 0<1 ? def(): return 5; : def(): return 6
baz = 1 ? def():
    return 5
: def():
    return 6
assert.equal(foo(), 6)
assert.equal(bar(), 5)
assert.equal(baz(), 5)


# SCOPING
c = 1
d = 2
g = 3
j = 4
(def scoped(a, b='foo'):
    nonlocal g, j
    c = "bar"
    g = 5
    j = 6
    ff = 7
    assert.equal(d, 2)      # not shadowed
    assert.equal(c, "bar")  # shadowed
    assert.equal(a, "baz")  # argument
    assert.equal(b, "foo")  # default argument
    assert.equal(g, 5)      # nonlocal
    assert.equal(j, 6)      # nonlocal
    assert.equal(ff, 7)     # local
    for i in range(1, 2):
        assert.equal(i, 1)  # local iteration
)("baz")

# internal vars no longer visible
assert.throws(
    def():
        i
    ,
    /i is not defined/
)
assert.throws(
    def():
        ff
    ,
    /ff is not defined/
)
# c should revert
assert.equal(c, 1)
# g, j should stay overridden
assert.equal(g, 5)
assert.equal(j, 6)


# ANNOTATIONS
# these particular ones should be harmless, we'll test violation cases in error directory
def concat(a: String, b: String):
    return a + b

def add(a: Number, b: Number = 13) -> Number:
    return a + b

def boz(foo: "foo is often used in programming", bar: "so is bar") -> "boz, because baz was already taken":
    return bar + foo

class Qux:
    def quux(self: Qux, timeline: [Date]) -> [Date]:
        timeline.push(Date.now())
        return timeline

assert.equal(concat('air', 'plane'), 'airplane')
assert.equal(add(5, 2), 7)
assert.equal(boz(2, 11), 13)

# now that we infer types from property access, make sure this causes no errors
def Quux(a:Number, b:[Number], c:{String:Number}) -> [[Number]]:
    b.push(a)
    if a:
        return [b]
    return [[c['test']]]
    # unfortunately we can't yet assert on the case of return [[Number]] or Number since it resolves to ?, which we don't assert on

# test resolve type on deferred function definition
FOO = None
def FOO_use():
    return FOO()
FOO = def(): return 'foo';
assert.equal(FOO_use(), 'foo')


