BunnyBell

From Esolang
Jump to navigation Jump to search

The World's Most Practical Esolang!

BunnyBell is a programming language created by User:PixelatedStarfish in 2022 and it is designed to be the most practical esolang possible (though some may find this heretical). It was initially created as a second major version of MacroBeep, designed to support subroutines. MacroBeep v2.0 diverged to the point where it was best renamed, as a new language entirely.

Differences include the following:

  • Macros are replaced by functions that can take arguments.
  • Cells are four bytes in size, instead of one.
  • The global tape of cells is removed and replaced by a local tape for each call to a function.
  • The global tape pointer is removed. Instead, cells are addressable via the reference operator &.
  • Instructions support more than one argument.

Changes were implemented to better meet the design goals of MacroBeep versions 1.x by means of efficient memory management, concise source code, and the elimination of global memory constructs inherited from Turing tarpits

Interpreter

An interpreter for BunnyBell is in progress. File extension is .bbe

Goals

  • Test each command for expected and unexpected arguments.
  • Test for error cases.
  • Prove useability via the implementation of the Game of Life and a simple command-line game to play.
  • Prove useability via the implementation of various program forms on the esolangs wiki, especially finite state machines that take input, display output, and demonstrate arithmetic capabilities.

Considerations

In order of priority:

  • Implement const, handle, hend [implemented]
  • Test instructions
  • Function overloading. [implemented]
  • Higher order functions.
  • Cyclic references; see linker section.
  • The shebang line

Examples of concepts:

  • Overloading
##
Output:
10
14
##
func main
 foo 5 &0
 foo 10 4 &1
 out &0
 cout 10
 out &1
return 
func foo a &loc
 add &0 a 5
return &&loc &0
func foo a b &loc
 add &0 a b
return &&loc &0
  • Higher Order Functions (which require a type)
 func main
 foo :bar &0
return
func foo :inFun &loc
 inFun &0 10 5
return &&loc &0
# &&loc is set to 15
func bar &re a b
 add &0 a b
return &&re &0
  • Cyclic references
  1. File A.bbe has the statement include B.bbe
  2. File B.bbe has the statement include A.bbe
  • Shebang line
#!/usr/bin/bbe

Design Goals: Why Use BunnyBell?

This language is best used for the fun of tinkering. It is designed for students; to ease the transition from learning a high level language to a lower level one, and to practice essential programming skills, such as tracing, debugging, and memory management.

  • It has a simplified syntax, for programs that are easier to write, organize, trace, and debug.
  • It has simple, programmer controlled memory management.
  • Significant punctuation is minimized.
  • Significant white space is minimized.

Memory

All memory in this language is bound to functions (or subroutines, if you prefer that term). Function calls implicitly have access to local tapes of addressable cells.

Cells

BunnyBell programs store information in four byte cells. A cell stores a 32-bit integer value. Cells are allocated to function calls (see Syntax) in contiguous tapes. Each cell on the tape has an address referenced by the refer operation, & and an integer. &0 refers to the first cell on the strip, &1 to the second, etc. (It may seem odd written out, but that is conventional, and it would annoy programmers to no end if &1 referred to cell 1.)

The Call Stack

This stores function calls, which are explained in the Syntax section.

Syntax

Statements

Statements are a single line of code that are interpreted to run a program. Each statement starts with a command, which can take arguments. The arguments for a command can be separated by tabs or spaces.

Functions and Calls

Functions define subprograms, and can be called to perform a task. A function call executes the sub program defined by a function. Calls cannot run include statements, but they can take input, store information in memory, and output to calling (parent) function calls. After processing include statements, all BunnyBell programs call the main function implicitly. Every function called from the main function is a child of the main function call.

The ‘func’ command starts a function definition. It takes a function name and input arguments for the function. Functions end with a ‘return’ command which takes no arguments. Outputting to the parent call is accomplished by ‘emit’ or ‘set’ commands.

When a function is called execution stops for the parent call, and resumes when the child call completes execution. The program ends when the main call completes or when a halt instruction executes.

Arguments

A command can take a few different types of arguments:

  • An ADDRESS is an argument that refers to a cell with the refer operator (&).
  • An INTEGER is any integer (whole number) in the range of 32 bits.
  • A VALUE can be an INTEGER or an ADDRESS, in the latter case, the information stored in a cell is interpreted as an integer.
  • A CHARACTER SET is any non integer argument.

Command arguments take the following order from left to right:

  • Command name
  • Addresses in order of seniority
  • Values
  • Character sets

The only exception is when their is an argument grouping of indeterminate length. That always has the rightmost position, regardless of typing.

Higher order functions may be added to this list, which would be prioritized after the command name, but before the addresses. These would be used in user-defined functions.

A Detailed Explanation of the Refer Operator (&)

The basics of how the refer operator works are explained in Cells. The ampersand refers to a cell via its address, such as &0.

To refer to cells of parent calls use a sequence of refer operators. &&0 refers to the first cell of the parent call, and &&&0 refers to the first cell of the grandparent call.

Combining the refer operator with parentheses allows cells to refer to other cells. As shown in the cat program, &(&0) Any address in parentheses is interpreted as a value, so this operation gets the value stored in cell 0 and then gets an address. Effectively, it gets an address stored in cell 0. So cell 0 refers to another cell.

Finally, note that terms do not distribute. &(&12)(&17) is not a valid expression, and it throws an error.

Grammar

Program := {(Statement | Func)}
Func := Fi, {Statement}, Re
Fi := 'func', Word, Lt
Re := 'return', Lt
Statement := Command, {Arg}, Lt
Command := Word, Sp
Arg := {(Int | Cell | Word), Sp}
Cell := ({And}, And, Int) | ({And} ‘(‘, Cell, ‘)’)
Int := any integer
Word := visible char strings
And := '&'
Sp := ' '
Lt := a newline char

Commands

There are 26 commands in BunnyBell. Note that arguments in [brackets] are optional

func
     defines a function with or without arguments
     Args: FUNCTION NAME, [FUNCTION ARGUMENTS]
return
     Ends a function and returns cell values to the 
     specified location in a parent function, such that 
     parent cells are overwritten. Note that in 
     the three argument case, the third argument must 
     refer to an address of the same call as the second.
     The third argument must also refer to an address 
     that is greater than that of the second argument.
      &1 &3  not  &3 &1 
     Args: [PARENT ADDRESS, 
     ONE OR MORE CELLS AS SPECIFED BY ARUGMENTS 2 AND 3]
set
     This sets a cell.
     Args: ADDRESS TO SET, VALUE 
add 
     This adds value to cell.
     Args: ADDRESS, VALUE
sub
     This takes value from cell.
     Args: ADDRESS, VALUE
out
     This prints the value given.
     Args: VALUE
cout 
     The prints the value given as an ASCII character.
     Args: VALUE
input
     Takes input at a cell. 
     Integer (0), char (1),  or string (2)
     Args: ADDRESS, TYPE AS VALUE
print
     Prints a string, supports escape sequences.
     Args: STRING TO PRINT
bell 
     Don’t you know? A bell goes ding! 
     It makes sound!
     Args: [HERTZ, [DURATION IN MILLIS]]
wait
     Wait for a number of milliseconds 
     (1000 if not specified).
     Args: [TIME TO WAIT IN MILLIS]
random
     Set cell to a number between MIN and MAX
     with min inclusive.
     Args: ADDRESS, MIN, MAX
halt   
     End program.
label
     A label to go to. These are not global, 
     instead they are defined locally in a function. 
     Args: LABEL NAME
beq
     Branch to LABEL if args are equal.
     Args: VALUE, VALUE, LABEL  
bneq 
     Branch to LABEL if args are not equal.
     Args: VALUE, VALUE, LABEL 
bgr 
     Branch to LABEL 
     if A is greater than B.
     Args: A, B, LABEL    
bleq 
     Branch to LABEL 
     if A is less than or equal to B.
     Args: A, B, LABEL   
goto
     Go to label specified
     Args: LABEL
istore
     Store an array of integers,
     separated by white space, starting at the 
     specified address.
     Args: ADDRESS, ARRAY CONTENT
cstore
     Store an array of characters,
     starting at the specified address.
     Args: ADDRESS, ARRAY CONTENT
bound
     Set a bound on function memory, such that cells 
     beyond the set bound are freed.
     Cells cannot be allocated beyond the bound.
     Exceeding the bound throws a Not A Cell Error.
     Args: BOUND (A VALUE)    
const
     The specified cell is now a constant.
     Args: ADDRESS
handle 
     Go to the label in any of the listed errors occurs in a handle block.
     Args: LABEL, ERRCODE OR SET OF ERRCODES 
hend
     Ends the handle block above.
include
     Include contents of specified file.
     or directory
     Args: PATH TO FILE
# 
     Inline comment.
## 
     Frames multiline comment.

Program Examples

Hello World

func main
 print Hello World
 return

Truth Machine

func main
 inp &0 0
 beq &0 0 Terminate
 label Forever
  out 1
  goto Forever
 label Terminate
  out 0
 return

Cat (Echo)

##
Note that inp is storing text as a region of continuous cells from cell one. 
&(&0) means "get the value stored at cell 0 and refer to that cell." 
&&0 would refer to cell zero of the calling macro (which does not exist) so it is not used here. 
##
func main
   print Feed the cat\n
   input &1 2
  label loopStart
    add &0 1
   cout &(&0) 
   bneq &(&0) 0 loopStart
 return

Errors

Error                                   (0)
Runtime Error                           (1)
File Not Found                          (2)
Missing Return; Unexpected End Of File  (3)
Undefined Function                      (4)
Undefined Label                         (5)
Not a Cell; Undefined Address           (6)
Unexpected Argument Type                (7)
Bad Argument                            (8)
More Arguments Expected                 (9)
Missing Main Function                  (10)
Duplicate Function Definition          (11)  
Duplicate Label Definition             (12)  
Include in Function                    (13) 
Linker Error; Check Includes           (14)
Stack Overflow                         (15)
Out of Memory                          (16)
Unmatched Parenthesis                  (17)
Numeric Error; Maximum Value is [Int]  (18)
Cannot Modify Constant                 (19)

Whenever possible, an error should describe the function and instruction at which it occurred.

Proof of Turing Completeness

An Indirect Proof

A Turing machine is defined as a finite state machine that can operate on an arbitrarily large set of contiguous cells that store information and function as a memory tape. The finite state machine controls a pointer that points to a cell on the tape. The operations a Turing machine can perform are listed as:

  • Increment the pointer.
  • Decrement the pointer.
  • Write to a cell.
  • Read from a cell.
  • Perform a logical branch.

Any program that can simulate a Turing machine is also Turing complete. The most direct proof is to simulate one, but the cat program requires some of the capabilities of a Turing machine to write an input to memory and output each character.

The cat program uses:

  • Memory cells accessed via a pointer (which is also a cell)
  • An operation to increment the pointer (add 1) which can be substituted for a decrement by inverting the argument (add -1)
  • Operations to read and write to cells.
  • A branching operation.

According to the definition above, BunnyBell can simulate all the operations of a Turing machine. Ergo BunnyBell can simulate a Turing machine, proving Turing completeness.

Proof by Translation to bf

bf is a Turing complete language with 8 commands. It can be proven that BunnyBell is also Turing complete by translating BunnyBell to bf. Assume that cells do not wrap at 0 or 255.

Given a function t, with an an arbitrarily large number of cells and a pointer to any cell stored at cell 0. The following translation can be done.

Translation Table
bf BunnyBell
> add &0 1
< add &0 -1
+ add &(&0) 1
- add &(&0) -1
. cout &(&0)
, inp &(&0) 0
[ beq &(&0) 0 closeBracket :: label openBracket
] bneq &(&0) 0 openBracket :: label closeBracket

(Please note that the double colon should be substituted for a newline.)

The Debugger

Every BunnyBell interpreter should have a debugger that prints the following at each step:

  • The current function call, with arguments.
  • The current instruction in full.
  • Value stored at the last cell accessed.
  • Any program output at that step.

The Linker

First, note that source files are run from a run directory, and its sub directories.

The linker formats source code so that it can run appropriately. All leading whitespace before an instruction is removed. Additionally, all include statements are processed. The contents of each included files are concatenated into a single set of source.

The include statement includes all the functions in a file so a program can use them. As stated above, include statements include files at the path identified. So...

include run\foo\hw.bbe

... includes the hw.bbe file in a subdirectory foo, in run. Note that the run directory (or folder) is included in the file path. This ensures that the interpreter will look for files in the run directory.

The entire contents of a directory can be included using an asterisk ( * ) like so...

include run\foo\* 

The closing angle bracket ( > ) gets replaced with the path from run to hw.bbe . So...

include run\foo\bar\tm.bbe

is equivalent to

include >\bar\tm.bbe

Handling Cyclic References

A cyclic reference is when a file includes itself, or when files are linked together in a Hamiltonian Cycle. Currently, these are handled by generic case errors. In Macrobeep there is no test case for a cyclic reference (and there should have been!). The challenge lies in handling this case without slowing the linker too much. Ideally, a specific linker error would be thrown for this situation.

A Practical Esolang?

Esoteric programming languages tend to be programming languages that are highly impractical. How could there be a language that is both practical and esoteric? Indeed there may be objections to the notion of a practical esolang. That said, do not confuse BunnyBell with some ordinary programming language. It pushes the boundaries of what an esoteric programming language can be! BunnyBell innovates into practicality! Is that not great? Is this paragraph not wonderful? Is this good search engine optimization? Programming Language! BunnyBell! May the crawlers notice this humble article as it announces itself! BunnyBell, BunnyBell, Bunny Bell, bunny bell, the programming language calls out into the great wilds of infinity! May it be heard, seen, appreciated! Enjoy this programming language; the world’s most practical esolang!