Vyxal, 150 149 148 bytes
k2T0ẋ→_0→0?£{D¥L<|¥i:‛+-$c[:‛ +ḟ←_:_←›Ǔṫ∇∇+J←›ǔ→_|:‛<>$c[:‛ >ḟ←+→|:\.=[←_← iC₴|:\,=[←_:_←?CȦ→_|:\[=[←_← i¬[Ȯ¥$ȯ\]ḟ∇∇+$]|:\]=[Ȯ¥$Ẏf\[=TG‹∇$_]]]]]]]_›
Try it Online!
"But there's already a Vyxal answer that's 100 99 98 bytes shorter bro what is this cringe" I hear you say. Well this version doesn't use eval
, and instead runs it manually. Note that it can be slow because it's doing things like rotating a 30000 item list quite frequently, so here's a version with only 100 cells for testing
Some assumptions this program makes:
- Closed brackets
- EOF handled manually
- Program on first line, each input character on a new line
Explained
Quick Overview
k2T0ẋ→_ # Tape
0→ # Cell pointer
0 # Instruction pointer
?£ # Prog
{D¥L<| # While the instruction pointer is less than program length
:,←_,¥i # Get command
:‛+-$c[:‛ +ḟ←_:_←›Ǔṫ∇∇+J←›ǔ→_| # Handle addition and subtraction in the same place
:‛<>$c[:‛ >ḟ←+→| # Handle moving the pointer left and right
:\.=[←_← iC,| # Output
:\,=[←_:_←?CȦ→_| # Input
:\[=[ # jump to next `]` if tape[cell] == 0
←_← i¬[Ȯ¥$ȯ:,\]ḟ›∇+$]|
:\]=[Ȯ¥$Ẏf\[=TG›∇$_] # Jump back to the previous `[`
]]]]]]
_ # Remove the char from the stack
› # Next command
}
Detailed Explanation
k2T0ẋ→_
This is the tape. It consists of 30000 0s in a list. It's stored in a global variable called _
.
0→
This is the cell pointer. It tracks which cell is being pointed to. It's stored in the ghost variable. The ghost variable is another name for the variable with no name (as in, its name is literally ""
- the empty string.)
0
This is the instruction pointer. It tracks which character of the program is being executed. It's stored on the stack as the bottom of the stack. Throughout this answer, the stack is [instruction pointer, current character, ...]
where ...
is whatever processing is happening in each command.
?£
This gets the program to execute and stores it in the register.
{D¥L<|
This is the main program execution loop. It calls its code while the instruction pointer is less than the length of the program. Before performing the comparison, the instruction pointer is triplicated (i.e. three copies of it are pushed to the stack). This is so that there is a copy for the comparison, for getting the current character in the program and for maintaining the value of the pointer.
¥i
This gets the character at the index of the instruction pointer and puts it on the stack. The stack is now [instruction pointer, command]
. We now move on to handling the commands
Addition and Subtraction
:‛+-$c[:‛ +ḟ←_:_←›Ǔṫ∇∇+J←›ǔ→_
The above snippet is the entirety of the section that handles the +
and -
commands.
:‛+-$c
This checks if the command is in the string "+-"
, while leaving a copy of the command on the stack for further comparison if needed.
[:‛ +ḟ
If the command is one of +
or -
, then the command is duplicated yet again, and its index in the string " +"
is returned. This returns 1
for +
and -1
for -
, as -1
is returned for characters not in the string. The 1
or -1
acts as an offset for the current cell, and saves having to check for +
and -
individually. It also means +
can be used for both commands instead of ›
for addition and ‹
for subtraction.
←_:_←›Ǔ
This pushes the tape (stored in the global variable called _
), the value of the cell pointer + 1 (stored in the ghost variable and then incremented) and then rotates the tape left that many times. The :_
after ←_
is needed because there seems to be a bug with list mutability when rotating. (TODO: Fix)
After the rotation, the cell that is to be incremented or decremented is at the tail of the tape.
ṫ∇∇+J
This separates the tail and the rest of the list - first it pushes tape[:-1]
and then it pushes tape[-1]
. It then rotates the top three items on the stack so that the order is [tape[:-1], tape[-1], offset]
. The offset is then added to the tail, and the tail is then appended back to the rest of the tape.
←›ǔ→_
The tape is then rotated cell pointer + 1
times to the right to "undo" the left rotation and then placed back into the global variable called _
.
Moving the Cell Pointer
|:‛<>$c[:‛ >ḟ←+→
The above snippet is the entirety of the section that handles the <
and >
commands.
|:‛<>$c
Just like with the +
and -
commands, a check is done to see if the command is in the string "<>"
.
[:‛ >ḟ
And also just like with the +
and -
commands, if the command is one of <
or >
, then the command is duplicated yet again, and its index in the string " >"
is returned. This returns 1
for >
and -1
for <
. The 1
or -1
acts as an offset for the current cell location, and saves having to check for <
and >
individually. It also means +
can be used for both commands instead of ›
for moving right and ‹
for moving left.
←+→
This adds the offset to the cell pointer and updates the value stored in the ghost variable. It also acts as a weird face.
Output
|:\.=[←_← iC₴
The above snippet is the entirety of the section that handles the .
command.
|:\.=
This checks if the command is equal to the string "."
. Unlike addition/subtraction and cell pointer movement, input and output cannot be handled in the same if-statement, as their functions are different at a fundamental level.
[←_← i
If the command is .
, the tape is pushed, as well as the cell pointer's value. The item at the location of the cell pointer is then retrieved. The space after the second ←
is needed to avoid it being interpreted as ←i
C₴
This prints that item after converting it to its ASCII equivalent (think chr
in python)
Input
|:\,=[←_:_←?CȦ→_
The above snippet is the entirety of the section that handles the ,
command.
|:\,=
This checks if the command is equal to the string ","
.
[←_:_←?C
If the command is ,
, the tape and current cell pointer are pushed to the stack, as well as the ordinal value (think ord
in python) of the next input.
Ȧ→_
This sets the cell pointer
th item of tape
to the input. Basically tape[cell_pointer] = ord(input())
.
Looping
|:\[=[←_← i0=[Ȯ¥$ȯ\]ḟ∇∇+$]
The above snippet is the entirety of the section that handles the [
command.
|:\[=
The usual check for a certain character. [
this time.
[←_← i
If the command is [
, get the cell pointer
th item of tape
.
¬[
If the cell is not a truthy value (i.e. not 0
), then:
Ȯ¥$ȯ\]ḟ
Push program[instruction_pointer:]
and find the first ]
in that string. The stack is now [instruction pointer, command, position of "]"]
.
∇∇+$
Rotate the stack so that its order is [command, position of "]", instruction pointer]
and add the position of the ]
to the instruction pointer. This has the effect of iterating through the program until the next ]
is found without having to have lengthy while loops.
|:\]=[Ȯ¥$Ẏf\[=TG‹∇$_
The above snippet is the entirety of the section that handles the ]
command.
|:\]=
Check if the command is ]
[Ȯ¥$Ẏf\[=TG
And if it is, get the greatest index of all [
s in the string program[0:instruction_pointer]
. This has the effect of backtracking to the matching [
without having to have a lengthy while loop.
‹∇$_
Decrement that so that the instruction pointer will be re-incremented to the position of the matching [
and rotate the stack so that the stack order is once again [instruction pointer, command]
Final bits
]]]]]]] # Close all the if statements
_ # Remove the command from the stack
› # Move the instruction pointer forward 1
} # Close the main while loop
,
on EOF? Or that it's up to us to choose a value when trying,
on EOF? Or is EOF undefined behaviour altogether? \$\endgroup\$