Browse Source

* Multiline strings and INCLUDESTRING(FILE)

git-svn-id: trunk@47731 -
michael 4 years ago
parent
commit
9e238f4724
2 changed files with 485 additions and 6 deletions
  1. 293 4
      packages/fcl-passrc/src/pscanner.pp
  2. 192 2
      packages/fcl-passrc/tests/tcscanner.pas

+ 293 - 4
packages/fcl-passrc/src/pscanner.pp

@@ -68,6 +68,7 @@ const
   nErrWrongSwitchToggle = 1032;
   nNoResourceSupport = 1033;
   nResourceFileNotFound = 1034;
+  nErrInvalidMultiLineLineEnding = 1035;
 
 // resourcestring patterns of messages
 resourcestring
@@ -107,6 +108,7 @@ resourcestring
   SInvalidDispatchFieldName = 'Invalid Dispatch field name';
   SErrWrongSwitchToggle = 'Wrong switch toggle, use ON/OFF or +/-';
   SNoResourceSupport = 'No support for resources of type "%s"';
+  SErrInvalidMultiLineLineEnding = 'Invalid multilinestring line ending type: use one of CR/LF/CRLF/SOURCE/PLATFORM' ;
 
 type
   TMessageType = (
@@ -296,7 +298,8 @@ type
     msPrefixedAttributes,  { Allow attributes, disable proc modifier [] }
     msOmitRTTI,            { treat class section 'published' as 'public' and typeinfo does not work on symbols declared with this switch }
     msMultiHelpers,        { off=only one helper per type, on=all }
-    msImplicitFunctionSpec { implicit function specialization }
+    msImplicitFunctionSpec, { implicit function specialization }
+    msMultiLineStrings      { Multiline strings }
     );
   TModeSwitches = Set of TModeSwitch;
 
@@ -419,14 +422,18 @@ type
   end;
 
   { TLineReader }
+  TEOLStyle = (elPlatform,elSource,elLF,elCR,elCRLF);
 
   TLineReader = class
   Private
     FFilename: string;
+  Protected
+    EOLStyle : TEOLStyle;
   public
     constructor Create(const AFilename: string); virtual;
     function IsEOF: Boolean; virtual; abstract;
     function ReadLine: string; virtual; abstract;
+    function LastEOLStyle: TEOLStyle; virtual;
     property Filename: string read FFilename;
   end;
 
@@ -575,6 +582,7 @@ const
     'FALSE', // false
     'TRUE'  // true  Note: True is <>'0'
     );
+
 type
   TMaxPrecInt = {$ifdef fpc}int64{$else}NativeInt{$endif};
   TMaxFloat = {$ifdef fpc}extended{$else}double{$endif};
@@ -738,6 +746,8 @@ type
     FModuleRow: Integer;
     FMacros: TStrings; // Objects are TMacroDef
     FDefines: TStrings;
+    FMultilineLineFeedStyle: TEOLStyle;
+    FMultilineLineTrimLeft: Integer;
     FNonTokens: TTokens;
     FOnComment: TPScannerCommentEvent;
     FOnDirective: TPScannerDirectiveEvent;
@@ -802,12 +812,16 @@ type
     procedure Error(MsgNumber: integer; const Msg: string);overload;
     procedure Error(MsgNumber: integer; const Fmt: string; Args: array of {$ifdef pas2js}jsvalue{$else}const{$endif});overload;
     procedure PushSkipMode;
+    function GetMultiLineStringLineEnd(aReader: TLineReader): string;
+
     function HandleDirective(const ADirectiveText: String): TToken; virtual;
     function HandleLetterDirective(Letter: char; Enable: boolean): TToken; virtual;
     procedure HandleBoolDirective(bs: TBoolSwitch; const Param: String); virtual;
     procedure DoHandleComment(Sender: TObject; const aComment : string); virtual;
     procedure DoHandleDirective(Sender: TObject; Directive, Param: String;
       var Handled: boolean); virtual;
+    procedure HandleMultilineStringTrimLeft(const AParam : String);
+    procedure HandleMultilineStringLineEnding(const AParam : string);
     procedure HandleIFDEF(const AParam: String);
     procedure HandleIFNDEF(const AParam: String);
     procedure HandleIFOPT(const AParam: String);
@@ -820,6 +834,7 @@ type
     procedure HandleError(Param: String); virtual;
     procedure HandleMessageDirective(Param: String); virtual;
     procedure HandleIncludeFile(Param: String); virtual;
+    procedure HandleIncludeString(Param: String); virtual;
     procedure HandleResource(Param : string); virtual;
     procedure HandleOptimizations(Param : string); virtual;
     procedure DoHandleOptimization(OptName, OptValue: string); virtual;
@@ -836,6 +851,7 @@ type
     procedure PushStackItem; virtual;
     procedure PopStackItem; virtual;
     function DoFetchTextToken: TToken;
+    function DoFetchMultilineTextToken: TToken;
     function DoFetchToken: TToken;
     procedure ClearFiles;
     Procedure ClearMacros;
@@ -909,7 +925,8 @@ type
     property SkipGlobalSwitches: Boolean read FSkipGlobalSwitches write FSkipGlobalSwitches;
     property MaxIncludeStackDepth: integer read FMaxIncludeStackDepth write FMaxIncludeStackDepth default DefaultMaxIncludeStackDepth;
     property ForceCaret : Boolean read GetForceCaret;
-
+    Property MultilineLineFeedStyle : TEOLStyle Read FMultilineLineFeedStyle Write FMultilineLineFeedStyle;
+    Property MultilineLineTrimLeft : Integer Read FMultilineLineTrimLeft Write FMultilineLineTrimLeft;
     property LogEvents : TPScannerLogEvents read FLogEvents write FLogEvents;
     property OnLog : TPScannerLogHandler read FOnLog write FOnLog;
     property OnFormatPath: TPScannerFormatPathEvent read FOnFormatPath write FOnFormatPath;
@@ -1102,7 +1119,8 @@ const
     'PREFIXEDATTRIBUTES',
     'OMITRTTI',
     'MULTIHELPERS',
-    'IMPLICITFUNCTIONSPECIALIZATION'
+    'IMPLICITFUNCTIONSPECIALIZATION',
+    'MULTILINESTRINGS'
     );
 
   LetterSwitchNames: array['A'..'Z'] of string=(
@@ -2368,6 +2386,17 @@ end;
 constructor TLineReader.Create(const AFilename: string);
 begin
   FFileName:=AFileName;
+  if LineEnding=#13 then
+    EOLStyle:=elCR
+  else if LineEnding=#13#10 then
+    EOLStyle:=elCRLF
+   else
+    EOLStyle:=elLF
+end;
+
+function TLineReader.LastEOLStyle: TEOLStyle;
+begin
+  Result:=EOLStyle;
 end;
 
 { ---------------------------------------------------------------------
@@ -2456,11 +2485,20 @@ begin
     EOL:=(FContent[FPos] in [#10,#13]);
   until isEOF or EOL;
   If EOL then
+    begin
+    if FContent[FPOS]=#10 then
+      EOLSTYLE:=elLF
+    else
+      EOLStyle:=elCR;
     Result:=Copy(FContent,LPos,FPos-LPos)
+    end
   else
     Result:=Copy(FContent,LPos,FPos-LPos+1);
   If (not isEOF) and (FContent[FPos]=#13) and (FContent[FPos+1]=#10) then
+    begin
     inc(FPos);
+    EOLStyle:=elCRLF;
+    end;
 end;
 
 { TFileStreamLineReader }
@@ -3045,7 +3083,7 @@ begin
       Result:=tkoperator;
 end;
 
-Procedure TPascalScanner.PopStackItem;
+procedure TPascalScanner.PopStackItem;
 
 var
   IncludeStackItem: TIncludeStackItem;
@@ -3300,6 +3338,167 @@ begin
     [FormatPath(CurFilename),CurRow,CurColumn,FLastMsg]);
 end;
 
+function TPascalScanner.GetMultiLineStringLineEnd(aReader : TLineReader) : string;
+
+Var
+  aLF : String;
+  aStyle: TEOLStyle;
+
+
+begin
+  aStyle:=MultilineLineFeedStyle;
+  if aStyle=elSource then
+    aStyle:=aReader.LastEOLStyle;
+  case aStyle of
+    elCR : aLF:=#13;
+    elCRLF : aLF:=#13#10;
+    elLF : aLF:=#10;
+    elPlatform : alf:=sLineBreak;
+  else
+    aLF:=#10;
+  end;
+  Result:=aLF;
+end;
+
+function TPascalScanner.DoFetchMultilineTextToken:TToken;
+
+var
+  StartPos,OldLength     : Integer;
+  TokenStart    : {$ifdef UsePChar}PChar{$else}integer{$endif};
+  {$ifndef UsePChar}
+  s: String;
+  l: integer;
+  {$endif}
+
+
+  Procedure AddToCurString(addLF : Boolean);
+  var
+    SectionLength,i : Integer;
+    aLF : String;
+
+  begin
+    i:=MultilineLineTrimLeft;
+    if I=-1 then
+      I:=StartPos+1;
+    if I>0 then
+      begin
+      While ({$ifdef UsePChar} TokenStart^{$ELSE}FCurLine[TokenStart]{$ENDIF} in [' ',#9]) and (TokenStart<=FTokenPos) and (I>0) do
+        begin
+        Inc(TokenStart);
+        Dec(I);
+        end;
+      end
+    else if I=-2 then
+      begin
+      While ({$ifdef UsePChar} TokenStart^{$ELSE}FCurLine[TokenStart]{$ENDIF} in [' ',#9]) and (TokenStart<=FTokenPos) do
+        Inc(TokenStart);
+      end;
+
+    SectionLength := FTokenPos - TokenStart+Ord(AddLF);
+    {$ifdef UsePChar}
+    SetLength(FCurTokenString, OldLength + SectionLength);
+    if SectionLength > 0 then
+      Move(TokenStart^, FCurTokenString[OldLength + 1], SectionLength);
+    {$else}
+    FCurTokenString:=FCurTokenString+copy(FCurLine,TokenStart,SectionLength);
+    {$endif}
+    if AddLF then
+      begin
+      GetMultiLineStringLineEnd(FCurSourceFile);
+      FCurTokenString:=FCurTokenString+aLF;
+      Inc(OldLength,Length(aLF));
+      end;
+    Inc(OldLength, SectionLength);
+  end;
+
+begin
+  Result:=tkEOF;
+  OldLength:=0;
+  FCurTokenString := '';
+  {$ifndef UsePChar}
+  s:=FCurLine;
+  l:=length(s);
+  StartPos:=FTokenPos;
+  {$ELSE}
+  StartPos:=FTokenPos-PChar(FCurLine);
+  {$endif}
+
+  repeat
+    {$ifndef UsePChar}
+    if FTokenPos>l then break;
+    {$endif}
+    case {$ifdef UsePChar}FTokenPos[0]{$else}s[FTokenPos]{$endif} of
+      '^' :
+        begin
+        TokenStart := FTokenPos;
+        Inc(FTokenPos);
+        if {$ifdef UsePChar}FTokenPos[0] in Letters{$else}(FTokenPos<l) and (s[FTokenPos] in Letters){$endif} then
+          Inc(FTokenPos);
+        if Result=tkEOF then Result := tkChar else Result:=tkString;
+        end;
+      '#':
+        begin
+        TokenStart := FTokenPos;
+        Inc(FTokenPos);
+        if {$ifdef UsePChar}FTokenPos[0]='$'{$else}(FTokenPos<l) and (s[FTokenPos]='$'){$endif} then
+        begin
+          Inc(FTokenPos);
+          repeat
+            Inc(FTokenPos);
+          until {$ifdef UsePChar}not (FTokenPos[0] in HexDigits){$else}(FTokenPos>l) or not (s[FTokenPos] in HexDigits){$endif};
+        end else
+          repeat
+            Inc(FTokenPos);
+          until {$ifdef UsePChar}not (FTokenPos[0] in Digits){$else}(FTokenPos>l) or not (s[FTokenPos] in Digits){$endif};
+        if Result=tkEOF then Result := tkChar else Result:=tkString;
+        end;
+      '`':
+        begin
+          TokenStart := FTokenPos;
+          Inc(FTokenPos);
+
+          while true do
+          begin
+            if {$ifdef UsePChar}FTokenPos[0] = '`'{$else}(FTokenPos<=l) and (s[FTokenPos]=''''){$endif} then
+              if {$ifdef UsePChar}FTokenPos[1] = '`'{$else}(FTokenPos<l) and (s[FTokenPos+1]=''''){$endif} then
+                Inc(FTokenPos)
+              else
+                break;
+
+            if {$ifdef UsePChar}FTokenPos[0] = #0{$else}FTokenPos>l{$endif} then
+              begin
+              FTokenPos:=FTokenPos-1;
+              AddToCurString(true);
+              // Writeln('Curtokenstring : >>',FCurTOkenString,'<<');
+              if not Self.FetchLine then
+                Error(nErrOpenString,SErrOpenString);
+              // Writeln('Current line is now : ',FCurLine);
+              {$ifndef UsePChar}
+              s:=FCurLine;
+              l:=length(s);
+              {$ELSE}
+              FTokenPos:=PChar(FCurLine);
+              {$endif}
+              TokenStart:=FTokenPos;
+              end
+            else
+              Inc(FTokenPos);
+          end;
+          Inc(FTokenPos);
+          Result := tkString;
+        end;
+    else
+      Break;
+    end;
+    AddToCurString(false);
+  until false;
+  if length(FCurTokenString)>1 then
+    begin
+    FCurTokenString[1]:='''';
+    FCurTokenString[Length(FCurTokenString)]:='''';
+    end;
+end;
+
 function TPascalScanner.DoFetchTextToken:TToken;
 var
   OldLength     : Integer;
@@ -3445,6 +3644,42 @@ begin
     DoLog(mtInfo,nLogOpeningFile,SLogOpeningFile,[FormatPath(FCurFileName)],True);
 end;
 
+procedure TPascalScanner.HandleIncludeString(Param: String);
+
+var
+  NewSourceFile: TLineReader;
+  aString,aLine,aFileName : string;
+
+begin
+  Param:=Trim(Param);
+  if Length(Param)>1 then
+    begin
+    if (Param[1]='''') then
+      begin
+      if Param[length(Param)]<>'''' then
+        Error(nErrOpenString,SErrOpenString,[]);
+      Param:=copy(Param,2,length(Param)-2);
+      end;
+    end;
+  NewSourceFile := FileResolver.FindIncludeFile(Param);
+  if not Assigned(NewSourceFile) then
+    Error(nErrIncludeFileNotFound, SErrIncludeFileNotFound, [Param]);
+  try
+    AString:='';
+    While not NewSourceFile.IsEOF Do
+      begin
+      ALine:=NewSourceFile.ReadLine;
+      if aString<>'' then
+        aString:=aString+GetMultiLineStringLineEnd(NewSourceFile);
+      AString:=aString+aLine;
+      end;
+  finally
+    NewSourceFile.Free;
+  end;
+  FCurTokenString:=''''+AString+'''';
+  FCurToken:=tkString;
+end;
+
 procedure TPascalScanner.HandleResource(Param: string);
 
 Var
@@ -4169,6 +4404,11 @@ begin
         DoBoolDirective(bsHints);
       'I','INCLUDE':
         Result:=HandleInclude(Param);
+      'INCLUDESTRING','INCLUDESTRINGFILE':
+        begin
+        HandleIncludeString(Param);
+        Result:=tkString;
+        end;
       'INTERFACES':
         HandleInterfaces(Param);
       'LONGSTRINGS':
@@ -4181,6 +4421,10 @@ begin
         HandleMode(Param);
       'MODESWITCH':
         HandleModeSwitch(Param);
+      'MULTILINESTRINGLINEENDING':
+        HandleMultilineStringLineEnding(Param);
+      'MULTILINESTRINGTRIMLEFT':
+        HandleMultilineStringTrimLeft(Param);
       'NOTE':
         DoLog(mtNote,nUserDefined,SUserDefined,[Param]);
       'NOTES':
@@ -4306,6 +4550,44 @@ begin
     OnDirective(Sender,Directive,Param,Handled);
 end;
 
+procedure TPascalScanner.HandleMultilineStringTrimLeft(const AParam: String);
+
+Var
+  S : String;
+  i : integer;
+
+begin
+  S:=UpperCase(Trim(aParam));
+  Case UpperCase(S) of
+    'ALL'  : I:=-2;
+    'AUTO' : I:=-1;
+    'NONE' : I:=0;
+  else
+    If not TryStrToInt(S,I) then
+      I:=0;
+  end;
+  MultilineLineTrimLeft:=I;
+
+end;
+
+procedure TPascalScanner.HandleMultilineStringLineEnding(const AParam: string);
+
+Var
+  S : TEOLStyle;
+
+begin
+  Case UpperCase(Trim(aParam)) of
+    'CR' : s:=elCR;
+    'LF' : s:=elLF;
+    'CRLF' : s:=elCRLF;
+    'SOURCE' : s:=elSource;
+    'PLATFORM' : s:=elPlatform;
+  else
+    Error(nErrInvalidMultiLineLineEnding,sErrInvalidMultiLineLineEnding);
+  end;
+  MultilineLineFeedStyle:=S;
+end;
+
 function TPascalScanner.DoFetchToken: TToken;
 
 var
@@ -4403,6 +4685,13 @@ begin
       end;
     '#', '''':
       Result:=DoFetchTextToken;
+    '`' :
+      begin
+      If not (msMultiLineStrings in CurrentModeSwitches) then
+         Error(nErrInvalidCharacter, SErrInvalidCharacter,
+        [{$ifdef UsePChar}FTokenPos[0]{$else}s[FTokenPos]{$endif}]);
+      Result:=DoFetchMultilineTextToken;
+      end;
     '&':
       begin
       TokenStart := FTokenPos;

+ 192 - 2
packages/fcl-passrc/tests/tcscanner.pas

@@ -58,10 +58,12 @@ type
     FDoCommentCalled : Boolean;
     FComment: string;
     FPathPrefix : String;
+    FTestTokenString: String;
   protected
     procedure DoComment(Sender: TObject; aComment: String);
     procedure SetUp; override;
     procedure TearDown; override;
+    Procedure DoMultilineError;
     Function TokenToString(tk : TToken) : string;
     Procedure AssertEquals(Msg : String; Expected,Actual : TToken); overload;
     Procedure AssertEquals(Msg : String; Expected,Actual : TModeSwitch); overload;
@@ -75,6 +77,7 @@ type
     Property Scanner : TPascalScanner Read FScanner;
     // Path for source filename.
     Property PathPrefix : String Read FPathPrefix Write FPathPrefix;
+    Property TestTokenString : String Read FTestTokenString;
   published
     Procedure TestEmpty;
     procedure TestEOF;
@@ -98,6 +101,17 @@ type
     procedure TestSelf;
     procedure TestSelfNoToken;
     procedure TestString;
+    procedure TestMultilineStringError;
+    procedure TestMultilineStringSource;
+    Procedure TestMultilineStringLF;
+    Procedure TestMultilineStringCR;
+    Procedure TestMultilineStringCRLF;
+    Procedure TestMultilineStringPlatform;
+    Procedure TestMultilineLineEndingDirective;
+    Procedure TestMultilineTrimLeftDirective;
+    procedure TestMultilineStringTrimAll;
+    procedure TestMultilineStringTrimAuto;
+    procedure TestMultilineStringTrim2;
     procedure TestNumber;
     procedure TestChar;
     procedure TestCharString;
@@ -240,6 +254,9 @@ type
     Procedure TestInclude;
     Procedure TestInclude2;
     Procedure TestInclude3;
+    Procedure TestIncludeString;
+    Procedure TestIncludeStringFile;
+    Procedure TestIncludeString2Lines;
     Procedure TestUnDefine1;
     Procedure TestMacro1;
     procedure TestMacro2;
@@ -401,6 +418,7 @@ end;
 
 procedure TTestScanner.SetUp;
 begin
+  FTestTokenString:='';
   FDoCommentCalled:=False;
   FResolver:=TStreamResolver.Create;
   FResolver.OwnsStreams:=True;
@@ -414,6 +432,11 @@ begin
   FreeAndNil(FResolver);
 end;
 
+procedure TTestScanner.DoMultilineError;
+begin
+  TestToken(pscanner.tkString,'`A '#10'multiline string`');
+end;
+
 function TTestScanner.TokenToString(tk: TToken): string;
 begin
   Result:=GetEnumName(TypeInfo(TToken),Ord(tk));
@@ -484,6 +507,7 @@ begin
   NewSource(ASource);
   tk:=FScanner.FetchToken;
   AssertEquals('Read token equals expected token.',t,tk);
+  FTestTokenString:=FScanner.CurTokenString;
   if CheckEOF then
     begin
     tk:=FScanner.FetchToken;
@@ -522,7 +546,9 @@ begin
     tk:=FScanner.FetchToken;
     AssertEquals(Format('Read token %d equals expected token.',[i]),t[i],tk);
     if tk=tkIdentifier then
-      LastIdentifier:=FScanner.CurtokenString;
+      LastIdentifier:=FScanner.CurtokenString
+    else if tk=tkString then
+      fTestTokenString:=FScanner.CurTokenString;
     end;
   if CheckEOF then
     begin
@@ -663,6 +689,142 @@ begin
   TestToken(pscanner.tkString,'''A string''');
 end;
 
+procedure TTestScanner.TestMultilineStringError;
+begin
+  AssertException('Need modeswitch',EScannerError,@DoMultilineError);
+end;
+
+procedure TTestScanner.TestMultilineStringSource;
+
+const
+  S = '''AB'#13#10'CD''';
+
+begin
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elSource;
+  DoTestToken(pscanner.tkString,'`AB'#13#10'CD`');
+  AssertEquals('Correct lineending',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineStringLF;
+
+const
+  S = '''AB'#10'CD''';
+
+begin
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elLF;
+  DoTestToken(pscanner.tkString,'`AB'#13#10'CD`');
+  AssertEquals('Correct lineending',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineStringCR;
+const
+  S = '''AB'#13'CD''';
+
+begin
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elCR;
+  DoTestToken(pscanner.tkString,'`AB'#10'CD`');
+  AssertEquals('Correct lineending',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineStringCRLF;
+const
+  S = '''AB'#13#10'CD''';
+
+begin
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elCRLF;
+  DoTestToken(pscanner.tkString,'`AB'#10'CD`');
+  AssertEquals('Correct lineending',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineStringPlatform;
+
+const
+  S = '''AB'+sLineBreak+'CD''';
+
+begin
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elPlatform;
+  DoTestToken(pscanner.tkString,'`AB'#13#10'CD`');
+  AssertEquals('Correct lineending',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineLineEndingDirective;
+begin
+  AssertTrue('Default platform', FSCanner.MultilineLineFeedStyle=elPlatform);
+  TestTokens([tkComment],'{$MULTILINESTRINGLINEENDING CR}');
+  AssertTrue('CR', FSCanner.MultilineLineFeedStyle=elCR);
+  TestTokens([tkComment],'{$MULTILINESTRINGLINEENDING LF}');
+  AssertTrue('LF', FSCanner.MultilineLineFeedStyle=elLF);
+  TestTokens([tkComment],'{$MULTILINESTRINGLINEENDING CRLF}');
+  AssertTrue('CRLF', FSCanner.MultilineLineFeedStyle=elCRLF);
+  TestTokens([tkComment],'{$MULTILINESTRINGLINEENDING SOURCE}');
+  AssertTrue('SOURCE', FSCanner.MultilineLineFeedStyle=elSOURCE);
+  TestTokens([tkComment],'{$MULTILINESTRINGLINEENDING PLATFORM}');
+  AssertTrue('Platform', FSCanner.MultilineLineFeedStyle=elPlatform);
+
+end;
+
+procedure TTestScanner.TestMultilineTrimLeftDirective;
+begin
+  AssertTrue('Default', FSCanner.MultilineLineTrimLeft=0);
+  TestTokens([tkComment],'{$MULTILINESTRINGTRIMLEFT 1}');
+  AssertTrue('1', FSCanner.MultilineLineTrimLeft=1);
+  TestTokens([tkComment],'{$MULTILINESTRINGTRIMLEFT 2}');
+  AssertTrue('2', FSCanner.MultilineLineTrimLeft=2);
+  TestTokens([tkComment],'{$MULTILINESTRINGTRIMLEFT ALL}');
+  AssertTrue('ALL', FSCanner.MultilineLineTrimLeft=-2);
+  TestTokens([tkComment],'{$MULTILINESTRINGTRIMLEFT AUTO}');
+  AssertTrue('AUTO', FSCanner.MultilineLineTrimLeft=-1);
+end;
+
+procedure TTestScanner.TestMultilineStringTrimAll;
+
+const
+   S = '''AB'#10'CD''';
+
+begin
+  SCanner.MultilineLineTrimLeft:=-2;
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elLF;
+  DoTestToken(pscanner.tkString,'`AB'#13#10'    CD`');
+  AssertEquals('Correct trim',S,TestTokenString);
+
+end;
+
+procedure TTestScanner.TestMultilineStringTrimAuto;
+const
+   S = '''AB'#10' CD''';
+
+begin
+  SCanner.MultilineLineTrimLeft:=-1;
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elLF;
+  Scanner.SkipWhiteSpace:=True;
+  DoTestToken(pscanner.tkString,' `AB'#13#10'   CD`');
+  AssertEquals('Correct trim',S,TestTokenString);
+end;
+
+procedure TTestScanner.TestMultilineStringTrim2;
+
+const
+  S = '''AB'#10' CD''';
+  S2 = '''AB'#10'CD''';
+
+begin
+  SCanner.MultilineLineTrimLeft:=2;
+  Scanner.CurrentModeSwitches:=[msMultiLineStrings];
+  Scanner.MultilineLineFeedStyle:=elLF;
+  Scanner.SkipWhiteSpace:=True;
+  DoTestToken(pscanner.tkString,' `AB'#13#10'   CD`');
+  AssertEquals('Correct trim',S,TestTokenString);
+  DoTestToken(pscanner.tkString,' `AB'#13#10' CD`');
+  AssertEquals('Correct trim 2',S2,TestTokenString);
+end;
+
 procedure TTestScanner.TestCharString;
 
 begin
@@ -1652,6 +1814,34 @@ begin
   TestTokens([tkIf,tkTrue,tkThen,tkElse],'{$I src/myinclude1.inc} else',True,False);
 end;
 
+procedure TTestScanner.TestIncludeString;
+begin
+  FResolver.AddStream('myinclude.inc',TStringStream.Create('if true then'));
+  FScanner.SkipWhiteSpace:=True;
+  FScanner.SkipComments:=True;
+  TestTokens([tkString],'{$INCLUDESTRING myinclude.inc}',False,False);
+  AssertEquals('Correct string','''if true then''',TestTokenString)
+end;
+
+procedure TTestScanner.TestIncludeStringFile;
+begin
+  FResolver.AddStream('myinclude.inc',TStringStream.Create('if true then'));
+  FScanner.SkipWhiteSpace:=True;
+  FScanner.SkipComments:=True;
+  TestTokens([tkString],'{$INCLUDESTRINGFILE myinclude.inc}',False,False);
+  AssertEquals('Correct string','''if true then''',TestTokenString)
+end;
+
+procedure TTestScanner.TestIncludeString2Lines;
+begin
+  FResolver.AddStream('myinclude.inc',TStringStream.Create('if true then'#10'else'));
+  FScanner.SkipWhiteSpace:=True;
+  FScanner.SkipComments:=True;
+  FScanner.MultilineLineFeedStyle:=elCRLF;
+  TestTokens([tkString],'{$INCLUDESTRING myinclude.inc}',False,False);
+  AssertEquals('Correct string','''if true then'#13#10'else''',TestTokenString)
+end;
+
 procedure TTestScanner.TestUnDefine1;
 begin
   FSCanner.Defines.Add('ALWAYS');
@@ -1928,7 +2118,7 @@ begin
   DoTestToken(tkLineEnding,#$EF+#$BB+#$BF);
 end;
 
-Procedure TTestScanner.TestBooleanSwitch;
+procedure TTestScanner.TestBooleanSwitch;
 
 begin
   Scanner.CurrentBoolSwitches:=[bsHints];