SICKBAY

From Esolang
Jump to navigation Jump to search

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 GOSUB and RETURN, SICKBAY uses a call ring buffer, which supports not only GOSUB and RETURN but also PROLONG and CUTSHORT.
  • While some BASICs support computed line numbers in GOTO and GOSUB, SICKBAY supports computed line numbers only in line number definitions. It thus lacks an IF 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 PRINT A%, not PRINTA%.)

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 END. END is an alias for RETURN. RETURN (or CUTSHORT) 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 GOSUB/RETURN/PROLONG/CUTSHORT is executed, a default size of 10 line numbers will be used. A DIM RING 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.)

GOSUB 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. RETURN 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. RETURN does not continue to execute remaining statements on the same line as the GOSUB after colons (see clarifying example below.)

PROLONG pushes the given line number onto the bottom of the call ring buffer. CUTSHORT 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 PROLONG is to pretend that a GOSUB was made from a line number before the first real GOSUB was ever made, effectively adding some code that will be executed after the program ends. The practical effect of CUTSHORT is to make the program end prematurely, when attempting to RETURN to the rootmost caller (initially this would be the "main program".)

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

In GOTO and GOSUB, 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. H% = H%(0). 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 (0 - 100) must be used. Note that this means a negative line number cannot be jumped to, as GOTO 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 GOSUB 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 RND%(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 PRINT CHR$ form. Literal strings may also be printed, but only one thing may (and exactly one thing must) be printed per PRINT statement (so to just print a blank line, print a null string literal.) Anything printed with a PRINT statement will be followed by a newline, unless the semicolon is given after the statement, which suppresses the newline.

The INPUT IntVar 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 INPUT CHR$ IntVar 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

  • (A%-((A%/7)*7)) computes A mod 7.
  • (A%/A%) is 0 if A% is 0, 1 otherwise. Then you can use (A%*A%) for AND and ((A%+A%)/(A%+A%)) 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 S% to contain a number representing the state of the finite control (including a halt state), T%() for the tape, and H% for the tape head. You only need to branch conditionally on S%.

Notes

  • If you write your line number expressions naïvely, SICKBAY suffers a similar flaw as Thue when it comes to input, but it is easier to work around here, as you can use number theory (multiples and modulus) to write your line number expressions less naïvely.

Extensions

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

  • ANTEGOSUB and ANTERETURN, which work like GOSUB and RETURN, except they use the bottom of the call ring buffer.
  • POP, 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 CUTSHORT. Similarly, RETURNTO IntConst pushes a line number onto the top of the call ring buffer (complement of PROLONG.)
  • GOTO NEXT IntConst (or GOSUB NEXT IntConst, etc.) goes to the given line number, or, if that line does not currently exist in the program, the next highest line. (Actually, with RETURNTO there is no need for GOTO NEXT; you can just say RETURNTO 100:RETURN for the same effect.)
  • GOSUB DYNA IntConst (resp. ANTEGOSUB DYNA) 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 RETURNed (resp. ANTERETURNed) to, and execution will resume there, or at the next highest line.
  • DIM IntVar (IntExpr) to dimension a finite array, or DIM IntVar (*) to ensure that the array is not bounded, and DIM IntVar * to ensure that elements of the array are not bounded, and DIM * to just ensure that all variables are not bounded.
  • Floating-point variables, with line numbers that may be fractional (surely best paired with GOTO NEXT).

External resources