x86-64 machine code, 21 bytes
(Or 20 bytes for an x86-32 version with an explicit length input, allowing dec/jnz
as the loop condition. Using cl
for a shift count makes it not a win to use loop
, and 64-bit mode has 2-byte dec
so it's break-even to make it explicit-length).
Callable as void vucd_implicit(char *rdi)
with the x86-64 System V calling convention. (It leaves RDI pointing to the terminating 0
byte if you want to use that bonus return value.)
# disassembly: objdump -drwC -Mintel
0000000000401000 <theloop>:
401000: b8 a0 bb ef fb mov eax,0xfbefbba0
401005: d3 e8 shr eax,cl
401007: 30 c8 xor al,cl
401009: 24 20 and al,0x20
40100b: 30 c8 xor al,cl
40100d: aa stos BYTE PTR es:[rdi],al
000000000040100e <vowel_up_consonant_down>: # the function entry point
40100e: 8a 0f mov cl,BYTE PTR [rdi]
401010: 84 c9 test cl,cl
401012: 75 ec jne 401000 <theloop>
401014: c3 ret
Notice that the function entry point is in the middle of the loop. This is something you can do in real life; as far as other tools are concerned, theloop
is another function that falls into this one as a tailcall.
This uses something like Arnauld's xor/and/xor idea for applying the lcase bit to an input character, instead of the more obvious and cl, ~0x20
to clear it in the original, and al, 0x20
to isolate it from the mask, and or al, cl
to combine. That would be 1 byte larger because and cl, imm8
can't use the AL,imm special encoding with no ModRM.
Having the bitmap left-shifted by 5 so the bit we want lines up with 0x20 is also due to @Arnauld's answer. I had been planning to use bt
/salc
like in a previous vowel/consonant bitmap answer and mask that with 0x20
until I tried Arnauld's way and found it could be done even more efficiently.
NASM source (Try it online! with a test caller that does strlen on a command line arg and uses a write() system call afterward)
global vowel_up_consonant_down
theloop:
; consonant bitmap
; ZYXWVUTSRQPONMLKJIHGFEDCBA@ For indexing with ASCII c&31 directly
mov eax, 111110111110111110111011101b << 5 ; line up with the lcase bit
; the low bit is 1 to preserve 0x20 ' '
shr eax, cl ; AL & 0x20 is how the lowercase bit *should* be set
xor al, cl ; bitdiff = (mask>>c) & c
and al, 0x20 ; isolate the lowercase bit
xor al, cl ; flip the lcase bit if needed
stosb ; and store
vowel_up_consonant_down:
mov cl, [rdi]
test cl, cl
jnz theloop ; }while(c != 0)
ret
Variants
no spaces: 19 bytes
If we didn't need to handle spaces (ASCII 0x20), we enter the function at the top, with the mov cl, [rdi]
load at the top, but still leave the loop condition at the bottom. So we'd load and re-store the terminating 0
, and the XOR that produced it would set ZF. The low bit of the bitmap would be 0 instead of 1.
vucd_pure_alphabetic:
.loop:
mov cl, [rdi]
... ; same, but with bitmap[0] => 0
xor al,cl
jnz .loop ; mask>>0 leave the terminating 0 unmodified; xor sets ZF
Upper-case-only input, like the A to Z in the question indicates: 19 bytes
(Or 17 without spaces either.)
If we can assume the lower-case bit was already cleared on input ASCII bytes, we can save one XOR (and change the other one to an OR)
...
shr eax, cl
and al, 0x20
or al, cl
...
Using the bt
instruction:
Normally testing a bitmap is a job for the bt
instruction, but where we're not branching on the result, it turns out to be cheaper to shift it, even though that means we can't easily use the loop
instruction. (I haven't gone back to this idea to re-golf it after realizing we need to handle spaces).
I suspect there's room for more golfing, but the first version of this I tried was
vucd:
.loop:
mov dl, [rdi]
; ZYXWVUTSRQPONMLKJIHGFEDCBA@ 1-indexed using ASCII codes directly
mov esi, 111110111110111110111011101b ; consonant/vowel bitmap for use with bt
bt esi, edx ; CF = mask & (1U << (c&31))
%if CPUMODE == 32
salc ; 1B only sets AL = 0 or 0xFF. Not available in 64-bit mode
%else
sbb eax, eax ; 2B eax = 0 or -1, according to CF.
%endif
xor al, dl
and al, 0x20 ; just the lowercase bit
xor al, dl
loop .loop
ret
Not re-tested after tweaking to handle spaces.
bt
+ salc
in 32-bit mode costs the same as shr reg,cl
+ the extra test cl,cl
that's needed because we can't use loop
. So I think this is also 21 bytes. But 32-bit mode explicit-length can just dec/jnz
a reg other than cl
for a 20-byte total.
mov esi, imm32
can be hoisted out of the loop, or we can use EAX. Neither affects byte count, only efficiency or the calling-convention.