; File: escape.asm
;
; Handle escape sequence in user input
;

; Some system call constants
;
%define STDIN 0
%define STDOUT 1
%define SYSCALL_EXIT  1
%define SYSCALL_READ  3
%define SYSCALL_WRITE 4

; Some ASCII constants
;
%define NUL 0           ; null
%define BEL 7           ; bell
%define BS 8            ; backspace
%define HT 9            ; horizontal tab
%define LF 10           ; linefeed
%define VT 11           ; vertical tab
%define FF 12           ; formfeed
%define CR 13           ; carriage return

; Some constants for variables
;
%define BUFLEN 256




        SECTION .data                   ; initialized data section

msg1:   db "Enter string: "             ; user prompt
len1:   equ $-msg1                      ; length of first message

msg2:   db "Original: "                 ; original string label
len2:   equ $-msg2                      ; length of second message

msg3:   db "Convert:  "                 ; converted string label
len3:   equ $-msg3

msg4:   db LF, "Error: no characters read", LF
len4:   equ $-msg4

msg5:   db "Error: unknown escape sequence \"
echar:  db ' '                          ; byte for bad char
        db LF
len5:   equ $-msg5

msg6:   db "Error: string too short", LF
len6:   equ $-msg6

msg7:   db "Error: octal value overflow in \"
eoct:   db "   "                        ; 3 bytes for bad octal
        db LF
len7:   equ $-msg7


        SECTION .bss                    ; uninitialized data section
buf:    resb BUFLEN                     ; buffer for read
ilen:   resd 1                          ; input length
newstr: resb BUFLEN                     ; converted string
olen:   resd 1                          ; output length
escloc: resd 1                          ; addr of start of esc seq



        SECTION .text                   ; Code section.
        global  _start                  ; let loader see entry point

_start: nop                             ; Entry point.
start:                                  ; address for gdb

        ; prompt user for input
        ;
        mov     eax, SYSCALL_WRITE      ; write function
        mov     ebx, STDOUT             ; Arg1: file descriptor
        mov     ecx, msg1               ; Arg2: addr of message
        mov     edx, len1               ; Arg3: length of message
        int     080h                    ; ask kernel to write

        ; read user input
        ;
        mov     eax, SYSCALL_READ       ; read function
        mov     ebx, STDIN              ; Arg 1: file descriptor
        mov     ecx, buf                ; Arg 2: address of buffer
        mov     edx, BUFLEN             ; Arg 3: buffer length
        int     080h

        ; error check
        ;
        mov     [ilen], eax             ; save length of string read
        cmp     eax, 0                  ; check if any chars read
        jg      read_OK                 ; >0 chars read = OK
        mov     eax, SYSCALL_WRITE      ; ow print error mesg
        mov     ebx, STDOUT
        mov     ecx, msg4
        mov     edx, len4
        int     080h
        jmp     exit                    ; skip over rest
read_OK:


; Start of Main Loop
;
; assuming ilen >= 0
; ecx has char count
; esi points to next char in source string
; edi points to next char in destination string
; al has current char
;
L1_init:
        mov     ecx, [ilen]             ; initialize count
        mov     esi, buf                ; point to start of buffer
        mov     edi, newstr             ; point to start of new string

L1_top:
        jecxz   L1_end                  ; too far for short jecxz jump
        mov     al, [esi]               ; get a character
        dec     ecx                     ; update char count
        inc     esi                     ; update source pointer
        cmp     al, '\'                 ; escape sequence?
        je      do_escape

        ; Bottom of main loop. Copy character into newstr
L1_cont:
        mov     [edi], al               ; store char in new string
        inc     edi                     ; update dest pointer
        jmp     short L1_top            ; loop to top if more chars

        ; Post loop clean up
L1_end:
        sub     edi, newstr             ; compute length of newstr
        mov     [olen], edi             ; save it
        jmp     do_report
;
; End of Main Loop


; Process escape sequence
;
do_escape:
        jecxz   too_short               ; no more chars?
        mov     [escloc], esi           ; remember the start
        mov     al, [esi]               ; get char after bslash
        dec     ecx
        inc     esi
        jmp     short single_char       ; check for \x

        ; Error: \ at end of string
too_short:
        mov     eax, SYSCALL_WRITE      ; error, no more chars
        mov     ebx, STDOUT
        mov     ecx, msg6               ; "String too short"
        mov     edx, len6
        int     080h
        jmp     short L1_end

        ; Check if the escape sequence is \ followed by
        ; a legal single character
single_char:
        cmp     al, '\'                 ; is \\?
        je      L1_cont                 ;

        cmp     al, 'a'                 ; is \a?
        jne     not_a
        mov     al, BEL                 ; make noisse
        jmp     L1_cont
not_a:

        cmp     al, 'b'                 ; is \b?
        jne     not_b
        mov     al, BS                  ; backspace
        jmp     L1_cont
not_b:

        cmp     al, 'f'                 ; is \f?
        jne     not_f
        mov     al, FF                  ; form feed
        jmp     L1_cont
not_f:

        cmp     al, 'n'                 ; is \n?
        jne     not_n
        mov     al, LF                  ; line feed
        jmp     L1_cont
not_n:

        cmp     al, 'r'                 ; is \r?
        jne     not_r
        mov     al, CR                  ; carriage return
        jmp     L1_cont
not_r:

        cmp     al, 't'                 ; is \t?
        jne     not_t
        mov     al, HT                  ; horizontal tab
        jmp     L1_cont
not_t:

        cmp     al, 'v'                 ; is \v?
        jne     not_v
        mov     al, VT                  ; vertical tab
        jmp     L1_cont
not_v:

        ; Process octal escape sequence
        ; bl register has char being built
        ;
do_octal:
        cmp     al, '0'                 ; octal?
        jb      bad_char
        cmp     al, '7'
        ja      bad_char
        sub     al, '0'
        mov     bl, al                  ; store digit 1

        ; check second octal digit
        ;
        jecxz   save_octal              ; no more chars?
        mov     al, [esi]               ; get char
        inc     esi
        dec     ecx
        cmp     al, '0'                 ; legal octal digit?
        jb      end_octal
        cmp     al, '7'
        ja      end_octal
        sub     al, '0'                 ; compute digit 2
        shl     bl, 3
        add     bl, al                  ; store digit 2

        ; check third octal digit
        ;
        jecxz   save_octal              ; no more chars?
        mov     al, [esi]               ; get char
        inc     esi
        dec     ecx
        cmp     al, '0'                 ; legal octal digit?
        jb      end_octal
        cmp     al, '7'
        ja      end_octal
        sub     al, '0'                 ; compute digit 3
        shl     bl, 3
        jc      overflow                ; octal overflow?
        add     bl, al                  ; store digit 3
        jmp     save_octal

end_octal:                              ; Post process octal sequence
        inc     ecx                     ; put char back
        dec     esi

save_octal:
        mov     al, bl                  ; save octal char constructed
        jmp     L1_cont


        ; Error: Unknown escape sequence
        ;
bad_char:
        push    ecx                     ; save char count
        mov     [echar], al             ; insert bad char in msg5
        mov     eax, SYSCALL_WRITE      ; write error message
        mov     ebx, STDOUT
        mov     ecx, msg5               ; "Unknown escape seq..."
        mov     edx, len5
        int     080h

        pop     ecx                     ; restore char count
        mov     al, '\'                 ; insert dummy char
        jmp     L1_cont


        ; Error: octal value exceeds 255
        ;
overflow:
        push    ecx                     ; save char count
        mov     ebx, [escloc]           ; get start of offending seq
        mov     ax, [ebx]               ; copy seq to error message
        mov     [eoct], ax
        mov     al, [ebx+2]
        mov     [eoct+2], al

        mov     eax, SYSCALL_WRITE      ; write error message
        mov     ebx, STDOUT
        mov     ecx, msg7               ; "Octal overflow"
        mov     edx, len7
        int     080h

        pop     ecx                     ; restore char count
        mov     al, '\'                 ; insert dummy char
        jmp     L1_cont


; Report the results
;

do_report:

        ; print out user input for feedback
        ;
        mov     eax, SYSCALL_WRITE      ; write message
        mov     ebx, STDOUT
        mov     ecx, msg2
        mov     edx, len2
        int     080h

        mov     eax, SYSCALL_WRITE      ; write user input
        mov     ebx, STDOUT
        mov     ecx, buf
        mov     edx, [ilen]
        int     080h

        ; print out converted string
        ;
        mov     eax, SYSCALL_WRITE      ; write message
        mov     ebx, STDOUT
        mov     ecx, msg3
        mov     edx, len3
        int     080h

        mov     eax, SYSCALL_WRITE      ; write out string
        mov     ebx, STDOUT
        mov     ecx, newstr
        mov     edx, [olen]
        int     080h


; Final exit
;
exit:   mov     eax, SYSCALL_EXIT       ; exit function
        mov     ebx, 0                  ; exit code, 0=normal
        int     080h                    ; ask kernel to take over
