#%% expr,barebones
a = 5; b = 3
print a, b  #: 5 3

#%% assign_optional,barebones
a = None
print a  #: None
a = 5
print a  #: 5

b: Optional[float] = Optional[float](6.5)
c: Optional[float] = 5.5
print b, c #: 6.5 5.5

#%% assign_type_alias,barebones
I = int
print I(5) #: 5

L = Dict[int, str]
l = L()
print l #: {}
l[5] = 'haha'
print l #: {5: 'haha'}

#%% assign_type_annotation,barebones
a: List[int] = []
print a  #: []

#%% assign_type_err,barebones
a = 5
if 1:
    a = 3.3  #! 'float' does not match expected type 'int'
a

#%% assign_atomic,barebones
i = 1
f = 1.1

@llvm
def xchg(d: Ptr[int], b: int) -> None:
    %tmp = atomicrmw xchg i64* %d, i64 %b seq_cst
    ret {} {}
@llvm
def aadd(d: Ptr[int], b: int) -> int:
    %tmp = atomicrmw add i64* %d, i64 %b seq_cst
    ret i64 %tmp
@llvm
def amin(d: Ptr[int], b: int) -> int:
    %tmp = atomicrmw min i64* %d, i64 %b seq_cst
    ret i64 %tmp
@llvm
def amax(d: Ptr[int], b: int) -> int:
    %tmp = atomicrmw max i64* %d, i64 %b seq_cst
    ret i64 %tmp
def min(a, b): return a if a < b else b
def max(a, b): return a if a > b else b

@extend
class int:
    def __atomic_xchg__(self: Ptr[int], i: int):
        print 'atomic:', self[0], '<-', i
        xchg(self, i)
    def __atomic_add__(self: Ptr[int], i: int):
        print 'atomic:', self[0], '+=', i
        return aadd(self, i)
    def __atomic_min__(self: Ptr[int], b: int):
        print 'atomic:', self[0], '<?=', b
        return amin(self, b)
    def __atomic_max__(self: Ptr[int], b: int):
        print 'atomic:', self[0], '>?=', b
        return amax(self, b)

@atomic
def foo(x):
    global i, f

    i += 1 #: atomic: 1 += 1
    print i #: 2
    i //= 2 #: atomic: 2 <- 1
    print i #: 1
    i = 3 #: atomic: 1 <- 3
    print i #: 3
    i = min(i, 10) #: atomic: 3 <?= 10
    print i #: 3
    i = max(i, 10) #: atomic: 3 >?= 10
    print i #: 10
    i = max(20, i) #: atomic: 10 <- 20
    print i #: 20

    f += 1.1
    f = 3.3
    f = max(f, 5.5)
foo(1)
print i, f #: 20 5.5

#%% assign_atomic_real
i = 1
f = 1.1
@atomic
def foo(x):
    global i, f

    i += 1
    print i #: 2
    i //= 2
    print i #: 1
    i = 3
    print i #: 3
    i = min(i, 10)
    print i #: 3
    i = max(i, 10)
    print i #: 10

    f += 1.1
    f = 3.3
    f = max(f, 5.5)
foo(1)
print i, f #: 10 5.5

#%% assign_member,barebones
class Foo:
    x: Optional[int]
f = Foo()
print f.x #: None
f.x = 5
print f.x #: 5

fo = Optional(Foo())
fo.x = 6
print fo.x #: 6

#%% assign_member_err_1,barebones
class Foo:
    x: Optional[int]
Foo().y = 5 #! 'Foo' object has no attribute 'y'

#%% assign_member_err_2,barebones
@tuple
class Foo:
    x: Optional[int]
Foo().x = 5 #! cannot modify tuple attributes

#%% return,barebones
def foo():
    return 1
print foo()  #: 1

def bar():
    print 2
    return
    print 1
bar()  #: 2

#%% yield,barebones
def foo():
    yield 1
print [i for i in foo()], str(foo())[:16]  #: [1] <generator at 0x

#%% yield_void,barebones
def foo():
    yield
    print 1
y = foo()
print y.done()  #: False
y.next()  #: 1
# TODO: next() should work here!
print y.done()  #: True

#%% yield_return,barebones
def foo():
    yield 1
    return
    yield 2
print list(foo())  #: [1]

#%% while,barebones
a = 3
while a:
    print a
    a -= 1
#: 3
#: 2
#: 1

#%% for_break_continue,barebones
for i in range(10):
    if i % 2 == 0:
        continue
    print i
    if i >= 5:
        break
#: 1
#: 3
#: 5

#%% for_error,barebones
for i in 1:
    pass
#! 'int' object has no attribute '__iter__'

#%% for_void,barebones
def foo(): yield
for i in foo():
    print i.__class__.__name__  #: NoneType

#%% if,barebones
for a, b in [(1, 2), (3, 3), (5, 4)]:
    if a > b:
        print '1',
    elif a == b:
        print '=',
    else:
        print '2',
print '_'  #: 2 = 1 _

if 1:
    print '1' #: 1

#%% static_if,barebones
def foo(x, N: Static[int]):
    if isinstance(x, int):
        return x + 1
    elif isinstance(x, float):
        return x.__pow__(.5)
    elif isinstance(x, Tuple[int, str]):
        return f'foo: {x[1]}'
    elif isinstance(x, Tuple) and (N >= 3 or staticlen(x) > 2):
        return x[2:]
    elif hasattr(x, '__len__'):
        return 'len ' + str(x.__len__())
    else:
        compile_error('invalid type')
print foo(N=1, x=1) #: 2
print foo(N=1, x=2.0) #: 1.41421
print foo(N=1, x=(1, 'bar')) #: foo: bar
print foo(N=1, x=(1, 2)) #: len 2
print foo(N=3, x=(1, 2)) #: ()
print foo(N=1, x=(1, 2, 3)) #: (3)

#%% try_throw,barebones
class MyError(Static[Exception]):
    def __init__(self, message: str):
        super().__init__('MyError', message)
try:
    raise MyError("hello!")
except MyError as e:
    print str(e)  #: hello!
try:
    raise OSError("hello os!")
# TODO: except (MyError, OSError) as e:
#     print str(e)
except MyError:
    print "my"
except OSError as o:
    print "os", o.typename, len(o.message), o.file[-20:], o.line
    #: os OSError 9 typecheck_stmt.codon 249
finally:
    print "whoa"  #: whoa

# Test function name
def foo():
    raise MyError("foo!")
try:
    foo()
except MyError as e:
    print e.typename, e.message #: MyError foo!

#%% throw_error,barebones
raise 'hello'
#! exceptions must derive from BaseException

#%% function_builtin_error,barebones
@__force__
def foo(x):
    pass
#! builtin, exported and external functions cannot be generic

#%% extend,barebones
@extend
class int:
    def run_lola_run(self):
        while self > 0:
            yield self
            self -= 1
print list((5).run_lola_run())  #: [5, 4, 3, 2, 1]


#%% early_return,barebones
def foo(x):
    print  x-1
    return
    print len(x)
foo(5) #: 4

def foo2(x):
    if isinstance(x, int):
        print  x+1
        return
    print len(x)
foo2(1) #: 2
foo2('s') #: 1

#%% superf,barebones
class Foo:
    def foo(a):
        # superf(a)
        print 'foo-1', a
    def foo(a: int):
        superf(a)
        print 'foo-2', a
    def foo(a: str):
        superf(a)
        print 'foo-3', a
    def foo(a):
        superf(a)
        print 'foo-4', a
Foo.foo(1)
#: foo-1 1
#: foo-2 1
#: foo-4 1

class Bear:
    def woof(x):
        return f'bear woof {x}'
@extend
class Bear:
    def woof(x):
        return superf(x) + f' bear w--f {x}'
print Bear.woof('!')
#: bear woof ! bear w--f !

class PolarBear(Static[Bear]):
    def woof():
        return 'polar ' + superf('@')
print PolarBear.woof()
#: polar bear woof @ bear w--f @

#%% superf_error,barebones
class Foo:
    def foo(a):
        superf(a)
        print 'foo-1', a
Foo.foo(1)
#! no superf methods found
#! during the realization of foo(a: int)

#%% staticmethod,barebones
class Foo:
    def __repr__(self):
        return 'Foo'
    def m(self):
        print 'm', self
    @staticmethod
    def sm(i):
        print 'sm', i
Foo.sm(1)  #: sm 1
Foo().sm(2)  #: sm 2
Foo().m()  #: m Foo
