UTF-8 Variable Length String Library

User projects written in or related to FreeBASIC.
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

UTF-8 Variable Length String Library

Postby Munair » Dec 06, 2017 16:58

For the IDE/RAD I'm working on for Linux, it is essential to have full UTF-8 support for all possible string operations. Fortunately UTF-8 encoded text can be stored as simple variable length strings. But one cannot use Left, Right, Mid etc on those strings as it will break the multibyte Unicode.

With a little help from the Lazarus project (Free Pascal) I was able to write basic string routines that support UTF-8. I decided to use the 'U' prefix to distinguish from normal string routines. So Left will be ULeft, Instr will be UInstr etc. It may be a bit confusing because of the existing 'unsigned' prefix, but we're talking string routines here so it should be OK. I couldn't think of anything more appropriate without going verbose.

Additional routines are added not native to FreeBASIC: ResizeStr, UIsAscii, URemove, UInsert, UReplace and UReplaceAll. The beauty here is that all functions, including these additions, can also be used on 'normal' or ASCII strings. This means that one can simply concatenate strings as always: str3 = str2 + str1 (provided each string contains valid ASCII or UTF-8 byte sequences), because the functions will find out for themselves where the multi-byte encoded characters are.

One routine that's missing is the UMid statement. Perhaps someone can help out there, but I doubt there is a real need for it. Meanwhile, you're welcome to test, use and improve the code. MLGPL License included. ;)

On a final note, I decided not to hard code the string type so it can be changed or renamed at any time. It also makes the code more readable IMO.

Last updated: 9 December 2017, 16:56 UTC

Code: Select all

/'
   ---------------------------------------------------------------------
   Copyright (C) 2017 Frank Hoogerbeets <frank@ditrianum.org>
   ---------------------------------------------------------------------
   This library is free software; you can redistribute it and/or modify
   it under the terms of the Modified GNU Library General Public
   License either version 2.0 of the License, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   
   See the Modified GNU Library General Public License for more details:
   http://www.basicstudio.org/mlgpl.html
   http://www.basicstudio.org/mlgpl.txt
   ---------------------------------------------------------------------
'/
#include "crt/string.bi"

' --- general ---

#define nil 0

type PChar as zstring ptr
type PCChar as const zstring ptr

function ByteIndex(b as any ptr, size as uinteger, chval as ubyte) _
as integer
   ' returns -1 if byte was not found
   dim p as any ptr
   dim result as integer = -1
   p = memchr(b, chval, size)
   if p > 0 then
      result = p - b
   end if
   return result
end function

sub ResizeStr(byref s as string, byval size as uinteger)
   var slen = len(s)
   if size > slen then
      s += space(size - slen)
   elseif size < slen then
      s = left(s, size)
   end if
end sub

type ustring as string ' not hard-coded allows for future adjustment

' house-keeping routines
declare function UTF8ChLenX(p as pchar) as uinteger
declare function UTF8ChLen(p as pchar) as uinteger
declare function UTF8ChStart(p as pchar, byval length as uinteger, _
   byval index as uinteger) as pchar
declare sub UTF8ResizeBuffer (byref s as ustring, byref ostr as pchar, _
   byval icnt as uinteger, byval ocnt as uinteger, _
   byval osize as integer, byval nsize as integer)
declare function UTF8InstrP(sp as pchar, byval splen as uinteger, _
   sb as pchar, byval sblen as uinteger) as pchar
declare function UTF8Len(p as pchar, byval bytes as uinteger) _
   as uinteger

' string routines supporting UTF-8
declare function UInsert overload (byref s1 as const ustring, _
   byref s2 as const ustring, byval start as uinteger) as const ustring
declare function UInsert overload (byref s1 as const ustring, _
   byref s2 as const ustring, byval start as uinteger, _
   byval count as uinteger) as const ustring
declare function UInstr overload (byref sp as const ustring, _
   byref sb as const ustring) as integer
declare function UInstr overload (byval spos as uinteger, _
   byref sp as const ustring, byref sb as const ustring) as integer
declare function UIsAscii(byref s as const ustring) as boolean
declare function ULCase(byref s as const ustring, _
   byref lang as const string = "") as const ustring
declare function ULeft(byref s as const ustring, _
   byval count as uinteger) as const ustring
declare function ULen(byref s as const ustring) as uinteger
declare function UMid overload (byref s as const ustring, _
   byval start as uinteger, byval count as uinteger) as const ustring
declare function UMid overload (byref s as const ustring, _
   byval start as uinteger) as const ustring
declare function URemove(byref s as ustring, byval start as uinteger, _
   byval count as uinteger) as const ustring
declare sub UReplace(byref t as string, byref i as const string, _
byref s as const string, byval a as integer = 1)
declare sub UReplaceAll(byref t as ustring, byref i as const ustring, _
   byref s as const ustring, byval a as integer = 1)
declare function URight(byref s as const ustring, _
   byval count as uinteger) as const ustring
declare function UUCase(byref s as const ustring, _
   byref lang as const string = "") as const ustring

' --- house-keeping routines ---

function UTF8ChLenX(p as pchar) as uinteger
   dim result as uinteger = 1
   select case (*p)[0]
      case &hC0 to &hDF
         ' two bytes
         if ((*p)[1] and &hC0) = &h80 then
            result = 2
         end if
      case &hE0 to &hEF
         ' three bytes
         if ((*p)[1] and &hC0) = &h80 then
            if ((*p)[2] and &hC0) = &h80 then
               result = 3
            end if
         end if
      case &hF0 to &hF7
         ' four bytes
         if ((*p)[1] and &hC0) = &h80 then
            if ((*p)[2] and &hC0) = &h80 then
               if ((*p)[3] and &hC0) = &h80 then
                  result = 4
               end if
            end if
         end if
   end select
   return result
end function

function UTF8ChLen(p as pchar) as uinteger
   if p = nil then
      return 0
   end if
   if (*p)[0] < &hC0 then
      return 1
   end if
   return UTF8ChLenX(p)
end function

function UTF8ChStart(p as pchar, byval length as uinteger, _
byval index as uinteger) as pchar
   dim chlen as uinteger
   dim result as pchar = p
   if result <> 0 then
      while (index > 0) and (length > 0)
         chlen = UTF8ChLen(result)
         length -= chlen
         index -= 1
         result += chlen
      wend
      if (index <> 0) or (length < 0) then
         result = nil
      end if
   end if
   return result
end function

function UTF8InstrP(sp as pchar, byval splen as uinteger, _
sb as pchar, byval sblen as uinteger) as pchar
   ' returns pointer to search string, or 0 if not found
   dim p as integer
   if (sp = nil) orelse (sb = nil) orelse (sblen = 0) then
      return nil
   end if
   while splen > 0
      p = ByteIndex(sp, splen, (*sb)[0])
      if p < 0 then
         exit function
      end if
      sp += p
      splen -= p
      if splen < sblen then
         exit function
      end if
      if memcmp(sp, sb, sblen) = 0 then
         return sp
      end if
      sp += 1
      splen -= 1      
   wend
   return 0
end function

function UTF8Len(p as pchar, byval bytes as uinteger) as uinteger
   dim chlen as uinteger
   dim result as uinteger = 0
   while bytes > 0
      result += 1
      chlen = UTF8ChLen(p)
      p += chlen
      bytes -= chlen
   wend
   return result
end function

sub UTF8ResizeBuffer (byref s as ustring, byref ostr as pchar, _
byval icnt as uinteger, byval ocnt as uinteger, _
byval osize as integer, nsize as integer)
   if not (nsize > osize) then
      exit sub
   end if
   if (nsize > 20) orelse (osize > 20) then
      exit sub
   end if
   if (nsize > osize) andalso (ocnt >= icnt - 1) then
      ResizeStr(s, len(s) + nsize - osize)
      ostr = strptr(s)
   end if
end sub

' string routines supporting UTF-8

' UTF-8 insert string
function UInsert overload (byref s1 as const ustring, byref s2 _
as const ustring, byval start as uinteger) as const ustring
   return ULeft(s1, start - 1) + s2 + UMid(s1, start)
end function

' UTF-8 insert string with overwrite, remove
function UInsert overload (byref s1 as const ustring, byref s2 _
as const ustring, byval start as uinteger, byval count as uinteger) _
as const ustring
   return ULeft(s1, start - 1) + s2 + UMid(s1, start + count)
end function

' UTF-8 Instr (fixed start position)
function UInstr overload (byref sp as const ustring, _
byref sb as const ustring) as integer
   ' with default start position
   return UInstr(1, sp, sb)
end function

' UTF-8 Instr (custom start position)
function UInstr overload (byval spos as uinteger, _
byref sp as const ustring, byref sb as const ustring) as integer
   ' with custom start position
   dim as uinteger i, splen
   dim as pchar p, sposp
   if spos = 1 then
      i = instr(sp, sb)
      if i > 0 then
         return UTF8Len(sp, i - 1) + 1
      end if
   elseif spos > 1 then
      splen = len(sp)
      sposp = UTF8ChStart(sp, splen, spos - 1)
      if sposp = nil then
         return 0
      end if
      p = UTF8InstrP(sposp, splen + (strptr(sp) - sposp), sb, len(sb))
      if p = nil then
         return 0
      end if
      return spos + UTF8Len(sposp, p - sposp)
   end if
   return 0
end function

function UIsAscii(byref s as const ustring) as boolean
   ' returns TRUE if string only contains chars < &h80
   for i as uinteger = 0 to len(s)
      if s[i] > &h7F then
         return false
      end if
   next
   return true
end function

' UTF-8 left part of string
function ULeft(byref s as const ustring, byval count as uinteger) _
as const ustring
   return UMid(s, 1, count)
end function

' UTF-8 length
function ULen(byref s as const ustring) as uinteger
   return UTF8Len(s, len(s))
end function

' UTF-8 mid rest of the string
function UMid overload (byref s as const ustring, _
byval start as uinteger) as const ustring
   return UMid(s, start, ulen(s) - start + 1)
end function

' UTF-8 mid default
function UMid overload (byref s as const ustring, byval start _
as uinteger, byval count as uinteger) as const ustring
   dim as pchar sbpos, ebpos
   dim as uinteger maxb, slen
   dim result as ustring
   slen = len(s)
   sbpos = UTF8ChStart(s, slen, start - 1)
   result = ""
   if sbpos = nil then
      return result
   else
      maxb = strptr(s) + slen - sbpos
      ebpos = UTF8ChStart(sbpos, maxb, count)
      if ebpos = nil then
         result = mid(s, sbpos - strptr(s) + 1, maxb)
      else
         result = mid(s, sbpos - strptr(s) + 1, ebpos - sbpos)
      end if
   end if   
   return result
end function

function URemove(byref s as ustring, byval start as uinteger, _
byval count as uinteger) as const ustring
   return UInsert(s, "", start, count)
end function

sub UReplace(byref t as ustring, byref i as const ustring, _
byref s as const ustring, byval a as integer = 1)
  dim as uinteger li, p
  p = instr(a, t, i)
  if p > 0 then
      li = len(i)
    if li <> len(s) then
         t = left(t, p - 1) + s + mid(t, p + li)
      else
         mid(t, p) = s
      end if
   end if
end sub

sub UReplaceAll(byref t as ustring, byref i as const ustring, _
byref s as const ustring, byval a as integer = 1)
  dim as uinteger li, ls, p
  p = instr(a, t, i)
  if p = 0 then
      exit sub
   end if
  li = len(i)
  ls = len(s)
  if li = ls then
      li = 0
   end if   
  while p > 0
      if li then
         t = left(t, p - 1) + s + mid(t, p + li)
      else
         mid(t, p) = s
      end if
    p = instr(p + ls, t, i)
  wend
end sub

function URight(byref s as const ustring, byval count as uinteger) _
as const ustring
   return UMid(s, ulen(s) - count + 1)
end function

function ULCase(byref s as const ustring, byref lang _
as const string = "") as const ustring
   dim as uinteger cdiff, p
   dim as pchar ostr
   dim as pcchar istr, istrend
   dim as boolean IsTurkish
   dim as ubyte c1, c2, c3, nc1, nc2, nc3
   dim as ustring result
      
   if len(s) = 0 then
      exit function
   end if
   
   istr = strptr(s)
   istrend = istr + len(s)
   result = s
   
   while istr < istrend
      c1 = (*istr)[0]
      select case c1
         case &h41 to &h5A
            exit while
         case &hC3 to &hFF
            select case c1
               case &hC3 to &hC9, &hCE, &hCF, &hD0 to &hD5, &hE1, &hE2, &hE5
                  c2 = (*istr)[1]
                  select case c1
                     case &hC3
                        if (c2 >= &h80) andalso (c2 <= &h9E) then
                           exit while
                        end if
                     case &hC4
                        select case c2
                           case &h80 to &hAF, &hB2 to &hB6
                              if (c2 mod 2) = 0 then
                                 exit while
                              end if
                           case &hB8 to &hFF
                              if (c2 mod 2) = 1 then
                                 exit while
                              end if
                           case &hB0
                              exit while
                        end select
                     case &hC5
                        select case c2
                           case &h8A to &hB7
                              if (c2 mod 2) = 0 then
                                 exit while
                              end if
                           case &h00 to &h88, &hB9 to &hFF
                              if (c2 mod 2) = 1 then
                                 exit while
                              end if
                           case &hB8
                              exit while
                        end select
                     case &hE5
                        if c2 = &hBC then
                           select case (*istr)[2]
                              case &hA1 to &hBA
                                 exit while
                           end select
                        end if
                     case else
                        exit while
                  end select
            end select
      end select
    istr += 1
   wend
   
   if istr >= istrend then
      return result
   end if
   
   if lang = "tr" orelse lang = "az" then
      IsTurkish = true
   end if
   
   ostr = strptr(result) + (istr - strptr(s))
   cdiff = 0
   
  while istr < istrend
      c1 = (*istr)[0]
      select case c1
         case &h41 to &h5A
            ' ASCII
            if IsTurkish andalso (c1 = asc("I")) then
               p = ostr - strptr(result)
               ResizeStr(result, len(result) + 1)
               ostr = strptr(result) + p
               (*ostr)[0] = &hC4
               ostr += 1
               (*ostr)[0] = &hB1
               cdiff -= 1
            else
               (*ostr)[0] = c1 + &h20
            end if
            istr += 1
            ostr += 1
         case &hC3 to &hD5
            ' two bytes
            c2 = (*istr)[1]
            nc1 = c1
            nc2 = c2
            select case c1
               case &hC3
                  select case c2
                     case &h80 to &h96, &h98 to &h9E
                        nc2 = c2 + &h20
                  end select
               case &hC4
                  select case c2
                     case &h80 to &hAF, &hB2 to &hB7
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &hB0
                        (*ostr)[0] = asc("i")
                        istr += 2
                        ostr += 1
                        cdiff += 1
                        continue while
                     case &hB9 to &hBE
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &hBF
                        nc1 = &hC5
                        nc2 = &h80
                  end select
               case &hC5
                  select case c2
                     case &h8A to &hB7
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &h00 to &h88, &hB9 to &hBE
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &hB8
                        nc1 = &hC3
                        nc2 = &hBF
                  end select
               case &hC6
                  select case c2
                     case &h81
                        nc1 = &hC9
                        nc2 = &h93
                     case &h82 to &h85
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &h87, &h88, &h8B, &h8C
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &h86
                        nc1 = &hC9
                        nc2 = &h94
                     case &h89
                        nc1 = &hC9
                        nc2 = &h96
                     case &h8A
                        nc1 = &hC9
                        nc2 = &h97
                     case &h8E
                        nc1 = &hC7
                        nc2 = &h9D
                     case &h8F
                        nc1 = &hC9
                        nc2 = &h99
                     case &h90
                        nc1 = &hC9
                        nc2 = &h9B
                     case &h91, &h98
                        nc2 = c2 + 1
                     case &h93
                        nc1 = &hC9
                        nc2 = &hA0
                     case &h94
                        nc1 = &hC9
                        nc2 = &hA3
                     case &h96
                        nc1 = &hC9
                        nc2 = &hA9
                     case &h97
                        nc1 = &hC9
                        nc2 = &hA8
                     case &h9C
                        nc1 = &hC9
                        nc2 = &hAF
                     case &h9D
                        nc1 = &hC9
                        nc2 = &hB2
                     case &h9F
                        nc1 = &hC9
                        nc2 = &hB5
                     case &hA0 to &hA5, &hAC
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &hA7, &hAF
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &hA6
                        nc1 = &hCA
                        nc2 = &h80
                     case &hA9
                        nc1 = &hCA
                        nc2 = &h83
                     case &hAE
                        nc1 = &hCA
                        nc2 = &h88
                     case &hB8, &hBC
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &hB3 to &hB6
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &hB1
                        nc1 = &hCA
                        nc2 = &h8A
                     case &hB2
                        nc1 = &hCA
                        nc2 = &h8B
                     case &hB7
                        nc1 = &hCA
                        nc2 = &h92
                  end select
               case &hC7
                  select case c2
                     case &h84 to &h8C, &hB1 to &hB3
                        if ((c2 and &hF) mod 3) = 1 then
                           nc2 = c2 + 2
                        elseif ((c2 and &hF) mod 3) = 2 then
                           nc2 = c2 + 1
                        end if
                     case &h8D to &h9C
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &h9E to &hAF, &hB4, &hB5, &hB8 to &hBF
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &hB6
                        nc1 = &hC6
                        nc2 = &h95
                     case &hB7
                        nc1 = &hC6
                        nc2 = &hBF
                  end select
               case &hC8
                  if c2 >= &h80 andalso c2 <= &hB3 then
                     if (c2 mod 2) = 0 then
                        nc2 = c2 + 1
                     end if
                  end if
                  select case c2
                     case &hA0
                        nc1 = &hC6
                        nc2 = &h9E
                     case &hA1
                        nc2 = c2
                     case &hBA, &hBE
                        p = ostr - strptr(result)
                        ResizeStr(result, len(result) + 1)
                        ostr = strptr(result) + p
                        (*ostr)[0] = &hE2
                        ostr += 1
                        (*ostr)[0] = &hB1
                        ostr += 1
                        if c2 = &hBA then
                           (*ostr)[0] = &hA5
                        else
                           (*ostr)[0] = &hA6
                        end if
                        cdiff -= 1
                        ostr += 1
                        istr += 2
                        continue while
                     case &hBD
                        nc1 = &hC6
                        nc2 = &h9A
                     case &hBB
                        nc2 = c2 + 1
                  end select                        
               case &hC9
                  select case c2
                     case &h81, &h82
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &h86 to &h8F
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &h83
                        nc1 = &hC6
                        nc2 = &h80
                     case &h84
                        nc1 = &hCA
                        nc2 = &h89
                     case &h85
                        nc1 = &hCA
                        nc2 = &h8C
                  end select
               case &hCE
                  select case c2
                     case &h86
                        nc2 = &hAC
                     case &h88
                        nc2 = &hAD
                     case &h89
                        nc2 = &hAE
                     case &h8A
                        nc2 = &hAF
                     case &h8C
                        ' nc2 doesn't change
                        nc1 = &hCF
                     case &h8E
                        nc1 = &hCF
                        nc2 = &h8D
                     case &h8F
                        nc1 = &hCF
                        nc2 = &h8E
                     case &h91 to &h9F
                        nc2 = c2 + &h20
                     case &hA0 to &hAB
                        nc1 = &hCF
                        nc2 = c2 - &h20
                  end select
               case &hCF
                  select case c2
                     case &h8F
                        nc2 = &h97
                     case &h98
                        nc2 = &h99
                     case &h9A
                        nc2 = &h9B
                     case &h9C
                        nc2 = &h9D
                     case &h9E
                        nc2 = &h9F
                     case &hA0 to &hAF
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                     case &hB4
                        nc1 = &hCE
                        nc2 = &hB8
                     case &hB7
                        nc2 = &hB8
                     case &hB9
                        nc2 = &hB2
                     case &hBA
                        nc2 = &hBB
                     case &hBD
                        nc1 = &hCD
                        nc2 = &hBB
                     case &hBE
                        nc1 = &hCD
                        nc2 = &hBC
                     case &hBF
                        nc1 = &hCD
                        nc2 = &hBD
                  end select
               case &hD0
                  c2 = (*istr)[1]
                  select case c2
                     case &h80 to &h8F
                        nc1 = c1 + 1
                        nc2 = c2 + &h10
                     case &h90 to &h9F
                        nc2 = c2 + &h20
                     case &hA0 to &hAF
                        nc1 = c1 + 1
                        nc2 = c2 - &h20
                  end select
               case &hD1
                  if c2 >= &hA0 andalso c2 <= &hBF then
                     if (c2 mod 2) = 0 then
                        nc2 = c2 + 1
                     end if
                  end if
               case &hD2
                  select case c2
                     case &h80
                        nc2 = c2 + 1
                     case &h8A to &hBF
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                  end select
               case &hD3
                  select case c2
                     case &h80
                        nc2 = &h8F
                     case &h81 to &h8E
                        if (c2 mod 2) = 1 then
                           nc2 = c2 + 1
                        end if
                     case &h90 to &hBF
                        if (c2 mod 2) = 0 then
                           nc2 = c2 + 1
                        end if
                  end select
               case &hD4
                  if (c2 mod 2) = 0 then
                     nc2 = c2 + 1
                  end if
                  if c2 >= &hB1 andalso c2 <= &hBF then
                     nc1 = &hD5
                     nc2 = c2 - &h10
                  end if
               case &hD5
                  select case c2
                     case &h80 to &h8F
                        nc2 = c2 + &h30
                     case &h90 to &h96
                        nc1 = &hD6
                        nc2 = c2 - &h10
                  end select
            end select
            ' evaluate
            if cdiff <> 0 then
               (*ostr)[0] = nc1
               (*ostr)[1] = nc2
            else
               if nc1 <> c1 then (*ostr)[0] = nc1
               if nc2 <> c2 then (*ostr)[1] = nc2
            end if
            istr += 2
            ostr += 2
         ' three bytes
         case &hE1
            nc1 = c1
            c2 = (*istr)[1]
            c3 = (*istr)[2]
            nc2 = c2
            nc3 = c3
            select case c2
               case &h82
                  if c3 >= &hA0 andalso c3 <= &hBF then
                        nc1 = &hE2
                        nc2 = &hB4
                        nc3 = c3 - &h20
                  end if
               case &h83
                  if c3 >= &h80 andalso c3 <= &h85 then
                        nc1 = &hE2
                        nc2 = &hB4
                        nc3 = c3 + &h20
                  end if
               case &hB8 to &hBB
                  if (c3 mod 2) = 0 then
                     nc3 = c3 + 1
                  end if
                  if c2 = &hBA then
                     if (c3 >= &h96) andalso (c3 <= &h9F) then
                        nc3 = c3
                     end if
                  end if
                  if c2 = &hBA andalso c3 = &h9E then
                     istr += 3
                     (*ostr)[0] = &hC3
                     ostr += 1
                     (*ostr)[0] = &h9F
                     ostr += 1
                     cdiff += 1
                     continue while
                  end if
               case &hBC
                  if (c3 mod &h10) \ 8 = 1 then
                     nc3 = c3 - 8
                  end if
               case &hBD
                  select case c3
                     case &h80 to &h8F, &hA0 to &hAF
                        if (c3 mod &h10) \ 8 = 1 then
                           nc3 = c3 - 8
                        end if
                     case &h99, &h9B, &h9D, &h9F
                        nc3 = c3 - 8
                  end select
               case &hBE
                  select case c3
                     case &h80 to &hB9
                        if (c3 mod &h10) \ 8 = 1 then
                           nc3 = c3 - 8
                        end if
                     case &hBA
                        nc2 = &hBD
                        nc3 = &hB0
                     case &hBB
                        nc2 = &hBD
                        nc3 = &hB1
                     case &hBC
                        nc3 = &hB3
                  end select
            end select
            if cdiff <> 0 then
               (*ostr)[0] = nc1
               (*ostr)[1] = nc2
               (*ostr)[2] = nc3
            else
               if c1 <> nc1 then (*ostr)[0] = nc1
               if c2 <> nc2 then (*ostr)[1] = nc2
               if c3 <> nc3 then (*ostr)[2] = nc3
            end if
            istr += 3
            ostr += 3
         case &hE2
            nc1 = c1
            c2 = (*istr)[1]
            c3 = (*istr)[2]
            nc2 = c2
            nc3 = c3
            select case c2
               case &h84
                  select case c3
                     case &hA6
                        istr += 3
                        (*ostr)[0] = &hCF
                        ostr += 1
                        (*ostr)[0] = &h89
                        ostr += 1
                        cdiff += 1
                        continue while
                     case &hAA
                        istr += 3
                        (*ostr)[0] = &h6B
                        ostr += 1
                        cdiff += 2
                        continue while
                     case &hAB
                        istr += 3
                        (*ostr)[0] = &hC3
                        ostr += 1
                        (*ostr)[0] = &hA5
                        ostr += 1
                        cdiff += 1
                        continue while
                  end select
               case &h85
                  if (c3 >= &hA0) andalso (c3 <= &hAF) then
                     nc3 = c3 + &h10
                  end if
               case &h86
                  if c3 = &h83 then
                     nc3 = c3 + 1
                  end if
               case &h92
                  if (c3 >= &hB6) andalso (c3 <= &hBF) then
                     nc2 = &h93
                     nc3 = c3 - &h26
                  end if
               case &h93
                  if (c3 >= &h80) andalso (c3 <= &h8F) then
                     nc3 = c3 + &h26
                  end if
               case &hB0
                  select case c3
                     case &h80 to &h8F
                        nc3 = c3 + &h30
                     case &h90 to &hAE
                        nc2 = &hB1
                        nc3 = c3 - &h10
                  end select
               case &hB1
                  select case c3
                     case &hA0
                        nc3 = c3 + 1
                     case &hA2, &hA4, &hAD to &hAF, &hB0
                        istr += 3
                        (*ostr)[0] = &hC9
                        ostr += 1
                        select case c3
                           case &hA2
                              (*ostr)[0] = &hAB
                           case &hA4
                              (*ostr)[0] = &hBD
                           case &hAD
                              (*ostr)[0] = &h91
                           case &hAE
                              (*ostr)[0] = &hB1
                           case &hAF
                              (*ostr)[0] = &h90
                           case &hB0
                              (*ostr)[0] = &h92
                        end select
                        ostr += 1
                        cdiff += 1
                        continue while
                     case &hA3
                        nc2 = &hB5
                        nc3 = &hBD
                     case &hA7, &hA9, &hAB
                        nc3 = c3 + 1
                     case &hB2, &hB5
                        nc3 = c3 + 1
                     case &hBE, &hBF
                        istr += 3
                        (*ostr)[0] = &hC8
                        ostr += 1
                        if c3 = &hBE then
                           (*ostr)[0] = &hBF
                        else
                           (*ostr)[0] = &h80
                        end if                        
                        ostr += 1
                        cdiff += 1
                        continue while
                  end select
               case &hB2
                  if (c3 mod 2) = 0 then
                     nc3 = c3 + 1
                  end if
               case &hB3
                  if (c3 >= &h80) andalso (c3 <= &hA3) then
                     if (c3 mod 2) = 0 then nc3 = c3 + 1
                  end if
            end select
            if cdiff <> 0 then
               (*ostr)[0] = nc1
               (*ostr)[1] = nc2
               (*ostr)[2] = nc3
            else
               if c1 <> nc1 then (*ostr)[0] = nc1
               if c2 <> nc2 then (*ostr)[1] = nc2
               if c3 <> nc3 then (*ostr)[2] = nc3
            end if
            istr += 3
            ostr += 3
         case &hEF
            c2 = (*istr)[1]
            c3 = (*istr)[2]
            if c2 = &hBC then
               if (c3 >= &hA1) and (c3 <= &hBA) then
                  (*ostr)[0] = c1
                  (*ostr)[1] = &hBD
                  (*ostr)[2] = c3 - &h20
               end if
            end if
            if cdiff <> 0 then
               (*ostr)[0] = c1
               (*ostr)[1] = c2
               (*ostr)[2] = c3
            end if
            istr += 3
            ostr += 3
         case else
            if cdiff <> 0 then (*ostr)[0] = c1
            istr += 1
            ostr += 1
      end select
  wend
  ' set final buffer size
   ResizeStr(result, ostr - strptr(result))
   return result
end function

function UUCase(byref s as const ustring, byref lang _
as const string = "") as const ustring
   dim as uinteger icnt, ocnt
   dim as pchar ostr
   dim as integer chlen, nchlen
   dim as ushort nchar, ochar
   dim as boolean chpr, IsTurkish
   dim as ustring result
   
   if len(s) = 0 then
      exit function
   end if
   
   result = s
   ostr = strptr(result)
   if lang = "tr" or lang = "az" then
      IsTurkish = true
   end if
   
   icnt = 0
   ocnt = 0
   
   while icnt <= len(s)
      if (s[icnt] >= &h61) andalso (s[icnt] <= &h7A) then
         if IsTurkish andalso (s[icnt] = asc("i")) then
            ResizeStr(result, len(result) + 1)
            ostr = strptr(result)
            (*ostr)[ocnt] = &hC4
            (*ostr)[ocnt + 1] = &hB0
            icnt += 1
            ocnt += 2
         else
            (*ostr)[ocnt] = s[icnt] - &h20
            icnt += 1
            ocnt += 1
         end if
      else
         chlen = UTF8ChLen(cast(pchar, @s[icnt]))
         chpr = false
         nchlen = chlen
         if chlen = 2 then
            ochar = (s[icnt] shl 8) or s[icnt + 1]
            nchar = 0
            select case ochar
               case &hC39F
                  nchar = &h5353
               case &hC3A0 to &hC3B6, &hC3B8 to &hC3BE
                  nchar = ochar - &h20
               case &hC3BF
                  nchar = &hC5B8
               case &hC481 to &hC4B0
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hCB1
                  (*ostr)[ocnt] = asc("I")
                  nchlen = 1
                  chpr = true
               case &hC4B2 to &hC4B7
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC4B9 to &hC4BF
                  if (ochar mod 2) = 0 then
                     nchar = ochar - 1
                  end if
               case &hC580
                  nchar = &hC4BF
               case &hC581 to &hC588
                  if (ochar mod 2) = 0 then
                     nchar = ochar - 1
                  end if
               case &hC58A to &hC5B7
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC5B9 to &hC5BE
                  if (ochar mod 2) = 0 then
                     nchar = ochar - 1
                  end if
               case &hC5BF
                  (*ostr)[ocnt] = asc("S")
                  nchlen = 1
                  chpr = true
               case &hC680
                  nchar = &hC983
               case &hC682 to &hC685
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC688
                  nchar = &hC687
               case &hC68C
                  nchar = &hC68B
               case &hC692
                  nchar = &hC691
               case &hC695
                  nchar = &hC7B6
               case &hC699
                  nchar = &hC698
               case &hC69A
                  nchar = &hC8BD
               case &hC69E
                  nchar = &hC8A0
               case &hC6A0 to &hC6A5
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC6A8
                  nchar = &hC6A7
               case &hC6AD
                  nchar = &hC6AC
               case &hC6B0
                  nchar = &hC6AF
               case &hC6B3 to &hC6B6
                  if (ochar mod 2) = 0 then
                     nchar = ochar - 1
                  end if
               case &hC6B9
                  nchar = &hC6B8
               case &hC6BD
                  nchar = &hC6BC
               case &hC6BF
                  nchar = &hC7B7
               case &hC784 to &hC786
                  nchar = &hC784
               case &hC787 to &hC789
                  nchar = &hC787
               case &hC78A to &hC78C
                  nchar = &hC78A
               case &hC78E
                  nchar = &hC78D
               case &hC790
                  nchar = &hC78F
               case &hC791 to &hC79C
                  if (ochar mod 2) = 0 then
                     nchar = ochar - 1
                  end if
               case &hC79D
                  nchar = &hC68E
               case &hC79F
                  nchar = &hC79E
               case &hC7A0 to &hC7AF
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC7B2 to &hC7B3
                  nchar = &hC7B1
               case &hC7B5
                  nchar = &hC7B4
               case &hC7B8 to &hC7BF
                  if ochar mod 2 = 1 then
                     nchar = ochar - 1
                  end if
               case &hC880 to &hC89F
                  if ochar mod 2 = 1 then
                     nchar = ochar - 1
                  end if
               case &hC8A2 to &hC8B3
                  if ochar mod 2 = 1 then
                     nchar = ochar - 1
                  end if
               case &hC8BC
                  nchar = &hC8BB
               case &hC8BF
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hBE
                  nchlen = 3
                  chpr = true
               case &hC980
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hBF
                  nchlen = 3
                  chpr = true
               case &hC982
                  nchar = &hC981
               case &hC986 to &hC98F
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hC990
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hAF
                  nchlen = 3
                  chpr = true
               case &hC991
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hAD
                  nchlen = 3
                  chpr = true
               case &hC992
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hB0
                  nchlen = 3
                  chpr = true
               case &h993
                  nchar = &hC681
               case &hC994
                  nchar = &hC686
               case &hC996
                  nchar = &hC689
               case &hC997
                  nchar = &hC68A
               case &hC999
                  nchar = &hC68F
               case &hC99B
                  nchar = &hC690
               case &hC9A0
                  nchar = &hC693
               case &hC9A3
                  nchar = &hC694
               case &hC9A5
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hEA
                  (*ostr)[ocnt + 1] = &h9E
                  (*ostr)[ocnt + 2] = &h8D
                  nchlen = 3
                  chpr = true
               case &hC9A8
                  nchar = &hC697
               case &hC9A9
                  nchar = &hC696
               case &hC9AB
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hA2
                  nchlen = 3
                  chpr = true
               case &hC9AF
                  nchar = &hC69C
               case &hC9B1
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hAE
                  nchlen = 3
                  chpr = true
               case &hC9B2
                  nchar = &hC69D
               case &hC9B5
                  nchar = &hC69F
               case &hC9BD
                  UTF8ResizeBuffer(result, ostr, icnt, ocnt, 2, 3)
                  (*ostr)[ocnt] = &hE2
                  (*ostr)[ocnt + 1] = &hB1
                  (*ostr)[ocnt + 2] = &hA4
                  nchlen = 3
                  chpr = true
               case &hCA80
                  nchar = &hC6A6
               case &hCA83
                  nchar = &hC6A9
               case &hCA88
                  nchar = &hC6AE
               case &hCA89
                  nchar = &hC984
               case &hCA8A
                  nchar = &hC6B1
               case &hCA8B
                  nchar = &hC6B2
               case &hCA8C
                  nchar = &hC985
               case &hCA92
                  nchar = &hC6B7
               case &hCEAC
                  nchar = &hCE86
               case &hCEAD
                  nchar = &hCE88
               case &hCEAE
                  nchar = &hCE89
               case &hCEAF
                  nchar = &hCE8A
               case &hCEB1 to &hCEBF
                  nchar = ochar - &h20
               case &hCF80, &hCF81, &hCF83 to &hCF8B
                  nchar = ochar - &hE0
               case &hCF82
                  nchar = &hCEA3
               case &hCF8C
                  nchar = &hCE8C
               case &hCF8D
                  nchar = &hCE8E
               case &hCF8E
                  nchar = &hCE8F
               case &hCF90
                  nchar = &hCE92
               case &hCF91
                  nchar = &hCE98
               case &hCF95
                  nchar = &hCEA6
               case &hCF96
                  nchar = &hCEA0
               case &hCF97
                  nchar = &hCF8F
               case &hCF99 to &hCF9F, &hCFA0 to &hCFAF
                  if (ochar mod 2) = 1 then
                     nchar = ochar - 1
                  end if
               case &hCFB0
                  nchar = &hCE9A
               case &hCFB1
                  nchar = &hCEA1
               case &hCFB2
                  nchar = &hCFB9
               case &hCFB5
                  nchar = &hCE95
               case &hCFB8
                  nchar = &hCFB7
               case &hCFBB
                  nchar = &hCFBA
               case &hD0B0 to &hD0BF
                  nchar = ochar - &h20
               case &hD180 to &hD18F
                  nchar = ochar - &hE0
               case &hD190 to &hD19F
                  nchar = ochar - &h110
            end select
            if nchar <> 0 then
               (*ostr)[ocnt] = hibyte(nchar)
               (*ostr)[ocnt + 1] = lobyte(nchar)
               chpr = true
            end if
         end if
         if (icnt <> ocnt + 1) andalso (chpr = false) then
            for i as uinteger = 0 to chlen - 1
               (*ostr)[ocnt + i] = s[icnt + i]
            next
         end if
         icnt += chlen
         ocnt += nchlen
      end if
   wend
   ResizeStr(result, ocnt)
   return result
end function
Last edited by Munair on Dec 09, 2017 16:56, edited 12 times in total.
jj2007
Posts: 181
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: UTF-8 Variable Length String Library

Postby jj2007 » Dec 06, 2017 18:39

This looks very promising, compliments for your hard work!
TeeEmCee
Posts: 200
Joined: Jul 22, 2006 0:54
Location: Auckland

Re: UTF-8 Variable Length String Library

Postby TeeEmCee » Dec 07, 2017 6:56

Cool!
I also used "type ustring as string" to denote a string containing UTF8 characters. I haven't gotten around to writing replacements for all the builtin string functions/statements yet, though.
I think UTF8 strings really should be added to FB as a native data type, with ustring overloads for all the string functions. Since wstrings are incomplete and terribly misnamed, and are UTF16 on Windows, it's pretty painful to use them for unicode support and UTF8 seems the way to go in FB.
St_W
Posts: 1166
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: UTF-8 Variable Length String Library

Postby St_W » Dec 07, 2017 15:44

I agree that UTF-8 (and ideally also UTF-16 and UTF-32) support should be added to FB as Unicode is pretty common nowadays. Unfortunately your code is GPL licensed and thus it's usefulness is quite restricted (e.g. it is incompatible with the license used for FreeBasics internal runtime library).
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 07, 2017 15:49

St_W wrote:I agree that UTF-8 (and ideally also UTF-16 and UTF-32) support should be added to FB as Unicode is pretty common nowadays. Unfortunately your code is GPL licensed and thus it's usefulness is quite restricted (e.g. it is incompatible with the license used for FreeBasics internal runtime library).

Could you point me to that specific license? I perhaps mistakenly thought that "FreeBASIC is a multiplatform, free/open source (GPL) BASIC compiler."
marcov
Posts: 2399
Joined: Jun 16, 2005 9:45
Location: Eindhoven, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby marcov » Dec 07, 2017 16:17

Munair wrote:
St_W wrote:I agree that UTF-8 (and ideally also UTF-16 and UTF-32) support should be added to FB as Unicode is pretty common nowadays. Unfortunately your code is GPL licensed and thus it's usefulness is quite restricted (e.g. it is incompatible with the license used for FreeBasics internal runtime library).

Could you point me to that specific license? I perhaps mistakenly thought that "FreeBASIC is a multiplatform, free/open source (GPL) BASIC compiler."


So is the compiler source of Free Pascal, but the libraries have a different license. Which should have been near the top of lazutf8 btw:

See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.


which also applies to derivatives.

The modified (L) GPL is key here. The modification is so significant that the L doesn't matter anymore. See also http://wiki.freepascal.org/licensing

Note that even GCC licenses its libraries not under the GPL.
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 07, 2017 16:21

marcov wrote:
Munair wrote:
St_W wrote:I agree that UTF-8 (and ideally also UTF-16 and UTF-32) support should be added to FB as Unicode is pretty common nowadays. Unfortunately your code is GPL licensed and thus it's usefulness is quite restricted (e.g. it is incompatible with the license used for FreeBasics internal runtime library).

Could you point me to that specific license? I perhaps mistakenly thought that "FreeBASIC is a multiplatform, free/open source (GPL) BASIC compiler."


So is the compiler source of Free Pascal, but the libraries have a different license. Which should have been near the top of lazutf8 btw:

See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.


which also applies to derivatives.

The modified (L) GPL is key here. The modification is so significant that the L doesn't matter anymore. See also http://wiki.freepascal.org/licensing

Note that even GCC licenses its libraries not under the GPL.

Thank marcov. I will look into it and adjust the license accordingly. I admit I overlooked it.
St_W
Posts: 1166
Joined: Feb 11, 2009 14:24
Location: Austria
Contact:

Re: UTF-8 Variable Length String Library

Postby St_W » Dec 07, 2017 16:25

Munair wrote:Could you point me to that specific license? I perhaps mistakenly thought that "FreeBASIC is a multiplatform, free/open source (GPL) BASIC compiler."
The compiler itself is GPL, but the runtime library is LGPL with an additional static linking exception. Otherwise every application created with FB would have to use GPL or a compatible license. And as you know maybe, GPL can be a real problem because of its restrictions - and the compatibility issues with other libraries therefore.
You can find the license in FB's readme file:
https://github.com/freebasic/fbc/blob/master/readme.txt
marcov
Posts: 2399
Joined: Jun 16, 2005 9:45
Location: Eindhoven, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby marcov » Dec 07, 2017 16:48

Munair wrote:
Thank marcov. I will look into it and adjust the license accordingly. I admit I overlooked it.


No problem. It is not that there is any problem taking the source from FPC/Lazarus. It is just how it generally goes in OSS development tools, the libraries are more free than the compiler, so that generated applications are not chained to GPL.
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 07, 2017 16:54

I overlooked the fact that the FB runtime library is LGPL. Although some of this library's code is "translated" from the Lazarus project, it also includes rewritten and newly written code. So I'm not sure what rules are in effect here. I would be happy to go with LGPL for this and future libraries if that doesn't conflict with other licenses so that it can be included in the FB project.
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 07, 2017 18:06

License adjusted.
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 07, 2017 22:35

Lowercase function added. See the first post for the updated source code and description. The MLGPL License can be found here: http://www.ditrianum.org/mlgpl.html and as text: http://www.ditrianum.org/mlgpl.txt

The Lowercase function was only tested on the standard ASCII capital letters &h41 to &h5A. There's no guarantee that all languages are properly covered. The Lazarus project got a few fixes in the process too. ;)
jj2007
Posts: 181
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: UTF-8 Variable Length String Library

Postby jj2007 » Dec 08, 2017 19:44

Hi Munair,
You inspired me to implement uLeft$() and friends, see Unicode and UTF-8: Using non-Latin charsets in Assembler. Thanks ;-)
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 08, 2017 20:24

jj2007 wrote:Hi Munair,
You inspired me to implement uLeft$() and friends, see Unicode and UTF-8: Using non-Latin charsets in Assembler. Thanks ;-)

Nice. Well done!
Munair
Posts: 344
Joined: Oct 19, 2017 15:00
Location: 't Zand, NL
Contact:

Re: UTF-8 Variable Length String Library

Postby Munair » Dec 08, 2017 20:37

UUCase is now included, which makes the library complete as far as basic string operations are concerned. See the updated source code in the first post of this thread.

Be sure to read the MLGPL license if you plan to use or modify the library: http://www.basicstudio.org/mlgpl.html Or download the text version if you wish: http://www.basicstudio.org/mlgpl.txt

Updated example code:

Code: Select all

dim as ustring s, t

s = "Sale €5,-"
t = "€5"
print UIsAscii(s)
print ULen(s) 'prints '9'
print UInstr(s, t) 'prints '6'
print UMid(s, 6, 4) 'prints "5,-"
print ULeft(s, 4) 'prints "Sale"
print UIsAscii(ULeft(s, 4))
print URight(s, 4) 'prints "5,-"
print URemove(s, 6, 4) 'prints "Sale"
s = UInsert(s, "€ ", 6)
print s
print len(s)
UReplaceAll(s, "€", "$")
print s
print len(s)
UReplace(s, "$", "€")
print s
print len(s)
s = "ПРИВЕТ"
print s
s = ULCase(s)
print s
print UUCase(s)
sleep
end

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 1 guest