unit uengShaderFileParser; {$mode objfpc}{$H+} {$I uengShaderFile.inc} interface uses Classes, SysUtils, uengShaderFileTypes {$IFDEF SHADER_FILE_USE_BITSPACE_UTILS} , uutlGenerics {$ELSE} , fgl {$ENDIF} ; type //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TengTokenParameter = packed record Name: String; Quoted: Boolean; Line: Integer; Col: Integer; end; TengTokenParameterList = class(specialize TutlSimpleList) private fFilename: String; public property Filename: String read fFilename; constructor Create(const aFilename: String); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// TengParseArgs = class private fFilename: String; fCode: TStringList; fLineLength: Integer; fLineCount: Integer; fCurrentLine: String; fCurrentChar: Char; fCol: Integer; fLine: Integer; procedure SetCol(const aValue: Integer); procedure SetLine(const aValue: Integer); function GetEndOfLine: Boolean; function GetEndOfFile: Boolean; function GetCode: TStrings; public property Code: TStrings read GetCode; property LineLength: Integer read fLineLength; property LineCount: Integer read fLineCount; property CurrentLine: String read fCurrentLine; property CurrentChar: Char read fCurrentChar; property EndOfLine: Boolean read GetEndOfLine; property EndOfFile: Boolean read GetEndOfFile; property Filename: String read fFilename; property Col: Integer read fCol write SetCol; property Line: Integer read fLine write SetLine; procedure NextCol; procedure NextLine; function ExtractToken(const aSender: TObject; const aParameters: TengTokenParameterList): Boolean; function GetTokenPreview(const aSender: TObject; var aToken: String): Boolean; constructor Create(const aStream: TStream; const aFilename: String); destructor Destroy; override; end; implementation uses uengShaderFileConstants; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TengTokenParameterList//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TengTokenParameterList.Create(const aFilename: String); begin inherited Create(true); fFilename := aFilename; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TengParseArgs///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TengParseArgs.SetCol(const aValue: Integer); begin fCol := aValue; if not EndOfLine then fCurrentChar := fCurrentLine[fCol] else fCurrentChar := #0; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TengParseArgs.SetLine(const aValue: Integer); begin fLine := aValue; if not EndOfFile then begin fCurrentLine := fCode[fLine]; fLineLength := Length(fCurrentLine); end else begin fCurrentLine := ''; fLineLength := 0; end; Col := 1; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TengParseArgs.GetEndOfLine: Boolean; begin result := (fCol > fLineLength) end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TengParseArgs.GetEndOfFile: Boolean; begin result := (fLine >= fLineCount); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TengParseArgs.NextCol; begin Col := Col + 1; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TengParseArgs.NextLine; begin Line := Line + 1; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TengParseArgs.ExtractToken(const aSender: TObject; const aParameters: TengTokenParameterList): Boolean; type TCharType = ( ctUnknown, ctValidTokenChar, ctInvalidTokenChar ); TParseFlag = ( pfQuote, pfInToken, pfIsComment, pfCommentTokenAdded ); TParseFlags = set of TParseFlag; var pf: TParseFlags; ct: TCharType; s: String; pLine, pCol, oldLine, oldCol: Integer; procedure AddPart(const aTrimAndCheck: Boolean = true); var len: Integer; p: TengTokenParameter; begin if aTrimAndCheck then s := Trim(s); if not aTrimAndCheck or (s <> '') then begin len := Length(s); if aTrimAndCheck and ( (s[1] = TOKEN_CHAR_QUOTE) and (s[len] = TOKEN_CHAR_QUOTE)) then begin if not (s[1] = TOKEN_CHAR_QUOTE) then raise EengShaderPart.Create('missing leading quote char', Line, Col, Filename, aSender); if not (s[len] = TOKEN_CHAR_QUOTE) then raise EengShaderPart.Create('missing trailing quote char', Line, Col, Filename, aSender); delete(s, len, 1); delete(s, 1, 1); p.Quoted := true; end else p.Quoted := false; p.Name := s; p.Line := pLine; p.Col := pCol; aParameters.Add(p); end; s := ''; pLine := Line; pCol := Col; ct := ctUnknown; end; begin result := false; aParameters.Clear; if (CurrentChar <> TOKEN_CHAR_BEGIN) or (Col + 1 > LineLength) or ( (CurrentLine[Col+1] <> TOKEN_CHAR_IDENT) and (CurrentLine[Col+1] <> TOKEN_CHAR_COMMENT)) then exit; result := true; s := ''; ct := ctUnknown; oldLine := Line; oldCol := Col; pf := []; if (CurrentLine[Col+1] = TOKEN_CHAR_COMMENT) then Include(pf, pfIsComment); AddPart; // initialize while not EndOfFile do begin while not EndOfLine do begin case CurrentChar of TOKEN_CHAR_BEGIN: begin if (pfQuote in pf) then s := s + CurrentChar else if not (pfInToken in pf) then Include(pf, pfInToken) else EengShaderPart.Create('invalid char in token', Line, Col, Filename, aSender); end; TOKEN_CHAR_END: begin if not (pfQuote in pf) then begin AddPart(not (pfIsComment in pf)); NextCol; if (aParameters.Count <= 0) or (aParameters[0].Name = TOKEN_CHAR_IDENT) then raise EengShaderPart.Create('empty token', Line, Col, Filename, aSender); exit; end else s := s + CurrentChar; end; TOKEN_CHAR_QUOTE: begin if not (pfQuote in pf) and not (pfIsComment in pf) then AddPart; s := s + CurrentChar; if (pfQuote in pf) then exclude(pf, pfQuote) else include(pf, pfQuote); if not (pfQuote in pf) and not (pfIsComment in pf) then AddPart; end; TOKEN_CHAR_COMMENT: begin s := s + CurrentChar; if (pfIsComment in pf) and not (pfCommentTokenAdded in pf) then begin include(pf, pfCommentTokenAdded); AddPart(false); end; end; else if not (pfQuote in pf) and not (pfIsComment in pf) then begin if (CurrentChar in TOKEN_SPLIT_CHARS) then begin AddPart; end else if (CurrentChar in VALID_TOKEN_CHARS) then begin if (ct <> ctValidTokenChar) then AddPart; ct := ctValidTokenChar; end else begin if (ct <> ctInvalidTokenChar) then AddPart; ct := ctInvalidTokenChar; end; end; s := s + CurrentChar; end; NextCol; end; s := s + sLineBreak; NextLine; end; raise EengShaderPart.Create('incomplete token', oldLine, oldCol, Filename, aSender); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TengParseArgs.GetCode: TStrings; begin result := fCode; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TengParseArgs.GetTokenPreview(const aSender: TObject; var aToken: String): Boolean; var c: Integer; begin result := false; if (CurrentChar <> TOKEN_CHAR_BEGIN) or (Col >= LineLength) or not (CurrentLine[Col + 1] in [TOKEN_CHAR_IDENT, TOKEN_CHAR_COMMENT]) then exit; result := true; aToken := CurrentChar; if (aToken = TOKEN_CHAR_COMMANT_END) then exit; c := Col + 1; case CurrentLine[c] of TOKEN_CHAR_IDENT: begin aToken := ''; repeat aToken := aToken + CurrentLine[c]; inc(c); until (c > LineLength) or not (CurrentLine[c] in VALID_TOKEN_CHARS); aToken := Trim(aToken); if (aToken = TOKEN_CHAR_IDENT) then raise EengInvalidToken.Create('empty token', Line, Col, Filename, aSender); end; TOKEN_CHAR_COMMENT: aToken := TOKEN_CHAR_COMMENT; end; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TengParseArgs.Create(const aStream: TStream; const aFilename: String); begin inherited Create; fFilename := aFilename; fCode := TStringList.Create; fCode.LoadFromStream(aStream); fLineCount := fCode.Count; Line := 0; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// destructor TengParseArgs.Destroy; begin FreeAndNil(fCode); inherited Destroy; end; end.