Bur
Bur is an esoteric programming language invented by Zane Porter in 2018. Bur is a simple stack-based language that features partially nondeterministic math, and no control structures other than function calls, which can be made conditionally or unconditionally.
Name
The language is named "Bur" because the author believes Bur instructions look small and prickly, like cockleburs.
Language description
Functions
Every Bur program must at least contain a main function. Every function in a Bur program that calls another function must be declared after the function they depend on. Parameters cannot be passed with function calls; they must be pushed to the stack by the calling function, and popped by the function being called. Function calls can be recursive, and any function calls can be used as the result of a conditional statement evaluating to "true."
Loops
In Bur, the only way to create a loop of any sort is by recursing a function unconditionally (for any infinite loop) or conditionally (for any terminating loop). This recursion can easily overflow the call stack, so the author recommends implementations of Bur detect loops created by recursion and replace them with corresponding for/while loops during compilation or interpretation. As with regular functions, all variables and parameters associated with looping must be bounced off the stack between iterations.
Conditionals
Conditional statements in Bur are only capable of comparing the top two elements of the stack, and making a single function call if the expression evaluates to "true." No boolean operators are defined, so complex logical expressions must be created by chaining multiple conditional statements. There are five comparison operators that can be applied to the top two elements of the stack: '=', '(', ')', '[', and ']'. These correspond to '==', '>', '<', '>=', and '<=', respectively, if read as "pop() (condition) pop()."
Math
The only math defined in Bur is the ability to pop and divide the top two elements of the stack. However, there is one caveat that prevents Bur's math from being completely deterministic. If a Bur program attempts to divide by zero, the numerator will be randomly incremented or decremented by 1. This allows for functions that use trial and error to achieve addition and subtraction.
Variables and data types
Bur can only work directly with numeric data. The details of these numbers is not well defined, but the author's own implementation uses doubles exclusively. Variables only have two functions: receive a value popped from the top of the stack, and push a copy of their current value to the stack. Assigning a value to a variable requires a literal to be pushed to the stack first, then popped into the variable.
Input and output
Bur currently has no means of direct user input. The only implemented output is the ability to print to the console being used to run the program. The default print behavior is top pop and print the top number of the stack, truncating the zeros if the number has an equivalent integer value. Printing can optionally include a newline character, and numbers being printed can be interpreted as ASCII codes to produce text output.
Comments
Comments in Bur are created by putting text or code inside double quotes.
Formatting and syntax
Whitespace is ignored completely in Bur, but it can be used to improve readability by grouping related statements together.
Implementations
The first two implementations of Bur were written by the language's author. The first implementation, created in late 2018, was an interpreter written in Java. The second, created in early 2019, was a compiler (more specifically, a transpiler) written in Windows PowerShell. The compiler takes an input file written in Bur, translates it to C, and calls GCC to compiler the C to a binary executable. The Bur compiler includes optimizations to recognize recursion and replace it with iteration (where possible) to reduce the risk of overflowing the call stack. Source code for the interpreter and compiler is available on the author's GitHub page (here).
Recommended practices
The author recommends two important practices for writing programs in Bur. First, any functions that work with the stack should clean up after themselves. A callee function should only pop elements that it pushed, and elements that were pushed for it by the caller. The callee should also only push elements that it intends to pop, or elements that it intends for its caller to pop. Second, all variables should be treated as strictly private, even when using recursion to simulate a loop.
Sample code
The following code is for a main method that simply pushes the integer 12 to the stack:
~@, #12! ;
Changing it to the following will cause it to assign 12 to the variable "foo":
~@, #12! "push 12 to the stack" ~$foo? "pop 12 off the stack and into foo" ;
Variables can push their value to the stack the same way literals can:
~@, #12! ~$foo? ~$foo! "push a copy of foo's value onto the stack" ~$bar? "pop the copy of foo's value into bar" ;
An example of printing:
~@, #65!#65!#65!#65! "push four 65's to the stack" ! "pop '65' and print to the console" !v "pop '65' and print, followed by a newline" !. "pop '65' and interpret as ascii, printing an 'A'" !v. "pop the final '65', print it as 'A', and include a newline" ;
Declare and call a function to make three copies of the top of the stack:
~@triple ~$x? ~$x! ~$x! ~$x! ; ~@, "stuff" @triple@ "call triple()" "more stuff" ;
Divide 16 by 8 and print:
~@, #16! "push 16 to the stack" #8! "push 8 to the stack" ` "pop/push 16 and 8 to the math stack, and divide" ? "pop result from the math stack to the normal stack" ! "print" ;
The same as before, but with no whitespace:
~@,#16!#8!`?!;
A Hello, World! program:
~@,#33!#100!#108!#114!#111!#87!#32!#44!#111!#108!#108!#101!#72!!.!.!.!.!.!.!.!.!.!.!.!.!v.;
The following tests whether x is > or <= zero:
~@greaterThan #62!!v. "print the character '>'" ; ~@lessThanEqualTo #60!!.#61!!v. "print '<='" ; ~@, "stuff that affects x" #0! "push 0" ~$x! "push x" ‽(greaterThan* "if (pop() > pop()){ greaterThan(); }" #0! "push 0 again" ~$x! "push x again" ‽]lessThanEqualTo* "if (pop() <= pop()){ lessThanEqualTo(); }" ;
This function uses division by zero and conditional recursion to achieve deterministic incrementation of the top element of the stack:
~@addOne ~$a? "get the starting value" ~$a!#0!`?~$b? "b is now either a+1 or a-1, with a 50-50 chance of each" ~$a! "push a copy of the original value to pick back up in case of recursion" ~$a!~$b!‽]addOne* "if b <= a, we got a decrement, so recurse to try again" ~$a? "pop the original value from the stack because we don't need it anymore" ~$b! "push the new value onto the stack to be picked back up by the original caller" ;
A Fibonacci number calculator, assuming the existence of a function with the effect "push(pop()+pop())":
~@loop ~$x? ~$y? ~$y!!v ~$x! ~$y! ~$x! @add@ @loop@ ; ~@, #0!!v #1! #1! @loop@ ;
A prime number calculator that has been obfuscated:
~@a~$a?~$a!#0!`?~$b?~$a!~$a!~$b!‽]a*~$a?~$b!;~@b~$a? ~$a!#0!`?~$b?~$a!~$a!~$b!‽[b*~$a?~$b!;~@c~$c?~$d?~$c !@b@~$c?~$d!@b@~$d?~$d!~$c!#0!~$d!‽(c*;~@d@c@~$c?~$d ?~$c!;~@e~$c?~$d?~$c!@a@~$c?~$d!@b@~$d?~$d!~$c!#0!~$ d!‽(e*;~@f@e@~$c?~$d?~$c!;~@g~$e?~$f?#1!;~@9~$e?~$f? #0!;~@8~$e?~$e!@b@~$e?~$e!#0!~$e!‽(8*;~@7~$e?~$f?~$f !~$e!`114?@8@~$e?~$e!~$e!#0!~$e!‽=g*#0!~$e!‽(9*#0!~$ e!‽)9*;~@6~$e?~$f?~$f!~$e!#0!~$f!‽=g*#1!~$f!‽=g*#1!~ $f!‽(7*;~@5#1!;~@4#0!;~@3~$g?~$h?~$h!@a@~$h?~$h!~$g! ~$h!~$g!@6@~$i?#0!~$i!‽=3*;~@2~$g?#1!~$g!@3@~$g?~$h? ~$h!~$g!‽=5*~$h!~$g!‽)4*~$h!~$g!‽(4*;~@1~$x!!v;~@0~$ x?~$x!@2@#1!‽=1*~$x!@a@@a@~$x?~$x!@0@;~@,#2!!v#3!@0@;
An obfuscated FizzBuzz program:
~@0~$a?~$a!#0!`?~$b?~$a!~$a!~$b!‽]0*~$a?~$b!;~@1~$a?~$a!#0!`?~$b?~$a!~$a!~$b!‽[1*~$a?~$b!;~@2~$ e?~$f?#1!;~@3~$e?~$f?#0!;~@5~$e?~$e!@1@~$e?~$e!#0!~$e!‽(5*;~@4~$e?~$f?~$f!~$e!`114?@5@~$e?~$e!~ $e!#0!~$e!‽=2*#0!~$e!‽(3*#0!~$e!‽)3*;~@6~$e?~$f?~$f!~$e!#0!~$f!‽=2*#1!~$f!‽=2*#1!~$f!‽(4*;~@7#1 02!!.#105!!.#122!!.#122!!.;~@8#98!!.#117!!.#122!!.#122!!.;~@9~$x!!;~@a#5!~$x!@6@#0!‽=9*;~@b~$x? #3!~$x!@6@#1!‽=7*#5!~$x!@6@#1!‽=8*#3!~$x!@6@#0!‽=a*#0!!v.~$x!@0@~$x?~$x!#100!~$x!‽]b*;~@,#1!@b@;