#!/usr/bin/env attl
#
# Tests for the builtins except the following:
# if: if runs $1 if $0 is true, otherwise runs $2
# fail: fail execution of a procedure
# set: sets a variable
# get: get the contents of a variable
# ret: return from a procedure
# return: return from a procedure
# to: define a procedure
# see basic.attl for tests for these builtins

## Test function
# Performs a test case case and then passes the test if cond returns nonzero
# Can also rescue failures.
to test case cond {
	rescue {
		let res $1
		if [cond $res "fail"] {
			print "PASS rescue test $1: $2\n" $case $res
			return 0
		} else {
			print "FAIL rescue test $1: $2\n" $case $res
			return 0
		}
		return "$res"
	}
	let res [case]
	if [cond $res "return"] {
		print "PASS test $1: $2\n" $case $res
	} else {
		print "FAIL test $1: $2\n" $case $res
	}
}

## Output

# print: print to the environnment's current output writer
print "PASS print"

## Help
# explain: set the help for a procedure
explain "PASS" "PASS help"

# help: get help for a procedure
help "PASS"

## Strings
# sadd: returns a string  with $2 appended to string $1
test {sadd "PA" "SS"} {seq $1 "PASS"}
# sget: gets a rune from a string by index
test {sget "abµc" 2} {seq $1 181}
# slen: returns the length of a string in runes
test {slen "abµc"} {seq $1 4}
# expand: interpolate strings from environment
let e "PASS expand"
print "$1\n" [expand "$e"]

# Equality
# seq: checks if [str $1] == [str $2]
if [seq "7" 7] {
	print "PASS seq\n"
}

# ieq: checks if $1 == $2, where $1 and $2 must be Int
if [ieq 7 7] {
	print "PASS ieq\n"
}

## Integers
# iadd: adds an Int to an Int
test {iadd 3 4} {ieq $1 7}
# isub: subtracts an Int from an Int
test {isub 3 4} {ieq $1 -1}
# imul: multiplies an Int by an Int
test {imul 3 4} {ieq $1 12}
# idiv: divides an Int by an Int
test {idiv 12 4} {ieq $1 3}
# idiv: divide an Int by 0 should give an error
test {idiv 12 0} {seq $1 "division by 0"}
# ilt: checks if $1 < $2, where $1 and $2 must be Int
test {ilt 12 4} {ieq $1 0}
# ilt: checks if $1 < $2, where $1 and $2 must be Int
test {ilt 4 12} {ieq $1 -1}
# igt: checks if $1 > $2, where $1 and $2 must be Int
test {igt 12 4} {ieq $1 -1}
# igt: checks if $1 > $2, where $1 and $2 must be Int
test {igt 4 12} {ieq $1 0}
# ile: checks if $1 <= $2, where $1 and $2 must be Int
test {ile 12 4} {ieq $1 0}
# ile: checks if $1 <= $2, where $1 and $2 must be Int
test {ile 4 12} {ieq $1 -1}
# ige: checks if $1 >= $2, where $1 and $2 must be Int
test {ige 12 4} {ieq $1 -1}
# ige: checks if $1 >= $2, where $1 and $2 must be Int
test {ige 4 12} {ieq $1 0}

let i 123
# inc: increments the named integer $1 and returns it
test {inc i} {ieq $i 124}

set i 123
# dec: decrements the named integer $1 and returns it
test {dec i} {ieq $i 122}

## Booleans
# bor: returns true if one of its arguments are true
test {bor 0 0} {ieq $1 0}
# bor: returns true if one of its arguments are true
test {bor 0 -1} {ieq $1 -1}
# bor: returns true if one of its arguments are true
test {bor -1 0} {ieq $1 -1}
# bor: returns true if one of its arguments are true
test {bor -1 -1} {ieq $1 -1}
# band: returns true if all of its arguments are true
test {band 0 0} {ieq $1 0}
# band: returns true if all of its arguments are true
test {band 0 -1} {ieq $1 0}
# band: returns true if all of its arguments are true
test {band -1 0} {ieq $1 0}
# band: returns true if all of its arguments are true
test {band -1 -1} {ieq $1 -1}
# bxor: returns true if its arguments are different
test {bxor 0 0} {ieq $1 0}
# bxor: returns true if its arguments are different
test {bxor 0 -1} {ieq $1 -1}
# bxor: returns true if its arguments are different
test {bxor -1 0} {ieq $1 -1}
# bxor: returns true if its arguments are different
test {bxor -1 -1} {ieq $1 0}
# bnot
test {bnot -1 } {ieq $1 0}
# bnot
test {bnot 0 } {ieq $1 -1}

## Lists
# list: creates a new array list
let l [list 1 2 3]
test {expand "$l"} {seq $1 "[list 1 2 3]"}
# llen: returns the length of a list
test {llen $l} {ieq $1 3}
# lget: gets a value from a list by index
test {lget $l 0} {ieq $1 1}
# lget: gets a value from a list by index
test {lget $l 1} {ieq $1 2}
# lget: out of bounds
test {lget $l 77} {seq $1 "index out of range"}
# lget: out of bounds
test {lget $l -1} {seq $1 "index out of range"}
# lset: sets a value to a list by index and value
lset $l 1 7
test {lget $l 1} {ieq $1 7}
# ladd: returns a list with $2 appended to List $1
set l [ladd $l 99]
test {lget $l 3} {seq $1 99}
# leach: calls the block $4 for each entry in the list
to leach_test l {
	let ksum 0
	let vsum 0
	let reps 0
	let l2 $l
	leach $l k v {
	    inc reps
		set ksum [iadd $ksum $k] 2
		set vsum [iadd $vsum $v] 2
		# try to cause overflow, should not cause leach to change
		ladd $l2 77
	}
	return [list $ksum $vsum $reps]
}
test {leach_test $l} {set l $1; seq [str $l] "[list 6 110 4]"}

## Maps
# map: creates a new hash map
let m [map "key3" "value3"]
test {expand "$m"} {seq $1 "[map  key3 value3]"}
# mget: gets a value from a map by key
test {mget $m "key3"} {seq $1 "value3"}

# mset: sets a value to a map by key and value
mset $m "key1" "value1"
mset $m "key2" "2"
test {mget $m key2} {seq $1 "2"}
test {mget $m key1} {seq $1 "value1"}

# mkeys: returns all keys of a map as an unsorted list
test {mkeys $m} {set k $1; ieq [llen $k] 3}

# meach: calls the block $4 for each entry in the list
to meach_test m {
	let ksum ""
	let vsum ""
	let reps 0
	meach $m k v {
	    inc reps
		set ksum [sadd $ksum $k]
		set vsum [sadd $vsum [str $v]]
		# try to cause overflow, should not cause meach to change iterations
		mset $m $ksum $vsum
	}
	return $reps
}
test {meach_test $m} {set m $1;  ieq $m 3}

## Conversions
# str: converts $1 to String
test {str 123} {seq $1 "123"}

# int: converts $1 to Int
test {int "-123"} {ieq $1 -123}
test {int "banana"} {seq $1 "Value cannot be converted"}
test {int "-123banana"} {ieq $1 -123}

# val: gets the value of a value
let vi 23
# FIXME: val has a bug
# test {val $vi} {ieq $1 23}

## Types
# type: returns $1 converted to a type
test {type Foo} {seq $1 "Foo"}
# typeof: returns the type of $1 or Unknown if not known
test {typeof 1} {seq $1 [type Int]}
# typeof: String
test {typeof "foo"} {seq $1 [type String]}
# typeof: Word
test {typeof FooWord} {seq $1 [type Word]}
# teq: checks if $1 and $2 are exactly the same type"
test {teq [type Foo] [type Foo]} {ieq $1 -1}

## Control statemnts
let i 10
# while: executes $2 while $1 returns true
to while_test {
	while { igt $i 0 } {
		dec i
	}
}
test {while_test} {ieq $i 0}

# switch: selects one of many cases
to switch_test v {
	switch $v 0 {
		return 10
	} 1 {
		return 20
	} 2 {
		return 30
	} "3" {
		return 40
	} default {
		return 50
	}
}
test {switch_test 0} {ieq $1 10}
test {switch_test 1} {ieq $1 20}
test {switch_test 2} {ieq $1 30}
test {switch_test "3"} {ieq $1 40}
test {switch_test "not in case"} {ieq $1 50}

