/* Module "check.wren" */
/*
Check is a class containing static methods to check that:
(1) a particular value is of a given type or types and, where possible, within
a given range of values; or
(2) a more general condition is met.
If it isn't, the fiber is aborted with an appropriate error message.
*/
class Check {
/* Private helper methods. */
static abort_(name, value, desc) {
if (name == "" || name == null) name = "Value"
Fiber.abort("'%(name)' must be %(desc), have '%(value)'.")
}
static isSafeInt_(value) {
return (value is Num) && value.isInteger && value.abs <= Num.maxSafeInteger
}
static listType_(name, value, elementType, firstOnly) {
var etype = elementType.toString
var isInt = false
if (elementType == "Int") {
etype = "Num"
isInt = true
}
var ok
var index = 0
if (value.count == 0) {
ok = true
} else if (firstOnly) {
ok = value[0].type.toString == etype && (isInt ? value[0].isInteger : true)
} else {
ok = true
for (e in value) {
if (e.type.toString != etype || (isInt && !e.isInteger)) {
ok = false
break
}
index = index + 1
}
}
if (!ok) abort_(name, value[index], "a List<%(elementType)> but, at index %(index)")
}
static mapType_(name, value, keyType, valType, firstOnly) {
listType_(name + ".keys", value.keys.toList, keyType, firstOnly)
listType_(name + ".values", value.values.toList, valType, firstOnly)
}
/* Methods to check if the value is a Num. */
static num(name, value) {
if (!(value is Num)) abort_(name, value, "a number")
}
static num(name, value, min) {
if (!((value is Num) && value >= min)) abort_(name, value, "a number >= %(min)")
}
static num(name, value, min, max) {
num("max", max, min)
if (!((value is Num) && value >= min && value <= max)) {
abort_(name, value, "a number between %(min) and %(max)")
}
}
static nonZeroNum(name, value) {
if (!((value is Num) && value != 0)) {
abort_(name, value, "a non-zero number")
}
}
static nonNegNum(name, value) { num(name, value, 0) }
static numOpt(name, value, options) {
if (!((value is Num) && options.any { |o| value == o })) {
abort_(name, value, "a number within %(options)")
}
}
/* Methods to check if the value is an an integer Num. */
static int(name, value) {
if (!((value is Num) && value.isInteger)) abort_(name, value, "an integer")
}
static int(name, value, min) {
if (!((value is Num) && value.isInteger && value >= min)) {
abort_(name, value, "an integer >= %(min)")
}
}
static nonZeroInt(name, value) {
if (!((value is Num) && value.isInteger && value != 0)) {
abort_(name, value, "a non-zero integer")
}
}
static int(name, value, min, max) {
int("max", max, min)
if (!((value is Num) && value.isInteger && value >= min && value <= max)) {
abort_(name, value, "an integer between %(min) and %(max)")
}
}
static posInt(name, value) { int(name, value, 1) }
static nonNegInt(name, value) { int(name, value, 0) }
static safeInt(name, value) {
if (!isSafeInt_(value)) abort_(name, value, "a 'safe' integer")
}
static posSafeInt(name, value) {
if (!(isSafeInt_(value) && value > 0)) {
abort_(name, value, "a positive 'safe' integer")
}
}
static nonNegSafeInt(name, value) {
if (!(isSafeInt_(value) && value >= 0)) {
abort_(name, value, "a non-negative 'safe' integer")
}
}
static nonZeroSafeInt(name, value) {
if (!(isSafeInt_(value) && value != 0)) {
abort_(name, value, "a non-zero 'safe' integer")
}
}
static intOpt(name, value, options) {
if (!((value is Num) && value.isInteger && options.any { |o| value == o })) {
abort_(name, value, "an integer within %(options)")
}
}
/* Methods to check if the value is a string or a single character. */
static str(name, value) {
if (!(value is String)) abort_(name, value, "a string")
}
static str(name, value, minLen) {
if (!((value is String) && value.count >= minLen)) {
abort_(name, value, "a string of minimum length %(minLen)")
}
}
static str(name, value, minLen, maxLen) {
int("maximum length", maxLen, minLen)
if (!((value is String) && value.count >= minLen && value.count <= maxLen)) {
if (minLen != maxLen) {
abort_(name, value, "a string betwen %(minLen) and %(maxLen) in length")
} else if (minLen == 1) {
abort_(name, value, "a character")
} else {
abort_(name, value, "a string of length %(minLen)")
}
}
}
static strOpt(name, value, options) {
if (!((value is String) && options.any { |o| value == o })) {
abort_(name, value, "a string within %(options)")
}
}
static char(name, value) { str(name, value, 1, 1) }
static char(name, value, min, max) {
int("max", max, min)
char(name, value)
if (value.codePoints[0] < min || value.codePoints[0] > max) {
abort_(name, value, "a character between '%(min)' and '%(max)'")
}
}
static charOpt(name, value, optionStr) {
char(name, value)
if (!optionStr.contains(value)) abort_(name, value, "a character within '%(optionStr)'")
}
/* Method to check if a value is a valid Wren identifier. */
static ident(name, value) {
str(name, value, 1)
var allChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
var digits = "0123456789"
return value.all { |c| allChars.indexOf(c) >= 0 } && digits.indexOf(value[0]) == -1
}
/* Methods to check if the value is a list or a typed list. */
static list(name, value) {
if (!(value is List)) abort_(name, value, "a list")
}
static list(name, value, minLen) {
if (!((value is List) && value.count >= minLen)) {
abort_(name, value, "a list of minimum length %(minLen)")
}
}
static list(name, value, minLen, maxLen) {
int("maximum length", maxLen, minLen)
if (!((value is List) && value.count >= minLen && value.count <= maxLen)) {
if (minLen != maxLen) {
abort_(name, value, "a list betwen %(minLen) and %(maxLen) in length")
} else {
abort_(name, value, "a list of length %(minLen)")
}
}
}
static typedList(name, value, elementType, firstOnly) {
list(name, value)
listType_(name, value, elementType, firstOnly)
}
static typedList(name, value, elementType, firstOnly, minLen) {
list(name, value, minLen)
listType_(name, value, elementType, firstOnly)
}
static typedList(name, value, elementType, firstOnly, minLen, maxLen) {
int("maximum length", maxLen, minLen)
list(name, value, minLen, maxLen)
listType_(name, value, elementType, firstOnly)
}
/* Methods to check if the value is a map or a typed map. */
static map(name, value) {
if (!(value is Map)) abort_(name, value, "a map")
}
static map(name, value, minLen) {
if (!((value is Map) && value.count >= minLen)) {
abort_(name, value, "a map of minimum length %(minLen)")
}
}
static map(name, value, minLen, maxLen) {
int("maximum length", maxLen, minLen)
if (!((value is Map) && value.count >= minLen && value.count <= maxLen)) {
if (minLen != maxLen) {
abort_(name, value, "a map betwen %(minLen) and %(maxLen) in length")
} else {
abort_(name, value, "a map of length %(minLen)")
}
}
}
static typedMap(name, value, keyType, valType, firstOnly) {
map(name, value)
mapType_(name, value, keyType, valType, firstOnly)
}
static typedMap(name, value, keyType, valType, firstOnly, minLen) {
map(name, value, minLen)
mapType_(name, value, keyType, valType, firstOnly)
}
static typedMap(name, value, keyType, valType, firstOnly, minLen, maxLen) {
int("maximum length", maxLen, minLen)
map(name, value, minLen, maxLen)
mapType_(name, value, keyType, valType, firstOnly)
}
/* Methods to check if a value is of some other type. */
static bool(name, value) {
if (!(value is Bool)) abort_(name, value, "a boolean")
}
static range(name, value) {
if (!(value is Range)) abort_(name, value, "a range")
}
static func(name, value) {
if (!(value is Fn)) abort_(name, value, "a function")
}
static func(name, value, arity) {
func(name, value)
if (value.arity != arity) abort_(name, value.arity, "a function with arity %(arity)")
}
static seq(name, value) {
if (!(value is Sequence)) abort_(name, value, "a sequence")
}
static cls(name, value) {
if (!(value is Class)) abort_(name, value, "a class")
}
static fiber(name, value) {
if (!(value is Fiber)) abort_(name, value, "a fiber")
}
static type(name, value, type) {
if (value.type.toString != type.toString) abort_(name, value, "an object of type %(type)")
}
static type(name, value, type, message) {
if (value.type.toString != type.toString) abort_(name, value, message)
}
static typeOpt(name, value, options) {
if (!options.any { |o| value.type.toString == o.toString }) {
abort_(name, value, "an object whose type is within %(options)")
}
}
/* Methods to check if a condition is met. */
static ok(condition, message) {
if (!condition) Fiber.abort(message)
}
static ok(condition) { ok(condition, "Condition is not met.") }
}
/*
SafeInt is a class containing self-explanatory static methods to check
integer arithmetic is 'safe' i.e. within the ± 2^53 limit.
*/
class SafeInt {
static add(i, j) {
Check.safeInt("Argument", i)
Check.safeInt("Argument", j)
var res = i + j
Check.safeInt("Result", res)
return res
}
static sub(i, j) {
Check.safeInt("Argument", i)
Check.safeInt("Argument", j)
var res = i - j
Check.safeInt("Result", res)
return res
}
static mul(i, j) {
Check.safeInt("Argument", i)
Check.safeInt("Argument", j)
var res = i * j
Check.safeInt("Result", res)
return res
}
static pow(i, j) {
Check.safeInt("Argument", i)
Check.safeInt("Argument", j)
var res = i.pow(j)
Check.safeInt("Result", res)
return res
}
}
/* SafeInts checks the safety of arithmetic operations for a list of integers. */
class SafeInts {
static sum(a) { a.reduce(0) { |acc, x| SafeInt.add(acc, x) } }
static prod(a) { a.reduce(1) { |acc, x| SafeInt.mul(acc, x) } }
}
/*
Benchmark is a class containing static methods to report
how fast a piece of code runs over a given number of iterations.
*/
class Benchmark {
// Private helper method to format results to 3 decimal places.
static fmt_(s) {
s = s.toString
var len = s.count
var ix = s.indexOf(".")
if (ix == len - 4) return s
if (ix == len - 3) return s + "0"
if (ix == len - 2) return s + "00"
if (ix == -1) return s + ".000"
return s[0..ix+3]
}
// Returns how fast 'fn' runs (best, mean, worst) over 'iter' iterations
// in milliseconds rounded to 3 decimal places.
// If 'print' is true, prints the results to the terminal in a fixed format.
static run(name, iter, print, fn) {
Check.str("Name", name, 1)
Check.posInt("Iterations", iter)
Check.func("Function", fn, 0)
var best
var worst
var sum = 0
for (i in 1..iter) {
var start = System.clock
fn.call()
var time = System.clock - start
sum = sum + time
if (i == 1) {
best = time
worst = time
} else if (time < best) {
best = time
} else if (time > worst) {
worst = time
}
}
best = (best * 1e6).round/ 1e3
worst = (worst * 1e6).round / 1e3
var mean = (sum/iter * 1e6).round / 1e3
if (print) {
var title = "Running '%(name)' over %(iter) iteration(s):"
System.print(title)
System.print("-" * title.count)
System.print("Best %(fmt_(best)) ms")
System.print("Mean %(fmt_(mean)) ms")
System.print("Worst %(fmt_(worst)) ms\n")
}
return [best, mean, worst]
}
}