{$optimize 7}
{---------------------------------------------------------------}
{                                                               }
{  Character set handling routines                              }
{                                                               }
{  This module handles different character sets and performs    }
{  conversions between them.                                    }
{                                                               }
{  Externally available procedures:                             }
{                                                               }
{  ConvertMacRomanToUCS - convert MacRoman character to UCS     }
{  ConvertUCSToMacRoman - convert UCS character to MacRoman     }
{                                                               }
{---------------------------------------------------------------}

unit Charset;

{$LibPrefix '0/obj/'}

interface

{$segment 'SCANNER'}

uses CCommon, Table;

const
   maxUCSCodePoint = $10ffff;

type
   ucsCodePoint = 0..maxUCSCodePoint;
   utf8Rec = record
      length: integer;
      bytes: packed array [1..4] of byte;
      end;
   utf16Rec = record
      length: integer;
      codeUnits: packed array [1..2] of integer;
      end;


function ConvertMacRomanToUCS(ch: char): ucsCodePoint;

{ convert a character from MacRoman charset to UCS (Unicode)     }
{                                                                }
{ Returns UCS code point value for the character.                }


function ConvertUCSToMacRoman(ch: ucsCodePoint): integer;

{ convert a character from UCS (Unicode) to MacRoman charset     }
{                                                                }
{ Returns ordinal value of the character, or -1 if it can't be   }
{ converted.                                                     }


procedure UTF16Encode(ch: ucsCodePoint; var utf16: utf16Rec);

{ Encode a UCS code point in UTF-16                              }
{                                                                }
{ ch - the code point                                            }
{ utf16 - set to the UTF-16 representation of the code point     }


procedure UTF8Encode(ch: ucsCodePoint; var utf8: utf8Rec);

{ Encode a UCS code point in UTF-8                               }
{                                                                }
{ ch - the code point                                            }
{ utf16 - set to the UTF-8 representation of the code point      }


function ValidUCNForIdentifier(ch: ucsCodePoint; initial: boolean): boolean;

{ Check if a code point is valid for a UCN in an identifier      }
{                                                                }
{ ch - the code point                                            }
{ initial - is this UCN the initial element of the identifier?   }

{----------------------------------------------------------------}

implementation

function ConvertMacRomanToUCS{(ch: char): ucsCodePoint};

{ convert a character from MacRoman charset to UCS (Unicode)     }
{                                                                }
{ Returns UCS code point value for the character.                }

begin {ConvertMacRomanToUCS}
if ord(ch) < $80 then
   ConvertMacRomanToUCS := ord(ch)
else if ord(ch) <= $ff then
   ConvertMacRomanToUCS := ord4(macRomanToUCS[ord(ch)]) & $0000ffff
else
   ConvertMacRomanToUCS := $00fffd; {invalid input => REPLACEMENT CHARACTER}
end; {ConvertMacRomanToUCS}


function ConvertUCSToMacRoman{(ch: ucsCodePoint): integer};

{ convert a character from UCS (Unicode) to MacRoman charset     }
{                                                                }
{ Returns ordinal value of the character, or -1 if it can't be   }
{ converted.                                                     }

label 1;

var
   i: $80..$ff;                         {loop index}
   ch16bit: integer;                    {16-bit version of ch (maybe negative)}

begin {ConvertUCSToMacRoman}
if ch < $80 then
   ConvertUCSToMacRoman := ord(ch)
else begin
   if ch <= $00ffff then begin
      ch16bit := ord(ch);
      for i := $80 to $ff do begin
         if macRomanToUCS[i] = ch16Bit then begin
            ConvertUCSToMacRoman := i;
            goto 1;
            end {if}
         end; {for}
      end; {if}
   ConvertUCSToMacRoman := -1;
   end; {else}
1:
end; {ConvertUCSToMacRoman}


procedure UTF16Encode{ch: ucsCodePoint; var utf16: utf16Rec};

{ Encode a UCS code point in UTF-16                              }
{                                                                }
{ ch - the code point                                            }
{ utf16 - set to the UTF-16 representation of the code point     }

begin {UTF16Encode}
if ch <= $00ffff then begin
   utf16.length := 1;
   utf16.codeUnits[1] := ord(ch);
   end {if}
else begin
   utf16.length := 2;
   ch := ch - $010000;
   utf16.codeUnits[1] := $D800 | ord(ch >> 10);
   utf16.codeUnits[2] := $DC00 | ord(ch & $03ff);
   end; {else}
end; {UTF16Encode}


procedure UTF8Encode{ch: ucsCodePoint; var utf8: utf8Rec};

{ Encode a UCS code point in UTF-8                               }
{                                                                }
{ ch - the code point                                            }
{ utf16 - set to the UTF-8 representation of the code point      }

begin {UTF8Encode}
if ch <= $00007f then begin
   utf8.length := 1;
   utf8.bytes[1] := ord(ch);
   end {if}
else if ch <= $0007ff then begin
   utf8.length := 2;
   utf8.bytes[1] := $C0 | ord(ch >> 6);
   utf8.bytes[2] := $80 | ord(ch & $3f)
   end {else if}
else if ch <= $00ffff then begin
   utf8.length := 3;
   utf8.bytes[1] := $E0 | ord(ch >> 12);
   utf8.bytes[2] := $80 | ord((ch >> 6) & $3f);
   utf8.bytes[3] := $80 | ord(ch & $3f);
   end {else if}
else begin
   utf8.length := 4;
   utf8.bytes[1] := $F0 | ord(ch >> 18);
   utf8.bytes[2] := $80 | ord((ch >> 12) & $3f);
   utf8.bytes[3] := $80 | ord((ch >> 6) & $3f);
   utf8.bytes[4] := $80 | ord(ch & $3f);
   end; {else}
end; {UTF8Encode}


function ValidUCNForIdentifier{(ch: ucsCodePoint; initial: boolean): boolean};

{ Check if a code point is valid for a UCN in an identifier      }
{                                                                }
{ ch - the code point                                            }
{ initial - is this UCN the initial element of the identifier?   }

begin {ValidUCNForIdentifier}
{See C17 Annex D}
ValidUCNForIdentifier := false;
if    (ch = $0000A8)
   or (ch = $0000AA)
   or (ch = $0000AD)
   or (ch = $0000AF)
   or ((ch >= $0000B2) and (ch <= $0000B5))
   or ((ch >= $0000B7) and (ch <= $0000BA))
   or ((ch >= $0000BC) and (ch <= $0000BE))
   or ((ch >= $0000C0) and (ch <= $0000D6))
   or ((ch >= $0000D8) and (ch <= $0000F6))
   or ((ch >= $0000F8) and (ch <= $0000FF))
   or ((ch >= $000100) and (ch <= $00167F))
   or ((ch >= $001681) and (ch <= $00180D))
   or ((ch >= $00180F) and (ch <= $001FFF))
   or ((ch >= $00200B) and (ch <= $00200D))
   or ((ch >= $00202A) and (ch <= $00202E))
   or ((ch >= $00203F) and (ch <= $002040))
   or (ch = $002054)
   or ((ch >= $002060) and (ch <= $00206F))
   or ((ch >= $002070) and (ch <= $00218F))
   or ((ch >= $002460) and (ch <= $0024FF))
   or ((ch >= $002776) and (ch <= $002793))
   or ((ch >= $002C00) and (ch <= $002DFF))
   or ((ch >= $002E80) and (ch <= $002FFF))
   or ((ch >= $003004) and (ch <= $003007))
   or ((ch >= $003021) and (ch <= $00302F))
   or ((ch >= $003031) and (ch <= $00303F))
   or ((ch >= $003040) and (ch <= $00D7FF))
   or ((ch >= $00F900) and (ch <= $00FD3D))
   or ((ch >= $00FD40) and (ch <= $00FDCF))
   or ((ch >= $00FDF0) and (ch <= $00FE44))
   or ((ch >= $00FE47) and (ch <= $00FFFD))
   or ((ch >= $010000) and (ch <= $01FFFD))
   or ((ch >= $020000) and (ch <= $02FFFD))
   or ((ch >= $030000) and (ch <= $03FFFD))
   or ((ch >= $040000) and (ch <= $04FFFD))
   or ((ch >= $050000) and (ch <= $05FFFD))
   or ((ch >= $060000) and (ch <= $06FFFD))
   or ((ch >= $070000) and (ch <= $07FFFD))
   or ((ch >= $080000) and (ch <= $08FFFD))
   or ((ch >= $090000) and (ch <= $09FFFD))
   or ((ch >= $0A0000) and (ch <= $0AFFFD))
   or ((ch >= $0B0000) and (ch <= $0BFFFD))
   or ((ch >= $0C0000) and (ch <= $0CFFFD))
   or ((ch >= $0D0000) and (ch <= $0DFFFD))
   or ((ch >= $0E0000) and (ch <= $0EFFFD))
   then ValidUCNForIdentifier := true;

if initial then
   if    ((ch >= $000300) and (ch <= $00036F))
      or ((ch >= $001DC0) and (ch <= $001DFF))
      or ((ch >= $0020D0) and (ch <= $0020FF))
      or ((ch >= $00FE20) and (ch <= $00FE2F))
      then ValidUCNForIdentifier := false;
end; {ValidUCNForIdentifier}

end.
