proc string.length uses ebx, _str
    mov     eax, 0
    mov     ebx, [_str]
  @@:
    cmp     [ebx], byte 0
    je	    @f
    inc     eax
    inc     ebx
    jmp     @b
  @@:
    ret
 endp

 proc string.copy uses eax ebx ecx, _src, _dst
    mov     eax, [_src]
    mov     ebx, [_dst]
  @@:
    mov     cl, [eax]
    mov     [ebx], cl
    cmp     cl, 0
    je	    @f
    inc     eax
    inc     ebx
    jmp     @b
  @@:
    ret
 endp

 proc string.concatenate uses eax, _src, _dst
    stdcall string.length, [_dst]
    add     eax, [_dst]
    stdcall string.copy, [_src], eax
    ret
 endp

 proc string.cmp uses ecx esi edi, _str1, _str2, _n
    mov     ecx, [_n]
    test    ecx, ecx	     ; Max length is zero?
    je	    .done

    mov     esi, [_str1]	; esi = string s1
    mov     edi, [_str2]	; edi = string s2
    cld
 .compare:
    cmpsb		     ; Compare two bytes
    jne     .done
    cmp     byte [esi-1], 0  ; End of string?
    je	    .done
    dec     ecx 	     ; Length limit reached?
    jne     .compare
 .done:
    seta    al		     ; al = (s1 > s2)
    setb    ah		     ; ah = (s1 < s2)
    sub     al, ah
    movsx   eax, al	     ; eax = (s1 > s2) - (s1 < s2), i.e. -1, 0, 1
    ret
 endp

 proc string.to_lower_case uses eax, _str
    mov     eax, [_str]
  @@:
    cmp     [eax], byte 0
    je	    @f
    cmp     [eax], byte 65
    jl	    .next
    cmp     [eax], byte 90
    jg	    .next
    add     [eax], byte 97 - 65
 .next:
    inc     eax
    jmp     @b
  @@:
    ret
 endp

 proc string.to_upper_case uses eax, _str
    mov     eax, [_str]
  @@:
    cmp     [eax], byte 0
    je	    @f
    cmp     [eax], byte 97
    jl	    .next
    cmp     [eax], byte 122
    jg	    .next
    sub     [eax], byte 97 - 65
 .next:
    inc     eax
    jmp     @b
  @@:
    ret
 endp

 proc string.match uses ebx ecx edx, _str1, _str2
    mov     ebx, [_str1]
    mov     ecx, [_str2]
  @@:
    cmp     [ebx], byte 0
    je	    @f
    cmp     [ecx], byte 0
    je	    @f

    mov     dl, [ebx]
    cmp     [ecx], byte '?'
    je	    .next
    cmp     [ecx], byte '*'
    je	    .next_ebx
    cmp     [ecx], dl
    je	    .next

    cmp     [ecx - 1], byte '*'
    je	    .next_ecx

    jmp     @f

 .next_ecx:
    dec     ecx
    jmp     .next
 .next_ebx:
    dec     ebx
 .next:
    inc     ebx
    inc     ecx
    jmp     @b
  @@:

  @@:
    cmp     [ecx], byte 0
    je	    @f
    cmp     [ecx], byte '*'
    jne     @f
    inc     ecx
    jmp     @b
  @@:

    cmp     [ecx], byte 0
    je	    @f
    mov     eax, 0
    ret
  @@:
    mov     eax, 1
    ret
 endp

 proc string.trim_last uses eax, _str
    stdcall string.length, [_str]
    add     eax, [_str]
    dec     eax
  @@:
    cmp     [eax], byte ' '
    jne     @f
    mov     [eax], byte 0
    dec     eax
    jmp     @b
  @@:
    ret
 endp

 proc string.trim_first, _str
    mov     eax, [_str]
  @@:
    cmp     [eax], byte ' '
    jne     @f
    inc     eax
    jmp     @b
  @@:
    ret
 endp

 proc string.index_of uses ebx ecx, _str, _char, _num
    mov     ebx, [_char]
    mov     ecx, [_str]
    mov     eax, 0
  @@:
    cmp     [ecx], byte 0
    je	    @f
    cmp     [ecx], bl
    jne     .after_check
    dec     [_num]
    jz	    .finded
 .after_check:
    inc     ecx
    inc     eax
    jmp     @b
  @@:
    mov     eax, -1
 .finded:
    ret
 endp

 proc string.last_index_of uses ebx ecx, _str, _char, _num
    stdcall string.length, [_str]
    mov     ecx, [_str]
    add     ecx, eax
    mov     ebx, [_char]
  @@:
    cmp     eax, 0
    je	    @f
    cmp     [ecx], bl
    jne     .after_check
    dec     [_num]
    jz	    .finded
 .after_check:
    dec     ecx
    dec     eax
    jmp     @b
  @@:
    mov     eax, -2
 .finded:
    inc     eax
    ret
 endp