{
 *****************************************************************************
  This file is part of the LazEdit package from the Lazarus IDE.

  This content of this file is licensensed: Modified LGPL-2
  Or at the users choice: Modified LGPL-3
  See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
  for details about the license.

  Alternatively, the contents of this file may be used under the terms of the
  Mozilla Public License Version 1.1 http://www.mozilla.org/MPL/

  A copy used under either License can have the other Licenses removed from this
  header. A note should be added that the original file is available with the
  above choice of License.
 *****************************************************************************

  Written by Martin Friebe 2023
}

unit TextMateGrammar;

{$mode objfpc}{$H+}
{ $DEFINE TMATE_MATCHING}

interface

uses
  Classes, SysUtils, fgl, fpjson, jsonparser, jsonscanner, Math,
  // LazUtils
  Laz2_DOM, Laz2_XMLRead, PList2JSon,
  //LazLoggerBase,
  LazLoggerDummy, LazClasses,
  // LazEdit
  xregexpr;

type

  { TTextMateGrammarException }

  TTextMateGrammarException = class(Exception)
  private
    FLocation: String;
  public
    constructor Create(const msg: string);
    constructor Create(const msg, JsKey: string);
    constructor Create(const msg: string; JsIndex: integer);
    function Copy: TTextMateGrammarException;
    function AddLocation(JsKey: string): TTextMateGrammarException;
    function AddLocation(JsIndex: integer): TTextMateGrammarException;
    function ErrorLocation: String;
    function FullError: String;
  end;

  TTextMatePattern = class;
  TTextMatePatternClass = class of TTextMatePattern;
  TTextMatePatternList  = specialize TFPGObjectList<TTextMatePattern>;
  TTextMatePatternMap   = specialize TFPGMapObject<string, TTextMatePattern>;
  TTextMatePatternArray = array of TTextMatePattern;
  TTextMatePatternBaseNested = class;
  TTextMatePatternForwarderNewEnd = class;
  TTextMateGrammar = class;

  TSynAttributeInfo = record
    TokId: Integer;
    TokObject: TObject;
  end;

  TPopulateAttributeInfoProc = procedure (Sender: TTextMateGrammar; APattern: TTextMatePattern;
    AContextName: String; var AnAttribInfo: TSynAttributeInfo) of object;
  TCheckAttributeInfoProc = procedure (Sender: TTextMatePattern;
    const AnAttribInfo: TSynAttributeInfo;
    out AnUseId, AnUseObject: Boolean) of object;
  TGetIncludedGrammar = function(Sender: TTextMateGrammar; AScopeName: string): TTextMateGrammar of object;


  TTextMateFoundCaptureBounds = record
    Start, Len: integer;
  end;
  TTextMateFoundCaptureBoundsArray = array of TTextMateFoundCaptureBounds;

  TTextMateMatchInfo = record
    Start: Integer;
    Pattern: TTextMatePattern;
    Index: Integer;
  end;

  TTextMatePatternStateFlag = (
    psfNestedinCapture,
    psfMatchBeginInitialized,
    psfMatchBeginDone,
    psfMatchEndInitialized,
    psfWhileInBeginLine,
    psfWhileDone
  );
  TTextMatePatternStateFlags = set of TTextMatePatternStateFlag;

  { TTextMatePatternStateEntry }

  TTextMatePatternStateEntry = object
    Grammar: TTextMateGrammar;
    Pattern: TTextMatePattern;
    PatternIndex: integer;
    SubTextBeginPos, SubTextLen: integer; // begin is always 1 // if otherwise then ATextOffset needs adjustment
    CurrentTokenAttribInfo: TSynAttributeInfo;
    Flags: TTextMatePatternStateFlags;
    FoundCaptures: TTextMateFoundCaptureBoundsArray;

    CachedMatchInfo: TTextMateMatchInfo;
    CachedCaptureLen: Integer;

    procedure SetCache(AMatchInfo: TTextMateMatchInfo; ACaptureLen: Integer); inline;
    function  GetCache(out AMatchInfo: TTextMateMatchInfo; out ACaptureLen: Integer): boolean; inline;
    procedure ClearCache; inline;
  end;
  PTextMatePatternStateEntry = ^TTextMatePatternStateEntry;
  TTextMatePatternStateArray = array of TTextMatePatternStateEntry;

  { TTextMatePatternState }

  TTextMatePatternState = object
  private
    function GetEntryP(ADepth: integer): PTextMatePatternStateEntry; inline;
    function GetParent(ADepth: integer): TTextMatePattern;  inline;
    function GetPattern(ADepth: integer): TTextMatePattern; inline;
  public
    Grammar: TTextMateGrammar;
    StateList: TTextMatePatternStateArray;
    StateIdx: integer;

    procedure ValidateParentChain(AGrammar: TTextMateGrammar);
    procedure InitForDepth(ANewDepth: integer);
    procedure Add(const AMatchInfo: TTextMateMatchInfo; const ASubTextBeginPos, ASubTextLen: integer; const AFlags: TTextMatePatternStateFlags = []);  inline;
    procedure Pop;  inline;
    procedure ClearRecurseData;
    procedure CallParentNextToken(const AText: String; ACurTokenPos: integer;
      var ANextTokenPos: integer; // May be set by caller, so it can be kept unchanged if StateIdx = 0
      AnInitInfoOnly: Boolean = False); inline;
    property Parent[ADepth: integer]: TTextMatePattern read GetParent;
    property Pattern[ADepth: integer]: TTextMatePattern read GetPattern;
    property EntryP[ADepth: integer]: PTextMatePatternStateEntry read GetEntryP; // valid only until next add/pop
  end;

  { TTextMatePattern }

  TTextMatePattern = class
  private
    FMainIndex: integer; // used as RANGE in SynEdit
    FComment: String;
    FName: String;
    FDebugName: String;
    FAttribInfo: TSynAttributeInfo; // Info for SynEdit
    FRefCount: Integer;
  protected
    // detect recursions that do not move forward
    FDeepestRecurseTextAtOffset: Integer;
    FDeepestRecurseWasInit: Boolean;
    function IsEndlessRecursion(ACurTokenPos: integer; AnInitInfoOnly: Boolean): boolean; inline;
    procedure ClearDeepestRecurseData; virtual;

  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); virtual;
    procedure InitStateAfterAdded(AStateEntry: PTextMatePatternStateEntry); virtual;
    procedure GetCurrentTokenInfo(const AStates: TTextMatePatternState;
      const ACheckProc: TCheckAttributeInfoProc;
      out ATokenKind: integer; out AnAttr: TObject;
      ADepth: Integer
      ); virtual;
    procedure InitFoundCaptures(AStateEntryP: PTextMatePatternStateEntry; ARegEx: TRegExpr); //inline; // BUG in fpc

    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; virtual;

  protected
    function  GetForwardTarget: TTextMatePatternBaseNested; virtual;
    procedure FlattenNested(AGrammar: TTextMateGrammar; ARemoveMissingIncludes: boolean); virtual;
    class function GetCopyFor(AnOther:TTextMatePattern; AnIndexOffset: Integer; ANewList: TTextMatePatternList): TTextMatePattern;
    procedure CopyFrom(AnOther:TTextMatePattern; AnIndexOffset: Integer; ANewList: TTextMatePatternList); virtual;
    procedure DoInitRegex(var ARegEx: TRegExpr; const AText: string; AnErrKey: String);
  public
    procedure InitRegEx; virtual;
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; const AText: String); inline;
    procedure GetCurrentTokenInfo(const AStates: TTextMatePatternState;
      const ACheckProc: TCheckAttributeInfoProc;
      out ATokenKind: integer; out AnAttr: TObject); inline;

    procedure NextToken(var AStates: TTextMatePatternState; const AText: String;
      ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); virtual;
    procedure IncRefCount; virtual;
    procedure DecRefCount; virtual;
  public
    function DebugName: string; virtual;
    function DebugDump(AnIndent: Integer = 2; AnIncludeNested: Boolean = True; APrefix: string = ''): string; virtual;
    property ForwardTarget: TTextMatePatternBaseNested read GetForwardTarget;

    property Index: integer read FMainIndex;
    property Name: String read FName;
    property Comment: String read FComment;
    property AttribInfo: TSynAttributeInfo read FAttribInfo;
  end;

  { TTextMatePatternBaseNested }

  TTextMatePatternBaseNested = class(TTextMatePattern)
  private
    FParent: TTextMatePattern; // TTextMatePatternState.Parent takes precedence
    FRecurseMatchPos: integer;
    Patterns, UnFlatPatterns: TTextMatePatternArray;
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState;
      AParent: TTextMatePattern; const AText: String; ADepth: Integer); override;
    function FindPatternForNextMatchPos( // left-most matchg pos
      const AText: String; ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out AMatchInfo: TTextMateMatchInfo;
      AMatchMustStartBefore: integer = 0): boolean;
    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; override;

    function GetForwardTarget: TTextMatePatternBaseNested; override;
    procedure FlattenNested(AGrammar: TTextMateGrammar; ARemoveMissingIncludes: boolean); override;
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  public
    procedure NextToken(var AStates: TTextMatePatternState; const AText: String;
      ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); override;

    procedure IncRefCount; override;
    procedure DecRefCount; override;
  public
    function DebugDump(AnIndent: Integer = 2; AnIncludeNested: Boolean = True; APrefix: string = ''): string; override;
  end;

  { TTextMatePatternNested }

  TTextMatePatternNested = class(TTextMatePatternBaseNested)
  end;

  { TTextMatePatternNestedList }

  TTextMatePatternNestedList = class(TTextMatePatternNested)
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar;
      var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    procedure FlattenNested(AGrammar: TTextMateGrammar; ARemoveMissingIncludes: boolean); override;
  end;

  //TTextMatePatternInclude = class(TTextMatePatternNestedList)
  TTextMatePatternInclude = class(TTextMatePattern)
  protected
    FSourceScope, FSourceKey: String;
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  protected
    function GetFirstMatchPos(const AText: String;
      const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry; out APattern: TTextMatePattern;
      out AFoundStartPos: integer; AMatchMustStartBefore: integer = 0
      ): Boolean; override;
  end;

  { TTextMatePatternCapture }

  TTextMatePatternCapture = class(TTextMatePatternBaseNested)
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    procedure InitStateAfterAdded(AStateEntry: PTextMatePatternStateEntry); override;
  end;

  TTextMatePatternCaptureArray = array of TTextMatePatternCapture;

  { TTextMatePatternCaptures }

  TTextMatePatternCaptures = object
    CaptureArray: TTextMatePatternCaptureArray; // may contain nil entries
    function FindCaptureForNextMatchPos( // left-most matchg pos
      ATextStartOffset: integer;
      const AFoundCapturePosList: TTextMateFoundCaptureBoundsArray;
      out ACaptureInfo: TTextMateMatchInfo): boolean;
    procedure CopyFrom(AnOther: TTextMatePatternCaptures; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList);
  end;

  { TTextMatePatternMatch }

  TTextMatePatternMatch = class(TTextMatePattern)
  private
    FRegExMatch: TRegExpr;
    Match: String;
    Captures: TTextMatePatternCaptures;
  protected
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; override;
  public
    destructor Destroy; override;
    procedure InitRegEx; override;
    procedure NextToken(var AStates: TTextMatePatternState; const AText: String;
      ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); override;
  end;

  { TTextMatePatternBeginEnd }

  TTextMatePatternBeginEnd = class(TTextMatePatternBaseNested)
  private
    FRegExMatchBegin, FRegExMatchEnd: TRegExpr;
    FEndHasBackRef: Boolean;
    FEndForwarders: array of TTextMatePatternForwarderNewEnd;
    FEndForwarderCnt: integer;
    FRecursiveFirstMatchPos: integer;
    MatchBegin, MatchEnd: String;
    ContentName: String;
    ContentAttribInfo: TSynAttributeInfo;
    EndPatternLast: Boolean;
    Captures, CapturesBegin, CapturesEnd: TTextMatePatternCaptures;
  protected
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; override;
  public
    destructor Destroy; override;
    procedure InitRegEx; override;
    procedure NextToken(var AStates: TTextMatePatternState; const AText: String;
      ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); override;
  end;

  { TTextMatePatternBeginWhile }

  TTextMatePatternBeginWhile = class(TTextMatePatternBaseNested)
  private
    FRegExMatchBegin, FRegExMatchWhile: TRegExpr;
    MatchBegin, MatchWhile: String;
    ContentName: String;
    ContentAttribInfo: TSynAttributeInfo;
    Captures, CapturesBegin, CapturesWhile: TTextMatePatternCaptures;
  protected
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  protected
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    procedure InitStateAfterAdded(AStateEntry: PTextMatePatternStateEntry); override;
    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; override;
  public
    destructor Destroy; override;
    procedure InitRegEx; override;
    procedure NextToken(var AStates: TTextMatePatternState;
      const AText: String; ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); override;
  end;

  { TTextMatePatternRoot }

  TTextMatePatternRoot = class(TTextMatePatternNested)
  protected
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
  public
    ScopeName: String;
  end;

  { TTextMatePatternForwarder }

  TTextMatePatternForwarder = class(TTextMatePatternBaseNested)
  private
    FForwardTo: TTextMatePatternBaseNested;
  protected
    procedure CopyFrom(AnOther: TTextMatePattern; AnIndexOffset: Integer;
      ANewList: TTextMatePatternList); override;
  protected
    procedure ClearDeepestRecurseData; override;
    function  GetForwardTarget: TTextMatePatternBaseNested; override;
    procedure InitStates(const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState; AParent: TTextMatePattern;
      const AText: String; ADepth: Integer); override;
    function GetFirstMatchPos(const AText: String; const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry;
      out APattern: TTextMatePattern; out AFoundStartPos: integer;
      AMatchMustStartBefore: integer = 0): Boolean; override;
    procedure CopyPatterns(const ASrc: TTextMatePatternArray);
  public
    procedure NextToken(var AStates: TTextMatePatternState;
      const AText: String; ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False); override;
    property ForwardTo: TTextMatePatternBaseNested read FForwardTo;

    function DebugDump(AnIndent: Integer = 2; AnIncludeNested: Boolean = True; APrefix: string = ''): string; override;
  end;

  { TTextMatePatternForwarderNewEnd }

  TTextMatePatternForwarderNewEnd = class(TTextMatePatternForwarder)
  private
    FRegExMatchEnd: TRegExpr;
  protected
    function GetFirstMatchPos(const AText: String;
      const ATextStartOffset: integer;
      AStateEntryP: PTextMatePatternStateEntry; out APattern: TTextMatePattern;
      out AFoundStartPos: integer; AMatchMustStartBefore: integer = 0
      ): Boolean; override;
  public
    destructor Destroy; override;
    procedure NextToken(var AStates: TTextMatePatternState;
      const AText: String; ACurTokenPos: integer; out ANextTokenPos: integer;
      AnInitInfoOnly: Boolean = False; AnIsCalledAsParent: Boolean = False);
      override;
  end;


  { TTextMateGrammar }

  TTextMateGrammar = class(TFreeNotifyingObject)
  private type
    TOtherGrammarMap = specialize TFPGMapObject<string, TTextMateGrammar>;
  private
    FOnCheckAttributeInfo: TCheckAttributeInfoProc;
    FOnPopulateAttributeInfo: TPopulateAttributeInfoProc;
    FParserError: String;
    FSampleText: String;
    FSampleTextFile: String;
    FLangName: string;

    FRegExMatchFoldBegin, FRegExMatchFoldEnd: TRegExpr;
    MatchFoldBegin, MatchFoldEnd: String;


    FMainPatternCount: integer;
    FMainPatternList: TTextMatePatternList;
    FPatternRepo: TTextMatePatternMap;
    FRootPattern: TTextMatePatternRoot;
    FTheEmptyPattern: TTextMatePattern;
    FOtherGrammars: TOtherGrammarMap;


    procedure ClearAttributeInfo(var AnAttribInfo: TSynAttributeInfo);
    function  CreatePatternObject(AClass: TTextMatePatternClass; InitRefCnt: Integer = 1): TTextMatePattern;
    procedure DoOtherGrammarFreed(Sender: TObject);

    function  GetJson(AGrammarDef: String): TJSONObject;
    procedure ParseLanguage(ALangDef: TJSONData);
    function  IsInclude(APatternJson: TJSONObject): boolean;
    function  IncludeName(APatternJson: TJSONObject; out AnOtherScopeName: string): string;
    function  ResolveInclude(APatternJson: TJSONObject): TTextMatePattern;
    procedure CopyPatterns(AnOtherGrammar: TTextMateGrammar; AnIncludeName: String);
    function  CreatePattern(AParent: TTextMatePattern; APatternJson: TJSONObject; AllowPatternOnly: Boolean = False): TTextMatePattern;
    procedure ReadPattern(APattern: TTextMatePattern; APatternJson: TJSONObject);
    function  ParsePattern(AParent: TTextMatePattern; APatternJson: TJSONObject): TTextMatePattern;
    procedure ParsePatterns(AParent: TTextMatePattern; var APatternArray: TTextMatePatternArray; APatterns: TJSONArray);

  private
    FCurrentPattern: TTextMatePattern;
    FCurrentState: TTextMatePatternState;
    FLanguageScopeName: String;
    FLineText: String;
    FCurrentTokenPos, FNextTokenPos: Integer;
    FMissingIncludes: String;
    FCurrentTokenKind: integer;
    FCurrentAttrib: TObject;
    FOnGetIncludedGrammar: TGetIncludedGrammar;
    function GetCurrentPatternIndex: Integer;
    function GetCurrentTokenLen: integer; inline;

  public
    constructor Create;
    destructor Destroy; override;
    procedure ParseGrammar(AGrammarDef: String);
    // ResolveExternalIncludes: Call only what all other grammars have done the initial ParseGrammar
    procedure ResolveExternalIncludes;
    procedure ClearGrammar;
    function DebugDump(AnIndent: Integer = 2; AnIncludeNested: Boolean = True): string;

    procedure SetLine(const AText: String; const AnInitialPatternIndex: Integer);
    procedure First;
    procedure Next;
    function  IsAtEol: boolean;
    procedure NextToEol;
    function  IsFoldBegin: boolean;
    function  IsFoldEnd: boolean;

    property CurrentTokenPos: integer read FCurrentTokenPos;
    property NextTokenPos: integer read FNextTokenPos;
    property CurrentTokenLen: integer read GetCurrentTokenLen;
    property CurrentTokenKind: integer read FCurrentTokenKind;
    property CurrentAttrib: TObject read FCurrentAttrib;
    property CurrentPatternIndex: Integer read GetCurrentPatternIndex;

    property CurrentState: TTextMatePatternState read FCurrentState;

    property LanguageName: String read FLangName write FLangName;
    property LanguageScopeName: String read FLanguageScopeName;
    property SampleText: String read FSampleText write FSampleText;
    property SampleTextFile: String read FSampleTextFile;
    property ParserError: String read FParserError;
    property MissingIncludes: String read FMissingIncludes;

    property RootPattern: TTextMatePatternRoot read FRootPattern;
    property MainPatternList: TTextMatePatternList read FMainPatternList;

    property OnPopulateAttributeInfo: TPopulateAttributeInfoProc read FOnPopulateAttributeInfo write FOnPopulateAttributeInfo;
    property OnCheckAttributeInfo: TCheckAttributeInfoProc read FOnCheckAttributeInfo write FOnCheckAttributeInfo;
    property OnGetIncludedGrammar: TGetIncludedGrammar read FOnGetIncludedGrammar write FOnGetIncludedGrammar;
  end;

implementation

function LeftmostFoundPattern(var Info1: TTextMateMatchInfo; const Info2: TTextMateMatchInfo): boolean; inline;
begin
  Result := Info1.Index >= 0;
  if Info2.Index >= 0 then begin
    if (not Result) or (Info2.Start < Info1.Start) then begin
      Info1 := Info2;
      Result := Info1.Index >= 0;
    end;
  end;
end;

procedure AssertJsObject(AJSon: TJSONData); inline;
begin
  if (AJSon = nil) or (not (AJSon is TJSONObject)) then
    raise TTextMateGrammarException.Create('Expected Js-Object');
end;

procedure AssertJsArray(AJSon: TJSONData); inline;
begin
  if (AJSon = nil) or (not (AJSon is TJSONArray)) then
    raise TTextMateGrammarException.Create('Expected Js-Array');
end;

function jsAsDict(AJSon: TJSONData): TJSONObject; inline;
begin
  if AJSon = nil then exit(nil);
  if not (AJSon is TJSONObject) then
    raise TTextMateGrammarException.Create('Expected Js-Object');
  Result := TJSONObject(AJSon);
end;

function jsAsArray(AJSon: TJSONData): TJSONArray; inline;
begin
  if AJSon = nil then exit(nil);
  if not (AJSon is TJSONArray) then
    raise TTextMateGrammarException.Create('Expected Js-Array');
  Result := TJSONArray(AJSon);
end;

function jsKeyAsDict(AJSon: TJSONData; AKey: String): TJSONObject;
begin
  AssertJsObject(AJSon);
  Result := jsAsDict(TJSONObject(AJSon)[AKey]);
end;

function jsKeyAsArray(AJSon: TJSONData; AKey: String): TJSONArray;
begin
  AssertJsObject(AJSon);
  Result := jsAsArray(TJSONObject(AJSon)[AKey]);
end;

function jsIndexAsDict(AJSon: TJSONData; AnIdx: Integer): TJSONObject;
begin
  AssertJsArray(AJSon);
  Result := jsAsDict(TJSONArray(AJSon)[AnIdx]);
end;

function jsKeyAsString(AJSon: TJSONData; AKey: String; AllowNil: Boolean = True): string;
var
  R: TJSONObject;
  R2: TJSONData;
begin
  AssertJsObject(AJSon);
  R := TJSONObject(AJSon);
  if AllowNil and (R.IndexOfName(AKey) < 0) then
    exit('');
  R2 := R[AKey];
  if not (R2 is TJSONString) then
    raise TTextMateGrammarException.Create('Expected String', AKey);
  Result := R2.AsString;
end;

function jsKeyAsNumber(AJSon: TJSONData; AKey: String; AllowNil: Boolean = True): integer;
var
  R: TJSONObject;
  R2: TJSONData;
begin
  AssertJsObject(AJSon);
  R := TJSONObject(AJSon);
  if AllowNil and (R.IndexOfName(AKey) < 0) then
    exit(0);
  R2 := R[AKey];
  if not (R2 is TJSONIntegerNumber) then
    raise TTextMateGrammarException.Create('Expected Number', AKey);
  Result := R2.AsInteger;
end;

{ TTextMateGrammarException }

constructor TTextMateGrammarException.Create(const msg: string);
begin
  inherited Create(msg);
end;

constructor TTextMateGrammarException.Create(const msg, JsKey: string);
begin
  inherited Create(msg);
  FLocation := '.' + JsKey;
end;

constructor TTextMateGrammarException.Create(const msg: string; JsIndex: integer);
begin
  inherited Create(msg);
  FLocation := '[' + IntToStr(JsIndex) + ']';
end;

function TTextMateGrammarException.Copy: TTextMateGrammarException;
begin
  Result := TTextMateGrammarException.Create(Message);
  Result.FLocation := FLocation;
end;

function TTextMateGrammarException.AddLocation(JsKey: string
  ): TTextMateGrammarException;
begin
  FLocation := '.' + JsKey + FLocation;
  Result := Self;
end;

function TTextMateGrammarException.AddLocation(JsIndex: integer
  ): TTextMateGrammarException;
begin
  FLocation := '[' + IntToStr(JsIndex) + ']' + FLocation;
  Result := Self;
end;

function TTextMateGrammarException.ErrorLocation: String;
begin
  Result := FLocation;
  if (Result <> '') and (Result[1] = '.') then
    Delete(Result, 1, 1);
end;

function TTextMateGrammarException.FullError: String;
begin
  Result := Message + ' at ' + ErrorLocation;
end;

{ TTextMatePatternStateEntry }

procedure TTextMatePatternStateEntry.SetCache(AMatchInfo: TTextMateMatchInfo;
  ACaptureLen: Integer);
begin
  {$IFDEF TMATE_MATCHING} debugln(['SET cache >>> Pos:', AMatchInfo.Start, ' Idx:', AMatchInfo.Index, ' C-Len:', ACaptureLen]); {$ENDIF}
  CachedMatchInfo  := AMatchInfo;
  CachedCaptureLen := ACaptureLen;
end;

function TTextMatePatternStateEntry.GetCache(out
  AMatchInfo: TTextMateMatchInfo; out ACaptureLen: Integer): boolean;
begin
  Result := CachedMatchInfo.Start > 0;
  {$IFDEF TMATE_MATCHING}debugln(Result, ['Got cache at ', CachedMatchInfo.Start]);{$ENDIF}
  AMatchInfo  := CachedMatchInfo;
  ACaptureLen := CachedCaptureLen;
  CachedMatchInfo.Start  := 0;
end;

procedure TTextMatePatternStateEntry.ClearCache;
begin
  CachedMatchInfo.Start  := 0;
end;

{ TTextMatePatternState }

function TTextMatePatternState.GetEntryP(ADepth: integer
  ): PTextMatePatternStateEntry;
begin
  Result := @StateList[StateIdx - ADepth];
end;

function TTextMatePatternState.GetParent(ADepth: integer
  ): TTextMatePattern;
begin
  if ADepth < StateIdx then
    Result := StateList[StateIdx - (ADepth + 1)].Pattern
  else
    Result := nil;
end;

function TTextMatePatternState.GetPattern(ADepth: integer): TTextMatePattern;
begin
  Result := StateList[StateIdx - ADepth].Pattern;
end;

procedure TTextMatePatternState.ValidateParentChain(AGrammar: TTextMateGrammar);
var
  i, j: Integer;
  NewFwrd: TTextMatePatternForwarder;
  Par, Cur, Cur2: TTextMatePatternBaseNested;
begin
  for i := 1 to StateIdx do begin
    assert(StateList[i].Pattern is TTextMatePatternBaseNested, 'TTextMatePatternState.ValidateParentChain: StateList[i].Pattern is TTextMatePatternBaseNested');
    Cur := TTextMatePatternBaseNested(StateList[i].Pattern);
    if Cur.FParent = StateList[i-1].Pattern then
      Continue;
    if Cur.FParent = nil then begin
      Cur.FParent := StateList[i-1].Pattern;
      Continue;
    end;

    Par := TTextMatePatternBaseNested(StateList[i-1].Pattern);
    Cur2 := Cur.GetForwardTarget;

    NewFwrd := TTextMatePatternForwarder(AGrammar.CreatePatternObject(TTextMatePatternForwarder));
    NewFwrd.CopyPatterns(Cur2.Patterns);

    NewFwrd.FParent := Par;
    NewFwrd.FForwardTo := Cur2;
    StateList[i].Pattern := NewFwrd;

    j := StateList[i].PatternIndex;
    assert((j>=0) and (j < length(Par.Patterns)), 'TTextMatePatternState.ValidateParentChain: (j>=0) and (j < length(Par.Patterns))');
    assert(Par.Patterns[j].GetForwardTarget = Cur2, 'TTextMatePatternState.ValidateParentChain: Par.Patterns[j].GetForwardTarget = Cur2');
    Par.Patterns[j] := NewFwrd;
  end;
end;

procedure TTextMatePatternState.InitForDepth(ANewDepth: integer);
var
  i: Integer;
begin
  if (Length(StateList) < ANewDepth) or (Length(StateList) > ANewDepth + 256) then
    SetLength(StateList, ANewDepth + 16);
  StateIdx := ANewDepth - 1; // for the top caller
  for i := 0 to ANewDepth - 1 do
    StateList[i].Grammar := Grammar;
end;

procedure TTextMatePatternState.Add(const AMatchInfo: TTextMateMatchInfo;
  const ASubTextBeginPos, ASubTextLen: integer;
  const AFlags: TTextMatePatternStateFlags);
begin
  {$IFDEF TMATE_MATCHING}
  DebugLn(['TTextMatePatternState.Add >>> OLD idx=', StateIdx, ' ADD ',ASubTextBeginPos,'..',ASubTextLen]);
  {$ENDIF}
  inc(StateIdx);
  if StateIdx >= Length(StateList) then
    SetLength(StateList, StateIdx + 16);
  StateList[StateIdx].Pattern := AMatchInfo.Pattern;
  StateList[StateIdx].PatternIndex := AMatchInfo.Index;
  StateList[StateIdx].SubTextBeginPos := ASubTextBeginPos;
  StateList[StateIdx].SubTextLen := ASubTextLen;
  StateList[StateIdx].CurrentTokenAttribInfo.TokId := -1;
  StateList[StateIdx].CurrentTokenAttribInfo.TokObject := nil;
  StateList[StateIdx].Flags := AFlags;
  StateList[StateIdx].ClearCache;
  StateList[StateIdx].Grammar := Grammar;
  AMatchInfo.Pattern.InitStateAfterAdded(@StateList[StateIdx]);
end;

procedure TTextMatePatternState.Pop;
begin
  StateList[StateIdx].Pattern.ClearDeepestRecurseData;
  dec(StateIdx);
end;

procedure TTextMatePatternState.ClearRecurseData;
var
  i: Integer;
begin
  for i := 0 to min(StateIdx, High(StateList)) do begin
    StateList[i].ClearCache;
    StateList[i].Pattern.ClearDeepestRecurseData;
  end;
end;

procedure TTextMatePatternState.CallParentNextToken(const AText: String;
  ACurTokenPos: integer; var ANextTokenPos: integer; AnInitInfoOnly: Boolean);
var
  st: PTextMatePatternStateEntry;
begin
  {$IFDEF TMATE_MATCHING}
  debugln(['CallParentNextToken <<< for "'+StateList[StateIdx].Pattern.ClassName+'"  | Depth', StateIdx, ', CurPos:',ACurTokenPos, ', NExtPos ', ANextTokenPos, ' ',AnInitInfoOnly]);
  {$ENDIF}
  if StateIdx = 0 then
    exit;
  Pop;
  st := @StateList[StateIdx];
  st^.Pattern.NextToken(Self, AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly, True);
end;

{ TTextMatePattern }

procedure TTextMatePattern.InitRegEx;
begin
  //
end;

procedure TTextMatePattern.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; const AText: String);
begin
  InitStates(AGrammar, AStates, nil, AText, 0);
end;

procedure TTextMatePattern.GetCurrentTokenInfo(
  const AStates: TTextMatePatternState;
  const ACheckProc: TCheckAttributeInfoProc; out ATokenKind: integer; out
  AnAttr: TObject);
begin
  GetCurrentTokenInfo(AStates, ACheckProc, ATokenKind, AnAttr, 0);
end;

procedure TTextMatePattern.NextToken(var AStates: TTextMatePatternState;
  const AText: String; ACurTokenPos: integer; out ANextTokenPos: integer;
  AnInitInfoOnly: Boolean; AnIsCalledAsParent: Boolean);
begin
  ANextTokenPos := Length(AText);
  assert(False, 'TTextMatePattern.NextToken: False');
  if AStates.Grammar = nil then;
  if ACurTokenPos=0 then ;
  if AnInitInfoOnly then ;
  if AnIsCalledAsParent then ;
end;

procedure TTextMatePattern.IncRefCount;
begin
  inc(FRefCount);
end;

procedure TTextMatePattern.DecRefCount;
begin
  if FRefCount > 0 then
    dec(FRefCount);
end;

function TTextMatePattern.DebugName: string;
begin
  Result := FName;
  If FDebugName <> '' then
    Result := '#' + FDebugName + ': ' + Result;

  if Result = '' then
    Result := ClassName;
  if FComment <> '' then
    Result := Result + ' // ' + FComment;
end;

function TTextMatePattern.DebugDump(AnIndent: Integer;
  AnIncludeNested: Boolean; APrefix: string): string;
begin
  Result := StringOfChar(' ', AnIndent) + APrefix
          + '[' + IntToStr(FMainIndex) + '] ' + DebugName + LineEnding;
  if AnIncludeNested then ;
end;

function TTextMatePattern.IsEndlessRecursion(ACurTokenPos: integer;
  AnInitInfoOnly: Boolean): boolean;
begin
  Result :=
     (FDeepestRecurseWasInit and AnInitInfoOnly) or
     (ACurTokenPos < FDeepestRecurseTextAtOffset) or
     ((not FDeepestRecurseWasInit) and (ACurTokenPos = FDeepestRecurseTextAtOffset))
     ;
  FDeepestRecurseWasInit := False;
  if not Result then
    FDeepestRecurseTextAtOffset := ACurTokenPos
  {$IFDEF TMATE_MATCHING} else DebugLn(['TTextMatePatternRoot.NextToken: recursion did not advance']) {$ENDIF}
  ;
end;

procedure TTextMatePattern.ClearDeepestRecurseData;
begin
  FDeepestRecurseTextAtOffset := 0;
  FDeepestRecurseWasInit := False;
end;

procedure TTextMatePattern.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  assert(False, 'TTextMatePattern.InitStates: not TTextMatePatternBaseNested');
  if AGrammar=nil then ;
  if AStates.Grammar=nil then ;
  if AParent=nil then ;
  if AText='' then ;
  if ADepth=0 then ;
end;

procedure TTextMatePattern.GetCurrentTokenInfo(
  const AStates: TTextMatePatternState;
  const ACheckProc: TCheckAttributeInfoProc; out ATokenKind: integer; out
  AnAttr: TObject; ADepth: Integer);
var
  st: PTextMatePatternStateEntry;
  UseId, UseObj: Boolean;
  AParent: TTextMatePattern;
  ParTokenKind: integer;
  ParAttr: TObject;
begin
  st := AStates.EntryP[ADepth];

  ACheckProc(Self, st^.CurrentTokenAttribInfo, UseId, UseObj);
  ATokenKind :=  st^.CurrentTokenAttribInfo.TokId;
  AnAttr :=  st^.CurrentTokenAttribInfo.TokObject;
  if UseId and UseObj then
    exit;

  AParent := AStates.Parent[ADepth];
  if AParent <> nil then begin
    AParent.GetCurrentTokenInfo(AStates, ACheckProc, ParTokenKind, ParAttr, ADepth + 1);
    if not UseId then
      ATokenKind := ParTokenKind;
    if not UseObj then
      AnAttr := ParAttr;
  end;
end;

procedure TTextMatePattern.InitStateAfterAdded(
  AStateEntry: PTextMatePatternStateEntry);
begin
  if AStateEntry=nil then ;
end;

procedure TTextMatePattern.InitFoundCaptures(
  AStateEntryP: PTextMatePatternStateEntry; ARegEx: TRegExpr);
var
  c, i: Integer;
begin
  c := ARegEx.SubExprMatchCount;
  assert(c>=0, 'TTextMatePatternBeginEnd.NextToken: c>=0');
  SetLength(AStateEntryP^.FoundCaptures, c + 1);
  for i := 0 to c do begin
    AStateEntryP^.FoundCaptures[i].Start := ARegEx.MatchPos[i];
    AStateEntryP^.FoundCaptures[i].Len   := ARegEx.MatchLen[i];
  end;
end;

function TTextMatePattern.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
begin
  Result := False;
  APattern:=nil;
  AFoundStartPos:=0;
  assert(False);
  if AText='' then ;
  if ATextStartOffset=0 then ;
  if AStateEntryP=nil then ;
  if AMatchMustStartBefore=0 then ;
end;

function TTextMatePattern.GetForwardTarget: TTextMatePatternBaseNested;
begin
  assert(False, 'TTextMatePattern.GetForwardTarget: False');
  Result := nil;
  //Result := Self;
end;

procedure TTextMatePattern.FlattenNested(AGrammar: TTextMateGrammar;
  ARemoveMissingIncludes: boolean);
begin
  if AGrammar=nil then ;
  if ARemoveMissingIncludes then ;
end;

class function TTextMatePattern.GetCopyFor(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList): TTextMatePattern;
begin
  Result := nil;
  if AnOther = nil then
    exit;
  Result := ANewList[AnOther.Index + AnIndexOffset];
end;

procedure TTextMatePattern.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
begin
  FComment    := AnOther.FComment;
  FName       := AnOther.FName;
  FDebugName  := AnOther.FDebugName;
  FAttribInfo := AnOther.FAttribInfo;
  FAttribInfo.TokId := FAttribInfo.TokId + AnIndexOffset;
  if ANewList=nil then ;
end;

procedure TTextMatePattern.DoInitRegex(var ARegEx: TRegExpr;
  const AText: string; AnErrKey: String);
var
  err: Integer;
begin
  ARegEx.Free;
  ARegEx := TRegExpr.Create(AText);
  ARegEx.RaiseForRuntimeError := False;
  ARegEx.AllowBraceWithoutMin := True;
  ARegEx.AllowLiteralBraceWithoutRange := True;
  //ARegEx.AllowUnsafeLookBehind := True;
  try
    ARegEx.Compile;
  except
    on E: Exception do raise TTextMateGrammarException.Create(E.Message, AnErrKey);
  end;
  err := ARegEx.LastError;
  if err <> 0 then
    raise TTextMateGrammarException.Create(ARegEx.ErrorMsg(err), AnErrKey);
end;

procedure TTextMatePatternBaseNested.InitStates(
  const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState;
  AParent: TTextMatePattern; const AText: String; ADepth: Integer);
begin
  if AParent = nil then
    AParent := FParent;
  AParent.InitStates(AGrammar, AStates, nil, AText, ADepth + 1);

  AStates.StateList[AStates.StateIdx - ADepth].Pattern := self;
  AStates.StateList[AStates.StateIdx - ADepth].SubTextBeginPos := 1;
  AStates.StateList[AStates.StateIdx - ADepth].SubTextLen := Length(AText);
  AStates.StateList[AStates.StateIdx - ADepth].CurrentTokenAttribInfo.TokId := -1;
  AStates.StateList[AStates.StateIdx - ADepth].CurrentTokenAttribInfo.TokObject := nil;
  AStates.StateList[AStates.StateIdx - ADepth].Flags := [];
  AStates.StateList[AStates.StateIdx - ADepth].ClearCache;
  FDeepestRecurseTextAtOffset := 0;
end;

function TTextMatePatternBaseNested.FindPatternForNextMatchPos(
  const AText: String; ATextStartOffset: integer;
  AStateEntryP: PTextMatePatternStateEntry; out AMatchInfo: TTextMateMatchInfo;
  AMatchMustStartBefore: integer): boolean;
var
  i, FndStartPos, OldRecurseMatchPos: integer;
  FndPattern: TTextMatePattern;
begin
  Result := False;

  AMatchInfo.Index := -1;
  if ATextStartOffset > AStateEntryP^.SubTextLen + 1 then
    exit;

  if ATextStartOffset = FRecurseMatchPos then begin
    {$IFDEF TMATE_MATCHING} debugln(['FindPatternForNextMatchPos - Recurse at ', ATextStartOffset, ' -- ', DebugName]); {$ENDIF}
    exit;
  end;
  OldRecurseMatchPos := FRecurseMatchPos;
  FRecurseMatchPos := ATextStartOffset;

  AMatchInfo.Start := high(AMatchInfo.Start);
  for i := 0 to Length(Patterns) - 1 do begin
    if not Patterns[i].GetFirstMatchPos(AText, ATextStartOffset, AStateEntryP, FndPattern, FndStartPos, AMatchMustStartBefore) then
      continue;
    Result := True;

    if FndStartPos >= AMatchInfo.Start then
      continue;

    if (FndStartPos < AMatchMustStartBefore) or (AMatchMustStartBefore < 1) then
      AMatchMustStartBefore := FndStartPos;

    AMatchInfo.Start := FndStartPos;
    AMatchInfo.Pattern := FndPattern;
    AMatchInfo.Index := i;

    if AMatchInfo.Start = ATextStartOffset then
      Break;
  end;
  FRecurseMatchPos := OldRecurseMatchPos;
end;

{ TTextMatePatternBaseNested }

function TTextMatePatternBaseNested.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
var
  AMatchInfo: TTextMateMatchInfo;
begin
  assert(AStateEntryP^.Pattern is TTextMatePatternBaseNested, 'TTextMatePatternBaseNested.GetFirstMatchPos: AStateEntryP^.Pattern is TTextMatePatternBaseNested');
  Result := TTextMatePatternBaseNested(AStateEntryP^.Pattern).FindPatternForNextMatchPos
    (AText, ATextStartOffset, AStateEntryP, AMatchInfo, AMatchMustStartBefore);
  AFoundStartPos := AMatchInfo.Start;
  APattern := AMatchInfo.Pattern;
end;

function TTextMatePatternBaseNested.GetForwardTarget: TTextMatePatternBaseNested;
begin
  Result := Self;
end;

procedure TTextMatePatternBaseNested.FlattenNested(AGrammar: TTextMateGrammar;
  ARemoveMissingIncludes: boolean);
var
  NewPtnList: TTextMatePatternArray;
  NewPtnIdx: integer;

  procedure InsertInto(ASrcList: TTextMatePatternArray);
  var
    i, j, l: Integer;
    PNest: TTextMatePatternNested;
    PIncl: TTextMatePatternInclude;
    Src, SrcFwd, Tmp: TTextMatePattern;
    k, pt1, pt2: String;
    dbl: Boolean;
  begin
    SetLength(NewPtnList, Length(NewPtnList) + Length(ASrcList) - 1);
    for i := 0 to Length(ASrcList) - 1 do begin
      Src := ASrcList[i];
      PNest := nil;
      if (Src is TTextMatePatternInclude) then begin
        j := 0;
        repeat
          PIncl := TTextMatePatternInclude(Src);
          if (PIncl.FSourceScope = '$base') or (PIncl.FSourceScope = AGrammar.FLanguageScopeName) then
            k := PIncl.FSourceKey
          else
            k := PIncl.FSourceScope + '#' + PIncl.FSourceKey;

          if k = '' then begin
            Src := AGrammar.FRootPattern;
            Break;
          end;
          if AGrammar.FPatternRepo.IndexOf(k) >= 0 then begin
            Src := AGrammar.FPatternRepo[k];
            if Src is TTextMatePatternInclude then begin
              inc(j);
              if j > AGrammar.MainPatternList.Count then
                raise TTextMateGrammarException.Create('Invalid nested pattern');
              continue;
            end;
          end
          else begin
            if (ARemoveMissingIncludes) then begin
              Src := nil;
              if (AGrammar.FOtherGrammars.IndexOf(PIncl.FSourceScope) >= 0) and
                 (AGrammar.FOtherGrammars[PIncl.FSourceScope] <> nil)
              then begin
                if AGrammar.FMissingIncludes <> '' then AGrammar.FMissingIncludes := AGrammar.FMissingIncludes + ', ';
                AGrammar.FMissingIncludes := AGrammar.FMissingIncludes + k;
              end;
            end;
          end;
          break;
        until False;
      end;
      if Src is TTextMatePatternNested then begin
        PNest := TTextMatePatternNested(Src);
      end;

      if PNest <> nil then begin
        if PNest.FParent = Self then
          raise TTextMateGrammarException.Create('Invalid nested pattern');
        PNest.FParent := Self;
        InsertInto(PNest.Patterns);
        PNest.FParent := nil;
      end
      else
      if Src <> nil then begin
        l := NewPtnIdx - 1;
        dbl := False;
        if Src is TTextMatePatternBaseNested then
          SrcFwd := Src.ForwardTarget
        else
          SrcFwd := Src;
        pt1:= '';
        if (SrcFwd is TTextMatePatternBeginEnd)   then pt1 := TTextMatePatternBeginEnd(SrcFwd).MatchBegin
        else if (SrcFwd is TTextMatePatternMatch) then pt1 := TTextMatePatternMatch(SrcFwd).Match;

        while l >= 0 do begin
          Tmp := NewPtnList[l];
          if Tmp is TTextMatePatternBaseNested then
            Tmp := Tmp.ForwardTarget;
          pt2:= '';
          if (Tmp is TTextMatePatternBeginEnd)   then pt2 := TTextMatePatternBeginEnd(Tmp).MatchBegin
          else if (Tmp is TTextMatePatternMatch) then pt2 := TTextMatePatternMatch(Tmp).Match;

          dbl := (Tmp = SrcFwd) or
                 ( (pt1 <> '') and (pt1=pt2) );
          if dbl then
            break;
          dec(l);
        end;
        if not dbl then begin
          NewPtnList[NewPtnIdx] := Src;
          inc(NewPtnIdx);
        end;
      end;
    end;
  end;

begin
  if UnFlatPatterns = nil then
    UnFlatPatterns := Patterns;
  SetLength(NewPtnList, 1);
  NewPtnIdx := 0;
  InsertInto(UnFlatPatterns);
  SetLength(NewPtnList, NewPtnIdx);
  Patterns := NewPtnList
end;

procedure TTextMatePatternBaseNested.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternBaseNested absolute AnOther;
  i: Integer;
  Src: TTextMatePatternArray;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  FParent := GetCopyFor(TheOther.FParent, AnIndexOffset, ANewList);
  Src := TTextMatePatternBaseNested(AnOther).UnFlatPatterns;
  if Src = nil then
    Src := TTextMatePatternBaseNested(AnOther).Patterns;
  SetLength(Patterns, Length(Src));
  for i := 0 to Length(Src) - 1 do
    Patterns[i] :=  GetCopyFor(Src[i], AnIndexOffset, ANewList);
end;

procedure TTextMatePatternBaseNested.NextToken(
  var AStates: TTextMatePatternState; const AText: String;
  ACurTokenPos: integer; out ANextTokenPos: integer; AnInitInfoOnly: Boolean;
  AnIsCalledAsParent: Boolean);
var
  FndMatchInfo: TTextMateMatchInfo;
  st: PTextMatePatternStateEntry;
begin
  {$IFDEF TMATE_MATCHING}
  debuglnEnter(['> '+ClassName+'.NextToken ',DebugName,' --- TkPos:', ACurTokenPos, ' ',dbgs(AnInitInfoOnly), '  txt-len:',AStates.EntryP[0]^.SubTextLen,'/',length(AText) ]); try
  {$ENDIF}
  if not AnIsCalledAsParent then
  if IsEndlessRecursion(ACurTokenPos, AnInitInfoOnly) then begin
    if AnInitInfoOnly
    then ANextTokenPos := ACurTokenPos
    else AStates.CallParentNextToken(AText, Length(AText) + 1, ANextTokenPos, False);
    exit;
  end;

  st := AStates.EntryP[0];
  st^.CurrentTokenAttribInfo := FAttribInfo;

  if AnInitInfoOnly
  then ANextTokenPos := ACurTokenPos
  else ANextTokenPos := st^.SubTextLen + 1;

  if ACurTokenPos > st^.SubTextLen then begin // can happen in captures, as text is not full line text
    assert(ACurTokenPos=st^.SubTextLen+1, 'TTextMatePatternBaseNested.NextToken: ACurTokenPos=Length(AText)+1');
    {$IFDEF TMATE_MATCHING} debugln(['EXIT - because curpos ALREADY was TOO FAR']); {$ENDIF}
    AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
    exit;
  end;

  if not TTextMatePatternBaseNested(st^.Pattern).FindPatternForNextMatchPos
         (AText, ACurTokenPos, st, FndMatchInfo)
     or
     (AnInitInfoOnly and (FndMatchInfo.Start > ACurTokenPos))
  then begin
    {$IFDEF TMATE_MATCHING} debugln([''+ClassName+'.NextToken  << no match or init ', FndMatchInfo.Start]); {$ENDIF}
    if AnInitInfoOnly then begin
      // TODO, cache if AnInitInfoOnly
      if not AnIsCalledAsParent then
        FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
    end
    else
    if (self is TTextMatePatternCapture) then // ANextTokenPos is end of catpured text + 1
      AStates.CallParentNextToken(AText, ANextTokenPos, ANextTokenPos, True);

    exit;
  end;

  AStates.Add(FndMatchInfo, 1, st^.SubTextLen, st^.Flags * [psfNestedinCapture]);
  FndMatchInfo.Pattern.NextToken(AStates, AText, FndMatchInfo.Start, ANextTokenPos, True);
  assert((ANextTokenPos=FndMatchInfo.Start) or (not AnInitInfoOnly), 'TTextMatePatternBaseNested.NextToken: (ANextTokenPos=FndMatchInfo.Start) or (not AnInitInfoOnly)');
  {$IFDEF TMATE_MATCHING}
  finally debuglnExit(['< '+ClassName+'.NextToken  << ',ANextTokenPos  ]); end;
  {$ENDIF}
end;

procedure TTextMatePatternBaseNested.IncRefCount;
begin
  inherited IncRefCount;
  if FParent <> nil then
    FParent.IncRefCount;
end;

procedure TTextMatePatternBaseNested.DecRefCount;
begin
  inherited DecRefCount;
  if FParent <> nil then
    FParent.DecRefCount;
end;

function TTextMatePatternBaseNested.DebugDump(AnIndent: Integer;
  AnIncludeNested: Boolean; APrefix: string): string;
var
  i: Integer;
  l: TTextMatePatternArray;
begin
  Result := inherited DebugDump(AnIndent, AnIncludeNested, APrefix);
  if AnIncludeNested then begin
    l := Patterns;
    Patterns := nil;
    for i := 0 to Length(l) - 1 do
      Result := Result + l[i].DebugDump(AnIndent + 2, AnIncludeNested, APrefix);
    Patterns := l;
  end;
end;

{ TTextMatePatternNestedList }

procedure TTextMatePatternNestedList.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  assert(False, 'TTextMatePatternNestedList.InitStates: False');
end;

procedure TTextMatePatternNestedList.FlattenNested(AGrammar: TTextMateGrammar;
  ARemoveMissingIncludes: boolean);
begin
  //
end;

{ TTextMatePatternInclude }

procedure TTextMatePatternInclude.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternInclude absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  FSourceScope := TheOther.FSourceScope;
  FSourceKey   := TheOther.FSourceKey;
end;

function TTextMatePatternInclude.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
begin
  Result := False;
end;

{ TTextMatePatternCapture }

procedure TTextMatePatternCapture.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  assert(False, 'TTextMatePatternCapture.InitStates: can not be on range stack');
end;

procedure TTextMatePatternCapture.InitStateAfterAdded(
  AStateEntry: PTextMatePatternStateEntry);
begin
  inherited InitStateAfterAdded(AStateEntry);
  Include(AStateEntry^.Flags, psfNestedinCapture)
end;

{ TTextMatePatternCaptures }

function TTextMatePatternCaptures.FindCaptureForNextMatchPos(
  ATextStartOffset: integer;
  const AFoundCapturePosList: TTextMateFoundCaptureBoundsArray; out
  ACaptureInfo: TTextMateMatchInfo): boolean;
var
  i: integer;
begin
  Result := False;
  ACaptureInfo.Index := -1;

  ACaptureInfo.Start := high(ACaptureInfo.Start);
  for i := 0 to min(Length(CaptureArray), Length(AFoundCapturePosList)) - 1 do begin
    if (CaptureArray[i] = nil) or
       (AFoundCapturePosList[i].Start < ATextStartOffset) or (AFoundCapturePosList[i].Len <= 0)
    then
      continue;
    Result := True;

// If starting at same point: which to take?? the last or the shorter???
    if AFoundCapturePosList[i].Start > ACaptureInfo.Start then
      continue;

    ACaptureInfo.Start := AFoundCapturePosList[i].Start;
    ACaptureInfo.Pattern := CaptureArray[i];
    ACaptureInfo.Index := i;

    if ACaptureInfo.Start = 1 then
      Break;
  end;
  {$IFDEF TMATE_MATCHING}
  debugln(['TTextMatePatternCaptures.FindCaptureForNextMatchPos ',dbgs(Result), ' pos ', ACaptureInfo.Start, ' idx ',ACaptureInfo.Index]);
  {$ENDIF}
end;

procedure TTextMatePatternCaptures.CopyFrom(AnOther: TTextMatePatternCaptures;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  i: Integer;
begin
  SetLength(CaptureArray, Length(AnOther.CaptureArray));
  for i := 0 to Length(AnOther.CaptureArray) - 1 do
    CaptureArray[i] :=  TTextMatePatternCapture(TTextMatePattern.GetCopyFor(AnOther.CaptureArray[i], AnIndexOffset, ANewList));

end;

{ TTextMatePatternMatch }

procedure TTextMatePatternMatch.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternMatch absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  Captures.CopyFrom(TheOther.Captures, AnIndexOffset, ANewList);
  Match := TheOther.Match;
  DoInitRegex(FRegExMatch, Match, 'match');
end;

procedure TTextMatePatternMatch.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  assert(False, 'Single match can not be on range stack');
end;

function TTextMatePatternMatch.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
begin
  FRegExMatch.SetInputSubString(AText, AStateEntryP^.SubTextBeginPos, AStateEntryP^.SubTextLen);

  Result := FRegExMatch.ExecPos(ATextStartOffset, AMatchMustStartBefore) and (FRegExMatch.MatchLen[0] > 0);
  if Result then
    AFoundStartPos := FRegExMatch.MatchPos[0];
  APattern := Self;
end;

destructor TTextMatePatternMatch.Destroy;
begin
  FreeAndNil(FRegExMatch);
  inherited Destroy;
end;

procedure TTextMatePatternMatch.InitRegEx;
begin
  DoInitRegex(FRegExMatch, Match, 'match');
end;

procedure TTextMatePatternMatch.NextToken(var AStates: TTextMatePatternState;
  const AText: String; ACurTokenPos: integer; out ANextTokenPos: integer;
  AnInitInfoOnly: Boolean; AnIsCalledAsParent: Boolean);
var
  st: PTextMatePatternStateEntry;
  CaptTextLen, MatchEndPos: Integer;
  r: Boolean;
  FndCapture: TTextMateMatchInfo;
begin
  {$IFDEF TMATE_MATCHING}
  debuglnEnter(['> TTextMatePatternMatch.NextToken ',DebugName,' --- TkPos:', ACurTokenPos, ' ',dbgs(AnInitInfoOnly), '  txt-len:',AStates.EntryP[0]^.SubTextLen,'/',length(AText) ]); try
  {$ENDIF}
  if not AnIsCalledAsParent then
  if IsEndlessRecursion(ACurTokenPos, AnInitInfoOnly) then begin
    if AnInitInfoOnly
    then ANextTokenPos := ACurTokenPos
    else AStates.CallParentNextToken(AText, Length(AText) + 1, ANextTokenPos, False);
    exit;
  end;

  st := AStates.EntryP[0];
  st^.CurrentTokenAttribInfo := FAttribInfo;

  if not (psfMatchBeginInitialized in st^.Flags) then begin
    if FRegExMatch.MatchPos[0] <> ACurTokenPos then begin
      // In case the pattern was called, since the parent did match it
      GetFirstMatchPos(AText, ACurTokenPos, st, FndCapture.Pattern, FndCapture.Start);
      assert(FRegExMatch.MatchPos[0]=ACurTokenPos, 'TTextMatePatternBeginEnd.NextToken: FRegExMatch.MatchPos[0]=ACurTokenPos');
    end;
    InitFoundCaptures(st, FRegExMatch);
    Include(st^.Flags, psfMatchBeginInitialized);
  end;

  MatchEndPos := st^.FoundCaptures[0].Start + st^.FoundCaptures[0].Len;

  if (ACurTokenPos >= MatchEndPos) or
     (ACurTokenPos > st^.SubTextLen)
  then begin
    assert(ACurTokenPos = MatchEndPos, 'TTextMatePatternMatch.NextToken: ACurTokenPos = MatchEndPos');
    {$IFDEF TMATE_MATCHING}debugln(['EXIT - because curpos ALREADY was TOO FAR']); {$ENDIF}
    AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
    exit;
  end;

  r := st^.GetCache(FndCapture, CaptTextLen);
  if not r then begin
    CaptTextLen := MatchEndPos-1; // TODO: cache
    if ACurTokenPos < MatchEndPos then begin
      r := Captures.FindCaptureForNextMatchPos(ACurTokenPos, st^.FoundCaptures, FndCapture);
      if r then
        CaptTextLen := FndCapture.Start + st^.FoundCaptures[FndCapture.Index].Len - 1;
    end;
  end;

  if AnInitInfoOnly
  then ANextTokenPos := ACurTokenPos
  else ANextTokenPos := CaptTextLen + 1;

  if (not r) or (AnInitInfoOnly and (FndCapture.Start > ACurTokenPos))
  then begin
    if AnInitInfoOnly then begin
      if r then
        st^.SetCache(FndCapture, CaptTextLen);
      if not AnIsCalledAsParent then
        FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
    end
    else begin
      assert(ANextTokenPos >= MatchEndPos, 'TTextMatePatternMatch.NextToken: ANextTokenPos >= MatchEndPos');
      AStates.CallParentNextToken(AText, MatchEndPos, ANextTokenPos, True);
    end;
    exit;
  end;

  assert((FndCapture.Pattern <> nil) and (CaptTextLen > 0), 'TTextMatePatternMatch.NextToken: (FndCapture.Pattern <> nil) and (CaptTextLen <> '')');
  AStates.Add(FndCapture, 1, CaptTextLen);
  FndCapture.Pattern.NextToken(AStates, AText, FndCapture.Start, ANextTokenPos, True);
  assert((ANextTokenPos=FndCapture.Start) or (not AnInitInfoOnly), 'TTextMatePatternRoot.NextToken: (ANextTokenPos=FndCapture.Bounds.Start) or (not AnInitInfoOnly)');
  {$IFDEF TMATE_MATCHING}
  finally debuglnExit(['< TTextMatePatternMatch.NextToken ' ]); end;
  {$ENDIF}
end;

{ TTextMatePatternBeginEnd }

procedure TTextMatePatternBeginEnd.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternBeginEnd absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  ContentName := TheOther.ContentName;
  ContentAttribInfo := TheOther.ContentAttribInfo;
  EndPatternLast := TheOther.EndPatternLast;
  Captures.CopyFrom(TheOther.Captures, AnIndexOffset, ANewList);
  CapturesBegin.CopyFrom(TheOther.CapturesBegin, AnIndexOffset, ANewList);
  CapturesEnd.CopyFrom(TheOther.CapturesEnd, AnIndexOffset, ANewList);
  MatchBegin := TheOther.MatchBegin;
  MatchEnd := TheOther.MatchEnd;
  DoInitRegex(FRegExMatchBegin, MatchBegin, 'matchBegin');
  DoInitRegex(FRegExMatchEnd, MatchEnd, 'matchEnd');
end;

procedure TTextMatePatternBeginEnd.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  inherited InitStates(AGrammar, AStates, AParent, AText, ADepth);

  AStates.StateList[AStates.StateIdx - ADepth].CurrentTokenAttribInfo := ContentAttribInfo;
  AStates.StateList[AStates.StateIdx - ADepth].Flags := [psfMatchBeginDone];
end;

function TTextMatePatternBeginEnd.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
var
  AMatchInfo: TTextMateMatchInfo;
  NewFwrd: TTextMatePatternForwarderNewEnd;
  i, j: Integer;
  t: String;
  EndPattern: TRegExpr;
begin
  if ATextStartOffset = FRecursiveFirstMatchPos then
    exit(False);

  FRegExMatchBegin.SetInputSubString(AText, AStateEntryP^.SubTextBeginPos, AStateEntryP^.SubTextLen);
  Result := FRegExMatchBegin.ExecPos(ATextStartOffset, AMatchMustStartBefore);
  if Result then begin
    AFoundStartPos := FRegExMatchBegin.MatchPos[0];

    if FEndHasBackRef then begin;
      t := MatchEnd;
      for i := 1 to FRegExMatchBegin.SubExprMatchCount do
        t := ReplaceRegExpr('(?<!(?:^|[^\\])\\(?:\\\\)*)(\\'+IntToStr(i)+')', t, '(?:'+QuoteRegExprMetaChars(FRegExMatchBegin.Match[i])+')');
      i := FEndForwarderCnt - 1;
      j := -1;
      while i >= 0 do begin
        if t = FEndForwarders[i].FRegExMatchEnd.Expression then
          break;
        if FEndForwarders[i].FRefCount = 0 then
          j := i;
        dec(i);
      end;
      if i >= 0 then
        NewFwrd := FEndForwarders[i]
      else
      if j >= 0 then
        NewFwrd := FEndForwarders[j]
      else
      begin
        NewFwrd := TTextMatePatternForwarderNewEnd(AStateEntryP^.Grammar.CreatePatternObject(TTextMatePatternForwarderNewEnd, 0));
        NewFwrd.DoInitRegex(NewFwrd.FRegExMatchEnd,   t, 'matchEnd');
        NewFwrd.FForwardTo := Self;
        NewFwrd.CopyPatterns(Self.Patterns);
        if FEndForwarderCnt >= Length(FEndForwarders) then
          SetLength(FEndForwarders, FEndForwarderCnt+16);
        FEndForwarders[FEndForwarderCnt] := NewFwrd;
        inc(FEndForwarderCnt);
      end;

      EndPattern := NewFwrd.FRegExMatchEnd;
      APattern := NewFwrd;
    end
    else
    begin
      APattern := Self;
      EndPattern := FRegExMatchEnd;
    end;

    // check for zero length
    if FRegExMatchBegin.MatchLen[0] <= 0 then begin
      EndPattern.SetInputSubString(AText, AStateEntryP^.SubTextBeginPos, AStateEntryP^.SubTextLen);
      if EndPattern.ExecPos(AFoundStartPos, AFoundStartPos+1) and
         (EndPattern.MatchPos[0] = AFoundStartPos) and (EndPattern.MatchLen[0] <=0)
      then begin
        if EndPatternLast then begin
          FRecursiveFirstMatchPos := AFoundStartPos;
          try
            if TTextMatePatternBaseNested(AStateEntryP^.Pattern).FindPatternForNextMatchPos(AText, AFoundStartPos, AStateEntryP, AMatchInfo, AFoundStartPos + 1) and
               (AMatchInfo.Start = AFoundStartPos)
            then
              Result := False;
          finally
            FRecursiveFirstMatchPos := 0;
          end;
        end
        else
          Result := False;
      end;
    end;
  end;
end;

destructor TTextMatePatternBeginEnd.Destroy;
var
  i: Integer;
begin
  for i := 0 to FEndForwarderCnt - 1 do
    FEndForwarders[i].FForwardTo := nil;
  FreeAndNil(FRegExMatchBegin);
  FreeAndNil(FRegExMatchEnd);
  inherited Destroy;
end;

procedure TTextMatePatternBeginEnd.InitRegEx;
begin
  FEndHasBackRef := ExecRegExpr('(?<!(?:^|[^\\])\\(?:\\\\)*)\\\d+', MatchEnd);
  DoInitRegex(FRegExMatchBegin, MatchBegin, 'matchBegin');
  if FEndHasBackRef  then
    DoInitRegex(FRegExMatchEnd,   '.', 'matchEnd')
  else
    DoInitRegex(FRegExMatchEnd,   MatchEnd, 'matchEnd');
end;

procedure TTextMatePatternBeginEnd.NextToken(
  var AStates: TTextMatePatternState; const AText: String;
  ACurTokenPos: integer; out ANextTokenPos: integer; AnInitInfoOnly: Boolean;
  AnIsCalledAsParent: Boolean);
var
  st: PTextMatePatternStateEntry;
  FndCapture, FndCapture2: TTextMateMatchInfo;
  r: Boolean;
  BodyPos, EndPos, EndEnd: Integer;
  CaptTextLen: Integer;
begin
  {$IFDEF TMATE_MATCHING}
  debuglnEnter(['> TTextMatePatternBeginEnd.NextToken ',DebugName,' --- TkPos:', ACurTokenPos, ' ',dbgs(AnInitInfoOnly), '  txt-len:',AStates.EntryP[0]^.SubTextLen,'/',length(AText) ]); try
  {$ENDIF}

  if not AnIsCalledAsParent then
  if IsEndlessRecursion(ACurTokenPos, AnInitInfoOnly) then begin
    if AnInitInfoOnly
    then ANextTokenPos := ACurTokenPos
    else AStates.CallParentNextToken(AText, Length(AText) + 1, ANextTokenPos, False);
    exit;
  end;

  st := AStates.EntryP[0];
  st^.CurrentTokenAttribInfo := FAttribInfo;

  BodyPos := 0;

  r := st^.GetCache(FndCapture, CaptTextLen);
  assert(not(r and AnInitInfoOnly), 'TTextMatePatternBeginEnd.NextToken: not(r and AnInitInfoOnly)');

  (*  Check matchBegin  *)
  if (not (psfMatchBeginDone in st^.Flags)) then begin
    {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken >> beginMatch']); {$ENDIF}
    if not (psfMatchBeginInitialized in st^.Flags) then begin
      if FRegExMatchBegin.MatchPos[0] <> ACurTokenPos then begin
        // In case the pattern was called, since the parent did match it
        GetFirstMatchPos(AText, ACurTokenPos, st, FndCapture2.Pattern, FndCapture2.Start);
        assert(FRegExMatchBegin.MatchPos[0]=ACurTokenPos, 'TTextMatePatternBeginEnd.NextToken: FRegExMatchBegin.MatchPos[0]=ACurTokenPos');
      end;
      InitFoundCaptures(st, FRegExMatchBegin);
      Include(st^.Flags, psfMatchBeginInitialized);
    end;

    BodyPos := st^.FoundCaptures[0].Start + st^.FoundCaptures[0].Len;
    if (ACurTokenPos < BodyPos) and not r then begin
      CaptTextLen := BodyPos - 1; // TODO: cache
      r := CapturesBegin.FindCaptureForNextMatchPos(ACurTokenPos, st^.FoundCaptures, FndCapture);
      if Captures.FindCaptureForNextMatchPos(ACurTokenPos, st^.FoundCaptures, FndCapture2) then
        r := LeftmostFoundPattern(FndCapture, FndCapture2);
      if r then
        CaptTextLen := FndCapture.Start + st^.FoundCaptures[FndCapture.Index].Len - 1;

      if AnInitInfoOnly and ( (not r) or (FndCapture.Start > ACurTokenPos) ) then begin
        if r then
          st^.SetCache(FndCapture, CaptTextLen);
        ANextTokenPos := ACurTokenPos;
        if not AnIsCalledAsParent then
          FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
        {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken exit inside MatchBegin ',BodyPos  ]); {$ENDIF}
        exit;
      end;
    end;
  end;

  (*  Check body / nested patterns  *)
  if (not r) and (BodyPos <= st^.SubTextLen + 1) then begin
    Include(st^.Flags, psfMatchBeginDone);
    CaptTextLen := st^.SubTextLen;
    if BodyPos < ACurTokenPos then
      BodyPos := ACurTokenPos;

    if not (psfMatchEndInitialized in st^.Flags) then begin
      if ContentAttribInfo.TokId >= 0 then
        st^.CurrentTokenAttribInfo := ContentAttribInfo;
      assert(st^.Pattern is TTextMatePatternBaseNested, 'TTextMatePatternBeginEnd.NextToken: st^.Pattern is TTextMatePatternBaseNested');
      r := TTextMatePatternBaseNested(st^.Pattern).FindPatternForNextMatchPos(AText, BodyPos, st, FndCapture);
      {$IFDEF TMATE_MATCHING} debugln(r, ['TTextMatePatternBeginEnd.NextToken  FOUND BODY PTRN ', FndCapture.Start]); {$ENDIF}

      (*  Check matchEnd  *)
      // TODO: \1 to refer to begin pattern
      FRegExMatchEnd.SetInputSubString(AText, st^.SubTextBeginPos, st^.SubTextLen);
      if FRegExMatchEnd.ExecPos(BodyPos) then begin
        EndPos := FRegExMatchEnd.MatchPos[0];
        {$IFDEF TMATE_MATCHING} debugln(r, ['TTextMatePatternBeginEnd.NextToken  FOUND END PTRN ', EndPos]); {$ENDIF}

        if (not r) or (FndCapture.Start > EndPos) or
           ( (not EndPatternLast) and (FndCapture.Start >= EndPos) )
        then begin
          InitFoundCaptures(st, FRegExMatchEnd);
          Include(st^.Flags, psfMatchEndInitialized);
        end;
      end;
    end
    else
    // End pattern was found as next, there are no more "Patterns" from the content
    if (ContentAttribInfo.TokId >= 0) and (ACurTokenPos < st^.FoundCaptures[0].Start) then
      st^.CurrentTokenAttribInfo := ContentAttribInfo;
  end;

  (*  Check matchEnd captures  *)
  if psfMatchEndInitialized in st^.Flags then begin
    EndPos := st^.FoundCaptures[0].Start;
    EndEnd := EndPos + st^.FoundCaptures[0].Len;
    if ACurTokenPos > EndPos then
      EndPos := ACurTokenPos;
    {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken  END ',EndPos,'-',EndEnd]); {$ENDIF}

    if ACurTokenPos >= EndEnd then begin
      {$IFDEF TMATE_MATCHING} debugln(['EXIT - because curpos ALREADY was TOO FAR']); {$ENDIF}
      AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
        exit;
    end;

    r := CapturesEnd.FindCaptureForNextMatchPos(EndPos, st^.FoundCaptures, FndCapture);
    if Captures.FindCaptureForNextMatchPos(EndPos, st^.FoundCaptures, FndCapture2)
    then
      r := LeftmostFoundPattern(FndCapture, FndCapture2);
    if r then
      CaptTextLen := FndCapture.Start + st^.FoundCaptures[FndCapture.Index].Len - 1;
  end;


  if (ContentAttribInfo.TokId >= 0) and (ContentAttribInfo.TokId <> FAttribInfo.TokId) and
     ( (not (psfMatchEndInitialized in st^.Flags)) or (EndPos > BodyPos) ) and  // Not yet in matchEnd regex
     not AnInitInfoOnly
  then begin
    if (ACurTokenPos < BodyPos) and
       ( (not r) or (FndCapture.Start > BodyPos) )
    then begin
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken exit after MatchBegin ',BodyPos  ]); {$ENDIF}
      ANextTokenPos := BodyPos;
      if ((psfNestedinCapture in st^.Flags) and (ANextTokenPos > st^.SubTextLen)) or
         (ANextTokenPos >= EndEnd)
      then
        AStates.CallParentNextToken(AText, ANextTokenPos, ANextTokenPos, True);
      exit;
    end;

    if (ACurTokenPos < EndPos) and (psfMatchEndInitialized in st^.Flags) and
       ( (not r) or (FndCapture.Start > EndPos) )
    then begin
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken  exit before matchEnd', EndPos]); {$ENDIF}
      ANextTokenPos := EndPos;
      st^.CurrentTokenAttribInfo := FAttribInfo;
      if ((psfNestedinCapture in st^.Flags) and (ANextTokenPos > st^.SubTextLen)) or
         (ANextTokenPos >= EndEnd)
      then
        AStates.CallParentNextToken(AText, ANextTokenPos, ANextTokenPos, True);
      exit;
    end;
  end;

  if AnInitInfoOnly
  then ANextTokenPos := ACurTokenPos
  else ANextTokenPos := st^.SubTextLen+ 1;

  if (psfNestedinCapture in st^.Flags) and (ACurTokenPos > st^.SubTextLen) then begin
    assert(ACurTokenPos=st^.SubTextLen+1, 'TTextMatePatternBeginEnd.NextToken: ACurTokenPos=st^.SubTextLen+1');
    {$IFDEF TMATE_MATCHING} debugln(['EXIT - because curpos ALREADY was TOO FAR']); {$ENDIF}
    AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
    exit;
  end;

  if (psfMatchEndInitialized in st^.Flags) and (ANextTokenPos >= EndPos) then
    st^.CurrentTokenAttribInfo := FAttribInfo;

  if (not r) or (AnInitInfoOnly and (FndCapture.Start > ACurTokenPos))
  then begin
    if AnInitInfoOnly then begin
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken exit for init']); {$ENDIF}
      if r then
        st^.SetCache(FndCapture, CaptTextLen);
      if not AnIsCalledAsParent then
        FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
    end
    else
    if (psfMatchEndInitialized in st^.Flags) then begin
      assert(ANextTokenPos >= EndEnd, 'TTextMatePatternBeginEnd.NextToken: ANextTokenPos >= EndEnd');
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken  EXIT GOT TO END ', ANextTokenPos, ' ' ,EndEnd]); {$ENDIF}
      AStates.CallParentNextToken(AText, EndEnd, ANextTokenPos, True);
    end
    else
    if (psfNestedinCapture in st^.Flags) and (ANextTokenPos > st^.SubTextLen)then begin
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken  EXIT GOT TO LENGTH ', ANextTokenPos, ' ' ,EndEnd]); {$ENDIF}
      AStates.CallParentNextToken(AText, ANextTokenPos, ANextTokenPos, True);
    end;
    exit;
  end;

  AStates.Add(FndCapture, 1, CaptTextLen);
  FndCapture.Pattern.NextToken(AStates, AText, FndCapture.Start, ANextTokenPos, True);
  assert((ANextTokenPos=FndCapture.Start) or (not AnInitInfoOnly), 'TTextMatePatternRoot.NextToken: (ANextTokenPos=FndCapture.Bounds.Start) or (not AnInitInfoOnly)');

  {$IFDEF TMATE_MATCHING}
  finally debuglnExit(['< TTextMatePatternBeginEnd.NextToken << ',ANextTokenPos ]); end;
  {$ENDIF}
end;

{ TTextMatePatternBeginWhile }

procedure TTextMatePatternBeginWhile.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternBeginWhile absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  ContentName := TheOther.ContentName;
  ContentAttribInfo := TheOther.ContentAttribInfo;
  Captures.CopyFrom(TheOther.Captures, AnIndexOffset, ANewList);
  CapturesBegin.CopyFrom(TheOther.CapturesBegin, AnIndexOffset, ANewList);
  CapturesWhile.CopyFrom(TheOther.CapturesWhile, AnIndexOffset, ANewList);
  MatchBegin := TheOther.MatchBegin;
  MatchWhile := TheOther.MatchWhile;
  DoInitRegex(FRegExMatchBegin, MatchBegin, 'matchBegin');
  DoInitRegex(FRegExMatchWhile, MatchWhile, 'MatchWhile');
end;

procedure TTextMatePatternBeginWhile.InitStates(
  const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState;
  AParent: TTextMatePattern; const AText: String; ADepth: Integer);
begin
  inherited InitStates(AGrammar, AStates, AParent, AText, ADepth);

  AStates.StateList[AStates.StateIdx - ADepth].CurrentTokenAttribInfo := ContentAttribInfo;
end;

procedure TTextMatePatternBeginWhile.InitStateAfterAdded(
  AStateEntry: PTextMatePatternStateEntry);
begin
  inherited InitStateAfterAdded(AStateEntry);
  Include(AStateEntry^.Flags, psfWhileInBeginLine);
end;

function TTextMatePatternBeginWhile.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
begin
  FRegExMatchBegin.SetInputSubString(AText, AStateEntryP^.SubTextBeginPos, AStateEntryP^.SubTextLen);
  Result := FRegExMatchBegin.ExecPos(ATextStartOffset, AMatchMustStartBefore);
  if Result then
    AFoundStartPos := FRegExMatchBegin.MatchPos[0];
  APattern := Self;
end;

destructor TTextMatePatternBeginWhile.Destroy;
begin
  FreeAndNil(FRegExMatchBegin);
  FreeAndNil(FRegExMatchWhile);
  inherited Destroy;
end;

procedure TTextMatePatternBeginWhile.InitRegEx;
begin
  DoInitRegex(FRegExMatchBegin, MatchBegin, 'matchBegin');
  DoInitRegex(FRegExMatchWhile, MatchWhile, 'matchWhile');
end;

procedure TTextMatePatternBeginWhile.NextToken(
  var AStates: TTextMatePatternState; const AText: String;
  ACurTokenPos: integer; out ANextTokenPos: integer; AnInitInfoOnly: Boolean;
  AnIsCalledAsParent: Boolean);
var
  st: PTextMatePatternStateEntry;
  BodyPos, CaptTextLen: Integer;
  MatchCaptures: TTextMatePatternCaptures;
  FndCapture, FndCapture2: TTextMateMatchInfo;
  r: Boolean;
begin
  {$IFDEF TMATE_MATCHING}
  debuglnEnter(['> TTextMatePatternBeginWhile.NextToken ',DebugName,' --- TkPos:', ACurTokenPos, ' ',dbgs(AnInitInfoOnly), '  txt-len:',AStates.EntryP[0]^.SubTextLen,'/',length(AText) ]); try
  {$ENDIF}
  if not AnIsCalledAsParent then
  if IsEndlessRecursion(ACurTokenPos, AnInitInfoOnly) then begin
    if AnInitInfoOnly
    then ANextTokenPos := ACurTokenPos
    else AStates.CallParentNextToken(AText, Length(AText) + 1, ANextTokenPos, False);
    exit;
  end;

  st := AStates.EntryP[0];
  st^.CurrentTokenAttribInfo := FAttribInfo;

  if (psfNestedinCapture in st^.Flags) and (ACurTokenPos > st^.SubTextLen) then begin
    AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
    exit;
  end;

  if (psfWhileInBeginLine in st^.Flags) then begin
    if not (psfMatchBeginInitialized in st^.Flags) then begin
      if FRegExMatchBegin.MatchPos[0] <> ACurTokenPos then begin
        // In case the pattern was called, since the parent did match it
        GetFirstMatchPos(AText, ACurTokenPos, st, FndCapture2.Pattern, FndCapture2.Start);
        assert(FRegExMatchBegin.MatchPos[0]=ACurTokenPos, 'TTextMatePatternBeginEnd.NextToken: FRegExMatchBegin.MatchPos[0]=ACurTokenPos');
      end;
      InitFoundCaptures(st, FRegExMatchBegin);
      Include(st^.Flags, psfMatchBeginInitialized);
    end;

    MatchCaptures := CapturesBegin;
  end
  else begin
    // Continuation line
    if not (psfWhileDone in st^.Flags) then begin
      Include(st^.Flags, psfWhileDone);
      FRegExMatchWhile.SetInputSubString(AText, st^.SubTextBeginPos, st^.SubTextLen);
      if not FRegExMatchWhile.ExecPos(ACurTokenPos) then begin
        AStates.CallParentNextToken(AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly);
        exit;
      end;

      InitFoundCaptures(st, FRegExMatchWhile);
      Include(st^.Flags, psfMatchBeginInitialized);
    end;

    MatchCaptures := CapturesWhile;
  end;


  r := False;
  if (not (psfMatchBeginDone in st^.Flags)) then begin
    BodyPos := st^.FoundCaptures[0].Start + st^.FoundCaptures[0].Len;
    if (ACurTokenPos < BodyPos) then begin
      CaptTextLen := BodyPos - 1; // TODO: cache
      r := MatchCaptures.FindCaptureForNextMatchPos(ACurTokenPos, st^.FoundCaptures, FndCapture);
      if Captures.FindCaptureForNextMatchPos(ACurTokenPos, st^.FoundCaptures, FndCapture2) then
        r := LeftmostFoundPattern(FndCapture, FndCapture2);
      if r then
        CaptTextLen := FndCapture.Start + st^.FoundCaptures[FndCapture.Index].Len - 1;

      if AnInitInfoOnly and ( (not r) or (FndCapture.Start > ACurTokenPos) ) then begin
        //if r then
        //  st^.SetCache(FndCapture, CaptTextLen);
        ANextTokenPos := ACurTokenPos;
        if not AnIsCalledAsParent then
          FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
        {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken exit inside MatchBegin ',BodyPos  ]); {$ENDIF}
        exit;
      end;
    end;
  end;

  if (not r) and (ACurTokenPos >=  BodyPos) then begin
    Include(st^.Flags, psfMatchBeginDone);
    if ContentAttribInfo.TokId >= 0 then
      st^.CurrentTokenAttribInfo := ContentAttribInfo;
  end;


  if AnInitInfoOnly
  then ANextTokenPos := ACurTokenPos
  else ANextTokenPos := st^.SubTextLen+ 1;

  if not r then begin
    if AnInitInfoOnly then begin
      if not AnIsCalledAsParent then
        FDeepestRecurseWasInit := True; // Next call must be NOT AnInitInfoOnly
    end
    else
    if (psfNestedinCapture in st^.Flags) and (ANextTokenPos > st^.SubTextLen)then begin
      {$IFDEF TMATE_MATCHING} debugln(['TTextMatePatternBeginEnd.NextToken  EXIT GOT TO LENGTH ', ANextTokenPos, ' ' ,st^.SubTextLen]); {$ENDIF}
      AStates.CallParentNextToken(AText, ANextTokenPos, ANextTokenPos, True);
    end;
    exit;
  end;

  AStates.Add(FndCapture, 1, CaptTextLen);
  FndCapture.Pattern.NextToken(AStates, AText, FndCapture.Start, ANextTokenPos, True);

  {$IFDEF TMATE_MATCHING}
  finally debuglnExit(['< TTextMatePatternBeginWhile.NextToken ' ]); end;
  {$ENDIF}
end;

{ TTextMatePatternRoot }

procedure TTextMatePatternRoot.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternRoot absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  ScopeName := TheOther.ScopeName;
end;

procedure TTextMatePatternRoot.InitStates(const AGrammar: TTextMateGrammar;
  var AStates: TTextMatePatternState; AParent: TTextMatePattern;
  const AText: String; ADepth: Integer);
begin
  AStates.InitForDepth(ADepth + 1);
  AStates.StateList[AStates.StateIdx - ADepth].Pattern := self;
  AStates.StateList[AStates.StateIdx - ADepth].SubTextBeginPos := 1;
  AStates.StateList[AStates.StateIdx - ADepth].SubTextLen := Length(AText);
  AStates.StateList[AStates.StateIdx - ADepth].CurrentTokenAttribInfo := FAttribInfo;
  AStates.StateList[AStates.StateIdx - ADepth].Flags := [];
  AStates.StateList[AStates.StateIdx - ADepth].Flags := [];
  AStates.StateList[AStates.StateIdx - ADepth].ClearCache;
  FDeepestRecurseTextAtOffset := 0;
end;

{ TTextMatePatternForwarder }

function TTextMatePatternForwarder.GetForwardTarget: TTextMatePatternBaseNested;
begin
  Result := FForwardTo.GetForwardTarget;
end;

procedure TTextMatePatternForwarder.InitStates(
  const AGrammar: TTextMateGrammar; var AStates: TTextMatePatternState;
  AParent: TTextMatePattern; const AText: String; ADepth: Integer);
begin
  // Call with own FParent
  FForwardTo.InitStates(AGrammar, AStates, FParent, AText, ADepth);

  AStates.StateList[AStates.StateIdx - ADepth].Pattern := Self;
end;

function TTextMatePatternForwarder.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
begin
  Result := FForwardTo.GetFirstMatchPos(AText, ATextStartOffset, AStateEntryP,
    APattern, AFoundStartPos, AMatchMustStartBefore);
  if APattern = FForwardTo then
    APattern := Self;
end;

procedure TTextMatePatternForwarder.CopyPatterns(
  const ASrc: TTextMatePatternArray);
var
  j: Integer;
begin
  SetLength(Patterns, Length(ASrc));
  for j := 0 to Length(Patterns) - 1 do
    Patterns[j] := ASrc[j];
end;

procedure TTextMatePatternForwarder.CopyFrom(AnOther: TTextMatePattern;
  AnIndexOffset: Integer; ANewList: TTextMatePatternList);
var
  TheOther: TTextMatePatternForwarder absolute AnOther;
begin
  inherited CopyFrom(AnOther, AnIndexOffset, ANewList);
  FForwardTo := TTextMatePatternForwarder(GetCopyFor(TheOther.FForwardTo, AnIndexOffset, ANewList));
end;

procedure TTextMatePatternForwarder.ClearDeepestRecurseData;
begin
  inherited ClearDeepestRecurseData;
  FForwardTo.ClearDeepestRecurseData;
end;

procedure TTextMatePatternForwarder.NextToken(
  var AStates: TTextMatePatternState; const AText: String;
  ACurTokenPos: integer; out ANextTokenPos: integer; AnInitInfoOnly: Boolean;
  AnIsCalledAsParent: Boolean);
begin
  FForwardTo.NextToken(AStates, AText, ACurTokenPos, ANextTokenPos, AnInitInfoOnly, AnIsCalledAsParent);
end;

function TTextMatePatternForwarder.DebugDump(AnIndent: Integer;
  AnIncludeNested: Boolean; APrefix: string): string;
var
  i: Integer;
begin
  Result := FForwardTo.DebugDump(AnIndent, False, '[' + IntToStr(FMainIndex) + ']=>' + APrefix);
  if AnIncludeNested then
    for i := 0 to Length(Patterns) - 1 do
      Result := Result + Patterns[i].DebugDump(AnIndent + 2, AnIncludeNested, APrefix);
end;

{ TTextMatePatternForwarderNewEnd }

function TTextMatePatternForwarderNewEnd.GetFirstMatchPos(const AText: String;
  const ATextStartOffset: integer; AStateEntryP: PTextMatePatternStateEntry;
  out APattern: TTextMatePattern; out AFoundStartPos: integer;
  AMatchMustStartBefore: integer): Boolean;
var
  Tmp: TRegExpr;
begin
  Tmp := TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd;
  TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd := FRegExMatchEnd;
  Result := inherited GetFirstMatchPos(AText, ATextStartOffset, AStateEntryP,
    APattern, AFoundStartPos, AMatchMustStartBefore);
  TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd := Tmp;
end;

destructor TTextMatePatternForwarderNewEnd.Destroy;
var
  be: TTextMatePatternBeginEnd;
  i: Integer;
begin
  be := TTextMatePatternBeginEnd(FForwardTo);
  if be <> nil then begin
    i := be.FEndForwarderCnt-1;
    while i >= 0 do begin
      if be.FEndForwarders[i] = Self then
        break;
      dec(i);
    end;
    if i >= 0 then begin
      move(be.FEndForwarders[i+1], be.FEndForwarders[i], i - (be.FEndForwarderCnt - 1)*SizeOf(TTextMatePatternForwarderNewEnd));
      dec(be.FEndForwarderCnt);
    end;
  end;
  FForwardTo := nil;
  FRegExMatchEnd.Free;

  inherited Destroy;
end;

procedure TTextMatePatternForwarderNewEnd.NextToken(
  var AStates: TTextMatePatternState; const AText: String;
  ACurTokenPos: integer; out ANextTokenPos: integer; AnInitInfoOnly: Boolean;
  AnIsCalledAsParent: Boolean);
var
  Tmp: TRegExpr;
begin
  Tmp := TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd;
  TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd := FRegExMatchEnd;
  inherited NextToken(AStates, AText, ACurTokenPos, ANextTokenPos,
    AnInitInfoOnly, AnIsCalledAsParent);
  TTextMatePatternBeginEnd(FForwardTo).FRegExMatchEnd := Tmp;
end;

{ TTextMateGrammar }

procedure TTextMateGrammar.ClearAttributeInfo(
  var AnAttribInfo: TSynAttributeInfo);
begin
  AnAttribInfo.TokId := -1;
  AnAttribInfo.TokObject := nil;
end;

function TTextMateGrammar.CreatePatternObject(AClass: TTextMatePatternClass;
  InitRefCnt: Integer): TTextMatePattern;
begin
  if FMainPatternList.Count = FMainPatternList.Capacity then
    FMainPatternList.Capacity := FMainPatternList.Capacity +
    Min(Max(250, FMainPatternList.Capacity shr 2), 8000);
  Result := AClass.Create;
  Result.FMainIndex := FMainPatternList.Add(Result);
  Result.FRefCount := InitRefCnt;
end;

procedure TTextMateGrammar.DoOtherGrammarFreed(Sender: TObject);
var
  i: Integer;
begin
  ClearGrammar;
  i := FOtherGrammars.IndexOfData(TTextMateGrammar(Sender));
  if i >= 0 then
    FOtherGrammars.Delete(i);
end;

function TTextMateGrammar.GetJson(AGrammarDef: String): TJSONObject;
var
  i: Integer;
  R: TJSONData;
  stream: TStringStream;
  AXml: TXMLDocument;
  jparser: TJSONParser;
begin
  Result := nil;
  R := nil;
  FParserError := 'Unknown Error';
  if AGrammarDef = '' then
    exit;

  try
    i := 1;
    while AGrammarDef[i] in [' ', #9] do inc(i);
    if AGrammarDef[i] = '<' then begin
      stream := nil;
      AXml := nil;
      try
        stream := TStringStream.Create(AGrammarDef);
        ReadXMLFile(AXml, stream);
        R := PListXml2Json(AXml);
      finally
        FreeAndNil(AXml);
        FreeAndNil(stream);
      end;
    end
    else begin
      jparser := TJSONParser.Create(AGrammarDef, [joUTF8,joComments,joIgnoreTrailingComma
          {$IF FPC_VERSION >= 030202} , joBOMCheck {$ENDIF}
          {,joIgnoreDuplicates}]);
      try
        R := jparser.Parse;
      finally
        jparser.Free;
      end;
    end;
  except
    on E: Exception do begin
      R := nil;
      FParserError := 'Error: ' + E.message;
    end;
  end;

  if R is TJSONObject then
    Result := TJSONObject(R);
  if Result <> nil then
    FParserError := '';
end;

procedure TTextMateGrammar.ParseLanguage(ALangDef: TJSONData);
begin
  //
end;

function TTextMateGrammar.IsInclude(APatternJson: TJSONObject): boolean;
begin
  Result := APatternJson.IndexOfName('include') >= 0;
end;

function TTextMateGrammar.IncludeName(APatternJson: TJSONObject; out
  AnOtherScopeName: string): string;
var
  j: TJSONData;
  i: SizeInt;
begin
  j := APatternJson['include'];
  if not (j is TJSONString) then
    raise TTextMateGrammarException.Create('include is not string', 'include');
  Result := TJSONString(APatternJson['include']).AsString;

  i := pos('#', Result);
  if i < 1 then i := Length(Result)+1;
  AnOtherScopeName := copy(Result, 1, i-1);
  system.Delete(Result, 1, i);
  if (Result = '') and (AnOtherScopeName = '') then
    raise TTextMateGrammarException.Create('invalid include without ref', 'include');
  if AnOtherScopeName = '$self' then
    AnOtherScopeName := '';
  if (AnOtherScopeName <> '') and (FOtherGrammars.IndexOf(AnOtherScopeName) < 0) then
    FOtherGrammars.Add(AnOtherScopeName);
end;

function TTextMateGrammar.ResolveInclude(APatternJson: TJSONObject
  ): TTextMatePattern;
var
  n, OtherScopeName: String;
begin
  Result := nil;
  n := IncludeName(APatternJson, OtherScopeName);
  if OtherScopeName <> '' then begin
    Result := CreatePatternObject(TTextMatePatternInclude);
    TTextMatePatternInclude(Result).FSourceScope := OtherScopeName;
    TTextMatePatternInclude(Result).FSourceKey := n;
    exit
  end;

  if n = '' then begin
    Result := FRootPattern;
  end
  else
  begin
    if FPatternRepo.IndexOf(n) >= 0 then
      Result := FPatternRepo[n];
  end;
  if Result = nil then begin
    debugln('unknown include ' + n);

    if FTheEmptyPattern = nil then
      FTheEmptyPattern := TTextMatePatternNestedList.Create;
    Result := FTheEmptyPattern;
    if FMissingIncludes <> '' then FMissingIncludes := FMissingIncludes + ', ';
    FMissingIncludes := FMissingIncludes + n;
  end;
end;

procedure TTextMateGrammar.CopyPatterns(AnOtherGrammar: TTextMateGrammar;
  AnIncludeName: String);
var
  InsPos, i: Integer;
  k: String;
  o: TTextMatePattern;
begin
  InsPos := FMainPatternList.Count;
  for i := 0 to AnOtherGrammar.FMainPatternCount - 1 do
    CreatePatternObject(TTextMatePatternClass(AnOtherGrammar.FMainPatternList[i].ClassType));

  for i := 0 to AnOtherGrammar.FMainPatternCount - 1 do
    FMainPatternList[InsPos + i].CopyFrom(AnOtherGrammar.FMainPatternList[i], InsPos, FMainPatternList);

  FPatternRepo.Add(AnIncludeName+'#', TTextMatePattern.GetCopyFor(AnOtherGrammar.RootPattern, InsPos, FMainPatternList));
  for i := 0 to AnOtherGrammar.FPatternRepo.Count - 1 do begin
    k := AnOtherGrammar.FPatternRepo.Keys[i];
    if pos('#', k) > 0 then
      continue;
    o := TTextMatePattern.GetCopyFor(AnOtherGrammar.FPatternRepo.Data[i], InsPos, FMainPatternList);
    FPatternRepo.Add(AnIncludeName+'#'+k, o);
  end;
end;

function TTextMateGrammar.CreatePattern(AParent: TTextMatePattern;
  APatternJson: TJSONObject; AllowPatternOnly: Boolean): TTextMatePattern;
begin
  if APatternJson.IndexOfName('while') >= 0 then begin
    if APatternJson.IndexOfName('begin') < 0 then
      raise TTextMateGrammarException.Create('invalid pattern - missing begin for while');
    Result := CreatePatternObject(TTextMatePatternBeginWhile);
  end
  else
  if APatternJson.IndexOfName('end') >= 0 then begin
    if APatternJson.IndexOfName('begin') < 0 then
      raise TTextMateGrammarException.Create('invalid pattern - missing begin for end');
    Result := CreatePatternObject(TTextMatePatternBeginEnd);
  end
  else
  if APatternJson.IndexOfName('match') >= 0 then begin
    Result := CreatePatternObject(TTextMatePatternMatch);
  end
  else
  if AllowPatternOnly and (APatternJson.IndexOfName('patterns') >= 0) then begin
    Result := CreatePatternObject(TTextMatePatternNestedList);
  end
  else
    raise TTextMateGrammarException.Create('invalid pattern');
end;

procedure TTextMateGrammar.ReadPattern(APattern: TTextMatePattern;
  APatternJson: TJSONObject);

  // Each capture must have a () in the reg-ex, therefore the max idx is length(regex)/2
  procedure ReadCaptures(AParent: TTextMatePattern; var ACapturesArray: TTextMatePatternCaptureArray;
    AnOwner: TJSONObject; AKey: String; AMaxIndex: integer);
  var
    cnt, i, j: Integer;
    CaptObjJson, CaptItemJson: TJSONObject;
  begin
    CaptObjJson := jsKeyAsDict(AnOwner, AKey);
    //inc(FParsingCaptures);

    cnt := AMaxIndex div 2 + 1;
    SetLength(ACapturesArray, cnt);
    j := -1;
    for i := 0 to cnt-1 do begin
      try
//        ACapturesArray[i] := nil;
        if CaptObjJson.IndexOfName(IntToStr(i)) < 0 then
          continue;
        CaptItemJson :=  jsKeyAsDict(CaptObjJson, IntToStr(i));
        if i > j then
          j := i;
        ACapturesArray[i] := TTextMatePatternCapture(CreatePatternObject(TTextMatePatternCapture));
        ACapturesArray[i].FName := jsKeyAsString(CaptItemJson, 'name');
        if assigned(FOnPopulateAttributeInfo) and (ACapturesArray[i].FName <> '') then
          FOnPopulateAttributeInfo(Self, ACapturesArray[i], ACapturesArray[i].FName, ACapturesArray[i].FAttribInfo)
        else
          ClearAttributeInfo(ACapturesArray[i].FAttribInfo);
        if CaptItemJson.IndexOfName('patterns') >= 0 then
          ParsePatterns(AParent, ACapturesArray[i].Patterns, jsKeyAsArray(CaptItemJson, 'patterns'));
      except
        on E: TTextMateGrammarException do raise E.Copy.AddLocation(i).AddLocation(AKey);
        on E: Exception do raise TTextMateGrammarException.Create(E.Message, i).AddLocation(AKey);
      end;
    end;
    SetLength(ACapturesArray, j+1);
    //dec(FParsingCaptures);
  end;

var
  ptrnBW: TTextMatePatternBeginWhile absolute APattern;
  ptrnBE: TTextMatePatternBeginEnd   absolute APattern;
  ptrnM:  TTextMatePatternMatch      absolute APattern;
begin
  if APattern is TTextMatePatternBeginWhile then begin
    ptrnBW.MatchBegin  := jsKeyAsString(APatternJson, 'begin');
    ptrnBW.MatchWhile  := jsKeyAsString(APatternJson, 'while');
    ptrnBW.ContentName := jsKeyAsString(APatternJson, 'contentName');
    if assigned(FOnPopulateAttributeInfo) and (ptrnBW.ContentName <> '') then
      FOnPopulateAttributeInfo(Self, ptrnBW, ptrnBW.ContentName, ptrnBW.ContentAttribInfo)
    else
      ClearAttributeInfo(ptrnBW.ContentAttribInfo);
    if APatternJson.IndexOfName('captures') >= 0 then
      ReadCaptures(APattern, ptrnBW.Captures.CaptureArray, APatternJson, 'captures', max(Length(ptrnBW.MatchBegin), Length(ptrnBW.MatchWhile)));
    if APatternJson.IndexOfName('beginCaptures') >= 0 then
      ReadCaptures(APattern, ptrnBW.CapturesBegin.CaptureArray, APatternJson, 'beginCaptures', Length(ptrnBW.MatchBegin));
    if APatternJson.IndexOfName('whileCaptures') >= 0 then
      ReadCaptures(APattern, ptrnBW.CapturesWhile.CaptureArray, APatternJson, 'whileCaptures', Length(ptrnBW.MatchWhile));
  end
  else
  if APattern is TTextMatePatternBeginEnd then begin
    ptrnBE.MatchBegin     := jsKeyAsString(APatternJson, 'begin');
    ptrnBE.MatchEnd       := jsKeyAsString(APatternJson, 'end');
    ptrnBE.ContentName    := jsKeyAsString(APatternJson, 'contentName');
    if assigned(FOnPopulateAttributeInfo) and (ptrnBE.ContentName <> '') then
      FOnPopulateAttributeInfo(Self, ptrnBE, ptrnBE.ContentName, ptrnBE.ContentAttribInfo)
    else
      ClearAttributeInfo(ptrnBE.ContentAttribInfo);
    ptrnBE.EndPatternLast := jsKeyAsNumber(APatternJson, 'applyEndPatternLast') = 1;
    if APatternJson.IndexOfName('captures') >= 0 then
      ReadCaptures(APattern, ptrnBE.Captures.CaptureArray, APatternJson, 'captures', max(Length(ptrnBE.MatchBegin), Length(ptrnBE.MatchEnd)));
    if APatternJson.IndexOfName('beginCaptures') >= 0 then
      ReadCaptures(APattern, ptrnBE.CapturesBegin.CaptureArray, APatternJson, 'beginCaptures', Length(ptrnBE.MatchBegin));
    if APatternJson.IndexOfName('endCaptures') >= 0 then
      ReadCaptures(APattern, ptrnBE.CapturesEnd.CaptureArray, APatternJson, 'endCaptures', Length(ptrnBE.MatchEnd));
  end
  else
  if APattern is TTextMatePatternMatch then begin
    ptrnM.Match  := jsKeyAsString(APatternJson, 'match');
    if APatternJson.IndexOfName('captures') >= 0 then
      ReadCaptures(APattern, ptrnM.Captures.CaptureArray, APatternJson, 'captures', Length(ptrnM.Match));
  end
  else
  if APattern is TTextMatePatternNestedList then begin
    // no exception
  end
  else
    raise TTextMateGrammarException.Create('invalid pattern');

  if APattern is TTextMatePatternBaseNested then begin
    try
      if APatternJson.IndexOfName('patterns') >= 0 then
        ParsePatterns(APattern, ptrnBE.Patterns, jsKeyAsArray(APatternJson, 'patterns'));
    except
      on E: TTextMateGrammarException do raise E.Copy.AddLocation('patterns');
      on E: Exception do raise TTextMateGrammarException.Create(E.Message, 'patterns');
    end;
  end;

  APattern.FComment := jsKeyAsString(APatternJson, 'Fcomment');
  APattern.FName    := jsKeyAsString(APatternJson, 'name');
  if assigned(FOnPopulateAttributeInfo) and (APattern.FName <> '') then
    FOnPopulateAttributeInfo(Self, APattern, APattern.FName, APattern.FAttribInfo)
  else
    ClearAttributeInfo(APattern.FAttribInfo);

  APattern.InitRegEx;
end;

function TTextMateGrammar.ParsePattern(AParent: TTextMatePattern;
  APatternJson: TJSONObject): TTextMatePattern;
begin
  if IsInclude(APatternJson) then begin
    Result := ResolveInclude(APatternJson);
    exit;
  end;

  Result := CreatePattern(AParent, APatternJson, True);
  ReadPattern(Result, APatternJson);
end;

procedure TTextMateGrammar.ParsePatterns(AParent: TTextMatePattern;
  var APatternArray: TTextMatePatternArray; APatterns: TJSONArray);
var
  i, j: Integer;
  PtrnJson: TJSONObject;
begin
  SetLength(APatternArray, APatterns.Count);
  j := -1;
  for i := 0 to APatterns.Count -1 do begin
    try
      PtrnJson :=  jsIndexAsDict(APatterns, i);
      if jsKeyAsNumber(PtrnJson, 'disabled') = 1 then
        continue;
      inc(j);

      APatternArray[j] := ParsePattern(AParent, PtrnJson);
      if APatternArray[j].Name = '' then APatternArray[j].FDebugName := AParent.FDebugName+'['+IntToStr(j)+']';

    except
      on E: TTextMateGrammarException do raise E.Copy.AddLocation(i);
      on E: Exception do raise TTextMateGrammarException.Create(E.Message, i);
    end;
  end;
  SetLength(APatternArray, j+1);
end;

function TTextMateGrammar.GetCurrentTokenLen: integer;
begin
  Result := FNextTokenPos - FCurrentTokenPos;
end;

function TTextMateGrammar.GetCurrentPatternIndex: Integer;
begin
  if FCurrentState.StateIdx < 0 then
    exit(-1);
  FCurrentState.ValidateParentChain(Self);
  FCurrentPattern := FCurrentState.Pattern[0];
  Result := FCurrentPattern.FMainIndex;
end;

constructor TTextMateGrammar.Create;
begin
  FMainPatternList := TTextMatePatternList.Create(True);
  FPatternRepo := TTextMatePatternMap.Create(False);
  FCurrentState.InitForDepth(0);
  FOtherGrammars := TOtherGrammarMap.Create(False);
end;

destructor TTextMateGrammar.Destroy;
var
  i: Integer;
begin
  for i := 0 to FOtherGrammars.Count-1 do
    if FOtherGrammars.Data[i] <> nil then
      FOtherGrammars.Data[i].RemoveFreeNotification(@DoOtherGrammarFreed);
  inherited Destroy;

  ClearGrammar;
  FMainPatternList.Free;
  FPatternRepo.Free;
  FTheEmptyPattern.Free;
  FOtherGrammars.Free;
  FRegExMatchFoldBegin.Free;
  FRegExMatchFoldEnd.Free;
end;

procedure TTextMateGrammar.ParseGrammar(AGrammarDef: String);
type
  TIndexNameMap = specialize TFPGMap<string, string>;
var
  JSonDef, RepoJson, EntryJSon: TJSONObject;
  i, j: Integer;
  EntryName, InclName, SourceName: String;
  p: TTextMatePattern;
  InclList: TIndexNameMap;
begin
  ClearGrammar;
  FRootPattern := TTextMatePatternRoot(CreatePatternObject(TTextMatePatternRoot));
  if assigned(FOnPopulateAttributeInfo) then
    FOnPopulateAttributeInfo(Self, FRootPattern, 'Default-text', FRootPattern.FAttribInfo); // dash in name

  FParserError := 'Unknown Error';
  JSonDef := nil;
  try
    JSonDef := GetJson(AGrammarDef);
    if JSonDef = nil then
      exit;

    FSampleText       := jsKeyAsString(JSonDef, 'sampleText');
    FSampleTextFile   := jsKeyAsString(JSonDef, 'sampleTextFile');

    FRootPattern.FName     := jsKeyAsString(JSonDef, 'name');
    FRootPattern.ScopeName := jsKeyAsString(JSonDef, 'scopeName');


    MatchFoldBegin := jsKeyAsString(JSonDef, 'foldingStartMarker');
    MatchFoldEnd := jsKeyAsString(JSonDef, 'foldingStopMarker');
    if MatchFoldBegin <> '' then
      FRootPattern.DoInitRegex(FRegExMatchFoldBegin, MatchFoldBegin, 'foldingStartMarker');
    if MatchFoldEnd <> '' then
      FRootPattern.DoInitRegex(FRegExMatchFoldEnd, MatchFoldEnd, 'foldingStopMarker');

    // language file?
    if JSonDef.IndexOfName('contributes') >= 0 then begin
      try
        ParseLanguage(JSonDef['contributes']);
      except
        on E: TTextMateGrammarException do raise E.Copy.AddLocation('contributes');
        on E: Exception do raise TTextMateGrammarException.Create(E.Message, 'contributes');
      end;
      exit;
    end;

    if JSonDef.IndexOfName('repository') >= 0 then begin
      RepoJson := jsKeyAsDict(JSonDef, 'repository');
      InclList := TIndexNameMap.Create;
      try
        try
          for i := 0 to RepoJson.Count - 1 do begin
            EntryName := RepoJson.Names[i];
            EntryJSon := jsKeyAsDict(RepoJson, EntryName);

            if not IsInclude(EntryJSon) then begin
              try
                FPatternRepo.Add(EntryName, CreatePattern(nil, EntryJSon, True));
              except
                on E: TTextMateGrammarException do raise E.Copy.AddLocation(EntryName);
                on E: Exception do raise TTextMateGrammarException.Create(E.Message, EntryName);
              end;
              continue;
            end;

            // include
            InclName := IncludeName(EntryJSon, SourceName);
            if SourceName <> '' then begin
              try
                FPatternRepo.Add(EntryName, ResolveInclude(EntryJSon));
              except
                on E: TTextMateGrammarException do raise E.Copy.AddLocation(EntryName);
                on E: Exception do raise TTextMateGrammarException.Create(E.Message, EntryName);
              end;
              continue;
            end;

            // # other entry from repo
            if FPatternRepo.IndexOf(InclName) >=0 then begin
              p := FPatternRepo[InclName];
              if p <> nil then begin
                FPatternRepo.Add(EntryName, p);
                continue;
              end;
            end;

            InclList.Add(EntryName, InclName);
          end;

          j := InclList.Count;
          while j > 0 do begin
            for i := InclList.Count - 1 downto 0 do begin
              EntryName := InclList.Keys[i];
              InclName  := InclList.Data[i];
              if FPatternRepo.IndexOf(InclName) >=0 then begin
                p := FPatternRepo[InclName];
                if p <> nil then begin
                  FPatternRepo.Add(EntryName, p);
                  InclList.Delete(i);
                  continue;
                end;
              end;
            end;
            if j = InclList.Count then
              raise Exception.Create('circular includes');
            j := InclList.Count;
          end;

        finally
          InclList.Free;
        end;

        try
          for i := 0 to RepoJson.Count - 1 do begin
            EntryName := RepoJson.Names[i];
            EntryJSon := jsKeyAsDict(RepoJson, EntryName);
            if IsInclude(EntryJSon) then
              continue;
            p := FPatternRepo[EntryName];
            p.FDebugName := EntryName;
            ReadPattern(p, jsKeyAsDict(RepoJson, EntryName));
          end;
        except
          on E: TTextMateGrammarException do raise E.Copy.AddLocation(EntryName);
          on E: Exception do raise TTextMateGrammarException.Create(E.Message, EntryName);
        end;

      except
        on E: TTextMateGrammarException do raise E.Copy.AddLocation('repository');
        on E: Exception do raise TTextMateGrammarException.Create(E.Message, 'repository');
      end;
    end;


    try
      if JSonDef.IndexOfName('patterns') >= 0 then
        ParsePatterns(FRootPattern, FRootPattern.Patterns, jsKeyAsArray(JSonDef, 'patterns'));
    except
      on E: TTextMateGrammarException do raise E.Copy.AddLocation('patterns');
      on E: Exception do raise TTextMateGrammarException.Create(E.Message, 'patterns');
    end;

    for i := 0 to FMainPatternList.Count - 1 do
      FMainPatternList[i].FlattenNested(Self, False);
    FParserError := '';
  except
    on E: TTextMateGrammarException do begin
      FParserError := 'Error: ' + E.FullError;
    end;
    on E: Exception do begin
      FParserError := 'Error: ' + E.message;
    end;
  end;
  if (FRootPattern <> nil) then begin
    FLangName := FRootPattern.FName;
    FLanguageScopeName := FRootPattern.ScopeName;
    FMainPatternCount := FMainPatternList.Count;
  end;
  if FParserError <> '' then
    ClearGrammar;

  JSonDef.Free;
end;

procedure TTextMateGrammar.ResolveExternalIncludes;
var
  o: TTextMateGrammar;
  i: Integer;
begin
  if (OnGetIncludedGrammar = nil) or (FOtherGrammars.Count = 0) then
    exit;

  try
    for i := 0 to FOtherGrammars.Count - 1 do begin
      o := FOnGetIncludedGrammar(Self, FOtherGrammars.Keys[i]);
      if o = nil then begin
        if FMissingIncludes <> '' then FMissingIncludes := FMissingIncludes + ', ';
        FMissingIncludes := FMissingIncludes + FOtherGrammars.Keys[i];
        continue;
      end;
      o.AddFreeNotification(@DoOtherGrammarFreed);
      FOtherGrammars.Data[i] := o;
      CopyPatterns(o, FOtherGrammars.Keys[i]);
    end;

    for i := 0 to FMainPatternList.Count - 1 do
      FMainPatternList[i].FlattenNested(Self, True);
  except
    on E: TTextMateGrammarException do begin
      FParserError := 'Error: ' + E.FullError;
    end;
    on E: Exception do begin
      FParserError := 'Error: ' + E.message;
    end;
  end;
end;

procedure TTextMateGrammar.ClearGrammar;
begin
  FCurrentState.InitForDepth(0);
  FCurrentState.Grammar := Self;
  FRootPattern := nil;
  FMainPatternList.Clear;
  FMainPatternCount := 0;
  FPatternRepo.Clear;
  FMissingIncludes := '';
end;

function TTextMateGrammar.DebugDump(AnIndent: Integer; AnIncludeNested: Boolean
  ): string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to FPatternRepo.Count - 1 do
    Result := Result + StringOfChar(' ', AnIndent) + '### ' + FPatternRepo.Keys[i] + LineEnding
            + FPatternRepo.Data[i].DebugDump(AnIndent + 2, AnIncludeNested);
  if Result <> '' then
    Result := StringOfChar(' ', AnIndent) + '===== REPO =====' + LineEnding
            + Result
            + StringOfChar(' ', AnIndent) + '===== ROOT =====' + LineEnding;
  Result := Result + FRootPattern.DebugDump(AnIndent, AnIncludeNested);
end;

procedure TTextMateGrammar.SetLine(const AText: String;
  const AnInitialPatternIndex: Integer);
begin
  FLineText := AText;
  if (AnInitialPatternIndex < 0) or (AnInitialPatternIndex >= FMainPatternList.Count) then
    FCurrentPattern := FRootPattern
  else
    FCurrentPattern := FMainPatternList[AnInitialPatternIndex];

  FCurrentState.ClearRecurseData; // the previous state
  FCurrentState.Grammar := Self;
  FCurrentPattern.InitStates(Self, FCurrentState, AText);
  FCurrentTokenPos := 1;
  FNextTokenPos := 1;
end;

procedure TTextMateGrammar.First;
begin
  if FCurrentPattern = nil then
    exit;

  FCurrentPattern.NextToken(FCurrentState, FLineText, 1, FNextTokenPos, True);
  FCurrentPattern := FCurrentState.Pattern[0];
  assert(FNextTokenPos=1, 'TTextMateGrammar.Next: FNextTokenPos=1');

  if FLineText <> '' then
    Next;
end;

procedure TTextMateGrammar.Next;
begin
  if FCurrentPattern = nil then
    exit;

  FCurrentPattern.GetCurrentTokenInfo(FCurrentState, FOnCheckAttributeInfo, FCurrentTokenKind, FCurrentAttrib);

  FCurrentTokenPos := FNextTokenPos;
  if FCurrentTokenPos > Length(FLineText) then
    exit;

  FCurrentPattern.NextToken(FCurrentState, FLineText, FCurrentTokenPos, FNextTokenPos);
  if FNextTokenPos < 0 then FNextTokenPos := Length(FLineText) + 1;
  FCurrentPattern := FCurrentState.Pattern[0];
  //assert((FNextTokenPos > FCurrentTokenPos) or (FLineText=''), 'TTextMateGrammar.Next: (FNextTokenPos > FCurrentTokenPos) or (FLineText='')');
  assert((FNextTokenPos >= FCurrentTokenPos) or (FLineText=''), 'TTextMateGrammar.Next: (FNextTokenPos > FCurrentTokenPos) or (FLineText='')');
  assert(FCurrentPattern<>nil, 'TTextMateGrammar.Next: FCurrentPattern<>nil');

  if FNextTokenPos <= FCurrentTokenPos then FNextTokenPos := Length(FLineText) + 1; // error, skip line
end;

function TTextMateGrammar.IsAtEol: boolean;
begin
  Result := FCurrentTokenPos > Length(FLineText);
end;

procedure TTextMateGrammar.NextToEol;
var
  c, n: integer;
begin
  c := 0;
  FCurrentState.Pattern[0].NextToken(FCurrentState, FLineText, 1, n);
  while (c < n) and (n <= Length(FLineText)) do begin
    c := n;
    FCurrentState.Pattern[0].NextToken(FCurrentState, FLineText, c, n);
  end;
  FCurrentPattern := FCurrentState.Pattern[0];
  FCurrentTokenPos := Length(FLineText) + 1;
end;

function TTextMateGrammar.IsFoldBegin: boolean;
begin
  if MatchFoldBegin = '' then
    exit(false);
  try
    FRegExMatchFoldBegin.InputString := FLineText;
    Result := FRegExMatchFoldBegin.Exec;
  except
    Result := False;
  end;
end;

function TTextMateGrammar.IsFoldEnd: boolean;
begin
  if MatchFoldEnd = '' then
    exit(false);
  try
    FRegExMatchFoldEnd.InputString := FLineText;
    Result := FRegExMatchFoldEnd.Exec;
  except
    Result := False;
  end;
end;

end.

