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).
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
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
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)
; 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
mov cl, [rdi]
test cl, cl
jnz theloop ; }while(c != 0)
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.
mov cl, [rdi]
... ; same, but with bitmap => 0
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
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
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
sbb eax, eax ; 2B eax = 0 or -1, according to CF.
xor al, dl
and al, 0x20 ; just the lowercase bit
xor al, dl
Not re-tested after tweaking to handle spaces.
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.