From 14a561a40f5e389a1340021ce04c454b982899c8 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Tue, 2 Jan 2024 19:00:23 -0800 Subject: [PATCH 01/14] Adding TODO's --- scheme/base.sld | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scheme/base.sld b/scheme/base.sld index e4ef9f1e..94ba0e2a 100644 --- a/scheme/base.sld +++ b/scheme/base.sld @@ -1525,7 +1525,15 @@ "(void *data, int argc, closure _, object k, object n)" " Cyc_get_ratio(data, k, n, 0);") + ;; TODO: integrate into quotient? + (define-c fixnum? + "(void *data, int argc, closure _, object k, object obj)" + " return_closcall1(data, k, + obj_is_int(obj) ? boolean_t : boolean_f); ") + (define (quotient x y) + ;; TODO: if x and y are fixnums, do fast divide and return a fixnum + ;; TODO: above good enough or are there special cases?? (truncate (/ x y))) (define truncate-quotient quotient) From 8875c534dcbfbef405ad67170e7b51c7c5a65ab5 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sat, 6 Jan 2024 09:14:01 -0800 Subject: [PATCH 02/14] Issue #519 - allow fixnum results from Cyc_div_op Need to extend this to fast_div, but this is another good edge case. --- runtime.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime.c b/runtime.c index 9532a263..c1e7195e 100644 --- a/runtime.c +++ b/runtime.c @@ -4241,6 +4241,12 @@ object Cyc_div_op(void *data, common_type * x, object y) } x->double_t.tag = double_tag; x->double_t.value = ((double)x->integer_t.value) / (obj_obj2int(y)); + + if (x->double_t.value == round(x->double_t.value)) { + int tmp = x->double_t.value; + x->integer_t.tag = integer_tag; + x->integer_t.value = tmp; + } } else if (tx == double_tag && ty == -1) { x->double_t.value = x->double_t.value / (obj_obj2int(y)); } else if (tx == integer_tag && ty == integer_tag) { From 92d5d80cc1fba6b558cf7f2a3e4673b26ad428ca Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sat, 6 Jan 2024 09:25:26 -0800 Subject: [PATCH 03/14] Added TODO --- scheme/base.sld | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scheme/base.sld b/scheme/base.sld index 94ba0e2a..b4539130 100644 --- a/scheme/base.sld +++ b/scheme/base.sld @@ -1535,6 +1535,11 @@ ;; TODO: if x and y are fixnums, do fast divide and return a fixnum ;; TODO: above good enough or are there special cases?? (truncate (/ x y))) + ;; TODO: something like this, but do we want inline? + ;;(let ((result (/ x y))) + ;; (if (and (fixnum? x) (fixnum? y)) + ;; (exact (truncate result)) + ;; (truncate result)))) (define truncate-quotient quotient) (define truncate-remainder remainder) From 1a0f42386b948f1c9db4dff3a3967c36f5dce4f5 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sat, 6 Jan 2024 14:37:15 -0800 Subject: [PATCH 04/14] Issue #519 - Return fixnum from div if possible --- runtime.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/runtime.c b/runtime.c index c1e7195e..57549b14 100644 --- a/runtime.c +++ b/runtime.c @@ -4139,10 +4139,15 @@ object Cyc_fast_div(void *data, object ptr, object x, object y) { if (obj_is_int(y)){ if (obj_obj2int(y) == 0) { goto divbyzero; } // Overflow can occur if y = 0 || (x = 0x80000000 && y = -1) - // We already check for 0 above and the value of x above is a - // bignum, so no futher checks are required. - assign_double(ptr, (double)(obj_obj2int(x)) / obj_obj2int(y)); - return ptr; + // We already check for 0 above and the invalid value of x would + // be a bignum, so no futher checks are required. + double result = (double)(obj_obj2int(x)) / obj_obj2int(y); + if (result == round(result)) { + return obj_int2obj(result); + } else { + assign_double(ptr, result); + return ptr; + } } else if (is_object_type(y) && type_of(y) == double_tag) { assign_double(ptr, (double)(obj_obj2int(x)) / double_value(y)); return ptr; @@ -4253,6 +4258,11 @@ object Cyc_div_op(void *data, common_type * x, object y) x->double_t.tag = double_tag; x->double_t.value = ((double)x->integer_t.value) / ((integer_type *) y)->value; + if (x->double_t.value == round(x->double_t.value)) { + int tmp = x->double_t.value; + x->integer_t.tag = integer_tag; + x->integer_t.value = tmp; + } } else if (tx == double_tag && ty == integer_tag) { x->double_t.value = x->double_t.value / ((integer_type *) y)->value; } else if (tx == integer_tag && ty == double_tag) { From 43923a6e4449589b4859d1dccfa35e4c9efbb236 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sat, 6 Jan 2024 14:37:36 -0800 Subject: [PATCH 05/14] Issue #519 - Allow truncate-quotient to return fixnums Return a fixnum when fixnum args are received, per R7RS. --- scheme/base.sld | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/scheme/base.sld b/scheme/base.sld index b4539130..bb35f6aa 100644 --- a/scheme/base.sld +++ b/scheme/base.sld @@ -1525,21 +1525,17 @@ "(void *data, int argc, closure _, object k, object n)" " Cyc_get_ratio(data, k, n, 0);") - ;; TODO: integrate into quotient? - (define-c fixnum? - "(void *data, int argc, closure _, object k, object obj)" - " return_closcall1(data, k, - obj_is_int(obj) ? boolean_t : boolean_f); ") + (define-c fixnum? + "(void *data, int argc, closure _, object k, object obj)" + " return_closcall1(data, k, + obj_is_int(obj) ? boolean_t : boolean_f); " + "(void *data, object ptr, object obj)" + " return obj_is_int(obj) ? boolean_t : boolean_f; ") (define (quotient x y) - ;; TODO: if x and y are fixnums, do fast divide and return a fixnum - ;; TODO: above good enough or are there special cases?? - (truncate (/ x y))) - ;; TODO: something like this, but do we want inline? - ;;(let ((result (/ x y))) - ;; (if (and (fixnum? x) (fixnum? y)) - ;; (exact (truncate result)) - ;; (truncate result)))) + (if (and (fixnum? x) (fixnum? y)) + (exact (truncate (/ x y))) + (truncate (/ x y)))) (define truncate-quotient quotient) (define truncate-remainder remainder) From ffcbca6c3eb26135f8dcb63b4d4c60497ebff79d Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 07:23:59 -0800 Subject: [PATCH 06/14] Issue #519 - Stage new test cases --- tests/base.scm | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/base.scm b/tests/base.scm index ef6e19ec..c578a9f2 100644 --- a/tests/base.scm +++ b/tests/base.scm @@ -59,6 +59,40 @@ 1.2e+40)) ) +;TODO: +;(floor/ 5 2) =⇒ 2 1 +;(floor/ -5 2) =⇒ -3 1 +;(floor/ 5 -2) =⇒ -3 -1 +;(floor/ -5 -2) =⇒ 2 -1 +;(truncate/ 5 2) =⇒ 2 1 +;(truncate/ -5 2) =⇒ -2 -1 +;(truncate/ 5 -2) =⇒ -2 1 +;(truncate/ -5 -2) =⇒ 2 -1 +;(truncate/ -5.0 -2) =⇒ 2.0 -1.0 +; +;(gcd 32 -36) =⇒ 4 +;(gcd) =⇒ 0 +;(lcm 32 -36) =⇒ 288 +;(lcm 32.0 -36) =⇒ 288.0 ; inexact +;(lcm) =⇒ 1 +; +;(floor -4.3) =⇒ -5.0 +;(ceiling -4.3) =⇒ -4.0 +;(truncate -4.3) =⇒ -4.0 +;(round -4.3) =⇒ -4.0 +;(floor 3.5) =⇒ 3.0 +;(ceiling 3.5) =⇒ 4.0 +;(truncate 3.5) =⇒ 3.0 +;(round 3.5) =⇒ 4.0 ; inexact +;(round 7/2) =⇒ 4 ; exact +;(round 7) =⇒ 7 +; +;(numerator (/ 6 4)) =⇒ 3 +;(denominator (/ 6 4)) =⇒ 2 +;(denominator +;(inexact (/ 6 4))) =⇒ 2.0 + + (test-group "truncate" (test -1 (truncate -1)) From d048b3d4f07c9655d6ead75309a215fec04f16e7 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 07:44:04 -0800 Subject: [PATCH 07/14] Issue #519 - numerator/denominator Return fixnum or bignum values from this function when they are passed as arg, per R7RS. --- runtime.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime.c b/runtime.c index 57549b14..195cac19 100644 --- a/runtime.c +++ b/runtime.c @@ -8636,6 +8636,12 @@ void Cyc_get_ratio(void *data, object cont, object n, int numerator) // Special case make_double(val, 1.0); return_closcall1(data, cont, &val); + } else if (obj_is_int(n) || type_of(n) == bignum_tag) { + if (numerator) { + return_closcall1(data, cont, n); + } else { + return_closcall1(data, cont, obj_int2obj((1))); + } } else { double numer, denom; make_double(val, 0.0); From 034d26a18a42d3af10a272d1a12eb1a72902ea5a Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 13:10:38 -0800 Subject: [PATCH 08/14] Initial round of tests --- tests/base.scm | 69 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/tests/base.scm b/tests/base.scm index c578a9f2..48aa601a 100644 --- a/tests/base.scm +++ b/tests/base.scm @@ -59,47 +59,46 @@ 1.2e+40)) ) -;TODO: -;(floor/ 5 2) =⇒ 2 1 -;(floor/ -5 2) =⇒ -3 1 -;(floor/ 5 -2) =⇒ -3 -1 -;(floor/ -5 -2) =⇒ 2 -1 -;(truncate/ 5 2) =⇒ 2 1 -;(truncate/ -5 2) =⇒ -2 -1 -;(truncate/ 5 -2) =⇒ -2 1 -;(truncate/ -5 -2) =⇒ 2 -1 -;(truncate/ -5.0 -2) =⇒ 2.0 -1.0 -; -;(gcd 32 -36) =⇒ 4 -;(gcd) =⇒ 0 -;(lcm 32 -36) =⇒ 288 -;(lcm 32.0 -36) =⇒ 288.0 ; inexact -;(lcm) =⇒ 1 -; -;(floor -4.3) =⇒ -5.0 -;(ceiling -4.3) =⇒ -4.0 -;(truncate -4.3) =⇒ -4.0 -;(round -4.3) =⇒ -4.0 -;(floor 3.5) =⇒ 3.0 -;(ceiling 3.5) =⇒ 4.0 -;(truncate 3.5) =⇒ 3.0 -;(round 3.5) =⇒ 4.0 ; inexact -;(round 7/2) =⇒ 4 ; exact -;(round 7) =⇒ 7 -; -;(numerator (/ 6 4)) =⇒ 3 -;(denominator (/ 6 4)) =⇒ 2 -;(denominator -;(inexact (/ 6 4))) =⇒ 2.0 - - (test-group - "truncate" + "numeric operations - floor, truncate, " (test -1 (truncate -1)) (test -1.0 (truncate -1.0)) (test -1.0 (truncate -1.1)) (test -1.0 (truncate -1.1)) (test +inf.0 (truncate +inf.0)) + + (test (values 2 1) (floor/ 5 2)) + (test (values -3 1) (floor/ -5 2)) + (test (values -3 -1) (floor/ 5 -2)) + (test (values 2 -1) (floor/ -5 -2)) + (test (values 2 1) (truncate/ 5 2)) + (test (values -2 -1) (truncate/ -5 2)) + (test (values -2 1) (truncate/ 5 -2)) + (test (values 2 -1) (truncate/ -5 -2)) +; TODO: +; (test (values 2.0 -1.0) (truncate/ -5.0 -2)) + + (test 4 (gcd 32 -36)) + (test 0 (gcd)) + (test 288 (lcm 32 -36)) + (test 288.0 (lcm 32.0 -36)) + (test 1 (lcm)) + + (test -5.0 (floor -4.3)) + (test -4.0 (ceiling -4.3)) + (test -4.0 (truncate -4.3)) + (test -4.0 (round -4.3)) + (test 3.0 (floor 3.5)) + (test 4.0 (ceiling 3.5)) + (test 3.0 (truncate 3.5)) + (test 4.0 (round 3.5)) + (test 4.0 (round 7/2)) ;; Rationals not supported, so result is inexact + (test 7 (round 7)) + + ; TODO: + ;(test 3 (numerator (/ 6 4))) + ;(test 2 (denominator (/ 6 4))) + (test 2.0 (denominator (inexact (/ 6 4)))) ) (test-group From 749d4b6a0ceb4855b55bfbdf9e5f23802a4ca6b7 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 19:29:20 -0800 Subject: [PATCH 09/14] Issue #519 - Properly handle doubles in remainder --- runtime.c | 24 ++++++++++++++++++------ tests/base.scm | 8 +++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/runtime.c b/runtime.c index 195cac19..fa16c719 100644 --- a/runtime.c +++ b/runtime.c @@ -4629,6 +4629,7 @@ void Cyc_bignum_remainder(void *data, object cont, object num1, object num2, obj void Cyc_remainder(void *data, object cont, object num1, object num2) { int i = 0, j = 0; + double ii = 0, jj = 0; object result; if (obj_is_int(num1)) { if (obj_is_int(num2)){ @@ -4641,8 +4642,9 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) Cyc_bignum_remainder(data, cont, bn, num2, bn); } else if (is_object_type(num2) && type_of(num2) == double_tag){ - i = obj_obj2int(num1); - j = ((double_type *)num2)->value; + ii = obj_obj2int(num1); + jj = ((double_type *)num2)->value; + goto handledouble; } else { goto typeerror; @@ -4658,6 +4660,7 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) Cyc_bignum_remainder(data, cont, num1, num2, rem); } else if (is_object_type(num2) && type_of(num2) == double_tag){ + // TODO: correct to convert bignum to double here j = ((double_type *)num2)->value; alloc_bignum(data, bn); Cyc_int2bignum(obj_obj2int(j), &(bn->bn)); @@ -4668,18 +4671,21 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) } } else if (is_object_type(num1) && type_of(num1) == double_tag){ if (obj_is_int(num2)){ - i = ((double_type *)num1)->value; - j = obj_obj2int(num2); + ii = ((double_type *)num1)->value; + jj = obj_obj2int(num2); + goto handledouble; } else if (is_object_type(num2) && type_of(num2) == bignum_tag){ + // TODO: convert bignum to double here i = ((double_type *)num1)->value; alloc_bignum(data, bn); Cyc_int2bignum(obj_obj2int(i), &(bn->bn)); Cyc_bignum_remainder(data, cont, bn, num2, bn); } else if (is_object_type(num2) && type_of(num2) == double_tag){ - i = ((double_type *)num1)->value; - j = ((double_type *)num2)->value; + ii = ((double_type *)num1)->value; + jj = ((double_type *)num2)->value; + goto handledouble; } else { goto typeerror; @@ -4690,6 +4696,12 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) if (j == 0) { Cyc_rt_raise_msg(data, "Divide by zero"); } result = obj_int2obj(i % j); return_closcall1(data, cont, result); +handledouble: +{ + if (jj == 0) { Cyc_rt_raise_msg(data, "Divide by zero"); } + make_double(dresult, fmod(ii, jj)); + return_closcall1(data, cont, &dresult); +} typeerror: { make_string(s, "Bad argument type"); diff --git a/tests/base.scm b/tests/base.scm index 48aa601a..3fea3896 100644 --- a/tests/base.scm +++ b/tests/base.scm @@ -75,8 +75,7 @@ (test (values -2 -1) (truncate/ -5 2)) (test (values -2 1) (truncate/ 5 -2)) (test (values 2 -1) (truncate/ -5 -2)) -; TODO: -; (test (values 2.0 -1.0) (truncate/ -5.0 -2)) + (test (values 2.0 -1.0) (truncate/ -5.0 -2)) (test 4 (gcd 32 -36)) (test 0 (gcd)) @@ -95,9 +94,8 @@ (test 4.0 (round 7/2)) ;; Rationals not supported, so result is inexact (test 7 (round 7)) - ; TODO: - ;(test 3 (numerator (/ 6 4))) - ;(test 2 (denominator (/ 6 4))) + (test 3.0 (numerator (/ 6 4))) ;; Inexact because we don't support rationals yet + (test 2.0 (denominator (/ 6 4))) ;; Inexact because we don't support rationals yet (test 2.0 (denominator (inexact (/ 6 4)))) ) From 38276ffd46d712df56240099cf38d4de3610e995 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 19:31:30 -0800 Subject: [PATCH 10/14] Issue #519 - Document latest fixes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec00b84..5684dc80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Features Bug Fixes +- Updated various numeric functions to properly handle numeric type conversions, including `quotient`, `remainder`, `numerator`, `denominator`, `truncate`, `truncate-quotient`, and `/`. - Fix `exact` to properly handle complex numbers, including raising an error when passed `nan` or `inf` double values. - Ensure the runtime properly differentiates between `+inf.0` and `-inf.0`. Thanks to jpellegrini for the bug report. - jpellegrini reported that Cyclone returns `#f` when comparing complex numbers using operators other than `=`. Instead it is better to raise an error in these situations. From 88fb4b909f71cfef30d12bee723403d41a87667c Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Sun, 7 Jan 2024 19:40:32 -0800 Subject: [PATCH 11/14] Issue #519 - fix bignum TODO's in Cyc_remainder --- runtime.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/runtime.c b/runtime.c index fa16c719..3b2733e6 100644 --- a/runtime.c +++ b/runtime.c @@ -4660,11 +4660,9 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) Cyc_bignum_remainder(data, cont, num1, num2, rem); } else if (is_object_type(num2) && type_of(num2) == double_tag){ - // TODO: correct to convert bignum to double here - j = ((double_type *)num2)->value; - alloc_bignum(data, bn); - Cyc_int2bignum(obj_obj2int(j), &(bn->bn)); - Cyc_bignum_remainder(data, cont, num1, bn, bn); + ii = mp_get_double(&bignum_value(num1)); + jj = ((double_type *)num2)->value; + goto handledouble; } else { goto typeerror; @@ -4676,11 +4674,9 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) goto handledouble; } else if (is_object_type(num2) && type_of(num2) == bignum_tag){ - // TODO: convert bignum to double here - i = ((double_type *)num1)->value; - alloc_bignum(data, bn); - Cyc_int2bignum(obj_obj2int(i), &(bn->bn)); - Cyc_bignum_remainder(data, cont, bn, num2, bn); + ii = ((double_type *)num1)->value; + jj = mp_get_double(&bignum_value(num2)); + goto handledouble; } else if (is_object_type(num2) && type_of(num2) == double_tag){ ii = ((double_type *)num1)->value; @@ -4697,11 +4693,11 @@ void Cyc_remainder(void *data, object cont, object num1, object num2) result = obj_int2obj(i % j); return_closcall1(data, cont, result); handledouble: -{ - if (jj == 0) { Cyc_rt_raise_msg(data, "Divide by zero"); } - make_double(dresult, fmod(ii, jj)); - return_closcall1(data, cont, &dresult); -} + { + if (jj == 0) { Cyc_rt_raise_msg(data, "Divide by zero"); } + make_double(dresult, fmod(ii, jj)); + return_closcall1(data, cont, &dresult); + } typeerror: { make_string(s, "Bad argument type"); From e4992492b335b39965371d6c189e15f3ee1bcc40 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Mon, 8 Jan 2024 18:26:38 -0800 Subject: [PATCH 12/14] Add stub for `rationalize` --- CHANGELOG.md | 1 + scheme/base.sld | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5684dc80..c6051785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Features - Enhanced the reader to parse rationals and store them as inexact numbers. +- Add a stub for `(rationalize x y)` to `(scheme base)`. Bug Fixes diff --git a/scheme/base.sld b/scheme/base.sld index bb35f6aa..669b8cde 100644 --- a/scheme/base.sld +++ b/scheme/base.sld @@ -205,15 +205,10 @@ write-u8 binary-port? textual-port? - + rationalize ;;;; ; Possibly missing functions: -; ; u8-ready? -; -; ; No complex or rational numbers at this time -; rationalize -; ; ;; syntax-rules ;;;; ) @@ -1483,6 +1478,9 @@ "(void *data, object ptr, object z)" " return Cyc_is_complex(z); ") (define rational? number?) + ;; Stub, doesn't do much now because rationals are not supported + (define (rationalize x y) + (/ x y)) (define (max first . rest) (foldl (lambda (old new) (if (> old new) old new)) first rest)) (define (min first . rest) (foldl (lambda (old new) (if (< old new) old new)) first rest)) ; Implementations of gcd and lcm using Euclid's algorithm From e7725a2a36fa91eb4a013803e70b10e4e6d5e16a Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Mon, 8 Jan 2024 19:00:39 -0800 Subject: [PATCH 13/14] Issue #519 - Fix fxlength --- srfi/143.sld | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/srfi/143.sld b/srfi/143.sld index 2dadeebc..4edeb2a6 100644 --- a/srfi/143.sld +++ b/srfi/143.sld @@ -158,10 +158,11 @@ return_closcall1(data, k, obj_int2obj(count));") (define (fxlength i) - (ceiling (/ (log (if (fxnegative? i) - (fxneg i) - (fx+ 1 i))) - (log 2)))) + (exact + (ceiling (/ (log (if (fxnegative? i) + (fxneg i) + (fx+ 1 i))) + (log 2))))) (define (fxif mask n0 n1) (fxior (fxand mask n0) From bfc0ddc1d7bea9f669f09d89e58ab1f5b93ed094 Mon Sep 17 00:00:00 2001 From: Justin Ethier Date: Mon, 8 Jan 2024 19:44:00 -0800 Subject: [PATCH 14/14] Remove travis CI link --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4898b838..2583109e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ ![Cyclone Scheme](docs/images/cyclone-logo-04-header.png "Cyclone Scheme") -[![Travis CI](https://travis-ci.org/justinethier/cyclone.svg?branch=master)](https://travis-ci.org/justinethier/cyclone) - [![Github CI - Linux](https://github.com/justinethier/cyclone-bootstrap/workflows/Ubuntu%20Linux%20Build/badge.svg)](https://github.com/justinethier/cyclone-bootstrap) [![Github CI - MacOS](https://github.com/justinethier/cyclone-bootstrap/workflows/MacOS%20Build/badge.svg)](https://github.com/justinethier/cyclone-bootstrap)