SICKBAY

SICKBAY is an esoteric dialect of BASIC designed by Chris Pressey from two ideas probably conceived around 2009-2012. The two salient ideas are:


 * While most BASICs support a call stack which is used to implement  and , SICKBAY uses a call ring buffer, which supports not only   and   but also   and.
 * While some BASICs support computed line numbers in  and , SICKBAY supports computed line numbers only in line number definitions.  It thus lacks an   because, in a way similar to Strelnokoff, it doesn't seem to need one.

Syntax
A SICKBAY program is a series of lines. Each line must have a line number (which may be an expression.) Unlike BASIC, adjacent tokens must be separated by one or more spaces, if they would otherwise look like one word (e.g. you need , not  .)

The language's syntax is defined by the following EBNF (plus some pseudo-productions for terminals) grammar:

SICKBAY  ::= {Line}. Line     ::= IntExpr Stmt {":" Stmt} Newline. Stmt     ::= "REM" ArbText | "LET" IntVar "=" IntExpr | "GOTO" IntConst | "GOSUB" IntConst | "RETURN" | "END" | "PROLONG" IntConst | "CUTSHORT" | "DIM" "RING" "(" IntExpr ")" | "PRINT" (StrConst | IntExpr | "CHR$" IntExpr) [";"] | "INPUT" (IntVar | "CHR$" IntVar) . IntExpr  ::= IntVar | IntConst | "RND%" "(" IntExpr ")" | "(" IntExpr IntOp IntExpr ")" . IntOp    ::= "+" | "-" | "*" | "/". IntVar   ::= IntId ["(" IntExpr ")"]. IntId    ::= /[A-Z][A-Z0-9]%/. IntConst ::= /[0-9][0-9]*/. StrConst ::= /"[^"]*"/. ArbText   ::= /[^\n]*/. Newline   ::= /\n+/.

Semantics
Many of the SICKBAY statements have meanings very similar to those in BASIC, and I appeal to your knowledge of that language to make this description complete.

Execution
Lines are executed in numerical order, which may have nothing to do with the order they appear in the program text; however, if two lines have the same line number, the one which appears first in the program text takes precedence (the other lines with the same number are not "seen" during execution.) Execution begins initially from the lowest-numbered line in the program.

Line numbers are "live"; they are recomputed from their expressions each time execution progresses from one line to the next. (Two acceptable ways to implement this are: every time a variable x changes, recalculate the line number of every line that uses x in its line expression; or, just before any jump or proceeding to the next line, recalculate all line numbers.)

Attempting to proceed to the next line when there are no more higher-numbered lines in the program causes. is an alias for. (or ) with nothing on the call ring buffer ends the program and returns to the operating system.

The call ring buffer is of fixed size, and contains line numbers (concrete line numbers, not expressions.) If no size is chosen before any   is executed, a default size of 10 line numbers will be used. A  statement may be executed to set the size of the ring buffer if it has not yet been set. (If it has already been set, an error occurs.)

pushes the current line number onto the top of the call ring buffer and moves execution to the line with the number given to it. pops a line number from the top of the call ring buffer and moves execution to the next line in the program strictly following that line number. does not continue to execute remaining statements on the same line as the  after colons (see clarifying example below.)

pushes the given line number onto the bottom of the call ring buffer. pops a line number from the bottom of the call ring buffer. Neither of these change the flow of execution immediately. The practical effect of  is to pretend that a   was made from a line number before the first real   was ever made, effectively adding some code that will be executed after the program ends. The practical effect of  is to make the program end prematurely, when attempting to   to the rootmost caller (initially this would be the "main program".)

If space in the call ring buffer is exhausted, an error occurs.

In  and , if the given line number does not exist at the time the statement is executed, an error occurs.

Variables
All variables initially have the value zero. Any variable may be used as an array; the variable itself is just an alias for the first element of the array, i.e. .  Arrays don't have bounds and don't need dimensioning. (Although there should possibly be an option to do this, and implementations (especially for small systems like 8-bit micros) which don't wish to support unbounded integers/arrays should be allowed to require this option.)

Integers may be negative. However, the syntax for integer constants only allows non-negative integers; to give a negative constant, an expression such as  must be used. Note that this means a negative line number cannot be jumped to, as  et al must be followed by an integer constant, not an expression. (However, a negative line number may be returned to, as it is possible to write a program which begins executing at a negative line number and makes a  from it.)

Operators have no precedence; parentheses must be used around all operations (see grammar).

Like Strelnokoff,  is integer division, truncating downwards, and evaluating to zero if the divisor is zero (there is no division by zero error.)

The n  function evaluates to an integer from 0 to n-1, chosen randomly. If n is zero or negative, an error occurs.

I/O
Integer expressions may be printed; they are formatted as decimal numerals, possibly preceded by a negative sign, but, unlike most BASICs, not preceded or followed by any spaces. The ASCII character for a given integer value may be printed with the  form. Literal strings may also be printed, but only one thing may (and exactly one thing must) be printed per  statement (so to just print a blank line, print a null string literal.)  Anything printed with a   statement will be followed by a newline, unless the semicolon is given after the statement, which suppresses the newline.

The  form accepts an integer, formatted as decimal numerals, possibly preceded by a negative sign, from the input stream, and places it in the variable. Any whitespace preceding, and the first whitespace following the integer is swallowed up; if the integer is not followed by at least one whitespace character, an error occurs. The  form accepts a single character from the input stream and places its ASCII value in the variable.

Examples
Hello, world!:

20 PRINT "WORLD!" 10 PRINT "HELLO, "; 20 PRINT "SAILOR"

99 bottles of beer:

5 REM TODO: IMPROVE GRAMMAR (1 -> "BOTTLE", 0 -> "NO MORE") 10 LET B% = 99 (100+B%) END 100 GOTO 200:REM BEGIN LOOP 200 PRINT B%;:PRINT " BOTTLES OF BEER ON THE WALL," 205 PRINT B%;:PRINT " BOTTLES OF BEER," 210 PRINT "TAKE ONE DOWN, PASS IT AROUND," 215 LET B% = (B% - 1) 220 PRINT B%;:PRINT " BOTTLES OF BEER ON THE WALL.":PRINT "" 230 GOTO 100

PROLONG example (note that it simulates a GOSUB having been made from line 300, so it returns to the next line, line 400):

100 PROLONG 300 200 PRINT "PRINTED!" 300 RETURN 400 PRINT "THIS IS PRINTED TOO!"

CUTSHORT example:

100 GOSUB 300 200 PRINT "NOT PRINTED!" 300 GOSUB 600 400 PRINT "PRINTED, TOO!" 500 RETURN 600 PRINT "PRINTED!" 700 CUTSHORT 800 RETURN

Clarifying example about where RETURN returns to:

100 GOSUB 200:PRINT "NOT PRINTED" 110 PRINT "ALSO PRINTED":END 200 PRINT "THIS IS PRINTED":RETURN

Truth-machine:

100 INPUT A% (200+A%) PRINT 0; 200 PRINT 1;:GOTO 200

Range-checker (demonstrating some attempted robustness):

1 PRINT "PLEASE GIVE ME A NUMBER BETWEEN 8 AND 47: ";:INPUT A%:GOTO15 ((A%+1)*2) LET B%=2 15 REM PLACEHOLDER 101 REM OUTSIDE BOUNDS (101-B%) PRINT "THANKS!":END (((A%*A%)+1)*4000) PRINT "TOO SMALL!":END ((((A%*A%)+1)*4000)-B%) PRINT "TOO LARGE!":END

(TODO: turn that into a "guess the secret number" game.)

Programming techniques

 * computes A mod 7.
 * is 0 if A% is 0, 1 otherwise. Then you can use   for AND and   for OR.
 * To convert the following conditional statement from a less esoteric BASIC:

300 IF S%=20 THEN GOTO 500 ELSE GOTO 700

...assuming you don't use line 301, you can say:

(300+((S%-20)/(S%-20))) GOTO 500 300 GOTO 700

This suggests it is possible to code any 2-symbol Turing machine in SICKBAY (and thus that SICKBAY is Turing-complete): use  to contain a number representing the state of the finite control (including a halt state),   for the tape, and   for the tape head. You only need to branch conditionally on.

Extensions
Consider the above description to describe SICKBAY 1.0. SICKBAY 2.0 may or may not contain some of the following features:


 * and, which work like   and  , except they use the bottom of the call ring buffer.
 * , which pops a line number off the top of the call ring buffer and discards it. Applesoft BASIC supports this.  It is the complement of  .  Similarly,   pushes a line number onto the top of the call ring buffer (complement of  .)
 * (or, etc.) goes to the given line number, or, if that line does not currently exist in the program, the next highest line.  (Actually, with   there is no need for  ; you can just say   for the same effect.)
 * (resp. ) require a call ring buffer capable of storing integer expressions.  It pushes the current line number expression on the call ring buffer.  That expression will be computed when it is  ed (resp.  ed) to, and execution will resume there, or at the next highest line.
 * to dimension a finite array, or  to ensure that the array is not bounded, and   to ensure that elements of the array are not bounded, and   to just ensure that all variables are not bounded.
 * Floating-point variables, with line numbers that may be fractional (surely best paired with ).

External resources

 * The SICKBAY reference distribution, which includes the reference interpreter in Python, called SAWBONES