Basic Time Travel is an unstructured esoteric programming language based on BASIC that allows time travel. Its only way of making loops involves making the program travel to the past to get its past self to run the same statements again.
Why am I suddenly thinking about spaghetti?
A program is a sequence of statements, with one statement per line. With a few exceptions, each statement starts with a line number, like so:
10 print "Hello, world!" 20 x = 2 + 3 30 print x
Line numbers do not have to be contiguous, but they do have to be ascending, can't repeat, and must be non-negative integers.
Variable names can contain letters, digits, underscores, apostrophes, and any non-ASCII character, but they can't start with a digit or an apostrophe. No words are reserved; whether a word is a keyword or a variable name is determined by context. All variable names and keywords is case-insensitive, with the exception that the case of the first letter of a variable's name determines whether it's global or local (more on that later). (This means that
Foo are distinct variables, but
FOO are the same, as are
Variables can be either integers or strings, but strings have limited support. String variables have names starting with a
$. All integer variables start out with the value 0, and string variables with the empty string.
If a line starts with the keyword
rem (without a line number), then it's a comment, unless
rem is immediately followed by
Basic Time Travel has three types of integer literals, any of which can be used anywhere a number is expected (including as line numbers):
'Ais equivalent to
An expression is an optional unary operator followed by a literal, a variable name, or an
@ sign (which gives the current global time, explained later). Possible unary operators are
+ (which usually does nothing) and
- (which negates the value). (There's also another unary operator,
\, but that's only allowed in
input statements and will be explained later.)
The range of integers is implementation-dependent. In the implementation on this page, numbers will be bigints if your browser supports bigints; otherwise the minimum/maximum value is ±9007199254740991. Exceeding the range is an error and will destroy all threads.
Assignment statements have one of the following forms:
line-num var = expr
line-num var = expr1 op expr2
line-num var op expr
The first two forms do what you'd expect. That last form will perform the calculation
var op expr and store the result into var (equivalent to
var = var op expr).
Possible ops are
% (modulo, always positive), and
^ (power). All operations floor their result to an integer, and division by zero causes a black hole that stops all threads.
Note that these operators are part of the syntax of the assignment statement; they can't be used in arbitrary expressions, and multiple binary operators can't be combined in the same statement.
Constants can be defined with a statement without a line number of the form
var = expr. expr can only include literals and constants that have already been defined. Names of constants are completely case-insensitive; that is,
Foo refer to the same constant, if a constant with either of those names has been defined.
Constants can be used anywhere variables can (except on the left-hand side of an assignment). Additionally, line numbers can have the forms
constant + literal or
constant - literal, which do what you'd expect (thought only those forms or just a plain literal; in particular, a plain constant is not a valid line number). The normal restrictions on line numbers (must be ascending and non-negative, can't be duplicates) apply here, too.
if statement has the form
line-num if expr1 compare expr2 statement. compare is any one or more of
>; if more than one comparison operator is given, the statement runs if any of the operators apply (so e.g.
<= is less than or equals, and
<> is not equals, and
>< work as well). The statement must be a single statement (with its line number removed); you can't have a block of statements, but you can nest if statements (which acts as an "and" conditional).
Time travel uses a model where the program can go back and change its past. If a program goes back in time, then the program and its past self will be around at the same time, in separate threads; these threads can then interact with each other.
There's a global timer that starts at 0, and each thread has a local timer that also starts at 0 and goes at the same speed as the global timer. By default, these don't correspond to real time, they just determine the ordering of events in the program. The line number at the beginning of each line determines at what time on the local timer the statement executes. You can find the current value of the global timer by using
@ anywhere an expression is expected (there's no way to get the value of the local timer, since you already know that from the line number).
Variables can be either local or global. Global variables (whose names start with a capital letter) are shared by all threads, and allow communication between threads. For local variables (whose names start with a lowercase letter), each thread has its own copy of the variable, and the thread remembers them when it travels through time.
The most important time travel command is
goto, which causes the thread to travel to a particular point in time. Its most basic form is
line-number goto expression; this will cause the thread to travel to the global time mentioned in expression. expression can be preceded by
@, in which case it's treated as relative to the current global time. Also, between
goto and the expression (or
@), you can put a symbol specifying what order the thread should run relative to other threads:
|The thread should run first, before any threads that already exist|
|The thread should run immediately before its current incarnation (the default)|
|The thread should run immediately after its current incarnation|
|The thread should run last, after any threads that already exist|
|The thread should run in a random place in the sequence|
If the expression has a unary operator and the
goto isn't relative, then a thread order indicator must be specified to distinguish the statement from a statement that adds to or subtracts from the variable
If the time specified is after the present, then the interpreter will continue running other threads normally, and start running the current thread again when it arrives. If the time specified is before the present, then the interpreter will roll back everything, including global variables, local variables (of threads other than the one that's currently time traveling), and any output (so going back to the start of the program clears the screen), to how it was at that point in global time. However, if a thread goes to a particular time, it will always arrive at that time, each time the program gets to that time, no matter what else happens.
Here's an example, "Count", which counts up forever, each time erasing the previous number and replacing it:
1000 slow 1001 Count = 0 1002 count = Count 1003 print count 2000 goto } 1000 2001 Count = count + 1
slowwill be explained later, but it makes it so the numbers stay long enough to be visible.
countare set to 0, and 0 is printed.
Count = 0, but then the new thread (at local time 2001) runs
Count = count + 1, which sets the global variable
Countto 1. Then lines 1002 and 1003 end up seeing 1 as the count and printing that.
countas 1, so at global time 1001/local time 2001, it sets
Countto 2, and since this new thread runs last (due to
}), the original thread sees
Countas 2, prints that, and goes back in time again.
In addition to traveling through time, threads can also stop time. If a thread runs the command
line-number stop, then other threads will stop running, global time will stop increasing, local time for other threads will stop increasing, but the current thread will continue running and local time will continue increasing for it. This lasts until a command of the form
line-number start is executed;
leave will also cancel this effect.
line-number freeze cryonically freezes the current thread. The current thread will stop running, but other threads will continue as normal.
line-number thaw wakes up all frozen threads. When a thread thaws, its local time will be the same as when it's frozen.
This is similar to a
goto with a time in the future, but it's useful if you don't yet know the time you're going to. It also differs from
goto in that
goto some time in the future will cause the thread to always arrive at that time no matter what, whereas if a thread is
thawed on one loop but not on another, it'll only thaw on the loop where it's
There are two ways that you can synchronize global time within the program with the actual real-world time.
The first is slow mode. If a thread runs the command
line-number slow, then after that, each unit of global time will take 1 millisecond. This means, for instance, that if a command is executed at global time 500, and the next command is executed at global time 600, the interpreter will wait 100 milliseconds (1/10 of a second) between executing those commands. No attempt is made to account for the time it takes to actually do the relevant calculations, so this could get out of sync with real time.
You can end slow mode with
line-number fast, and you can make the program start in slow mode by having a line that's just
slow without a line number. Slow mode state acts like a global variable; if you travel back in time, then the slow mode state will be whatever it was at that time. A difference between putting
0 slow at the beginning of the program and using line-number-less
slow is that in the former case, going back before time 0 will result in slow mode being disabled, whereas it won't using the line-number-less version.
The second way to synchronize with real time is the
set command (
line-number set). Running the
set command will set the clock used for global time to be the number of milliseconds since January 1, 1970 (not counting leap seconds). This doesn't change the relative ordering of events; rather, it adds an offset to any
@ expression, and subtracts that offset from any
goto command. The offset is treated like a global variable; going back before the
set command will restore the offset to 0. (This means that the value of
@ after a
goto command might not be the argument that you gave to the command.)
set sets the global variable
Z to the current time zone offset, in number of milliseconds east of UTC. This means that
@+Z gives the current local time.
line-number leave causes the current thread to exit. (Note that if the program travels back in time before the
leave command, the thread will still run.)
The main way to output stuff is with the
; disables this.
A string is any text (without newlines) within double quotes. Backslash escapes are not supported, but you can put two double quotes next to each other to include a quotation mark in the output. Also, an additional operator,
\, is supported in
-, because that would be interpreted as adding to or subtracting from a variable called
The main way to input stuff is with the
input statement. It has the same syntax as
+ before it) is just printed. All threads and the global time will pause as the program waits for input. An
input statement that doesn't mention any variables is allowed; in that case, it just prints any expressions and strings and waits for the user to click "OK".
It's also possible to input strings, by putting the name of a string variable in an
input statement. String variables have names that start with
$; the first letter after the dollars sign determines whether it's global or local, and string variables work the same as integer variables in terms of time travel. Integer variable names can't start with decimal digits; since
$ is also used for hexadecimal literals, string variable names can't start with hexadecimal digits.
Input statements are the only way to get a string into a string variable. There is no assignment statement for strings.
Once a string is in a variable, there are a few ways to use it:
line-number if $variable string-literal statement(note the lack of equals sign). This compares case-insensitively.
line-number variable < $variable; this removes the first character from the string and puts the Unicode code of that character into variable.
Basic Time Travel has some limited audio output capabilities, using the
chime command. Chimes will only be audible if the program is in slow mode.
chime takes a single argument, which can either be a note name (
B) or a variable (not an arbitrary expression) containing a note number. There are two octaves of chimes, from C to C; the lower octave is indicated by lowercase letters or numbers from 1 to 7, and the higher octave is indicated by uppercase letters or numbers from 8 to 15. Note that the high C is not available directly as a note name (but you can use B♯), but is available as a note number. Also note that the note numbers are diatonic, not chromatic (1 is C, 2 is D, etc., and there's no number for C♯ etc.).
In addition, there are two ways to specify accidentals. One is to follow the note name or variable by a number sign (
#), which will raise the note one half step. The other is to set a local variable named
key; if this variable has a positive value, then it uses a key signature with that many sharps, and if it's negative, then it uses that many flats (e.g.
key = 1 means that all F's, whether specified as the name
F or a variable with the value 4 or 11, will be sharp). Both of these combine, so e.g. if
key is 1,
chime F# will give F double sharp (G), and if
key is -1,
chime B# will give B natural. If an accidental puts the note outside the two-octave range, then the note won't play.
Note that the chimes have non-harmonic overtones, so things like octaves played at the same time won't sound as good as they would on most instruments.
||||var-name op expr|
|program||→||list of stmt, separated by newlines|
Photosensitivity warning: Due to how this language handles output, some programs may cause the output to flicker while running.