From 662784212e5871360f6bcdbaa033c8780ac89f62 Mon Sep 17 00:00:00 2001
From: Basile Burg <basile.b@gmx.com>
Date: Sun, 31 Oct 2021 07:41:55 +0100
Subject: [PATCH] add an highlither for styx sources

---
 lazproj/dexed.lpi       |  12 +-
 src/u_common.pas        |  13 +
 src/u_editoroptions.pas |  10 +
 src/u_sxsyn.pas         | 828 ++++++++++++++++++++++++++++++++++++++++
 src/u_synmemo.pas       |  11 +-
 5 files changed, 868 insertions(+), 6 deletions(-)
 create mode 100644 src/u_sxsyn.pas

diff --git a/lazproj/dexed.lpi b/lazproj/dexed.lpi
index 31cf1194..31006aed 100644
--- a/lazproj/dexed.lpi
+++ b/lazproj/dexed.lpi
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <CONFIG>
   <ProjectOptions>
-    <Version Value="12"/>
+    <Version Value="11"/>
     <PathDelim Value="\"/>
     <General>
-      <Flags>
-        <CompatibilityMode Value="True"/>
-      </Flags>
       <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
       <Title Value="dexed"/>
       <ResourceType Value="res"/>
       <UseXPManifest Value="True"/>
@@ -551,7 +549,7 @@
         <PackageName Value="LCL"/>
       </Item8>
     </RequiredPackages>
-    <Units Count="62">
+    <Units Count="63">
       <Unit0>
         <Filename Value="dexed.lpr"/>
         <IsPartOfProject Value="True"/>
@@ -876,6 +874,10 @@
         <Filename Value="..\src\u_synmultiguttermarks.pas"/>
         <IsPartOfProject Value="True"/>
       </Unit61>
+      <Unit62>
+        <Filename Value="..\src\u_sxsyn.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit62>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
diff --git a/src/u_common.pas b/src/u_common.pas
index 8bb6d613..18c44fb7 100644
--- a/src/u_common.pas
+++ b/src/u_common.pas
@@ -303,6 +303,11 @@ type
    *)
   function hasCppSyntax(const ext: string): boolean;
 
+  (**
+   * Returns true if ext matches a file extension whose type is highlightable (C/C++)
+   *)
+  function hasSxSyntax(const ext: string): boolean;
+
   (**
    * Returns true if ext matches a file extension whose type can be passed as source.
    *)
@@ -1349,6 +1354,14 @@ begin
   end;
 end;
 
+function hasSxSyntax(const ext: string): boolean;
+begin
+  result := false;
+  case ext of
+    '.sx', '.sar': result := true;
+  end;
+end;
+
 function isDlangCompilable(const ext: string): boolean;
 begin
   result := false;
diff --git a/src/u_editoroptions.pas b/src/u_editoroptions.pas
index 22fd8625..96693465 100644
--- a/src/u_editoroptions.pas
+++ b/src/u_editoroptions.pas
@@ -699,6 +699,16 @@ begin
   anEditor.CppHighlighter.SymbolAttri := anEditor.D2Highlighter.symbols;
   anEditor.CppHighlighter.NumberAttri := anEditor.D2Highlighter.numbers;
 
+  anEditor.SxHighlighter.attributes := anEditor.D2Highlighter.attributes;
+  anEditor.SxHighlighter.comments := anEditor.D2Highlighter.comments;
+  anEditor.SxHighlighter.identifiers := anEditor.D2Highlighter.identifiers;
+  anEditor.SxHighlighter.keywords := anEditor.D2Highlighter.keywords;
+  anEditor.SxHighlighter.whites := anEditor.D2Highlighter.whites;
+  anEditor.SxHighlighter.strings := anEditor.D2Highlighter.strings;
+  anEditor.SxHighlighter.symbols := anEditor.D2Highlighter.symbols;
+  anEditor.SxHighlighter.numbers := anEditor.D2Highlighter.numbers;
+  anEditor.SxHighlighter.errors := anEditor.D2Highlighter.errors;
+
   anEditor.completionMenu.TheForm.Font.Assign(font);
   anEditor.autoCloseCurlyBrace            := fAutoCloseCurlyBrace;
   anEditor.autoClosedPairs                := fAutoClosedPairs;
diff --git a/src/u_sxsyn.pas b/src/u_sxsyn.pas
new file mode 100644
index 00000000..3fdea3e3
--- /dev/null
+++ b/src/u_sxsyn.pas
@@ -0,0 +1,828 @@
+unit u_sxsyn;
+
+{$I u_defines.inc}
+
+interface
+
+uses
+  Classes, SysUtils, Graphics,
+  SynEditHighlighter, SynEditHighlighterFoldBase, SynEditTypes,
+  u_common;
+
+type
+
+  KeywordMatch = record
+  private
+
+    {
+          rendered on 2021-Oct-31 06:16:53.798781 by IsItThere.
+           - PRNG seed: 6574
+           - map length: 128
+           - case sensitive: true
+    }
+
+    const fWords: array [0..127] of string =
+      ('', '', 'alias', 'this', 'f32', '', 's32', 's8', 'throw', 'f64', 'switch', 's64', '', '', '', '', '', '', 'finally', '', '', '', 'if', '', '', '', '', '', '', '', '', '', '', '', '', '', 'protection', 's16', '', '', 'goto', 'usize', '', '', 'in', '', 'static', '', 'try', '', '', '', 'enum', 'do', '', '', '', 'super', 'const', 'union', '', '', 'else', '', 'version', '', '', '', '', 'u32', 'u8', 'echo', '', 'true', 'u64', 'asm', '', 'struct', '', 'auto', 'bool', '', '', '', '', '', 'false', 'null', 'class', '', '', 'overload', '', '', '', '', 'label', '', 'assert', 'continue', 'u16', 'foreach', 'break', '', '', '', 'ssize', '', '', '', '', 'unit', 'while', '', 'var', '', 'with', 'template', '', 'import', 'on', '', '', 'function', '', 'return', '', '');
+
+    const fFilled: array [0..127] of boolean =
+      (false, false, true, true, true, false, true, true, true, true, true, true, false, false, false, false, false, false, true, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, true, false, true, false, true, false, false, false, true, true, false, false, false, true, true, true, false, false, true, false, true, false, false, false, false, true, true, true, false, true, true, true, false, true, false, true, true, false, false, false, false, false, true, true, true, false, false, true, false, false, false, false, true, false, true, true, true, true, true, false, false, false, true, false, false, false, false, true, true, false, true, false, true, true, false, true, true, false, false, true, false, true, false, false);
+
+    const fCoefficients: array [0..255] of Byte =
+      (251, 141, 241, 229, 153, 224, 69, 154, 183, 60, 181, 128, 168, 190, 25, 39, 142, 116, 172, 48, 49, 203, 195, 46, 229, 38, 232, 88, 114, 147, 97, 185, 242, 43, 34, 75, 89, 228, 21, 156, 254, 129, 186, 74, 224, 78, 146, 25, 49, 118, 232, 70, 92, 34, 215, 44, 47, 48, 153, 28, 19, 111, 146, 97, 159, 224, 143, 72, 212, 194, 30, 104, 134, 217, 123, 30, 68, 206, 208, 50, 96, 3, 30, 30, 90, 245, 234, 93, 191, 97, 112, 45, 103, 11, 225, 255, 132, 128, 78, 62, 41, 62, 214, 100, 191, 64, 95, 146, 234, 243, 236, 12, 68, 60, 72, 216, 44, 23, 42, 201, 206, 188, 188, 190, 114, 193, 20, 143, 119, 31, 235, 96, 72, 92, 146, 146, 227, 33, 253, 76, 142, 189, 58, 38, 203, 68, 19, 35, 82, 49, 150, 78, 157, 199, 33, 210, 223, 133, 237, 56, 89, 123, 20, 177, 68, 205, 21, 128, 142, 35, 222, 180, 138, 141, 10, 160, 212, 166, 255, 166, 75, 212, 216, 239, 76, 119, 45, 62, 130, 21, 116, 119, 253, 50, 241, 160, 208, 225, 244, 55, 57, 238, 239, 107, 190, 143, 165, 125, 217, 80, 18, 244, 72, 210, 28, 77, 138, 40, 239, 124, 75, 87, 92, 121, 107, 222, 0, 23, 215, 213, 133, 144, 208, 216, 191, 170, 75, 182, 75, 230, 218, 136, 44, 134, 191, 183, 126, 108, 58, 29, 52, 163, 111, 131, 198, 230);
+
+    class function hash(const w: string): Word; static;
+  public
+    class function contains(const w: string): boolean; static;
+  end;
+
+  TTokenKind = (tkNone, tkError, tkCommt, tkIdent, tkKeywd, tkStrng, tkBlank, tkSymbl, tkNumbr, tkAttri);
+
+  TRangeKind = (rkNone, rkString1, rkString2, rkBlockCom1, rkAttrib);
+
+  TSynSxSynRange = class (TSynCustomHighlighterRange)
+  private
+    attribParenCount    : Integer;
+    rangeKind           : TRangeKind;
+  public
+    procedure Assign(source: TSynCustomHighlighterRange); override;
+    function Compare(range: TSynCustomHighlighterRange): integer; override;
+    procedure Clear; override;
+    procedure copyFrom(source: TSynSxSynRange);
+  end;
+
+  TSynSxSyn = class (TSynCustomFoldHighlighter)
+	private
+    fWhiteAttrib: TSynHighlighterAttributes;
+		fNumbrAttrib: TSynHighlighterAttributes;
+		fSymblAttrib: TSynHighlighterAttributes;
+		fIdentAttrib: TSynHighlighterAttributes;
+		fCommtAttrib: TSynHighlighterAttributes;
+		fStrngAttrib: TSynHighlighterAttributes;
+    fKeywdAttrib: TSynHighlighterAttributes;
+    fAttriAttrib: TSynHighlighterAttributes;
+    fErrorAttrib: TSynHighlighterAttributes;
+
+    fLineBuf: string;
+    fTokStart, fTokStop: Integer;
+    fTokKind: TTokenKind;
+    fCurrRange: TSynSxSynRange;
+    fLineNum: integer;
+
+    fAttribLut: array[TTokenKind] of TSynHighlighterAttributes;
+
+    procedure setWhiteAttrib(value: TSynHighlighterAttributes);
+    procedure setNumbrAttrib(value: TSynHighlighterAttributes);
+    procedure setSymblAttrib(value: TSynHighlighterAttributes);
+    procedure setIdentAttrib(value: TSynHighlighterAttributes);
+    procedure setCommtAttrib(value: TSynHighlighterAttributes);
+    procedure setStrngAttrib(value: TSynHighlighterAttributes);
+    procedure setKeywdAttrib(value: TSynHighlighterAttributes);
+    procedure setAttriAttrib(value: TSynHighlighterAttributes);
+    procedure setErrorAttrib(value: TSynHighlighterAttributes);
+
+    function safeLookupChar(): PChar;
+    function canLookup2Char(): boolean;
+    procedure lexOpAndOpEqual();
+    procedure lexOpAndOpOpAndOpEqual(const op: char);
+    procedure lexOpAndOpOpAndOpEqualAndOpOpEqual(const op: char);
+    procedure lexAssEquOrLambda();
+    procedure lexHexLiteral();
+    procedure lexBinLiteral();
+    procedure lexIntLiteral();
+    procedure lexFloatingLiteralFractionalPart();
+    procedure lexExponent();
+    procedure lexStringLiteral();
+    procedure lexRawStringLiteral();
+    procedure lexLineComment();
+    procedure lexStarComment();
+    procedure lexIdentifier();
+
+  protected
+    function GetRangeClass: TSynCustomHighlighterRangeClass; override;
+    function GetIdentChars: TSynIdentChars; override;
+
+  public
+
+		constructor create(aOwner: TComponent); override;
+    destructor destroy; override;
+
+    procedure GetTokenEx(out TokenStart: PChar; out TokenLength: integer); override;
+    function GetDefaultAttribute(Index: integer): TSynHighlighterAttributes; override;
+    procedure setLine(const NewValue: string; LineNumber: Integer); override;
+    procedure next; override;
+    function  GetTokenAttribute: TSynHighlighterAttributes; override;
+    function GetToken: string; override;
+    function GetTokenKind: integer; override;
+    function GetTokenPos: Integer; override;
+    function GetEol: Boolean; override;
+    procedure SetRange(value: Pointer); override;
+    procedure ResetRange; override;
+    function GetRange: Pointer; override;
+
+    property whites:      TSynHighlighterAttributes read fWhiteAttrib write setWhiteAttrib stored true;
+    property numbers:     TSynHighlighterAttributes read fNumbrAttrib write setNumbrAttrib stored true;
+    property symbols:     TSynHighlighterAttributes read fSymblAttrib write setSymblAttrib stored true;
+    property identifiers: TSynHighlighterAttributes read fIdentAttrib write setIdentAttrib stored true;
+    property comments:    TSynHighlighterAttributes read fCommtAttrib write setCommtAttrib stored true;
+    property strings:     TSynHighlighterAttributes read fStrngAttrib write setStrngAttrib stored true;
+    property keywords:    TSynHighlighterAttributes read fKeywdAttrib write setKeywdAttrib stored true;
+    property attributes:  TSynHighlighterAttributes read fAttriAttrib write setAttriAttrib stored true;
+    property errors:      TSynHighlighterAttributes read fErrorAttrib write setErrorAttrib stored true;
+
+  end;
+
+implementation
+
+{$PUSH}{$R-}{$COPERATORS ON}
+class function KeywordMatch.hash(const w: string): Word;
+var
+  i: integer;
+begin
+  Result := 0;
+  for i := 1 to length(w) do
+    Result += fCoefficients[Byte(w[i])];
+  Result := Result mod 128;
+end;
+
+class function KeywordMatch.contains(const w: string): boolean;
+var
+  h: Word;
+begin
+  result := false;
+  h := hash(w);
+  if fFilled[h] then
+    result := fWords[h] = w;
+end;
+{$POP}
+
+procedure TSynSxSynRange.Assign(source: TSynCustomHighlighterRange);
+var
+  rng: TSynSxSynRange;
+begin
+  inherited;
+  if source is TSynSxSynRange then
+  begin
+    rng       := TSynSxSynRange(source);
+    rangeKind := rng.rangeKind;
+  end;
+end;
+
+function TSynSxSynRange.Compare(range: TSynCustomHighlighterRange): integer;
+begin
+  result := inherited Compare(range);
+  assert(range <> nil);
+  if not result.equals(0) then
+    exit;
+  if range is TSynSxSynRange then
+    result := integer(rangeKind = TSynSxSynRange(range).rangeKind);
+end;
+
+procedure TSynSxSynRange.Clear;
+begin
+  rangeKind := TRangeKind.rkNone;
+end;
+
+procedure TSynSxSynRange.copyFrom(source: TSynSxSynRange);
+begin
+  if source.isAssigned then
+    rangeKind := source.rangeKind;
+end;
+
+constructor TSynSxSyn.create(aOwner: TComponent);
+begin
+	inherited create(aOwner);
+
+  DefaultFilter:= 'STYX source|*.sx|STYX archive|*.sar';
+
+  WordBreakChars := WordBreakChars - ['@'];
+
+  fWhiteAttrib := TSynHighlighterAttributes.Create('White','White');
+  fNumbrAttrib := TSynHighlighterAttributes.Create('Numbr','Numbr');
+  fSymblAttrib := TSynHighlighterAttributes.Create('Symbl','Symbl');
+  fIdentAttrib := TSynHighlighterAttributes.Create('Ident','Ident');
+  fCommtAttrib := TSynHighlighterAttributes.Create('Commt','Commt');
+  fStrngAttrib := TSynHighlighterAttributes.Create('Strng','Strng');
+  fKeywdAttrib := TSynHighlighterAttributes.Create('Keywd','Keywd');
+  fAttriAttrib := TSynHighlighterAttributes.Create('Attri','Attri');
+  fErrorAttrib := TSynHighlighterAttributes.Create('Error','Error');
+
+  AddAttribute(fWhiteAttrib);
+  AddAttribute(fNumbrAttrib);
+  AddAttribute(fSymblAttrib);
+  AddAttribute(fIdentAttrib);
+  AddAttribute(fCommtAttrib);
+  AddAttribute(fStrngAttrib);
+  AddAttribute(fKeywdAttrib);
+  AddAttribute(fAttriAttrib);
+  AddAttribute(fErrorAttrib);
+
+  fAttribLut[TTokenKind.tkident] := fIdentAttrib;
+  fAttribLut[TTokenKind.tkBlank] := fWhiteAttrib;
+  fAttribLut[TTokenKind.tkCommt] := fCommtAttrib;
+  fAttribLut[TTokenKind.tkKeywd] := fKeywdAttrib;
+  fAttribLut[TTokenKind.tkNumbr] := fNumbrAttrib;
+  fAttribLut[TTokenKind.tkStrng] := fStrngAttrib;
+  fAttribLut[TTokenKind.tksymbl] := fSymblAttrib;
+  fAttribLut[TTokenKind.tkAttri] := fAttriAttrib;
+  fAttribLut[TTokenKind.tkError] := fErrorAttrib;
+
+end;
+
+destructor TSynSxSyn.destroy;
+begin
+  fCurrRange.Free;
+  inherited;
+end;
+
+function TSynSxSyn.GetRangeClass: TSynCustomHighlighterRangeClass;
+begin
+  result := TSynSxSynRange;
+end;
+
+function TSynSxSyn.GetIdentChars: TSynIdentChars;
+begin
+  result := ['_', 'A'..'Z', 'a'..'z', '0'..'9'];
+end;
+
+procedure TSynSxSyn.setWhiteAttrib(value: TSynHighlighterAttributes);
+begin
+  fWhiteAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setNumbrAttrib(value: TSynHighlighterAttributes);
+begin
+  fNumbrAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setSymblAttrib(value: TSynHighlighterAttributes);
+begin
+  fSymblAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setIdentAttrib(value: TSynHighlighterAttributes);
+begin
+  fIdentAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setCommtAttrib(value: TSynHighlighterAttributes);
+begin
+  fCommtAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setStrngAttrib(value: TSynHighlighterAttributes);
+begin
+  fStrngAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setKeywdAttrib(value: TSynHighlighterAttributes);
+begin
+  fKeywdAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setAttriAttrib(value: TSynHighlighterAttributes);
+begin
+  fAttriAttrib.Assign(value);
+end;
+
+procedure TSynSxSyn.setErrorAttrib(value: TSynHighlighterAttributes);
+begin
+  fErrorAttrib.Assign(value);
+end;
+
+function TSynSxSyn.GetTokenAttribute: TSynHighlighterAttributes;
+begin
+  result := fAttribLut[fTokKind];
+end;
+
+procedure TSynSxSyn.SetRange(value: Pointer);
+var
+  stored: TSynSxSynRange;
+begin
+  inherited SetRange(value);
+  stored := TSynSxSynRange(CodeFoldRange.RangeType);
+  if fCurrRange.isAssigned and stored.isAssigned then
+    fCurrRange.copyFrom(stored);
+end;
+
+function TSynSxSyn.GetRange: Pointer;
+var
+  stored: TSynSxSynRange;
+begin
+  stored := TSynSxSynRange(inherited GetRange);
+  if stored.isNotAssigned then
+    stored := TSynSxSynRange.Create(nil);
+  stored.copyFrom(fCurrRange);
+
+  CodeFoldRange.RangeType := Pointer(stored);
+  Result := inherited GetRange;
+end;
+
+procedure TSynSxSyn.ResetRange;
+begin
+  if fCurrRange.isNotAssigned then
+    fCurrRange := TSynSxSynRange.Create(nil)
+  else
+    fCurrRange.Clear;
+end;
+
+procedure TSynSxSyn.setLine(const NewValue: string; LineNumber: Integer);
+begin
+  inherited;
+  fLineBuf := NewValue;
+  fLineNum := LineNumber;
+  fTokStop := 1;
+  next;
+end;
+
+function TSynSxSyn.GetEol: Boolean;
+begin
+  result :=  fTokKind = tkNone;
+end;
+
+function TSynSxSyn.GetTokenPos: Integer;
+begin
+  result := fTokStart - 1;
+end;
+
+function TSynSxSyn.GetToken: string;
+begin
+  result := copy(fLineBuf, FTokStart, fTokStop - FTokStart);
+end;
+
+procedure TSynSxSyn.GetTokenEx(out TokenStart: PChar; out TokenLength: integer);
+begin
+  TokenStart  := @fLineBuf[FTokStart];
+  TokenLength := fTokStop - FTokStart;
+end;
+
+function TSynSxSyn.GetDefaultAttribute(Index: integer): TSynHighlighterAttributes;
+begin
+  result := nil;
+end;
+
+function TSynSxSyn.GetTokenKind: integer;
+begin
+  Result := Integer(fTokKind);
+end;
+
+function TSynSxSyn.safeLookupChar: PChar;
+begin
+  if fTokStop >= length(fLineBuf) then
+    result := nil
+  else result := @fLineBuf[fTokStop + 1];
+end;
+
+function TSynSxSyn.canLookup2Char(): boolean;
+begin
+  result := fTokStop + 2 <= length(fLineBuf);
+end;
+
+procedure TSynSxSyn.lexOpAndOpEqual();
+var
+  nextPChar: PChar;
+  nextChar: char;
+begin
+  fTokKind  := TTokenKind.tkSymbl;
+  nextPChar := safeLookupChar();
+  if nextPChar <> nil then
+  begin
+    nextChar := nextPChar^;
+    if nextChar = '=' then
+      fTokStop += 1;
+  end;
+  fTokStop  += 1;
+end;
+
+procedure TSynSxSyn.lexOpAndOpOpAndOpEqual(const op: char);
+var
+  nextPChar: PChar;
+  nextChar: char;
+begin
+  fTokKind  := TTokenKind.tkSymbl;
+  nextPChar := safeLookupChar();
+  if nextPChar <> nil then
+  begin
+    nextChar := fLineBuf[fTokStop];
+    if (nextChar = op) or (nextChar = '=') then
+      fTokStop += 1;
+  end;
+  fTokStop  += 1;
+end;
+
+procedure TSynSxSyn.lexOpAndOpOpAndOpEqualAndOpOpEqual(const op: char);
+var
+  nextPChar: PChar;
+  nextChar: char;
+  can2: boolean;
+begin
+  fTokKind  := TTokenKind.tkSymbl;
+  can2      := canLookup2Char();
+  nextPChar := safeLookupChar();
+  // <<=
+  if can2 and (fLineBuf[fTokStop + 1] = op) and (fLineBuf[fTokStop+2] = '=') then
+    fTokStop  += 2
+  else if nextPChar <> nil then
+  begin
+    nextChar := nextPChar^;
+    // << or <=
+    if (nextChar = op) or (nextChar = '=') then
+      fTokStop += 1;
+  end;
+  fTokStop  += 1;
+end;
+
+procedure TSynSxSyn.lexAssEquOrLambda();
+var
+  nextPChar: PChar;
+  nextChar: char;
+begin
+  fTokKind  := TTokenKind.tkSymbl;
+  nextPChar := safeLookupChar();
+  if nextPChar <> nil then
+  begin
+    nextChar := fLineBuf[fTokStop];
+    if (nextChar = '=') or (nextChar = '>') then
+      fTokStop += 1;
+  end;
+  fTokStop  += 1;
+end;
+
+procedure TSynSxSyn.lexIntLiteral();
+var
+  nextPChar: PChar;
+  nextChar: char;
+begin
+  fTokKind:=TTokenKind.tkNumbr;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '0'..'9', '_':
+      begin
+        fTokStop += 1;
+        continue;
+      end;
+      '.':
+      begin
+        nextPChar := safeLookupChar();
+        if nextPChar <> nil then
+        begin
+          nextChar := nextPChar^;
+          if nextChar in ['0' .. '9'] then
+          begin
+            fTokStop += 1;
+            lexFloatingLiteralFractionalPart();
+            exit;
+          end;
+        end;
+        break;
+      end;
+    end;
+    break;
+  end;
+end;
+
+procedure TSynSxSyn.lexFloatingLiteralFractionalPart();
+begin
+  fTokKind:=TTokenKind.tkNumbr;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '0'..'9', '_': fTokStop += 1;
+      'e', 'E':
+      begin
+        lexExponent();
+        exit;
+      end
+      else break;
+    end;
+  end;
+end;
+
+procedure TSynSxSyn.lexExponent();
+begin
+  fTokStop += 1;
+  if fTokStop > fLineBuf.length then
+  begin
+    fTokKind:=TTokenKind.tkError;
+    exit;
+  end;
+  if fLineBuf[fTokStop] in ['+', '-'] then
+    fTokStop += 1;
+  if fTokStop > fLineBuf.length then
+  begin
+    fTokKind:=TTokenKind.tkError;
+    exit;
+  end;
+  while fTokStop <= fLineBuf.length do
+  begin
+    if fLineBuf[fTokStop] in ['0' .. '9'] then
+    begin
+      fTokStop += 1;
+      continue;
+    end else
+      break;
+  end;
+end;
+
+procedure TSynSxSyn.lexHexLiteral();
+begin
+  fTokStop += 2;
+  fTokKind:=TTokenKind.tkNumbr;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '0'..'9', 'a'..'f', 'A'..'F', '_':
+      begin
+        fTokStop += 1;
+        continue;
+      end
+      else while (fTokStop <= fLineBuf.length) and
+        (fLineBuf[fTokStop] in ['g' .. 'z', 'G' .. 'Z']) do
+      begin
+        fTokKind := TTokenKind.tkError;
+        fTokStop += 1;
+      end;
+    end;
+    break;
+  end;
+end;
+
+procedure TSynSxSyn.lexBinLiteral();
+begin
+  fTokStop += 2;
+  fTokKind:=TTokenKind.tkNumbr;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '0', '1', '_':
+      begin
+        fTokStop += 1;
+        continue;
+      end;
+      else while (fTokStop <= fLineBuf.length) and
+        (fLineBuf[fTokStop] in ['2' .. '9', 'a' .. 'z', 'A' .. 'Z']) do
+      begin
+        fTokKind := TTokenKind.tkError;
+        fTokStop += 1;
+      end;
+    end;
+    break;
+  end;
+end;
+
+procedure TSynSxSyn.lexStringLiteral();
+var
+  firstLine: Boolean;
+  terminate: Boolean = false;
+begin
+  fTokKind  := TTokenKind.tkStrng;
+  firstLine := fCurrRange.rangeKind = TRangeKind.rkNone;
+  if firstLine then
+    fTokStop  += 1;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '\' : fTokStop += 2;
+      '"' :
+      begin
+        fTokStop += 1;
+        terminate := true;
+        break;
+      end
+      else fTokStop += 1;
+    end;
+  end;
+  if firstLine and not terminate then
+    fCurrRange.rangeKind:= TRangeKind.rkString1
+  else if (fCurrRange.rangeKind = TRangeKind.rkString1) and terminate then
+    fCurrRange.rangeKind:= TRangeKind.rkNone;
+end;
+
+procedure TSynSxSyn.lexRawStringLiteral();
+var
+  firstLine: Boolean;
+  terminate: Boolean = false;
+begin
+  fTokKind  := TTokenKind.tkStrng;
+  firstLine := fCurrRange.rangeKind = TRangeKind.rkNone;
+  if firstLine then
+    fTokStop  += 1;
+  while fTokStop <= fLineBuf.length do
+  begin
+    if fLineBuf[fTokStop] = '`' then
+    begin
+      fTokStop += 1;
+      terminate := true;
+      break;
+    end
+    else fTokStop += 1;
+  end;
+  if firstLine and not terminate then
+    fCurrRange.rangeKind:= TRangeKind.rkString2
+  else if (fCurrRange.rangeKind = TRangeKind.rkString2) and terminate then
+    fCurrRange.rangeKind:= TRangeKind.rkNone;
+end;
+
+procedure TSynSxSyn.lexLineComment();
+begin
+  fTokKind  := TTokenKind.tkCommt;
+  fTokStop  := fLineBuf.length + 1;
+end;
+
+procedure TSynSxSyn.lexStarComment();
+var
+  firstLine: Boolean;
+  terminate: Boolean = false;
+begin
+  fTokKind  := TTokenKind.tkCommt;
+  firstLine := fCurrRange.rangeKind = TRangeKind.rkNone;
+  while fTokStop <= fLineBuf.length do
+  begin
+    if fLineBuf[fTokStop] = '*' then
+    begin
+      fTokStop += 1;
+      if (fTokStop <= fLineBuf.length) and (fLineBuf[fTokStop] = '/') then
+      begin
+        fTokStop += 1;
+        terminate := true;
+        break;
+      end;
+    end
+    else fTokStop += 1;
+  end;
+  if firstLine and not terminate then
+    fCurrRange.rangeKind := TRangeKind.rkBlockCom1
+  else if (fCurrRange.rangeKind = TRangeKind.rkBlockCom1) and terminate then
+    fCurrRange.rangeKind := TRangeKind.rkNone;
+end;
+
+procedure TSynSxSyn.lexIdentifier();
+var
+  dollar: boolean;
+  oneChr: boolean = false;
+begin
+  fTokKind  := TTokenKind.tkIdent;
+  dollar    := fLineBuf[fTokStop] = '$';
+  if dollar then
+    fTokStop += 1;
+  while fTokStop <= fLineBuf.length do
+  begin
+    case fLineBuf[fTokStop] of
+      '_', 'a'..'z', 'A'..'Z':
+      begin
+        oneChr    := true;
+        fTokStop  += 1;
+        continue;
+      end;
+      '0' .. '9':
+      begin
+        if oneChr then
+        begin
+          fTokStop  += 1;
+          continue;
+        end
+        else break; // e.g $0
+      end
+      else break;
+    end;
+  end;
+  if dollar and not oneChr then
+    fTokKind := TTokenKind.tkError
+  else
+  begin
+    if KeywordMatch.contains(GetToken()) then
+      fTokKind := TTokenKind.tkKeywd;
+  end;
+end;
+
+procedure TSynSxSyn.next;
+var
+  llen: integer;
+  nextPChar: PChar;
+  nextChar: char;
+begin
+  fTokKind  := tkNone;
+  fTokStart := fTokStop;
+  llen := length(fLineBuf);
+  // EOL
+  if fTokStop > llen then
+    exit;
+  if fCurrRange.isNotAssigned then
+    fCurrRange := TSynSxSynRange.Create(nil);
+
+  if (fLineBuf.length > 1) then
+  begin
+    // she bang
+    if (fLineNum = 0) and (fLineBuf[1..2] = '#!') then
+    begin
+      lexLineComment();
+      exit;
+    end
+    // "ยง" of SAR
+    else if (fTokStart = 1) and (fLineBuf[1..2] = #194#167) then
+    begin
+      lexLineComment();
+      exit;
+    end;
+  end;
+
+  case fCurrRange.rangeKind of
+    TRangeKind.rkString1: begin lexStringLiteral(); exit; end;
+    TRangeKind.rkString2: begin lexRawStringLiteral(); exit; end;
+    TRangeKind.rkBlockCom1: begin lexStarComment(); exit; end;
+  end;
+
+  case fLineBuf[fTokStop] of
+    #0 .. #10, #13, #32:
+    begin
+      fTokStop += 1;
+      fTokKind := TTokenKind.tkBlank;
+      exit;
+    end;
+    // `//comment` `/*comment` `/=` `/`
+    '/':
+    begin
+      fTokKind := TTokenKind.tkSymbl;
+      fTokStop += 1;
+      if (fTokStop <= llen) then
+        case fLineBuf[fTokStop] of
+        '/' : lexLineComment();
+        '*' : begin fTokStop  += 1; lexStarComment(); end;
+        '=' : fTokStop += 1;
+      end;
+    end;
+    // `_ident`  `$kwIdent`
+    'a'..'z', 'A'..'Z', '_', '$': lexIdentifier();
+    // `0x...` `0b...` `012...` `0.12...` `012...`
+    '0':
+    begin
+      nextPChar := safeLookupChar();
+      if nextPChar <> nil then
+      begin
+        nextChar := nextPChar^;
+        if (nextChar = 'x') or (nextChar = 'X') then
+          lexHexLiteral()
+        else if (nextChar = 'b') or (nextChar = 'B') then
+          lexBinLiteral()
+        else lexIntLiteral();
+      end
+      else lexIntLiteral();
+    end;
+    // number
+    '1' .. '9' : lexIntLiteral();
+    // "string"
+    '"': lexStringLiteral();
+    // `string`
+    '`': lexRawStringLiteral();
+    // `-` `-=` `--`
+    '-': lexOpAndOpOpAndOpEqual('-');
+    // `&` `&=` `&&`
+    '&': lexOpAndOpOpAndOpEqual('&');
+    // `|` `|=` `||`
+    '|': lexOpAndOpOpAndOpEqual('|');
+    // `+` `+=` `++`
+    '+': lexOpAndOpOpAndOpEqual('+');
+    // `*` `*=` `%` `%=` `^` `^=` `~` `~=`  `!` `!=`
+    '*', '%', '^', '!', '~': lexOpAndOpEqual();
+    // `<` `<<` `<=` `<<=` `>` `>>` `>=` `>>=`
+    '<': lexOpAndOpOpAndOpEqualAndOpOpEqual('<');
+    '>': lexOpAndOpOpAndOpEqualAndOpOpEqual('>');
+    // `=`, `==`, `=>`
+    '=': lexAssEquOrLambda();
+    '.', '(', ')', ',', ':' , '[', ']', '?', ';' :
+    begin
+      fTokKind  := TTokenKind.tkSymbl;
+      fTokStop  += 1;
+    end;
+    '@':
+    begin
+      fTokKind := TTokenKind.tkError;
+      fTokStop  += 1;
+      if fTokStop <= llen then
+      begin
+        lexIdentifier();
+        fTokKind := TTokenKind.tkAttri;
+      end;
+    end;
+    '{':
+    begin
+      fTokKind  := TTokenKind.tkSymbl;
+      fTokStop  += 1;
+      StartCodeFoldBlock();
+    end;
+    '}':
+    begin
+      fTokKind  := TTokenKind.tkSymbl;
+      fTokStop  += 1;
+      EndCodeFoldBlock();
+    end
+    else begin
+      fTokStop  += 1;
+      fTokKind  := TTokenKind.tkError;
+      exit;
+    end;
+  end;
+end;
+
+end.
+
diff --git a/src/u_synmemo.pas b/src/u_synmemo.pas
index d6da536f..9d136a90 100644
--- a/src/u_synmemo.pas
+++ b/src/u_synmemo.pas
@@ -13,7 +13,7 @@ uses
   SynGutterBase, LCLVersion,
   //SynEditMarkupFoldColoring,
   Clipbrd, fpjson, jsonparser, LazUTF8, LazUTF8Classes, Buttons, StdCtrls,
-  u_common, u_writableComponent, u_d2syn, u_txtsyn, u_dialogs,
+  u_common, u_writableComponent, u_d2syn, u_txtsyn, u_dialogs, u_sxsyn,
   u_sharedres, u_dlang, u_stringrange, u_dbgitf, u_observer, u_diff,
   u_processes, u_synmultiguttermarks;
 
@@ -222,6 +222,7 @@ type
     fD2Highlighter: TSynD2Syn;
     fTxtHighlighter: TSynTxtSyn;
     fCppHighlighter: TSynCppSyn;
+    fSxHighlighter: TSynSxSyn;
     fImages: TImageList;
     fMatchSelectionOpts: TSynSearchOptions;
     fMatchIdentOpts: TSynSearchOptions;
@@ -402,6 +403,7 @@ type
     property D2Highlighter: TSynD2Syn read fD2Highlighter;
     property TxtHighlighter: TSynTxtSyn read fTxtHighlighter;
     property CppHighlighter: TSynCppSyn read fCppHighlighter;
+    property SxHighlighter: TSynSxSyn read fSxHighlighter;
     property defaultFontSize: Integer read fDefaultFontSize write setDefaultFontSize;
     property ddocDelay: Integer read fDDocDelay write setDDocDelay;
     property autoDotDelay: Integer read fAutoDotDelay write setAutoDotDelay;
@@ -1162,6 +1164,7 @@ begin
   fD2Highlighter := TSynD2Syn.create(self);
   fTxtHighlighter := TSynTxtSyn.Create(self);
   fCppHighlighter := TSynCppSyn.Create(self);
+  fSxHighlighter := TSynSxSyn.Create(self);
   Highlighter := fD2Highlighter;
 
   fTempFileName := GetTempDir(false) + 'temp_' + uniqueObjStr(self) + '.d';
@@ -3350,14 +3353,18 @@ procedure TDexedMemo.loadFromFile(const fname: string);
 var
   e: string;
   c: boolean;
+  s: boolean;
 begin
   e := fname.extractFileExt;
   fIsDsource := hasDlangSyntax(e);
   c := hasCppSyntax(e);
+  s := hasSxSyntax(e);
   if not fIsDsource then
   begin
     if c then
       Highlighter := CppHighlighter
+    else if s then
+      Highlighter := SxHighlighter
     else
       Highlighter := TxtSyn;
   end;
@@ -3399,6 +3406,8 @@ begin
   begin
     if hasCppSyntax(ext) then
       Highlighter := CppHighlighter
+    else if hasSxSyntax(ext) then
+      Highlighter := SxHighlighter
     else
       Highlighter := TxtHighlighter;
   end;