Rainbo
Rainbo is an unimplemented concept of a programming language created by Blair Myhre in 2024. It features Lisp syntax and is rather similar to Racket. The main idea of Rainbo is that there should be a separation between compile-time and run-time, determined by syntax. This may pose many dangers when writing code, so the esoteric portion of its conception is how to always write safe code.
Syntax
The syntax of Rainbo does not stray very far from that of any arbitrary Lisp, i.e. Racket or Clojure. One of Rainbo's priority is that programmers familiar with any other given Lisp can quickly pick it up for its familiar structure.
;; a recursive fibonacci function in Rainbo can easily be translated to other Lisps (defn fib (n) (if (<= n 1) n (+ (fib (- n 2)) (fib (- n 1)))))
As an homage to Racket, a basic Rainbo program remains very simple in its grammar and doesn't add too many characters to mark things a computer couldn't otherwise differentiate. Below is a table that shows all the special characters and patterns Rainbo uses.
Pattern | Definition | Example |
---|---|---|
()
|
Encapsulates atoms into a list. Lists behave as they would in other languages. | (def foo "Hello, World!")
|
:()
|
Encapsulates atoms into an indexable list. All elements will be immutable, but unlike normal lists, elements can be indexed. | (def pi-digits :(3 1 4 1 5 9))
|
'
|
When used as a prefix, the following atom becomes literal, and not an evaluation. Literal lists can be indexed in this instance. | (get-index 1 '(fibonacci 5))
|
;;
|
Denotes a comment, which extends to the end of a line and is single-line. | ;; Hello, world!
|
:
|
When prefixed against any word, the word is identified as a keyword and serves a special meaning. | :true
|
{}
|
Denotes code that should be executed at run-time, as opposed to compile-time. Also denotes conditions for run-to-comp code. | {(def foo 'bar)}
|
;{}
|
Denotes code that should be executed at compile-time, and is only accessible at compile-time. | ;{(def x (+ 5 10))}
|
As it turns out, Rainbo is extremely simple because the only headache that needs focusing on is how the code is executed.
The Concept of Rainbo
Rainbo's sole purpose is to propose a challenge for implementation: balancing compilation and interpretation. This means that some or all code can be compiled, and some or all code can be interpreted. The only way to distinguish the two is syntax. When describing the idea over a Discord conversation, Blair Myhre stated:
You can normally define code that would be compiled, then accessible at runtime ... you can make the compiler ignore other code and only run it at runtime. I understand that would mean both comp[ilation] and interp[retation] would have to be written, which I think gives it its beauty.
Compilation and Interpretation
The highest order of code that takes precedence over all other definitions is code that is executed at compile-time and is only accessible at compile-time. Code enclosed within ;{}
is defined at compile-time, and can be read and used by other compiled code, but becomes unknown when compilation is finished. Therefore, you can actually define something twice with the same name, and they will have completely different values.
(def x (fizzbuzz 15)) ;; will be "fizz buzz" ;{(defn fizzbuzz (n) (if (and (= (mod n 3) 0) (= (mod n 5) 0)) "fizz buzz" (if (= (mod n 5) 0) "buzz" (if (= (mod n 3) 0) "fizz" n))))}
In this example, the function fizzbuzz
will be defined immediately, even before its use as a definition of x
. Normally this would result in an error because you cannot bind the value of a function that does not exist, but the function takes precedence over the variable, and so it can be evaluated.
;; this code is an addendum to the above {(def y (fizzbuzz 18))}
This code results in an error though, because even though the function was in fact defined, it does not exist in the run-time context, so the interpreter cannot properly bind a value to y
. An easy fix is to simply remove the ;{}
encapsulation.
Code accessible only at run-time has the least precedence and is evaluated last, by the interpreter. It is denoted by being within braces. Obviously, run-time context cannot carry over to compile-time context, but Rainbo actually supports a dangerous feature that allows you to save run-time context as something to be compiled the next time the compiler is used. (This feature is currently being devised...)
;{(defn (add x y) (+ x y))} (def ten (add 5 5)) {(defn (add x y) (- x y))} (def twelve (add 6 6))
In the example above, ten
will be 10 and twelve
will be 12, because even though there exists two definitions of add
where one will subtract the values, it only exists in run-time context. Therefore, the binding to twelve
ignores that definition and evaluates the definition only accessible at compile-time.
Rationale
Rainbo is simply an experiment to prove that compilation and interpretation can be effectively combined. One reason to distinguish code is that functions cannot be redefined once they are defined, so having two definitions exists only in their own respective context allows many versions of the same idea. This leads to some danger when managing code between modules or files, but that is the beauty of Rainbo: problem-solving!
Reference
This section is also a work in progress...
std
Definition | Description |
---|---|
(= x y)
|
Compares two numerical values x and y and gives a boolean if they are equal.
|
(> x y)
|
Compares two numerical values x and y and gives a boolean if x is greater than y .
|
(< x y)
|
Compares two numerical values x and y and gives a boolean if x is less than y .
|
(>= x y)
|
Compares two numerical values x and y and gives a boolean if x is greater than or equal to y .
|
(<= x y)
|
Compares two numerical values x and y and gives a boolean if x is less than or equal to y .
|
(+ x y)
|
Evaluates the sum of two numerical values x and y .
|
(- x y)
|
Evaluates the difference of two numerical values x and y .
|
(* x y)
|
Evaluates the product of two numerical values x and y .
|
(/ x y)
|
Evaluates the quotient of two numerical values x and y .
|
(def name bind-value)
|
Immutably binds the result of bind-value when it is evaluated to atom name .
|
(defn name (param?*) bind-value)
|
Immutably binds the expression bind-value to atom name so that each time name is referenced, bind-value is evaluated using the given arguments param .
|
(require lib*)
|
Immediately defines all expressions bound in any given library lib . If a library is within a directory specified in Rainbo's config, it can be accessed with that library's atom name. Otherwise, it must be a string containing the relative path to the main code file.
|
std/conc
Require the library with (require std/conc)
Provides functions to group multiple evaluations into one.
Definition | Description |
---|---|
(do eval*)
|
Evaluate all atoms and lists eval and give the last evaluated value.
|
std/io
Require the library with (require std/io)
Provides interaction between the code and IO.
Definition | Description |
---|---|
(printf str argv*)
|
Print a formatted string str to the output stream. Every argv given will take place of ~a within the string. Newlines can be given with ~n .
|
(put value)
|
Print the literal value of any given value without evaluating it first.
|
(readline type?)
|
Prompt an input and read the next line of the input stream. Optionally convert the input to a given type 'int , 'bool , or 'list of characters from the string input.
|
std/math
Require the library with (require std/math)
Provides mathematical functions beyond the basic equality comparisons and four operations.
Definition | Description |
---|---|
(** n exp)
|
Gives n to the power of exp .
|
(++ n)
|
Gives a new value of n incremented by 1.
|
(-- n)
|
Gives a new value of n decremented by 1.
|
(mod x y)
|
Gives the remainder of the quotient of x and y .
|
(round places n)
|
Rounds a given number n to places amount of decimal places after the decimal.
|
(sqrt n)
|
Gives the square-root value of n in full, or to two decimal places if its decimal value repeats more than 10 times.
|
Examples
Cat program
;; cat.rnbo (require std/io) (printf (readline))
Using the difference between Comp'd and Interp'd code
;; check_pi.rnbo ;{(require std/math) (def pi 3.14159265358979)} (defn (is-pi? n) (= (round 2 (copy pi) n))) {(require std/io) (printf (is-pi? (readline 'int)))}