module "markdown parser"

struct TMarkdownResults
  .hContent     dd ?
  .hIndex       dd ?
  .hKeywords    dd ?
  .hDescription dd ?
ends

fcloseLink              = 1
fcloseImage             = 2
fcloseInlineImage       = 3
fcloseObject            = 4


fblkQuote       = 1
fblkTable       = 2


fstateHeader    = $01
fstatePara      = $02
fstateWhite     = $04
fstateBold      = $08
fstateUnderline = $20
fstateStrikeout = $40
fstateItalic    = $80

fstateTextNow  = $200         ; don't use the second byte for any other flags.
fstateTextPrev = $100

fstateInlineSource    =  $10000
fstateBlockSource     =  $20000
fstateTable           = $100000
fstateSubChapter      = $200000


; Translates some markdown text to HTML
; Arguments:
;     .markdown - pointer or handler of the markdown text.
;     .procFixLink - [proc .ptrLink] procedure that process the link address and returns
;                    handle to the processed link address. if [.procFixLink]=NULL, the links
;                    are inserted as they are in the HTML.
;     .fBacklinks - flag, that defines whether to insert after the headers back links to the
;                   table of contents item.
;     .pResult - pointer to TMarkdownResults structure where the result strings are returned.
;
; Returns:
;   The procedure returns  4 string handles in the TMarkdownResults structure pointed by
;   .pResult argument.

proc TranslateMarkdown, .markdown, .procFixLink, .fBacklinks, .pResult

.result      dd ?
.index       dd ?
.keywords    dd ?
.description dd ?

  ; first and second pass local variables
  .IdString        rb  256
  .IdLen           dd  ?

  .cntH2           dd  ?
  .cntH3           dd  ?
  .cntH4           dd  ?
  .cntH5           dd  ?
  .cntH6           dd  ?

.pHashTable dd ?
.pHTML      dd ?
.limit      dd ?

  .blocks rb 1024
  .pblock dd ?

begin
        pushad

        stdcall GetMem, 4*65536
        mov     [.pHashTable], eax
        stdcall GetMem, 16384
        mov     [.pHTML], eax
        add     eax, 8192
        mov     [.limit], eax

        stdcall StrNew
        mov     [.result], eax

        stdcall StrNew
        mov     [.index], eax

        stdcall StrNew
        mov     [.keywords], eax

        stdcall StrNew
        mov     [.description], eax

        stdcall StrPtr, [.markdown]
        mov     esi, eax

        call    .FirstPass

        stdcall StrPtr, [.markdown]
        mov     esi, eax
        call    .SecondPass

        stdcall FreeMem, [.pHTML]
        stdcall FreeMem, [.pHashTable]

        mov     edi, [.pResult]

        mov     eax, [.result]
        mov     edx, [.index]
        mov     ebx, [.keywords]
        mov     ecx, [.description]

        mov     [edi+TMarkdownResults.hContent], eax
        mov     [edi+TMarkdownResults.hIndex], edx
        mov     [edi+TMarkdownResults.hKeywords], ebx
        mov     [edi+TMarkdownResults.hDescription], ecx

        popad
        return

;.... start of .FirstPass ..........................................

.FirstPass:
        mov     edi, [.pHTML]
        mov     [.cntH2], 0
        mov     [.cntH3], 0
        mov     [.cntH4], 0
        mov     [.cntH5], 0
        mov     [.cntH6], 0

.line_start:
        cmp     edi, [.limit]
        jb      @f

        mov     dword [edi], 0
        stdcall StrCat, [.index], [.pHTML]
        mov     edi, [.pHTML]

@@:
        cmp     byte [esi], '#'
        je      .header

        cmp     byte [esi], '['
        je      .link

.skip_to_eol:
        mov     al, [esi]
        lea     esi, [esi+1]
        cmp     al, $0d
        je      .line_start
        cmp     al, $0a
        je      .line_start
        test    al, al
        jnz     .skip_to_eol

.end_of_file:
        mov     dword [edi], 0
        stdcall StrCat, [.index], [.pHTML]

        retn

; ----- return from .FirstPass -------------------

; create table of contents item.
.header:
        xor     ecx, ecx

.scan_level:
        inc     ecx
.scan_space:
        lea     esi, [esi+1]
        mov     al, [esi]
        cmp     al, '#'
        je      .scan_level
        cmp     al, ' '
        je      .scan_space
        cmp     al, $09
        je      .scan_space

        cmp     al, $0d
        je      .line_start
        cmp     al, $0a
        je      .line_start

        cmp     ecx, 6
        jbe     @f
        mov     ecx, 6
@@:
        call    .BuildID

        mov     dword [edi], '<a i'
        lea     edi, [edi+4]
        mov     dword [edi], 'd="B'
        lea     edi, [edi+4]

        call    .InsertID

        mov     dword [edi], '" hr'
        mov     dword [edi+4], 'ef="'
        mov     word  [edi+8], '#H'
        lea     edi, [edi+10]

        call    .InsertID

        mov     dword [edi],    '" st'
        mov     dword [edi+4],  'yle='
        mov     dword [edi+8],  '"pad'
        mov     dword [edi+12], 'ding'
        mov     dword [edi+16], '-lef'
        mov     word [edi+20],  't:'
        lea     edi, [edi+22]

        push    ecx
        mov     eax, ecx
        mov     ecx, 10
        sub     eax, 2
        jns     @f
        xor     eax, eax
@@:
        call    _NumToStrU
        pop     ecx

        mov     dword [edi], 'em">'
        lea     edi, [edi+4]

        cmp     ecx, 1
        je      @f
        call    .InsertID
@@:
; copy the header text

.copy_loop:
        mov     al, [esi]
        lea     esi, [esi+1]
        cmp     al, $0d
        je      .end_header
        cmp     al, $0a
        je      .end_header
        test    al, al
        jz      .end_header

        mov     [edi], al
        lea     edi, [edi+1]
        jmp     .copy_loop

.end_header:
        dec     esi
        mov     dword [edi],   '</a>'
        mov     dword [edi+4], '<br '
        mov     word [edi+8], '/>'
        lea     edi, [edi+10]
        jmp     .line_start

;link processing
.link:
        cmp     byte [esi+1], '#'
        je      .skip_to_eol

        mov     edx, $811C9DC5   ; 2166136261 ; FNV offset basis
        mov     ecx, esi
        and     [.link_level], 0

.label_loop:
        mov     al, [esi]
        lea     esi, [esi+1]

        cmp     al, $20
        je      .label_loop
        cmp     al, $09
        je      .label_loop

        cmp     al, $0d
        je      .line_start
        cmp     al, $0a
        je      .line_start

        cmp     al, "["
        jne     @f
        inc     [.link_level]
@@:
        test    al, al
        jz      .end_of_file

        xor     dl, al
        imul    edx, $01000193                  ;   16777619              ; FNV prime
        cmp     al, ']'
        jne     .label_loop

        dec     [.link_level]
        jnz     .label_loop


; fold the hash to 16 bit value...
        mov     ebx, edx
        shr     ebx, 16
        xor     ebx, edx
        and     ebx, $ffff
        mov     edx, ebx

; search free slot
.search_slot:
        mov     eax, [.pHashTable]
        xchg    ecx, [eax+4*ebx]
        test    ecx, ecx
        jz      .skip_to_eol

; conflict...
; compare the labels on ecx (the old content of the slot) with [HashTable+4*ebx] (the new pointer)
        push    edi ecx

        mov     edi, [eax+4*ebx]
        and     [.link_level], 0

.first:
        mov     al, [ecx]
        lea     ecx, [ecx+1]

        cmp     al, $20
        je      .first
        cmp     al, $09
        je      .first

.second:
        mov     ah, [edi]
        lea     edi, [edi+1]
        cmp     ah, $20
        je      .second
        cmp     ah, $09
        je      .second

        cmp     al, ah
        jne     .add_not_equal

        cmp     ah, '['
        jne     @f
        inc     [.link_level]
@@:
        cmp     ah, ']'
        jne     .first

        dec     [.link_level]
        jnz     .first

        pop     ecx edi
        jmp     .skip_to_eol

; save it to the next slot
.add_not_equal:
        pop     ecx edi

        inc     ebx
        and     ebx, $ffff
        cmp     ebx, edx
        jne     .search_slot

        int3    ; full hash table -> to be implemented error processing

;---- End of .FirstPass --------------------------------


;.... Start of .SecondPass .............................

locals
  ; .SecondPass only local variables
  .state        dd ?
  .last_header  dd ?
  .linkID_end   dd ?
  .link_level   dd ?

  .close_now    dd ?
  .close_index  dd ?
  .close_stack  rb 256           ; 256 levels of nested links.
endl

.SecondPass:
        mov     edi, [.pHTML]

        xor     eax, eax
        mov     [.state], eax
        mov     [.close_index], eax
        mov     [.cntH2], eax
        mov     [.cntH3], eax
        mov     [.cntH4], eax
        mov     [.cntH5], eax
        mov     [.cntH6], eax
        mov     [.pblock], eax


.line_start2:
        shr     byte [.state+1], 1   ; fstateTextPrev = fstateTextNow; fStateTextNow = 0

        cmp     edi, [.limit]
        jb      @f
        mov     dword [edi], 0
        stdcall StrCat, [.result], [.pHTML]
        mov     edi, [.pHTML]
@@:

        test    [.state], fstateBlockSource
        jnz     .copy_source

        cmp     byte [esi], '['
        je      .link_definition

        cmp     byte [esi], '#'
        je      .header2

        cmp     byte [esi], ';'
        je      .block_element

        cmp     dword [esi], '----'
        je      .horiz_rule


.scan_line2:
        lodsb

        cmp     al, ' '
        je      .white_space
        cmp     al, $09         ; tab
        je      .white_space

        cmp     al, $0d
        je      .end_of_line
        cmp     al, $0a
        je      .end_of_line
        test    al, al
        jz      .end_of_line

; it is not white space character...
        test    [.state], fstateTextPrev or fstateTextNow or fstateHeader
        jz      .start_para

.process_char:
        or      [.state], fstateTextNow

        test    [.state], fstateInlineSource
        jnz     .inline_source

        cmp     al, '*'
        je      .process_bold

        cmp     al, '_'
        je      .process_underline

        cmp     al, '-'
        je      .process_strikeout

        cmp     al, '/'
        je      .process_italic

        cmp     al, '['
        je      .is_link_begin

        cmp     al, ']'
        je      .is_link_end

        cmp     al, '\'
        je      .escape_next

.inline_source:
        cmp     al, '`'
        je      .process_inline_code


.normal_char:
        and     [.state], not fstateWhite

.markup_ok:

        call    .store_char
        jmp     .scan_line2


.escape_next:

        cmp     byte [esi], $20
        jbe     .normal_char

        lodsb
        jmp     .normal_char


; stores the char from al in [edi], and
; replaces the invalid HTML characters
; with their &name; representation.

.store_char:
        cmp     al, 'A'
        jb      @f

        mov     [edi], al
        inc     edi
        retn

@@:
        cmp     al, '<'
        je      .char_less_then
        cmp     al, '>'
        je      .char_greater_then
        cmp     al, '"'
        je      .char_quote
        cmp     al, '&'
        je      .char_amp
;        cmp     al, "'"
;        je      .char_apos

        mov     [edi], al
        lea     edi, [edi+1]
        retn

.char_less_then:
        mov     dword [edi], '&lt;'
        lea     edi, [edi+4]
        retn

.char_greater_then:
        mov     dword [edi], '&gt;'
        lea     edi, [edi+4]
        retn

.char_quote:
        mov     dword [edi], '&quo'
        mov     word [edi+4], 't;'
        lea     edi, [edi+6]
        retn

;.char_apos:
;        mov     dword [edi], '&apo'
;        mov     word [edi+4], 's;'
;        lea     edi, [edi+6]
;        retn

.char_amp:
        mov     dword [edi], '&amp'
        mov     byte [edi+4], ';'
        lea     edi, [edi+5]
        retn


.start_para:
        test    [.state], fstateTable
        jz      .not_td_open

        mov     dword [edi], '<td>'
        lea     edi, [edi+4]

        cmp     al, '('         ; col, row span
        jne     .td_ok

        dec     edi

        mov     dword [edi], ' col'
        mov     dword [edi+4], 'span'
        mov     word  [edi+8], '="'
        lea     edi, [edi+10]

.copy_span:
        mov     al, [esi]
        lea     esi, [esi+1]
        cmp     al, ','
        je      .row_span
        cmp     al, ')'
        je      .end_span

        call    .store_char
        jmp     .copy_span

.row_span:
        mov     dword [edi],    '" ro'
        mov     dword [edi+4],  'wspa'
        mov     dword [edi+8],  'n="'
        lea     edi, [edi+11]
        jmp     .copy_span

.end_span:
        mov     word [edi], '">'
        lea     edi, [edi+2]

        mov     al, [esi]
        lea     esi, [esi+1]

.td_ok:
        or      [.state], fstatePara
        jmp     .process_char

.not_td_open:
        mov     word [edi], '<p'
        lea     edi, [edi+2]
        or      [.state], fstatePara

; is it bullet list?

        cmp     al, '*'
        jne     .close_p_tag

        cmp     byte [esi], ' '
        jne     .close_p_tag

        mov     dword [edi], ' cla'
        mov     dword [edi+4], 'ss="'
        mov     dword [edi+8], 'uli"'
        lea     edi, [edi+12]

        inc     esi
        mov     al, [esi]
        inc     esi

.close_p_tag:
        mov     byte [edi], '>'
        lea     edi, [edi+1]
        jmp     .process_char

.process_inline_code:
        mov     ecx, fstateInlineSource
        mov     ebx, 'code'
        jmp     .ProcessTag

.process_bold:
        mov     ecx, fstateBold
        mov     ebx, 'b'
        jmp     .ProcessTag


.process_italic:
        mov     ecx, fstateItalic
        mov     ebx, 'i'
        jmp     .ProcessTag

.process_underline:
        mov     ecx, fstateUnderline
        mov     ebx, 'u'
        jmp     .ProcessTag

.process_strikeout:
        mov     ecx, fstateStrikeout
        mov     ebx, 's'

; ecx = state mask
; ebx = html tag
.ProcessTag:
        mov     dl, al

        test    [.state], ecx
        jz      .start_tag

; close tag
        not     ecx
        and     [.state], ecx

        mov     word [edi], '</'
        lea     edi, [edi+2]
        jmp     .finish_tag

.start_tag:
        test    [.state], fstateWhite
        jz      .normal_char
        cmp     byte [esi], $20
        je      .normal_char
        cmp     byte [esi], $09
        je      .normal_char
        cmp     byte [esi], $0d
        je      .normal_char
        cmp     byte [esi], $0a
        je      .normal_char

        or      [.state], ecx

; open tag
        mov     byte [edi], '<'
        lea     edi, [edi+1]

.finish_tag:
        mov     [edi], ebx

.taglen:
        lea     edi, [edi+1]
        shr     ebx, 8
        jnz     .taglen

        mov     byte [edi], '>'
        lea     edi, [edi+1]

        jmp     .scan_line2


.white_space:
        test    [.state], fstateWhite
        jnz     .scan_line2

        or      [.state], fstateWhite
        mov     [edi], al
        lea     edi, [edi+1]
        jmp     .scan_line2

; end of line and end of file processing
.end_of_line:
        test    al, al
        jz      @f

        xor     al, $0d xor $0a
        cmp     [esi], al
        jne     @f
        inc     esi

@@:
        or      [.state], fstateWhite

        test    al, al
        jz      .close_p_eof

        test    [.state], fstateTextNow
        jnz     .para_ok

.close_p_eof:
        call    .close_paragraph

.para_ok:
        test    [.state], fstateHeader
        jz      .crlf

; insert back link
        cmp     [.fBacklinks], 0
        je      .backlink_ok

        mov     dword [edi],    '<a c'
        mov     dword [edi+4],  'lass'
        mov     dword [edi+8],  '="ba'
        mov     dword [edi+12], 'ck" '
        lea     edi, [edi+16]

        mov     dword [edi],   'href'
        mov     dword [edi+4], '="#B'
        lea     edi, [edi+8]

        call    .InsertID

        mov     word  [edi], '">'
        mov     dword [edi+2], '</a>'
        lea     edi, [edi+6]

.backlink_ok:
        mov     byte [edi], '<'
        lea     edi, [edi+1]

        mov     ecx, [.last_header]
        add     ecx, '/h0>'
        mov     [edi], ecx
        lea     edi, [edi+4]

        and     [.state], not (fstateHeader or fstateTextNow)   ; For the paragraph flow, the header loops like empty line.

; after the header end, starts subchapter

        mov     dword [edi], '<div'
        mov     dword [edi+4], ' cla'
        mov     dword [edi+8], 'ss="'
        mov     dword [edi+12], 'chap'
        mov     dword [edi+16], 'ter"'
        mov     byte  [edi+20], '>'
        add     edi, 21

        or      [.state], fstateSubChapter

.crlf:
        test    [.state], fstateTextPrev or fstateTextNow
        jz     @f
        mov     word [edi], $0a0d
        lea     edi, [edi+2]
@@:
        test    al, al
        jnz     .line_start2

        call    .close_subchapter

.finish:
        mov     dword [edi], 0
        stdcall StrCat, [.result], [.pHTML]
        retn


.is_link_begin:
;        test    [.state], fstateWhite
;        jz      .normal_char

.searchID:
        dec     esi

;.SearchLink:
        push    edi
        push    esi

; first compute the hash for the link label
        mov     edx, $811C9DC5                  ; 2166136261              ; FNV offset basis
        and     [.link_level], 0

.hashloop:
        mov     al, [esi]
        lea     esi, [esi+1]

        cmp     al, $20
        je      .hashloop
        cmp     al, $09
        je      .hashloop

        cmp     al, $0d
        je      .not_a_link
        cmp     al, $0a
        je      .not_a_link
        test    al, al
        je      .not_a_link

        cmp     al, '['
        jne     @f
        inc     [.link_level]
@@:

        xor     dl, al
        imul    edx, $01000193                  ;   16777619              ; FNV prime
        cmp     al, ']'
        jne     .hashloop

        dec     [.link_level]
        jnz     .hashloop


; fold the hash to 16 bit value...
        mov     [.linkID_end], esi

        mov     ebx, edx
        shr     ebx, 16
        xor     ebx, edx
        dec     ebx

.hash_table_loop:
        lea     ebx, [ebx+1]
        and     ebx, $ffff

        mov     eax, [.pHashTable]
        mov     edi, [eax+4*ebx]
        mov     esi, [esp]

        test    edi, edi
        jnz     .cmp_labels

;.inline_link:                   ; the linkID was not found in the hash table. So, it should be inline link address.

        mov     edi, esi           ; the address of the link is in esi
        mov     esi, [.linkID_end] ; the address of the end of the linkID.
        jmp     .create_link

.not_a_link:
        pop     esi edi
        mov     al, [esi]
        lea     esi, [esi+1]
        jmp     .normal_char    ; the link address was not found, so it is not link at all.

.cmp_labels:

        and     [.link_level], 0

.cmp_labels_loop:
        mov     al, [esi]
        lea     esi, [esi+1]

        cmp     al, ' '
        je      .cmp_labels_loop
        cmp     al, $09
        je      .cmp_labels_loop

.target:
        mov     ah, [edi]
        lea     edi, [edi+1]

        cmp     ah, ' '
        je      .target
        cmp     ah, $09
        je      .target

        cmp     al, ah
        jne     .hash_table_loop      ; it is not the same label - try again.

        cmp     ah, '['
        jne     @f

        inc     [.link_level]
        jmp     .cmp_labels_loop
@@:
        cmp     ah, ']'
        jne     .cmp_labels_loop

        dec     [.link_level]
        jnz     .cmp_labels_loop

; the labels are equal, so search the address.

.create_link:
        mov     [.close_now], fcloseLink

        mov     edx, [esp]              ; the start of the linkID.

        cmp     byte [esi], '['         ; is the link has separate ID and text?
        jne     @f
        mov     [esp], esi              ; replace the source address.
@@:
        inc     edx
        cmp     byte [edx], '#'       ; is it internal link?
        je      .firstnw

; inline image?
        cmp     byte [edx], '?'       ; is it inline image?
        jne     .maybe_block_image

        mov     [.close_now], fcloseInlineImage
        jmp     .firstnw

.maybe_block_image:
        cmp     byte [edx], '!'       ; is it an image?
        jne     .maybe_object

        mov     [.close_now], fcloseImage
        jmp     .firstnw

.maybe_object:
        cmp     byte [edx], '$'
        jne     .firstnw

        mov     [.close_now], fcloseObject

.firstnw:
        and     [.link_level], 0
        cmp     byte [edi], '['         ; inline address.
        jne     .firstnw_loop

        inc     edi
        inc     [.link_level]

.firstnw_loop:
        mov     al, [edi]
        inc     edi

        cmp     al, ' '
        je      .firstnw_loop
        cmp     al, $09
        je      .firstnw_loop

        dec     edi
        mov     edx, edi

.internal_link_address:
        pop     esi edi

        cmp     [.close_now], fcloseLink
        je      .it_is_link

        cmp     [.close_now], fcloseObject
        je      .it_is_object

; it is image.
        mov     dword [edi],    '<img'
        mov     dword [edi+4],  ' cla'
        mov     dword [edi+8],  'ss="'

        cmp     [.close_now], fcloseImage
        je      .block_image

        mov     dword [edi+12], 'ico'
        jmp     .check_style

.block_image:
        mov     dword [edi+12], 'txt'

; copy the style number if any:
.check_style:
        lea     edi, [edi+15]

        push    esi
        mov     esi, edx

        lodsb

        cmp     al, '!'
        je      .copy_style
        cmp     al, '?'
        je      .copy_style

        dec     esi

.copy_style:
        lodsb
        cmp     al, '0'
        jb      .style_ok
        cmp     al, '9'
        ja      .style_ok

        stosb
        jmp     .copy_style


.style_ok:
        lea     edx, [esi-1]
        pop     esi

.img_tag_end:
        mov     dword [edi], '" sr'
        mov     dword [edi+4],  'c="'
        lea     edi, [edi+7]
        jmp     .link_tag_ok

.it_is_object:
        push    esi

        mov     esi, edx
        cmp     byte [edx], '$'
        jne     @f
        inc     esi
@@:
        mov     dword [edi],   '<obj'
        mov     dword [edi+4], 'ect '
        add     edi, 8

        cmp     byte [esi], '0'
        jb      .par_data
        cmp     byte [esi], '9'
        ja      .par_data

        mov     dword [edi],  ' wid'
        mov     dword [edi+4],'th="'
        add     edi, 8

.copy_width:
        lodsb
        cmp     al, 'x'
        je      .width_ok
        cmp     al, ' '
        jb      .height_ok
        cmp     al, ']'
        je      .height_ok
        cmp     al, '0'
        jb      .height_ok
        cmp     al, '9'
        ja      .height_ok

        stosb
        jmp     .copy_width

.width_ok:
        mov     dword [edi],   '" he'
        mov     dword [edi+4], 'ight'
        mov     word [edi+8],  '="'
        add     edi, 10

.copy_height:
        lodsb
        cmp     al, ':'
        je      .height_ok
        cmp     al, ' '
        jb      .height_ok
        cmp     al, ']'
        je      .height_ok
        cmp     al, '0'
        jb      .height_ok
        cmp     al, '9'
        ja      .height_ok

        stosb
        jmp     .copy_height

.height_ok:
        mov     word [edi], '" '
        add     edi, 2

.par_data:
        mov     dword [edi],   'data'
        mov     word [edi+4],  '="'
        add     edi, 6

        lea     edx, [esi-1]
        pop     esi
        jmp     .link_tag_ok


; yes the link was found.
.it_is_link:
        mov     dword [edi],   '<a h'
        mov     dword [edi+4], 'ref='
        mov     byte [edi+8], '"'
        lea     edi, [edi+9]

.link_tag_ok:
        push    ecx esi

; push the close flag:

        mov     esi, [.close_index]
        mov     ecx, [.close_now]
        mov     [.close_stack+esi], cl
        inc     [.close_index]

; copy link address

        mov     esi, edx

        cmp     byte [esi], '?'
        je      .skip_img_char

        cmp     byte [esi], ':'
        je      .skip_img_char
        cmp     byte [esi], '$'
        je      .skip_img_char

        cmp     byte [esi], '!'
        jne     .copy_link_prefix

.skip_img_char:

        inc     esi

.copy_link_prefix:
        cmp     [.procFixLink], 0
        je      .copy_link_proc

        stdcall [.procFixLink], esi, edi
        mov     edi, eax
        mov     esi, edx

.copy_link_proc:

.copy_link_loop:
        lodsb

        cmp     al, $0d
        je      .end_link
        cmp     al, $0a
        je      .end_link

        cmp     al, '['
        jne     @f
        inc     [.link_level]
@@:
        cmp     al, ']'
        je      .end_link_check

        test    al, al
        jz      .end_link

.store_link_char:
        call    .store_char
        jmp     .copy_link_loop

.end_link_check:
        dec     [.link_level]
        jnz     .store_link_char

.end_link:
        pop     esi ecx

        cmp     [.close_now], fcloseImage
        je      .finalize_image
        cmp     [.close_now], fcloseInlineImage
        je      .finalize_image

; finalize link and object tags.

        mov     word [edi], '">'
        add     edi, 2

.go_link_text:
        and     [.state], not fstateWhite

        inc     esi                     ; skip the leading "["
        cmp     byte [esi], '#'
        jne     .scan_line2
        inc     esi
        jmp     .scan_line2

.finalize_image:
        mov     dword [edi],   '" al'
        mov     dword [edi+4], 't="'
        lea     edi, [edi+7]
        jmp     .go_link_text

;....................................................................

.is_link_end:
        call    .close_one_link
        jc      .normal_char

        jmp     .scan_line2


.close_all_links:
        call    .close_one_link
        jnc     .close_all_links
        retn

; closes one link from the stack.
; returns CF=1 on empty stack.

.close_one_link:

        mov     ecx, [.close_index]
        sub     ecx, 1
        jnc     .close_pop

        retn

.close_pop:
        mov     [.close_index], ecx
        mov     al, [.close_stack+ecx]

        cmp     al, fcloseObject
        je      .close_object_tag

        cmp     al, fcloseLink
        je      .close_link_tag

; close the image tag

        mov     dword [edi], '" />'
        add     edi, 4

.close_end:
        retn


.close_link_tag:

        mov     dword [edi], '</a>'
        add     edi, 4
        retn

.close_object_tag:
        mov     dword [edi],   '</ob'
        mov     dword [edi+4], 'ject'
        mov     byte  [edi+8], '>'
        add     edi, 9
        retn

;....................................................................
; Processing of block source code
.copy_source:

.source_line:

        cmp     edi, [.limit]
        jb      @f
        mov     dword [edi], 0
        stdcall StrCat, [.result], [.pHTML]
        mov     edi, [.pHTML]
@@:

        cmp     dword [esi], ';end'
        je      .end_source_block

.copy_source_line:
        mov     al, [esi]
        lea     esi, [esi+1]

        call    .store_char

        test    al, al
        jz      .end_source_block2

        cmp     al, $0d
        je      .end_source_line
        cmp     al, $0a
        jne     .copy_source_line

.end_source_line:
        xor     al, $0d xor $0a
        cmp     [esi], al
        jne     .source_line

        inc     esi
        mov     [edi], al
        lea     edi, [edi+1]
        jmp     .source_line

.end_source_block2:
        dec     esi

.end_source_block:
        mov     dword [edi],   '</co'
        mov     dword [edi+4], 'de><'
        mov     dword [edi+8], '/pre'
        mov     dword [edi+12],'></d'
        mov     dword [edi+16],'iv> '
        mov     word  [edi+20], $0a0d
        lea     edi, [edi+22]

        and     [.state], not fstateBlockSource
        jmp     .skip2

;....................................................................

.close_paragraph:
        test    [.state], fstatePara
        jz      .close_para_ok

        call    .close_inlines
        call    .close_all_links

        test    [.state], fstateTable
        jnz     .close_td

        mov     dword [edi], '</p>'
        lea     edi, [edi+4]
        jmp     .close_para_ok

.close_td:
        mov     dword [edi], '</td'
        mov     dword [edi+4], '>'+($0a0d shl 8)
        lea     edi, [edi+7]

.close_para_ok:
        and     [.state], not fstatePara
        retn

;....................................................................

.close_subchapter:

        test    [.state], fstateSubChapter
        jz      .close_subchapter_ok

        mov     dword [edi], '</di'
        mov      word [edi+4], 'v>'
        add     edi, 6

        and     [.state], not fstateSubChapter

.close_subchapter_ok:
        retn

;....................................................................

; closes all open inline tags.

.close_inlines:

        test    [.state], fstateBold
        jz      .bold_ok

        mov     dword [edi], '</b>'
        add     edi, 4

.bold_ok:
        test    [.state], fstateItalic
        jz      .italic_ok

        mov     dword [edi], '</i>'
        add     edi, 4

.italic_ok:
        test    [.state], fstateUnderline
        jz      .underline_ok

        mov     dword [edi], '</u>'
        add     edi, 4

.underline_ok:
        test    [.state], fstateStrikeout
        jz      .strike_ok

        mov     dword [edi], '</s>'
        add     edi, 4

.strike_ok:
        test    [.state], fstateInlineSource
        jz      .inline_source_ok

        mov     dword [edi],   '</co'
        mov     dword [edi+4], 'de>'
        add     edi, 7

.inline_source_ok:
        and     [.state], not (fstateBold or fstateItalic or fstateUnderline or fstateStrikeout or fstateInlineSource)
        retn


;....................................................................
; horizontal rule element

.horiz_rule:
        call    .close_paragraph

        mov     dword [edi], '<hr '
        mov     dword [edi+4], '/>'+($0a0d shl 16)
        lea     edi, [edi+8]
        jmp     .skip2

;....................................................................
; Processing of the block elements.

.block_element:
        cmp     dword [esi], ';end'
        je      .end_block

        cmp     dword [esi], ';kil'
        jne     .not_kill
        cmp     byte [esi+4], 'l'
        je      .end_all_blocks


.not_kill:
        cmp     dword [esi], ';beg'
        jne     .not_code
        cmp     word [esi+4], 'in'
        jne     .not_code

; code block
        call    .close_paragraph

        mov     dword [edi],    '<div'
        mov     dword [edi+4],  ' cla'
        mov     dword [edi+8],  'ss="'
        mov     dword [edi+12], 'code'
        mov     dword [edi+16], '"><p'
        mov     dword [edi+20], 're><'
        mov     dword [edi+24], 'code'
        mov     dword [edi+28], ' cla'
        mov     dword [edi+32], 'ss="'
        lea     edi, [edi+36]

        push    esi
        add     esi, 6
        call    .CopyName
        pop     esi

        mov     word [edi],     '">'
        lea     edi, [edi+2]

        or      [.state], fstateBlockSource
        jmp     .skip2


.not_code:
        cmp     dword [esi], ';tab'
        jne     .not_table
        cmp     word [esi+4], 'le'
        jne     .not_table

; table block;
        call    .close_paragraph

        mov     al, fblkTable
        call    .PushBlock

        mov     dword [edi], '<tab'
        mov     dword [edi+4], 'le c'
        mov     dword [edi+8], 'lass'
        mov     dword [edi+12],'="tx'
        mov     dword [edi+16], 't"><'
        mov     dword [edi+20], 'tr>'+($0d shl 24)
        mov     byte  [edi+24], $0a
        lea     edi,  [edi+25]

        or      [.state], fstateTable
        jmp     .skip2


.not_table:
        cmp     dword [esi], ';---'
        jne     .not_row

.table_row:
        call    .close_paragraph

        mov     dword [edi],   '</tr'
        mov     dword [edi+4], '><tr'
        mov     byte  [edi+8], '>'
        lea     edi, [edi+9]
        jmp     .skip2


.not_row:
        cmp     dword [esi], ';quo'
        jne     .not_quote
        cmp     word [esi+4], 'te'
        jne     .not_quote

; block quote
        call    .close_paragraph

        mov     al, fblkQuote
        call    .PushBlock

        mov     dword [edi],    '<div'
        mov     dword [edi+4],  ' cla'
        mov     dword [edi+8],  'ss="'
        mov     dword [edi+12], 'bq">'
        mov     dword [edi+16], '<h1 '
        mov     dword [edi+20], 'clas'
        mov     dword [edi+24], 's="b'
        mov     dword [edi+28], 'q">'
        lea     edi, [edi+31]

        push    esi
        add     esi, 6

        call    .CopyName

        mov     dword [edi],  '</h1'
        mov     byte [edi+4], '>'
        lea     edi, [edi+5]

        pop     esi
        jmp     .skip2


.not_quote:
        cmp     dword [esi], ';key'
        jne     .not_keywords
        cmp     dword [esi+4], 'word'
        jne     .not_keywords
        cmp     byte [esi+8], 's'
        jne     .not_keywords

        add     esi, 9
        mov     ecx, [.keywords]
        jmp     .append_line_to_string

.not_keywords:
        cmp     dword [esi], ';des'
        jne     .not_desc
        cmp     dword [esi+4], 'crip'
        jne     .not_desc
        cmp     dword [esi+8], 'tion'
        jne     .not_desc

        add     esi, 12
        mov     ecx, [.description]

.append_line_to_string:


        push    esi     ; first argument of

        nop

.seek_eoa:
        mov     al, [esi]
        lea     esi, [esi+1]

        test    al, al
        jz      .end_of_appended

        cmp     al, $0d
        je      .end_of_appended

        cmp     al, $0a
        jne     .seek_eoa

.end_of_appended:
        dec     esi
        mov     byte [esi], 0

        stdcall StrCat, ecx ; start address from the stack
        mov     [esi], al   ; restore the text
        jmp     .skip2


.not_desc:
        jmp     .skip2          ; ";" at the begining without keyword is comment. The line is ignored.


.end_block:
        call    .close_paragraph
        call    .close_subchapter
        call    .do_end_block
        jmp     .skip2

.end_all_blocks:
        call    .close_paragraph

.end_all_loop:
        call    .do_end_block
        jnc     .end_all_loop
        jmp     .skip2


.do_end_block:
        call    .PopBlock
        test    eax, eax
        jnz     .what_block
        stc
        retn

.what_block:
        cmp     eax, fblkQuote
        je      .end_quote
        cmp     eax, fblkTable
        je      .end_table

        clc
        retn

.end_quote:
        mov     dword [edi], '</di'
        mov     word  [edi+4], 'v>'
        lea     edi, [edi+6]
        clc
        retn

.end_table:
        mov     dword [edi], '</tr'
        mov     dword [edi+4], '>'+($0a0d shl 8)
        mov     dword [edi+7], '</ta'
        mov     dword [edi+11],'ble>'
        mov     word  [edi+15], $0a0d
        lea     edi, [edi+17]
        and     [.state], not fstateTable
        clc
        retn

;....................................................................
.PushBlock:
        push    ecx
        mov     ecx, [.pblock]
        mov     [.blocks+ecx], al
        inc     ecx
        cmp     ecx, 1024
        jb      @f
        dec     ecx
@@:
        mov     [.pblock], ecx
        pop     ecx
        retn

.PopBlock:
        mov     eax, [.pblock]
        dec     eax
        jns     @f
        xor     eax, eax
        mov     [.pblock], eax
        retn
@@:
        mov     [.pblock], eax
        movzx   eax, [.blocks+eax]
        retn

;....................................................................
; processing link definitions in the second pass.
.link_definition:

        mov     al, [esi+1]
        cmp     al, '#'
        je      .define_anchor

        cmp     al, '^'
        jne     .skip2


;.define_footnote:

        jmp     .skip2                  ; any other variant should be
                                        ; stripped from the text.

.define_anchor:
        add     esi, 2
        mov     dword [edi], '<p>'
        lea     edi, [edi+3]
        or      [.state], fstatePara or fstateTextNow

        mov     dword [edi],    '<a c'
        mov     dword [edi+4],  'lass'
        mov     dword [edi+8],  '="a"'
        mov     dword [edi+12], ' id='
        mov     byte [edi+16],  '"'
        add     edi, 17

        push    esi
        call    .copy_id

        mov     dword [edi],   '" hr'
        mov     dword [edi+4], 'ef="'
        mov     byte [edi+8], '#'
        lea     edi, [edi+9]

        pop     esi
        call    .copy_id

        mov     word [edi], '">'
        lea     edi, [edi+2]

        mov     dword [edi], '</a>'
        lea     edi, [edi+4]

        mov     al, [esi]
        jmp     .normal_char

.copy_id:
        mov     [.link_level], 1

.copy_id_loop:
        mov     al, [esi]
        lea     esi, [esi+1]

        cmp     al, '['
        jne     @f
        inc     [.link_level]
@@:
        cmp     al, ']'
        je      .cpid_ok
        cmp     al, $0d
        je      .cpid_ok
        cmp     al, $0a
        je      .cpid_ok
        test    al, al
        jz      .cpid_ok

.store:
        mov     [edi], al
        lea     edi, [edi+1]
        jmp     .copy_id_loop

.cpid_ok:
        dec     [.link_level]
        jnz     .store

        retn

;....................................................................

; processing header items and building of the table of contents.

.header2:
        call    .close_subchapter
        xor     ecx, ecx

.scan_level2:
        inc     ecx
.scan_space2:
        lea     esi, [esi+1]
        mov     al, [esi]
        cmp     al, '#'
        je      .scan_level2
        cmp     al, ' '
        je      .scan_space2
        cmp     al, $09
        je      .scan_space2

        cmp     al, $0d
        je      .end_of_line
        cmp     al, $0a
        je      .end_of_line

        cmp     ecx, 6
        jbe     @f
        mov     ecx, 6
@@:

; create ID
        call    .BuildID

        shl     ecx, 16
        mov     [.last_header], ecx

        add     ecx, '<h0 '
        mov     [edi], ecx
        mov     dword [edi+4], 'id="'
        mov     byte [edi+8], 'H'
        lea     edi, [edi+9]

; insert the id here.

        call    .InsertID

        mov     word [edi], '">'
        lea     edi, [edi+2]

        cmp     [.last_header], 1 shl 16
        je      @f
        call    .InsertID
        mov     dword [edi], '&nbs'
        mov     word [edi+4], 'p;'
        lea     edi, [edi+6]
@@:
        or      [.state], fstateHeader
        jmp     .scan_line2

.skip2:
        mov     al, [esi]
        lea     esi, [esi+1]

        test    al, al
        jz      .finish
        cmp     al, $0d
        je      .skip_eol
        cmp     al, $0a
        jne     .skip2

.skip_eol:
        xor     al, $0d xor $0a
        cmp     [esi], al
        jne     .line_start2
        inc     esi
        jmp     .line_start2

;----- End of .SecondPass ------------------------------


; ecx - level of the header 1..6
.BuildID:
        push    eax ebx ecx edx edi

        lea     edi, [.IdString]
        cmp     ecx, 1
        jbe     .main

        sub     ecx, 2
        push    ecx

        inc     [.cntH2+4*ecx]

.zero:
        inc     ecx
        cmp     ecx, 5
        je      .end_zero
        mov     [.cntH2+4*ecx], 0
        jmp     .zero

.end_zero:
        xor     ebx, ebx
        mov     ecx, 10

.loop_numbers:
        mov     eax, [.cntH2+4*ebx]
        call    _NumToStrU
        mov     al, '.'
        stosb
        inc     ebx
        cmp     ebx, [esp]
        jbe     .loop_numbers

        pop     ecx
        jmp     .finID

.main:
        mov     eax, 'main'
        stosd

.finID:
        lea     eax, [.IdString]
        sub     edi, eax
        mov     [.IdLen], edi

        pop     edi edx ecx ebx eax
        retn

.InsertID:
        push    ecx esi

        lea     esi, [.IdString]
        mov     ecx, [.IdLen]

        rep movsb

        pop     esi ecx
        retn



.CopyName:
        push    ecx edx

.sp_loop:
        lodsb
        cmp     al, $0a
        je      .end_cp_name
        cmp     al, $0d
        je      .end_cp_name

        cmp     al, ' '
        jbe     .sp_loop

        dec     esi

.found:
        mov     ecx, esi
        mov     edx, esi

.name_eof:
        lodsb

        test    al, al
        jz      .eol
        cmp     al, $0a
        je      .eol
        cmp     al, $0d
        je      .eol
        cmp     al, ' '
        jbe     .name_eof

        lea     ecx, [esi]
        jmp     .name_eof

.eol:
        sub     ecx, edx
        jz      .end_cp_name

        xchg    esi, edx

.copy_name_loop:
        lodsb
        call    .store_char
        loop    .copy_name_loop

        mov     esi, edx

.end_cp_name:
        pop     edx ecx
        retn
endp









endmodule