diff --git a/stdlib/io.affine b/stdlib/io.affine index 2747b23..fc68363 100644 --- a/stdlib/io.affine +++ b/stdlib/io.affine @@ -37,7 +37,7 @@ use string::{ split, join }; /// /// Example: /// printf("Hello, {}! You are {} years old.", ["Alice", 30]) -fn printf(format: String, args: [Any]) -> () { +pub fn printf(format: String, args: [Any]) -> () { let flen = len(format); let mut arg_idx = 0; let mut i = 0; @@ -59,23 +59,23 @@ fn printf(format: String, args: [Any]) -> () { } /// Print formatted string with trailing newline -fn println_fmt(format: String, args: [Any]) -> () { +pub fn println_fmt(format: String, args: [Any]) -> () { printf(format, args); println(""); } /// Print debug representation of any value -fn debug(value: T) -> () { +pub fn debug(value: T) -> () { eprintln("DEBUG: " ++ show(value)); } /// Print error with newline to stderr (convenience wrapper) -fn error(msg: String) -> () { +pub fn error(msg: String) -> () { eprintln("[ERROR] " ++ msg); } /// Print warning with newline to stderr -fn warn(msg: String) -> () { +pub fn warn(msg: String) -> () { eprintln("[WARN] " ++ msg); } @@ -86,7 +86,7 @@ fn warn(msg: String) -> () { // read_file, write_file, append_file, file_exists are builtins — see module header /// Read file as a list of lines -fn read_lines(path: String) -> Result<[String], String> { +pub fn read_lines(path: String) -> Result<[String], String> { match read_file(path) { Ok(content) => Ok(split(content, "\n")), Err(msg) => Err(msg) @@ -97,7 +97,7 @@ fn read_lines(path: String) -> Result<[String], String> { /// /// Note: this reads the entire file; a more efficient builtin would be /// preferable for large files once the runtime supports stat(). -fn file_size(path: String) -> Result { +pub fn file_size(path: String) -> Result { match read_file(path) { Ok(content) => Ok(len(content)), Err(msg) => Err(msg) @@ -124,7 +124,7 @@ extern fn remove_dir(path: String) -> Result<(), String>; // ============================================================================ /// Join path components with the system separator (/) -fn path_join(components: [String]) -> String { +pub fn path_join(components: [String]) -> String { let mut result = ""; let mut first = true; for component in components { @@ -142,7 +142,7 @@ fn path_join(components: [String]) -> String { /// /// Returns None if no extension is found. /// Example: path_extension("file.txt") => Some("txt") -fn path_extension(path: String) -> Option { +pub fn path_extension(path: String) -> Option { let plen = len(path); let mut i = plen - 1; while i >= 0 { @@ -166,7 +166,7 @@ fn path_extension(path: String) -> Option { /// Get the filename component from a path /// /// Example: path_filename("/home/user/file.txt") => "file.txt" -fn path_filename(path: String) -> String { +pub fn path_filename(path: String) -> String { let plen = len(path); if plen == 0 { return ""; @@ -185,7 +185,7 @@ fn path_filename(path: String) -> String { /// Get the directory component from a path /// /// Example: path_dirname("/home/user/file.txt") => "/home/user" -fn path_dirname(path: String) -> String { +pub fn path_dirname(path: String) -> String { let plen = len(path); if plen == 0 { return "."; @@ -207,7 +207,7 @@ fn path_dirname(path: String) -> String { /// Get the filename without its extension (stem) /// /// Example: path_stem("archive.tar.gz") => "archive.tar" -fn path_stem(path: String) -> String { +pub fn path_stem(path: String) -> String { let filename = path_filename(path); let flen = len(filename); let mut i = flen - 1; @@ -239,7 +239,7 @@ extern fn chdir(path: String) -> Result<(), String>; // read_line is a builtin — see module header /// Read all input from stdin until EOF -fn read_stdin() -> Result { +pub fn read_stdin() -> Result { let mut parts = []; let mut done = false; while !done { @@ -256,7 +256,7 @@ fn read_stdin() -> Result { } /// Prompt user for input and return their response -fn prompt(message: String) -> Result { +pub fn prompt(message: String) -> Result { print(message); read_line() } @@ -268,7 +268,7 @@ fn prompt(message: String) -> Result { // time_now is a builtin — see module header /// Measure the wall-clock time of a function call (in seconds) -fn timed(f: () -> T) -> (T, Float) { +pub fn timed(f: () -> T) -> (T, Float) { let start = time_now(); let result = f(); let elapsed = time_now() - start; diff --git a/stdlib/math.affine b/stdlib/math.affine index 8da276c..783c263 100644 --- a/stdlib/math.affine +++ b/stdlib/math.affine @@ -47,31 +47,40 @@ const NEG_INFINITY: Float = -1.0 / 0.0; // ============================================================================ /// Absolute value of an integer -fn abs(x: Int) -> Int { +pub fn abs(x: Int) -> Int { if x < 0 { -x } else { x } } /// Absolute value of a float -fn abs_float(x: Float) -> Float { +pub fn abs_float(x: Float) -> Float { if x < 0.0 { -x } else { x } } /// Sign of an integer: -1, 0, or 1 -fn sign(x: Int) -> Int { +pub fn sign(x: Int) -> Int { if x > 0 { 1 } else if x < 0 { -1 } else { 0 } } /// Sign of a float: -1, 0, or 1 -fn sign_float(x: Float) -> Int { +pub fn sign_float(x: Float) -> Int { if x > 0.0 { 1 } else if x < 0.0 { -1 } else { 0 } } +/// Copy the sign of `sign_source` onto the magnitude of `magnitude`. +/// Returns `+|magnitude|` when `sign_source >= 0.0`, else `-|magnitude|`. +/// Branchless replacement for the common pattern +/// `if cond { x } else { -x }` where `cond` is itself the sign of some value. +pub fn copysign(magnitude: Float, sign_source: Float) -> Float { + let m = if magnitude < 0.0 { -magnitude } else { magnitude }; + if sign_source < 0.0 { -m } else { m } +} + // ============================================================================ // Power and roots // ============================================================================ /// Integer exponentiation via repeated squaring -fn pow(base: Int, exp: Int) -> Int { +pub fn pow(base: Int, exp: Int) -> Int { if exp == 0 { return 1; } @@ -87,12 +96,12 @@ fn pow(base: Int, exp: Int) -> Int { } /// Square of an integer -fn square(x: Int) -> Int { +pub fn square(x: Int) -> Int { x * x } /// Cube of an integer -fn cube(x: Int) -> Int { +pub fn cube(x: Int) -> Int { x * x * x } @@ -105,12 +114,12 @@ fn cube(x: Int) -> Int { // floor, ceil, round, trunc are builtins — see module header /// Convert an integer to a float -fn to_float(n: Int) -> Float { +pub fn to_float(n: Int) -> Float { float(n) } /// Fractional part of a float (x - trunc(x)) -fn fract(x: Float) -> Float { +pub fn fract(x: Float) -> Float { x - to_float(trunc(x)) } @@ -121,27 +130,27 @@ fn fract(x: Float) -> Float { // sin, cos, tan, asin, acos, atan, atan2 are builtins — see module header /// Convert degrees to radians -fn deg_to_rad(degrees: Float) -> Float { +pub fn deg_to_rad(degrees: Float) -> Float { degrees * PI / 180.0 } /// Convert radians to degrees -fn rad_to_deg(radians: Float) -> Float { +pub fn rad_to_deg(radians: Float) -> Float { radians * 180.0 / PI } /// Hyperbolic sine -fn sinh(x: Float) -> Float { +pub fn sinh(x: Float) -> Float { (exp(x) - exp(-x)) / 2.0 } /// Hyperbolic cosine -fn cosh(x: Float) -> Float { +pub fn cosh(x: Float) -> Float { (exp(x) + exp(-x)) / 2.0 } /// Hyperbolic tangent -fn tanh(x: Float) -> Float { +pub fn tanh(x: Float) -> Float { sinh(x) / cosh(x) } @@ -152,7 +161,7 @@ fn tanh(x: Float) -> Float { // exp, log, log10, log2 are builtins — see module header /// Logarithm with arbitrary base -fn log_base(base: Float, x: Float) -> Float { +pub fn log_base(base: Float, x: Float) -> Float { log(x) / log(base) } @@ -161,27 +170,27 @@ fn log_base(base: Float, x: Float) -> Float { // ============================================================================ /// Minimum of two integers -fn min_int(a: Int, b: Int) -> Int { +pub fn min_int(a: Int, b: Int) -> Int { if a < b { a } else { b } } /// Maximum of two integers -fn max_int(a: Int, b: Int) -> Int { +pub fn max_int(a: Int, b: Int) -> Int { if a > b { a } else { b } } /// Minimum of two floats -fn min_float(a: Float, b: Float) -> Float { +pub fn min_float(a: Float, b: Float) -> Float { if a < b { a } else { b } } /// Maximum of two floats -fn max_float(a: Float, b: Float) -> Float { +pub fn max_float(a: Float, b: Float) -> Float { if a > b { a } else { b } } /// Clamp an integer between min_val and max_val (inclusive) -fn clamp_int(value: Int, min_val: Int, max_val: Int) -> Int { +pub fn clamp_int(value: Int, min_val: Int, max_val: Int) -> Int { if value < min_val { min_val } else if value > max_val { @@ -192,7 +201,7 @@ fn clamp_int(value: Int, min_val: Int, max_val: Int) -> Int { } /// Clamp a float between min_val and max_val (inclusive) -fn clamp_float(value: Float, min_val: Float, max_val: Float) -> Float { +pub fn clamp_float(value: Float, min_val: Float, max_val: Float) -> Float { if value < min_val { min_val } else if value > max_val { @@ -203,7 +212,7 @@ fn clamp_float(value: Float, min_val: Float, max_val: Float) -> Float { } /// Linear interpolation between a and b by factor t (0.0 to 1.0) -fn lerp(a: Float, b: Float, t: Float) -> Float { +pub fn lerp(a: Float, b: Float, t: Float) -> Float { a + (b - a) * t } @@ -212,7 +221,7 @@ fn lerp(a: Float, b: Float, t: Float) -> Float { // ============================================================================ /// Greatest common divisor via Euclid's algorithm -fn gcd(a: Int, b: Int) -> Int { +pub fn gcd(a: Int, b: Int) -> Int { let mut x = abs(a); let mut y = abs(b); @@ -226,7 +235,7 @@ fn gcd(a: Int, b: Int) -> Int { } /// Least common multiple -fn lcm(a: Int, b: Int) -> Int { +pub fn lcm(a: Int, b: Int) -> Int { if a == 0 || b == 0 { return 0; } @@ -234,17 +243,17 @@ fn lcm(a: Int, b: Int) -> Int { } /// Check if n is even -fn is_even(n: Int) -> Bool { +pub fn is_even(n: Int) -> Bool { n % 2 == 0 } /// Check if n is odd -fn is_odd(n: Int) -> Bool { +pub fn is_odd(n: Int) -> Bool { n % 2 != 0 } /// Check if n is a prime number (trial division) -fn is_prime(n: Int) -> Bool { +pub fn is_prime(n: Int) -> Bool { if n < 2 { return false; } @@ -265,7 +274,7 @@ fn is_prime(n: Int) -> Bool { } /// Integer division rounding towards negative infinity (floor division) -fn div_floor(a: Int, b: Int) -> Int { +pub fn div_floor(a: Int, b: Int) -> Int { let q = a / b; if (a % b != 0) && ((a < 0) != (b < 0)) { q - 1 @@ -275,7 +284,7 @@ fn div_floor(a: Int, b: Int) -> Int { } /// Modulo that always returns a non-negative result -fn mod_positive(a: Int, b: Int) -> Int { +pub fn mod_positive(a: Int, b: Int) -> Int { let r = a % b; if r < 0 { r + abs(b) @@ -289,7 +298,7 @@ fn mod_positive(a: Int, b: Int) -> Int { // ============================================================================ /// Factorial of n (n!) -fn factorial(n: Int) -> Int { +pub fn factorial(n: Int) -> Int { if n <= 1 { 1 } else { @@ -298,7 +307,7 @@ fn factorial(n: Int) -> Int { } /// n-th Fibonacci number (iterative) -fn fibonacci(n: Int) -> Int { +pub fn fibonacci(n: Int) -> Int { if n <= 1 { n } else { @@ -316,17 +325,17 @@ fn fibonacci(n: Int) -> Int { } /// Sum of first n natural numbers: 1 + 2 + ... + n -fn sum_naturals(n: Int) -> Int { +pub fn sum_naturals(n: Int) -> Int { n * (n + 1) / 2 } /// Sum of first n squares: 1^2 + 2^2 + ... + n^2 -fn sum_squares(n: Int) -> Int { +pub fn sum_squares(n: Int) -> Int { n * (n + 1) * (2 * n + 1) / 6 } /// Binomial coefficient C(n, k) = n! / (k! * (n-k)!) -fn binomial(n: Int, k: Int) -> Int { +pub fn binomial(n: Int, k: Int) -> Int { if k < 0 || k > n { return 0; } @@ -346,7 +355,7 @@ fn binomial(n: Int, k: Int) -> Int { // ============================================================================ /// Arithmetic mean of a list of floats -fn mean(values: [Float]) -> Float { +pub fn mean(values: [Float]) -> Float { let n = len(values); if n == 0 { return 0.0; @@ -359,7 +368,7 @@ fn mean(values: [Float]) -> Float { } /// Sum of a list of floats -fn sum_float(values: [Float]) -> Float { +pub fn sum_float(values: [Float]) -> Float { let mut tot = 0.0; for v in values { tot = tot + v; diff --git a/stdlib/option.affine b/stdlib/option.affine index fc5db84..3c750c1 100644 --- a/stdlib/option.affine +++ b/stdlib/option.affine @@ -20,7 +20,7 @@ use prelude::{Option, Some, None, Result, Ok, Err}; // ============================================================================ /// Map over Some value -fn map(f: T -> U, opt: Option) -> Option { +pub fn map(f: T -> U, opt: Option) -> Option { match opt { Some(value) => Some(f(value)), None => None @@ -28,7 +28,7 @@ fn map(f: T -> U, opt: Option) -> Option { } /// Apply function in Option to value in Option -fn apply(f_opt: Option U>, value_opt: Option) -> Option { +pub fn apply(f_opt: Option U>, value_opt: Option) -> Option { match (f_opt, value_opt) { (Some(f), Some(value)) => Some(f(value)), _ => None @@ -36,7 +36,7 @@ fn apply(f_opt: Option U>, value_opt: Option) -> Option { } /// Flat map (bind) over Option -fn flat_map(f: T -> Option, opt: Option) -> Option { +pub fn flat_map(f: T -> Option, opt: Option) -> Option { match opt { Some(value) => f(value), None => None @@ -44,12 +44,12 @@ fn flat_map(f: T -> Option, opt: Option) -> Option { } /// Also known as 'and_then' -fn and_then(opt: Option, f: T -> Option) -> Option { +pub fn and_then(opt: Option, f: T -> Option) -> Option { flat_map(f, opt) } /// Or else - use alternative Option if None -fn or_else(opt: Option, alternative: Option) -> Option { +pub fn or_else(opt: Option, alternative: Option) -> Option { match opt { Some(_) => opt, None => alternative @@ -57,7 +57,7 @@ fn or_else(opt: Option, alternative: Option) -> Option { } /// Or else computed - compute alternative only if None -fn or_else_with(opt: Option, f: () -> Option) -> Option { +pub fn or_else_with(opt: Option, f: () -> Option) -> Option { match opt { Some(_) => opt, None => f() @@ -69,7 +69,7 @@ fn or_else_with(opt: Option, f: () -> Option) -> Option { // ============================================================================ /// Check if Option is Some -fn is_some(opt: Option) -> Bool { +pub fn is_some(opt: Option) -> Bool { match opt { Some(_) => true, None => false @@ -77,7 +77,7 @@ fn is_some(opt: Option) -> Bool { } /// Check if Option is None -fn is_none(opt: Option) -> Bool { +pub fn is_none(opt: Option) -> Bool { match opt { Some(_) => false, None => true @@ -85,7 +85,7 @@ fn is_none(opt: Option) -> Bool { } /// Check if Option contains specific value -fn contains(opt: Option, value: T) -> Bool { +pub fn contains(opt: Option, value: T) -> Bool { match opt { Some(x) => x == value, None => false @@ -97,7 +97,7 @@ fn contains(opt: Option, value: T) -> Bool { // ============================================================================ /// Unwrap Some value or panic -fn unwrap(opt: Option) -> T { +pub fn unwrap(opt: Option) -> T { match opt { Some(value) => value, None => panic("Called unwrap on None") @@ -105,7 +105,7 @@ fn unwrap(opt: Option) -> T { } /// Unwrap with custom error message -fn expect(opt: Option, msg: String) -> T { +pub fn expect(opt: Option, msg: String) -> T { match opt { Some(value) => value, None => panic(msg) @@ -121,7 +121,7 @@ pub fn unwrap_or(opt: Option, default: T) -> T { } /// Unwrap or compute default value -fn unwrap_or_else(opt: Option, f: () -> T) -> T { +pub fn unwrap_or_else(opt: Option, f: () -> T) -> T { match opt { Some(value) => value, None => f() @@ -129,7 +129,7 @@ fn unwrap_or_else(opt: Option, f: () -> T) -> T { } /// Get value or return from function with default -fn ok_or(opt: Option, err: E) -> Result { +pub fn ok_or(opt: Option, err: E) -> Result { match opt { Some(value) => Ok(value), None => Err(err) @@ -137,7 +137,7 @@ fn ok_or(opt: Option, err: E) -> Result { } /// Get value or compute error -fn ok_or_else(opt: Option, f: () -> E) -> Result { +pub fn ok_or_else(opt: Option, f: () -> E) -> Result { match opt { Some(value) => Ok(value), None => Err(f()) @@ -149,7 +149,7 @@ fn ok_or_else(opt: Option, f: () -> E) -> Result { // ============================================================================ /// Filter Option based on predicate -fn filter(pred: T -> Bool, opt: Option) -> Option { +pub fn filter(pred: T -> Bool, opt: Option) -> Option { match opt { Some(value) => if pred(value) { Some(value) } else { None }, None => None @@ -157,7 +157,7 @@ fn filter(pred: T -> Bool, opt: Option) -> Option { } /// Zip two Options together -fn zip(a: Option, b: Option) -> Option<(A, B)> { +pub fn zip(a: Option, b: Option) -> Option<(A, B)> { match (a, b) { (Some(x), Some(y)) => Some((x, y)), _ => None @@ -165,7 +165,7 @@ fn zip(a: Option, b: Option) -> Option<(A, B)> { } /// Zip with function -fn zip_with(f: (A, B) -> C, a: Option, b: Option) -> Option { +pub fn zip_with(f: (A, B) -> C, a: Option, b: Option) -> Option { match (a, b) { (Some(x), Some(y)) => Some(f(x, y)), _ => None @@ -173,7 +173,7 @@ fn zip_with(f: (A, B) -> C, a: Option, b: Option) -> Option { } /// Unzip Option of pair -fn unzip(opt: Option<(A, B)>) -> (Option, Option) { +pub fn unzip(opt: Option<(A, B)>) -> (Option, Option) { match opt { Some((a, b)) => (Some(a), Some(b)), None => (None, None) @@ -185,7 +185,7 @@ fn unzip(opt: Option<(A, B)>) -> (Option, Option) { // ============================================================================ /// Transpose Option of list to list of Option -fn transpose(opt: Option<[T]>) -> [Option] { +pub fn transpose(opt: Option<[T]>) -> [Option] { match opt { Some(list) => { let mut result = []; @@ -199,7 +199,7 @@ fn transpose(opt: Option<[T]>) -> [Option] { } /// Collect list of Options into Option of list (None on any None) -fn collect(opts: [Option]) -> Option<[T]> { +pub fn collect(opts: [Option]) -> Option<[T]> { let mut values = []; for opt in opts { match opt { @@ -211,7 +211,7 @@ fn collect(opts: [Option]) -> Option<[T]> { } /// Filter out Nones from list -fn cat_options(opts: [Option]) -> [T] { +pub fn cat_options(opts: [Option]) -> [T] { let mut values = []; for opt in opts { match opt { @@ -226,7 +226,7 @@ fn cat_options(opts: [Option]) -> [T] { } /// Map list with function returning Option, filtering Nones -fn map_filter(f: T -> Option, list: [T]) -> [U] { +pub fn map_filter(f: T -> Option, list: [T]) -> [U] { let mut results = []; for x in list { results = results ++ [f(x)]; @@ -235,7 +235,7 @@ fn map_filter(f: T -> Option, list: [T]) -> [U] { } /// Find first Some in list of Options -fn first_some(opts: [Option]) -> Option { +pub fn first_some(opts: [Option]) -> Option { for opt in opts { match opt { Some(value) => return Some(value), @@ -246,7 +246,7 @@ fn first_some(opts: [Option]) -> Option { } /// Get head of list as Option -fn head(list: [T]) -> Option { +pub fn head(list: [T]) -> Option { if len(list) > 0 { Some(list[0]) } else { @@ -255,7 +255,7 @@ fn head(list: [T]) -> Option { } /// Get tail of list as Option -fn tail(list: [T]) -> Option<[T]> { +pub fn tail(list: [T]) -> Option<[T]> { if len(list) > 0 { Some(list[1:]) } else { @@ -264,7 +264,7 @@ fn tail(list: [T]) -> Option<[T]> { } /// Get last element of list as Option -fn last(list: [T]) -> Option { +pub fn last(list: [T]) -> Option { let n = len(list); if n > 0 { Some(list[n - 1]) @@ -274,7 +274,7 @@ fn last(list: [T]) -> Option { } /// Get element at index as Option -fn get(list: [T], index: Int) -> Option { +pub fn get(list: [T], index: Int) -> Option { if index >= 0 && index < len(list) { Some(list[index]) } else { @@ -287,7 +287,7 @@ fn get(list: [T], index: Int) -> Option { // ============================================================================ /// Convert bool to Option -fn bool_to_option(b: Bool) -> Option<()> { +pub fn bool_to_option(b: Bool) -> Option<()> { if b { Some(()) } else { @@ -296,17 +296,17 @@ fn bool_to_option(b: Bool) -> Option<()> { } /// AND operation on Options (both must be Some) -fn option_and(a: Option, b: Option) -> Option<(T, U)> { +pub fn option_and(a: Option, b: Option) -> Option<(T, U)> { zip(a, b) } /// OR operation on Options (first Some wins) -fn option_or(a: Option, b: Option) -> Option { +pub fn option_or(a: Option, b: Option) -> Option { or_else(a, b) } /// XOR operation on Options (exactly one must be Some) -fn option_xor(a: Option, b: Option) -> Option { +pub fn option_xor(a: Option, b: Option) -> Option { match (a, b) { (Some(x), None) => Some(x), (None, Some(y)) => Some(y), @@ -319,7 +319,7 @@ fn option_xor(a: Option, b: Option) -> Option { // ============================================================================ /// Flatten nested Option -fn flatten(opt: Option>) -> Option { +pub fn flatten(opt: Option>) -> Option { match opt { Some(inner) => inner, None => None @@ -327,19 +327,19 @@ fn flatten(opt: Option>) -> Option { } /// Replace None with provided Option -fn replace_none(opt: Option, replacement: Option) -> Option { +pub fn replace_none(opt: Option, replacement: Option) -> Option { or_else(opt, replacement) } /// Take value from Option, leaving None -fn take(mut opt: Option) -> Option { +pub fn take(mut opt: Option) -> Option { let result = opt; opt = None; result } /// Insert value into Option if None -fn get_or_insert(mut opt: Option, value: T) -> T { +pub fn get_or_insert(mut opt: Option, value: T) -> T { match opt { Some(x) => x, None => { diff --git a/stdlib/result.affine b/stdlib/result.affine index 3ca89cd..4e8dbee 100644 --- a/stdlib/result.affine +++ b/stdlib/result.affine @@ -18,7 +18,7 @@ use prelude::{Result, Ok, Err, Option, Some, None, map}; // ============================================================================ /// Convert Option to Result -fn option_to_result(opt: Option, err: E) -> Result { +pub fn option_to_result(opt: Option, err: E) -> Result { match opt { Some(value) => Ok(value), None => Err(err) @@ -26,7 +26,7 @@ fn option_to_result(opt: Option, err: E) -> Result { } /// Convert Result to Option (discarding error) -fn result_to_option(result: Result) -> Option { +pub fn result_to_option(result: Result) -> Option { match result { Ok(value) => Some(value), Err(_) => None @@ -38,7 +38,7 @@ fn result_to_option(result: Result) -> Option { // ============================================================================ /// Map over Ok value -fn map_ok(f: T -> U, result: Result) -> Result { +pub fn map_ok(f: T -> U, result: Result) -> Result { match result { Ok(value) => Ok(f(value)), Err(e) => Err(e) @@ -46,7 +46,7 @@ fn map_ok(f: T -> U, result: Result) -> Result { } /// Map over Err value -fn map_err(f: E -> F, result: Result) -> Result { +pub fn map_err(f: E -> F, result: Result) -> Result { match result { Ok(value) => Ok(value), Err(e) => Err(f(e)) @@ -54,7 +54,7 @@ fn map_err(f: E -> F, result: Result) -> Result { } /// Apply function in Result to value in Result -fn apply(f_result: Result U, E>, value_result: Result) -> Result { +pub fn apply(f_result: Result U, E>, value_result: Result) -> Result { match (f_result, value_result) { (Ok(f), Ok(value)) => Ok(f(value)), (Err(e), _) => Err(e), @@ -63,7 +63,7 @@ fn apply(f_result: Result U, E>, value_result: Result) -> Re } /// Flat map (bind) over Result -fn flat_map(f: T -> Result, result: Result) -> Result { +pub fn flat_map(f: T -> Result, result: Result) -> Result { match result { Ok(value) => f(value), Err(e) => Err(e) @@ -71,12 +71,12 @@ fn flat_map(f: T -> Result, result: Result) -> Result } /// Also known as 'and_then' -fn and_then(result: Result, f: T -> Result) -> Result { +pub fn and_then(result: Result, f: T -> Result) -> Result { flat_map(f, result) } /// Or else - use alternative Result on error -fn or_else(result: Result, f: E -> Result) -> Result { +pub fn or_else(result: Result, f: E -> Result) -> Result { match result { Ok(value) => Ok(value), Err(e) => f(e) @@ -88,7 +88,7 @@ fn or_else(result: Result, f: E -> Result) -> Result // ============================================================================ /// Check if Result is Ok -fn is_ok(result: Result) -> Bool { +pub fn is_ok(result: Result) -> Bool { match result { Ok(_) => true, Err(_) => false @@ -96,7 +96,7 @@ fn is_ok(result: Result) -> Bool { } /// Check if Result is Err -fn is_err(result: Result) -> Bool { +pub fn is_err(result: Result) -> Bool { match result { Ok(_) => false, Err(_) => true @@ -108,7 +108,7 @@ fn is_err(result: Result) -> Bool { // ============================================================================ /// Unwrap Ok value or panic -fn unwrap(result: Result) -> T { +pub fn unwrap(result: Result) -> T { match result { Ok(value) => value, Err(_) => panic("Called unwrap on Err value") @@ -116,7 +116,7 @@ fn unwrap(result: Result) -> T { } /// Unwrap Err value or panic -fn unwrap_err(result: Result) -> E { +pub fn unwrap_err(result: Result) -> E { match result { Ok(_) => panic("Called unwrap_err on Ok value"), Err(e) => e @@ -124,7 +124,7 @@ fn unwrap_err(result: Result) -> E { } /// Unwrap with custom error message -fn expect(result: Result, msg: String) -> T { +pub fn expect(result: Result, msg: String) -> T { match result { Ok(value) => value, Err(_) => panic(msg) @@ -132,7 +132,7 @@ fn expect(result: Result, msg: String) -> T { } /// Unwrap or provide default value -fn unwrap_or(result: Result, default: T) -> T { +pub fn unwrap_or(result: Result, default: T) -> T { match result { Ok(value) => value, Err(_) => default @@ -140,7 +140,7 @@ fn unwrap_or(result: Result, default: T) -> T { } /// Unwrap or compute default value -fn unwrap_or_else(result: Result, f: E -> T) -> T { +pub fn unwrap_or_else(result: Result, f: E -> T) -> T { match result { Ok(value) => value, Err(e) => f(e) @@ -152,7 +152,7 @@ fn unwrap_or_else(result: Result, f: E -> T) -> T { // ============================================================================ /// Transpose Option of Result to Result of Option -fn transpose_opt(opt: Option>) -> Result, E> { +pub fn transpose_opt(opt: Option>) -> Result, E> { match opt { Some(Ok(value)) => Ok(Some(value)), Some(Err(e)) => Err(e), @@ -161,7 +161,7 @@ fn transpose_opt(opt: Option>) -> Result, E> { } /// Collect list of Results into Result of list (fails on first error) -fn collect(results: [Result]) -> Result<[T], E> { +pub fn collect(results: [Result]) -> Result<[T], E> { let values = []; for result in results { match result { @@ -173,7 +173,7 @@ fn collect(results: [Result]) -> Result<[T], E> { } /// Partition list of Results into successes and failures -fn partition_results(results: [Result]) -> ([T], [E]) { +pub fn partition_results(results: [Result]) -> ([T], [E]) { let oks = []; let errs = []; for result in results { @@ -186,7 +186,7 @@ fn partition_results(results: [Result]) -> ([T], [E]) { } /// Try to apply function to all elements, collecting errors -fn try_map(f: T -> Result, list: [T]) -> Result<[U], E> { +pub fn try_map(f: T -> Result, list: [T]) -> Result<[U], E> { let results = map(list, f); collect(results) } @@ -196,7 +196,7 @@ fn try_map(f: T -> Result, list: [T]) -> Result<[U], E> { // ============================================================================ /// Convert bool to Result -fn bool_to_result(b: Bool, err: E) -> Result<(), E> { +pub fn bool_to_result(b: Bool, err: E) -> Result<(), E> { if b { Ok(()) } else { @@ -205,7 +205,7 @@ fn bool_to_result(b: Bool, err: E) -> Result<(), E> { } /// AND operation on Results (both must be Ok) -fn result_and(a: Result, b: Result) -> Result<(T, U), E> { +pub fn result_and(a: Result, b: Result) -> Result<(T, U), E> { match (a, b) { (Ok(x), Ok(y)) => Ok((x, y)), (Err(e), _) => Err(e), @@ -214,7 +214,7 @@ fn result_and(a: Result, b: Result) -> Result<(T, U), E> { } /// OR operation on Results (first Ok wins) -fn result_or(a: Result, b: Result) -> Result { +pub fn result_or(a: Result, b: Result) -> Result { match a { Ok(value) => Ok(value), Err(_) => b @@ -228,7 +228,7 @@ fn result_or(a: Result, b: Result) -> Result { /// Try block emulation - execute function and catch panics as Err. /// Named `attempt` (not `try`) because `try` is a reserved keyword /// (try/catch/finally); see #135 keyword-as-identifier slice. -fn attempt(f: () -> T) -> Result { +pub fn attempt(f: () -> T) -> Result { // TODO: Requires exception handling support try { Ok(f()) @@ -239,7 +239,7 @@ fn attempt(f: () -> T) -> Result { } /// Retry operation n times on failure -fn retry(n: Int, f: () -> Result) -> Result { +pub fn retry(n: Int, f: () -> Result) -> Result { let result = f(); match result { Ok(_) => result, diff --git a/stdlib/testing.affine b/stdlib/testing.affine index b9fb5ec..e367ae3 100644 --- a/stdlib/testing.affine +++ b/stdlib/testing.affine @@ -16,28 +16,28 @@ use prelude::{ Option, Result, Some, None, Ok, Err }; // ============================================================================ /// Assert that a condition is true; panic with message on failure -fn assert(condition: Bool, message: String) -> () { +pub fn assert(condition: Bool, message: String) -> () { if !condition { panic("Assertion failed: " ++ message); } } /// Assert two values are equal -fn assert_eq(actual: T, expected: T, message: String) -> () { +pub fn assert_eq(actual: T, expected: T, message: String) -> () { if actual != expected { panic("Assertion failed: " ++ message ++ "\n Expected: " ++ show(expected) ++ "\n Actual: " ++ show(actual)); } } /// Assert two values are not equal -fn assert_ne(actual: T, expected: T, message: String) -> () { +pub fn assert_ne(actual: T, expected: T, message: String) -> () { if actual == expected { panic("Assertion failed: values should not be equal: " ++ message ++ "\n Both: " ++ show(actual)); } } /// Assert a Result is Ok and return the inner value -fn assert_ok(result: Result, message: String) -> T { +pub fn assert_ok(result: Result, message: String) -> T { match result { Ok(value) => value, Err(e) => panic("Assertion failed: expected Ok, got Err(" ++ show(e) ++ "): " ++ message) @@ -45,7 +45,7 @@ fn assert_ok(result: Result, message: String) -> T { } /// Assert a Result is Err and return the error value -fn assert_err(result: Result, message: String) -> E { +pub fn assert_err(result: Result, message: String) -> E { match result { Ok(v) => panic("Assertion failed: expected Err, got Ok(" ++ show(v) ++ "): " ++ message), Err(e) => e @@ -53,7 +53,7 @@ fn assert_err(result: Result, message: String) -> E { } /// Assert an Option is Some and return the inner value -fn assert_some(opt: Option, message: String) -> T { +pub fn assert_some(opt: Option, message: String) -> T { match opt { Some(value) => value, None => panic("Assertion failed: expected Some, got None: " ++ message) @@ -61,7 +61,7 @@ fn assert_some(opt: Option, message: String) -> T { } /// Assert an Option is None -fn assert_none(opt: Option, message: String) -> () { +pub fn assert_none(opt: Option, message: String) -> () { match opt { Some(v) => panic("Assertion failed: expected None, got Some(" ++ show(v) ++ "): " ++ message), None => {} @@ -73,35 +73,35 @@ fn assert_none(opt: Option, message: String) -> () { // ============================================================================ /// Assert actual < bound -fn assert_lt(actual: T, bound: T, message: String) -> () { +pub fn assert_lt(actual: T, bound: T, message: String) -> () { if !(actual < bound) { panic("Assertion failed: " ++ show(actual) ++ " should be < " ++ show(bound) ++ ": " ++ message); } } /// Assert actual <= bound -fn assert_le(actual: T, bound: T, message: String) -> () { +pub fn assert_le(actual: T, bound: T, message: String) -> () { if !(actual <= bound) { panic("Assertion failed: " ++ show(actual) ++ " should be <= " ++ show(bound) ++ ": " ++ message); } } /// Assert actual > bound -fn assert_gt(actual: T, bound: T, message: String) -> () { +pub fn assert_gt(actual: T, bound: T, message: String) -> () { if !(actual > bound) { panic("Assertion failed: " ++ show(actual) ++ " should be > " ++ show(bound) ++ ": " ++ message); } } /// Assert actual >= bound -fn assert_ge(actual: T, bound: T, message: String) -> () { +pub fn assert_ge(actual: T, bound: T, message: String) -> () { if !(actual >= bound) { panic("Assertion failed: " ++ show(actual) ++ " should be >= " ++ show(bound) ++ ": " ++ message); } } /// Assert two floats are equal within an epsilon tolerance -fn assert_float_eq(actual: Float, expected: Float, epsilon: Float, message: String) -> () { +pub fn assert_float_eq(actual: Float, expected: Float, epsilon: Float, message: String) -> () { let diff = if actual > expected { actual - expected } else { expected - actual }; if diff > epsilon { panic("Assertion failed: " ++ show(actual) ++ " != " ++ show(expected) ++ " (epsilon " ++ show(epsilon) ++ "): " ++ message); @@ -113,21 +113,21 @@ fn assert_float_eq(actual: Float, expected: Float, epsilon: Float, message: Stri // ============================================================================ /// Assert a list is empty -fn assert_empty(list: [T], message: String) -> () { +pub fn assert_empty(list: [T], message: String) -> () { if len(list) != 0 { panic("Assertion failed: list should be empty (length " ++ show(len(list)) ++ "): " ++ message); } } /// Assert a list is not empty -fn assert_not_empty(list: [T], message: String) -> () { +pub fn assert_not_empty(list: [T], message: String) -> () { if len(list) == 0 { panic("Assertion failed: list should not be empty: " ++ message); } } /// Assert list has expected length -fn assert_length(list: [T], expected_len: Int, message: String) -> () { +pub fn assert_length(list: [T], expected_len: Int, message: String) -> () { let actual_len = len(list); if actual_len != expected_len { panic("Assertion failed: expected length " ++ show(expected_len) ++ ", got " ++ show(actual_len) ++ ": " ++ message); @@ -135,7 +135,7 @@ fn assert_length(list: [T], expected_len: Int, message: String) -> () { } /// Assert list contains a specific element -fn assert_contains(list: [T], element: T, message: String) -> () { +pub fn assert_contains(list: [T], element: T, message: String) -> () { let mut found = false; for x in list { if x == element { @@ -148,7 +148,7 @@ fn assert_contains(list: [T], element: T, message: String) -> () { } /// Assert a string contains a substring -fn assert_str_contains(haystack: String, needle: String, message: String) -> () { +pub fn assert_str_contains(haystack: String, needle: String, message: String) -> () { if string_find(haystack, needle) < 0 { panic("Assertion failed: \"" ++ haystack ++ "\" should contain \"" ++ needle ++ "\": " ++ message); } @@ -174,7 +174,7 @@ type TestSuite = { } /// Run a single test case, catching panics -fn run_test(test: TestCase) -> TestResult { +pub fn run_test(test: TestCase) -> TestResult { println(" Running: " ++ test.name); try { let result = test.test(); @@ -201,7 +201,7 @@ fn run_test(test: TestCase) -> TestResult { } /// Run all tests in a suite and return (passed, failed) counts -fn run_suite(suite: TestSuite) -> (Int, Int) { +pub fn run_suite(suite: TestSuite) -> (Int, Int) { println("Running suite: " ++ suite.name); let mut passed = 0; let mut failed = 0; @@ -218,7 +218,7 @@ fn run_suite(suite: TestSuite) -> (Int, Int) { } /// Run multiple test suites and return aggregate (passed, failed) counts -fn run_suites(suites: [TestSuite]) -> (Int, Int) { +pub fn run_suites(suites: [TestSuite]) -> (Int, Int) { let mut total_passed = 0; let mut total_failed = 0; @@ -239,7 +239,7 @@ fn run_suites(suites: [TestSuite]) -> (Int, Int) { // ============================================================================ /// Check that a property holds for all elements in a list -fn for_all(values: [T], prop: T -> Bool) -> TestResult { +pub fn for_all(values: [T], prop: T -> Bool) -> TestResult { for value in values { if !prop(value) { return Fail("Property failed for value: " ++ show(value)); @@ -249,7 +249,7 @@ fn for_all(values: [T], prop: T -> Bool) -> TestResult { } /// Check that a property holds for at least one element -fn exists(values: [T], prop: T -> Bool) -> TestResult { +pub fn exists(values: [T], prop: T -> Bool) -> TestResult { for value in values { if prop(value) { return Pass; @@ -259,7 +259,7 @@ fn exists(values: [T], prop: T -> Bool) -> TestResult { } /// Check that a property is an involution (applying twice yields the original) -fn assert_involution(f: T -> T, values: [T], message: String) -> () { +pub fn assert_involution(f: T -> T, values: [T], message: String) -> () { for v in values { let round_tripped = f(f(v)); if round_tripped != v { @@ -269,7 +269,7 @@ fn assert_involution(f: T -> T, values: [T], message: String) -> () { } /// Check that a binary operation is commutative over given pairs -fn assert_commutative(op: (T, T) -> R, pairs: [(T, T)], message: String) -> () { +pub fn assert_commutative(op: (T, T) -> R, pairs: [(T, T)], message: String) -> () { for (a, b) in pairs { let lhs = op(a, b); let rhs = op(b, a); @@ -293,7 +293,7 @@ type BenchResult = { /// Benchmark a function by running it for the given number of iterations. /// Returns timing statistics using the time_now() builtin. -fn bench(f: () -> (), iterations: Int) -> BenchResult { +pub fn bench(f: () -> (), iterations: Int) -> BenchResult { let start = time_now(); let mut i = 0; @@ -311,7 +311,7 @@ fn bench(f: () -> (), iterations: Int) -> BenchResult { } /// Compare performance of two functions side-by-side -fn bench_compare(name1: String, f1: () -> (), name2: String, f2: () -> (), iterations: Int) -> (BenchResult, BenchResult) { +pub fn bench_compare(name1: String, f1: () -> (), name2: String, f2: () -> (), iterations: Int) -> (BenchResult, BenchResult) { println("Benchmarking " ++ name1 ++ " vs " ++ name2 ++ " (" ++ show(iterations) ++ " iterations)"); let r1 = bench(f1, iterations);