x86 assembly, 85 bytes
Bytecode:
8d0c244289deb00331dbcd803c0175418024247f8a042401f375203c1b0f94c374e23c09740c3c0a
74083c1f7ed83c7f74d4b00431db43cd80ebcb3c5b75014380e3fe0c203c617cbb3c7a7fb731dbeb
b3b001cd80
Source (with explanation in comments):
.section .text
.globl _start
_start:
leal (%esp), %ecx # read to (%esp)
incl %edx # read one byte
loop:
movl %ebx, %esi # save previous %ebx
loopnosave:
movb $3, %al # read
xorl %ebx, %ebx # stdin
int $0x80
cmpb $1, %al # exit if we didn't get exactly one byte
jne quit
andb $0x7f, (%esp)
movb (%esp), %al # moving byte into %al saves bytes later on
addl %esi, %ebx # restore previous value of %ebx (currently 0)
# we use add instead of mov here to update flags
jnz ignore # check if we're in the middle of an esc-sequence
cmpb $0x1b, %al # check for esc
sete %bl # if it is esc, set the "ignore" flag...
je loop # ... and skip the output
cmpb $0x09, %al # explicitly output tabs and newlines
je out
cmpb $0x0a, %al
je out
cmpb $0x1f, %al # don't output control characters
jle loopnosave
cmpb $0x7f, %al
je loopnosave
out:
movb $4, %al # write
xorl %ebx, %ebx
incl %ebx # stdout
int $0x80
jmp loopnosave # we clobbered %ebx, so we don't want to keep it
ignore:
cmpb $'[', %al # check if it's [
jne ok # skip next instruction if not
incl %ebx # set %ebx to anything but 1
ok:
andb $0xfe, %bl # clear lowest bit on %ebx
orb $0x20, %al # uppercase to lowercase
cmpb $'a', %al
jl loop
cmpb $'z', %al
jg loop
xorl %ebx, %ebx # clear ignore flag if letter found
jmp loop
quit:
movb $1, %al
int $0x80
Try it online! (with testcases)
Overview / general explanation:
The program reads one byte at a time. %al
holds the input byte, %ebx
holds a flag that gets set when an ESC character is encountered. Since we need %ebx
to call sys_read
and sys_write
, %esi
is used as a temporary storage space for its previous value.
The %ebx
flag is 0
ordinarily, 1
if the previous character was ESC, and 2
or greater if an ESC[
was found. On every loop, the program first tests if the flag is zero. If it is, we either set the flag (if the next character is ESC) or print the next character and move on.
If it's nonzero, we don't print the character, but instead unset the least significant bit of the flag. This has the effect of zeroing it if it's 1
, but it will remain nonzero if it's any other positive integer (signifying a preceding ESC[
). Then we set it to zero (by xoring it with itself) if the current character is a letter, terminating the ESC[
sequence.
One trick I'd like to point out: it's a little miraculously convenient that the file descriptor for STDIN happens to be 0
. This allows us to postpone moving %esi
back to %ebx
until the first time we have to check the flag, which happens to be checking whether it's zero. A movl
instruction would not update flags, but addl
does, and since %ebx
was previously set to zero to read from STDIN, the add instruction happens to be equivalent.
Uses many of the same tricks as in my cat program.
0x9B
? Should it be interpreted as0x1B
once the most significant bit has been cleared? (I think the answer is yes, according to the description.) \$\endgroup\$