5
\$\begingroup\$

This is a follow up on A progression of quines.
Based on the reviews I got, I have reworked my first and fifth programs.

The nice quine

The answer I received from ecm made me realize that my nice quine wasn't all that nice. More than 900 characters on a single line is just too much! Like she wrote in her "q.asm", I too give each source line its own db now.

QN. Nicer

This program executes a subroutine twice. The first time it is call-ed and the second time the code just falls through. I could do this because at the end a simple ret is enough to exit to DOS.
In the appended source text the asterisk (*) is used as an end of line marker. That's why I had to avoid using the asterisk in my actual code.
I've also avoided the use of embedded single quote characters (') because they would throw off the FASM parser.

        org     256
        mov     ah, 02h         ; DOS.PrintChar
        xor     bx, bx          ; 1st pass BX=0
        call    job
        inc     bx              ; 2nd pass BX=1
job:    mov     cx, 36          ; Number of lines/pass
        mov     si, text
more:   test    bx, bx          ; In 2nd pass we prefix with ...
        jz      first
        mov     dl, 100         ; d
        int     21h
        mov     dl, 98          ; b
        int     21h
        mov     dl, 32          ; space
        int     21h
        mov     dl, 39          ; quote
        int     21h
first:  lodsb                   ; No asterisk-only strings
next:   mov     dl, al
        int     21h
        lodsb
        cmp     al, 42          ; Terminating asterisk
        jne     next
        test    bx, bx          ; In 2nd pass we suffix with ...
        jz      crlf
        mov     dl, al          ; asterisk
        int     21h
        mov     dl, 39          ; quote
        int     21h
crlf:   mov     dl, 13          ; newline
        int     21h
        mov     dl, 10
        int     21h
        loop    more
        ret                     ; Exits to DOS on 2nd pass!
text:
db '        org     256*'
db '        mov     ah, 02h         ; DOS.PrintChar*'
db '        xor     bx, bx          ; 1st pass BX=0*'
db '        call    job*'
db '        inc     bx              ; 2nd pass BX=1*'
db 'job:    mov     cx, 36          ; Number of lines/pass*'
db '        mov     si, text*'
db 'more:   test    bx, bx          ; In 2nd pass we prefix with ...*'
db '        jz      first*'
db '        mov     dl, 100         ; d*'
db '        int     21h*'
db '        mov     dl, 98          ; b*'
db '        int     21h*'
db '        mov     dl, 32          ; space*'
db '        int     21h*'
db '        mov     dl, 39          ; quote*'
db '        int     21h*'
db 'first:  lodsb                   ; No asterisk-only strings*'
db 'next:   mov     dl, al*'
db '        int     21h*'
db '        lodsb*'
db '        cmp     al, 42          ; Terminating asterisk*'
db '        jne     next*'
db '        test    bx, bx          ; In 2nd pass we suffix with ...*'
db '        jz      crlf*'
db '        mov     dl, al          ; asterisk*'
db '        int     21h*'
db '        mov     dl, 39          ; quote*'
db '        int     21h*'
db 'crlf:   mov     dl, 13          ; newline*'
db '        int     21h*'
db '        mov     dl, 10*'
db '        int     21h*'
db '        loop    more*'
db '        ret                     ; Exits to DOS on 2nd pass!*'
db 'text:*'

The short quine

Reworking the shortest quine was much harder. Some good ideas didn't stand the test of the numbers. The Norton Editor doesn't allow using the character codes 0, 1, 26, or 255 and DOS just eats the character codes 7, 8, 9, 10, 13, or 26. Avoiding all of those tiny numbers in a very small program is itself a challenge!
Since this one line program uses the DOS.PrintString function 09h, it cannot have embedded dollar characters ($) and the FASM parser would be thrown off by embedded quotation marks (").

Q5. Shorter by 5

Here the big gain came from building the next AX through xor-ing it with some mask value.

    6264h    ; db
xor 4044h
    ----
    2220h    ;  "
xor 0602h
    ----
    2422h    ; "$

I've also included the first tip by Fifoernik to use cwd as a shortcut for xor dx, dx.

db "¸db™²ü‰×«5D@«5##‰E#Àì#Í!Ã"

The character codes involved:

B8 64 62 99 B2 FC 89 D7 AB 35 44 40 AB 35 02 06 89 45 19 C0 EC 02 CD 21 C3

The equivalent program:

mov     ax, "db"
cwd
mov     dl, 252
mov     di, dx
stosw
xor     ax, 4044h
stosw
xor     ax, 0602h
mov     [di+25], ax
shr     ah, 2
int     21h
ret

Q6. Shorter by 7

Following the second tip by Fifoernik - I will have to investigate this a bit further - I could use the pre-existing value 09h in the high byte of the BP register and shave off another 2 bytes, replacing shr ah, 2 by xchg ax, bp.

db "¸db™²ü‰×«5D@«5##‰E#•Í!Ã"

The character codes involved:

B8 64 62 99 B2 FC 89 D7 AB 35 44 40 AB 35 02 06 89 45 17 95 CD 21 C3

The equivalent program:

mov     ax, "db"
cwd
mov     dl, 252
mov     di, dx
stosw
xor     ax, 4044h
stosw
xor     ax, 0602h
mov     [di+23], ax
xchg    ax, bp
int     21h
ret

New summary

Next table shows how the quines' sourcefiles gradually became smaller.

       QN    Q0   Q1   Q2   Q3   Q4  Q5  Q6
-------------------------------------------
.ASM  2640  1837  319  294  178  35  30  28

.COM  1243   960  186   42   44  30  25  23

Every program was tested using FASM in MS-DOS 6.20

C:\FASM>fasm q6.asm q6.com
flat assembler version 1.70 (2086464 kilobytes memory)
1 passes, 23 bytes.

C:\FASM>q6 > q6_.asm

C:\FASM>fc q6.asm q6_.asm
Comparaison des fichiers en cours : Q6.ASM et Q6_.ASM
FC: aucune différence trouvée

Conclusion

I fear it will be very hard to shorten that last 23-bytes program, but you never know...
As before, I leave it up to you to decide if 'a smaller quine' should refer to a smaller source file or to a smaller executable file.

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Judging by looking at many of your previous posts, I notice that you've changed your programming style for the purpose of writing this program.

  • You don't use whitelines to subdivide the code into logical blocks
    Allowing blank lines is just a matter of replacing that first lodsb by a jmp first.

  • You don't use character constants
    There's no conflict in the code if you replace the numbers 100 and 98 by their readable counterparts 'd' and 'b'.

  • You've written a label on a separate line
    Probably you wrote this for simplicity but I prefer to see what is a data label on an actual data line. Additionally you can drop the colon.

  • You don't indent those db lines
    This is definitely not nice! Maybe you were afraid to have to write all of those space characters but the good news is that DOS can do that for you. Simply have your code print a TAB character and DOS will expand it even when output is redirected.


And then there's also that magic number in mov cx, 36. It's easy to eliminate it if you introduce an EndOfFile marker similar to the EndOfLine marker that you already use. In my version of your code I have chosen 126 as the EndOfLine marker and 127 as the EndOfFile marker. Sadly ASCII 127 doesn't show in this Windows environment that Code Review is! Trust me, it works OK.

Because the loop now runs without using the CX register you can put it to good use replacing BX. This will save some bytes because the pair test bx, bx jz ... is now turned into jcxz ....


        org     256

        mov     ah, 02h         ; DOS.PrintChar
        xor     cx, cx          ; 1st pass CX=0
        call    job
        inc     cx              ; 2nd pass CX=1
        mov     dl, 'Z'         ; The data label
        int     21h

job:    mov     si, Z
more:   jcxz    first           ; 2nd pass prefixes:
        mov     dl, 9
        int     21h
        mov     dl, 'd'
        int     21h
        mov     dl, 'b'
        int     21h
        mov     dl, 9
        int     21h
        mov     dl, 34          ; quote
        int     21h
        jmp     first
next:   mov     dl, al
        int     21h
first:  lodsb
        cmp     al, 126         ; EOL or EOF ?
        jb      next
        jcxz    crlf            ; 2nd pass suffixes:
        mov     dl, al          ; EOL or EOF
        int     21h
        mov     dl, 34          ; quote
        int     21h
crlf:   mov     dl, 13
        int     21h
        mov     dl, 10
        int     21h
        cmp     byte [si-1], 127 ; EOF ?
        jb      more
        ret                     ; Exits to DOS on 2nd pass!

Z       db      "        org     256~"
        db      "~"
        db      "        mov     ah, 02h         ; DOS.PrintChar~"
        db      "        xor     cx, cx          ; 1st pass CX=0~"
        db      "        call    job~"
        db      "        inc     cx              ; 2nd pass CX=1~"
        db      "        mov     dl, 'Z'         ; The data label~"
        db      "        int     21h~"
        db      "~"
        db      "job:    mov     si, Z~"
        db      "more:   jcxz    first           ; 2nd pass prefixes:~"
        db      "        mov     dl, 9~"
        db      "        int     21h~"
        db      "        mov     dl, 'd'~"
        db      "        int     21h~"
        db      "        mov     dl, 'b'~"
        db      "        int     21h~"
        db      "        mov     dl, 9~"
        db      "        int     21h~"
        db      "        mov     dl, 34          ; quote~"
        db      "        int     21h~"
        db      "        jmp     first~"
        db      "next:   mov     dl, al~"
        db      "        int     21h~"
        db      "first:  lodsb~"
        db      "        cmp     al, 126         ; EOL or EOF ?~"
        db      "        jb      next~"
        db      "        jcxz    crlf            ; 2nd pass suffixes:~"
        db      "        mov     dl, al          ; EOL or EOF~"
        db      "        int     21h~"
        db      "        mov     dl, 34          ; quote~"
        db      "        int     21h~"
        db      "crlf:   mov     dl, 13~"
        db      "        int     21h~"
        db      "        mov     dl, 10~"
        db      "        int     21h~"
        db      "        cmp     byte [si-1], 127 ; EOF ?~"
        db      "        jb      more~"
        db      "        ret                     ; Exits to DOS on 2nd pass!~"
        db      ""

These changes, especially the new indentations, have enlarged the source file considerably. It went up from 2640 to 3032 bytes.
Contrary the executable file is smaller now! Descending from 1243 to 1169 bytes.

BTW - nice job that 23-bytes quine!

\$\endgroup\$
0
\$\begingroup\$

The mov cx, 36 in QN is not as nice as I'd wish. I don't have a better way in mind though.

(To be fair, in my q.asm I assume that there are two linebreak characters, that LF is the second one each time, and that there is a linebreak at the very end of the string. If I were to write it now I would at least use jb loop instead of jne.)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.