mu/subx/apps/hex.subx

1536 lines
77 KiB
Plaintext

# Read a text file containing whitespace-separated pairs of ascii hex bytes
# from stdin, and convert them into binary bytes (octets) on stdout. Ignore
# comments between '#' and newline.
#
# To run (from the subx/ directory):
# $ ./subx translate *.subx apps/hex.subx -o apps/hex
# $ echo '80 81 82 # comment' |./subx run apps/hex |xxd -
# Expected output:
# 00000000: 8081 82
#
# Only hex bytes and comments are permitted. Outside of comments all words
# must be exactly 2 characters long and contain only characters [0-9a-f]. No
# uppercase hex.
== code
# instruction effective address register displacement immediate
# . op subop mod rm32 base index scale r32
# . 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
# for debugging: run a single test
#? e8/call test-skip-until-newline/disp32
#? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX
#? eb/jump $main:end/disp8
# main: run tests if necessary, convert stdin if not
# . prolog
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# - if argc > 1 and argv[1] == "test", then return run_tests()
# . argc > 1
81 7/subop/compare 1/mod/*+disp8 5/rm32/EBP . . . . 0/disp8 1/imm32 # compare *EBP
7e/jump-if-lesser-or-equal $run-main/disp8
# . argv[1] == "test"
# . . push args
68/push "test"/imm32
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call kernel-string-equal?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . check result
3d/compare-EAX 1/imm32
75/jump-if-not-equal $run-main/disp8
# . run-tests()
e8/call run-tests/disp32
8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX
eb/jump $main:end/disp8
$run-main:
# - otherwise convert stdin
# var ed/EAX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX
# configure ed to really exit()
# . ed->target = 0
c7 0/subop/copy 0/mod/direct 0/rm32/EAX . . . . . 0/imm32 # copy to *EAX
# return convert(Stdin, 1/stdout, 2/stderr, ed)
# . . push args
50/push-EAX/ed
68/push Stderr/imm32
68/push Stdout/imm32
68/push Stdin/imm32
# . . call
e8/call convert/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP
# . syscall(exit, 0)
bb/copy-to-EBX 0/imm32
$main:end:
b8/copy-to-EAX 1/imm32/exit
cd/syscall 0x80/imm8
# the main entry point
convert: # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
# pseudocode:
# repeatedly
# EAX = convert-next-octet(in, err, ed)
# if (EAX == 0xffffffff) break # eof
# write-byte(out, AL)
# flush(out)
#
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
50/push-EAX
$convert:loop:
# EAX = convert-next-octet(in, err, ed)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x14/disp8 . # push *(EBP+20)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call convert-next-octet/disp32
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# if (EAX == 0xffffffff) break
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xffffffff/imm32 # compare EAX
74/jump-if-equal $convert:loop-end/disp8
# write-byte(out, AL)
# . . push args
50/push-EAX
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
# . . call
e8/call write-byte/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# loop
eb/jump $convert:loop/disp8
$convert:loop-end:
# flush(out)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
$convert:end:
# . restore registers
58/pop-to-EAX
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
# read bytes from 'in' until a sequence of two lowercase hex (0-9, a-f) bytes
# skip spaces and newlines
# on '#' skip bytes until newline
# raise an error and abort on all other unexpected bytes
# return in EAX an _octet_ containing the binary value of the two hex characters
# return 0xffffffff on end of file
convert-next-octet: # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
# pseudocode:
# EAX = scan-next-byte(in, err, ed)
# if (EAX == 0xffffffff) return
# ECX = from-hex-char(EAX)
# EAX = scan-next-byte(in, err, ed)
# if (EAX == 0xffffffff) error("partial byte found.")
# EAX = from-hex-char(EAX)
# EAX = (ECX << 4) | EAX
# return
#
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
51/push-ECX
# EAX = scan-next-byte(in, err, ed)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call scan-next-byte/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# if (EAX == 0xffffffff) return
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xffffffff/imm32 # compare EAX
74/jump-if-equal $convert-next-octet:end/disp8
# EAX = from-hex-char(EAX)
e8/call from-hex-char/disp32
# ECX = EAX
89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX
# EAX = scan-next-byte(in, err, ed)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call scan-next-byte/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# if (EAX == 0xffffffff) error(ed, err, "partial byte found.")
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xffffffff/imm32 # compare EAX
75/jump-if-not-equal $convert-next-octet:convert/disp8
# . error-byte(ed, err, msg, '.') # reusing error-byte to avoid creating _yet_ another helper
# . . push args
68/push 0x2e/imm32/period/dummy
68/push "convert-next-octet: partial byte found"/imm32
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16)
# . . call
e8/call error-byte/disp32 # never returns
$convert-next-octet:convert:
# EAX = from-hex-char(EAX)
e8/call from-hex-char/disp32
# EAX = (ECX << 4) | EAX
# . ECX <<= 4
c1/shift 4/subop/left 3/mod/direct 1/rm32/ECX . . . . . 4/imm8 # shift ECX left by 4 bits
# . EAX |= ECX
09/or 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # EAX = bitwise OR with ECX
$convert-next-octet:end:
# . restore registers
59/pop-to-ECX
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-convert-next-octet:
# - check that the first two bytes of the input are assembled into the resulting octet
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to "abc"
# . write(_test-stream, "abc")
# . . push args
68/push "abc"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-convert-next-octet
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call convert-next-octet/disp32
# registers except ESP may be clobbered at this point
# pop args to convert-next-octet
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that convert-next-octet didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-convert-next-octet: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-convert-next-octet:end/disp8
# check-ints-equal(EAX, 0xab, msg)
# . . push args
68/push "F - test-convert-next-octet"/imm32
68/push 0xab/imm32/ab
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-convert-next-octet:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-convert-next-octet-handles-eof:
# - check that eof returns the sentinel octet
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# don't initialize '_test-stream'
# initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-convert-next-octet
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call convert-next-octet/disp32
# registers except ESP may be clobbered at this point
# pop args to convert-next-octet
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that convert-next-octet didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-convert-next-octet: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-convert-next-octet-handles-eof:end/disp8
# check-ints-equal(EAX, 0xffffffff, msg)
# . . push args
68/push "F - test-convert-next-octet-handles-eof"/imm32
68/push 0xffffffff/imm32/eof
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-convert-next-octet-handles-eof:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-convert-next-octet-aborts-on-single-hex-byte:
# - check that a single unaccompanied hex byte aborts
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to "a"
# . write(_test-stream, "a")
# . . push args
68/push "a"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-convert-next-octet
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call convert-next-octet/disp32
# registers except ESP may be clobbered at this point
# pop args to convert-next-octet
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that convert-next-octet aborted
# . check-ints-equal(ed->value, 2, msg)
# . . push args
68/push "F - test-convert-next-octet-aborts-on-single-hex-byte: unexpected abort"/imm32
68/push 2/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-convert-next-octet-aborts-on-single-hex-byte:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
# read whitespace until a hex byte, and return it
# return 0xffffffff if file ends without finding a hex byte
# on '#' skip all bytes until newline
# abort on any other byte
scan-next-byte: # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
# pseudocode:
# repeatedly
# EAX = read-byte(in)
# if (EAX == 0xffffffff) return EAX
# if (is-hex-digit?(EAX)) return EAX
# if (EAX == ' ' or '\t' or '\n') continue
# if (EAX == '#') skip-until-newline(in)
# else error-byte(ed, err, "invalid byte: " EAX)
#
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
$scan-next-byte:loop:
# EAX = read-byte(in)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call read-byte/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# if (EAX == 0xffffffff) return EAX
3d/compare-with-EAX 0xffffffff/imm32
74/jump-if-equal $scan-next-byte:end/disp8
# if (is-hex-digit?(EAX)) return EAX
# . save EAX for now
50/push-EAX
# . is-hex-digit?(EAX)
# . . push args
50/push-EAX
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . compare with 'false'
3d/compare-with-EAX 0/imm32
# . restore EAX (does not affect flags)
58/pop-to-EAX
# . check whether to return
75/jump-if-not-equal $scan-next-byte:end/disp8
$scan-next-byte:check1:
# if (EAX == ' ') continue
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x20/imm32 # compare EAX
74/jump-if-equal $scan-next-byte:loop/disp8
# if (EAX == '\t') continue
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x9/imm32 # compare EAX
74/jump-if-equal $scan-next-byte:loop/disp8
# if (EAX == '\n') continue
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xa/imm32 # compare EAX
74/jump-if-equal $scan-next-byte:loop/disp8
$scan-next-byte:check2:
# if (EAX == '#') skip-until-newline(in)
3d/compare-with-EAX 0x23/imm32
75/jump-if-not-equal $scan-next-byte:check3/disp8
# . skip-until-newline(in)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call skip-until-newline/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
eb/jump $scan-next-byte:loop/disp8
$scan-next-byte:check3:
# otherwise error-byte(ed, err, msg, EAX)
# . . push args
50/push-EAX
68/push "scan-next-byte: invalid byte"/imm32
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12)
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16)
# . . call
e8/call error-byte/disp32 # never returns
$scan-next-byte:end:
# . restore registers
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte:
# - check that the first byte of the input is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to "abc"
# . write(_test-stream, "abc")
# . . push args
68/push "abc"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-skips-whitespace:
# - check that the first byte after whitespace is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to input with leading whitespace
# . write(_test-stream, text)
# . . push args
68/push " abc"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-whitespace: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-skips-whitespace:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-whitespace"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-skips-whitespace:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-skips-comment:
# - check that the first byte after a comment (and newline) is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to input with leading comment
# . write(_test-stream, comment)
# . . push args
68/push "#x"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, Newline)
# . . push args
68/push Newline/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, real text)
# . . push args
68/push "ab"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-comment: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-skips-comment:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-comment"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-skips-comment:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-skips-comment-and-whitespace:
# - check that the first byte after a comment and any further whitespace is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to input with leading comment and more whitespace after newline
# . write(_test-stream, comment)
# . . push args
68/push "#x"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, Newline)
# . . push args
68/push Newline/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, real text)
# . . push args
68/push " ab"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-comment-and-whitespace: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-skips-comment-and-whitespace:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-comment-and-whitespace"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-skips-comment-and-whitespace:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-skips-whitespace-and-comment:
# - check that the first byte after any whitespace and comments is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to input with leading whitespace and comment
# . write(_test-stream, comment)
# . . push args
68/push " #x"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, Newline)
# . . push args
68/push Newline/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, real text)
# . . push args
68/push "ab"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-whitespace-and-comment: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-skips-whitespace-and-comment:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte-skips-whitespace-and-comment"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-skips-whitespace-and-comment:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-reads-final-byte:
# - check that the final byte in input is returned
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to input with single character
# . write(_test-stream, character)
# . . push args
68/push "a"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-reads-final-byte: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-reads-final-byte:end/disp8
# check-ints-equal(EAX, 0x61/a, msg)
# . . push args
68/push "F - test-scan-next-byte-reads-final-byte"/imm32
68/push 0x61/imm32/a
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-reads-final-byte:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-handles-eof:
# - check that the right sentinel value is returned when there's no data remaining to be read
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# leave '_test-stream' empty
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte didn't abort
# . check-ints-equal(ed->value, 0, msg)
# . . push args
68/push "F - test-scan-next-byte-handles-eof: unexpected abort"/imm32
68/push 0/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# return if abort
81 7/subop/compare 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # compare *(ECX+4)
75/jump-if-not-equal $test-scan-next-byte-handles-eof:end/disp8
# check-ints-equal(EAX, 0xffffffff/eof, msg)
# . . push args
68/push "F - test-scan-next-byte-handles-eof"/imm32
68/push 0xffffffff/imm32/eof
50/push-EAX
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-handles-eof:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
test-scan-next-byte-aborts-on-invalid-byte:
# - check that the a bad byte immediately aborts
# This test uses exit-descriptors. Use EBP for setting up local variables.
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# clear all streams
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-stream)
# . . push args
68/push _test-error-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-error-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-error-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to "x"
# . write(_test-stream, "x")
# . . push args
68/push "x"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
# . var ed/ECX : exit-descriptor
81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP
89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX
# . tailor-exit-descriptor(ed, 12)
# . . push args
68/push 0xc/imm32/nbytes-of-args-for-scan-next-byte
51/push-ECX/ed
# . . call
e8/call tailor-exit-descriptor/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
# . . push args
51/push-ECX/ed
68/push _test-error-buffered-file/imm32
68/push _test-buffered-file/imm32
# . . call
e8/call scan-next-byte/disp32
# registers except ESP may be clobbered at this point
# pop args to scan-next-byte
# . . discard first 2 args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . . restore ed
59/pop-to-ECX
# check that scan-next-byte aborted
# . check-ints-equal(ed->value, 2, msg)
# . . push args
68/push "F - test-scan-next-byte-aborts-on-invalid-byte"/imm32
68/push 2/imm32
# . . push ed->value
ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
$test-scan-next-byte-aborts-on-invalid-byte:end:
# . epilog
# don't restore ESP from EBP; manually reclaim locals
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
5d/pop-to-EBP
c3/return
skip-until-newline: # in : (address buffered-file) -> <void>
# pseudocode:
# push EAX
# repeatedly:
# EAX = read-byte(in)
# if (EAX == 0xffffffff) break
# if (EAX == 0x0a) break
# pop EAX
# . prolog
55/push-EBP
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
# . save registers
50/push-EAX
$skip-until-newline:loop:
# . EAX = read-byte(in)
# . . push args
ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8)
# . . call
e8/call read-byte/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . if (EAX == 0xffffffff) break
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xffffffff/imm32 # compare EAX
74/jump-if-equal $skip-until-newline:end/disp8
# . if (EAX != 0xa/newline) loop
81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xa/imm32 # compare EAX
75/jump-if-not-equal $skip-until-newline:loop/disp8
$skip-until-newline:end:
# . restore registers
58/pop-to-EAX
# . epilog
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
5d/pop-to-EBP
c3/return
test-skip-until-newline:
# - check that the read pointer points after the newline
# setup
# . clear-stream(_test-stream)
# . . push args
68/push _test-stream/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# . clear-stream(_test-buffered-file+4)
# . . push args
b8/copy-to-EAX _test-buffered-file/imm32
05/add-to-EAX 4/imm32
50/push-EAX
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# initialize '_test-stream' to "abc\nde"
# . write(_test-stream, "abc")
# . . push args
68/push "abc"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, Newline)
# . . push args
68/push Newline/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# . write(_test-stream, "de")
# . . push args
68/push "de"/imm32
68/push _test-stream/imm32
# . . call
e8/call write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP
# skip-until-newline(_test-buffered-file)
# . . push args
68/push _test-buffered-file/imm32
# . . call
e8/call skip-until-newline/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP
# check-ints-equal(_test-buffered-file->read, 4, msg)
# . . push args
68/push "F - test-skip-until-newline"/imm32
68/push 4/imm32
b8/copy-to-EAX _test-buffered-file/imm32
ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 8/disp8 . # push *(EAX+8)
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP
# . end
c3/return
== data
_test-error-stream:
# current write index
00 00 00 00
# current read index
00 00 00 00
# length (= 8)
08 00 00 00
# data
00 00 00 00 00 00 00 00 # 8 bytes
# a test buffered file for _test-stream
_test-error-buffered-file:
# file descriptor or (address stream)
_test-error-stream/imm32
# current write index
00 00 00 00
# current read index
00 00 00 00
# length (6)
06 00 00 00
# data
00 00 00 00 00 00 # 6 bytes
# . . vim:nowrap:textwidth=0