Challenge:
Your challenge is to write an interpreter for Whitespace. Given a string consisting of spaces, tabs, newlines, and potentially other characters, as well as possible inputs for the Whitespace program itself, output the result of the given Whitespace program.
Here is an overview of the Whitespace language and its builtins:
Whitespace is a stack-based language which uses only three characters: spaces (ASCII codepoint 32); tabs (ASCII codepoint 9); and newlines (ASCII codepoint 10); all other characters are ignored.
It only has a couple of basic builtins, which I will go over below. Whitespace has a stack, which can only consist of integers, as well as a heap, which is a map of integers (both the key and value).
From here on out I will be using S
for spaces, T
for tabs, and N
for newlines to make the text more compact.
Stack Manipulation (always starts with leading S
):
- Push a number to the stack:
SS
, followed by either anS
/T
for positive/negative respectively, followed by someS
and/orT
which is the binary representation of the number (S=0
;T=1
), followed by a trailing newlineN
. Some examples:SSSTN
pushes the number1
; a positive integer with binary1
.SSTTSN
pushes the number-2
; a negative integer with binary10
.SSSTSTSN
pushes the number10
; a positive integer with binary1010
.SSTTTSSTSSN
pushes the number-100
; a negative integer with binary1100100
.- Pushing number
0
is an edge case, since it can be done in multiple ways. Some examples:SSSN
: push a positive integer without any binary digits.SSTN
: push a negative integer without any binary digits.SSSSN
: push a positive integer with binary0
.SSTSSSN
: push a negative integer with binary000
.
- Duplicate the top of the stack:
SNS
. - Copy the 0-based \$n\$th item from the top of the stack to the top of the stack:
STS
followed by a number similar as mentioned earlier (excluding the leadingSS
). I.e. let's say the stack currently contains the integers[47,12,0,55]
, then we could useSTSSTSN
to copy the 0-based 2nd item (which is the12
in this case) to the top. So the stack becomes:[47,12,0,55,12]
.- NOTE: This index may not be negative. On TIO this would result in a negative index error, but that same program would push a
0
in the vii5ard interpreter, and it could even be different in yet another interpreter. For the sake of this challenge, you can therefore assume a given copy will never be negative. So the copy will always start withSTSS
, followed by the binary of the top-to-bottom index, followed by a trailingN
.
- NOTE: This index may not be negative. On TIO this would result in a negative index error, but that same program would push a
- Swap the top two items on the stack:
SNT
. - Discard the top item of the stack:
SNN
. - Discard/slice \$n\$ items from the top of the stack, but keep the top item:
STN
, followed by a number similar as mentioned earlier (excluding the leadingSS
). I.e. let's say the stack currently contains the integers[1,2,3,4,5,6,7]
, then we could useSTNSTTN
to discard 3 items from the stack (except the top one). So the stack becomes:[1,2,3,7]
.- You can again assume no negative slice values will be used, so this will always start with
STNS
, followed by the amount to slice, followed by a trailingN
.
- You can again assume no negative slice values will be used, so this will always start with
Arithmetic (always starts with leading TS
):
- Addition; add the top two items on the stack together:
TSSS
. - Subtraction; subtract the top item of the stack from the (top-1)th item of the stack:
TSST
. - Multiplication; multiply the top two items on the stack:
TSSN
. - Integer division; integer divide the (top-1)th item of the stack by the top item of the stack:
TSTS
. (NOTE: Since Whitespace only has integers, this will always be integer division and never result in decimal values.) - Modulo; take the modulo of the (top-1)th item of the stack with the top item of the stack:
TSTT
.- You can assume no negative values will be used for the modulo.
For each of these two argument builtins the same applies: if none or only a single integer is on the stack, this will result in an error. How you implement this error in your interpreter is up to you, as long as the program stops when this occurs. I thought about not allowing it for the sake of this challenge, but decided not to because it's a commonly used strategy to stop a program with an error when printing hardcoded texts, using the approach explained in this Whitespace codegolfing tip.
Heap Access (always starts with leading TT
):
- Pop the top two items of the stack, and store the top item in the (top-1)th address of the heap:
TTS
. I.e. let's say the stack contains the integers[1,2,3,4,5]
and the heap already contains[{2:10}]
. When you use this store builtin twice in a row, the stack would contain[1]
and the heap will contain[{2:3},{4:5}]
(note how the{2:10}
has been replaced with the{2:3}
).- NOTE: Just like with the Arithmetic builtins, if no or a single argument is given, it will cause an error. But for the sake of this challenge you can assume this will never be given for this builtin.
- Pop the top item of the stack, and push the item corresponding with that heap address to the top of the stack:
TTT
. I.e. let's say the stack contains the integers[1,4]
and the heap contains[{2:3},{4:5}]
. If you now use the retrieve builtin once, the stack would become[1,5]
(and the heap will remain the same).- NOTE: If you use an address that isn't in the heap yet (or the heap is empty), it will push a
0
to the stack instead. But for the sake of this challenge you can also ignore this.
- NOTE: If you use an address that isn't in the heap yet (or the heap is empty), it will push a
Flow Control (always starts with leading N
):
- Mark a location in the program with a label:
NSS
, followed by some (optional)S
/T
which aren't used by other labels/subroutines, followed by anN
. I.e. if you're only using a single label in your full program,NSSN
would be what to use when code-golfing. If you need two or three labels, you can addNSSSN
and/orNSSTN
.- Although it is possible to have multiple of the same labels in the TIO and vii5args interpreters, it will cause issues, so we assume the input will always only create a label/subroutine once.
- Also, although
NSSN
would be a logical first label to use, it's completely valid to use a labelNSSTSTSTTTSN
instead as only label in the program.
- Call a subroutine with the given label:
NST
, followed by some (optional)S
/T
which aren't used by other labels/subroutines, followed by anN
. I.e.NSTTSTSTTTSN
would jump to the labelTSTSTTTS
as subroutine. - Jump unconditionally to a label:
NSN
, followed by some (optional)S
/T
, followed by anN
. I.e.NSNN
would jump to the (empty) labelN
and continue the program flow from there. - Pop the top integer, and jump to a label if it is exactly 0:
NTS
, followed by some (optional)S
/T
, followed by anN
. I.e. if the stack is currently[4,1,0]
and we'd useNTSSN
, it would jump to the labelSN
and continue the program flow from there (with stack[4,1]
). If instead the stack is currently[4,1]
and we'd use theNTSSN
, it would jump past it to the next builtin below it (with stack[4]
). - Pop the top integer, and jump to a label if it is negative:
NTT
, followed by some (optional)S
/T
, followed by anN
. I.e. if the stack is currently[4,1,-10]
and we'd useNTTTN
, it would jump to the labelTN
and continue the program flow from there (with stack[4,1]
). If instead the stack is currently[4,1]
and we'd use theNTTTN
, it would jump past it to the next builtin below it (with stack[4]
).- Minor note: There is no Jump to label if positive builtin available in Whitespace.
- End a subroutine, and go back to the caller (a.k.a. return):
NTN
. - End the entire program:
NNN
(everything after that becomes no-ops).
I/O (always starts with leading TN
):
- Pop the top integer, and print as character with that codepoint to STDOUT:
TNSS
. I.e. if the stack is currently[10,101]
and we'd call theTNSS
twice, it will output a lowercasee
followed by a newline to STDOUT. - Pop the top integer, and print as integer to STDOUT:
TNST
. - Pop the top integer, and read a character from STDIN, for which its codepoint-integer will be stored in the heap with the popped integer as address:
TNTS
. I.e. if the stack is[0,0]
, the heap is empty, and STDIN contains the capital letterI
, and we'd useTNTS
. The stack will become[0]
and the heap[{0:73}]
. (After which we could use the retrieve builtinTTT
to put this input on the stack.) - Pop the top integer, and read an integer from STDIN, which will be stored in the heap with the popped integer as address:
TNTT
.
Challenge rules:
- You can assume the Whitespace input is always valid with the builtins above. So the compilation phase would always succeed.
- You can assume executing the Whitespace input will never result in any errors, except when the Arithmetic builtins will only get 0 or 1 stack-arguments instead of the required 2. Although there are loads of other possible errors when executing a Whitespace program, like negative indices, jumps to labels that doesn't exist, read from STDIN when it's empty, etc. For the sake of this challenge you can assume none of those kind of errors will occur, and the input-program is error-free (except for the Arithmetic builtins).
- You can assume the additional inputs given will be valid as well. So an integer when we want to read an integer or a character / string of multiple characters if we want to read those.
- The Whitespace program-input can be taken in any reasonable format. Could be a string, list of characters, list of codepoint-integers, etc. Same applies to the other inputs.
- Any non-whitespace character in the Whitespace-program input is ignored. You can assume the no-op characters will only be printable ASCII. So the Whitespace input will only contain the UTF-8/ASCII characters with the codepoints [9, 10, 32..126].
- Whitespace numbers can in theory be as large as you want, but for the sake of this challenge you can assume the integers used will be in the range [-9223372036854775808, 9223372036854775807] (signed 64 bit integer).
General rules:
- This is code-golf, so shortest answer in bytes wins.
Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language. - Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
- Default Loopholes are forbidden.
- If possible, please add a link with a test for your code (i.e. TIO).
- Also, adding an explanation for your answer is highly recommended.
Test cases:
You should copy the actual test cases from the Try it online links to verify your interpreter.
Input(s):
SSTTSTTTTNSSSTSNSSSTNSTSSTNSSTTSTNSSTTSNSNSSSSTNSTSSTNSSTTSSTTTSNSSTTTSSTTNSSSNSSTTSTNSSTTTNSSSTSNSSTTNSSSTTTNSSSTSNSSTTSSTTTSNSSSTSNSSTTNSSSTTTNSSTTSNSSSTSNSSTTSSTTTSNSSTTTSSTTNSSTTTNSSTTSNSSTTSTNSSTTNSSTTSSTTTSNSSTTNSSSTTTNSSTTSTNSSSNSSTTSTNSSTTSNSNSSSSTNSSTTTTTSNNSSNSSSTTSTTTSNTSSSTNSSNSNN
Builtins used:
Push num; duplicate; copy; add; create label; jump to label; print as char
Output:
Pollinium milk; plump pumpkin; lollipop?
Try it online as actual program using raw spaces, tabs, newlines.
See this Whitespace answer of mine for an explanation.
Input(s):
SSTTSNSNSTSSNTNST
Builtins used:
Push num; duplicate; multiply; print as int
Output:
4
Try it online as actual program using spaces, tabs, newlines, and comments.
Input(s):
SSTTNTNSTNNNTSNTNTSSS
Builtins used:
Push num; print as int; stop program; (no-ops)
Output:
-1
Try it online as actual program using raw spaces, tabs, newlines.
See this Whitespace answer of mine for an explanation.
Input(s):
SSSNSNSSNSTNTTTTTNSSNSNSTNSTSSSTSTSNTNSSSNTSSSTNTSSSSTSSTNSTSSTNSNSSSSTSSNTSTTSNSNTSSNSSSTNTSSTSNSNTSTNSSSTNTSSTNTSSSNSNTSTSSTNTSSTNSNNNSSSNSNNTSTSSTSSTSNSTSSTSNTSTTNTSNNNNNSSTNSNNTSSNNSNNNSSSSNTSSSNSNN
21
Builtins used:
Push num; duplicate; swap; copy; discard; add; subtract; multiply; divide; modulo; retrieve; create label; jump to label; jump to label if 0; stop program; read as int; print as char; print as int
Output:
21
21
23
20
5
25
31
24
3
27
37
26
Try it online as actual program using spaces, tabs, newlines, and comments.
See this Whitespace answer of mine for an explanation.
Input(s):
NSSNSSSNSNSTNTSTTTSNSSSSTSTSNTSSTNTSSNSSSNSNSTNTSTTTTSSTNTSNSSSNTNSTNSSSN
TThhiiss iiss ddoouubblee ssppeeaakk!!\n
Builtins used:
Push num; duplicate; subtract; retrieve; create label; jump to label if 0; read as char; read as int
Output:
0
Try it online as actual program using spaces, tabs, newlines, and comments.
See this Whitespace answer of mine for an explanation.
Inputs(s):
SSSNSNSTNTTTTTSSSNSNSTNTTTTTTSSSTNST
-3
5
Builtins used:
Push num; duplicate; add; retrieve; read as int; print as int
Output:
2
Try it online as actual program using spaces, tabs, newlines, and comments.
See this Whitespace answer of mine for an explanation.
Input(s):
SSSTSSSTTTTTSSSSSSSTTSSSTSTTTTTSSTTSNTNST
Builtins used:
Push num; print as int
Output:
4815162342
Try it online as actual program using spaces, tabs, newlines, and comments.
See this Whitespace answer of mine for an explanation.
Input(s):
SSSTTTNTNTSSSSTSSSSTNSSSTSTSNNSSTTNSSSTNTSSTSNSNTTSNSTSSTNSNTNSNTTNNSSSNSSSTTTNTTTSTNSSSTSTNTNSTSSSTSNNSSTNSNTSNNSNTTNSSSSSTSNTSTSSNSNTSSSNNSNTNNSSSSNNNN
〹
Builtins used:
Push num; duplicate; swap; discard; copy; slice; retrieve; create label; jump to label; jump to label if 0; jump to label if negative; exit program; read as char; print as char; print as int
Output:
12345!!
Try it online as actual program using spaces, tabs, newlines, and comments.
Inputs(s):
SSSTTNSNSSNSSNSTTSSSSTTTNTTSNSTNTNSSNNNSSTNNSSNTTTNTNSSTN
Builtins used:
Push num; duplicate; store; retrieve; call subroutine; return; exit program; print as char; (no-ops)
Output:
(character with unicode value 7)
Try it online as actual program using spaces, tabs, newlines, and comments.
0
is just the shortest, and in most cases you don't need to re-use the heap too much. \$\endgroup\$