View Issue Details

IDProjectCategoryView StatusLast Update
0035827FPCCompilerpublic2019-07-18 23:16
ReporterAkira1364Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
Platformx86_64OSWindowsOS Version10
Product Version3.3.1Product BuildTrunk 
Target VersionFixed in Version 
Summary0035827: [FEATURE / PATCH] An implementation of multi-line strings for FPC, as discussed on the mailing list.
DescriptionIf you haven't, you can read the (long) mailing list thread here:

https://lists.freepascal.org/pipermail/fpc-devel/2019-July/041407.html

I have attached two patch files: one that specifically includes my changes to the compiler itself, and one that adds my tests to the "tests/test" directory.

You can also view a side-by-side overall diff on my github fork branch of the compiler here:

https://github.com/graemeg/freepascal/compare/master...Akira13641:master?diff=split#files_bucket
TagsNo tags attached.
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files
  • multi_line_strings_main_rev2.patch (26,091 bytes)
    diff --git compiler/globals.pas compiler/globals.pas
    index 1f368e6..9e2a93a 100644
    --- compiler/globals.pas
    +++ compiler/globals.pas
    @@ -183,6 +183,10 @@ interface
     
              { WARNING: this pointer cannot be written as such in record token }
              pmessage : pmessagestaterecord;
    +         
    +         lineendingtype : tlineendingtype;
    +
    +         whitespacetrimcount : word;
            end;
     
         const
    @@ -574,6 +578,8 @@ interface
     {$endif defined(LLVM) and not defined(GENERIC_CPU)}
             controllertype : ct_none;
             pmessage : nil;
    +        lineendingtype : le_raw;
    +        whitespacetrimcount : 0
           );
     
         var
    @@ -1455,7 +1461,7 @@ implementation
            if localexepath='' then
             begin
               hs1 := ExtractFileName(exeName);
    -	  hs1 := ChangeFileExt(hs1,source_info.exeext);
    +          hs1 := ChangeFileExt(hs1,source_info.exeext);
     {$ifdef macos}
               FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
     {$else macos}
    diff --git compiler/globtype.pas compiler/globtype.pas
    index b6c142f..a03ff3a 100644
    --- compiler/globtype.pas
    +++ compiler/globtype.pas
    @@ -491,7 +491,8 @@ interface
              m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
              m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
              m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
    -         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
    +         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
    +         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
            );
            tmodeswitches = set of tmodeswitch;
     
    @@ -605,7 +606,18 @@ interface
              pocall_vectorcall
            );
            tproccalloptions = set of tproccalloption;
    -
    +       
    +       tlineendingtype = ({Carriage return, aka #13}
    +                          le_cr,
    +                          {Carriage return + line feed, aka #13#10}
    +                          le_crlf,
    +                          {Line feed, aka #10}
    +                          le_lf,
    +                          {Use the platform default}
    +                          le_platform,
    +                          {Use whatever is in the file}
    +                          le_raw);
    +                          
          const
            proccalloptionStr : array[tproccalloption] of string[16]=('',
                'CDecl',
    @@ -683,7 +695,8 @@ interface
              'ARRAYOPERATORS',
              'MULTIHELPERS',
              'ARRAYTODYNARRAY',
    -         'PREFIXEDATTRIBUTES'
    +         'PREFIXEDATTRIBUTES',
    +         'MULTILINESTRINGS'
              );
     
     
    diff --git compiler/msg/errore.msg compiler/msg/errore.msg
    index e42badb..b8bb9fe 100644
    --- compiler/msg/errore.msg
    +++ compiler/msg/errore.msg
    @@ -432,6 +432,12 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
     scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
     % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
     scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
    +scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or RAW.
    +% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or RAW.
    +scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
    +% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
    +scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
    +% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
     % \end{description}
     #
     # Parser
    @@ -1583,7 +1589,7 @@ parser_w_operator_overloaded_hidden_3=03347_W_Operator overload hidden by intern
     parser_e_threadvar_must_be_class=03348_E_Thread variables inside classes or records must be class variables
     % A \var{threadvar} section inside a class or record was started without it being prefixed by \var{class}.
     parser_e_only_static_members_via_object_type=03349_E_Only static methods and static variables can be referenced through an object type
    -parser_e_unbound_attribute=03350_E_Unbound custom attribute: "$1".
    +parser_e_unbound_attribute=03351_E_Unbound custom attribute: "$1".
     % A custom attribute is defined, but there is no identifier to bind it to.
     % This error occurs in a situation like the following:
     % \begin{verbatim}
    diff --git compiler/pdecobj.pas compiler/pdecobj.pas
    index c74a57f..c071ff1 100644
    --- compiler/pdecobj.pas
    +++ compiler/pdecobj.pas
    @@ -1143,7 +1143,7 @@ implementation
           procedure check_unbound_attributes;
             begin
               if assigned(rtti_attrs_def) and (rtti_attrs_def.get_attribute_count>0) then
    -            Message1(scan_e_unresolved_attribute,trtti_attribute(rtti_attrs_def.rtti_attributes[0]).typesym.prettyname);
    +            Message1(parser_e_unbound_attribute,trtti_attribute(rtti_attrs_def.rtti_attributes[0]).typesym.prettyname);
               rtti_attrs_def.free;
               rtti_attrs_def:=nil;
             end;
    diff --git compiler/scandir.pas compiler/scandir.pas
    index 9dd457b..9202247 100644
    --- compiler/scandir.pas
    +++ compiler/scandir.pas
    @@ -1003,6 +1003,41 @@ unit scandir;
               end;
           end;
     
    +    procedure dir_multilinestringlineending;
    +      var
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
    +        current_scanner.skipspace;
    +        s:=current_scanner.readid;
    +        if (s='CR') then
    +          current_settings.lineendingtype:=le_cr
    +        else if (s='CRLF') then
    +          current_settings.lineendingtype:=le_crlf
    +        else if (s='LF') then
    +          current_settings.lineendingtype:=le_lf
    +        else if (s='PLATFORM') then
    +          current_settings.lineendingtype:=le_platform
    +        else if (s='RAW') then
    +          current_settings.lineendingtype:=le_raw
    +        else
    +          Message(scan_e_unknown_lineending_type);
    +      end;
    +
    +    procedure dir_multilinestringtrimleft;
    +      var
    +        count : longint;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
    +        current_scanner.skipspace;
    +        count:=current_scanner.readval;
    +        if (count<0) or (count>65535) then
    +          Message(scan_e_trimcount_out_of_range)
    +        else
    +          current_settings.whitespacetrimcount:=count;
    +      end;
     
         procedure dir_namespace;
           var
    @@ -1973,6 +2008,8 @@ unit scandir;
             AddDirective('MMX',directive_all, @dir_mmx);
             AddDirective('MODE',directive_all, @dir_mode);
             AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
    +        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
    +        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
             AddDirective('NAMESPACE',directive_all, @dir_namespace);
             AddDirective('NODEFINE',directive_all, @dir_nodefine);
             AddDirective('NOTE',directive_all, @dir_note);
    diff --git compiler/scanner.pas compiler/scanner.pas
    index 88b2703..fb90be4 100644
    --- compiler/scanner.pas
    +++ compiler/scanner.pas
    @@ -101,6 +101,9 @@ interface
              procedure savetokenpos;
              procedure restoretokenpos;
              procedure writetoken(t: ttoken);
    +         procedure buildplatformnewlineascii(var len: longint);
    +         procedure buildplatformnewlineutf8;
    +         procedure buildplatformnewlineunicode;
              function readtoken : ttoken;
            public
               inputfile    : tinputfile;  { current inputfile list }
    @@ -146,6 +149,9 @@ interface
               { true, if we are parsing preprocessor expressions }
               in_preproc_comp_expr : boolean;
     
    +          { last character read }
    +          last_c : char;
    +
               constructor Create(const fn:string; is_macro: boolean = false);
               destructor Destroy;override;
             { File buffer things }
    @@ -3111,8 +3117,10 @@ type
                 else
                  ControllerType:=ct_none;
     {$POP}
    -           endpos:=replaytokenbuf.pos;
    -           if endpos-startpos<>expected_size then
    +            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +            whitespacetrimcount:=tokenreadword;
    +            endpos:=replaytokenbuf.pos;
    +            if endpos-startpos<>expected_size then
                  Comment(V_Error,'Wrong size of Settings read-in');
              end;
          end;
    @@ -3189,6 +3197,8 @@ type
                 if ControllerSupport then
                   tokenwriteenum(controllertype,sizeof(tcontrollertype));
     {$POP}
    +           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
    +           tokenwriteword(whitespacetrimcount);
                endpos:=recordtokenbuf.pos;
                size:=endpos-startpos;
                recordtokenbuf.seek(sizepos);
    @@ -4243,25 +4253,40 @@ type
         function tscannerfile.readquotedstring:string;
           var
             i : longint;
    -        msgwritten : boolean;
    +        msgwritten,in_multiline_string : boolean;
           begin
             i:=0;
             msgwritten:=false;
    -        if (c='''') then
    +        if (c in ['''','`']) then
               begin
    +            in_multiline_string:=(c='`');
    +            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +              begin
    +                result[0]:=chr(0);
    +                Illegal_Char(c);
    +              end;
                 repeat
                   readchar;
                   case c of
                     #26 :
                       end_of_file;
                     #10,#13 :
    -                  Message(scan_f_string_exceeds_line);
    +                  if not in_multiline_string then
    +                    Message(scan_f_string_exceeds_line);
                     '''' :
    -                  begin
    -                    readchar;
    -                    if c<>'''' then
    -                     break;
    -                  end;
    +                  if not in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'''' then
    +                       break;
    +                    end;
    +                '`' :
    +                  if in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'`' then
    +                       break;
    +                    end;
                   end;
                   if i<255 then
                     begin
    @@ -4389,7 +4414,7 @@ type
         procedure tscannerfile.skipuntildirective;
           var
             found : longint;
    -        next_char_loaded : boolean;
    +        next_char_loaded,in_multiline_string : boolean;
           begin
              found:=0;
              next_char_loaded:=false;
    @@ -4447,28 +4472,42 @@ type
                      if found=1 then
                       found:=2;
                    end;
    -             '''' :
    +             '''','`' :
                    if (current_commentstyle=comment_none) then
    -                begin
    -                  repeat
    -                    readchar;
    -                    case c of
    -                      #26 :
    -                        end_of_file;
    -                      #10,#13 :
    -                        break;
    -                      '''' :
    -                        begin
    -                          readchar;
    -                          if c<>'''' then
    -                           begin
    -                             next_char_loaded:=true;
    +                 begin
    +                   in_multiline_string:=(c='`');
    +                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
    +                     repeat
    +                       readchar;
    +                       case c of
    +                         #26 :
    +                           end_of_file;
    +                         #10,#13 :
    +                           if not in_multiline_string then
                                  break;
    -                           end;
    -                        end;
    -                    end;
    -                  until false;
    -                end;
    +                         '''' :
    +                           if not in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'''' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                         '`' :
    +                           if in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'`' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                       end;
    +                     until false;
    +                 end;
                  '(' :
                    begin
                      if (current_commentstyle=comment_none) then
    @@ -4653,6 +4692,46 @@ type
                                    Token Scanner
     ****************************************************************************}
     
    +    procedure tscannerfile.buildplatformnewlineascii(var len: longint);
    +      begin
    +        if target_info.newline=#13 then
    +          cstringpattern[len]:=#13
    +        else if target_info.newline=#13#10 then
    +          begin
    +            cstringpattern[len]:=#13;
    +            inc(len);
    +            cstringpattern[len]:=#10;
    +          end
    +        else if target_info.newline=#10 then
    +          cstringpattern[len]:=#10;
    +      end;
    +
    +    procedure tscannerfile.buildplatformnewlineutf8;
    +      begin
    +        if target_info.newline=#13 then
    +          concatwidestringchar(patternw,ord(#13))
    +        else if target_info.newline=#13#10 then
    +          begin
    +            concatwidestringchar(patternw,ord(#13));
    +            concatwidestringchar(patternw,ord(#10));
    +          end
    +        else if target_info.newline=#10 then
    +          concatwidestringchar(patternw,ord(#10));
    +      end;
    +
    +    procedure tscannerfile.buildplatformnewlineunicode;
    +      begin
    +        if target_info.newline=#13 then
    +          concatwidestringchar(patternw,asciichar2unicode(#13))
    +        else if target_info.newline=#13#10 then
    +          begin
    +            concatwidestringchar(patternw,asciichar2unicode(#13));
    +            concatwidestringchar(patternw,asciichar2unicode(#10));
    +          end
    +        else if target_info.newline=#10 then
    +          concatwidestringchar(patternw,asciichar2unicode(#10));
    +      end;
    +
         procedure tscannerfile.readtoken(allowrecordtoken:boolean);
           var
             code    : integer;
    @@ -4664,9 +4743,14 @@ type
             mac     : tmacro;
             asciinr : string[33];
             iswidestring : boolean;
    +        in_multiline_string,had_newline,first_multiline: boolean;
    +        trimcount: word;
           label
    -         exit_label;
    +        quote_label,exit_label;
           begin
    +        had_newline:=false;
    +        first_multiline:=false;
    +        last_c:=#0;
             flushpendingswitchesstate;
     
             { record tokens? }
    @@ -5098,8 +5182,11 @@ type
                      goto exit_label;
                    end;
     
    -             '''','#','^' :
    +             '''','#','^','`' :
                    begin
    +                 in_multiline_string:=(c='`');
    +                 if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +                   Illegal_Char(c);
                      len:=0;
                      cstringpattern:='';
                      iswidestring:=false;
    @@ -5206,26 +5293,52 @@ type
                                begin
                                  if len>=length(cstringpattern) then
                                    setlength(cstringpattern,length(cstringpattern)+256);
    -                              inc(len);
    -                              cstringpattern[len]:=chr(m);
    +                             inc(len);
    +                             cstringpattern[len]:=chr(m);
                                end;
                            end;
    -                     '''' :
    +                     '''','`' :
                            begin
    +                         in_multiline_string:=(c='`');
    +                         first_multiline:=in_multiline_string;
                              repeat
                                readchar;
    -                           case c of
    -                             #26 :
    -                               end_of_file;
    -                             #10,#13 :
    -                               Message(scan_f_string_exceeds_line);
    -                             '''' :
    -                               begin
    -                                 readchar;
    -                                 if c<>'''' then
    -                                  break;
    -                               end;
    -                           end;
    +                           quote_label:
    +                             case c of
    +                               #26 :
    +                                 end_of_file;
    +                               #32,#9,#11 :
    +                                 if (had_newline or first_multiline) and (current_settings.whitespacetrimcount > 0) then
    +                                   begin
    +                                     trimcount:=current_settings.whitespacetrimcount;
    +                                     while (c in [#32,#9,#11]) and (trimcount > 0) do
    +                                       begin
    +                                         readchar;
    +                                         dec(trimcount);
    +                                       end;
    +                                     had_newline:=false;
    +                                     first_multiline:=false;
    +                                     goto quote_label;
    +                                   end;
    +                               #10,#13 :
    +                                 if not in_multiline_string then
    +                                   Message(scan_f_string_exceeds_line);
    +                               '''' :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'''' then
    +                                      break;
    +                                   end;
    +                               '`' :
    +                                 if in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'`' then
    +                                      break;
    +                                   end;
    +                             end;
    +                           first_multiline:=false;
                                { interpret as utf-8 string? }
                                if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                                  begin
    @@ -5300,17 +5413,76 @@ type
                                  end
                                else if iswidestring then
                                  begin
    -                               if current_settings.sourcecodepage=CP_UTF8 then
    -                                 concatwidestringchar(patternw,ord(c))
    -                               else
    -                                 concatwidestringchar(patternw,asciichar2unicode(c))
    +                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     begin
    +                                       case current_settings.lineendingtype of
    +                                         le_cr : concatwidestringchar(patternw,ord(#13));
    +                                         le_crlf :
    +                                           begin
    +                                             concatwidestringchar(patternw,ord(#13));
    +                                             concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_lf : concatwidestringchar(patternw,ord(#10));
    +                                         le_platform : buildplatformnewlineutf8;
    +                                         le_raw : concatwidestringchar(patternw,ord(c));
    +                                       end;
    +                                     end
    +                                   else
    +                                     case current_settings.lineendingtype of
    +                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                       le_crlf :
    +                                         begin
    +                                           concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                           concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                       le_platform : buildplatformnewlineunicode;
    +                                       le_raw : concatwidestringchar(patternw,asciichar2unicode(c));
    +                                     end;
    +                                   had_newline:=true;
    +                                   last_c:=c;
    +                                   inc(line_no);
    +                                 end
    +                               else if not (in_multiline_string and (c in [#10,#13])) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     concatwidestringchar(patternw,ord(c))
    +                                   else
    +                                     concatwidestringchar(patternw,asciichar2unicode(c));
    +                                 end;
                                  end
                                else
                                  begin
    -                               if len>=length(cstringpattern) then
    -                                 setlength(cstringpattern,length(cstringpattern)+256);
    -                                inc(len);
    -                                cstringpattern[len]:=c;
    +                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    case current_settings.lineendingtype of
    +                                      le_cr : cstringpattern[len]:=#13;
    +                                      le_crlf :
    +                                        begin
    +                                          cstringpattern[len]:=#13;
    +                                          inc(len);
    +                                          cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_lf : cstringpattern[len]:=#10;
    +                                      le_platform : buildplatformnewlineascii(len);
    +                                      le_raw : cstringpattern[len]:=c;
    +                                    end;
    +                                    had_newline:=true;
    +                                    last_c:=c;
    +                                    inc(line_no);
    +                                  end
    +                                else if not (in_multiline_string and (c in [#10,#13])) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    cstringpattern[len]:=c;
    +                                  end;
                                  end;
                              until false;
                            end;
    @@ -5480,12 +5652,13 @@ exit_label:
                    current_scanner.preproc_pattern:=pattern;
                    readpreproc:=optoken;
                  end;
    -           '''' :
    -             begin
    -               readquotedstring;
    -               current_scanner.preproc_pattern:=cstringpattern;
    -               readpreproc:=_CSTRING;
    -             end;
    +           '''','`' :
    +             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
    +               begin
    +                 readquotedstring;
    +                 current_scanner.preproc_pattern:=cstringpattern;
    +                 readpreproc:=_CSTRING;
    +               end;
                '0'..'9' :
                  begin
                    readnumber;
    diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
    index 7009bd8..cc93428 100644
    --- compiler/utils/ppuutils/ppudump.pp
    +++ compiler/utils/ppuutils/ppudump.pp
    @@ -2444,6 +2444,8 @@ const
                 else
                  ControllerType:=ct_none;
     {$POP}
    +           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +           whitespacetrimcount:=gettokenbufword;
                endpos:=tbi;
                if endpos-startpos<>expected_size then
                  Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);
    
  • multi_line_strings_tests_rev3.patch (24,489 bytes)
    diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
    new file mode 100644
    index 0000000..007874c
    --- /dev/null
    +++ tests/test/tmultilinestring1.pp
    @@ -0,0 +1,14 @@
    +program tmultilinestring1;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const MyString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyString);
    +end.
    diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
    new file mode 100644
    index 0000000..59aeb2c
    --- /dev/null
    +++ tests/test/tmultilinestring10.pp
    @@ -0,0 +1,9 @@
    +program tmultilinestring10;
    +
    +{ Test the use of multiline strings from units in programs }
    +
    +uses umultilinestring1;
    +
    +begin
    +  Write(Long);
    +end.
    diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
    new file mode 100644
    index 0000000..206e61c
    --- /dev/null
    +++ tests/test/tmultilinestring11.pp
    @@ -0,0 +1,19 @@
    +{ %FAIL }
    +
    +program tmultilinestring11;
    +
    +{$modeswitch MultiLineStrings}
    +{$H-}
    +
    +{ Some Mark Twain... }
    +
    +const WayTooLong =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +begin
    +  Write(WayTooLong);
    +end.
    diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
    new file mode 100644
    index 0000000..ca6a800
    --- /dev/null
    +++ tests/test/tmultilinestring12.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring12;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 4}
    +
    +procedure TakesAString(const S: String);
    +begin
    +  Write(S);
    +end;
    +
    +begin
    +  TakesAString(`
    +    This
    +    works
    +    just
    +    fine!
    +  `);
    +end.
    diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
    new file mode 100644
    index 0000000..3652c76
    --- /dev/null
    +++ tests/test/tmultilinestring13.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring13;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const A =
    +`
    +``a``
    +`;
    +
    +const B =
    +`
    +'a'
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +end.
    diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
    new file mode 100644
    index 0000000..e181ff7
    --- /dev/null
    +++ tests/test/tmultilinestring14.pp
    @@ -0,0 +1,40 @@
    +program tmultilinestring14;
    +
    +{$modeswitch MultiLineStrings}
    +
    +{$MultiLineStringTrimLeft 2}
    +
    +const A = `
    +  A
    +  B
    +  C
    +  D
    +`;
    +
    +{$MultiLineStringTrimLeft 4}
    +
    +const B = `
    +    A
    +    B
    +    C
    +    D
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +
    +  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
    +    as it stops immediately when it is no longer actually *in* whitespace regardless. }
    +
    +  {$MultiLineStringTrimLeft 10000}
    +
    +  { Non-leading whitespace is preserved properly, of course. }
    +
    +  Write(`
    +        sdfs
    +        sd fs fs
    +        sd  fsfs  sdfd sfdf
    +        sdfs fsd
    +  `);
    +end.
    diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
    new file mode 100644
    index 0000000..fa3426b
    --- /dev/null
    +++ tests/test/tmultilinestring15.pp
    @@ -0,0 +1,37 @@
    +program tmultilinestring15;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +const X = `
    +  Hello
    +  every
    +  body!
    +`;
    +
    +const Y = `
    +    Goodbye
    +    every
    +    body!
    +    
    +`;
    +
    +{ Test some wacky concatentation }
    +
    +begin
    +  Write(X + Y);
    +  Write(Concat(X, Y));
    +  Write(
    +    '  Single line string ' +
    +    `
    +    and
    +    ` +
    +    `
    +    Multi
    +    line
    +    string
    +    ` +
    +    Y +
    +    X
    +  );
    +end.
    diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
    new file mode 100644
    index 0000000..9e1d7b5
    --- /dev/null
    +++ tests/test/tmultilinestring16.pp
    @@ -0,0 +1,30 @@
    +program tmultilinestring16;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +procedure TakesAnArray(constref A: array of String);
    +var S: String;
    +begin
    +  for S in A do Write(S);
    +end;
    +
    +begin
    +  TakesAnArray([
    +    ` Multi
    +      line
    +      one!`,
    +    `
    +      Multi
    +      line
    +      two!`,
    +    `
    +      Multi
    +      line
    +      three!
    +    `,
    +    'Single line one!' + sLineBreak +
    +    'Single line two!' + sLineBreak +
    +    'Single line three!'
    +  ]);
    +end.
    diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
    new file mode 100644
    index 0000000..116d6f8
    --- /dev/null
    +++ tests/test/tmultilinestring17.pp
    @@ -0,0 +1,39 @@
    +program tmultilinestring17;
    +
    +{$mode ObjFPC}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +type
    +  TMessage = record
    +    Msg: String;
    +  end;
    +
    +  TMyClass = class
    +  public
    +    procedure MyMessage(var Msg: TMessage); message `
    +      Multi
    +      Line
    +      Message!
    +    `;
    +  end;
    +
    +  procedure TMyClass.MyMessage(var Msg: TMessage);
    +  begin
    +    WriteLn('Ok!');
    +  end;
    +
    +  const M: TMessage = (
    +    Msg: `
    +      Multi
    +      Line
    +      Message!
    +    `
    +  );
    +
    +begin
    +  with TMyClass.Create() do begin
    +    DispatchStr(M);
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
    new file mode 100644
    index 0000000..d98f7b9
    --- /dev/null
    +++ tests/test/tmultilinestring18.pp
    @@ -0,0 +1,19 @@
    +program tmultilinestring18;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +{$Warnings On}
    +
    +procedure IsDeprecated; deprecated
    +`
    +  Multi
    +  line
    +  deprecation
    +  message!
    +`;
    +begin
    +end;
    +
    +begin
    +  IsDeprecated;
    +end.
    diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
    new file mode 100644
    index 0000000..a7e7201
    --- /dev/null
    +++ tests/test/tmultilinestring19.pp
    @@ -0,0 +1,15 @@
    +program tmultilinestring19;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +{ This is extremely unlikely, but it needs to work properly... }
    +
    +procedure Bloop; external name `
    +  Actually
    +  Called
    +  Bloop
    +`;
    +
    +begin
    +end.
    diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
    new file mode 100644
    index 0000000..3da55bc
    --- /dev/null
    +++ tests/test/tmultilinestring2.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
    new file mode 100644
    index 0000000..6491720
    --- /dev/null
    +++ tests/test/tmultilinestring20.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring20;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +uses umultilinestring2;
    +
    +begin
    +  DoIt;
    +end.
    diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
    new file mode 100644
    index 0000000..a3ee3f6
    --- /dev/null
    +++ tests/test/tmultilinestring21.pp
    @@ -0,0 +1,16 @@
    +{ %FAIL }
    +
    +program tmultilinestring21;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2000000}
    +
    +const S = `
    +  A
    +  B
    +  C
    +`;
    +
    +begin
    +  WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
    new file mode 100644
    index 0000000..e20b31b
    --- /dev/null
    +++ tests/test/tmultilinestring22.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring22;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const Test = ` ThisSpace>>>>     <<<<disappeared`;
    +
    +begin
    +  Write(Test);
    +end.
    \ No newline at end of file
    diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
    new file mode 100644
    index 0000000..fee9e04
    --- /dev/null
    +++ tests/test/tmultilinestring23.pp
    @@ -0,0 +1,51 @@
    +program tmultilinestring23;
    +
    +{$mode ObjFPC}{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +{$modeswitch PrefixedAttributes}
    +
    +uses RTTI;
    +
    +type
    +  TMultiLineAttribute = class(TCustomAttribute)
    +  private
    +    FString: String;
    +  public
    +    constructor Create(const S: String);
    +    property StringValue: String read FString;
    +  end;
    +
    +  constructor TMultiLineAttribute.Create(const S: String);
    +  begin
    +    FString := S;
    +  end;
    +
    +type
    +  [TMultiLineAttribute(
    +    `This is my
    +     pretty cool
    +     multi-line string
    +     attribute!`
    +  )]
    +  [TMultiLineAttribute(
    +    `This is my
    +     even cooler
    +     multi-line string
    +     attribute!`
    +  )]
    +  TMyClass = class
    +  end;
    +
    +var
    +  A: TMultiLineAttribute;
    +
    +begin
    +  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
    +    for TCustomAttribute(A) in GetAttributes() do begin
    +      WriteLn(A.StringValue);
    +      A.Free();
    +    end;
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
    new file mode 100644
    index 0000000..0fc0768
    --- /dev/null
    +++ tests/test/tmultilinestring24.pp
    @@ -0,0 +1,62 @@
    +program tmultilinestring24;
    +
    +{ engkin's bug example }
    + 
    +{$mode objfpc}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 15}
    +{$MultiLineStringLineEnding Platform}
    + 
    +var
    +{$MultiLineStringLineEnding CR}
    +  a: array[0..3] of string = (
    +``
    +,
    +`
    +`
    +,
    +`
    + 
    +`
    +,
    +`
    + 
    + 
    +`);
    + 
    +  {$MultiLineStringLineEnding CRLF}
    +b: array[0..3] of string = (
    +`1`
    +,
    +`1
    +2`
    +,
    +`1
    +2
    +3`
    +,
    +`1
    +2
    +3
    +4`);
    + 
    +procedure Test(StrArray:array of string);
    +var
    +  s,sHex: string;
    +  c: char;
    +begin
    +  for s in StrArray do
    +  begin
    +    WriteLn('Length: ',Length(s));
    +    sHex := ``;
    +    for c in s do
    +      sHex := sHex+`$`+hexStr(ord(c),2)+` `;
    +    WriteLn(sHex);
    +  end;
    +  WriteLn('---------------');
    +end;
    + 
    +begin
    +  Test(a);
    +  Test(b);
    +end.
    \ No newline at end of file
    diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
    new file mode 100644
    index 0000000..6625906
    --- /dev/null
    +++ tests/test/tmultilinestring3.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring3;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
    new file mode 100644
    index 0000000..6489542
    --- /dev/null
    +++ tests/test/tmultilinestring4.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring4;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
    new file mode 100644
    index 0000000..a868d93
    --- /dev/null
    +++ tests/test/tmultilinestring5.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring5;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
    new file mode 100644
    index 0000000..cda16cd
    --- /dev/null
    +++ tests/test/tmultilinestring6.pp
    @@ -0,0 +1,66 @@
    +program tmultilinestring6;
    +
    +{$CodePage UTF8}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CR}
    +
    +const A =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding CRLF}
    +
    +const B =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding LF}
    +
    +const C =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding PLATFORM}
    +
    +const D =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding RAW}
    +
    +const E =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +  Write(C);
    +  Write(D);
    +  Write(E);
    +end.
    diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
    new file mode 100644
    index 0000000..4946ba1
    --- /dev/null
    +++ tests/test/tmultilinestring7.pp
    @@ -0,0 +1,17 @@
    +{ %FAIL }
    +
    +program tmultilinestring7;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding DOESNOTEXIST}
    +
    +const Blah =
    +`
    +A
    +B
    +C
    +`;
    +
    +begin
    +  Write(Blah);
    +end.
    diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
    new file mode 100644
    index 0000000..00c6cc5
    --- /dev/null
    +++ tests/test/tmultilinestring8.pp
    @@ -0,0 +1,38 @@
    +{ %FAIL }
    +
    +program tmultilinestring8;
    +
    +{ Ryan's example from the mailing list }
    +
    +{$modeswitch MultiLineStrings}
    +
    +const lines: ansistring = `
    +  #version 150
    +
    +  uniform sampler2D textures[8];
    +  in vec2 vertexTexCoord;
    +  in vec4 vertexColor;
    +  in float vertexUVMap;
    +  out vec4 fragColor;
    +
    +  void main()
    +  {
    +    if (vertexUVMap == 255) {
    +      fragColor = vertexColor;
    +    } else {
    +      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
    +      if (vertexColor.a < fragColor.a) {
    +        fragColor.a = vertexColor.a;
    +      }
    +    }
    +
    +    // TODO: testing
    +    fragColor = vec4(1,0,0,1);
    +  }
    +`;
    +
    +var
    +  s: ansistring = lines;
    +begin
    +  writeln(b);
    +end.
    diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
    new file mode 100644
    index 0000000..033f8fb
    --- /dev/null
    +++ tests/test/tmultilinestring9.pp
    @@ -0,0 +1,13 @@
    +{ %FAIL }
    +
    +program tmultilinestring9;
    +
    +{ "Forget" to set the modeswitch... }
    +
    +const NotAllowed = `
    +Oh
    +no!
    +`;
    +
    +begin
    +end.
    diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
    new file mode 100644
    index 0000000..62dafe2
    --- /dev/null
    +++ tests/test/umultilinestring1.pp
    @@ -0,0 +1,21 @@
    +unit umultilinestring1;
    +
    +{$CodePage UTF8}
    +{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CRLF}
    +
    +interface
    +
    +{ Some Mark Twain... }
    +
    +const Long =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +implementation
    +
    +end.
    diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
    new file mode 100644
    index 0000000..ca08f05
    --- /dev/null
    +++ tests/test/umultilinestring2.pp
    @@ -0,0 +1,17 @@
    +unit umultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +interface
    +
    +procedure DoIt;
    +
    +implementation
    +
    +procedure DoIt; public name `SingleLineMultiLine`;
    +begin
    +  Write('Ok!');
    +end;
    +
    +end.
    
  • multi_line_strings_main_rev3.patch (26,930 bytes)
    diff --git compiler/globals.pas compiler/globals.pas
    index 1f368e6..511b51d 100644
    --- compiler/globals.pas
    +++ compiler/globals.pas
    @@ -183,6 +183,12 @@ interface
     
              { WARNING: this pointer cannot be written as such in record token }
              pmessage : pmessagestaterecord;
    +         
    +         lineendingtype : tlineendingtype;
    +
    +         whitespacetrimcount : word;
    +         
    +         whitespacetrimauto : boolean;
            end;
     
         const
    @@ -574,6 +580,9 @@ interface
     {$endif defined(LLVM) and not defined(GENERIC_CPU)}
             controllertype : ct_none;
             pmessage : nil;
    +        lineendingtype : le_raw;
    +        whitespacetrimcount : 0;
    +        whitespacetrimauto : false
           );
     
         var
    @@ -1455,7 +1464,7 @@ implementation
            if localexepath='' then
             begin
               hs1 := ExtractFileName(exeName);
    -	  hs1 := ChangeFileExt(hs1,source_info.exeext);
    +          hs1 := ChangeFileExt(hs1,source_info.exeext);
     {$ifdef macos}
               FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
     {$else macos}
    diff --git compiler/globtype.pas compiler/globtype.pas
    index b6c142f..a03ff3a 100644
    --- compiler/globtype.pas
    +++ compiler/globtype.pas
    @@ -491,7 +491,8 @@ interface
              m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
              m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
              m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
    -         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
    +         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
    +         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
            );
            tmodeswitches = set of tmodeswitch;
     
    @@ -605,7 +606,18 @@ interface
              pocall_vectorcall
            );
            tproccalloptions = set of tproccalloption;
    -
    +       
    +       tlineendingtype = ({Carriage return, aka #13}
    +                          le_cr,
    +                          {Carriage return + line feed, aka #13#10}
    +                          le_crlf,
    +                          {Line feed, aka #10}
    +                          le_lf,
    +                          {Use the platform default}
    +                          le_platform,
    +                          {Use whatever is in the file}
    +                          le_raw);
    +                          
          const
            proccalloptionStr : array[tproccalloption] of string[16]=('',
                'CDecl',
    @@ -683,7 +695,8 @@ interface
              'ARRAYOPERATORS',
              'MULTIHELPERS',
              'ARRAYTODYNARRAY',
    -         'PREFIXEDATTRIBUTES'
    +         'PREFIXEDATTRIBUTES',
    +         'MULTILINESTRINGS'
              );
     
     
    diff --git compiler/msg/errore.msg compiler/msg/errore.msg
    index d6a0bdf..b933741 100644
    --- compiler/msg/errore.msg
    +++ compiler/msg/errore.msg
    @@ -432,6 +432,12 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
     scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
     % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
     scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
    +scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or RAW.
    +% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or RAW.
    +scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
    +% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
    +scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
    +% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
     % \end{description}
     #
     # Parser
    diff --git compiler/scandir.pas compiler/scandir.pas
    index 9dd457b..1aebcfd 100644
    --- compiler/scandir.pas
    +++ compiler/scandir.pas
    @@ -1003,6 +1003,67 @@ unit scandir;
               end;
           end;
     
    +    procedure dir_multilinestringlineending;
    +      var
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
    +        current_scanner.skipspace;
    +        s:=current_scanner.readid;
    +        if (s='CR') then
    +          current_settings.lineendingtype:=le_cr
    +        else if (s='CRLF') then
    +          current_settings.lineendingtype:=le_crlf
    +        else if (s='LF') then
    +          current_settings.lineendingtype:=le_lf
    +        else if (s='PLATFORM') then
    +          current_settings.lineendingtype:=le_platform
    +        else if (s='RAW') then
    +          current_settings.lineendingtype:=le_raw
    +        else
    +          Message(scan_e_unknown_lineending_type);
    +      end;
    +
    +    procedure dir_multilinestringtrimleft;
    +      var
    +        count : longint;
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
    +        current_scanner.skipspace;
    +        if (c in ['1'..'9']) then
    +          begin
    +            count:=current_scanner.readval;
    +            if (count<0) or (count>65535) then
    +              Message(scan_e_trimcount_out_of_range)
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=count;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end
    +        else
    +          begin
    +            s:=current_scanner.readid;
    +            if s='ALL' then
    +              begin
    +                current_settings.whitespacetrimcount:=65535;
    +                current_settings.whitespacetrimauto:=false;
    +              end
    +            else if s='AUTO' then
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=true;
    +              end
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end;
    +      end;
     
         procedure dir_namespace;
           var
    @@ -1973,6 +2034,8 @@ unit scandir;
             AddDirective('MMX',directive_all, @dir_mmx);
             AddDirective('MODE',directive_all, @dir_mode);
             AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
    +        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
    +        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
             AddDirective('NAMESPACE',directive_all, @dir_namespace);
             AddDirective('NODEFINE',directive_all, @dir_nodefine);
             AddDirective('NOTE',directive_all, @dir_note);
    diff --git compiler/scanner.pas compiler/scanner.pas
    index 88b2703..253114a 100644
    --- compiler/scanner.pas
    +++ compiler/scanner.pas
    @@ -101,6 +101,9 @@ interface
              procedure savetokenpos;
              procedure restoretokenpos;
              procedure writetoken(t: ttoken);
    +         procedure buildplatformnewlineascii(var len: longint);
    +         procedure buildplatformnewlineutf8;
    +         procedure buildplatformnewlineunicode;
              function readtoken : ttoken;
            public
               inputfile    : tinputfile;  { current inputfile list }
    @@ -187,6 +190,7 @@ interface
               procedure tokenwritesizeint(val : asizeint);
               procedure tokenwritelongint(val : longint);
               procedure tokenwritelongword(val : longword);
    +          procedure tokenwritebyte(val : byte);
               procedure tokenwriteword(val : word);
               procedure tokenwriteshortint(val : shortint);
               procedure tokenwriteset(var b;size : longint);
    @@ -2898,6 +2902,14 @@ type
             recordtokenbuf.write(val,sizeof(shortint));
           end;
     
    +    procedure tscannerfile.tokenwritebyte(val : byte);
    +      begin
    +{$ifdef FPC_BIG_ENDIAN}
    +        val:=swapendian(val);
    +{$endif}
    +        recordtokenbuf.write(val,sizeof(byte));
    +      end;
    +
         procedure tscannerfile.tokenwriteword(val : word);
           begin
     {$ifdef FPC_BIG_ENDIAN}
    @@ -3111,8 +3123,11 @@ type
                 else
                  ControllerType:=ct_none;
     {$POP}
    -           endpos:=replaytokenbuf.pos;
    -           if endpos-startpos<>expected_size then
    +            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +            whitespacetrimcount:=tokenreadword;
    +            whitespacetrimauto:=boolean(tokenreadbyte);
    +            endpos:=replaytokenbuf.pos;
    +            if endpos-startpos<>expected_size then
                  Comment(V_Error,'Wrong size of Settings read-in');
              end;
          end;
    @@ -3189,6 +3204,9 @@ type
                 if ControllerSupport then
                   tokenwriteenum(controllertype,sizeof(tcontrollertype));
     {$POP}
    +           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
    +           tokenwriteword(whitespacetrimcount);
    +           tokenwritebyte(byte(whitespacetrimauto));
                endpos:=recordtokenbuf.pos;
                size:=endpos-startpos;
                recordtokenbuf.seek(sizepos);
    @@ -4243,25 +4261,40 @@ type
         function tscannerfile.readquotedstring:string;
           var
             i : longint;
    -        msgwritten : boolean;
    +        msgwritten,in_multiline_string : boolean;
           begin
             i:=0;
             msgwritten:=false;
    -        if (c='''') then
    +        if (c in ['''','`']) then
               begin
    +            in_multiline_string:=(c='`');
    +            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +              begin
    +                result[0]:=chr(0);
    +                Illegal_Char(c);
    +              end;
                 repeat
                   readchar;
                   case c of
                     #26 :
                       end_of_file;
                     #10,#13 :
    -                  Message(scan_f_string_exceeds_line);
    +                  if not in_multiline_string then
    +                    Message(scan_f_string_exceeds_line);
                     '''' :
    -                  begin
    -                    readchar;
    -                    if c<>'''' then
    -                     break;
    -                  end;
    +                  if not in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'''' then
    +                       break;
    +                    end;
    +                '`' :
    +                  if in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'`' then
    +                       break;
    +                    end;
                   end;
                   if i<255 then
                     begin
    @@ -4389,7 +4422,7 @@ type
         procedure tscannerfile.skipuntildirective;
           var
             found : longint;
    -        next_char_loaded : boolean;
    +        next_char_loaded,in_multiline_string : boolean;
           begin
              found:=0;
              next_char_loaded:=false;
    @@ -4447,28 +4480,42 @@ type
                      if found=1 then
                       found:=2;
                    end;
    -             '''' :
    +             '''','`' :
                    if (current_commentstyle=comment_none) then
    -                begin
    -                  repeat
    -                    readchar;
    -                    case c of
    -                      #26 :
    -                        end_of_file;
    -                      #10,#13 :
    -                        break;
    -                      '''' :
    -                        begin
    -                          readchar;
    -                          if c<>'''' then
    -                           begin
    -                             next_char_loaded:=true;
    +                 begin
    +                   in_multiline_string:=(c='`');
    +                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
    +                     repeat
    +                       readchar;
    +                       case c of
    +                         #26 :
    +                           end_of_file;
    +                         #10,#13 :
    +                           if not in_multiline_string then
                                  break;
    -                           end;
    -                        end;
    -                    end;
    -                  until false;
    -                end;
    +                         '''' :
    +                           if not in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'''' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                         '`' :
    +                           if in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'`' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                       end;
    +                     until false;
    +                 end;
                  '(' :
                    begin
                      if (current_commentstyle=comment_none) then
    @@ -4653,6 +4700,46 @@ type
                                    Token Scanner
     ****************************************************************************}
     
    +    procedure tscannerfile.buildplatformnewlineascii(var len: longint);
    +      begin
    +        if target_info.newline=#13 then
    +          cstringpattern[len]:=#13
    +        else if target_info.newline=#13#10 then
    +          begin
    +            cstringpattern[len]:=#13;
    +            inc(len);
    +            cstringpattern[len]:=#10;
    +          end
    +        else if target_info.newline=#10 then
    +          cstringpattern[len]:=#10;
    +      end;
    +
    +    procedure tscannerfile.buildplatformnewlineutf8;
    +      begin
    +        if target_info.newline=#13 then
    +          concatwidestringchar(patternw,ord(#13))
    +        else if target_info.newline=#13#10 then
    +          begin
    +            concatwidestringchar(patternw,ord(#13));
    +            concatwidestringchar(patternw,ord(#10));
    +          end
    +        else if target_info.newline=#10 then
    +          concatwidestringchar(patternw,ord(#10));
    +      end;
    +
    +    procedure tscannerfile.buildplatformnewlineunicode;
    +      begin
    +        if target_info.newline=#13 then
    +          concatwidestringchar(patternw,asciichar2unicode(#13))
    +        else if target_info.newline=#13#10 then
    +          begin
    +            concatwidestringchar(patternw,asciichar2unicode(#13));
    +            concatwidestringchar(patternw,asciichar2unicode(#10));
    +          end
    +        else if target_info.newline=#10 then
    +          concatwidestringchar(patternw,asciichar2unicode(#10));
    +      end;
    +
         procedure tscannerfile.readtoken(allowrecordtoken:boolean);
           var
             code    : integer;
    @@ -4664,9 +4751,15 @@ type
             mac     : tmacro;
             asciinr : string[33];
             iswidestring : boolean;
    +        in_multiline_string,had_newline,first_multiline : boolean;
    +        trimcount,multiline_start_column : word;
    +        last_c : char;
           label
    -         exit_label;
    +        quote_label,exit_label;
           begin
    +        had_newline:=false;
    +        first_multiline:=false;
    +        last_c:=#0;
             flushpendingswitchesstate;
     
             { record tokens? }
    @@ -5098,8 +5191,13 @@ type
                      goto exit_label;
                    end;
     
    -             '''','#','^' :
    +             '''','#','^','`' :
                    begin
    +                 in_multiline_string:=(c='`');
    +                 if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +                   Illegal_Char(c)
    +                 else
    +                   multiline_start_column:=current_filepos.column;
                      len:=0;
                      cstringpattern:='';
                      iswidestring:=false;
    @@ -5206,26 +5304,55 @@ type
                                begin
                                  if len>=length(cstringpattern) then
                                    setlength(cstringpattern,length(cstringpattern)+256);
    -                              inc(len);
    -                              cstringpattern[len]:=chr(m);
    +                             inc(len);
    +                             cstringpattern[len]:=chr(m);
                                end;
                            end;
    -                     '''' :
    +                     '''','`' :
                            begin
    +                         in_multiline_string:=(c='`');
    +                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                              repeat
                                readchar;
    -                           case c of
    -                             #26 :
    -                               end_of_file;
    -                             #10,#13 :
    -                               Message(scan_f_string_exceeds_line);
    -                             '''' :
    -                               begin
    -                                 readchar;
    -                                 if c<>'''' then
    -                                  break;
    -                               end;
    -                           end;
    +                           quote_label:
    +                             case c of
    +                               #26 :
    +                                 end_of_file;
    +                               #32,#9,#11 :
    +                                 if (had_newline or first_multiline) and (current_settings.whitespacetrimauto or (current_settings.whitespacetrimcount > 0)) then
    +                                   begin
    +                                     if current_settings.whitespacetrimauto then
    +                                       trimcount:=multiline_start_column
    +                                     else
    +                                       trimcount:=current_settings.whitespacetrimcount;
    +                                     while (c in [#32,#9,#11]) and (trimcount > 0) do
    +                                       begin
    +                                         readchar;
    +                                         dec(trimcount);
    +                                       end;
    +                                     had_newline:=false;
    +                                     first_multiline:=false;
    +                                     goto quote_label;
    +                                   end;
    +                               #10,#13 :
    +                                 if not in_multiline_string then
    +                                   Message(scan_f_string_exceeds_line);
    +                               '''' :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'''' then
    +                                      break;
    +                                   end;
    +                               '`' :
    +                                 if in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'`' then
    +                                      break;
    +                                   end;
    +                             end;
    +                           first_multiline:=false;
                                { interpret as utf-8 string? }
                                if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                                  begin
    @@ -5300,18 +5427,76 @@ type
                                  end
                                else if iswidestring then
                                  begin
    -                               if current_settings.sourcecodepage=CP_UTF8 then
    -                                 concatwidestringchar(patternw,ord(c))
    -                               else
    -                                 concatwidestringchar(patternw,asciichar2unicode(c))
    +                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     begin
    +                                       case current_settings.lineendingtype of
    +                                         le_cr : concatwidestringchar(patternw,ord(#13));
    +                                         le_crlf :
    +                                           begin
    +                                             concatwidestringchar(patternw,ord(#13));
    +                                             concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_lf : concatwidestringchar(patternw,ord(#10));
    +                                         le_platform : buildplatformnewlineutf8;
    +                                         le_raw : concatwidestringchar(patternw,ord(c));
    +                                       end;
    +                                     end
    +                                   else
    +                                     case current_settings.lineendingtype of
    +                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                       le_crlf :
    +                                         begin
    +                                           concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                           concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                       le_platform : buildplatformnewlineunicode;
    +                                       le_raw : concatwidestringchar(patternw,asciichar2unicode(c));
    +                                     end;
    +                                   had_newline:=true;
    +                                   inc(line_no);
    +                                 end
    +                               else if not (in_multiline_string and (c in [#10,#13])) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     concatwidestringchar(patternw,ord(c))
    +                                   else
    +                                     concatwidestringchar(patternw,asciichar2unicode(c));
    +                                 end;
                                  end
                                else
                                  begin
    -                               if len>=length(cstringpattern) then
    -                                 setlength(cstringpattern,length(cstringpattern)+256);
    -                                inc(len);
    -                                cstringpattern[len]:=c;
    +                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    case current_settings.lineendingtype of
    +                                      le_cr : cstringpattern[len]:=#13;
    +                                      le_crlf :
    +                                        begin
    +                                          cstringpattern[len]:=#13;
    +                                          inc(len);
    +                                          cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_lf : cstringpattern[len]:=#10;
    +                                      le_platform : buildplatformnewlineascii(len);
    +                                      le_raw : cstringpattern[len]:=c;
    +                                    end;
    +                                    had_newline:=true;
    +                                    inc(line_no);
    +                                  end
    +                                else if not (in_multiline_string and (c in [#10,#13])) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    cstringpattern[len]:=c;
    +                                  end;
                                  end;
    +                         last_c:=c;
                              until false;
                            end;
                          '^' :
    @@ -5338,6 +5523,7 @@ type
                          else
                           break;
                        end;
    +                 last_c:=c;
                      until false;
                      { strings with length 1 become const chars }
                      if iswidestring then
    @@ -5480,12 +5666,13 @@ exit_label:
                    current_scanner.preproc_pattern:=pattern;
                    readpreproc:=optoken;
                  end;
    -           '''' :
    -             begin
    -               readquotedstring;
    -               current_scanner.preproc_pattern:=cstringpattern;
    -               readpreproc:=_CSTRING;
    -             end;
    +           '''','`' :
    +             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
    +               begin
    +                 readquotedstring;
    +                 current_scanner.preproc_pattern:=cstringpattern;
    +                 readpreproc:=_CSTRING;
    +               end;
                '0'..'9' :
                  begin
                    readnumber;
    diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
    index 7009bd8..14f600f 100644
    --- compiler/utils/ppuutils/ppudump.pp
    +++ compiler/utils/ppuutils/ppudump.pp
    @@ -2444,6 +2444,9 @@ const
                 else
                  ControllerType:=ct_none;
     {$POP}
    +           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +           whitespacetrimcount:=gettokenbufword;
    +           whitespacetrimauto:=boolean(gettokenbufbyte);
                endpos:=tbi;
                if endpos-startpos<>expected_size then
                  Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);
    
  • multi_line_strings_tests_rev4.patch (25,952 bytes)
    diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
    new file mode 100644
    index 0000000..007874c
    --- /dev/null
    +++ tests/test/tmultilinestring1.pp
    @@ -0,0 +1,14 @@
    +program tmultilinestring1;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const MyString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyString);
    +end.
    diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
    new file mode 100644
    index 0000000..59aeb2c
    --- /dev/null
    +++ tests/test/tmultilinestring10.pp
    @@ -0,0 +1,9 @@
    +program tmultilinestring10;
    +
    +{ Test the use of multiline strings from units in programs }
    +
    +uses umultilinestring1;
    +
    +begin
    +  Write(Long);
    +end.
    diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
    new file mode 100644
    index 0000000..206e61c
    --- /dev/null
    +++ tests/test/tmultilinestring11.pp
    @@ -0,0 +1,19 @@
    +{ %FAIL }
    +
    +program tmultilinestring11;
    +
    +{$modeswitch MultiLineStrings}
    +{$H-}
    +
    +{ Some Mark Twain... }
    +
    +const WayTooLong =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +begin
    +  Write(WayTooLong);
    +end.
    diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
    new file mode 100644
    index 0000000..ca6a800
    --- /dev/null
    +++ tests/test/tmultilinestring12.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring12;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 4}
    +
    +procedure TakesAString(const S: String);
    +begin
    +  Write(S);
    +end;
    +
    +begin
    +  TakesAString(`
    +    This
    +    works
    +    just
    +    fine!
    +  `);
    +end.
    diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
    new file mode 100644
    index 0000000..3652c76
    --- /dev/null
    +++ tests/test/tmultilinestring13.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring13;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const A =
    +`
    +``a``
    +`;
    +
    +const B =
    +`
    +'a'
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +end.
    diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
    new file mode 100644
    index 0000000..e181ff7
    --- /dev/null
    +++ tests/test/tmultilinestring14.pp
    @@ -0,0 +1,40 @@
    +program tmultilinestring14;
    +
    +{$modeswitch MultiLineStrings}
    +
    +{$MultiLineStringTrimLeft 2}
    +
    +const A = `
    +  A
    +  B
    +  C
    +  D
    +`;
    +
    +{$MultiLineStringTrimLeft 4}
    +
    +const B = `
    +    A
    +    B
    +    C
    +    D
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +
    +  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
    +    as it stops immediately when it is no longer actually *in* whitespace regardless. }
    +
    +  {$MultiLineStringTrimLeft 10000}
    +
    +  { Non-leading whitespace is preserved properly, of course. }
    +
    +  Write(`
    +        sdfs
    +        sd fs fs
    +        sd  fsfs  sdfd sfdf
    +        sdfs fsd
    +  `);
    +end.
    diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
    new file mode 100644
    index 0000000..fa3426b
    --- /dev/null
    +++ tests/test/tmultilinestring15.pp
    @@ -0,0 +1,37 @@
    +program tmultilinestring15;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +const X = `
    +  Hello
    +  every
    +  body!
    +`;
    +
    +const Y = `
    +    Goodbye
    +    every
    +    body!
    +    
    +`;
    +
    +{ Test some wacky concatentation }
    +
    +begin
    +  Write(X + Y);
    +  Write(Concat(X, Y));
    +  Write(
    +    '  Single line string ' +
    +    `
    +    and
    +    ` +
    +    `
    +    Multi
    +    line
    +    string
    +    ` +
    +    Y +
    +    X
    +  );
    +end.
    diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
    new file mode 100644
    index 0000000..9e1d7b5
    --- /dev/null
    +++ tests/test/tmultilinestring16.pp
    @@ -0,0 +1,30 @@
    +program tmultilinestring16;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +procedure TakesAnArray(constref A: array of String);
    +var S: String;
    +begin
    +  for S in A do Write(S);
    +end;
    +
    +begin
    +  TakesAnArray([
    +    ` Multi
    +      line
    +      one!`,
    +    `
    +      Multi
    +      line
    +      two!`,
    +    `
    +      Multi
    +      line
    +      three!
    +    `,
    +    'Single line one!' + sLineBreak +
    +    'Single line two!' + sLineBreak +
    +    'Single line three!'
    +  ]);
    +end.
    diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
    new file mode 100644
    index 0000000..116d6f8
    --- /dev/null
    +++ tests/test/tmultilinestring17.pp
    @@ -0,0 +1,39 @@
    +program tmultilinestring17;
    +
    +{$mode ObjFPC}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +type
    +  TMessage = record
    +    Msg: String;
    +  end;
    +
    +  TMyClass = class
    +  public
    +    procedure MyMessage(var Msg: TMessage); message `
    +      Multi
    +      Line
    +      Message!
    +    `;
    +  end;
    +
    +  procedure TMyClass.MyMessage(var Msg: TMessage);
    +  begin
    +    WriteLn('Ok!');
    +  end;
    +
    +  const M: TMessage = (
    +    Msg: `
    +      Multi
    +      Line
    +      Message!
    +    `
    +  );
    +
    +begin
    +  with TMyClass.Create() do begin
    +    DispatchStr(M);
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
    new file mode 100644
    index 0000000..d98f7b9
    --- /dev/null
    +++ tests/test/tmultilinestring18.pp
    @@ -0,0 +1,19 @@
    +program tmultilinestring18;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +{$Warnings On}
    +
    +procedure IsDeprecated; deprecated
    +`
    +  Multi
    +  line
    +  deprecation
    +  message!
    +`;
    +begin
    +end;
    +
    +begin
    +  IsDeprecated;
    +end.
    diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
    new file mode 100644
    index 0000000..a7e7201
    --- /dev/null
    +++ tests/test/tmultilinestring19.pp
    @@ -0,0 +1,15 @@
    +program tmultilinestring19;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +{ This is extremely unlikely, but it needs to work properly... }
    +
    +procedure Bloop; external name `
    +  Actually
    +  Called
    +  Bloop
    +`;
    +
    +begin
    +end.
    diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
    new file mode 100644
    index 0000000..3da55bc
    --- /dev/null
    +++ tests/test/tmultilinestring2.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
    new file mode 100644
    index 0000000..6491720
    --- /dev/null
    +++ tests/test/tmultilinestring20.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring20;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +uses umultilinestring2;
    +
    +begin
    +  DoIt;
    +end.
    diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
    new file mode 100644
    index 0000000..a3ee3f6
    --- /dev/null
    +++ tests/test/tmultilinestring21.pp
    @@ -0,0 +1,16 @@
    +{ %FAIL }
    +
    +program tmultilinestring21;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2000000}
    +
    +const S = `
    +  A
    +  B
    +  C
    +`;
    +
    +begin
    +  WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
    new file mode 100644
    index 0000000..0b21b31
    --- /dev/null
    +++ tests/test/tmultilinestring22.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring22;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const Test = ` ThisSpace>>>>     <<<<disappeared`;
    +
    +begin
    +  Write(Test);
    +end.
    diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
    new file mode 100644
    index 0000000..fee9e04
    --- /dev/null
    +++ tests/test/tmultilinestring23.pp
    @@ -0,0 +1,51 @@
    +program tmultilinestring23;
    +
    +{$mode ObjFPC}{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +{$modeswitch PrefixedAttributes}
    +
    +uses RTTI;
    +
    +type
    +  TMultiLineAttribute = class(TCustomAttribute)
    +  private
    +    FString: String;
    +  public
    +    constructor Create(const S: String);
    +    property StringValue: String read FString;
    +  end;
    +
    +  constructor TMultiLineAttribute.Create(const S: String);
    +  begin
    +    FString := S;
    +  end;
    +
    +type
    +  [TMultiLineAttribute(
    +    `This is my
    +     pretty cool
    +     multi-line string
    +     attribute!`
    +  )]
    +  [TMultiLineAttribute(
    +    `This is my
    +     even cooler
    +     multi-line string
    +     attribute!`
    +  )]
    +  TMyClass = class
    +  end;
    +
    +var
    +  A: TMultiLineAttribute;
    +
    +begin
    +  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
    +    for TCustomAttribute(A) in GetAttributes() do begin
    +      WriteLn(A.StringValue);
    +      A.Free();
    +    end;
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
    new file mode 100644
    index 0000000..7a282f9
    --- /dev/null
    +++ tests/test/tmultilinestring24.pp
    @@ -0,0 +1,62 @@
    +program tmultilinestring24;
    +
    +{ engkin's bug example }
    +
    +{$mode objfpc}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 15}
    +{$MultiLineStringLineEnding Platform}
    +
    +var
    +{$MultiLineStringLineEnding CR}
    +  a: array[0..3] of string = (
    +``
    +,
    +`
    +`
    +,
    +`
    +
    +`
    +,
    +`
    +
    +
    +`);
    +
    +  {$MultiLineStringLineEnding CRLF}
    +b: array[0..3] of string = (
    +`1`
    +,
    +`1
    +2`
    +,
    +`1
    +2
    +3`
    +,
    +`1
    +2
    +3
    +4`);
    +
    +procedure Test(constref StrArray:array of string);
    +var
    +  s,sHex: string;
    +  c: char;
    +begin
    +  for s in StrArray do
    +  begin
    +    WriteLn('Length: ',Length(s));
    +    sHex := '';
    +    for c in s do
    +      sHex := sHex+'$'+hexStr(ord(c),2);
    +    WriteLn(sHex);
    +  end;
    +  WriteLn('---------------');
    +end;
    +
    +begin
    +  Test(a);
    +  Test(b);
    +end.
    diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
    new file mode 100644
    index 0000000..2d44f8b
    --- /dev/null
    +++ tests/test/tmultilinestring25.pp
    @@ -0,0 +1,26 @@
    +program tmultilinestring25;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft Auto}
    +
    +const
    +  Str1 = `SELECT o.*, C.Company
    +          from Orders O
    +          join Customer C
    +            on o.CustNo=C.ID
    +          where
    +            O.saledate=DATE '2001.03.20'`;
    +
    +const
    +  Str2 =
    +    `SELECT o.*, C.Company
    +     from Orders O
    +     join Customer C
    +       on o.CustNo=C.ID
    +     where
    +       O.saledate=DATE '2001.03.20'`;
    +
    +begin
    +  WriteLn(Str1);
    +  WriteLn(Str2);
    +end.
    diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
    new file mode 100644
    index 0000000..6b1c57f
    --- /dev/null
    +++ tests/test/tmultilinestring26.pp
    @@ -0,0 +1,24 @@
    +program tmultilinestring26;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const middleSpaceBug: array[0..5] of string = (
    +#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
    +,
    +#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
    +,
    +#$41`      -      `#$42  //<--- one space left instead of six spaces
    +,
    +#$41`      -      `#$42`  --  `  //<---- the same bug twice
    +,
    +'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
    +,
    +^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
    +);
    +
    +var S: String;
    +
    +begin
    +  for S in middleSpaceBug do WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
    new file mode 100644
    index 0000000..6625906
    --- /dev/null
    +++ tests/test/tmultilinestring3.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring3;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
    new file mode 100644
    index 0000000..6489542
    --- /dev/null
    +++ tests/test/tmultilinestring4.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring4;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
    new file mode 100644
    index 0000000..a868d93
    --- /dev/null
    +++ tests/test/tmultilinestring5.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring5;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
    new file mode 100644
    index 0000000..1ca0c5c
    --- /dev/null
    +++ tests/test/tmultilinestring6.pp
    @@ -0,0 +1,65 @@
    +program tmultilinestring6;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CR}
    +
    +const A =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding CRLF}
    +
    +const B =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding LF}
    +
    +const C =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding PLATFORM}
    +
    +const D =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding RAW}
    +
    +const E =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +  Write(C);
    +  Write(D);
    +  Write(E);
    +end.
    diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
    new file mode 100644
    index 0000000..4946ba1
    --- /dev/null
    +++ tests/test/tmultilinestring7.pp
    @@ -0,0 +1,17 @@
    +{ %FAIL }
    +
    +program tmultilinestring7;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding DOESNOTEXIST}
    +
    +const Blah =
    +`
    +A
    +B
    +C
    +`;
    +
    +begin
    +  Write(Blah);
    +end.
    diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
    new file mode 100644
    index 0000000..00c6cc5
    --- /dev/null
    +++ tests/test/tmultilinestring8.pp
    @@ -0,0 +1,38 @@
    +{ %FAIL }
    +
    +program tmultilinestring8;
    +
    +{ Ryan's example from the mailing list }
    +
    +{$modeswitch MultiLineStrings}
    +
    +const lines: ansistring = `
    +  #version 150
    +
    +  uniform sampler2D textures[8];
    +  in vec2 vertexTexCoord;
    +  in vec4 vertexColor;
    +  in float vertexUVMap;
    +  out vec4 fragColor;
    +
    +  void main()
    +  {
    +    if (vertexUVMap == 255) {
    +      fragColor = vertexColor;
    +    } else {
    +      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
    +      if (vertexColor.a < fragColor.a) {
    +        fragColor.a = vertexColor.a;
    +      }
    +    }
    +
    +    // TODO: testing
    +    fragColor = vec4(1,0,0,1);
    +  }
    +`;
    +
    +var
    +  s: ansistring = lines;
    +begin
    +  writeln(b);
    +end.
    diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
    new file mode 100644
    index 0000000..033f8fb
    --- /dev/null
    +++ tests/test/tmultilinestring9.pp
    @@ -0,0 +1,13 @@
    +{ %FAIL }
    +
    +program tmultilinestring9;
    +
    +{ "Forget" to set the modeswitch... }
    +
    +const NotAllowed = `
    +Oh
    +no!
    +`;
    +
    +begin
    +end.
    diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
    new file mode 100644
    index 0000000..62dafe2
    --- /dev/null
    +++ tests/test/umultilinestring1.pp
    @@ -0,0 +1,21 @@
    +unit umultilinestring1;
    +
    +{$CodePage UTF8}
    +{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CRLF}
    +
    +interface
    +
    +{ Some Mark Twain... }
    +
    +const Long =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +implementation
    +
    +end.
    diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
    new file mode 100644
    index 0000000..ca08f05
    --- /dev/null
    +++ tests/test/umultilinestring2.pp
    @@ -0,0 +1,17 @@
    +unit umultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +interface
    +
    +procedure DoIt;
    +
    +implementation
    +
    +procedure DoIt; public name `SingleLineMultiLine`;
    +begin
    +  Write('Ok!');
    +end;
    +
    +end.
    
  • multi_line_strings_tests_rev5.patch (26,938 bytes)
    diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
    new file mode 100644
    index 0000000..007874c
    --- /dev/null
    +++ tests/test/tmultilinestring1.pp
    @@ -0,0 +1,14 @@
    +program tmultilinestring1;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const MyString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyString);
    +end.
    diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
    new file mode 100644
    index 0000000..59aeb2c
    --- /dev/null
    +++ tests/test/tmultilinestring10.pp
    @@ -0,0 +1,9 @@
    +program tmultilinestring10;
    +
    +{ Test the use of multiline strings from units in programs }
    +
    +uses umultilinestring1;
    +
    +begin
    +  Write(Long);
    +end.
    diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
    new file mode 100644
    index 0000000..206e61c
    --- /dev/null
    +++ tests/test/tmultilinestring11.pp
    @@ -0,0 +1,19 @@
    +{ %FAIL }
    +
    +program tmultilinestring11;
    +
    +{$modeswitch MultiLineStrings}
    +{$H-}
    +
    +{ Some Mark Twain... }
    +
    +const WayTooLong =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +begin
    +  Write(WayTooLong);
    +end.
    diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
    new file mode 100644
    index 0000000..ca6a800
    --- /dev/null
    +++ tests/test/tmultilinestring12.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring12;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 4}
    +
    +procedure TakesAString(const S: String);
    +begin
    +  Write(S);
    +end;
    +
    +begin
    +  TakesAString(`
    +    This
    +    works
    +    just
    +    fine!
    +  `);
    +end.
    diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
    new file mode 100644
    index 0000000..3652c76
    --- /dev/null
    +++ tests/test/tmultilinestring13.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring13;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const A =
    +`
    +``a``
    +`;
    +
    +const B =
    +`
    +'a'
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +end.
    diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
    new file mode 100644
    index 0000000..e181ff7
    --- /dev/null
    +++ tests/test/tmultilinestring14.pp
    @@ -0,0 +1,40 @@
    +program tmultilinestring14;
    +
    +{$modeswitch MultiLineStrings}
    +
    +{$MultiLineStringTrimLeft 2}
    +
    +const A = `
    +  A
    +  B
    +  C
    +  D
    +`;
    +
    +{$MultiLineStringTrimLeft 4}
    +
    +const B = `
    +    A
    +    B
    +    C
    +    D
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +
    +  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
    +    as it stops immediately when it is no longer actually *in* whitespace regardless. }
    +
    +  {$MultiLineStringTrimLeft 10000}
    +
    +  { Non-leading whitespace is preserved properly, of course. }
    +
    +  Write(`
    +        sdfs
    +        sd fs fs
    +        sd  fsfs  sdfd sfdf
    +        sdfs fsd
    +  `);
    +end.
    diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
    new file mode 100644
    index 0000000..fa3426b
    --- /dev/null
    +++ tests/test/tmultilinestring15.pp
    @@ -0,0 +1,37 @@
    +program tmultilinestring15;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +const X = `
    +  Hello
    +  every
    +  body!
    +`;
    +
    +const Y = `
    +    Goodbye
    +    every
    +    body!
    +    
    +`;
    +
    +{ Test some wacky concatentation }
    +
    +begin
    +  Write(X + Y);
    +  Write(Concat(X, Y));
    +  Write(
    +    '  Single line string ' +
    +    `
    +    and
    +    ` +
    +    `
    +    Multi
    +    line
    +    string
    +    ` +
    +    Y +
    +    X
    +  );
    +end.
    diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
    new file mode 100644
    index 0000000..9e1d7b5
    --- /dev/null
    +++ tests/test/tmultilinestring16.pp
    @@ -0,0 +1,30 @@
    +program tmultilinestring16;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +procedure TakesAnArray(constref A: array of String);
    +var S: String;
    +begin
    +  for S in A do Write(S);
    +end;
    +
    +begin
    +  TakesAnArray([
    +    ` Multi
    +      line
    +      one!`,
    +    `
    +      Multi
    +      line
    +      two!`,
    +    `
    +      Multi
    +      line
    +      three!
    +    `,
    +    'Single line one!' + sLineBreak +
    +    'Single line two!' + sLineBreak +
    +    'Single line three!'
    +  ]);
    +end.
    diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
    new file mode 100644
    index 0000000..116d6f8
    --- /dev/null
    +++ tests/test/tmultilinestring17.pp
    @@ -0,0 +1,39 @@
    +program tmultilinestring17;
    +
    +{$mode ObjFPC}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +type
    +  TMessage = record
    +    Msg: String;
    +  end;
    +
    +  TMyClass = class
    +  public
    +    procedure MyMessage(var Msg: TMessage); message `
    +      Multi
    +      Line
    +      Message!
    +    `;
    +  end;
    +
    +  procedure TMyClass.MyMessage(var Msg: TMessage);
    +  begin
    +    WriteLn('Ok!');
    +  end;
    +
    +  const M: TMessage = (
    +    Msg: `
    +      Multi
    +      Line
    +      Message!
    +    `
    +  );
    +
    +begin
    +  with TMyClass.Create() do begin
    +    DispatchStr(M);
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
    new file mode 100644
    index 0000000..d98f7b9
    --- /dev/null
    +++ tests/test/tmultilinestring18.pp
    @@ -0,0 +1,19 @@
    +program tmultilinestring18;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +{$Warnings On}
    +
    +procedure IsDeprecated; deprecated
    +`
    +  Multi
    +  line
    +  deprecation
    +  message!
    +`;
    +begin
    +end;
    +
    +begin
    +  IsDeprecated;
    +end.
    diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
    new file mode 100644
    index 0000000..a7e7201
    --- /dev/null
    +++ tests/test/tmultilinestring19.pp
    @@ -0,0 +1,15 @@
    +program tmultilinestring19;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +{ This is extremely unlikely, but it needs to work properly... }
    +
    +procedure Bloop; external name `
    +  Actually
    +  Called
    +  Bloop
    +`;
    +
    +begin
    +end.
    diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
    new file mode 100644
    index 0000000..3da55bc
    --- /dev/null
    +++ tests/test/tmultilinestring2.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
    new file mode 100644
    index 0000000..6491720
    --- /dev/null
    +++ tests/test/tmultilinestring20.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring20;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +uses umultilinestring2;
    +
    +begin
    +  DoIt;
    +end.
    diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
    new file mode 100644
    index 0000000..a3ee3f6
    --- /dev/null
    +++ tests/test/tmultilinestring21.pp
    @@ -0,0 +1,16 @@
    +{ %FAIL }
    +
    +program tmultilinestring21;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2000000}
    +
    +const S = `
    +  A
    +  B
    +  C
    +`;
    +
    +begin
    +  WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
    new file mode 100644
    index 0000000..0b21b31
    --- /dev/null
    +++ tests/test/tmultilinestring22.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring22;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const Test = ` ThisSpace>>>>     <<<<disappeared`;
    +
    +begin
    +  Write(Test);
    +end.
    diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
    new file mode 100644
    index 0000000..fee9e04
    --- /dev/null
    +++ tests/test/tmultilinestring23.pp
    @@ -0,0 +1,51 @@
    +program tmultilinestring23;
    +
    +{$mode ObjFPC}{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +{$modeswitch PrefixedAttributes}
    +
    +uses RTTI;
    +
    +type
    +  TMultiLineAttribute = class(TCustomAttribute)
    +  private
    +    FString: String;
    +  public
    +    constructor Create(const S: String);
    +    property StringValue: String read FString;
    +  end;
    +
    +  constructor TMultiLineAttribute.Create(const S: String);
    +  begin
    +    FString := S;
    +  end;
    +
    +type
    +  [TMultiLineAttribute(
    +    `This is my
    +     pretty cool
    +     multi-line string
    +     attribute!`
    +  )]
    +  [TMultiLineAttribute(
    +    `This is my
    +     even cooler
    +     multi-line string
    +     attribute!`
    +  )]
    +  TMyClass = class
    +  end;
    +
    +var
    +  A: TMultiLineAttribute;
    +
    +begin
    +  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
    +    for TCustomAttribute(A) in GetAttributes() do begin
    +      WriteLn(A.StringValue);
    +      A.Free();
    +    end;
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
    new file mode 100644
    index 0000000..7a282f9
    --- /dev/null
    +++ tests/test/tmultilinestring24.pp
    @@ -0,0 +1,62 @@
    +program tmultilinestring24;
    +
    +{ engkin's bug example }
    +
    +{$mode objfpc}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 15}
    +{$MultiLineStringLineEnding Platform}
    +
    +var
    +{$MultiLineStringLineEnding CR}
    +  a: array[0..3] of string = (
    +``
    +,
    +`
    +`
    +,
    +`
    +
    +`
    +,
    +`
    +
    +
    +`);
    +
    +  {$MultiLineStringLineEnding CRLF}
    +b: array[0..3] of string = (
    +`1`
    +,
    +`1
    +2`
    +,
    +`1
    +2
    +3`
    +,
    +`1
    +2
    +3
    +4`);
    +
    +procedure Test(constref StrArray:array of string);
    +var
    +  s,sHex: string;
    +  c: char;
    +begin
    +  for s in StrArray do
    +  begin
    +    WriteLn('Length: ',Length(s));
    +    sHex := '';
    +    for c in s do
    +      sHex := sHex+'$'+hexStr(ord(c),2);
    +    WriteLn(sHex);
    +  end;
    +  WriteLn('---------------');
    +end;
    +
    +begin
    +  Test(a);
    +  Test(b);
    +end.
    diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
    new file mode 100644
    index 0000000..2d44f8b
    --- /dev/null
    +++ tests/test/tmultilinestring25.pp
    @@ -0,0 +1,26 @@
    +program tmultilinestring25;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft Auto}
    +
    +const
    +  Str1 = `SELECT o.*, C.Company
    +          from Orders O
    +          join Customer C
    +            on o.CustNo=C.ID
    +          where
    +            O.saledate=DATE '2001.03.20'`;
    +
    +const
    +  Str2 =
    +    `SELECT o.*, C.Company
    +     from Orders O
    +     join Customer C
    +       on o.CustNo=C.ID
    +     where
    +       O.saledate=DATE '2001.03.20'`;
    +
    +begin
    +  WriteLn(Str1);
    +  WriteLn(Str2);
    +end.
    diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
    new file mode 100644
    index 0000000..6b1c57f
    --- /dev/null
    +++ tests/test/tmultilinestring26.pp
    @@ -0,0 +1,24 @@
    +program tmultilinestring26;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const middleSpaceBug: array[0..5] of string = (
    +#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
    +,
    +#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
    +,
    +#$41`      -      `#$42  //<--- one space left instead of six spaces
    +,
    +#$41`      -      `#$42`  --  `  //<---- the same bug twice
    +,
    +'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
    +,
    +^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
    +);
    +
    +var S: String;
    +
    +begin
    +  for S in middleSpaceBug do WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring27.pp tests/test/tmultilinestring27.pp
    new file mode 100644
    index 0000000..9245120
    --- /dev/null
    +++ tests/test/tmultilinestring27.pp
    @@ -0,0 +1,16 @@
    +program tmultilinestring27;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft Auto}
    +
    +resourcestring S =
    +    `This
    +       is
    +     a
    +       multi-line
    +     resource
    +       string`;
    +
    +begin
    +  Write(S);
    +end.
    \ No newline at end of file
    diff --git tests/test/tmultilinestring28.pas tests/test/tmultilinestring28.pas
    new file mode 100644
    index 0000000..5509bb8
    --- /dev/null
    +++ tests/test/tmultilinestring28.pas
    @@ -0,0 +1,24 @@
    +{ %FAIL }
    +
    +{ Will show: 
    +  tmultilinestring28.pas(17,1) Fatal: Unterminated multi-line string beginning at line 8, column 7. }
    +
    +program tmultilinestring28;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const
    +  a = `this will be unterminated
    +with some
    +lines in it.
    +
    +var
    +  B : String;
    +
    +begin
    +  B:=`
    +again
    +something
    +end backticked`;
    +
    +end.
    diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
    new file mode 100644
    index 0000000..6625906
    --- /dev/null
    +++ tests/test/tmultilinestring3.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring3;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
    new file mode 100644
    index 0000000..6489542
    --- /dev/null
    +++ tests/test/tmultilinestring4.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring4;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
    new file mode 100644
    index 0000000..a868d93
    --- /dev/null
    +++ tests/test/tmultilinestring5.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring5;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
    new file mode 100644
    index 0000000..a72af31
    --- /dev/null
    +++ tests/test/tmultilinestring6.pp
    @@ -0,0 +1,65 @@
    +program tmultilinestring6;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CR}
    +
    +const A =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding CRLF}
    +
    +const B =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding LF}
    +
    +const C =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding PLATFORM}
    +
    +const D =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding SOURCE}
    +
    +const E =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +  Write(C);
    +  Write(D);
    +  Write(E);
    +end.
    diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
    new file mode 100644
    index 0000000..4946ba1
    --- /dev/null
    +++ tests/test/tmultilinestring7.pp
    @@ -0,0 +1,17 @@
    +{ %FAIL }
    +
    +program tmultilinestring7;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding DOESNOTEXIST}
    +
    +const Blah =
    +`
    +A
    +B
    +C
    +`;
    +
    +begin
    +  Write(Blah);
    +end.
    diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
    new file mode 100644
    index 0000000..00c6cc5
    --- /dev/null
    +++ tests/test/tmultilinestring8.pp
    @@ -0,0 +1,38 @@
    +{ %FAIL }
    +
    +program tmultilinestring8;
    +
    +{ Ryan's example from the mailing list }
    +
    +{$modeswitch MultiLineStrings}
    +
    +const lines: ansistring = `
    +  #version 150
    +
    +  uniform sampler2D textures[8];
    +  in vec2 vertexTexCoord;
    +  in vec4 vertexColor;
    +  in float vertexUVMap;
    +  out vec4 fragColor;
    +
    +  void main()
    +  {
    +    if (vertexUVMap == 255) {
    +      fragColor = vertexColor;
    +    } else {
    +      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
    +      if (vertexColor.a < fragColor.a) {
    +        fragColor.a = vertexColor.a;
    +      }
    +    }
    +
    +    // TODO: testing
    +    fragColor = vec4(1,0,0,1);
    +  }
    +`;
    +
    +var
    +  s: ansistring = lines;
    +begin
    +  writeln(b);
    +end.
    diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
    new file mode 100644
    index 0000000..033f8fb
    --- /dev/null
    +++ tests/test/tmultilinestring9.pp
    @@ -0,0 +1,13 @@
    +{ %FAIL }
    +
    +program tmultilinestring9;
    +
    +{ "Forget" to set the modeswitch... }
    +
    +const NotAllowed = `
    +Oh
    +no!
    +`;
    +
    +begin
    +end.
    diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
    new file mode 100644
    index 0000000..62dafe2
    --- /dev/null
    +++ tests/test/umultilinestring1.pp
    @@ -0,0 +1,21 @@
    +unit umultilinestring1;
    +
    +{$CodePage UTF8}
    +{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CRLF}
    +
    +interface
    +
    +{ Some Mark Twain... }
    +
    +const Long =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +implementation
    +
    +end.
    diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
    new file mode 100644
    index 0000000..ca08f05
    --- /dev/null
    +++ tests/test/umultilinestring2.pp
    @@ -0,0 +1,17 @@
    +unit umultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +interface
    +
    +procedure DoIt;
    +
    +implementation
    +
    +procedure DoIt; public name `SingleLineMultiLine`;
    +begin
    +  Write('Ok!');
    +end;
    +
    +end.
    
  • multi_line_strings_main_rev4.patch (31,693 bytes)
    diff --git compiler/globals.pas compiler/globals.pas
    index 1f368e6..a05b7bb 100644
    --- compiler/globals.pas
    +++ compiler/globals.pas
    @@ -183,6 +183,12 @@ interface
     
              { WARNING: this pointer cannot be written as such in record token }
              pmessage : pmessagestaterecord;
    +         
    +         lineendingtype : tlineendingtype;
    +
    +         whitespacetrimcount : word;
    +         
    +         whitespacetrimauto : boolean;
            end;
     
         const
    @@ -574,6 +580,9 @@ interface
     {$endif defined(LLVM) and not defined(GENERIC_CPU)}
             controllertype : ct_none;
             pmessage : nil;
    +        lineendingtype : le_platform;
    +        whitespacetrimcount : 0;
    +        whitespacetrimauto : false
           );
     
         var
    @@ -1455,7 +1464,7 @@ implementation
            if localexepath='' then
             begin
               hs1 := ExtractFileName(exeName);
    -	  hs1 := ChangeFileExt(hs1,source_info.exeext);
    +          hs1 := ChangeFileExt(hs1,source_info.exeext);
     {$ifdef macos}
               FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
     {$else macos}
    diff --git compiler/globtype.pas compiler/globtype.pas
    index b6c142f..90d5516 100644
    --- compiler/globtype.pas
    +++ compiler/globtype.pas
    @@ -491,7 +491,8 @@ interface
              m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
              m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
              m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
    -         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
    +         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
    +         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
            );
            tmodeswitches = set of tmodeswitch;
     
    @@ -605,7 +606,18 @@ interface
              pocall_vectorcall
            );
            tproccalloptions = set of tproccalloption;
    -
    +       
    +       tlineendingtype = ({Carriage return, aka #13}
    +                          le_cr,
    +                          {Carriage return + line feed, aka #13#10}
    +                          le_crlf,
    +                          {Line feed, aka #10}
    +                          le_lf,
    +                          {Use the platform default}
    +                          le_platform,
    +                          {Use whatever is in the file}
    +                          le_source);
    +                          
          const
            proccalloptionStr : array[tproccalloption] of string[16]=('',
                'CDecl',
    @@ -683,7 +695,8 @@ interface
              'ARRAYOPERATORS',
              'MULTIHELPERS',
              'ARRAYTODYNARRAY',
    -         'PREFIXEDATTRIBUTES'
    +         'PREFIXEDATTRIBUTES',
    +         'MULTILINESTRINGS'
              );
     
     
    diff --git compiler/msg/errore.msg compiler/msg/errore.msg
    index d6a0bdf..3307f3c 100644
    --- compiler/msg/errore.msg
    +++ compiler/msg/errore.msg
    @@ -432,6 +432,14 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
     scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
     % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
     scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
    +scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or SOURCE.
    +% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or SOURCE.
    +scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
    +% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
    +scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
    +% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
    +scan_f_unterminated_multiline_string=02109_F_Unterminated multi-line string beginning at line $1, column $2.
    +% The file contains an "unbalanced" number of multi-line string denoting backtick characters.
     % \end{description}
     #
     # Parser
    diff --git compiler/pbase.pas compiler/pbase.pas
    index dffd92d..ffef25c 100644
    --- compiler/pbase.pas
    +++ compiler/pbase.pas
    @@ -144,10 +144,16 @@ implementation
         procedure consume(i : ttoken);
           begin
             if (token<>i) and (idtoken<>i) then
    -          if token=_id then
    -            Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
    -          else
    -            Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str)
    +          begin
    +            if (current_scanner.multiline_start_column>0) and (current_scanner.multiline_start_line>0) then
    +              Message2(scan_f_unterminated_multiline_string,
    +                       tostr(current_scanner.multiline_start_line),
    +                       tostr(current_scanner.multiline_start_column))
    +            else if token=_id then
    +              Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
    +            else
    +              Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str);
    +          end
             else
               begin
                 if token=_END then
    @@ -178,7 +184,12 @@ implementation
                 if token=_EOF then
                  begin
                    Consume(atoken);
    -               Message(scan_f_end_of_file);
    +               if current_scanner.in_multiline_string then
    +                 Message2(scan_f_unterminated_multiline_string,
    +                          tostr(current_scanner.multiline_start_line),
    +                          tostr(current_scanner.multiline_start_column))
    +               else
    +                 Message(scan_f_end_of_file);
                    exit;
                  end;
               end;
    diff --git compiler/pstatmnt.pas compiler/pstatmnt.pas
    index 5f6987d..38926c8 100644
    --- compiler/pstatmnt.pas
    +++ compiler/pstatmnt.pas
    @@ -1226,7 +1226,12 @@ implementation
                    consume(_PLUS);
                  end;
                _EOF :
    -             Message(scan_f_end_of_file);
    +             if current_scanner.in_multiline_string then
    +               Message2(scan_f_unterminated_multiline_string,
    +                        tostr(current_scanner.multiline_start_line),
    +                        tostr(current_scanner.multiline_start_column))
    +             else
    +               Message(scan_f_end_of_file);
              else
                begin
                  { don't typecheck yet, because that will also simplify, which may
    diff --git compiler/scandir.pas compiler/scandir.pas
    index 9dd457b..74703d7 100644
    --- compiler/scandir.pas
    +++ compiler/scandir.pas
    @@ -1003,6 +1003,67 @@ unit scandir;
               end;
           end;
     
    +    procedure dir_multilinestringlineending;
    +      var
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
    +        current_scanner.skipspace;
    +        s:=current_scanner.readid;
    +        if (s='CR') then
    +          current_settings.lineendingtype:=le_cr
    +        else if (s='CRLF') then
    +          current_settings.lineendingtype:=le_crlf
    +        else if (s='LF') then
    +          current_settings.lineendingtype:=le_lf
    +        else if (s='PLATFORM') then
    +          current_settings.lineendingtype:=le_platform
    +        else if (s='SOURCE') then
    +          current_settings.lineendingtype:=le_source
    +        else
    +          Message(scan_e_unknown_lineending_type);
    +      end;
    +
    +    procedure dir_multilinestringtrimleft;
    +      var
    +        count : longint;
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
    +        current_scanner.skipspace;
    +        if (c in ['1'..'9']) then
    +          begin
    +            count:=current_scanner.readval;
    +            if (count<0) or (count>65535) then
    +              Message(scan_e_trimcount_out_of_range)
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=count;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end
    +        else
    +          begin
    +            s:=current_scanner.readid;
    +            if s='ALL' then
    +              begin
    +                current_settings.whitespacetrimcount:=65535;
    +                current_settings.whitespacetrimauto:=false;
    +              end
    +            else if s='AUTO' then
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=true;
    +              end
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end;
    +      end;
     
         procedure dir_namespace;
           var
    @@ -1973,6 +2034,8 @@ unit scandir;
             AddDirective('MMX',directive_all, @dir_mmx);
             AddDirective('MODE',directive_all, @dir_mode);
             AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
    +        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
    +        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
             AddDirective('NAMESPACE',directive_all, @dir_namespace);
             AddDirective('NODEFINE',directive_all, @dir_nodefine);
             AddDirective('NOTE',directive_all, @dir_note);
    diff --git compiler/scanner.pas compiler/scanner.pas
    index 88b2703..4b0282e 100644
    --- compiler/scanner.pas
    +++ compiler/scanner.pas
    @@ -146,6 +146,12 @@ interface
               { true, if we are parsing preprocessor expressions }
               in_preproc_comp_expr : boolean;
     
    +          { Having these tracked in the scanner class itself versus local variables allows for
    +            informative error handling that would be impossible otherwise }
    +          in_multiline_string : boolean;
    +          multiline_start_line : longint;
    +          multiline_start_column : word;
    +
               constructor Create(const fn:string; is_macro: boolean = false);
               destructor Destroy;override;
             { File buffer things }
    @@ -187,6 +193,7 @@ interface
               procedure tokenwritesizeint(val : asizeint);
               procedure tokenwritelongint(val : longint);
               procedure tokenwritelongword(val : longword);
    +          procedure tokenwritebyte(val : byte);
               procedure tokenwriteword(val : word);
               procedure tokenwriteshortint(val : shortint);
               procedure tokenwriteset(var b;size : longint);
    @@ -2898,6 +2905,14 @@ type
             recordtokenbuf.write(val,sizeof(shortint));
           end;
     
    +    procedure tscannerfile.tokenwritebyte(val : byte);
    +      begin
    +{$ifdef FPC_BIG_ENDIAN}
    +        val:=swapendian(val);
    +{$endif}
    +        recordtokenbuf.write(val,sizeof(byte));
    +      end;
    +
         procedure tscannerfile.tokenwriteword(val : word);
           begin
     {$ifdef FPC_BIG_ENDIAN}
    @@ -3111,8 +3126,11 @@ type
                 else
                  ControllerType:=ct_none;
     {$POP}
    -           endpos:=replaytokenbuf.pos;
    -           if endpos-startpos<>expected_size then
    +            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +            whitespacetrimcount:=tokenreadword;
    +            whitespacetrimauto:=boolean(tokenreadbyte);
    +            endpos:=replaytokenbuf.pos;
    +            if endpos-startpos<>expected_size then
                  Comment(V_Error,'Wrong size of Settings read-in');
              end;
          end;
    @@ -3189,6 +3207,9 @@ type
                 if ControllerSupport then
                   tokenwriteenum(controllertype,sizeof(tcontrollertype));
     {$POP}
    +           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
    +           tokenwriteword(whitespacetrimcount);
    +           tokenwritebyte(byte(whitespacetrimauto));
                endpos:=recordtokenbuf.pos;
                size:=endpos-startpos;
                recordtokenbuf.seek(sizepos);
    @@ -3760,7 +3781,10 @@ type
         procedure tscannerfile.end_of_file;
           begin
             checkpreprocstack;
    -        Message(scan_f_end_of_file);
    +        if in_multiline_string then
    +          Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
    +        else
    +          Message(scan_f_end_of_file);
           end;
     
       {-------------------------------------------
    @@ -4247,21 +4271,41 @@ type
           begin
             i:=0;
             msgwritten:=false;
    -        if (c='''') then
    +        if (c in ['''','`']) then
               begin
    +            in_multiline_string:=(c='`');
    +            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +              begin
    +                result[0]:=chr(0);
    +                Illegal_Char(c);
    +              end;
                 repeat
                   readchar;
                   case c of
                     #26 :
                       end_of_file;
                     #10,#13 :
    -                  Message(scan_f_string_exceeds_line);
    +                  if not in_multiline_string then
    +                    begin
    +                      if (multiline_start_line>0) and (multiline_start_column>0) then
    +                        Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
    +                      else
    +                        Message(scan_f_string_exceeds_line);
    +                    end;
                     '''' :
    -                  begin
    -                    readchar;
    -                    if c<>'''' then
    -                     break;
    -                  end;
    +                  if not in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'''' then
    +                       break;
    +                    end;
    +                '`' :
    +                  if in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'`' then
    +                       break;
    +                    end;
                   end;
                   if i<255 then
                     begin
    @@ -4447,28 +4491,42 @@ type
                      if found=1 then
                       found:=2;
                    end;
    -             '''' :
    +             '''','`' :
                    if (current_commentstyle=comment_none) then
    -                begin
    -                  repeat
    -                    readchar;
    -                    case c of
    -                      #26 :
    -                        end_of_file;
    -                      #10,#13 :
    -                        break;
    -                      '''' :
    -                        begin
    -                          readchar;
    -                          if c<>'''' then
    -                           begin
    -                             next_char_loaded:=true;
    +                 begin
    +                   in_multiline_string:=(c='`');
    +                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
    +                     repeat
    +                       readchar;
    +                       case c of
    +                         #26 :
    +                           end_of_file;
    +                         #10,#13 :
    +                           if not in_multiline_string then
                                  break;
    -                           end;
    -                        end;
    -                    end;
    -                  until false;
    -                end;
    +                         '''' :
    +                           if not in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'''' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                         '`' :
    +                           if in_multiline_string then
    +                             begin
    +                               readchar;
    +                               if c<>'`' then
    +                                begin
    +                                  next_char_loaded:=true;
    +                                  break;
    +                                end;
    +                             end;
    +                       end;
    +                     until false;
    +                 end;
                  '(' :
                    begin
                      if (current_commentstyle=comment_none) then
    @@ -4664,9 +4722,15 @@ type
             mac     : tmacro;
             asciinr : string[33];
             iswidestring : boolean;
    +        had_newline,first_multiline : boolean;
    +        trimcount : word;
    +        last_c : char;
           label
    -         exit_label;
    +        quote_label,exit_label;
           begin
    +        had_newline:=false;
    +        first_multiline:=false;
    +        last_c:=#0;
             flushpendingswitchesstate;
     
             { record tokens? }
    @@ -5098,8 +5162,19 @@ type
                      goto exit_label;
                    end;
     
    -             '''','#','^' :
    +             '''','#','^','`' :
                    begin
    +                 in_multiline_string:=(c='`');
    +                 if in_multiline_string then
    +                   begin
    +                     if not (m_multiline_strings in current_settings.modeswitches) then
    +                       Illegal_Char(c)
    +                     else
    +                       begin
    +                         multiline_start_line:=current_filepos.line;
    +                         multiline_start_column:=current_filepos.column;
    +                       end;
    +                   end;
                      len:=0;
                      cstringpattern:='';
                      iswidestring:=false;
    @@ -5206,26 +5281,62 @@ type
                                begin
                                  if len>=length(cstringpattern) then
                                    setlength(cstringpattern,length(cstringpattern)+256);
    -                              inc(len);
    -                              cstringpattern[len]:=chr(m);
    +                             inc(len);
    +                             cstringpattern[len]:=chr(m);
                                end;
                            end;
    -                     '''' :
    +                     '''','`' :
                            begin
    +                         in_multiline_string:=(c='`');
    +                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                              repeat
                                readchar;
    -                           case c of
    -                             #26 :
    -                               end_of_file;
    -                             #10,#13 :
    -                               Message(scan_f_string_exceeds_line);
    -                             '''' :
    -                               begin
    -                                 readchar;
    -                                 if c<>'''' then
    -                                  break;
    -                               end;
    -                           end;
    +                           quote_label:
    +                             case c of
    +                               #26 :
    +                                 end_of_file;
    +                               #32,#9,#11 :
    +                                 if (had_newline or first_multiline) and
    +                                    (current_settings.whitespacetrimauto or
    +                                    (current_settings.whitespacetrimcount>0)) then
    +                                   begin
    +                                     if current_settings.whitespacetrimauto then
    +                                       trimcount:=multiline_start_column
    +                                     else
    +                                       trimcount:=current_settings.whitespacetrimcount;
    +                                     while (c in [#32,#9,#11]) and (trimcount>0) do
    +                                       begin
    +                                         readchar;
    +                                         dec(trimcount);
    +                                       end;
    +                                     had_newline:=false;
    +                                     first_multiline:=false;
    +                                     goto quote_label;
    +                                   end;
    +                               #10,#13 :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     if (multiline_start_line>0) and (multiline_start_column>0) then
    +                                       Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
    +                                     else
    +                                       Message(scan_f_string_exceeds_line);
    +                                   end;
    +                               '''' :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'''' then
    +                                      break;
    +                                   end;
    +                               '`' :
    +                                 if in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'`' then
    +                                      break;
    +                                   end;
    +                             end;
    +                           first_multiline:=false;
                                { interpret as utf-8 string? }
                                if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                                  begin
    @@ -5300,18 +5411,110 @@ type
                                  end
                                else if iswidestring then
                                  begin
    -                               if current_settings.sourcecodepage=CP_UTF8 then
    -                                 concatwidestringchar(patternw,ord(c))
    -                               else
    -                                 concatwidestringchar(patternw,asciichar2unicode(c))
    +                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     begin
    +                                       case current_settings.lineendingtype of
    +                                         le_cr : concatwidestringchar(patternw,ord(#13));
    +                                         le_crlf :
    +                                           begin
    +                                             concatwidestringchar(patternw,ord(#13));
    +                                             concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_lf : concatwidestringchar(patternw,ord(#10));
    +                                         le_platform :
    +                                           begin
    +                                             if target_info.newline=#13 then
    +                                               concatwidestringchar(patternw,ord(#13))
    +                                             else if target_info.newline=#13#10 then
    +                                               begin
    +                                                 concatwidestringchar(patternw,ord(#13));
    +                                                 concatwidestringchar(patternw,ord(#10));
    +                                               end
    +                                             else if target_info.newline=#10 then
    +                                               concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_source : concatwidestringchar(patternw,ord(c));
    +                                       end;
    +                                     end
    +                                   else
    +                                     case current_settings.lineendingtype of
    +                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                       le_crlf :
    +                                         begin
    +                                           concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                           concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                       le_platform :
    +                                         begin
    +                                           if target_info.newline=#13 then
    +                                             concatwidestringchar(patternw,asciichar2unicode(#13))
    +                                           else if target_info.newline=#13#10 then
    +                                             begin
    +                                               concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                               concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                             end
    +                                           else if target_info.newline=#10 then
    +                                             concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_source : concatwidestringchar(patternw,asciichar2unicode(c));
    +                                     end;
    +                                   had_newline:=true;
    +                                   inc(line_no);
    +                                 end
    +                               else if not (in_multiline_string and (c in [#10,#13])) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     concatwidestringchar(patternw,ord(c))
    +                                   else
    +                                     concatwidestringchar(patternw,asciichar2unicode(c));
    +                                 end;
                                  end
                                else
                                  begin
    -                               if len>=length(cstringpattern) then
    -                                 setlength(cstringpattern,length(cstringpattern)+256);
    -                                inc(len);
    -                                cstringpattern[len]:=c;
    +                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    case current_settings.lineendingtype of
    +                                      le_cr : cstringpattern[len]:=#13;
    +                                      le_crlf :
    +                                        begin
    +                                          cstringpattern[len]:=#13;
    +                                          inc(len);
    +                                          cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_lf : cstringpattern[len]:=#10;
    +                                      le_platform :
    +                                        begin
    +                                          if target_info.newline=#13 then
    +                                            cstringpattern[len]:=#13
    +                                          else if target_info.newline=#13#10 then
    +                                            begin
    +                                              cstringpattern[len]:=#13;
    +                                              inc(len);
    +                                              cstringpattern[len]:=#10;
    +                                            end
    +                                          else if target_info.newline=#10 then
    +                                            cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_source : cstringpattern[len]:=c;
    +                                    end;
    +                                    had_newline:=true;
    +                                    inc(line_no);
    +                                  end
    +                                else if not (in_multiline_string and (c in [#10,#13])) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    cstringpattern[len]:=c;
    +                                  end;
                                  end;
    +                         last_c:=c;
                              until false;
                            end;
                          '^' :
    @@ -5338,6 +5541,7 @@ type
                          else
                           break;
                        end;
    +                 last_c:=c;
                      until false;
                      { strings with length 1 become const chars }
                      if iswidestring then
    @@ -5445,6 +5649,10 @@ exit_label:
             low,high,mid: longint;
             optoken: ttoken;
           begin
    +         { Added the assignment to NOTOKEN below because I got a DFA uninitialized result
    +           warning when building the compiler with -O3, which broke compilation with -Sew.
    +           - Akira1364 }
    +         readpreproc:=NOTOKEN;
              skipspace;
              case c of
                '_',
    @@ -5480,12 +5688,13 @@ exit_label:
                    current_scanner.preproc_pattern:=pattern;
                    readpreproc:=optoken;
                  end;
    -           '''' :
    -             begin
    -               readquotedstring;
    -               current_scanner.preproc_pattern:=cstringpattern;
    -               readpreproc:=_CSTRING;
    -             end;
    +           '''','`' :
    +             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
    +               begin
    +                 readquotedstring;
    +                 current_scanner.preproc_pattern:=cstringpattern;
    +                 readpreproc:=_CSTRING;
    +               end;
                '0'..'9' :
                  begin
                    readnumber;
    diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
    index 7009bd8..14f600f 100644
    --- compiler/utils/ppuutils/ppudump.pp
    +++ compiler/utils/ppuutils/ppudump.pp
    @@ -2444,6 +2444,9 @@ const
                 else
                  ControllerType:=ct_none;
     {$POP}
    +           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +           whitespacetrimcount:=gettokenbufword;
    +           whitespacetrimauto:=boolean(gettokenbufbyte);
                endpos:=tbi;
                if endpos-startpos<>expected_size then
                  Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);
    
  • multi_line_strings_tests_rev6.patch (26,935 bytes)
    diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
    new file mode 100644
    index 0000000..007874c
    --- /dev/null
    +++ tests/test/tmultilinestring1.pp
    @@ -0,0 +1,14 @@
    +program tmultilinestring1;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const MyString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyString);
    +end.
    diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
    new file mode 100644
    index 0000000..59aeb2c
    --- /dev/null
    +++ tests/test/tmultilinestring10.pp
    @@ -0,0 +1,9 @@
    +program tmultilinestring10;
    +
    +{ Test the use of multiline strings from units in programs }
    +
    +uses umultilinestring1;
    +
    +begin
    +  Write(Long);
    +end.
    diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
    new file mode 100644
    index 0000000..206e61c
    --- /dev/null
    +++ tests/test/tmultilinestring11.pp
    @@ -0,0 +1,19 @@
    +{ %FAIL }
    +
    +program tmultilinestring11;
    +
    +{$modeswitch MultiLineStrings}
    +{$H-}
    +
    +{ Some Mark Twain... }
    +
    +const WayTooLong =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +begin
    +  Write(WayTooLong);
    +end.
    diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
    new file mode 100644
    index 0000000..ca6a800
    --- /dev/null
    +++ tests/test/tmultilinestring12.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring12;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 4}
    +
    +procedure TakesAString(const S: String);
    +begin
    +  Write(S);
    +end;
    +
    +begin
    +  TakesAString(`
    +    This
    +    works
    +    just
    +    fine!
    +  `);
    +end.
    diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
    new file mode 100644
    index 0000000..3652c76
    --- /dev/null
    +++ tests/test/tmultilinestring13.pp
    @@ -0,0 +1,18 @@
    +program tmultilinestring13;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const A =
    +`
    +``a``
    +`;
    +
    +const B =
    +`
    +'a'
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +end.
    diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
    new file mode 100644
    index 0000000..e181ff7
    --- /dev/null
    +++ tests/test/tmultilinestring14.pp
    @@ -0,0 +1,40 @@
    +program tmultilinestring14;
    +
    +{$modeswitch MultiLineStrings}
    +
    +{$MultiLineStringTrimLeft 2}
    +
    +const A = `
    +  A
    +  B
    +  C
    +  D
    +`;
    +
    +{$MultiLineStringTrimLeft 4}
    +
    +const B = `
    +    A
    +    B
    +    C
    +    D
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +
    +  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
    +    as it stops immediately when it is no longer actually *in* whitespace regardless. }
    +
    +  {$MultiLineStringTrimLeft 10000}
    +
    +  { Non-leading whitespace is preserved properly, of course. }
    +
    +  Write(`
    +        sdfs
    +        sd fs fs
    +        sd  fsfs  sdfd sfdf
    +        sdfs fsd
    +  `);
    +end.
    diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
    new file mode 100644
    index 0000000..fa3426b
    --- /dev/null
    +++ tests/test/tmultilinestring15.pp
    @@ -0,0 +1,37 @@
    +program tmultilinestring15;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +const X = `
    +  Hello
    +  every
    +  body!
    +`;
    +
    +const Y = `
    +    Goodbye
    +    every
    +    body!
    +    
    +`;
    +
    +{ Test some wacky concatentation }
    +
    +begin
    +  Write(X + Y);
    +  Write(Concat(X, Y));
    +  Write(
    +    '  Single line string ' +
    +    `
    +    and
    +    ` +
    +    `
    +    Multi
    +    line
    +    string
    +    ` +
    +    Y +
    +    X
    +  );
    +end.
    diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
    new file mode 100644
    index 0000000..9e1d7b5
    --- /dev/null
    +++ tests/test/tmultilinestring16.pp
    @@ -0,0 +1,30 @@
    +program tmultilinestring16;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +procedure TakesAnArray(constref A: array of String);
    +var S: String;
    +begin
    +  for S in A do Write(S);
    +end;
    +
    +begin
    +  TakesAnArray([
    +    ` Multi
    +      line
    +      one!`,
    +    `
    +      Multi
    +      line
    +      two!`,
    +    `
    +      Multi
    +      line
    +      three!
    +    `,
    +    'Single line one!' + sLineBreak +
    +    'Single line two!' + sLineBreak +
    +    'Single line three!'
    +  ]);
    +end.
    diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
    new file mode 100644
    index 0000000..116d6f8
    --- /dev/null
    +++ tests/test/tmultilinestring17.pp
    @@ -0,0 +1,39 @@
    +program tmultilinestring17;
    +
    +{$mode ObjFPC}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 6}
    +
    +type
    +  TMessage = record
    +    Msg: String;
    +  end;
    +
    +  TMyClass = class
    +  public
    +    procedure MyMessage(var Msg: TMessage); message `
    +      Multi
    +      Line
    +      Message!
    +    `;
    +  end;
    +
    +  procedure TMyClass.MyMessage(var Msg: TMessage);
    +  begin
    +    WriteLn('Ok!');
    +  end;
    +
    +  const M: TMessage = (
    +    Msg: `
    +      Multi
    +      Line
    +      Message!
    +    `
    +  );
    +
    +begin
    +  with TMyClass.Create() do begin
    +    DispatchStr(M);
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
    new file mode 100644
    index 0000000..d98f7b9
    --- /dev/null
    +++ tests/test/tmultilinestring18.pp
    @@ -0,0 +1,19 @@
    +program tmultilinestring18;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +{$Warnings On}
    +
    +procedure IsDeprecated; deprecated
    +`
    +  Multi
    +  line
    +  deprecation
    +  message!
    +`;
    +begin
    +end;
    +
    +begin
    +  IsDeprecated;
    +end.
    diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
    new file mode 100644
    index 0000000..a7e7201
    --- /dev/null
    +++ tests/test/tmultilinestring19.pp
    @@ -0,0 +1,15 @@
    +program tmultilinestring19;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +{ This is extremely unlikely, but it needs to work properly... }
    +
    +procedure Bloop; external name `
    +  Actually
    +  Called
    +  Bloop
    +`;
    +
    +begin
    +end.
    diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
    new file mode 100644
    index 0000000..3da55bc
    --- /dev/null
    +++ tests/test/tmultilinestring2.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: AnsiString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
    new file mode 100644
    index 0000000..6491720
    --- /dev/null
    +++ tests/test/tmultilinestring20.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring20;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +uses umultilinestring2;
    +
    +begin
    +  DoIt;
    +end.
    diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
    new file mode 100644
    index 0000000..a3ee3f6
    --- /dev/null
    +++ tests/test/tmultilinestring21.pp
    @@ -0,0 +1,16 @@
    +{ %FAIL }
    +
    +program tmultilinestring21;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2000000}
    +
    +const S = `
    +  A
    +  B
    +  C
    +`;
    +
    +begin
    +  WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
    new file mode 100644
    index 0000000..0b21b31
    --- /dev/null
    +++ tests/test/tmultilinestring22.pp
    @@ -0,0 +1,10 @@
    +program tmultilinestring22;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const Test = ` ThisSpace>>>>     <<<<disappeared`;
    +
    +begin
    +  Write(Test);
    +end.
    diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
    new file mode 100644
    index 0000000..fee9e04
    --- /dev/null
    +++ tests/test/tmultilinestring23.pp
    @@ -0,0 +1,51 @@
    +program tmultilinestring23;
    +
    +{$mode ObjFPC}{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +{$modeswitch PrefixedAttributes}
    +
    +uses RTTI;
    +
    +type
    +  TMultiLineAttribute = class(TCustomAttribute)
    +  private
    +    FString: String;
    +  public
    +    constructor Create(const S: String);
    +    property StringValue: String read FString;
    +  end;
    +
    +  constructor TMultiLineAttribute.Create(const S: String);
    +  begin
    +    FString := S;
    +  end;
    +
    +type
    +  [TMultiLineAttribute(
    +    `This is my
    +     pretty cool
    +     multi-line string
    +     attribute!`
    +  )]
    +  [TMultiLineAttribute(
    +    `This is my
    +     even cooler
    +     multi-line string
    +     attribute!`
    +  )]
    +  TMyClass = class
    +  end;
    +
    +var
    +  A: TMultiLineAttribute;
    +
    +begin
    +  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
    +    for TCustomAttribute(A) in GetAttributes() do begin
    +      WriteLn(A.StringValue);
    +      A.Free();
    +    end;
    +    Free();
    +  end;
    +end.
    diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
    new file mode 100644
    index 0000000..7a282f9
    --- /dev/null
    +++ tests/test/tmultilinestring24.pp
    @@ -0,0 +1,62 @@
    +program tmultilinestring24;
    +
    +{ engkin's bug example }
    +
    +{$mode objfpc}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 15}
    +{$MultiLineStringLineEnding Platform}
    +
    +var
    +{$MultiLineStringLineEnding CR}
    +  a: array[0..3] of string = (
    +``
    +,
    +`
    +`
    +,
    +`
    +
    +`
    +,
    +`
    +
    +
    +`);
    +
    +  {$MultiLineStringLineEnding CRLF}
    +b: array[0..3] of string = (
    +`1`
    +,
    +`1
    +2`
    +,
    +`1
    +2
    +3`
    +,
    +`1
    +2
    +3
    +4`);
    +
    +procedure Test(constref StrArray:array of string);
    +var
    +  s,sHex: string;
    +  c: char;
    +begin
    +  for s in StrArray do
    +  begin
    +    WriteLn('Length: ',Length(s));
    +    sHex := '';
    +    for c in s do
    +      sHex := sHex+'$'+hexStr(ord(c),2);
    +    WriteLn(sHex);
    +  end;
    +  WriteLn('---------------');
    +end;
    +
    +begin
    +  Test(a);
    +  Test(b);
    +end.
    diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
    new file mode 100644
    index 0000000..2d44f8b
    --- /dev/null
    +++ tests/test/tmultilinestring25.pp
    @@ -0,0 +1,26 @@
    +program tmultilinestring25;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft Auto}
    +
    +const
    +  Str1 = `SELECT o.*, C.Company
    +          from Orders O
    +          join Customer C
    +            on o.CustNo=C.ID
    +          where
    +            O.saledate=DATE '2001.03.20'`;
    +
    +const
    +  Str2 =
    +    `SELECT o.*, C.Company
    +     from Orders O
    +     join Customer C
    +       on o.CustNo=C.ID
    +     where
    +       O.saledate=DATE '2001.03.20'`;
    +
    +begin
    +  WriteLn(Str1);
    +  WriteLn(Str2);
    +end.
    diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
    new file mode 100644
    index 0000000..6b1c57f
    --- /dev/null
    +++ tests/test/tmultilinestring26.pp
    @@ -0,0 +1,24 @@
    +program tmultilinestring26;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 5}
    +
    +const middleSpaceBug: array[0..5] of string = (
    +#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
    +,
    +#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
    +,
    +#$41`      -      `#$42  //<--- one space left instead of six spaces
    +,
    +#$41`      -      `#$42`  --  `  //<---- the same bug twice
    +,
    +'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
    +,
    +^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
    +);
    +
    +var S: String;
    +
    +begin
    +  for S in middleSpaceBug do WriteLn(S);
    +end.
    diff --git tests/test/tmultilinestring27.pp tests/test/tmultilinestring27.pp
    new file mode 100644
    index 0000000..9245120
    --- /dev/null
    +++ tests/test/tmultilinestring27.pp
    @@ -0,0 +1,16 @@
    +program tmultilinestring27;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft Auto}
    +
    +resourcestring S =
    +    `This
    +       is
    +     a
    +       multi-line
    +     resource
    +       string`;
    +
    +begin
    +  Write(S);
    +end.
    \ No newline at end of file
    diff --git tests/test/tmultilinestring28.pp tests/test/tmultilinestring28.pp
    new file mode 100644
    index 0000000..b8576c0
    --- /dev/null
    +++ tests/test/tmultilinestring28.pp
    @@ -0,0 +1,24 @@
    +{ %FAIL }
    +
    +{ Will show: 
    +  tmultilinestring28.pp(20,1) Fatal: Unterminated multi-line string beginning at line 11, column 7. }
    +
    +program tmultilinestring28;
    +
    +{$modeswitch MultiLineStrings}
    +
    +const
    +  a = `this will be unterminated
    +with some
    +lines in it.
    +
    +var
    +  B : String;
    +
    +begin
    +  B:=`
    +again
    +something
    +end backticked`;
    +
    +end.
    diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
    new file mode 100644
    index 0000000..6625906
    --- /dev/null
    +++ tests/test/tmultilinestring3.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring3;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: ShortString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
    new file mode 100644
    index 0000000..6489542
    --- /dev/null
    +++ tests/test/tmultilinestring4.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring4;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: WideString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
    new file mode 100644
    index 0000000..a868d93
    --- /dev/null
    +++ tests/test/tmultilinestring5.pp
    @@ -0,0 +1,22 @@
    +program tmultilinestring5;
    +
    +{$modeswitch MultiLineStrings}
    +
    +var MyStringV: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +const MyStringC: UnicodeString =
    +`
    +Hey
    +Hey
    +Hey
    +`;
    +
    +begin
    +  Write(MyStringV);
    +  Write(MyStringC);
    +end.
    diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
    new file mode 100644
    index 0000000..a72af31
    --- /dev/null
    +++ tests/test/tmultilinestring6.pp
    @@ -0,0 +1,65 @@
    +program tmultilinestring6;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CR}
    +
    +const A =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding CRLF}
    +
    +const B =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding LF}
    +
    +const C =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding PLATFORM}
    +
    +const D =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +{$MultiLineStringLineEnding SOURCE}
    +
    +const E =
    +`
    +😊
    +😊
    +😊
    +😊
    +😊
    +`;
    +
    +begin
    +  Write(A);
    +  Write(B);
    +  Write(C);
    +  Write(D);
    +  Write(E);
    +end.
    diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
    new file mode 100644
    index 0000000..4946ba1
    --- /dev/null
    +++ tests/test/tmultilinestring7.pp
    @@ -0,0 +1,17 @@
    +{ %FAIL }
    +
    +program tmultilinestring7;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding DOESNOTEXIST}
    +
    +const Blah =
    +`
    +A
    +B
    +C
    +`;
    +
    +begin
    +  Write(Blah);
    +end.
    diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
    new file mode 100644
    index 0000000..00c6cc5
    --- /dev/null
    +++ tests/test/tmultilinestring8.pp
    @@ -0,0 +1,38 @@
    +{ %FAIL }
    +
    +program tmultilinestring8;
    +
    +{ Ryan's example from the mailing list }
    +
    +{$modeswitch MultiLineStrings}
    +
    +const lines: ansistring = `
    +  #version 150
    +
    +  uniform sampler2D textures[8];
    +  in vec2 vertexTexCoord;
    +  in vec4 vertexColor;
    +  in float vertexUVMap;
    +  out vec4 fragColor;
    +
    +  void main()
    +  {
    +    if (vertexUVMap == 255) {
    +      fragColor = vertexColor;
    +    } else {
    +      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
    +      if (vertexColor.a < fragColor.a) {
    +        fragColor.a = vertexColor.a;
    +      }
    +    }
    +
    +    // TODO: testing
    +    fragColor = vec4(1,0,0,1);
    +  }
    +`;
    +
    +var
    +  s: ansistring = lines;
    +begin
    +  writeln(b);
    +end.
    diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
    new file mode 100644
    index 0000000..033f8fb
    --- /dev/null
    +++ tests/test/tmultilinestring9.pp
    @@ -0,0 +1,13 @@
    +{ %FAIL }
    +
    +program tmultilinestring9;
    +
    +{ "Forget" to set the modeswitch... }
    +
    +const NotAllowed = `
    +Oh
    +no!
    +`;
    +
    +begin
    +end.
    diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
    new file mode 100644
    index 0000000..62dafe2
    --- /dev/null
    +++ tests/test/umultilinestring1.pp
    @@ -0,0 +1,21 @@
    +unit umultilinestring1;
    +
    +{$CodePage UTF8}
    +{$H+}
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringLineEnding CRLF}
    +
    +interface
    +
    +{ Some Mark Twain... }
    +
    +const Long =
    +`
    +My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
    +And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
    +You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
    +`;
    +
    +implementation
    +
    +end.
    diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
    new file mode 100644
    index 0000000..ca08f05
    --- /dev/null
    +++ tests/test/umultilinestring2.pp
    @@ -0,0 +1,17 @@
    +unit umultilinestring2;
    +
    +{$modeswitch MultiLineStrings}
    +{$MultiLineStringTrimLeft 2}
    +
    +interface
    +
    +procedure DoIt;
    +
    +implementation
    +
    +procedure DoIt; public name `SingleLineMultiLine`;
    +begin
    +  Write('Ok!');
    +end;
    +
    +end.
    
  • multi_line_strings_main_rev5.patch (32,335 bytes)
    diff --git compiler/globals.pas compiler/globals.pas
    index 1f368e6..a05b7bb 100644
    --- compiler/globals.pas
    +++ compiler/globals.pas
    @@ -183,6 +183,12 @@ interface
     
              { WARNING: this pointer cannot be written as such in record token }
              pmessage : pmessagestaterecord;
    +         
    +         lineendingtype : tlineendingtype;
    +
    +         whitespacetrimcount : word;
    +         
    +         whitespacetrimauto : boolean;
            end;
     
         const
    @@ -574,6 +580,9 @@ interface
     {$endif defined(LLVM) and not defined(GENERIC_CPU)}
             controllertype : ct_none;
             pmessage : nil;
    +        lineendingtype : le_platform;
    +        whitespacetrimcount : 0;
    +        whitespacetrimauto : false
           );
     
         var
    @@ -1455,7 +1464,7 @@ implementation
            if localexepath='' then
             begin
               hs1 := ExtractFileName(exeName);
    -	  hs1 := ChangeFileExt(hs1,source_info.exeext);
    +          hs1 := ChangeFileExt(hs1,source_info.exeext);
     {$ifdef macos}
               FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
     {$else macos}
    diff --git compiler/globtype.pas compiler/globtype.pas
    index b6c142f..90d5516 100644
    --- compiler/globtype.pas
    +++ compiler/globtype.pas
    @@ -491,7 +491,8 @@ interface
              m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
              m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
              m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
    -         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
    +         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
    +         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
            );
            tmodeswitches = set of tmodeswitch;
     
    @@ -605,7 +606,18 @@ interface
              pocall_vectorcall
            );
            tproccalloptions = set of tproccalloption;
    -
    +       
    +       tlineendingtype = ({Carriage return, aka #13}
    +                          le_cr,
    +                          {Carriage return + line feed, aka #13#10}
    +                          le_crlf,
    +                          {Line feed, aka #10}
    +                          le_lf,
    +                          {Use the platform default}
    +                          le_platform,
    +                          {Use whatever is in the file}
    +                          le_source);
    +                          
          const
            proccalloptionStr : array[tproccalloption] of string[16]=('',
                'CDecl',
    @@ -683,7 +695,8 @@ interface
              'ARRAYOPERATORS',
              'MULTIHELPERS',
              'ARRAYTODYNARRAY',
    -         'PREFIXEDATTRIBUTES'
    +         'PREFIXEDATTRIBUTES',
    +         'MULTILINESTRINGS'
              );
     
     
    diff --git compiler/msg/errore.msg compiler/msg/errore.msg
    index d6a0bdf..3307f3c 100644
    --- compiler/msg/errore.msg
    +++ compiler/msg/errore.msg
    @@ -432,6 +432,14 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
     scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
     % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
     scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
    +scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or SOURCE.
    +% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or SOURCE.
    +scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
    +% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
    +scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
    +% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
    +scan_f_unterminated_multiline_string=02109_F_Unterminated multi-line string beginning at line $1, column $2.
    +% The file contains an "unbalanced" number of multi-line string denoting backtick characters.
     % \end{description}
     #
     # Parser
    diff --git compiler/pbase.pas compiler/pbase.pas
    index dffd92d..c994bc7 100644
    --- compiler/pbase.pas
    +++ compiler/pbase.pas
    @@ -144,10 +144,16 @@ implementation
         procedure consume(i : ttoken);
           begin
             if (token<>i) and (idtoken<>i) then
    -          if token=_id then
    -            Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
    -          else
    -            Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str)
    +          begin
    +            if current_scanner.had_multiline_string then
    +              Message2(scan_f_unterminated_multiline_string,
    +                       tostr(current_scanner.multiline_start_line),
    +                       tostr(current_scanner.multiline_start_column))
    +            else if token=_id then
    +              Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
    +            else
    +              Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str);
    +          end
             else
               begin
                 if token=_END then
    @@ -178,7 +184,12 @@ implementation
                 if token=_EOF then
                  begin
                    Consume(atoken);
    -               Message(scan_f_end_of_file);
    +               if current_scanner.had_multiline_string then
    +                 Message2(scan_f_unterminated_multiline_string,
    +                          tostr(current_scanner.multiline_start_line),
    +                          tostr(current_scanner.multiline_start_column))
    +               else
    +                 Message(scan_f_end_of_file);
                    exit;
                  end;
               end;
    diff --git compiler/pstatmnt.pas compiler/pstatmnt.pas
    index 5f6987d..ea32f6d 100644
    --- compiler/pstatmnt.pas
    +++ compiler/pstatmnt.pas
    @@ -1226,7 +1226,12 @@ implementation
                    consume(_PLUS);
                  end;
                _EOF :
    -             Message(scan_f_end_of_file);
    +             if current_scanner.had_multiline_string then
    +               Message2(scan_f_unterminated_multiline_string,
    +                        tostr(current_scanner.multiline_start_line),
    +                        tostr(current_scanner.multiline_start_column))
    +             else
    +               Message(scan_f_end_of_file);
              else
                begin
                  { don't typecheck yet, because that will also simplify, which may
    diff --git compiler/scandir.pas compiler/scandir.pas
    index 9dd457b..74703d7 100644
    --- compiler/scandir.pas
    +++ compiler/scandir.pas
    @@ -1003,6 +1003,67 @@ unit scandir;
               end;
           end;
     
    +    procedure dir_multilinestringlineending;
    +      var
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
    +        current_scanner.skipspace;
    +        s:=current_scanner.readid;
    +        if (s='CR') then
    +          current_settings.lineendingtype:=le_cr
    +        else if (s='CRLF') then
    +          current_settings.lineendingtype:=le_crlf
    +        else if (s='LF') then
    +          current_settings.lineendingtype:=le_lf
    +        else if (s='PLATFORM') then
    +          current_settings.lineendingtype:=le_platform
    +        else if (s='SOURCE') then
    +          current_settings.lineendingtype:=le_source
    +        else
    +          Message(scan_e_unknown_lineending_type);
    +      end;
    +
    +    procedure dir_multilinestringtrimleft;
    +      var
    +        count : longint;
    +        s : string;
    +      begin
    +        if not (m_multiline_strings in current_settings.modeswitches) then
    +          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
    +        current_scanner.skipspace;
    +        if (c in ['1'..'9']) then
    +          begin
    +            count:=current_scanner.readval;
    +            if (count<0) or (count>65535) then
    +              Message(scan_e_trimcount_out_of_range)
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=count;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end
    +        else
    +          begin
    +            s:=current_scanner.readid;
    +            if s='ALL' then
    +              begin
    +                current_settings.whitespacetrimcount:=65535;
    +                current_settings.whitespacetrimauto:=false;
    +              end
    +            else if s='AUTO' then
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=true;
    +              end
    +            else
    +              begin
    +                current_settings.whitespacetrimcount:=0;
    +                current_settings.whitespacetrimauto:=false;
    +              end;
    +          end;
    +      end;
     
         procedure dir_namespace;
           var
    @@ -1973,6 +2034,8 @@ unit scandir;
             AddDirective('MMX',directive_all, @dir_mmx);
             AddDirective('MODE',directive_all, @dir_mode);
             AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
    +        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
    +        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
             AddDirective('NAMESPACE',directive_all, @dir_namespace);
             AddDirective('NODEFINE',directive_all, @dir_nodefine);
             AddDirective('NOTE',directive_all, @dir_note);
    diff --git compiler/scanner.pas compiler/scanner.pas
    index 88b2703..52c3256 100644
    --- compiler/scanner.pas
    +++ compiler/scanner.pas
    @@ -146,6 +146,12 @@ interface
               { true, if we are parsing preprocessor expressions }
               in_preproc_comp_expr : boolean;
     
    +          { Having these tracked in the scanner class itself versus local variables allows for
    +            informative error handling that would be impossible otherwise }
    +          in_multiline_string, had_multiline_string : boolean;
    +          multiline_start_line : longint;
    +          multiline_start_column : word;
    +
               constructor Create(const fn:string; is_macro: boolean = false);
               destructor Destroy;override;
             { File buffer things }
    @@ -187,6 +193,7 @@ interface
               procedure tokenwritesizeint(val : asizeint);
               procedure tokenwritelongint(val : longint);
               procedure tokenwritelongword(val : longword);
    +          procedure tokenwritebyte(val : byte);
               procedure tokenwriteword(val : word);
               procedure tokenwriteshortint(val : shortint);
               procedure tokenwriteset(var b;size : longint);
    @@ -2898,6 +2905,14 @@ type
             recordtokenbuf.write(val,sizeof(shortint));
           end;
     
    +    procedure tscannerfile.tokenwritebyte(val : byte);
    +      begin
    +{$ifdef FPC_BIG_ENDIAN}
    +        val:=swapendian(val);
    +{$endif}
    +        recordtokenbuf.write(val,sizeof(byte));
    +      end;
    +
         procedure tscannerfile.tokenwriteword(val : word);
           begin
     {$ifdef FPC_BIG_ENDIAN}
    @@ -3111,8 +3126,11 @@ type
                 else
                  ControllerType:=ct_none;
     {$POP}
    -           endpos:=replaytokenbuf.pos;
    -           if endpos-startpos<>expected_size then
    +            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +            whitespacetrimcount:=tokenreadword;
    +            whitespacetrimauto:=boolean(tokenreadbyte);
    +            endpos:=replaytokenbuf.pos;
    +            if endpos-startpos<>expected_size then
                  Comment(V_Error,'Wrong size of Settings read-in');
              end;
          end;
    @@ -3189,6 +3207,9 @@ type
                 if ControllerSupport then
                   tokenwriteenum(controllertype,sizeof(tcontrollertype));
     {$POP}
    +           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
    +           tokenwriteword(whitespacetrimcount);
    +           tokenwritebyte(byte(whitespacetrimauto));
                endpos:=recordtokenbuf.pos;
                size:=endpos-startpos;
                recordtokenbuf.seek(sizepos);
    @@ -3760,7 +3781,10 @@ type
         procedure tscannerfile.end_of_file;
           begin
             checkpreprocstack;
    -        Message(scan_f_end_of_file);
    +        if in_multiline_string then
    +          Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
    +        else
    +          Message(scan_f_end_of_file);
           end;
     
       {-------------------------------------------
    @@ -4247,21 +4271,44 @@ type
           begin
             i:=0;
             msgwritten:=false;
    -        if (c='''') then
    +        if (c in ['''','`']) then
               begin
    +            had_multiline_string:=in_multiline_string;
    +            in_multiline_string:=(c='`');
    +            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +              begin
    +                result[0]:=chr(0);
    +                Illegal_Char(c);
    +              end;
                 repeat
                   readchar;
                   case c of
                     #26 :
                       end_of_file;
                     #10,#13 :
    -                  Message(scan_f_string_exceeds_line);
    +                  if not in_multiline_string then
    +                    begin
    +                      if had_multiline_string then
    +                        Message2(scan_f_unterminated_multiline_string,
    +                                 tostr(multiline_start_line),
    +                                 tostr(multiline_start_column))
    +                      else
    +                        Message(scan_f_string_exceeds_line);
    +                    end;
                     '''' :
    -                  begin
    -                    readchar;
    -                    if c<>'''' then
    -                     break;
    -                  end;
    +                  if not in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'''' then
    +                       break;
    +                    end;
    +                '`' :
    +                  if in_multiline_string then
    +                    begin
    +                      readchar;
    +                      if c<>'`' then
    +                       break;
    +                    end;
                   end;
                   if i<255 then
                     begin
    @@ -4447,28 +4494,44 @@ type
                      if found=1 then
                       found:=2;
                    end;
    -             '''' :
    +             '''','`' :
                    if (current_commentstyle=comment_none) then
    -                begin
    -                  repeat
    -                    readchar;
    -                    case c of
    -                      #26 :
    -                        end_of_file;
    -                      #10,#13 :
    -                        break;
    -                      '''' :
    -                        begin
    -                          readchar;
    -                          if c<>'''' then
    +                 begin
    +                   had_multiline_string:=in_multiline_string;
    +                   in_multiline_string:=(c='`');
    +                   if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
    +                     Illegal_Char(c);
    +                   repeat
    +                     readchar;
    +                     case c of
    +                       #26 :
    +                         end_of_file;
    +                       #10,#13 :
    +                         if not in_multiline_string then
    +                           break;
    +                       '''' :
    +                         if not in_multiline_string then
                                begin
    -                             next_char_loaded:=true;
    -                             break;
    +                             readchar;
    +                             if c<>'''' then
    +                              begin
    +                                next_char_loaded:=true;
    +                                break;
    +                              end;
                                end;
    -                        end;
    -                    end;
    -                  until false;
    -                end;
    +                       '`' :
    +                         if in_multiline_string then
    +                           begin
    +                             readchar;
    +                             if c<>'`' then
    +                              begin
    +                                next_char_loaded:=true;
    +                                break;
    +                              end;
    +                           end;
    +                     end;
    +                   until false;
    +                 end;
                  '(' :
                    begin
                      if (current_commentstyle=comment_none) then
    @@ -4664,9 +4727,15 @@ type
             mac     : tmacro;
             asciinr : string[33];
             iswidestring : boolean;
    +        had_newline,first_multiline : boolean;
    +        trimcount : word;
    +        last_c : char;
           label
    -         exit_label;
    +        quote_label,exit_label;
           begin
    +        had_newline:=false;
    +        first_multiline:=false;
    +        last_c:=#0;
             flushpendingswitchesstate;
     
             { record tokens? }
    @@ -5098,8 +5167,20 @@ type
                      goto exit_label;
                    end;
     
    -             '''','#','^' :
    +             '''','#','^','`' :
                    begin
    +                 had_multiline_string:=in_multiline_string;
    +                 in_multiline_string:=(c='`');
    +                 if in_multiline_string then
    +                   begin
    +                     if not (m_multiline_strings in current_settings.modeswitches) then
    +                       Illegal_Char(c)
    +                     else
    +                       begin
    +                         multiline_start_line:=current_filepos.line;
    +                         multiline_start_column:=current_filepos.column;
    +                       end;
    +                   end;
                      len:=0;
                      cstringpattern:='';
                      iswidestring:=false;
    @@ -5206,26 +5287,65 @@ type
                                begin
                                  if len>=length(cstringpattern) then
                                    setlength(cstringpattern,length(cstringpattern)+256);
    -                              inc(len);
    -                              cstringpattern[len]:=chr(m);
    +                             inc(len);
    +                             cstringpattern[len]:=chr(m);
                                end;
                            end;
    -                     '''' :
    +                     '''','`' :
                            begin
    +                         had_multiline_string:=in_multiline_string;
    +                         in_multiline_string:=(c='`');
    +                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                              repeat
                                readchar;
    -                           case c of
    -                             #26 :
    -                               end_of_file;
    -                             #10,#13 :
    -                               Message(scan_f_string_exceeds_line);
    -                             '''' :
    -                               begin
    -                                 readchar;
    -                                 if c<>'''' then
    -                                  break;
    -                               end;
    -                           end;
    +                           quote_label:
    +                             case c of
    +                               #26 :
    +                                 end_of_file;
    +                               #32,#9,#11 :
    +                                 if (had_newline or first_multiline) and
    +                                    (current_settings.whitespacetrimauto or
    +                                    (current_settings.whitespacetrimcount>0)) then
    +                                   begin
    +                                     if current_settings.whitespacetrimauto then
    +                                       trimcount:=multiline_start_column
    +                                     else
    +                                       trimcount:=current_settings.whitespacetrimcount;
    +                                     while (c in [#32,#9,#11]) and (trimcount>0) do
    +                                       begin
    +                                         readchar;
    +                                         dec(trimcount);
    +                                       end;
    +                                     had_newline:=false;
    +                                     first_multiline:=false;
    +                                     goto quote_label;
    +                                   end;
    +                               #10,#13 :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     if had_multiline_string then
    +                                       Message2(scan_f_unterminated_multiline_string,
    +                                                tostr(multiline_start_line),
    +                                                tostr(multiline_start_column))
    +                                     else
    +                                       Message(scan_f_string_exceeds_line);
    +                                   end;
    +                               '''' :
    +                                 if not in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'''' then
    +                                      break;
    +                                   end;
    +                               '`' :
    +                                 if in_multiline_string then
    +                                   begin
    +                                     readchar;
    +                                     if c<>'`' then
    +                                      break;
    +                                   end;
    +                             end;
    +                           first_multiline:=false;
                                { interpret as utf-8 string? }
                                if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                                  begin
    @@ -5300,18 +5420,119 @@ type
                                  end
                                else if iswidestring then
                                  begin
    -                               if current_settings.sourcecodepage=CP_UTF8 then
    -                                 concatwidestringchar(patternw,ord(c))
    -                               else
    -                                 concatwidestringchar(patternw,asciichar2unicode(c))
    +                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     begin
    +                                       case current_settings.lineendingtype of
    +                                         le_cr :
    +                                           concatwidestringchar(patternw,ord(#13));
    +                                         le_crlf :
    +                                           begin
    +                                             concatwidestringchar(patternw,ord(#13));
    +                                             concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_lf :
    +                                           concatwidestringchar(patternw,ord(#10));
    +                                         le_platform :
    +                                           begin
    +                                             if target_info.newline=#13 then
    +                                               concatwidestringchar(patternw,ord(#13))
    +                                             else if target_info.newline=#13#10 then
    +                                               begin
    +                                                 concatwidestringchar(patternw,ord(#13));
    +                                                 concatwidestringchar(patternw,ord(#10));
    +                                               end
    +                                             else if target_info.newline=#10 then
    +                                               concatwidestringchar(patternw,ord(#10));
    +                                           end;
    +                                         le_source :
    +                                           concatwidestringchar(patternw,ord(c));
    +                                       end;
    +                                     end
    +                                   else
    +                                     case current_settings.lineendingtype of
    +                                       le_cr :
    +                                         concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                       le_crlf :
    +                                         begin
    +                                           concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                           concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_lf :
    +                                         concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                       le_platform :
    +                                         begin
    +                                           if target_info.newline=#13 then
    +                                             concatwidestringchar(patternw,asciichar2unicode(#13))
    +                                           else if target_info.newline=#13#10 then
    +                                             begin
    +                                               concatwidestringchar(patternw,asciichar2unicode(#13));
    +                                               concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                             end
    +                                           else if target_info.newline=#10 then
    +                                             concatwidestringchar(patternw,asciichar2unicode(#10));
    +                                         end;
    +                                       le_source :
    +                                         concatwidestringchar(patternw,asciichar2unicode(c));
    +                                     end;
    +                                   had_newline:=true;
    +                                   inc(line_no);
    +                                 end
    +                               else if not (in_multiline_string and (c in [#10,#13])) then
    +                                 begin
    +                                   if current_settings.sourcecodepage=CP_UTF8 then
    +                                     concatwidestringchar(patternw,ord(c))
    +                                   else
    +                                     concatwidestringchar(patternw,asciichar2unicode(c));
    +                                 end;
                                  end
                                else
                                  begin
    -                               if len>=length(cstringpattern) then
    -                                 setlength(cstringpattern,length(cstringpattern)+256);
    -                                inc(len);
    -                                cstringpattern[len]:=c;
    +                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    case current_settings.lineendingtype of
    +                                      le_cr :
    +                                        cstringpattern[len]:=#13;
    +                                      le_crlf :
    +                                        begin
    +                                          cstringpattern[len]:=#13;
    +                                          inc(len);
    +                                          cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_lf :
    +                                        cstringpattern[len]:=#10;
    +                                      le_platform :
    +                                        begin
    +                                          if target_info.newline=#13 then
    +                                            cstringpattern[len]:=#13
    +                                          else if target_info.newline=#13#10 then
    +                                            begin
    +                                              cstringpattern[len]:=#13;
    +                                              inc(len);
    +                                              cstringpattern[len]:=#10;
    +                                            end
    +                                          else if target_info.newline=#10 then
    +                                            cstringpattern[len]:=#10;
    +                                        end;
    +                                      le_source :
    +                                        cstringpattern[len]:=c;
    +                                    end;
    +                                    had_newline:=true;
    +                                    inc(line_no);
    +                                  end
    +                                else if not (in_multiline_string and (c in [#10,#13])) then
    +                                  begin
    +                                    if len>=length(cstringpattern) then
    +                                      setlength(cstringpattern,length(cstringpattern)+256);
    +                                    inc(len);
    +                                    cstringpattern[len]:=c;
    +                                  end;
                                  end;
    +                         last_c:=c;
                              until false;
                            end;
                          '^' :
    @@ -5338,6 +5559,7 @@ type
                          else
                           break;
                        end;
    +                 last_c:=c;
                      until false;
                      { strings with length 1 become const chars }
                      if iswidestring then
    @@ -5445,6 +5667,10 @@ exit_label:
             low,high,mid: longint;
             optoken: ttoken;
           begin
    +         { Added the assignment to NOTOKEN below because I got a DFA uninitialized result
    +           warning when building the compiler with -O3, which broke compilation with -Sew.
    +           - Akira1364 }
    +         readpreproc:=NOTOKEN;
              skipspace;
              case c of
                '_',
    @@ -5480,12 +5706,13 @@ exit_label:
                    current_scanner.preproc_pattern:=pattern;
                    readpreproc:=optoken;
                  end;
    -           '''' :
    -             begin
    -               readquotedstring;
    -               current_scanner.preproc_pattern:=cstringpattern;
    -               readpreproc:=_CSTRING;
    -             end;
    +           '''','`' :
    +             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
    +               begin
    +                 readquotedstring;
    +                 current_scanner.preproc_pattern:=cstringpattern;
    +                 readpreproc:=_CSTRING;
    +               end;
                '0'..'9' :
                  begin
                    readnumber;
    diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
    index 7009bd8..14f600f 100644
    --- compiler/utils/ppuutils/ppudump.pp
    +++ compiler/utils/ppuutils/ppudump.pp
    @@ -2444,6 +2444,9 @@ const
                 else
                  ControllerType:=ct_none;
     {$POP}
    +           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
    +           whitespacetrimcount:=gettokenbufword;
    +           whitespacetrimauto:=boolean(gettokenbufbyte);
                endpos:=tbi;
                if endpos-startpos<>expected_size then
                  Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);
    
  • lazarus_multiline_strings.pas.patch (993 bytes)
    Index: components/codetools/linkscanner.pas
    ===================================================================
    --- components/codetools/linkscanner.pas	(revision 61600)
    +++ components/codetools/linkscanner.pas	(working copy)
    @@ -224,7 +224,8 @@
         cmsExternalClass,      { pas2js: allow  class external [pkgname] name [symbol] }
         cmsIgnoreAttributes,   { pas2js: ignore attributes }
         cmsOmitRTTI,           { pas2js: 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 }
    +    msMultiHelpers,        { off=only one helper per type, on=all }
    +    cmsMultiLineStrings    { disable/enable multi line string support in fpc }
         );
       TCompilerModeSwitches = set of TCompilerModeSwitch;
     const
    @@ -312,7 +313,8 @@
         'EXTERNALCLASS',
         'IGNOREATTRIBUTES',
         'OMITRTTI',
    -    'MULTIHELPERS'
    +    'MULTIHELPERS',
    +    'MULTILINESTRINGS'
         );
     
     
    

Activities

Awkward

2019-07-11 06:11

reporter   ~0117160

Last edited: 2019-07-11 06:13

View 2 revisions

Isn't it bad idea? That the not pascal way at all and really, not so necessary feature
btw, this way not too suitable for lines with spaces at start-end of text lines

Akira1364

2019-07-11 06:45

reporter   ~0117161

Last edited: 2019-07-11 19:35

View 7 revisions

@Awkward:
Whitespace trimming is handled via a customizable directive called {$MULTILINESTRINGTRIMLEFT} that takes a numeric value from 0 to 65535, which I specifically added to address concerns people expressed on the mailing list in that regard. Look at the tests for some examples.

I strongly disagree that the feature is unnecessary or "not the Pascal way", as I do not see how

'String' + sLineBreak +
'String' + sLineBreak +
'String' + sLineBreak +
'String' + sLineBreak
 times infinity

is at all particularly more "Pascal-like."

Akira1364

2019-07-11 17:44

reporter   ~0117183

Last edited: 2019-07-11 21:40

View 8 revisions

Here's a simple example adapted from one of my tests, that I think clearly explains how the trim directive works, that I'm posting here to make it more directly accessible:

program Example;

// You must set the below modeswitch to use the feature,
// and will get an "illegal char" error at the opening
// backtick of the first multi-line string encountered if you don't.
// To be clear: multi-line strings are exclusively denoted with backticks,
// not single quotes.

{$modeswitch MultiLineStrings}

{$MultiLineStringTrimLeft 1}

// There's two leading spaces on each line
// of the multi-line string below. One will
// be removed from each line, based on what we
// just set for the trim directive.

const A = `
  A
  B
  C
  D
`;

{$MultiLineStringTrimLeft 3}

// There's four leading spaces on each line
// of the multi-line string below. Three will
// be removed from each line, based on what we
// just set for the trim directive.

const B = `
    A
    B
    C
    D
`;

begin
  Write(A);
  Write(B);
end.

The output of that is, thus:

 A
 B
 C
 D

 A
 B
 C
 D

since we first remove one space from where only two existed, and then remove three spaces from where four existed, leaving us with identical strings that have one leading space on each line.

Note that the default setting for MULTILINESTRINGTRIMLEFT is 0, meaning, by default no whitespace is removed.

If you set the directive to a higher number than the amount of leading whitespace present, the scanner will just stop skipping characters when it hits a non-whitespace one anyways, so you don't need to worry about mangling your strings or anything like that.

Additionally (as was an actual requirement specified for this feature by Michael van Canneyt) there exists a {$MULTILINESTRINGLINEENDING} directive, which takes one of CR, CRLF, LF, PLATFORM, or RAW as valid options. This specifies which particular line ending characters (or combination of characters in the case of CRLF) should be used at the end of each line in a multi-line string.

For anyone not aware:

CR = 13
CRLF = 1310
LF = 10

PLATFORM means the compiler will use whichever line ending type is native to your operating system. RAW means the compiler will use the line endings exactly as present in the physical source file. RAW is the default.

SlightlyOutOfPhase

2019-07-11 19:33

reporter   ~0117187

This seems very well thought out to me.

Ryan Joseph

2019-07-11 19:59

reporter   ~0117188

This is an unmitigated win as far as I'm concerned. There's no good reason why Pascal shouldn't have multi-line strings in 2019.

jamie philbrook

2019-07-11 22:52

reporter   ~0117191

This I can see will break stuff.,..

Many coders, including me like to separate each entry vertically and do not expect line endings..
what is wrong with using the current method so that we can see what is actually happening...?
 Pascal like many other languages allow multiple lines to enter a single item, here you are basically creating CRLF inserts automatically and
they aren't visible / obvious to the coder..

 This is a bad idea, plain and simple...

 Now I would go for having Code tools or the Editor have a popup option at the point of entry to allow you to enter multiple lines
and the end results would have the Line Endings visibly encoded in front of you like we do now, at least you can see what is happening..

 '1'0000013#10'seee' etc...
 Just have Lazarus code tools give the coder an popup editor for this..

 having the compiler do it is going to be a nightmare for coders that like to format their source code in specific ways without worrying about
magical compiler inserts..
  This isn't a line by line formatted language... Please understand that...

Ryan Joseph

2019-07-11 23:00

reporter   ~0117192

Last edited: 2019-07-11 23:50

View 2 revisions

@Jamie: This is mainly for inserting code where whitespace doesn't matter and if you don't like it please don't use it. Format your code however you like this is merely giving us ANOTHER new option and it IS welcome for some of us. Please enough of the nonsense.

Here's what you're supposed to use it for;

====================

program test;

const lines: ansistring = `
 #version 150

 uniform sampler2D textures[8];
 in vec2 vertexTexCoord;
 in vec4 vertexColor;
 in float vertexUVMap;
 out vec4 fragColor;

 void main()
 {
   if (vertexUVMap == 255) {
     fragColor = vertexColor;
   } else {
     fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
     if (vertexColor.a < fragColor.a) {
       fragColor.a = vertexColor.a;
     }
   }
 }
`;

begin
end.

Akira1364

2019-07-11 23:25

reporter   ~0117194

Last edited: 2019-07-11 23:30

View 5 revisions

@Jamie:

>>> This I can see will break stuff.,..

No, it won't. Did you read the example that clearly explains the fairly robust level of control the feature provides the user?

>>> Pascal like many other languages allow multiple lines to enter a single item,
>>> here you are basically creating CRLF inserts automatically and
>>> they aren't visible / obvious to the coder..

Again, please actually read, and ensure that you understand, my description / example of the feature. The way you're describing it does not give me much confidence that you're familiar with how FPC's scanner even works, also.

@Ryan:

I'm glad you like the feature, but note that your example there does not compile as written (for several reasons.) I'd prefer to avoid confusing anyone, as much as possible.

Also note, again, the indentation / formatting concern people had is largely *solved* by my inclusion of {$MultiLineStringTrimLeft}.

Ryan Joseph

2019-07-11 23:32

reporter   ~0117195

Yeah I just copied that from the mail list and there was some garbage at the end I see now. Point still stands though.

jamie philbrook

2019-07-11 23:43

reporter   ~0117196

I fully understand what it does..
and that is the point...
I like to split my word segments in groups with multiple likes and have a single string without any CRLF in the middle of them...
I have done that for years where I break up a single string be it a constant or not but use multiple lines in the editor to lay it out..
I don't want anything other than what I put there inserted...

 You do as you please, I'll never turn that on..

 Sorry but that is how I code with large edited strings and many of them are constants.

Ryan Joseph

2019-07-11 23:50

reporter   ~0117197

Just to be clear here is the alternative to multi-line strings. If don't like working with strings like this you can replace all of it with ``. It's that simple.

const
  lines = '#version 150' +
  'uniform sampler2D textures[8];' +
  'in vec2 vertexTexCoord;' +
  'in vec4 vertexColor;' +
  'in float vertexUVMap;' +
  'out vec4 fragColor;' +
  
  'void main()' +
  '{' +
  ' if (vertexUVMap == 255) {' +
  ' fragColor = vertexColor;' +
  ' } else {' +
  ' fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);' +
  ' if (vertexColor.a < fragColor.a) {' +
  ' fragColor.a = vertexColor.a;' +
  ' }' +
  ' }' +
  '}';

Akira1364

2019-07-11 23:55

reporter   ~0117198

Last edited: 2019-07-12 01:31

View 4 revisions

@Jamie:

>>> I fully understand what it does..

I don't believe you do. What this feature does is just... literally read text, precisely the same way a text editor does.

>>> I like to split my word segments in groups with multiple likes
>>> and have a single string without any CRLF in the middle of them...

If you aren't putting some representation of a line ending in between the "+" symbols (such as sLineBreak or whatever) then your strings literally *don't have* newlines, and if you displayed them they'd appear as one extremely long horizontal line. I can't imagine what you mean by this.

>>> You do as you please, I'll never turn that on..

Nobody said you had to. I do in fact intend to keep this patch up to date and available until it gets merged, though, literally forever if I have to.

SlightlyOutOfPhase

2019-07-12 00:15

reporter   ~0117199

How could anyone find this confusing? I don't get it.

jamie philbrook

2019-07-12 02:50

reporter   ~0117201

You just don't get it do you?
I want to edit in multiline when I want to break up a group within a string/Constant.. I don't want
WRITELN to spit out multiline!
 and I don't want leading spaces removed!
 I want the compiler to assembler the string in order as I typed it, no matter if I split the line up to build a single string or keep it on a single line..

This issue is that I work with limited editing space at times on smaller screens and its very common to use multiple lines for a single string.

 trimming left spaces isn't going to cut it and spitting out line feeds for each line in the editor of code isn't going to cut it..
 
 I simply can't believe such a feature is even suggested for a compiler without some direct inline modifier on the fly to force this.

 why can't code tools in laz simply be enhanced to give you a editing window to visually insert the line endings instead of hiding them inside
a compiler function?

I really hope this does not make it past the QC desk. seriously /././

There are much more needed enhancement to the compiler that can be made, this just isn't one of them unless you want to hinder the coders.

Ryan Joseph

2019-07-12 03:01

reporter   ~0117202

@jamie: this is for code where whitespace doesn't matter. Please try to understand that and stop complaining like we're taking away your single-line strings because we're not. If you need more control of line endings/indentation etc... then *please* just ignore this feature. For the rest of us it's a welcome addition to make embedding code (like GLSL shaders and SQL queries) easier.

jamie philbrook

2019-07-12 03:23

reporter   ~0117203

Look to clarify this, I don't want spaces and line inserts In the final results is what I am getting at..

Yes I like the idea of multiline for strings etc. I use a mini editor to create the strings and then drop them into the
source code, it comes out as long line run with the visible chars, easy to know exactly what is in there..
This is what I am talking about
Const A = '
  Line one and here we go
 with a continued line etc and more on...
';

I don't want spaces on the left removed and I don't want inserted line feeds in that..

I made a little mini editor just to create specific strings with UTf8 symbols , tabs spaces and control chars that translate to a pascal
string, it may not look pretty in the source editor but it works, I just don't want the source editor taking away and inserting something I
didn't put in there.

 Personally I always thought an optional C type quoted string should be implemented, so if one starts with a " it should end with a " etc
but with that, you can use the escape chars to generate all the control chars you want but still keep it readable..
oh well

 Do what you want, no amount of bitching from me is going to make a difference. Just pleas make it switchable because I won't allow manipulation
of the content within the final string, but I do like the idea of the IDE be able to do multiline without the manipulation to the string.

Ryan Joseph

2019-07-12 03:29

reporter   ~0117204

You have to enable the feature via {$modeswitch MultiLineStrings}. In case you didn't know the multi-line stings are between back ticks `` so there's no chance of it messing with other strings. Users won't know it's even there unless they explicitly enable it.

Akira1364

2019-07-12 03:32

reporter   ~0117205

Last edited: 2019-07-12 05:29

View 8 revisions

@Ryan:

>>> "If you need more control of line endings/indentation etc... then *please* just ignore this feature."

I still think the trimming directive *does* give far more control than he seems to believe. And I can expand on it in the future if it turns out there's a specific thing (or things) that enough people want added.

I don't think any of this matters though, as he seems to think:

A) everyone uses Lazarus

B) that the compiler is at any point "generating" newlines rather than reading them from the file (and *possibly* changing them to a different *style* of newline if he has set a directive that specifically says it should)

C) that in general this feature is somehow magic, and not just dead-simple lexing

@Jamie:

>>> "I simply can't believe such a feature is even suggested for a compiler without some direct inline modifier on the fly to force this."

What do you even mean by this?

>>> "why can't code tools in laz simply be enhanced to give you a editing window to visually insert the line endings instead of hiding them inside a compiler function?"

Not only is that not the same thing at all, it would also be *significantly* more complex to implement than this straightforward compiler feature, believe it or not.

>>> "There are much more needed enhancement to the compiler that can be made, this just isn't one of them unless you want to hinder the coders."

Anyone "hindered" by this has far larger problems to deal with. Furthermore what you personally might think is more / less / e.t.c needed or important is completely irrelevant. I implemented this because I want to use it, and because I'm aware a non-trivial number of other people want to use it, and lastly because I *could* implement it.

I did not just open this issue out of the blue. I posted on the mailing list well beforehand very intentionally.

>>> "I don't want spaces on the left removed and I don't want inserted line feeds in that.."

By default, *left-side space are not removed*. As my example in one of my first comments *clearly* explains.

As far as having *no* newlines there, that is actually possible. I could in fact add a NONE option to the MultiLineStringLineEnding directive. It would be very easy. Perhaps I will.

Akira1364

2019-07-12 04:38

reporter   ~0117206

Last edited: 2019-07-12 05:24

View 2 revisions

I've just fixed a bug pointed out by @engkin on the Lazarus forums, and also added a test for the particular bit of syntax that had the issue.

The changes have been pushed to my Github fork already, and I've uploaded a new pair of patch files here as well.

Akira1364

2019-07-13 04:17

reporter   ~0117236

Fixed a second bug very helpfully reported again, by @engkin on the Lazarus forums.

Also, updated my branch to the latest. Noticed attributes had been merged. Added a test for multi-line string attributes! (Yes, they work!)

All changes have been pushed to my Github fork branch, and I've again uploaded two new patches here.

multi_line_strings_main_rev2.patch (26,091 bytes)
diff --git compiler/globals.pas compiler/globals.pas
index 1f368e6..9e2a93a 100644
--- compiler/globals.pas
+++ compiler/globals.pas
@@ -183,6 +183,10 @@ interface
 
          { WARNING: this pointer cannot be written as such in record token }
          pmessage : pmessagestaterecord;
+         
+         lineendingtype : tlineendingtype;
+
+         whitespacetrimcount : word;
        end;
 
     const
@@ -574,6 +578,8 @@ interface
 {$endif defined(LLVM) and not defined(GENERIC_CPU)}
         controllertype : ct_none;
         pmessage : nil;
+        lineendingtype : le_raw;
+        whitespacetrimcount : 0
       );
 
     var
@@ -1455,7 +1461,7 @@ implementation
        if localexepath='' then
         begin
           hs1 := ExtractFileName(exeName);
-	  hs1 := ChangeFileExt(hs1,source_info.exeext);
+          hs1 := ChangeFileExt(hs1,source_info.exeext);
 {$ifdef macos}
           FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
 {$else macos}
diff --git compiler/globtype.pas compiler/globtype.pas
index b6c142f..a03ff3a 100644
--- compiler/globtype.pas
+++ compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -605,7 +606,18 @@ interface
          pocall_vectorcall
        );
        tproccalloptions = set of tproccalloption;
-
+       
+       tlineendingtype = ({Carriage return, aka #13}
+                          le_cr,
+                          {Carriage return + line feed, aka #13#10}
+                          le_crlf,
+                          {Line feed, aka #10}
+                          le_lf,
+                          {Use the platform default}
+                          le_platform,
+                          {Use whatever is in the file}
+                          le_raw);
+                          
      const
        proccalloptionStr : array[tproccalloption] of string[16]=('',
            'CDecl',
@@ -683,7 +695,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'MULTILINESTRINGS'
          );
 
 
diff --git compiler/msg/errore.msg compiler/msg/errore.msg
index e42badb..b8bb9fe 100644
--- compiler/msg/errore.msg
+++ compiler/msg/errore.msg
@@ -432,6 +432,12 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
 scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
 % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
 scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
+scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or RAW.
+% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or RAW.
+scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
+% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
+scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
+% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
 % \end{description}
 #
 # Parser
@@ -1583,7 +1589,7 @@ parser_w_operator_overloaded_hidden_3=03347_W_Operator overload hidden by intern
 parser_e_threadvar_must_be_class=03348_E_Thread variables inside classes or records must be class variables
 % A \var{threadvar} section inside a class or record was started without it being prefixed by \var{class}.
 parser_e_only_static_members_via_object_type=03349_E_Only static methods and static variables can be referenced through an object type
-parser_e_unbound_attribute=03350_E_Unbound custom attribute: "$1".
+parser_e_unbound_attribute=03351_E_Unbound custom attribute: "$1".
 % A custom attribute is defined, but there is no identifier to bind it to.
 % This error occurs in a situation like the following:
 % \begin{verbatim}
diff --git compiler/pdecobj.pas compiler/pdecobj.pas
index c74a57f..c071ff1 100644
--- compiler/pdecobj.pas
+++ compiler/pdecobj.pas
@@ -1143,7 +1143,7 @@ implementation
       procedure check_unbound_attributes;
         begin
           if assigned(rtti_attrs_def) and (rtti_attrs_def.get_attribute_count>0) then
-            Message1(scan_e_unresolved_attribute,trtti_attribute(rtti_attrs_def.rtti_attributes[0]).typesym.prettyname);
+            Message1(parser_e_unbound_attribute,trtti_attribute(rtti_attrs_def.rtti_attributes[0]).typesym.prettyname);
           rtti_attrs_def.free;
           rtti_attrs_def:=nil;
         end;
diff --git compiler/scandir.pas compiler/scandir.pas
index 9dd457b..9202247 100644
--- compiler/scandir.pas
+++ compiler/scandir.pas
@@ -1003,6 +1003,41 @@ unit scandir;
           end;
       end;
 
+    procedure dir_multilinestringlineending;
+      var
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
+        current_scanner.skipspace;
+        s:=current_scanner.readid;
+        if (s='CR') then
+          current_settings.lineendingtype:=le_cr
+        else if (s='CRLF') then
+          current_settings.lineendingtype:=le_crlf
+        else if (s='LF') then
+          current_settings.lineendingtype:=le_lf
+        else if (s='PLATFORM') then
+          current_settings.lineendingtype:=le_platform
+        else if (s='RAW') then
+          current_settings.lineendingtype:=le_raw
+        else
+          Message(scan_e_unknown_lineending_type);
+      end;
+
+    procedure dir_multilinestringtrimleft;
+      var
+        count : longint;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
+        current_scanner.skipspace;
+        count:=current_scanner.readval;
+        if (count<0) or (count>65535) then
+          Message(scan_e_trimcount_out_of_range)
+        else
+          current_settings.whitespacetrimcount:=count;
+      end;
 
     procedure dir_namespace;
       var
@@ -1973,6 +2008,8 @@ unit scandir;
         AddDirective('MMX',directive_all, @dir_mmx);
         AddDirective('MODE',directive_all, @dir_mode);
         AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
+        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
+        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
         AddDirective('NAMESPACE',directive_all, @dir_namespace);
         AddDirective('NODEFINE',directive_all, @dir_nodefine);
         AddDirective('NOTE',directive_all, @dir_note);
diff --git compiler/scanner.pas compiler/scanner.pas
index 88b2703..fb90be4 100644
--- compiler/scanner.pas
+++ compiler/scanner.pas
@@ -101,6 +101,9 @@ interface
          procedure savetokenpos;
          procedure restoretokenpos;
          procedure writetoken(t: ttoken);
+         procedure buildplatformnewlineascii(var len: longint);
+         procedure buildplatformnewlineutf8;
+         procedure buildplatformnewlineunicode;
          function readtoken : ttoken;
        public
           inputfile    : tinputfile;  { current inputfile list }
@@ -146,6 +149,9 @@ interface
           { true, if we are parsing preprocessor expressions }
           in_preproc_comp_expr : boolean;
 
+          { last character read }
+          last_c : char;
+
           constructor Create(const fn:string; is_macro: boolean = false);
           destructor Destroy;override;
         { File buffer things }
@@ -3111,8 +3117,10 @@ type
             else
              ControllerType:=ct_none;
 {$POP}
-           endpos:=replaytokenbuf.pos;
-           if endpos-startpos<>expected_size then
+            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+            whitespacetrimcount:=tokenreadword;
+            endpos:=replaytokenbuf.pos;
+            if endpos-startpos<>expected_size then
              Comment(V_Error,'Wrong size of Settings read-in');
          end;
      end;
@@ -3189,6 +3197,8 @@ type
             if ControllerSupport then
               tokenwriteenum(controllertype,sizeof(tcontrollertype));
 {$POP}
+           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
+           tokenwriteword(whitespacetrimcount);
            endpos:=recordtokenbuf.pos;
            size:=endpos-startpos;
            recordtokenbuf.seek(sizepos);
@@ -4243,25 +4253,40 @@ type
     function tscannerfile.readquotedstring:string;
       var
         i : longint;
-        msgwritten : boolean;
+        msgwritten,in_multiline_string : boolean;
       begin
         i:=0;
         msgwritten:=false;
-        if (c='''') then
+        if (c in ['''','`']) then
           begin
+            in_multiline_string:=(c='`');
+            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+              begin
+                result[0]:=chr(0);
+                Illegal_Char(c);
+              end;
             repeat
               readchar;
               case c of
                 #26 :
                   end_of_file;
                 #10,#13 :
-                  Message(scan_f_string_exceeds_line);
+                  if not in_multiline_string then
+                    Message(scan_f_string_exceeds_line);
                 '''' :
-                  begin
-                    readchar;
-                    if c<>'''' then
-                     break;
-                  end;
+                  if not in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'''' then
+                       break;
+                    end;
+                '`' :
+                  if in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'`' then
+                       break;
+                    end;
               end;
               if i<255 then
                 begin
@@ -4389,7 +4414,7 @@ type
     procedure tscannerfile.skipuntildirective;
       var
         found : longint;
-        next_char_loaded : boolean;
+        next_char_loaded,in_multiline_string : boolean;
       begin
          found:=0;
          next_char_loaded:=false;
@@ -4447,28 +4472,42 @@ type
                  if found=1 then
                   found:=2;
                end;
-             '''' :
+             '''','`' :
                if (current_commentstyle=comment_none) then
-                begin
-                  repeat
-                    readchar;
-                    case c of
-                      #26 :
-                        end_of_file;
-                      #10,#13 :
-                        break;
-                      '''' :
-                        begin
-                          readchar;
-                          if c<>'''' then
-                           begin
-                             next_char_loaded:=true;
+                 begin
+                   in_multiline_string:=(c='`');
+                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
+                     repeat
+                       readchar;
+                       case c of
+                         #26 :
+                           end_of_file;
+                         #10,#13 :
+                           if not in_multiline_string then
                              break;
-                           end;
-                        end;
-                    end;
-                  until false;
-                end;
+                         '''' :
+                           if not in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'''' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                         '`' :
+                           if in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'`' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                       end;
+                     until false;
+                 end;
              '(' :
                begin
                  if (current_commentstyle=comment_none) then
@@ -4653,6 +4692,46 @@ type
                                Token Scanner
 ****************************************************************************}
 
+    procedure tscannerfile.buildplatformnewlineascii(var len: longint);
+      begin
+        if target_info.newline=#13 then
+          cstringpattern[len]:=#13
+        else if target_info.newline=#13#10 then
+          begin
+            cstringpattern[len]:=#13;
+            inc(len);
+            cstringpattern[len]:=#10;
+          end
+        else if target_info.newline=#10 then
+          cstringpattern[len]:=#10;
+      end;
+
+    procedure tscannerfile.buildplatformnewlineutf8;
+      begin
+        if target_info.newline=#13 then
+          concatwidestringchar(patternw,ord(#13))
+        else if target_info.newline=#13#10 then
+          begin
+            concatwidestringchar(patternw,ord(#13));
+            concatwidestringchar(patternw,ord(#10));
+          end
+        else if target_info.newline=#10 then
+          concatwidestringchar(patternw,ord(#10));
+      end;
+
+    procedure tscannerfile.buildplatformnewlineunicode;
+      begin
+        if target_info.newline=#13 then
+          concatwidestringchar(patternw,asciichar2unicode(#13))
+        else if target_info.newline=#13#10 then
+          begin
+            concatwidestringchar(patternw,asciichar2unicode(#13));
+            concatwidestringchar(patternw,asciichar2unicode(#10));
+          end
+        else if target_info.newline=#10 then
+          concatwidestringchar(patternw,asciichar2unicode(#10));
+      end;
+
     procedure tscannerfile.readtoken(allowrecordtoken:boolean);
       var
         code    : integer;
@@ -4664,9 +4743,14 @@ type
         mac     : tmacro;
         asciinr : string[33];
         iswidestring : boolean;
+        in_multiline_string,had_newline,first_multiline: boolean;
+        trimcount: word;
       label
-         exit_label;
+        quote_label,exit_label;
       begin
+        had_newline:=false;
+        first_multiline:=false;
+        last_c:=#0;
         flushpendingswitchesstate;
 
         { record tokens? }
@@ -5098,8 +5182,11 @@ type
                  goto exit_label;
                end;
 
-             '''','#','^' :
+             '''','#','^','`' :
                begin
+                 in_multiline_string:=(c='`');
+                 if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+                   Illegal_Char(c);
                  len:=0;
                  cstringpattern:='';
                  iswidestring:=false;
@@ -5206,26 +5293,52 @@ type
                            begin
                              if len>=length(cstringpattern) then
                                setlength(cstringpattern,length(cstringpattern)+256);
-                              inc(len);
-                              cstringpattern[len]:=chr(m);
+                             inc(len);
+                             cstringpattern[len]:=chr(m);
                            end;
                        end;
-                     '''' :
+                     '''','`' :
                        begin
+                         in_multiline_string:=(c='`');
+                         first_multiline:=in_multiline_string;
                          repeat
                            readchar;
-                           case c of
-                             #26 :
-                               end_of_file;
-                             #10,#13 :
-                               Message(scan_f_string_exceeds_line);
-                             '''' :
-                               begin
-                                 readchar;
-                                 if c<>'''' then
-                                  break;
-                               end;
-                           end;
+                           quote_label:
+                             case c of
+                               #26 :
+                                 end_of_file;
+                               #32,#9,#11 :
+                                 if (had_newline or first_multiline) and (current_settings.whitespacetrimcount > 0) then
+                                   begin
+                                     trimcount:=current_settings.whitespacetrimcount;
+                                     while (c in [#32,#9,#11]) and (trimcount > 0) do
+                                       begin
+                                         readchar;
+                                         dec(trimcount);
+                                       end;
+                                     had_newline:=false;
+                                     first_multiline:=false;
+                                     goto quote_label;
+                                   end;
+                               #10,#13 :
+                                 if not in_multiline_string then
+                                   Message(scan_f_string_exceeds_line);
+                               '''' :
+                                 if not in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'''' then
+                                      break;
+                                   end;
+                               '`' :
+                                 if in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'`' then
+                                      break;
+                                   end;
+                             end;
+                           first_multiline:=false;
                            { interpret as utf-8 string? }
                            if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                              begin
@@ -5300,17 +5413,76 @@ type
                              end
                            else if iswidestring then
                              begin
-                               if current_settings.sourcecodepage=CP_UTF8 then
-                                 concatwidestringchar(patternw,ord(c))
-                               else
-                                 concatwidestringchar(patternw,asciichar2unicode(c))
+                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     begin
+                                       case current_settings.lineendingtype of
+                                         le_cr : concatwidestringchar(patternw,ord(#13));
+                                         le_crlf :
+                                           begin
+                                             concatwidestringchar(patternw,ord(#13));
+                                             concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_lf : concatwidestringchar(patternw,ord(#10));
+                                         le_platform : buildplatformnewlineutf8;
+                                         le_raw : concatwidestringchar(patternw,ord(c));
+                                       end;
+                                     end
+                                   else
+                                     case current_settings.lineendingtype of
+                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
+                                       le_crlf :
+                                         begin
+                                           concatwidestringchar(patternw,asciichar2unicode(#13));
+                                           concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
+                                       le_platform : buildplatformnewlineunicode;
+                                       le_raw : concatwidestringchar(patternw,asciichar2unicode(c));
+                                     end;
+                                   had_newline:=true;
+                                   last_c:=c;
+                                   inc(line_no);
+                                 end
+                               else if not (in_multiline_string and (c in [#10,#13])) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     concatwidestringchar(patternw,ord(c))
+                                   else
+                                     concatwidestringchar(patternw,asciichar2unicode(c));
+                                 end;
                              end
                            else
                              begin
-                               if len>=length(cstringpattern) then
-                                 setlength(cstringpattern,length(cstringpattern)+256);
-                                inc(len);
-                                cstringpattern[len]:=c;
+                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    case current_settings.lineendingtype of
+                                      le_cr : cstringpattern[len]:=#13;
+                                      le_crlf :
+                                        begin
+                                          cstringpattern[len]:=#13;
+                                          inc(len);
+                                          cstringpattern[len]:=#10;
+                                        end;
+                                      le_lf : cstringpattern[len]:=#10;
+                                      le_platform : buildplatformnewlineascii(len);
+                                      le_raw : cstringpattern[len]:=c;
+                                    end;
+                                    had_newline:=true;
+                                    last_c:=c;
+                                    inc(line_no);
+                                  end
+                                else if not (in_multiline_string and (c in [#10,#13])) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    cstringpattern[len]:=c;
+                                  end;
                              end;
                          until false;
                        end;
@@ -5480,12 +5652,13 @@ exit_label:
                current_scanner.preproc_pattern:=pattern;
                readpreproc:=optoken;
              end;
-           '''' :
-             begin
-               readquotedstring;
-               current_scanner.preproc_pattern:=cstringpattern;
-               readpreproc:=_CSTRING;
-             end;
+           '''','`' :
+             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
+               begin
+                 readquotedstring;
+                 current_scanner.preproc_pattern:=cstringpattern;
+                 readpreproc:=_CSTRING;
+               end;
            '0'..'9' :
              begin
                readnumber;
diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
index 7009bd8..cc93428 100644
--- compiler/utils/ppuutils/ppudump.pp
+++ compiler/utils/ppuutils/ppudump.pp
@@ -2444,6 +2444,8 @@ const
             else
              ControllerType:=ct_none;
 {$POP}
+           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+           whitespacetrimcount:=gettokenbufword;
            endpos:=tbi;
            if endpos-startpos<>expected_size then
              Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);

Akira1364

2019-07-13 06:19

reporter   ~0117238

Uploaded another version of the tests patch after some revisions.

Is there a reason I cannot simply remove and replace my own attachments, BTW? Is it some limitation of Mantis? I'd much rather just update a single set of files rather than creating this ever-longer list of them.

multi_line_strings_tests_rev3.patch (24,489 bytes)
diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
new file mode 100644
index 0000000..007874c
--- /dev/null
+++ tests/test/tmultilinestring1.pp
@@ -0,0 +1,14 @@
+program tmultilinestring1;
+
+{$modeswitch MultiLineStrings}
+
+const MyString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyString);
+end.
diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
new file mode 100644
index 0000000..59aeb2c
--- /dev/null
+++ tests/test/tmultilinestring10.pp
@@ -0,0 +1,9 @@
+program tmultilinestring10;
+
+{ Test the use of multiline strings from units in programs }
+
+uses umultilinestring1;
+
+begin
+  Write(Long);
+end.
diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
new file mode 100644
index 0000000..206e61c
--- /dev/null
+++ tests/test/tmultilinestring11.pp
@@ -0,0 +1,19 @@
+{ %FAIL }
+
+program tmultilinestring11;
+
+{$modeswitch MultiLineStrings}
+{$H-}
+
+{ Some Mark Twain... }
+
+const WayTooLong =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+begin
+  Write(WayTooLong);
+end.
diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
new file mode 100644
index 0000000..ca6a800
--- /dev/null
+++ tests/test/tmultilinestring12.pp
@@ -0,0 +1,18 @@
+program tmultilinestring12;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 4}
+
+procedure TakesAString(const S: String);
+begin
+  Write(S);
+end;
+
+begin
+  TakesAString(`
+    This
+    works
+    just
+    fine!
+  `);
+end.
diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
new file mode 100644
index 0000000..3652c76
--- /dev/null
+++ tests/test/tmultilinestring13.pp
@@ -0,0 +1,18 @@
+program tmultilinestring13;
+
+{$modeswitch MultiLineStrings}
+
+const A =
+`
+``a``
+`;
+
+const B =
+`
+'a'
+`;
+
+begin
+  Write(A);
+  Write(B);
+end.
diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
new file mode 100644
index 0000000..e181ff7
--- /dev/null
+++ tests/test/tmultilinestring14.pp
@@ -0,0 +1,40 @@
+program tmultilinestring14;
+
+{$modeswitch MultiLineStrings}
+
+{$MultiLineStringTrimLeft 2}
+
+const A = `
+  A
+  B
+  C
+  D
+`;
+
+{$MultiLineStringTrimLeft 4}
+
+const B = `
+    A
+    B
+    C
+    D
+`;
+
+begin
+  Write(A);
+  Write(B);
+
+  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
+    as it stops immediately when it is no longer actually *in* whitespace regardless. }
+
+  {$MultiLineStringTrimLeft 10000}
+
+  { Non-leading whitespace is preserved properly, of course. }
+
+  Write(`
+        sdfs
+        sd fs fs
+        sd  fsfs  sdfd sfdf
+        sdfs fsd
+  `);
+end.
diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
new file mode 100644
index 0000000..fa3426b
--- /dev/null
+++ tests/test/tmultilinestring15.pp
@@ -0,0 +1,37 @@
+program tmultilinestring15;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+const X = `
+  Hello
+  every
+  body!
+`;
+
+const Y = `
+    Goodbye
+    every
+    body!
+    
+`;
+
+{ Test some wacky concatentation }
+
+begin
+  Write(X + Y);
+  Write(Concat(X, Y));
+  Write(
+    '  Single line string ' +
+    `
+    and
+    ` +
+    `
+    Multi
+    line
+    string
+    ` +
+    Y +
+    X
+  );
+end.
diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
new file mode 100644
index 0000000..9e1d7b5
--- /dev/null
+++ tests/test/tmultilinestring16.pp
@@ -0,0 +1,30 @@
+program tmultilinestring16;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+procedure TakesAnArray(constref A: array of String);
+var S: String;
+begin
+  for S in A do Write(S);
+end;
+
+begin
+  TakesAnArray([
+    ` Multi
+      line
+      one!`,
+    `
+      Multi
+      line
+      two!`,
+    `
+      Multi
+      line
+      three!
+    `,
+    'Single line one!' + sLineBreak +
+    'Single line two!' + sLineBreak +
+    'Single line three!'
+  ]);
+end.
diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
new file mode 100644
index 0000000..116d6f8
--- /dev/null
+++ tests/test/tmultilinestring17.pp
@@ -0,0 +1,39 @@
+program tmultilinestring17;
+
+{$mode ObjFPC}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+type
+  TMessage = record
+    Msg: String;
+  end;
+
+  TMyClass = class
+  public
+    procedure MyMessage(var Msg: TMessage); message `
+      Multi
+      Line
+      Message!
+    `;
+  end;
+
+  procedure TMyClass.MyMessage(var Msg: TMessage);
+  begin
+    WriteLn('Ok!');
+  end;
+
+  const M: TMessage = (
+    Msg: `
+      Multi
+      Line
+      Message!
+    `
+  );
+
+begin
+  with TMyClass.Create() do begin
+    DispatchStr(M);
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
new file mode 100644
index 0000000..d98f7b9
--- /dev/null
+++ tests/test/tmultilinestring18.pp
@@ -0,0 +1,19 @@
+program tmultilinestring18;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+{$Warnings On}
+
+procedure IsDeprecated; deprecated
+`
+  Multi
+  line
+  deprecation
+  message!
+`;
+begin
+end;
+
+begin
+  IsDeprecated;
+end.
diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
new file mode 100644
index 0000000..a7e7201
--- /dev/null
+++ tests/test/tmultilinestring19.pp
@@ -0,0 +1,15 @@
+program tmultilinestring19;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+{ This is extremely unlikely, but it needs to work properly... }
+
+procedure Bloop; external name `
+  Actually
+  Called
+  Bloop
+`;
+
+begin
+end.
diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
new file mode 100644
index 0000000..3da55bc
--- /dev/null
+++ tests/test/tmultilinestring2.pp
@@ -0,0 +1,22 @@
+program tmultilinestring2;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
new file mode 100644
index 0000000..6491720
--- /dev/null
+++ tests/test/tmultilinestring20.pp
@@ -0,0 +1,10 @@
+program tmultilinestring20;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+uses umultilinestring2;
+
+begin
+  DoIt;
+end.
diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
new file mode 100644
index 0000000..a3ee3f6
--- /dev/null
+++ tests/test/tmultilinestring21.pp
@@ -0,0 +1,16 @@
+{ %FAIL }
+
+program tmultilinestring21;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2000000}
+
+const S = `
+  A
+  B
+  C
+`;
+
+begin
+  WriteLn(S);
+end.
diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
new file mode 100644
index 0000000..e20b31b
--- /dev/null
+++ tests/test/tmultilinestring22.pp
@@ -0,0 +1,10 @@
+program tmultilinestring22;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const Test = ` ThisSpace>>>>     <<<<disappeared`;
+
+begin
+  Write(Test);
+end.
\ No newline at end of file
diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
new file mode 100644
index 0000000..fee9e04
--- /dev/null
+++ tests/test/tmultilinestring23.pp
@@ -0,0 +1,51 @@
+program tmultilinestring23;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+{$modeswitch PrefixedAttributes}
+
+uses RTTI;
+
+type
+  TMultiLineAttribute = class(TCustomAttribute)
+  private
+    FString: String;
+  public
+    constructor Create(const S: String);
+    property StringValue: String read FString;
+  end;
+
+  constructor TMultiLineAttribute.Create(const S: String);
+  begin
+    FString := S;
+  end;
+
+type
+  [TMultiLineAttribute(
+    `This is my
+     pretty cool
+     multi-line string
+     attribute!`
+  )]
+  [TMultiLineAttribute(
+    `This is my
+     even cooler
+     multi-line string
+     attribute!`
+  )]
+  TMyClass = class
+  end;
+
+var
+  A: TMultiLineAttribute;
+
+begin
+  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
+    for TCustomAttribute(A) in GetAttributes() do begin
+      WriteLn(A.StringValue);
+      A.Free();
+    end;
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
new file mode 100644
index 0000000..0fc0768
--- /dev/null
+++ tests/test/tmultilinestring24.pp
@@ -0,0 +1,62 @@
+program tmultilinestring24;
+
+{ engkin's bug example }
+ 
+{$mode objfpc}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 15}
+{$MultiLineStringLineEnding Platform}
+ 
+var
+{$MultiLineStringLineEnding CR}
+  a: array[0..3] of string = (
+``
+,
+`
+`
+,
+`
+ 
+`
+,
+`
+ 
+ 
+`);
+ 
+  {$MultiLineStringLineEnding CRLF}
+b: array[0..3] of string = (
+`1`
+,
+`1
+2`
+,
+`1
+2
+3`
+,
+`1
+2
+3
+4`);
+ 
+procedure Test(StrArray:array of string);
+var
+  s,sHex: string;
+  c: char;
+begin
+  for s in StrArray do
+  begin
+    WriteLn('Length: ',Length(s));
+    sHex := ``;
+    for c in s do
+      sHex := sHex+`$`+hexStr(ord(c),2)+` `;
+    WriteLn(sHex);
+  end;
+  WriteLn('---------------');
+end;
+ 
+begin
+  Test(a);
+  Test(b);
+end.
\ No newline at end of file
diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
new file mode 100644
index 0000000..6625906
--- /dev/null
+++ tests/test/tmultilinestring3.pp
@@ -0,0 +1,22 @@
+program tmultilinestring3;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
new file mode 100644
index 0000000..6489542
--- /dev/null
+++ tests/test/tmultilinestring4.pp
@@ -0,0 +1,22 @@
+program tmultilinestring4;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
new file mode 100644
index 0000000..a868d93
--- /dev/null
+++ tests/test/tmultilinestring5.pp
@@ -0,0 +1,22 @@
+program tmultilinestring5;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
new file mode 100644
index 0000000..cda16cd
--- /dev/null
+++ tests/test/tmultilinestring6.pp
@@ -0,0 +1,66 @@
+program tmultilinestring6;
+
+{$CodePage UTF8}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CR}
+
+const A =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding CRLF}
+
+const B =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding LF}
+
+const C =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding PLATFORM}
+
+const D =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding RAW}
+
+const E =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+begin
+  Write(A);
+  Write(B);
+  Write(C);
+  Write(D);
+  Write(E);
+end.
diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
new file mode 100644
index 0000000..4946ba1
--- /dev/null
+++ tests/test/tmultilinestring7.pp
@@ -0,0 +1,17 @@
+{ %FAIL }
+
+program tmultilinestring7;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding DOESNOTEXIST}
+
+const Blah =
+`
+A
+B
+C
+`;
+
+begin
+  Write(Blah);
+end.
diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
new file mode 100644
index 0000000..00c6cc5
--- /dev/null
+++ tests/test/tmultilinestring8.pp
@@ -0,0 +1,38 @@
+{ %FAIL }
+
+program tmultilinestring8;
+
+{ Ryan's example from the mailing list }
+
+{$modeswitch MultiLineStrings}
+
+const lines: ansistring = `
+  #version 150
+
+  uniform sampler2D textures[8];
+  in vec2 vertexTexCoord;
+  in vec4 vertexColor;
+  in float vertexUVMap;
+  out vec4 fragColor;
+
+  void main()
+  {
+    if (vertexUVMap == 255) {
+      fragColor = vertexColor;
+    } else {
+      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
+      if (vertexColor.a < fragColor.a) {
+        fragColor.a = vertexColor.a;
+      }
+    }
+
+    // TODO: testing
+    fragColor = vec4(1,0,0,1);
+  }
+`;
+
+var
+  s: ansistring = lines;
+begin
+  writeln(b);
+end.
diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
new file mode 100644
index 0000000..033f8fb
--- /dev/null
+++ tests/test/tmultilinestring9.pp
@@ -0,0 +1,13 @@
+{ %FAIL }
+
+program tmultilinestring9;
+
+{ "Forget" to set the modeswitch... }
+
+const NotAllowed = `
+Oh
+no!
+`;
+
+begin
+end.
diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
new file mode 100644
index 0000000..62dafe2
--- /dev/null
+++ tests/test/umultilinestring1.pp
@@ -0,0 +1,21 @@
+unit umultilinestring1;
+
+{$CodePage UTF8}
+{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CRLF}
+
+interface
+
+{ Some Mark Twain... }
+
+const Long =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+implementation
+
+end.
diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
new file mode 100644
index 0000000..ca08f05
--- /dev/null
+++ tests/test/umultilinestring2.pp
@@ -0,0 +1,17 @@
+unit umultilinestring2;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+interface
+
+procedure DoIt;
+
+implementation
+
+procedure DoIt; public name `SingleLineMultiLine`;
+begin
+  Write('Ok!');
+end;
+
+end.

J. Gareth Moreton

2019-07-13 06:26

developer   ~0117239

You have to have "developer" status to gain that right. Depending on how often you contribute, it might be something to ask Florian about. In the meantime, I've removed the old revisions for you.

Akira1364

2019-07-13 14:39

reporter   ~0117246

Thanks Gareth.

Ryan Joseph

2019-07-14 16:32

reporter   ~0117258

I'm trying to build from your GitHub using "lazbuild /compiler/ppcx64.lpi" but I get: Cannot open include file "msgtxt.inc". Do you know how that file is generated? I did it once last week but I can't remember at all!

Akira1364

2019-07-14 22:46

reporter   ~0117261

Last edited: 2019-07-15 18:54

View 9 revisions

If you build with the makefile they get generated automatically, so I hadn't checked the modified versions from my local repo in as it makes the diff look gigantic (and the errore.msg file that they're generated from is all that's actually needed.)

Went ahead and checked them in just now, though. I'll leave them of out the patch files here still I think, however.

That said:

I've just fixed a third bug once again helpfully reported by @engkin on the Lazarus forums, and have also now implemented two new aspects of {$MultiLineStringTrimLeft}: ALL, and AUTO.

ALL is basically just a more user-friendly way of passing the highest possible value, meaning it just guarantees that literally all leading whitespace is trimmed from each line (essentially by ensuring that the scanner runs out of whitespace to actually trim long before it decrements the temporary local trim count to zero, at which point it of course always stops immediately anyways.)

AUTO, however, is based on the column position of the opening backtick of the current multi-line string in the source file, as some people had suggested would be what they'd want.

So, for example, the following program (adapted from something someone posted on the forum thread):

program tmultilinestring25;
 
{$modeswitch MultiLineStrings}
{$MultiLineStringTrimLeft Auto}
 
// ↓↓↓ interpreted the same as if {$MultiLineStringTrimLeft 10} was set
 
const
  Str1 = `SELECT o.*, C.Company
          from Orders O
          join Customer C
            on o.CustNo=C.ID
          where
            O.saledate=DATE '2001.03.20'`;
 
// ↓↓↓ interpreted the same as if {$MultiLineStringTrimLeft 5} was set
 
const
  Str2 =
    `SELECT o.*, C.Company
     from Orders O
     join Customer C
       on o.CustNo=C.ID
     where
       O.saledate=DATE '2001.03.20'`;
 
begin
  WriteLn(Str1);
  WriteLn(Str2);
end.

prints the exact same thing for both:

SELECT o.*, C.Company
from Orders O
join Customer C
  on o.CustNo=C.ID
where
  O.saledate=DATE '2001.03.20'
SELECT o.*, C.Company
from Orders O
join Customer C
  on o.CustNo=C.ID
where
  O.saledate=DATE '2001.03.20'



multi_line_strings_main_rev3.patch (26,930 bytes)
diff --git compiler/globals.pas compiler/globals.pas
index 1f368e6..511b51d 100644
--- compiler/globals.pas
+++ compiler/globals.pas
@@ -183,6 +183,12 @@ interface
 
          { WARNING: this pointer cannot be written as such in record token }
          pmessage : pmessagestaterecord;
+         
+         lineendingtype : tlineendingtype;
+
+         whitespacetrimcount : word;
+         
+         whitespacetrimauto : boolean;
        end;
 
     const
@@ -574,6 +580,9 @@ interface
 {$endif defined(LLVM) and not defined(GENERIC_CPU)}
         controllertype : ct_none;
         pmessage : nil;
+        lineendingtype : le_raw;
+        whitespacetrimcount : 0;
+        whitespacetrimauto : false
       );
 
     var
@@ -1455,7 +1464,7 @@ implementation
        if localexepath='' then
         begin
           hs1 := ExtractFileName(exeName);
-	  hs1 := ChangeFileExt(hs1,source_info.exeext);
+          hs1 := ChangeFileExt(hs1,source_info.exeext);
 {$ifdef macos}
           FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
 {$else macos}
diff --git compiler/globtype.pas compiler/globtype.pas
index b6c142f..a03ff3a 100644
--- compiler/globtype.pas
+++ compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -605,7 +606,18 @@ interface
          pocall_vectorcall
        );
        tproccalloptions = set of tproccalloption;
-
+       
+       tlineendingtype = ({Carriage return, aka #13}
+                          le_cr,
+                          {Carriage return + line feed, aka #13#10}
+                          le_crlf,
+                          {Line feed, aka #10}
+                          le_lf,
+                          {Use the platform default}
+                          le_platform,
+                          {Use whatever is in the file}
+                          le_raw);
+                          
      const
        proccalloptionStr : array[tproccalloption] of string[16]=('',
            'CDecl',
@@ -683,7 +695,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'MULTILINESTRINGS'
          );
 
 
diff --git compiler/msg/errore.msg compiler/msg/errore.msg
index d6a0bdf..b933741 100644
--- compiler/msg/errore.msg
+++ compiler/msg/errore.msg
@@ -432,6 +432,12 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
 scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
 % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
 scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
+scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or RAW.
+% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or RAW.
+scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
+% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
+scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
+% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
 % \end{description}
 #
 # Parser
diff --git compiler/scandir.pas compiler/scandir.pas
index 9dd457b..1aebcfd 100644
--- compiler/scandir.pas
+++ compiler/scandir.pas
@@ -1003,6 +1003,67 @@ unit scandir;
           end;
       end;
 
+    procedure dir_multilinestringlineending;
+      var
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
+        current_scanner.skipspace;
+        s:=current_scanner.readid;
+        if (s='CR') then
+          current_settings.lineendingtype:=le_cr
+        else if (s='CRLF') then
+          current_settings.lineendingtype:=le_crlf
+        else if (s='LF') then
+          current_settings.lineendingtype:=le_lf
+        else if (s='PLATFORM') then
+          current_settings.lineendingtype:=le_platform
+        else if (s='RAW') then
+          current_settings.lineendingtype:=le_raw
+        else
+          Message(scan_e_unknown_lineending_type);
+      end;
+
+    procedure dir_multilinestringtrimleft;
+      var
+        count : longint;
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
+        current_scanner.skipspace;
+        if (c in ['1'..'9']) then
+          begin
+            count:=current_scanner.readval;
+            if (count<0) or (count>65535) then
+              Message(scan_e_trimcount_out_of_range)
+            else
+              begin
+                current_settings.whitespacetrimcount:=count;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end
+        else
+          begin
+            s:=current_scanner.readid;
+            if s='ALL' then
+              begin
+                current_settings.whitespacetrimcount:=65535;
+                current_settings.whitespacetrimauto:=false;
+              end
+            else if s='AUTO' then
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=true;
+              end
+            else
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end;
+      end;
 
     procedure dir_namespace;
       var
@@ -1973,6 +2034,8 @@ unit scandir;
         AddDirective('MMX',directive_all, @dir_mmx);
         AddDirective('MODE',directive_all, @dir_mode);
         AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
+        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
+        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
         AddDirective('NAMESPACE',directive_all, @dir_namespace);
         AddDirective('NODEFINE',directive_all, @dir_nodefine);
         AddDirective('NOTE',directive_all, @dir_note);
diff --git compiler/scanner.pas compiler/scanner.pas
index 88b2703..253114a 100644
--- compiler/scanner.pas
+++ compiler/scanner.pas
@@ -101,6 +101,9 @@ interface
          procedure savetokenpos;
          procedure restoretokenpos;
          procedure writetoken(t: ttoken);
+         procedure buildplatformnewlineascii(var len: longint);
+         procedure buildplatformnewlineutf8;
+         procedure buildplatformnewlineunicode;
          function readtoken : ttoken;
        public
           inputfile    : tinputfile;  { current inputfile list }
@@ -187,6 +190,7 @@ interface
           procedure tokenwritesizeint(val : asizeint);
           procedure tokenwritelongint(val : longint);
           procedure tokenwritelongword(val : longword);
+          procedure tokenwritebyte(val : byte);
           procedure tokenwriteword(val : word);
           procedure tokenwriteshortint(val : shortint);
           procedure tokenwriteset(var b;size : longint);
@@ -2898,6 +2902,14 @@ type
         recordtokenbuf.write(val,sizeof(shortint));
       end;
 
+    procedure tscannerfile.tokenwritebyte(val : byte);
+      begin
+{$ifdef FPC_BIG_ENDIAN}
+        val:=swapendian(val);
+{$endif}
+        recordtokenbuf.write(val,sizeof(byte));
+      end;
+
     procedure tscannerfile.tokenwriteword(val : word);
       begin
 {$ifdef FPC_BIG_ENDIAN}
@@ -3111,8 +3123,11 @@ type
             else
              ControllerType:=ct_none;
 {$POP}
-           endpos:=replaytokenbuf.pos;
-           if endpos-startpos<>expected_size then
+            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+            whitespacetrimcount:=tokenreadword;
+            whitespacetrimauto:=boolean(tokenreadbyte);
+            endpos:=replaytokenbuf.pos;
+            if endpos-startpos<>expected_size then
              Comment(V_Error,'Wrong size of Settings read-in');
          end;
      end;
@@ -3189,6 +3204,9 @@ type
             if ControllerSupport then
               tokenwriteenum(controllertype,sizeof(tcontrollertype));
 {$POP}
+           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
+           tokenwriteword(whitespacetrimcount);
+           tokenwritebyte(byte(whitespacetrimauto));
            endpos:=recordtokenbuf.pos;
            size:=endpos-startpos;
            recordtokenbuf.seek(sizepos);
@@ -4243,25 +4261,40 @@ type
     function tscannerfile.readquotedstring:string;
       var
         i : longint;
-        msgwritten : boolean;
+        msgwritten,in_multiline_string : boolean;
       begin
         i:=0;
         msgwritten:=false;
-        if (c='''') then
+        if (c in ['''','`']) then
           begin
+            in_multiline_string:=(c='`');
+            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+              begin
+                result[0]:=chr(0);
+                Illegal_Char(c);
+              end;
             repeat
               readchar;
               case c of
                 #26 :
                   end_of_file;
                 #10,#13 :
-                  Message(scan_f_string_exceeds_line);
+                  if not in_multiline_string then
+                    Message(scan_f_string_exceeds_line);
                 '''' :
-                  begin
-                    readchar;
-                    if c<>'''' then
-                     break;
-                  end;
+                  if not in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'''' then
+                       break;
+                    end;
+                '`' :
+                  if in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'`' then
+                       break;
+                    end;
               end;
               if i<255 then
                 begin
@@ -4389,7 +4422,7 @@ type
     procedure tscannerfile.skipuntildirective;
       var
         found : longint;
-        next_char_loaded : boolean;
+        next_char_loaded,in_multiline_string : boolean;
       begin
          found:=0;
          next_char_loaded:=false;
@@ -4447,28 +4480,42 @@ type
                  if found=1 then
                   found:=2;
                end;
-             '''' :
+             '''','`' :
                if (current_commentstyle=comment_none) then
-                begin
-                  repeat
-                    readchar;
-                    case c of
-                      #26 :
-                        end_of_file;
-                      #10,#13 :
-                        break;
-                      '''' :
-                        begin
-                          readchar;
-                          if c<>'''' then
-                           begin
-                             next_char_loaded:=true;
+                 begin
+                   in_multiline_string:=(c='`');
+                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
+                     repeat
+                       readchar;
+                       case c of
+                         #26 :
+                           end_of_file;
+                         #10,#13 :
+                           if not in_multiline_string then
                              break;
-                           end;
-                        end;
-                    end;
-                  until false;
-                end;
+                         '''' :
+                           if not in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'''' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                         '`' :
+                           if in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'`' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                       end;
+                     until false;
+                 end;
              '(' :
                begin
                  if (current_commentstyle=comment_none) then
@@ -4653,6 +4700,46 @@ type
                                Token Scanner
 ****************************************************************************}
 
+    procedure tscannerfile.buildplatformnewlineascii(var len: longint);
+      begin
+        if target_info.newline=#13 then
+          cstringpattern[len]:=#13
+        else if target_info.newline=#13#10 then
+          begin
+            cstringpattern[len]:=#13;
+            inc(len);
+            cstringpattern[len]:=#10;
+          end
+        else if target_info.newline=#10 then
+          cstringpattern[len]:=#10;
+      end;
+
+    procedure tscannerfile.buildplatformnewlineutf8;
+      begin
+        if target_info.newline=#13 then
+          concatwidestringchar(patternw,ord(#13))
+        else if target_info.newline=#13#10 then
+          begin
+            concatwidestringchar(patternw,ord(#13));
+            concatwidestringchar(patternw,ord(#10));
+          end
+        else if target_info.newline=#10 then
+          concatwidestringchar(patternw,ord(#10));
+      end;
+
+    procedure tscannerfile.buildplatformnewlineunicode;
+      begin
+        if target_info.newline=#13 then
+          concatwidestringchar(patternw,asciichar2unicode(#13))
+        else if target_info.newline=#13#10 then
+          begin
+            concatwidestringchar(patternw,asciichar2unicode(#13));
+            concatwidestringchar(patternw,asciichar2unicode(#10));
+          end
+        else if target_info.newline=#10 then
+          concatwidestringchar(patternw,asciichar2unicode(#10));
+      end;
+
     procedure tscannerfile.readtoken(allowrecordtoken:boolean);
       var
         code    : integer;
@@ -4664,9 +4751,15 @@ type
         mac     : tmacro;
         asciinr : string[33];
         iswidestring : boolean;
+        in_multiline_string,had_newline,first_multiline : boolean;
+        trimcount,multiline_start_column : word;
+        last_c : char;
       label
-         exit_label;
+        quote_label,exit_label;
       begin
+        had_newline:=false;
+        first_multiline:=false;
+        last_c:=#0;
         flushpendingswitchesstate;
 
         { record tokens? }
@@ -5098,8 +5191,13 @@ type
                  goto exit_label;
                end;
 
-             '''','#','^' :
+             '''','#','^','`' :
                begin
+                 in_multiline_string:=(c='`');
+                 if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+                   Illegal_Char(c)
+                 else
+                   multiline_start_column:=current_filepos.column;
                  len:=0;
                  cstringpattern:='';
                  iswidestring:=false;
@@ -5206,26 +5304,55 @@ type
                            begin
                              if len>=length(cstringpattern) then
                                setlength(cstringpattern,length(cstringpattern)+256);
-                              inc(len);
-                              cstringpattern[len]:=chr(m);
+                             inc(len);
+                             cstringpattern[len]:=chr(m);
                            end;
                        end;
-                     '''' :
+                     '''','`' :
                        begin
+                         in_multiline_string:=(c='`');
+                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                          repeat
                            readchar;
-                           case c of
-                             #26 :
-                               end_of_file;
-                             #10,#13 :
-                               Message(scan_f_string_exceeds_line);
-                             '''' :
-                               begin
-                                 readchar;
-                                 if c<>'''' then
-                                  break;
-                               end;
-                           end;
+                           quote_label:
+                             case c of
+                               #26 :
+                                 end_of_file;
+                               #32,#9,#11 :
+                                 if (had_newline or first_multiline) and (current_settings.whitespacetrimauto or (current_settings.whitespacetrimcount > 0)) then
+                                   begin
+                                     if current_settings.whitespacetrimauto then
+                                       trimcount:=multiline_start_column
+                                     else
+                                       trimcount:=current_settings.whitespacetrimcount;
+                                     while (c in [#32,#9,#11]) and (trimcount > 0) do
+                                       begin
+                                         readchar;
+                                         dec(trimcount);
+                                       end;
+                                     had_newline:=false;
+                                     first_multiline:=false;
+                                     goto quote_label;
+                                   end;
+                               #10,#13 :
+                                 if not in_multiline_string then
+                                   Message(scan_f_string_exceeds_line);
+                               '''' :
+                                 if not in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'''' then
+                                      break;
+                                   end;
+                               '`' :
+                                 if in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'`' then
+                                      break;
+                                   end;
+                             end;
+                           first_multiline:=false;
                            { interpret as utf-8 string? }
                            if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                              begin
@@ -5300,18 +5427,76 @@ type
                              end
                            else if iswidestring then
                              begin
-                               if current_settings.sourcecodepage=CP_UTF8 then
-                                 concatwidestringchar(patternw,ord(c))
-                               else
-                                 concatwidestringchar(patternw,asciichar2unicode(c))
+                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     begin
+                                       case current_settings.lineendingtype of
+                                         le_cr : concatwidestringchar(patternw,ord(#13));
+                                         le_crlf :
+                                           begin
+                                             concatwidestringchar(patternw,ord(#13));
+                                             concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_lf : concatwidestringchar(patternw,ord(#10));
+                                         le_platform : buildplatformnewlineutf8;
+                                         le_raw : concatwidestringchar(patternw,ord(c));
+                                       end;
+                                     end
+                                   else
+                                     case current_settings.lineendingtype of
+                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
+                                       le_crlf :
+                                         begin
+                                           concatwidestringchar(patternw,asciichar2unicode(#13));
+                                           concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
+                                       le_platform : buildplatformnewlineunicode;
+                                       le_raw : concatwidestringchar(patternw,asciichar2unicode(c));
+                                     end;
+                                   had_newline:=true;
+                                   inc(line_no);
+                                 end
+                               else if not (in_multiline_string and (c in [#10,#13])) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     concatwidestringchar(patternw,ord(c))
+                                   else
+                                     concatwidestringchar(patternw,asciichar2unicode(c));
+                                 end;
                              end
                            else
                              begin
-                               if len>=length(cstringpattern) then
-                                 setlength(cstringpattern,length(cstringpattern)+256);
-                                inc(len);
-                                cstringpattern[len]:=c;
+                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    case current_settings.lineendingtype of
+                                      le_cr : cstringpattern[len]:=#13;
+                                      le_crlf :
+                                        begin
+                                          cstringpattern[len]:=#13;
+                                          inc(len);
+                                          cstringpattern[len]:=#10;
+                                        end;
+                                      le_lf : cstringpattern[len]:=#10;
+                                      le_platform : buildplatformnewlineascii(len);
+                                      le_raw : cstringpattern[len]:=c;
+                                    end;
+                                    had_newline:=true;
+                                    inc(line_no);
+                                  end
+                                else if not (in_multiline_string and (c in [#10,#13])) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    cstringpattern[len]:=c;
+                                  end;
                              end;
+                         last_c:=c;
                          until false;
                        end;
                      '^' :
@@ -5338,6 +5523,7 @@ type
                      else
                       break;
                    end;
+                 last_c:=c;
                  until false;
                  { strings with length 1 become const chars }
                  if iswidestring then
@@ -5480,12 +5666,13 @@ exit_label:
                current_scanner.preproc_pattern:=pattern;
                readpreproc:=optoken;
              end;
-           '''' :
-             begin
-               readquotedstring;
-               current_scanner.preproc_pattern:=cstringpattern;
-               readpreproc:=_CSTRING;
-             end;
+           '''','`' :
+             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
+               begin
+                 readquotedstring;
+                 current_scanner.preproc_pattern:=cstringpattern;
+                 readpreproc:=_CSTRING;
+               end;
            '0'..'9' :
              begin
                readnumber;
diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
index 7009bd8..14f600f 100644
--- compiler/utils/ppuutils/ppudump.pp
+++ compiler/utils/ppuutils/ppudump.pp
@@ -2444,6 +2444,9 @@ const
             else
              ControllerType:=ct_none;
 {$POP}
+           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+           whitespacetrimcount:=gettokenbufword;
+           whitespacetrimauto:=boolean(gettokenbufbyte);
            endpos:=tbi;
            if endpos-startpos<>expected_size then
              Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);
multi_line_strings_tests_rev4.patch (25,952 bytes)
diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
new file mode 100644
index 0000000..007874c
--- /dev/null
+++ tests/test/tmultilinestring1.pp
@@ -0,0 +1,14 @@
+program tmultilinestring1;
+
+{$modeswitch MultiLineStrings}
+
+const MyString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyString);
+end.
diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
new file mode 100644
index 0000000..59aeb2c
--- /dev/null
+++ tests/test/tmultilinestring10.pp
@@ -0,0 +1,9 @@
+program tmultilinestring10;
+
+{ Test the use of multiline strings from units in programs }
+
+uses umultilinestring1;
+
+begin
+  Write(Long);
+end.
diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
new file mode 100644
index 0000000..206e61c
--- /dev/null
+++ tests/test/tmultilinestring11.pp
@@ -0,0 +1,19 @@
+{ %FAIL }
+
+program tmultilinestring11;
+
+{$modeswitch MultiLineStrings}
+{$H-}
+
+{ Some Mark Twain... }
+
+const WayTooLong =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+begin
+  Write(WayTooLong);
+end.
diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
new file mode 100644
index 0000000..ca6a800
--- /dev/null
+++ tests/test/tmultilinestring12.pp
@@ -0,0 +1,18 @@
+program tmultilinestring12;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 4}
+
+procedure TakesAString(const S: String);
+begin
+  Write(S);
+end;
+
+begin
+  TakesAString(`
+    This
+    works
+    just
+    fine!
+  `);
+end.
diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
new file mode 100644
index 0000000..3652c76
--- /dev/null
+++ tests/test/tmultilinestring13.pp
@@ -0,0 +1,18 @@
+program tmultilinestring13;
+
+{$modeswitch MultiLineStrings}
+
+const A =
+`
+``a``
+`;
+
+const B =
+`
+'a'
+`;
+
+begin
+  Write(A);
+  Write(B);
+end.
diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
new file mode 100644
index 0000000..e181ff7
--- /dev/null
+++ tests/test/tmultilinestring14.pp
@@ -0,0 +1,40 @@
+program tmultilinestring14;
+
+{$modeswitch MultiLineStrings}
+
+{$MultiLineStringTrimLeft 2}
+
+const A = `
+  A
+  B
+  C
+  D
+`;
+
+{$MultiLineStringTrimLeft 4}
+
+const B = `
+    A
+    B
+    C
+    D
+`;
+
+begin
+  Write(A);
+  Write(B);
+
+  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
+    as it stops immediately when it is no longer actually *in* whitespace regardless. }
+
+  {$MultiLineStringTrimLeft 10000}
+
+  { Non-leading whitespace is preserved properly, of course. }
+
+  Write(`
+        sdfs
+        sd fs fs
+        sd  fsfs  sdfd sfdf
+        sdfs fsd
+  `);
+end.
diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
new file mode 100644
index 0000000..fa3426b
--- /dev/null
+++ tests/test/tmultilinestring15.pp
@@ -0,0 +1,37 @@
+program tmultilinestring15;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+const X = `
+  Hello
+  every
+  body!
+`;
+
+const Y = `
+    Goodbye
+    every
+    body!
+    
+`;
+
+{ Test some wacky concatentation }
+
+begin
+  Write(X + Y);
+  Write(Concat(X, Y));
+  Write(
+    '  Single line string ' +
+    `
+    and
+    ` +
+    `
+    Multi
+    line
+    string
+    ` +
+    Y +
+    X
+  );
+end.
diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
new file mode 100644
index 0000000..9e1d7b5
--- /dev/null
+++ tests/test/tmultilinestring16.pp
@@ -0,0 +1,30 @@
+program tmultilinestring16;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+procedure TakesAnArray(constref A: array of String);
+var S: String;
+begin
+  for S in A do Write(S);
+end;
+
+begin
+  TakesAnArray([
+    ` Multi
+      line
+      one!`,
+    `
+      Multi
+      line
+      two!`,
+    `
+      Multi
+      line
+      three!
+    `,
+    'Single line one!' + sLineBreak +
+    'Single line two!' + sLineBreak +
+    'Single line three!'
+  ]);
+end.
diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
new file mode 100644
index 0000000..116d6f8
--- /dev/null
+++ tests/test/tmultilinestring17.pp
@@ -0,0 +1,39 @@
+program tmultilinestring17;
+
+{$mode ObjFPC}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+type
+  TMessage = record
+    Msg: String;
+  end;
+
+  TMyClass = class
+  public
+    procedure MyMessage(var Msg: TMessage); message `
+      Multi
+      Line
+      Message!
+    `;
+  end;
+
+  procedure TMyClass.MyMessage(var Msg: TMessage);
+  begin
+    WriteLn('Ok!');
+  end;
+
+  const M: TMessage = (
+    Msg: `
+      Multi
+      Line
+      Message!
+    `
+  );
+
+begin
+  with TMyClass.Create() do begin
+    DispatchStr(M);
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
new file mode 100644
index 0000000..d98f7b9
--- /dev/null
+++ tests/test/tmultilinestring18.pp
@@ -0,0 +1,19 @@
+program tmultilinestring18;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+{$Warnings On}
+
+procedure IsDeprecated; deprecated
+`
+  Multi
+  line
+  deprecation
+  message!
+`;
+begin
+end;
+
+begin
+  IsDeprecated;
+end.
diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
new file mode 100644
index 0000000..a7e7201
--- /dev/null
+++ tests/test/tmultilinestring19.pp
@@ -0,0 +1,15 @@
+program tmultilinestring19;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+{ This is extremely unlikely, but it needs to work properly... }
+
+procedure Bloop; external name `
+  Actually
+  Called
+  Bloop
+`;
+
+begin
+end.
diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
new file mode 100644
index 0000000..3da55bc
--- /dev/null
+++ tests/test/tmultilinestring2.pp
@@ -0,0 +1,22 @@
+program tmultilinestring2;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
new file mode 100644
index 0000000..6491720
--- /dev/null
+++ tests/test/tmultilinestring20.pp
@@ -0,0 +1,10 @@
+program tmultilinestring20;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+uses umultilinestring2;
+
+begin
+  DoIt;
+end.
diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
new file mode 100644
index 0000000..a3ee3f6
--- /dev/null
+++ tests/test/tmultilinestring21.pp
@@ -0,0 +1,16 @@
+{ %FAIL }
+
+program tmultilinestring21;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2000000}
+
+const S = `
+  A
+  B
+  C
+`;
+
+begin
+  WriteLn(S);
+end.
diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
new file mode 100644
index 0000000..0b21b31
--- /dev/null
+++ tests/test/tmultilinestring22.pp
@@ -0,0 +1,10 @@
+program tmultilinestring22;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const Test = ` ThisSpace>>>>     <<<<disappeared`;
+
+begin
+  Write(Test);
+end.
diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
new file mode 100644
index 0000000..fee9e04
--- /dev/null
+++ tests/test/tmultilinestring23.pp
@@ -0,0 +1,51 @@
+program tmultilinestring23;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+{$modeswitch PrefixedAttributes}
+
+uses RTTI;
+
+type
+  TMultiLineAttribute = class(TCustomAttribute)
+  private
+    FString: String;
+  public
+    constructor Create(const S: String);
+    property StringValue: String read FString;
+  end;
+
+  constructor TMultiLineAttribute.Create(const S: String);
+  begin
+    FString := S;
+  end;
+
+type
+  [TMultiLineAttribute(
+    `This is my
+     pretty cool
+     multi-line string
+     attribute!`
+  )]
+  [TMultiLineAttribute(
+    `This is my
+     even cooler
+     multi-line string
+     attribute!`
+  )]
+  TMyClass = class
+  end;
+
+var
+  A: TMultiLineAttribute;
+
+begin
+  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
+    for TCustomAttribute(A) in GetAttributes() do begin
+      WriteLn(A.StringValue);
+      A.Free();
+    end;
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
new file mode 100644
index 0000000..7a282f9
--- /dev/null
+++ tests/test/tmultilinestring24.pp
@@ -0,0 +1,62 @@
+program tmultilinestring24;
+
+{ engkin's bug example }
+
+{$mode objfpc}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 15}
+{$MultiLineStringLineEnding Platform}
+
+var
+{$MultiLineStringLineEnding CR}
+  a: array[0..3] of string = (
+``
+,
+`
+`
+,
+`
+
+`
+,
+`
+
+
+`);
+
+  {$MultiLineStringLineEnding CRLF}
+b: array[0..3] of string = (
+`1`
+,
+`1
+2`
+,
+`1
+2
+3`
+,
+`1
+2
+3
+4`);
+
+procedure Test(constref StrArray:array of string);
+var
+  s,sHex: string;
+  c: char;
+begin
+  for s in StrArray do
+  begin
+    WriteLn('Length: ',Length(s));
+    sHex := '';
+    for c in s do
+      sHex := sHex+'$'+hexStr(ord(c),2);
+    WriteLn(sHex);
+  end;
+  WriteLn('---------------');
+end;
+
+begin
+  Test(a);
+  Test(b);
+end.
diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
new file mode 100644
index 0000000..2d44f8b
--- /dev/null
+++ tests/test/tmultilinestring25.pp
@@ -0,0 +1,26 @@
+program tmultilinestring25;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft Auto}
+
+const
+  Str1 = `SELECT o.*, C.Company
+          from Orders O
+          join Customer C
+            on o.CustNo=C.ID
+          where
+            O.saledate=DATE '2001.03.20'`;
+
+const
+  Str2 =
+    `SELECT o.*, C.Company
+     from Orders O
+     join Customer C
+       on o.CustNo=C.ID
+     where
+       O.saledate=DATE '2001.03.20'`;
+
+begin
+  WriteLn(Str1);
+  WriteLn(Str2);
+end.
diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
new file mode 100644
index 0000000..6b1c57f
--- /dev/null
+++ tests/test/tmultilinestring26.pp
@@ -0,0 +1,24 @@
+program tmultilinestring26;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const middleSpaceBug: array[0..5] of string = (
+#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
+,
+#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
+,
+#$41`      -      `#$42  //<--- one space left instead of six spaces
+,
+#$41`      -      `#$42`  --  `  //<---- the same bug twice
+,
+'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
+,
+^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
+);
+
+var S: String;
+
+begin
+  for S in middleSpaceBug do WriteLn(S);
+end.
diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
new file mode 100644
index 0000000..6625906
--- /dev/null
+++ tests/test/tmultilinestring3.pp
@@ -0,0 +1,22 @@
+program tmultilinestring3;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
new file mode 100644
index 0000000..6489542
--- /dev/null
+++ tests/test/tmultilinestring4.pp
@@ -0,0 +1,22 @@
+program tmultilinestring4;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
new file mode 100644
index 0000000..a868d93
--- /dev/null
+++ tests/test/tmultilinestring5.pp
@@ -0,0 +1,22 @@
+program tmultilinestring5;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
new file mode 100644
index 0000000..1ca0c5c
--- /dev/null
+++ tests/test/tmultilinestring6.pp
@@ -0,0 +1,65 @@
+program tmultilinestring6;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CR}
+
+const A =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding CRLF}
+
+const B =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding LF}
+
+const C =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding PLATFORM}
+
+const D =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding RAW}
+
+const E =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+begin
+  Write(A);
+  Write(B);
+  Write(C);
+  Write(D);
+  Write(E);
+end.
diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
new file mode 100644
index 0000000..4946ba1
--- /dev/null
+++ tests/test/tmultilinestring7.pp
@@ -0,0 +1,17 @@
+{ %FAIL }
+
+program tmultilinestring7;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding DOESNOTEXIST}
+
+const Blah =
+`
+A
+B
+C
+`;
+
+begin
+  Write(Blah);
+end.
diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
new file mode 100644
index 0000000..00c6cc5
--- /dev/null
+++ tests/test/tmultilinestring8.pp
@@ -0,0 +1,38 @@
+{ %FAIL }
+
+program tmultilinestring8;
+
+{ Ryan's example from the mailing list }
+
+{$modeswitch MultiLineStrings}
+
+const lines: ansistring = `
+  #version 150
+
+  uniform sampler2D textures[8];
+  in vec2 vertexTexCoord;
+  in vec4 vertexColor;
+  in float vertexUVMap;
+  out vec4 fragColor;
+
+  void main()
+  {
+    if (vertexUVMap == 255) {
+      fragColor = vertexColor;
+    } else {
+      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
+      if (vertexColor.a < fragColor.a) {
+        fragColor.a = vertexColor.a;
+      }
+    }
+
+    // TODO: testing
+    fragColor = vec4(1,0,0,1);
+  }
+`;
+
+var
+  s: ansistring = lines;
+begin
+  writeln(b);
+end.
diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
new file mode 100644
index 0000000..033f8fb
--- /dev/null
+++ tests/test/tmultilinestring9.pp
@@ -0,0 +1,13 @@
+{ %FAIL }
+
+program tmultilinestring9;
+
+{ "Forget" to set the modeswitch... }
+
+const NotAllowed = `
+Oh
+no!
+`;
+
+begin
+end.
diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
new file mode 100644
index 0000000..62dafe2
--- /dev/null
+++ tests/test/umultilinestring1.pp
@@ -0,0 +1,21 @@
+unit umultilinestring1;
+
+{$CodePage UTF8}
+{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CRLF}
+
+interface
+
+{ Some Mark Twain... }
+
+const Long =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+implementation
+
+end.
diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
new file mode 100644
index 0000000..ca08f05
--- /dev/null
+++ tests/test/umultilinestring2.pp
@@ -0,0 +1,17 @@
+unit umultilinestring2;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+interface
+
+procedure DoIt;
+
+implementation
+
+procedure DoIt; public name `SingleLineMultiLine`;
+begin
+  Write('Ok!');
+end;
+
+end.

Akira1364

2019-07-17 03:49

reporter   ~0117278

Last edited: 2019-07-17 06:32

View 7 revisions

Ok, I've just finished up some final refactoring on this, and have attached a new pair of patch files:

- the RAW option for MultiLineStringLineEnding has been renamed to SOURCE based on some feedback on the Lazarus forums that this would be more clear (which I agree with.)
- PLATFORM has been made the default MultiLineStringLineEnding option instead of SOURCE, in order to better fit in with FPC's existing behavior in various areas where newlines are relevant.
- more excitingly (to me at least) I've implemented the multi-line-string-specific error message that I've wanted to since first reading some of the mailing list comments regarding concerns about unterminated multi-line strings.

So, to use this test example adapted from something originally posted by Michael:

{ %FAIL }

{ Will show:
  tmultilinestring28.pp(20,1) Fatal: Unterminated multi-line string beginning at line 11, column 7. }

program tmultilinestring28;

{$modeswitch MultiLineStrings}

const
  a = `this will be unterminated
with some
lines in it.

var
  B : String;

begin
  B:=`
again
something
end backticked`;

end.

As it says in the comment, when run, that program will stop compilation with:
"tmultilinestring28.pp(20,1) Fatal: Unterminated multi-line string beginning at line 11, column 7."

(20,1) of course being the very beginning of the next line after the second backtick (which is missing a semicolon), and line 11, column 7 being exactly where the multi-line string started.

I'm confident at this point to call the feature "as done as it possibly can be for an initial implementation", and with the help of some people (well, one person) on the Lazarus forums I think I've ironed out the bugs, so if a core team member feels up to reviewing it at some point, I believe that's all that's left.



multi_line_strings_tests_rev5.patch (26,938 bytes)
diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
new file mode 100644
index 0000000..007874c
--- /dev/null
+++ tests/test/tmultilinestring1.pp
@@ -0,0 +1,14 @@
+program tmultilinestring1;
+
+{$modeswitch MultiLineStrings}
+
+const MyString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyString);
+end.
diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
new file mode 100644
index 0000000..59aeb2c
--- /dev/null
+++ tests/test/tmultilinestring10.pp
@@ -0,0 +1,9 @@
+program tmultilinestring10;
+
+{ Test the use of multiline strings from units in programs }
+
+uses umultilinestring1;
+
+begin
+  Write(Long);
+end.
diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
new file mode 100644
index 0000000..206e61c
--- /dev/null
+++ tests/test/tmultilinestring11.pp
@@ -0,0 +1,19 @@
+{ %FAIL }
+
+program tmultilinestring11;
+
+{$modeswitch MultiLineStrings}
+{$H-}
+
+{ Some Mark Twain... }
+
+const WayTooLong =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+begin
+  Write(WayTooLong);
+end.
diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
new file mode 100644
index 0000000..ca6a800
--- /dev/null
+++ tests/test/tmultilinestring12.pp
@@ -0,0 +1,18 @@
+program tmultilinestring12;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 4}
+
+procedure TakesAString(const S: String);
+begin
+  Write(S);
+end;
+
+begin
+  TakesAString(`
+    This
+    works
+    just
+    fine!
+  `);
+end.
diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
new file mode 100644
index 0000000..3652c76
--- /dev/null
+++ tests/test/tmultilinestring13.pp
@@ -0,0 +1,18 @@
+program tmultilinestring13;
+
+{$modeswitch MultiLineStrings}
+
+const A =
+`
+``a``
+`;
+
+const B =
+`
+'a'
+`;
+
+begin
+  Write(A);
+  Write(B);
+end.
diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
new file mode 100644
index 0000000..e181ff7
--- /dev/null
+++ tests/test/tmultilinestring14.pp
@@ -0,0 +1,40 @@
+program tmultilinestring14;
+
+{$modeswitch MultiLineStrings}
+
+{$MultiLineStringTrimLeft 2}
+
+const A = `
+  A
+  B
+  C
+  D
+`;
+
+{$MultiLineStringTrimLeft 4}
+
+const B = `
+    A
+    B
+    C
+    D
+`;
+
+begin
+  Write(A);
+  Write(B);
+
+  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
+    as it stops immediately when it is no longer actually *in* whitespace regardless. }
+
+  {$MultiLineStringTrimLeft 10000}
+
+  { Non-leading whitespace is preserved properly, of course. }
+
+  Write(`
+        sdfs
+        sd fs fs
+        sd  fsfs  sdfd sfdf
+        sdfs fsd
+  `);
+end.
diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
new file mode 100644
index 0000000..fa3426b
--- /dev/null
+++ tests/test/tmultilinestring15.pp
@@ -0,0 +1,37 @@
+program tmultilinestring15;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+const X = `
+  Hello
+  every
+  body!
+`;
+
+const Y = `
+    Goodbye
+    every
+    body!
+    
+`;
+
+{ Test some wacky concatentation }
+
+begin
+  Write(X + Y);
+  Write(Concat(X, Y));
+  Write(
+    '  Single line string ' +
+    `
+    and
+    ` +
+    `
+    Multi
+    line
+    string
+    ` +
+    Y +
+    X
+  );
+end.
diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
new file mode 100644
index 0000000..9e1d7b5
--- /dev/null
+++ tests/test/tmultilinestring16.pp
@@ -0,0 +1,30 @@
+program tmultilinestring16;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+procedure TakesAnArray(constref A: array of String);
+var S: String;
+begin
+  for S in A do Write(S);
+end;
+
+begin
+  TakesAnArray([
+    ` Multi
+      line
+      one!`,
+    `
+      Multi
+      line
+      two!`,
+    `
+      Multi
+      line
+      three!
+    `,
+    'Single line one!' + sLineBreak +
+    'Single line two!' + sLineBreak +
+    'Single line three!'
+  ]);
+end.
diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
new file mode 100644
index 0000000..116d6f8
--- /dev/null
+++ tests/test/tmultilinestring17.pp
@@ -0,0 +1,39 @@
+program tmultilinestring17;
+
+{$mode ObjFPC}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+type
+  TMessage = record
+    Msg: String;
+  end;
+
+  TMyClass = class
+  public
+    procedure MyMessage(var Msg: TMessage); message `
+      Multi
+      Line
+      Message!
+    `;
+  end;
+
+  procedure TMyClass.MyMessage(var Msg: TMessage);
+  begin
+    WriteLn('Ok!');
+  end;
+
+  const M: TMessage = (
+    Msg: `
+      Multi
+      Line
+      Message!
+    `
+  );
+
+begin
+  with TMyClass.Create() do begin
+    DispatchStr(M);
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
new file mode 100644
index 0000000..d98f7b9
--- /dev/null
+++ tests/test/tmultilinestring18.pp
@@ -0,0 +1,19 @@
+program tmultilinestring18;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+{$Warnings On}
+
+procedure IsDeprecated; deprecated
+`
+  Multi
+  line
+  deprecation
+  message!
+`;
+begin
+end;
+
+begin
+  IsDeprecated;
+end.
diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
new file mode 100644
index 0000000..a7e7201
--- /dev/null
+++ tests/test/tmultilinestring19.pp
@@ -0,0 +1,15 @@
+program tmultilinestring19;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+{ This is extremely unlikely, but it needs to work properly... }
+
+procedure Bloop; external name `
+  Actually
+  Called
+  Bloop
+`;
+
+begin
+end.
diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
new file mode 100644
index 0000000..3da55bc
--- /dev/null
+++ tests/test/tmultilinestring2.pp
@@ -0,0 +1,22 @@
+program tmultilinestring2;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
new file mode 100644
index 0000000..6491720
--- /dev/null
+++ tests/test/tmultilinestring20.pp
@@ -0,0 +1,10 @@
+program tmultilinestring20;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+uses umultilinestring2;
+
+begin
+  DoIt;
+end.
diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
new file mode 100644
index 0000000..a3ee3f6
--- /dev/null
+++ tests/test/tmultilinestring21.pp
@@ -0,0 +1,16 @@
+{ %FAIL }
+
+program tmultilinestring21;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2000000}
+
+const S = `
+  A
+  B
+  C
+`;
+
+begin
+  WriteLn(S);
+end.
diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
new file mode 100644
index 0000000..0b21b31
--- /dev/null
+++ tests/test/tmultilinestring22.pp
@@ -0,0 +1,10 @@
+program tmultilinestring22;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const Test = ` ThisSpace>>>>     <<<<disappeared`;
+
+begin
+  Write(Test);
+end.
diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
new file mode 100644
index 0000000..fee9e04
--- /dev/null
+++ tests/test/tmultilinestring23.pp
@@ -0,0 +1,51 @@
+program tmultilinestring23;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+{$modeswitch PrefixedAttributes}
+
+uses RTTI;
+
+type
+  TMultiLineAttribute = class(TCustomAttribute)
+  private
+    FString: String;
+  public
+    constructor Create(const S: String);
+    property StringValue: String read FString;
+  end;
+
+  constructor TMultiLineAttribute.Create(const S: String);
+  begin
+    FString := S;
+  end;
+
+type
+  [TMultiLineAttribute(
+    `This is my
+     pretty cool
+     multi-line string
+     attribute!`
+  )]
+  [TMultiLineAttribute(
+    `This is my
+     even cooler
+     multi-line string
+     attribute!`
+  )]
+  TMyClass = class
+  end;
+
+var
+  A: TMultiLineAttribute;
+
+begin
+  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
+    for TCustomAttribute(A) in GetAttributes() do begin
+      WriteLn(A.StringValue);
+      A.Free();
+    end;
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
new file mode 100644
index 0000000..7a282f9
--- /dev/null
+++ tests/test/tmultilinestring24.pp
@@ -0,0 +1,62 @@
+program tmultilinestring24;
+
+{ engkin's bug example }
+
+{$mode objfpc}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 15}
+{$MultiLineStringLineEnding Platform}
+
+var
+{$MultiLineStringLineEnding CR}
+  a: array[0..3] of string = (
+``
+,
+`
+`
+,
+`
+
+`
+,
+`
+
+
+`);
+
+  {$MultiLineStringLineEnding CRLF}
+b: array[0..3] of string = (
+`1`
+,
+`1
+2`
+,
+`1
+2
+3`
+,
+`1
+2
+3
+4`);
+
+procedure Test(constref StrArray:array of string);
+var
+  s,sHex: string;
+  c: char;
+begin
+  for s in StrArray do
+  begin
+    WriteLn('Length: ',Length(s));
+    sHex := '';
+    for c in s do
+      sHex := sHex+'$'+hexStr(ord(c),2);
+    WriteLn(sHex);
+  end;
+  WriteLn('---------------');
+end;
+
+begin
+  Test(a);
+  Test(b);
+end.
diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
new file mode 100644
index 0000000..2d44f8b
--- /dev/null
+++ tests/test/tmultilinestring25.pp
@@ -0,0 +1,26 @@
+program tmultilinestring25;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft Auto}
+
+const
+  Str1 = `SELECT o.*, C.Company
+          from Orders O
+          join Customer C
+            on o.CustNo=C.ID
+          where
+            O.saledate=DATE '2001.03.20'`;
+
+const
+  Str2 =
+    `SELECT o.*, C.Company
+     from Orders O
+     join Customer C
+       on o.CustNo=C.ID
+     where
+       O.saledate=DATE '2001.03.20'`;
+
+begin
+  WriteLn(Str1);
+  WriteLn(Str2);
+end.
diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
new file mode 100644
index 0000000..6b1c57f
--- /dev/null
+++ tests/test/tmultilinestring26.pp
@@ -0,0 +1,24 @@
+program tmultilinestring26;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const middleSpaceBug: array[0..5] of string = (
+#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
+,
+#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
+,
+#$41`      -      `#$42  //<--- one space left instead of six spaces
+,
+#$41`      -      `#$42`  --  `  //<---- the same bug twice
+,
+'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
+,
+^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
+);
+
+var S: String;
+
+begin
+  for S in middleSpaceBug do WriteLn(S);
+end.
diff --git tests/test/tmultilinestring27.pp tests/test/tmultilinestring27.pp
new file mode 100644
index 0000000..9245120
--- /dev/null
+++ tests/test/tmultilinestring27.pp
@@ -0,0 +1,16 @@
+program tmultilinestring27;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft Auto}
+
+resourcestring S =
+    `This
+       is
+     a
+       multi-line
+     resource
+       string`;
+
+begin
+  Write(S);
+end.
\ No newline at end of file
diff --git tests/test/tmultilinestring28.pas tests/test/tmultilinestring28.pas
new file mode 100644
index 0000000..5509bb8
--- /dev/null
+++ tests/test/tmultilinestring28.pas
@@ -0,0 +1,24 @@
+{ %FAIL }
+
+{ Will show: 
+  tmultilinestring28.pas(17,1) Fatal: Unterminated multi-line string beginning at line 8, column 7. }
+
+program tmultilinestring28;
+
+{$modeswitch MultiLineStrings}
+
+const
+  a = `this will be unterminated
+with some
+lines in it.
+
+var
+  B : String;
+
+begin
+  B:=`
+again
+something
+end backticked`;
+
+end.
diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
new file mode 100644
index 0000000..6625906
--- /dev/null
+++ tests/test/tmultilinestring3.pp
@@ -0,0 +1,22 @@
+program tmultilinestring3;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
new file mode 100644
index 0000000..6489542
--- /dev/null
+++ tests/test/tmultilinestring4.pp
@@ -0,0 +1,22 @@
+program tmultilinestring4;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
new file mode 100644
index 0000000..a868d93
--- /dev/null
+++ tests/test/tmultilinestring5.pp
@@ -0,0 +1,22 @@
+program tmultilinestring5;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
new file mode 100644
index 0000000..a72af31
--- /dev/null
+++ tests/test/tmultilinestring6.pp
@@ -0,0 +1,65 @@
+program tmultilinestring6;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CR}
+
+const A =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding CRLF}
+
+const B =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding LF}
+
+const C =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding PLATFORM}
+
+const D =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding SOURCE}
+
+const E =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+begin
+  Write(A);
+  Write(B);
+  Write(C);
+  Write(D);
+  Write(E);
+end.
diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
new file mode 100644
index 0000000..4946ba1
--- /dev/null
+++ tests/test/tmultilinestring7.pp
@@ -0,0 +1,17 @@
+{ %FAIL }
+
+program tmultilinestring7;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding DOESNOTEXIST}
+
+const Blah =
+`
+A
+B
+C
+`;
+
+begin
+  Write(Blah);
+end.
diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
new file mode 100644
index 0000000..00c6cc5
--- /dev/null
+++ tests/test/tmultilinestring8.pp
@@ -0,0 +1,38 @@
+{ %FAIL }
+
+program tmultilinestring8;
+
+{ Ryan's example from the mailing list }
+
+{$modeswitch MultiLineStrings}
+
+const lines: ansistring = `
+  #version 150
+
+  uniform sampler2D textures[8];
+  in vec2 vertexTexCoord;
+  in vec4 vertexColor;
+  in float vertexUVMap;
+  out vec4 fragColor;
+
+  void main()
+  {
+    if (vertexUVMap == 255) {
+      fragColor = vertexColor;
+    } else {
+      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
+      if (vertexColor.a < fragColor.a) {
+        fragColor.a = vertexColor.a;
+      }
+    }
+
+    // TODO: testing
+    fragColor = vec4(1,0,0,1);
+  }
+`;
+
+var
+  s: ansistring = lines;
+begin
+  writeln(b);
+end.
diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
new file mode 100644
index 0000000..033f8fb
--- /dev/null
+++ tests/test/tmultilinestring9.pp
@@ -0,0 +1,13 @@
+{ %FAIL }
+
+program tmultilinestring9;
+
+{ "Forget" to set the modeswitch... }
+
+const NotAllowed = `
+Oh
+no!
+`;
+
+begin
+end.
diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
new file mode 100644
index 0000000..62dafe2
--- /dev/null
+++ tests/test/umultilinestring1.pp
@@ -0,0 +1,21 @@
+unit umultilinestring1;
+
+{$CodePage UTF8}
+{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CRLF}
+
+interface
+
+{ Some Mark Twain... }
+
+const Long =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+implementation
+
+end.
diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
new file mode 100644
index 0000000..ca08f05
--- /dev/null
+++ tests/test/umultilinestring2.pp
@@ -0,0 +1,17 @@
+unit umultilinestring2;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+interface
+
+procedure DoIt;
+
+implementation
+
+procedure DoIt; public name `SingleLineMultiLine`;
+begin
+  Write('Ok!');
+end;
+
+end.
multi_line_strings_main_rev4.patch (31,693 bytes)
diff --git compiler/globals.pas compiler/globals.pas
index 1f368e6..a05b7bb 100644
--- compiler/globals.pas
+++ compiler/globals.pas
@@ -183,6 +183,12 @@ interface
 
          { WARNING: this pointer cannot be written as such in record token }
          pmessage : pmessagestaterecord;
+         
+         lineendingtype : tlineendingtype;
+
+         whitespacetrimcount : word;
+         
+         whitespacetrimauto : boolean;
        end;
 
     const
@@ -574,6 +580,9 @@ interface
 {$endif defined(LLVM) and not defined(GENERIC_CPU)}
         controllertype : ct_none;
         pmessage : nil;
+        lineendingtype : le_platform;
+        whitespacetrimcount : 0;
+        whitespacetrimauto : false
       );
 
     var
@@ -1455,7 +1464,7 @@ implementation
        if localexepath='' then
         begin
           hs1 := ExtractFileName(exeName);
-	  hs1 := ChangeFileExt(hs1,source_info.exeext);
+          hs1 := ChangeFileExt(hs1,source_info.exeext);
 {$ifdef macos}
           FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
 {$else macos}
diff --git compiler/globtype.pas compiler/globtype.pas
index b6c142f..90d5516 100644
--- compiler/globtype.pas
+++ compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -605,7 +606,18 @@ interface
          pocall_vectorcall
        );
        tproccalloptions = set of tproccalloption;
-
+       
+       tlineendingtype = ({Carriage return, aka #13}
+                          le_cr,
+                          {Carriage return + line feed, aka #13#10}
+                          le_crlf,
+                          {Line feed, aka #10}
+                          le_lf,
+                          {Use the platform default}
+                          le_platform,
+                          {Use whatever is in the file}
+                          le_source);
+                          
      const
        proccalloptionStr : array[tproccalloption] of string[16]=('',
            'CDecl',
@@ -683,7 +695,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'MULTILINESTRINGS'
          );
 
 
diff --git compiler/msg/errore.msg compiler/msg/errore.msg
index d6a0bdf..3307f3c 100644
--- compiler/msg/errore.msg
+++ compiler/msg/errore.msg
@@ -432,6 +432,14 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
 scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
 % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
 scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
+scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or SOURCE.
+% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or SOURCE.
+scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
+% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
+scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
+% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
+scan_f_unterminated_multiline_string=02109_F_Unterminated multi-line string beginning at line $1, column $2.
+% The file contains an "unbalanced" number of multi-line string denoting backtick characters.
 % \end{description}
 #
 # Parser
diff --git compiler/pbase.pas compiler/pbase.pas
index dffd92d..ffef25c 100644
--- compiler/pbase.pas
+++ compiler/pbase.pas
@@ -144,10 +144,16 @@ implementation
     procedure consume(i : ttoken);
       begin
         if (token<>i) and (idtoken<>i) then
-          if token=_id then
-            Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
-          else
-            Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str)
+          begin
+            if (current_scanner.multiline_start_column>0) and (current_scanner.multiline_start_line>0) then
+              Message2(scan_f_unterminated_multiline_string,
+                       tostr(current_scanner.multiline_start_line),
+                       tostr(current_scanner.multiline_start_column))
+            else if token=_id then
+              Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
+            else
+              Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str);
+          end
         else
           begin
             if token=_END then
@@ -178,7 +184,12 @@ implementation
             if token=_EOF then
              begin
                Consume(atoken);
-               Message(scan_f_end_of_file);
+               if current_scanner.in_multiline_string then
+                 Message2(scan_f_unterminated_multiline_string,
+                          tostr(current_scanner.multiline_start_line),
+                          tostr(current_scanner.multiline_start_column))
+               else
+                 Message(scan_f_end_of_file);
                exit;
              end;
           end;
diff --git compiler/pstatmnt.pas compiler/pstatmnt.pas
index 5f6987d..38926c8 100644
--- compiler/pstatmnt.pas
+++ compiler/pstatmnt.pas
@@ -1226,7 +1226,12 @@ implementation
                consume(_PLUS);
              end;
            _EOF :
-             Message(scan_f_end_of_file);
+             if current_scanner.in_multiline_string then
+               Message2(scan_f_unterminated_multiline_string,
+                        tostr(current_scanner.multiline_start_line),
+                        tostr(current_scanner.multiline_start_column))
+             else
+               Message(scan_f_end_of_file);
          else
            begin
              { don't typecheck yet, because that will also simplify, which may
diff --git compiler/scandir.pas compiler/scandir.pas
index 9dd457b..74703d7 100644
--- compiler/scandir.pas
+++ compiler/scandir.pas
@@ -1003,6 +1003,67 @@ unit scandir;
           end;
       end;
 
+    procedure dir_multilinestringlineending;
+      var
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
+        current_scanner.skipspace;
+        s:=current_scanner.readid;
+        if (s='CR') then
+          current_settings.lineendingtype:=le_cr
+        else if (s='CRLF') then
+          current_settings.lineendingtype:=le_crlf
+        else if (s='LF') then
+          current_settings.lineendingtype:=le_lf
+        else if (s='PLATFORM') then
+          current_settings.lineendingtype:=le_platform
+        else if (s='SOURCE') then
+          current_settings.lineendingtype:=le_source
+        else
+          Message(scan_e_unknown_lineending_type);
+      end;
+
+    procedure dir_multilinestringtrimleft;
+      var
+        count : longint;
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
+        current_scanner.skipspace;
+        if (c in ['1'..'9']) then
+          begin
+            count:=current_scanner.readval;
+            if (count<0) or (count>65535) then
+              Message(scan_e_trimcount_out_of_range)
+            else
+              begin
+                current_settings.whitespacetrimcount:=count;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end
+        else
+          begin
+            s:=current_scanner.readid;
+            if s='ALL' then
+              begin
+                current_settings.whitespacetrimcount:=65535;
+                current_settings.whitespacetrimauto:=false;
+              end
+            else if s='AUTO' then
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=true;
+              end
+            else
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end;
+      end;
 
     procedure dir_namespace;
       var
@@ -1973,6 +2034,8 @@ unit scandir;
         AddDirective('MMX',directive_all, @dir_mmx);
         AddDirective('MODE',directive_all, @dir_mode);
         AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
+        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
+        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
         AddDirective('NAMESPACE',directive_all, @dir_namespace);
         AddDirective('NODEFINE',directive_all, @dir_nodefine);
         AddDirective('NOTE',directive_all, @dir_note);
diff --git compiler/scanner.pas compiler/scanner.pas
index 88b2703..4b0282e 100644
--- compiler/scanner.pas
+++ compiler/scanner.pas
@@ -146,6 +146,12 @@ interface
           { true, if we are parsing preprocessor expressions }
           in_preproc_comp_expr : boolean;
 
+          { Having these tracked in the scanner class itself versus local variables allows for
+            informative error handling that would be impossible otherwise }
+          in_multiline_string : boolean;
+          multiline_start_line : longint;
+          multiline_start_column : word;
+
           constructor Create(const fn:string; is_macro: boolean = false);
           destructor Destroy;override;
         { File buffer things }
@@ -187,6 +193,7 @@ interface
           procedure tokenwritesizeint(val : asizeint);
           procedure tokenwritelongint(val : longint);
           procedure tokenwritelongword(val : longword);
+          procedure tokenwritebyte(val : byte);
           procedure tokenwriteword(val : word);
           procedure tokenwriteshortint(val : shortint);
           procedure tokenwriteset(var b;size : longint);
@@ -2898,6 +2905,14 @@ type
         recordtokenbuf.write(val,sizeof(shortint));
       end;
 
+    procedure tscannerfile.tokenwritebyte(val : byte);
+      begin
+{$ifdef FPC_BIG_ENDIAN}
+        val:=swapendian(val);
+{$endif}
+        recordtokenbuf.write(val,sizeof(byte));
+      end;
+
     procedure tscannerfile.tokenwriteword(val : word);
       begin
 {$ifdef FPC_BIG_ENDIAN}
@@ -3111,8 +3126,11 @@ type
             else
              ControllerType:=ct_none;
 {$POP}
-           endpos:=replaytokenbuf.pos;
-           if endpos-startpos<>expected_size then
+            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+            whitespacetrimcount:=tokenreadword;
+            whitespacetrimauto:=boolean(tokenreadbyte);
+            endpos:=replaytokenbuf.pos;
+            if endpos-startpos<>expected_size then
              Comment(V_Error,'Wrong size of Settings read-in');
          end;
      end;
@@ -3189,6 +3207,9 @@ type
             if ControllerSupport then
               tokenwriteenum(controllertype,sizeof(tcontrollertype));
 {$POP}
+           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
+           tokenwriteword(whitespacetrimcount);
+           tokenwritebyte(byte(whitespacetrimauto));
            endpos:=recordtokenbuf.pos;
            size:=endpos-startpos;
            recordtokenbuf.seek(sizepos);
@@ -3760,7 +3781,10 @@ type
     procedure tscannerfile.end_of_file;
       begin
         checkpreprocstack;
-        Message(scan_f_end_of_file);
+        if in_multiline_string then
+          Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
+        else
+          Message(scan_f_end_of_file);
       end;
 
   {-------------------------------------------
@@ -4247,21 +4271,41 @@ type
       begin
         i:=0;
         msgwritten:=false;
-        if (c='''') then
+        if (c in ['''','`']) then
           begin
+            in_multiline_string:=(c='`');
+            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+              begin
+                result[0]:=chr(0);
+                Illegal_Char(c);
+              end;
             repeat
               readchar;
               case c of
                 #26 :
                   end_of_file;
                 #10,#13 :
-                  Message(scan_f_string_exceeds_line);
+                  if not in_multiline_string then
+                    begin
+                      if (multiline_start_line>0) and (multiline_start_column>0) then
+                        Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
+                      else
+                        Message(scan_f_string_exceeds_line);
+                    end;
                 '''' :
-                  begin
-                    readchar;
-                    if c<>'''' then
-                     break;
-                  end;
+                  if not in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'''' then
+                       break;
+                    end;
+                '`' :
+                  if in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'`' then
+                       break;
+                    end;
               end;
               if i<255 then
                 begin
@@ -4447,28 +4491,42 @@ type
                  if found=1 then
                   found:=2;
                end;
-             '''' :
+             '''','`' :
                if (current_commentstyle=comment_none) then
-                begin
-                  repeat
-                    readchar;
-                    case c of
-                      #26 :
-                        end_of_file;
-                      #10,#13 :
-                        break;
-                      '''' :
-                        begin
-                          readchar;
-                          if c<>'''' then
-                           begin
-                             next_char_loaded:=true;
+                 begin
+                   in_multiline_string:=(c='`');
+                   if not (in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches))) then
+                     repeat
+                       readchar;
+                       case c of
+                         #26 :
+                           end_of_file;
+                         #10,#13 :
+                           if not in_multiline_string then
                              break;
-                           end;
-                        end;
-                    end;
-                  until false;
-                end;
+                         '''' :
+                           if not in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'''' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                         '`' :
+                           if in_multiline_string then
+                             begin
+                               readchar;
+                               if c<>'`' then
+                                begin
+                                  next_char_loaded:=true;
+                                  break;
+                                end;
+                             end;
+                       end;
+                     until false;
+                 end;
              '(' :
                begin
                  if (current_commentstyle=comment_none) then
@@ -4664,9 +4722,15 @@ type
         mac     : tmacro;
         asciinr : string[33];
         iswidestring : boolean;
+        had_newline,first_multiline : boolean;
+        trimcount : word;
+        last_c : char;
       label
-         exit_label;
+        quote_label,exit_label;
       begin
+        had_newline:=false;
+        first_multiline:=false;
+        last_c:=#0;
         flushpendingswitchesstate;
 
         { record tokens? }
@@ -5098,8 +5162,19 @@ type
                  goto exit_label;
                end;
 
-             '''','#','^' :
+             '''','#','^','`' :
                begin
+                 in_multiline_string:=(c='`');
+                 if in_multiline_string then
+                   begin
+                     if not (m_multiline_strings in current_settings.modeswitches) then
+                       Illegal_Char(c)
+                     else
+                       begin
+                         multiline_start_line:=current_filepos.line;
+                         multiline_start_column:=current_filepos.column;
+                       end;
+                   end;
                  len:=0;
                  cstringpattern:='';
                  iswidestring:=false;
@@ -5206,26 +5281,62 @@ type
                            begin
                              if len>=length(cstringpattern) then
                                setlength(cstringpattern,length(cstringpattern)+256);
-                              inc(len);
-                              cstringpattern[len]:=chr(m);
+                             inc(len);
+                             cstringpattern[len]:=chr(m);
                            end;
                        end;
-                     '''' :
+                     '''','`' :
                        begin
+                         in_multiline_string:=(c='`');
+                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                          repeat
                            readchar;
-                           case c of
-                             #26 :
-                               end_of_file;
-                             #10,#13 :
-                               Message(scan_f_string_exceeds_line);
-                             '''' :
-                               begin
-                                 readchar;
-                                 if c<>'''' then
-                                  break;
-                               end;
-                           end;
+                           quote_label:
+                             case c of
+                               #26 :
+                                 end_of_file;
+                               #32,#9,#11 :
+                                 if (had_newline or first_multiline) and
+                                    (current_settings.whitespacetrimauto or
+                                    (current_settings.whitespacetrimcount>0)) then
+                                   begin
+                                     if current_settings.whitespacetrimauto then
+                                       trimcount:=multiline_start_column
+                                     else
+                                       trimcount:=current_settings.whitespacetrimcount;
+                                     while (c in [#32,#9,#11]) and (trimcount>0) do
+                                       begin
+                                         readchar;
+                                         dec(trimcount);
+                                       end;
+                                     had_newline:=false;
+                                     first_multiline:=false;
+                                     goto quote_label;
+                                   end;
+                               #10,#13 :
+                                 if not in_multiline_string then
+                                   begin
+                                     if (multiline_start_line>0) and (multiline_start_column>0) then
+                                       Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
+                                     else
+                                       Message(scan_f_string_exceeds_line);
+                                   end;
+                               '''' :
+                                 if not in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'''' then
+                                      break;
+                                   end;
+                               '`' :
+                                 if in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'`' then
+                                      break;
+                                   end;
+                             end;
+                           first_multiline:=false;
                            { interpret as utf-8 string? }
                            if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                              begin
@@ -5300,18 +5411,110 @@ type
                              end
                            else if iswidestring then
                              begin
-                               if current_settings.sourcecodepage=CP_UTF8 then
-                                 concatwidestringchar(patternw,ord(c))
-                               else
-                                 concatwidestringchar(patternw,asciichar2unicode(c))
+                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     begin
+                                       case current_settings.lineendingtype of
+                                         le_cr : concatwidestringchar(patternw,ord(#13));
+                                         le_crlf :
+                                           begin
+                                             concatwidestringchar(patternw,ord(#13));
+                                             concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_lf : concatwidestringchar(patternw,ord(#10));
+                                         le_platform :
+                                           begin
+                                             if target_info.newline=#13 then
+                                               concatwidestringchar(patternw,ord(#13))
+                                             else if target_info.newline=#13#10 then
+                                               begin
+                                                 concatwidestringchar(patternw,ord(#13));
+                                                 concatwidestringchar(patternw,ord(#10));
+                                               end
+                                             else if target_info.newline=#10 then
+                                               concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_source : concatwidestringchar(patternw,ord(c));
+                                       end;
+                                     end
+                                   else
+                                     case current_settings.lineendingtype of
+                                       le_cr : concatwidestringchar(patternw,asciichar2unicode(#13));
+                                       le_crlf :
+                                         begin
+                                           concatwidestringchar(patternw,asciichar2unicode(#13));
+                                           concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_lf : concatwidestringchar(patternw,asciichar2unicode(#10));
+                                       le_platform :
+                                         begin
+                                           if target_info.newline=#13 then
+                                             concatwidestringchar(patternw,asciichar2unicode(#13))
+                                           else if target_info.newline=#13#10 then
+                                             begin
+                                               concatwidestringchar(patternw,asciichar2unicode(#13));
+                                               concatwidestringchar(patternw,asciichar2unicode(#10));
+                                             end
+                                           else if target_info.newline=#10 then
+                                             concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_source : concatwidestringchar(patternw,asciichar2unicode(c));
+                                     end;
+                                   had_newline:=true;
+                                   inc(line_no);
+                                 end
+                               else if not (in_multiline_string and (c in [#10,#13])) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     concatwidestringchar(patternw,ord(c))
+                                   else
+                                     concatwidestringchar(patternw,asciichar2unicode(c));
+                                 end;
                              end
                            else
                              begin
-                               if len>=length(cstringpattern) then
-                                 setlength(cstringpattern,length(cstringpattern)+256);
-                                inc(len);
-                                cstringpattern[len]:=c;
+                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    case current_settings.lineendingtype of
+                                      le_cr : cstringpattern[len]:=#13;
+                                      le_crlf :
+                                        begin
+                                          cstringpattern[len]:=#13;
+                                          inc(len);
+                                          cstringpattern[len]:=#10;
+                                        end;
+                                      le_lf : cstringpattern[len]:=#10;
+                                      le_platform :
+                                        begin
+                                          if target_info.newline=#13 then
+                                            cstringpattern[len]:=#13
+                                          else if target_info.newline=#13#10 then
+                                            begin
+                                              cstringpattern[len]:=#13;
+                                              inc(len);
+                                              cstringpattern[len]:=#10;
+                                            end
+                                          else if target_info.newline=#10 then
+                                            cstringpattern[len]:=#10;
+                                        end;
+                                      le_source : cstringpattern[len]:=c;
+                                    end;
+                                    had_newline:=true;
+                                    inc(line_no);
+                                  end
+                                else if not (in_multiline_string and (c in [#10,#13])) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    cstringpattern[len]:=c;
+                                  end;
                              end;
+                         last_c:=c;
                          until false;
                        end;
                      '^' :
@@ -5338,6 +5541,7 @@ type
                      else
                       break;
                    end;
+                 last_c:=c;
                  until false;
                  { strings with length 1 become const chars }
                  if iswidestring then
@@ -5445,6 +5649,10 @@ exit_label:
         low,high,mid: longint;
         optoken: ttoken;
       begin
+         { Added the assignment to NOTOKEN below because I got a DFA uninitialized result
+           warning when building the compiler with -O3, which broke compilation with -Sew.
+           - Akira1364 }
+         readpreproc:=NOTOKEN;
          skipspace;
          case c of
            '_',
@@ -5480,12 +5688,13 @@ exit_label:
                current_scanner.preproc_pattern:=pattern;
                readpreproc:=optoken;
              end;
-           '''' :
-             begin
-               readquotedstring;
-               current_scanner.preproc_pattern:=cstringpattern;
-               readpreproc:=_CSTRING;
-             end;
+           '''','`' :
+             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
+               begin
+                 readquotedstring;
+                 current_scanner.preproc_pattern:=cstringpattern;
+                 readpreproc:=_CSTRING;
+               end;
            '0'..'9' :
              begin
                readnumber;
diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
index 7009bd8..14f600f 100644
--- compiler/utils/ppuutils/ppudump.pp
+++ compiler/utils/ppuutils/ppudump.pp
@@ -2444,6 +2444,9 @@ const
             else
              ControllerType:=ct_none;
 {$POP}
+           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+           whitespacetrimcount:=gettokenbufword;
+           whitespacetrimauto:=boolean(gettokenbufbyte);
            endpos:=tbi;
            if endpos-startpos<>expected_size then
              Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);

Akira1364

2019-07-17 04:07

reporter  

multi_line_strings_tests_rev6.patch (26,935 bytes)
diff --git tests/test/tmultilinestring1.pp tests/test/tmultilinestring1.pp
new file mode 100644
index 0000000..007874c
--- /dev/null
+++ tests/test/tmultilinestring1.pp
@@ -0,0 +1,14 @@
+program tmultilinestring1;
+
+{$modeswitch MultiLineStrings}
+
+const MyString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyString);
+end.
diff --git tests/test/tmultilinestring10.pp tests/test/tmultilinestring10.pp
new file mode 100644
index 0000000..59aeb2c
--- /dev/null
+++ tests/test/tmultilinestring10.pp
@@ -0,0 +1,9 @@
+program tmultilinestring10;
+
+{ Test the use of multiline strings from units in programs }
+
+uses umultilinestring1;
+
+begin
+  Write(Long);
+end.
diff --git tests/test/tmultilinestring11.pp tests/test/tmultilinestring11.pp
new file mode 100644
index 0000000..206e61c
--- /dev/null
+++ tests/test/tmultilinestring11.pp
@@ -0,0 +1,19 @@
+{ %FAIL }
+
+program tmultilinestring11;
+
+{$modeswitch MultiLineStrings}
+{$H-}
+
+{ Some Mark Twain... }
+
+const WayTooLong =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+begin
+  Write(WayTooLong);
+end.
diff --git tests/test/tmultilinestring12.pp tests/test/tmultilinestring12.pp
new file mode 100644
index 0000000..ca6a800
--- /dev/null
+++ tests/test/tmultilinestring12.pp
@@ -0,0 +1,18 @@
+program tmultilinestring12;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 4}
+
+procedure TakesAString(const S: String);
+begin
+  Write(S);
+end;
+
+begin
+  TakesAString(`
+    This
+    works
+    just
+    fine!
+  `);
+end.
diff --git tests/test/tmultilinestring13.pp tests/test/tmultilinestring13.pp
new file mode 100644
index 0000000..3652c76
--- /dev/null
+++ tests/test/tmultilinestring13.pp
@@ -0,0 +1,18 @@
+program tmultilinestring13;
+
+{$modeswitch MultiLineStrings}
+
+const A =
+`
+``a``
+`;
+
+const B =
+`
+'a'
+`;
+
+begin
+  Write(A);
+  Write(B);
+end.
diff --git tests/test/tmultilinestring14.pp tests/test/tmultilinestring14.pp
new file mode 100644
index 0000000..e181ff7
--- /dev/null
+++ tests/test/tmultilinestring14.pp
@@ -0,0 +1,40 @@
+program tmultilinestring14;
+
+{$modeswitch MultiLineStrings}
+
+{$MultiLineStringTrimLeft 2}
+
+const A = `
+  A
+  B
+  C
+  D
+`;
+
+{$MultiLineStringTrimLeft 4}
+
+const B = `
+    A
+    B
+    C
+    D
+`;
+
+begin
+  Write(A);
+  Write(B);
+
+  { The number-to-trim being larger, (even much larger) than the amount of whitespace is not a problem,
+    as it stops immediately when it is no longer actually *in* whitespace regardless. }
+
+  {$MultiLineStringTrimLeft 10000}
+
+  { Non-leading whitespace is preserved properly, of course. }
+
+  Write(`
+        sdfs
+        sd fs fs
+        sd  fsfs  sdfd sfdf
+        sdfs fsd
+  `);
+end.
diff --git tests/test/tmultilinestring15.pp tests/test/tmultilinestring15.pp
new file mode 100644
index 0000000..fa3426b
--- /dev/null
+++ tests/test/tmultilinestring15.pp
@@ -0,0 +1,37 @@
+program tmultilinestring15;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+const X = `
+  Hello
+  every
+  body!
+`;
+
+const Y = `
+    Goodbye
+    every
+    body!
+    
+`;
+
+{ Test some wacky concatentation }
+
+begin
+  Write(X + Y);
+  Write(Concat(X, Y));
+  Write(
+    '  Single line string ' +
+    `
+    and
+    ` +
+    `
+    Multi
+    line
+    string
+    ` +
+    Y +
+    X
+  );
+end.
diff --git tests/test/tmultilinestring16.pp tests/test/tmultilinestring16.pp
new file mode 100644
index 0000000..9e1d7b5
--- /dev/null
+++ tests/test/tmultilinestring16.pp
@@ -0,0 +1,30 @@
+program tmultilinestring16;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+procedure TakesAnArray(constref A: array of String);
+var S: String;
+begin
+  for S in A do Write(S);
+end;
+
+begin
+  TakesAnArray([
+    ` Multi
+      line
+      one!`,
+    `
+      Multi
+      line
+      two!`,
+    `
+      Multi
+      line
+      three!
+    `,
+    'Single line one!' + sLineBreak +
+    'Single line two!' + sLineBreak +
+    'Single line three!'
+  ]);
+end.
diff --git tests/test/tmultilinestring17.pp tests/test/tmultilinestring17.pp
new file mode 100644
index 0000000..116d6f8
--- /dev/null
+++ tests/test/tmultilinestring17.pp
@@ -0,0 +1,39 @@
+program tmultilinestring17;
+
+{$mode ObjFPC}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 6}
+
+type
+  TMessage = record
+    Msg: String;
+  end;
+
+  TMyClass = class
+  public
+    procedure MyMessage(var Msg: TMessage); message `
+      Multi
+      Line
+      Message!
+    `;
+  end;
+
+  procedure TMyClass.MyMessage(var Msg: TMessage);
+  begin
+    WriteLn('Ok!');
+  end;
+
+  const M: TMessage = (
+    Msg: `
+      Multi
+      Line
+      Message!
+    `
+  );
+
+begin
+  with TMyClass.Create() do begin
+    DispatchStr(M);
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring18.pp tests/test/tmultilinestring18.pp
new file mode 100644
index 0000000..d98f7b9
--- /dev/null
+++ tests/test/tmultilinestring18.pp
@@ -0,0 +1,19 @@
+program tmultilinestring18;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+{$Warnings On}
+
+procedure IsDeprecated; deprecated
+`
+  Multi
+  line
+  deprecation
+  message!
+`;
+begin
+end;
+
+begin
+  IsDeprecated;
+end.
diff --git tests/test/tmultilinestring19.pp tests/test/tmultilinestring19.pp
new file mode 100644
index 0000000..a7e7201
--- /dev/null
+++ tests/test/tmultilinestring19.pp
@@ -0,0 +1,15 @@
+program tmultilinestring19;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+{ This is extremely unlikely, but it needs to work properly... }
+
+procedure Bloop; external name `
+  Actually
+  Called
+  Bloop
+`;
+
+begin
+end.
diff --git tests/test/tmultilinestring2.pp tests/test/tmultilinestring2.pp
new file mode 100644
index 0000000..3da55bc
--- /dev/null
+++ tests/test/tmultilinestring2.pp
@@ -0,0 +1,22 @@
+program tmultilinestring2;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: AnsiString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring20.pp tests/test/tmultilinestring20.pp
new file mode 100644
index 0000000..6491720
--- /dev/null
+++ tests/test/tmultilinestring20.pp
@@ -0,0 +1,10 @@
+program tmultilinestring20;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+uses umultilinestring2;
+
+begin
+  DoIt;
+end.
diff --git tests/test/tmultilinestring21.pp tests/test/tmultilinestring21.pp
new file mode 100644
index 0000000..a3ee3f6
--- /dev/null
+++ tests/test/tmultilinestring21.pp
@@ -0,0 +1,16 @@
+{ %FAIL }
+
+program tmultilinestring21;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2000000}
+
+const S = `
+  A
+  B
+  C
+`;
+
+begin
+  WriteLn(S);
+end.
diff --git tests/test/tmultilinestring22.pp tests/test/tmultilinestring22.pp
new file mode 100644
index 0000000..0b21b31
--- /dev/null
+++ tests/test/tmultilinestring22.pp
@@ -0,0 +1,10 @@
+program tmultilinestring22;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const Test = ` ThisSpace>>>>     <<<<disappeared`;
+
+begin
+  Write(Test);
+end.
diff --git tests/test/tmultilinestring23.pp tests/test/tmultilinestring23.pp
new file mode 100644
index 0000000..fee9e04
--- /dev/null
+++ tests/test/tmultilinestring23.pp
@@ -0,0 +1,51 @@
+program tmultilinestring23;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+{$modeswitch PrefixedAttributes}
+
+uses RTTI;
+
+type
+  TMultiLineAttribute = class(TCustomAttribute)
+  private
+    FString: String;
+  public
+    constructor Create(const S: String);
+    property StringValue: String read FString;
+  end;
+
+  constructor TMultiLineAttribute.Create(const S: String);
+  begin
+    FString := S;
+  end;
+
+type
+  [TMultiLineAttribute(
+    `This is my
+     pretty cool
+     multi-line string
+     attribute!`
+  )]
+  [TMultiLineAttribute(
+    `This is my
+     even cooler
+     multi-line string
+     attribute!`
+  )]
+  TMyClass = class
+  end;
+
+var
+  A: TMultiLineAttribute;
+
+begin
+  with TRTTIType.Create(TypeInfo(TMyClass)) do begin
+    for TCustomAttribute(A) in GetAttributes() do begin
+      WriteLn(A.StringValue);
+      A.Free();
+    end;
+    Free();
+  end;
+end.
diff --git tests/test/tmultilinestring24.pp tests/test/tmultilinestring24.pp
new file mode 100644
index 0000000..7a282f9
--- /dev/null
+++ tests/test/tmultilinestring24.pp
@@ -0,0 +1,62 @@
+program tmultilinestring24;
+
+{ engkin's bug example }
+
+{$mode objfpc}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 15}
+{$MultiLineStringLineEnding Platform}
+
+var
+{$MultiLineStringLineEnding CR}
+  a: array[0..3] of string = (
+``
+,
+`
+`
+,
+`
+
+`
+,
+`
+
+
+`);
+
+  {$MultiLineStringLineEnding CRLF}
+b: array[0..3] of string = (
+`1`
+,
+`1
+2`
+,
+`1
+2
+3`
+,
+`1
+2
+3
+4`);
+
+procedure Test(constref StrArray:array of string);
+var
+  s,sHex: string;
+  c: char;
+begin
+  for s in StrArray do
+  begin
+    WriteLn('Length: ',Length(s));
+    sHex := '';
+    for c in s do
+      sHex := sHex+'$'+hexStr(ord(c),2);
+    WriteLn(sHex);
+  end;
+  WriteLn('---------------');
+end;
+
+begin
+  Test(a);
+  Test(b);
+end.
diff --git tests/test/tmultilinestring25.pp tests/test/tmultilinestring25.pp
new file mode 100644
index 0000000..2d44f8b
--- /dev/null
+++ tests/test/tmultilinestring25.pp
@@ -0,0 +1,26 @@
+program tmultilinestring25;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft Auto}
+
+const
+  Str1 = `SELECT o.*, C.Company
+          from Orders O
+          join Customer C
+            on o.CustNo=C.ID
+          where
+            O.saledate=DATE '2001.03.20'`;
+
+const
+  Str2 =
+    `SELECT o.*, C.Company
+     from Orders O
+     join Customer C
+       on o.CustNo=C.ID
+     where
+       O.saledate=DATE '2001.03.20'`;
+
+begin
+  WriteLn(Str1);
+  WriteLn(Str2);
+end.
diff --git tests/test/tmultilinestring26.pp tests/test/tmultilinestring26.pp
new file mode 100644
index 0000000..6b1c57f
--- /dev/null
+++ tests/test/tmultilinestring26.pp
@@ -0,0 +1,24 @@
+program tmultilinestring26;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 5}
+
+const middleSpaceBug: array[0..5] of string = (
+#$41` `#$42   //<--- this becomes #$41#$42 instead of #$41#$20#$42
+,
+#$41` - `#$42 //<--- this becomes #$41#$2D#$20#$42 instead of #$41#$20#$2D#$20#$42
+,
+#$41`      -      `#$42  //<--- one space left instead of six spaces
+,
+#$41`      -      `#$42`  --  `  //<---- the same bug twice
+,
+'  '#$41` `#$42   //<--- the space between backticks disappears: #$20#$20#$41#$42
+,
+^T' e s'` t`  //<--- last two: $73$74 instead of $73$20$74
+);
+
+var S: String;
+
+begin
+  for S in middleSpaceBug do WriteLn(S);
+end.
diff --git tests/test/tmultilinestring27.pp tests/test/tmultilinestring27.pp
new file mode 100644
index 0000000..9245120
--- /dev/null
+++ tests/test/tmultilinestring27.pp
@@ -0,0 +1,16 @@
+program tmultilinestring27;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft Auto}
+
+resourcestring S =
+    `This
+       is
+     a
+       multi-line
+     resource
+       string`;
+
+begin
+  Write(S);
+end.
\ No newline at end of file
diff --git tests/test/tmultilinestring28.pp tests/test/tmultilinestring28.pp
new file mode 100644
index 0000000..b8576c0
--- /dev/null
+++ tests/test/tmultilinestring28.pp
@@ -0,0 +1,24 @@
+{ %FAIL }
+
+{ Will show: 
+  tmultilinestring28.pp(20,1) Fatal: Unterminated multi-line string beginning at line 11, column 7. }
+
+program tmultilinestring28;
+
+{$modeswitch MultiLineStrings}
+
+const
+  a = `this will be unterminated
+with some
+lines in it.
+
+var
+  B : String;
+
+begin
+  B:=`
+again
+something
+end backticked`;
+
+end.
diff --git tests/test/tmultilinestring3.pp tests/test/tmultilinestring3.pp
new file mode 100644
index 0000000..6625906
--- /dev/null
+++ tests/test/tmultilinestring3.pp
@@ -0,0 +1,22 @@
+program tmultilinestring3;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: ShortString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring4.pp tests/test/tmultilinestring4.pp
new file mode 100644
index 0000000..6489542
--- /dev/null
+++ tests/test/tmultilinestring4.pp
@@ -0,0 +1,22 @@
+program tmultilinestring4;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: WideString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring5.pp tests/test/tmultilinestring5.pp
new file mode 100644
index 0000000..a868d93
--- /dev/null
+++ tests/test/tmultilinestring5.pp
@@ -0,0 +1,22 @@
+program tmultilinestring5;
+
+{$modeswitch MultiLineStrings}
+
+var MyStringV: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+const MyStringC: UnicodeString =
+`
+Hey
+Hey
+Hey
+`;
+
+begin
+  Write(MyStringV);
+  Write(MyStringC);
+end.
diff --git tests/test/tmultilinestring6.pp tests/test/tmultilinestring6.pp
new file mode 100644
index 0000000..a72af31
--- /dev/null
+++ tests/test/tmultilinestring6.pp
@@ -0,0 +1,65 @@
+program tmultilinestring6;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CR}
+
+const A =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding CRLF}
+
+const B =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding LF}
+
+const C =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding PLATFORM}
+
+const D =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+{$MultiLineStringLineEnding SOURCE}
+
+const E =
+`
+😊
+😊
+😊
+😊
+😊
+`;
+
+begin
+  Write(A);
+  Write(B);
+  Write(C);
+  Write(D);
+  Write(E);
+end.
diff --git tests/test/tmultilinestring7.pp tests/test/tmultilinestring7.pp
new file mode 100644
index 0000000..4946ba1
--- /dev/null
+++ tests/test/tmultilinestring7.pp
@@ -0,0 +1,17 @@
+{ %FAIL }
+
+program tmultilinestring7;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding DOESNOTEXIST}
+
+const Blah =
+`
+A
+B
+C
+`;
+
+begin
+  Write(Blah);
+end.
diff --git tests/test/tmultilinestring8.pp tests/test/tmultilinestring8.pp
new file mode 100644
index 0000000..00c6cc5
--- /dev/null
+++ tests/test/tmultilinestring8.pp
@@ -0,0 +1,38 @@
+{ %FAIL }
+
+program tmultilinestring8;
+
+{ Ryan's example from the mailing list }
+
+{$modeswitch MultiLineStrings}
+
+const lines: ansistring = `
+  #version 150
+
+  uniform sampler2D textures[8];
+  in vec2 vertexTexCoord;
+  in vec4 vertexColor;
+  in float vertexUVMap;
+  out vec4 fragColor;
+
+  void main()
+  {
+    if (vertexUVMap == 255) {
+      fragColor = vertexColor;
+    } else {
+      fragColor = texture(textures[int(vertexUVMap)], vertexTexCoord.st);
+      if (vertexColor.a < fragColor.a) {
+        fragColor.a = vertexColor.a;
+      }
+    }
+
+    // TODO: testing
+    fragColor = vec4(1,0,0,1);
+  }
+`;
+
+var
+  s: ansistring = lines;
+begin
+  writeln(b);
+end.
diff --git tests/test/tmultilinestring9.pp tests/test/tmultilinestring9.pp
new file mode 100644
index 0000000..033f8fb
--- /dev/null
+++ tests/test/tmultilinestring9.pp
@@ -0,0 +1,13 @@
+{ %FAIL }
+
+program tmultilinestring9;
+
+{ "Forget" to set the modeswitch... }
+
+const NotAllowed = `
+Oh
+no!
+`;
+
+begin
+end.
diff --git tests/test/umultilinestring1.pp tests/test/umultilinestring1.pp
new file mode 100644
index 0000000..62dafe2
--- /dev/null
+++ tests/test/umultilinestring1.pp
@@ -0,0 +1,21 @@
+unit umultilinestring1;
+
+{$CodePage UTF8}
+{$H+}
+{$modeswitch MultiLineStrings}
+{$MultiLineStringLineEnding CRLF}
+
+interface
+
+{ Some Mark Twain... }
+
+const Long =
+`
+My father was a St. Bernard, my mother was a collie, but I am a Presbyterian. This is what my mother told me, I do not know these nice distinctions myself. To me they are only fine large words meaning nothing. My mother had a fondness for such; she liked to say them, and see other dogs look surprised and envious, as wondering how she got so much education. But, indeed, it was not real education; it was only show: she got the words by listening in the dining-room and drawing-room when there was company, and by going with the children to Sunday-school and listening there; and whenever she heard a large word she said it over to herself many times, and so was able to keep it until there was a dogmatic gathering in the neighborhood, then she would get it off, and surprise and distress them all, from pocket-pup to mastiff, which rewarded her for all her trouble. If there was a stranger he was nearly sure to be suspicious, and when he got his breath again he would ask her what it meant. And she always told him. He was never expecting this but thought he would catch her; so when she told him, he was the one that looked ashamed, whereas he had thought it was going to be she. The others were always waiting for this, and glad of it and proud of her, for they knew what was going to happen, because they had had experience. When she told the meaning of a big word they were all so taken up with admiration that it never occurred to any dog to doubt if it was the right one; and that was natural, because, for one thing, she answered up so promptly that it seemed like a dictionary speaking, and for another thing, where could they find out whether it was right or not? for she was the only cultivated dog there was. By and by, when I was older, she brought home the word Unintellectual, one time, and worked it pretty hard all the week at different gatherings, making much unhappiness and despondency; and it was at this time that I noticed that during that week she was asked for the meaning at eight different assemblages, and flashed out a fresh definition every time, which showed me that she had more presence of mind than culture, though I said nothing, of course. She had one word which she always kept on hand, and ready, like a life-preserver, a kind of emergency word to strap on when she was likely to get washed overboard in a sudden way—that was the word Synonymous. When she happened to fetch out a long word which had had its day weeks before and its prepared meanings gone to her dump-pile, if there was a stranger there of course it knocked him groggy for a couple of minutes, then he would come to, and by that time she would be away down wind on another tack, and not expecting anything; so when he'd hail and ask her to cash in, I (the only dog on the inside of her game) could see her canvas flicker a moment—but only just a moment—then it would belly out taut and full, and she would say, as calm as a summer's day, “It's synonymous with supererogation,” or some godless long reptile of a word like that, and go placidly about and skim away on the next tack, perfectly comfortable, you know, and leave that stranger looking profane and embarrassed, and the initiated slatting the floor with their tails in unison and their faces transfigured with a holy joy.
+And it was the same with phrases. She would drag home a whole phrase, if it had a grand sound, and play it six nights and two matinees, and explain it a new way every time—which she had to, for all she cared for was the phrase; she wasn't interested in what it meant, and knew those dogs hadn't wit enough to catch her, anyway. Yes, she was a daisy! She got so she wasn't afraid of anything, she had such confidence in the ignorance of those creatures. She even brought anecdotes that she had heard the family and the dinner-guests laugh and shout over; and as a rule she got the nub of one chestnut hitched onto another chestnut, where, of course, it didn't fit and hadn't any point; and when she delivered the nub she fell over and rolled on the floor and laughed and barked in the most insane way, while I could see that she was wondering to herself why it didn't seem as funny as it did when she first heard it. But no harm was done; the others rolled and barked too, privately ashamed of themselves for not seeing the point, and never suspecting that the fault was not with them and there wasn't any to see.
+You can see by these things that she was of a rather vain and frivolous character; still, she had virtues, and enough to make up, I think. She had a kind heart and gentle ways, and never harbored resentments for injuries done her, but put them easily out of her mind and forgot them; and she taught her children her kindly way, and from her we learned also to be brave and prompt in time of danger, and not to run away, but face the peril that threatened friend or stranger, and help him the best we could without stopping to think what the cost might be to us. And she taught us not by words only, but by example, and that is the best way and the surest and the most lasting. Why, the brave things she did, the splendid things! she was just a soldier; and so modest about it—well, you couldn't help admiring her, and you couldn't help imitating her; not even a King Charles spaniel could remain entirely despicable in her society. So, as you see, there was more to her than her education.
+`;
+
+implementation
+
+end.
diff --git tests/test/umultilinestring2.pp tests/test/umultilinestring2.pp
new file mode 100644
index 0000000..ca08f05
--- /dev/null
+++ tests/test/umultilinestring2.pp
@@ -0,0 +1,17 @@
+unit umultilinestring2;
+
+{$modeswitch MultiLineStrings}
+{$MultiLineStringTrimLeft 2}
+
+interface
+
+procedure DoIt;
+
+implementation
+
+procedure DoIt; public name `SingleLineMultiLine`;
+begin
+  Write('Ok!');
+end;
+
+end.

Akira1364

2019-07-18 05:28

reporter   ~0117295

Did some minor format-fixing in a few places where I noticed my code wasn't quite following the FPC style properly.

multi_line_strings_main_rev5.patch (32,335 bytes)
diff --git compiler/globals.pas compiler/globals.pas
index 1f368e6..a05b7bb 100644
--- compiler/globals.pas
+++ compiler/globals.pas
@@ -183,6 +183,12 @@ interface
 
          { WARNING: this pointer cannot be written as such in record token }
          pmessage : pmessagestaterecord;
+         
+         lineendingtype : tlineendingtype;
+
+         whitespacetrimcount : word;
+         
+         whitespacetrimauto : boolean;
        end;
 
     const
@@ -574,6 +580,9 @@ interface
 {$endif defined(LLVM) and not defined(GENERIC_CPU)}
         controllertype : ct_none;
         pmessage : nil;
+        lineendingtype : le_platform;
+        whitespacetrimcount : 0;
+        whitespacetrimauto : false
       );
 
     var
@@ -1455,7 +1464,7 @@ implementation
        if localexepath='' then
         begin
           hs1 := ExtractFileName(exeName);
-	  hs1 := ChangeFileExt(hs1,source_info.exeext);
+          hs1 := ChangeFileExt(hs1,source_info.exeext);
 {$ifdef macos}
           FindFile(hs1,GetEnvironmentVariable('Commands'),false,localExepath);
 {$else macos}
diff --git compiler/globtype.pas compiler/globtype.pas
index b6c142f..90d5516 100644
--- compiler/globtype.pas
+++ compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_multiline_strings    { multi-line strings denoted with '`' are enabled and valid }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -605,7 +606,18 @@ interface
          pocall_vectorcall
        );
        tproccalloptions = set of tproccalloption;
-
+       
+       tlineendingtype = ({Carriage return, aka #13}
+                          le_cr,
+                          {Carriage return + line feed, aka #13#10}
+                          le_crlf,
+                          {Line feed, aka #10}
+                          le_lf,
+                          {Use the platform default}
+                          le_platform,
+                          {Use whatever is in the file}
+                          le_source);
+                          
      const
        proccalloptionStr : array[tproccalloption] of string[16]=('',
            'CDecl',
@@ -683,7 +695,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'MULTILINESTRINGS'
          );
 
 
diff --git compiler/msg/errore.msg compiler/msg/errore.msg
index d6a0bdf..3307f3c 100644
--- compiler/msg/errore.msg
+++ compiler/msg/errore.msg
@@ -432,6 +432,14 @@ scan_w_setpeosversion_not_support=02103_W_SETPEOSVERSION is not supported by the
 scan_w_setpesubsysversion_not_support=02104_W_SETPESUBSYSVERSION is not supported by the target OS
 % The \var{\{\$SETPESUBSYSVERSION\}} directive is not supported by the target OS.
 scan_n_changecputype=02105_N_Changed CPU type to be consistent with specified controller
+scan_e_unknown_lineending_type=02106_E_Unknown line ending type specified. Valid options are CR, CRLF, LF, PLATFORM, or SOURCE.
+% The line ending type that was specified is not one of CR, CRLF, LF, PLATFORM, or SOURCE.
+scan_e_trimcount_out_of_range=02107_E_The value of MULTILINESTRINGTRIMLEFT cannot be less than 0 or greater than 65535.
+% MULTILINESTRINGTRIMLEFT is stored in a "word" field, and thus is restricted to the range 0..65535.
+scan_e_illegal_directive=02108_E_Illegal compiler directive "$1"
+% You have specified a compiler directive that cannot (for one of several possible reasons) currently be used.
+scan_f_unterminated_multiline_string=02109_F_Unterminated multi-line string beginning at line $1, column $2.
+% The file contains an "unbalanced" number of multi-line string denoting backtick characters.
 % \end{description}
 #
 # Parser
diff --git compiler/pbase.pas compiler/pbase.pas
index dffd92d..c994bc7 100644
--- compiler/pbase.pas
+++ compiler/pbase.pas
@@ -144,10 +144,16 @@ implementation
     procedure consume(i : ttoken);
       begin
         if (token<>i) and (idtoken<>i) then
-          if token=_id then
-            Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
-          else
-            Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str)
+          begin
+            if current_scanner.had_multiline_string then
+              Message2(scan_f_unterminated_multiline_string,
+                       tostr(current_scanner.multiline_start_line),
+                       tostr(current_scanner.multiline_start_column))
+            else if token=_id then
+              Message2(scan_f_syn_expected,tokeninfo^[i].str,'identifier '+pattern)
+            else
+              Message2(scan_f_syn_expected,tokeninfo^[i].str,tokeninfo^[token].str);
+          end
         else
           begin
             if token=_END then
@@ -178,7 +184,12 @@ implementation
             if token=_EOF then
              begin
                Consume(atoken);
-               Message(scan_f_end_of_file);
+               if current_scanner.had_multiline_string then
+                 Message2(scan_f_unterminated_multiline_string,
+                          tostr(current_scanner.multiline_start_line),
+                          tostr(current_scanner.multiline_start_column))
+               else
+                 Message(scan_f_end_of_file);
                exit;
              end;
           end;
diff --git compiler/pstatmnt.pas compiler/pstatmnt.pas
index 5f6987d..ea32f6d 100644
--- compiler/pstatmnt.pas
+++ compiler/pstatmnt.pas
@@ -1226,7 +1226,12 @@ implementation
                consume(_PLUS);
              end;
            _EOF :
-             Message(scan_f_end_of_file);
+             if current_scanner.had_multiline_string then
+               Message2(scan_f_unterminated_multiline_string,
+                        tostr(current_scanner.multiline_start_line),
+                        tostr(current_scanner.multiline_start_column))
+             else
+               Message(scan_f_end_of_file);
          else
            begin
              { don't typecheck yet, because that will also simplify, which may
diff --git compiler/scandir.pas compiler/scandir.pas
index 9dd457b..74703d7 100644
--- compiler/scandir.pas
+++ compiler/scandir.pas
@@ -1003,6 +1003,67 @@ unit scandir;
           end;
       end;
 
+    procedure dir_multilinestringlineending;
+      var
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGLINEENDING');
+        current_scanner.skipspace;
+        s:=current_scanner.readid;
+        if (s='CR') then
+          current_settings.lineendingtype:=le_cr
+        else if (s='CRLF') then
+          current_settings.lineendingtype:=le_crlf
+        else if (s='LF') then
+          current_settings.lineendingtype:=le_lf
+        else if (s='PLATFORM') then
+          current_settings.lineendingtype:=le_platform
+        else if (s='SOURCE') then
+          current_settings.lineendingtype:=le_source
+        else
+          Message(scan_e_unknown_lineending_type);
+      end;
+
+    procedure dir_multilinestringtrimleft;
+      var
+        count : longint;
+        s : string;
+      begin
+        if not (m_multiline_strings in current_settings.modeswitches) then
+          Message1(scan_e_illegal_directive,'MULTILINESTRINGTRIMLEFT');
+        current_scanner.skipspace;
+        if (c in ['1'..'9']) then
+          begin
+            count:=current_scanner.readval;
+            if (count<0) or (count>65535) then
+              Message(scan_e_trimcount_out_of_range)
+            else
+              begin
+                current_settings.whitespacetrimcount:=count;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end
+        else
+          begin
+            s:=current_scanner.readid;
+            if s='ALL' then
+              begin
+                current_settings.whitespacetrimcount:=65535;
+                current_settings.whitespacetrimauto:=false;
+              end
+            else if s='AUTO' then
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=true;
+              end
+            else
+              begin
+                current_settings.whitespacetrimcount:=0;
+                current_settings.whitespacetrimauto:=false;
+              end;
+          end;
+      end;
 
     procedure dir_namespace;
       var
@@ -1973,6 +2034,8 @@ unit scandir;
         AddDirective('MMX',directive_all, @dir_mmx);
         AddDirective('MODE',directive_all, @dir_mode);
         AddDirective('MODESWITCH',directive_all, @dir_modeswitch);
+        AddDirective('MULTILINESTRINGLINEENDING',directive_all, @dir_multilinestringlineending);
+        AddDirective('MULTILINESTRINGTRIMLEFT',directive_all, @dir_multilinestringtrimleft);
         AddDirective('NAMESPACE',directive_all, @dir_namespace);
         AddDirective('NODEFINE',directive_all, @dir_nodefine);
         AddDirective('NOTE',directive_all, @dir_note);
diff --git compiler/scanner.pas compiler/scanner.pas
index 88b2703..52c3256 100644
--- compiler/scanner.pas
+++ compiler/scanner.pas
@@ -146,6 +146,12 @@ interface
           { true, if we are parsing preprocessor expressions }
           in_preproc_comp_expr : boolean;
 
+          { Having these tracked in the scanner class itself versus local variables allows for
+            informative error handling that would be impossible otherwise }
+          in_multiline_string, had_multiline_string : boolean;
+          multiline_start_line : longint;
+          multiline_start_column : word;
+
           constructor Create(const fn:string; is_macro: boolean = false);
           destructor Destroy;override;
         { File buffer things }
@@ -187,6 +193,7 @@ interface
           procedure tokenwritesizeint(val : asizeint);
           procedure tokenwritelongint(val : longint);
           procedure tokenwritelongword(val : longword);
+          procedure tokenwritebyte(val : byte);
           procedure tokenwriteword(val : word);
           procedure tokenwriteshortint(val : shortint);
           procedure tokenwriteset(var b;size : longint);
@@ -2898,6 +2905,14 @@ type
         recordtokenbuf.write(val,sizeof(shortint));
       end;
 
+    procedure tscannerfile.tokenwritebyte(val : byte);
+      begin
+{$ifdef FPC_BIG_ENDIAN}
+        val:=swapendian(val);
+{$endif}
+        recordtokenbuf.write(val,sizeof(byte));
+      end;
+
     procedure tscannerfile.tokenwriteword(val : word);
       begin
 {$ifdef FPC_BIG_ENDIAN}
@@ -3111,8 +3126,11 @@ type
             else
              ControllerType:=ct_none;
 {$POP}
-           endpos:=replaytokenbuf.pos;
-           if endpos-startpos<>expected_size then
+            lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+            whitespacetrimcount:=tokenreadword;
+            whitespacetrimauto:=boolean(tokenreadbyte);
+            endpos:=replaytokenbuf.pos;
+            if endpos-startpos<>expected_size then
              Comment(V_Error,'Wrong size of Settings read-in');
          end;
      end;
@@ -3189,6 +3207,9 @@ type
             if ControllerSupport then
               tokenwriteenum(controllertype,sizeof(tcontrollertype));
 {$POP}
+           tokenwriteenum(lineendingtype,sizeof(tlineendingtype));
+           tokenwriteword(whitespacetrimcount);
+           tokenwritebyte(byte(whitespacetrimauto));
            endpos:=recordtokenbuf.pos;
            size:=endpos-startpos;
            recordtokenbuf.seek(sizepos);
@@ -3760,7 +3781,10 @@ type
     procedure tscannerfile.end_of_file;
       begin
         checkpreprocstack;
-        Message(scan_f_end_of_file);
+        if in_multiline_string then
+          Message2(scan_f_unterminated_multiline_string, tostr(multiline_start_line), tostr(multiline_start_column))
+        else
+          Message(scan_f_end_of_file);
       end;
 
   {-------------------------------------------
@@ -4247,21 +4271,44 @@ type
       begin
         i:=0;
         msgwritten:=false;
-        if (c='''') then
+        if (c in ['''','`']) then
           begin
+            had_multiline_string:=in_multiline_string;
+            in_multiline_string:=(c='`');
+            if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+              begin
+                result[0]:=chr(0);
+                Illegal_Char(c);
+              end;
             repeat
               readchar;
               case c of
                 #26 :
                   end_of_file;
                 #10,#13 :
-                  Message(scan_f_string_exceeds_line);
+                  if not in_multiline_string then
+                    begin
+                      if had_multiline_string then
+                        Message2(scan_f_unterminated_multiline_string,
+                                 tostr(multiline_start_line),
+                                 tostr(multiline_start_column))
+                      else
+                        Message(scan_f_string_exceeds_line);
+                    end;
                 '''' :
-                  begin
-                    readchar;
-                    if c<>'''' then
-                     break;
-                  end;
+                  if not in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'''' then
+                       break;
+                    end;
+                '`' :
+                  if in_multiline_string then
+                    begin
+                      readchar;
+                      if c<>'`' then
+                       break;
+                    end;
               end;
               if i<255 then
                 begin
@@ -4447,28 +4494,44 @@ type
                  if found=1 then
                   found:=2;
                end;
-             '''' :
+             '''','`' :
                if (current_commentstyle=comment_none) then
-                begin
-                  repeat
-                    readchar;
-                    case c of
-                      #26 :
-                        end_of_file;
-                      #10,#13 :
-                        break;
-                      '''' :
-                        begin
-                          readchar;
-                          if c<>'''' then
+                 begin
+                   had_multiline_string:=in_multiline_string;
+                   in_multiline_string:=(c='`');
+                   if in_multiline_string and (not (m_multiline_strings in current_settings.modeswitches)) then
+                     Illegal_Char(c);
+                   repeat
+                     readchar;
+                     case c of
+                       #26 :
+                         end_of_file;
+                       #10,#13 :
+                         if not in_multiline_string then
+                           break;
+                       '''' :
+                         if not in_multiline_string then
                            begin
-                             next_char_loaded:=true;
-                             break;
+                             readchar;
+                             if c<>'''' then
+                              begin
+                                next_char_loaded:=true;
+                                break;
+                              end;
                            end;
-                        end;
-                    end;
-                  until false;
-                end;
+                       '`' :
+                         if in_multiline_string then
+                           begin
+                             readchar;
+                             if c<>'`' then
+                              begin
+                                next_char_loaded:=true;
+                                break;
+                              end;
+                           end;
+                     end;
+                   until false;
+                 end;
              '(' :
                begin
                  if (current_commentstyle=comment_none) then
@@ -4664,9 +4727,15 @@ type
         mac     : tmacro;
         asciinr : string[33];
         iswidestring : boolean;
+        had_newline,first_multiline : boolean;
+        trimcount : word;
+        last_c : char;
       label
-         exit_label;
+        quote_label,exit_label;
       begin
+        had_newline:=false;
+        first_multiline:=false;
+        last_c:=#0;
         flushpendingswitchesstate;
 
         { record tokens? }
@@ -5098,8 +5167,20 @@ type
                  goto exit_label;
                end;
 
-             '''','#','^' :
+             '''','#','^','`' :
                begin
+                 had_multiline_string:=in_multiline_string;
+                 in_multiline_string:=(c='`');
+                 if in_multiline_string then
+                   begin
+                     if not (m_multiline_strings in current_settings.modeswitches) then
+                       Illegal_Char(c)
+                     else
+                       begin
+                         multiline_start_line:=current_filepos.line;
+                         multiline_start_column:=current_filepos.column;
+                       end;
+                   end;
                  len:=0;
                  cstringpattern:='';
                  iswidestring:=false;
@@ -5206,26 +5287,65 @@ type
                            begin
                              if len>=length(cstringpattern) then
                                setlength(cstringpattern,length(cstringpattern)+256);
-                              inc(len);
-                              cstringpattern[len]:=chr(m);
+                             inc(len);
+                             cstringpattern[len]:=chr(m);
                            end;
                        end;
-                     '''' :
+                     '''','`' :
                        begin
+                         had_multiline_string:=in_multiline_string;
+                         in_multiline_string:=(c='`');
+                         first_multiline:=in_multiline_string and (last_c in [#0,#32,#61]);
                          repeat
                            readchar;
-                           case c of
-                             #26 :
-                               end_of_file;
-                             #10,#13 :
-                               Message(scan_f_string_exceeds_line);
-                             '''' :
-                               begin
-                                 readchar;
-                                 if c<>'''' then
-                                  break;
-                               end;
-                           end;
+                           quote_label:
+                             case c of
+                               #26 :
+                                 end_of_file;
+                               #32,#9,#11 :
+                                 if (had_newline or first_multiline) and
+                                    (current_settings.whitespacetrimauto or
+                                    (current_settings.whitespacetrimcount>0)) then
+                                   begin
+                                     if current_settings.whitespacetrimauto then
+                                       trimcount:=multiline_start_column
+                                     else
+                                       trimcount:=current_settings.whitespacetrimcount;
+                                     while (c in [#32,#9,#11]) and (trimcount>0) do
+                                       begin
+                                         readchar;
+                                         dec(trimcount);
+                                       end;
+                                     had_newline:=false;
+                                     first_multiline:=false;
+                                     goto quote_label;
+                                   end;
+                               #10,#13 :
+                                 if not in_multiline_string then
+                                   begin
+                                     if had_multiline_string then
+                                       Message2(scan_f_unterminated_multiline_string,
+                                                tostr(multiline_start_line),
+                                                tostr(multiline_start_column))
+                                     else
+                                       Message(scan_f_string_exceeds_line);
+                                   end;
+                               '''' :
+                                 if not in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'''' then
+                                      break;
+                                   end;
+                               '`' :
+                                 if in_multiline_string then
+                                   begin
+                                     readchar;
+                                     if c<>'`' then
+                                      break;
+                                   end;
+                             end;
+                           first_multiline:=false;
                            { interpret as utf-8 string? }
                            if (ord(c)>=$80) and (current_settings.sourcecodepage=CP_UTF8) then
                              begin
@@ -5300,18 +5420,119 @@ type
                              end
                            else if iswidestring then
                              begin
-                               if current_settings.sourcecodepage=CP_UTF8 then
-                                 concatwidestringchar(patternw,ord(c))
-                               else
-                                 concatwidestringchar(patternw,asciichar2unicode(c))
+                               if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     begin
+                                       case current_settings.lineendingtype of
+                                         le_cr :
+                                           concatwidestringchar(patternw,ord(#13));
+                                         le_crlf :
+                                           begin
+                                             concatwidestringchar(patternw,ord(#13));
+                                             concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_lf :
+                                           concatwidestringchar(patternw,ord(#10));
+                                         le_platform :
+                                           begin
+                                             if target_info.newline=#13 then
+                                               concatwidestringchar(patternw,ord(#13))
+                                             else if target_info.newline=#13#10 then
+                                               begin
+                                                 concatwidestringchar(patternw,ord(#13));
+                                                 concatwidestringchar(patternw,ord(#10));
+                                               end
+                                             else if target_info.newline=#10 then
+                                               concatwidestringchar(patternw,ord(#10));
+                                           end;
+                                         le_source :
+                                           concatwidestringchar(patternw,ord(c));
+                                       end;
+                                     end
+                                   else
+                                     case current_settings.lineendingtype of
+                                       le_cr :
+                                         concatwidestringchar(patternw,asciichar2unicode(#13));
+                                       le_crlf :
+                                         begin
+                                           concatwidestringchar(patternw,asciichar2unicode(#13));
+                                           concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_lf :
+                                         concatwidestringchar(patternw,asciichar2unicode(#10));
+                                       le_platform :
+                                         begin
+                                           if target_info.newline=#13 then
+                                             concatwidestringchar(patternw,asciichar2unicode(#13))
+                                           else if target_info.newline=#13#10 then
+                                             begin
+                                               concatwidestringchar(patternw,asciichar2unicode(#13));
+                                               concatwidestringchar(patternw,asciichar2unicode(#10));
+                                             end
+                                           else if target_info.newline=#10 then
+                                             concatwidestringchar(patternw,asciichar2unicode(#10));
+                                         end;
+                                       le_source :
+                                         concatwidestringchar(patternw,asciichar2unicode(c));
+                                     end;
+                                   had_newline:=true;
+                                   inc(line_no);
+                                 end
+                               else if not (in_multiline_string and (c in [#10,#13])) then
+                                 begin
+                                   if current_settings.sourcecodepage=CP_UTF8 then
+                                     concatwidestringchar(patternw,ord(c))
+                                   else
+                                     concatwidestringchar(patternw,asciichar2unicode(c));
+                                 end;
                              end
                            else
                              begin
-                               if len>=length(cstringpattern) then
-                                 setlength(cstringpattern,length(cstringpattern)+256);
-                                inc(len);
-                                cstringpattern[len]:=c;
+                                if in_multiline_string and (c in [#10,#13]) and (not ((c=#10) and (last_c=#13))) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    case current_settings.lineendingtype of
+                                      le_cr :
+                                        cstringpattern[len]:=#13;
+                                      le_crlf :
+                                        begin
+                                          cstringpattern[len]:=#13;
+                                          inc(len);
+                                          cstringpattern[len]:=#10;
+                                        end;
+                                      le_lf :
+                                        cstringpattern[len]:=#10;
+                                      le_platform :
+                                        begin
+                                          if target_info.newline=#13 then
+                                            cstringpattern[len]:=#13
+                                          else if target_info.newline=#13#10 then
+                                            begin
+                                              cstringpattern[len]:=#13;
+                                              inc(len);
+                                              cstringpattern[len]:=#10;
+                                            end
+                                          else if target_info.newline=#10 then
+                                            cstringpattern[len]:=#10;
+                                        end;
+                                      le_source :
+                                        cstringpattern[len]:=c;
+                                    end;
+                                    had_newline:=true;
+                                    inc(line_no);
+                                  end
+                                else if not (in_multiline_string and (c in [#10,#13])) then
+                                  begin
+                                    if len>=length(cstringpattern) then
+                                      setlength(cstringpattern,length(cstringpattern)+256);
+                                    inc(len);
+                                    cstringpattern[len]:=c;
+                                  end;
                              end;
+                         last_c:=c;
                          until false;
                        end;
                      '^' :
@@ -5338,6 +5559,7 @@ type
                      else
                       break;
                    end;
+                 last_c:=c;
                  until false;
                  { strings with length 1 become const chars }
                  if iswidestring then
@@ -5445,6 +5667,10 @@ exit_label:
         low,high,mid: longint;
         optoken: ttoken;
       begin
+         { Added the assignment to NOTOKEN below because I got a DFA uninitialized result
+           warning when building the compiler with -O3, which broke compilation with -Sew.
+           - Akira1364 }
+         readpreproc:=NOTOKEN;
          skipspace;
          case c of
            '_',
@@ -5480,12 +5706,13 @@ exit_label:
                current_scanner.preproc_pattern:=pattern;
                readpreproc:=optoken;
              end;
-           '''' :
-             begin
-               readquotedstring;
-               current_scanner.preproc_pattern:=cstringpattern;
-               readpreproc:=_CSTRING;
-             end;
+           '''','`' :
+             if not ((c='`') and (not (m_multiline_strings in current_settings.modeswitches))) then
+               begin
+                 readquotedstring;
+                 current_scanner.preproc_pattern:=cstringpattern;
+                 readpreproc:=_CSTRING;
+               end;
            '0'..'9' :
              begin
                readnumber;
diff --git compiler/utils/ppuutils/ppudump.pp compiler/utils/ppuutils/ppudump.pp
index 7009bd8..14f600f 100644
--- compiler/utils/ppuutils/ppudump.pp
+++ compiler/utils/ppuutils/ppudump.pp
@@ -2444,6 +2444,9 @@ const
             else
              ControllerType:=ct_none;
 {$POP}
+           lineendingtype:=tlineendingtype(tokenreadenum(sizeof(tlineendingtype)));
+           whitespacetrimcount:=gettokenbufword;
+           whitespacetrimauto:=boolean(gettokenbufbyte);
            endpos:=tbi;
            if endpos-startpos<>expected_size then
              Writeln(['Wrong size of Settings read-in: ',expected_size,' expected, but got ',endpos-startpos]);

Imants Gulbis

2019-07-18 11:51

reporter   ~0117301

I propose to attach this patch for Lazarus so that every time you use multi line string and press ctrl+space you do not get error invalid modeswitch MultiLineStrings.

lazarus_multiline_strings.pas.patch (993 bytes)
Index: components/codetools/linkscanner.pas
===================================================================
--- components/codetools/linkscanner.pas	(revision 61600)
+++ components/codetools/linkscanner.pas	(working copy)
@@ -224,7 +224,8 @@
     cmsExternalClass,      { pas2js: allow  class external [pkgname] name [symbol] }
     cmsIgnoreAttributes,   { pas2js: ignore attributes }
     cmsOmitRTTI,           { pas2js: 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 }
+    msMultiHelpers,        { off=only one helper per type, on=all }
+    cmsMultiLineStrings    { disable/enable multi line string support in fpc }
     );
   TCompilerModeSwitches = set of TCompilerModeSwitch;
 const
@@ -312,7 +313,8 @@
     'EXTERNALCLASS',
     'IGNOREATTRIBUTES',
     'OMITRTTI',
-    'MULTIHELPERS'
+    'MULTIHELPERS',
+    'MULTILINESTRINGS'
     );
 
 

Akira1364

2019-07-18 22:28

reporter   ~0117312

Thanks Imants! I actually have a patch for SynEdit I intend to submit for Lazarus once this actual patch for the feature in FPC gets reviewed, and your patch for CodeTools would certainly go along well with it.

(CodeTools may or may not eventually need additional tweaks beyond that for "full support", but one thing at a time, I'd say.)

Issue History

Date Modified Username Field Change
2019-07-11 04:01 Akira1364 New Issue
2019-07-11 04:01 Akira1364 File Added: multi_line_strings_main.patch
2019-07-11 04:01 Akira1364 File Added: multi_line_strings_tests.patch
2019-07-11 06:11 Awkward Note Added: 0117160
2019-07-11 06:13 Awkward Note Edited: 0117160 View Revisions
2019-07-11 06:45 Akira1364 Note Added: 0117161
2019-07-11 06:48 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 06:48 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 06:49 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 17:14 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 17:15 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 17:44 Akira1364 Note Added: 0117183
2019-07-11 17:46 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 17:52 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 18:22 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 18:23 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 18:23 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 19:33 SlightlyOutOfPhase Note Added: 0117187
2019-07-11 19:35 Akira1364 Note Edited: 0117161 View Revisions
2019-07-11 19:36 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 19:59 Ryan Joseph Note Added: 0117188
2019-07-11 21:40 Akira1364 Note Edited: 0117183 View Revisions
2019-07-11 22:52 jamie philbrook Note Added: 0117191
2019-07-11 23:00 Ryan Joseph Note Added: 0117192
2019-07-11 23:25 Akira1364 Note Added: 0117194
2019-07-11 23:25 Akira1364 Note Edited: 0117194 View Revisions
2019-07-11 23:26 Akira1364 Note Edited: 0117194 View Revisions
2019-07-11 23:29 Akira1364 Note Edited: 0117194 View Revisions
2019-07-11 23:30 Akira1364 Note Edited: 0117194 View Revisions
2019-07-11 23:32 Ryan Joseph Note Added: 0117195
2019-07-11 23:43 jamie philbrook Note Added: 0117196
2019-07-11 23:50 Ryan Joseph Note Added: 0117197
2019-07-11 23:50 Ryan Joseph Note Edited: 0117192 View Revisions
2019-07-11 23:55 Akira1364 Note Added: 0117198
2019-07-11 23:56 Akira1364 Note Edited: 0117198 View Revisions
2019-07-11 23:57 Akira1364 Note Edited: 0117198 View Revisions
2019-07-12 00:15 SlightlyOutOfPhase Note Added: 0117199
2019-07-12 01:31 Akira1364 Note Edited: 0117198 View Revisions
2019-07-12 02:50 jamie philbrook Note Added: 0117201
2019-07-12 03:01 Ryan Joseph Note Added: 0117202
2019-07-12 03:23 jamie philbrook Note Added: 0117203
2019-07-12 03:29 Ryan Joseph Note Added: 0117204
2019-07-12 03:32 Akira1364 Note Added: 0117205
2019-07-12 03:33 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 03:34 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 03:36 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 03:40 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 03:46 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 04:38 Akira1364 File Added: multi_line_strings_main_rev1.patch
2019-07-12 04:38 Akira1364 File Added: multi_line_strings_tests_rev1.patch
2019-07-12 04:38 Akira1364 Note Added: 0117206
2019-07-12 05:24 Akira1364 Note Edited: 0117206 View Revisions
2019-07-12 05:27 Akira1364 Note Edited: 0117205 View Revisions
2019-07-12 05:29 Akira1364 Note Edited: 0117205 View Revisions
2019-07-13 04:17 Akira1364 File Added: multi_line_strings_tests_rev2.patch
2019-07-13 04:17 Akira1364 File Added: multi_line_strings_main_rev2.patch
2019-07-13 04:17 Akira1364 Note Added: 0117236
2019-07-13 06:19 Akira1364 File Added: multi_line_strings_tests_rev3.patch
2019-07-13 06:19 Akira1364 Note Added: 0117238
2019-07-13 06:24 J. Gareth Moreton File Deleted: multi_line_strings_tests_rev1.patch
2019-07-13 06:24 J. Gareth Moreton File Deleted: multi_line_strings_tests_rev2.patch
2019-07-13 06:24 J. Gareth Moreton File Deleted: multi_line_strings_main.patch
2019-07-13 06:25 J. Gareth Moreton File Deleted: multi_line_strings_main_rev1.patch
2019-07-13 06:25 J. Gareth Moreton File Deleted: multi_line_strings_tests.patch
2019-07-13 06:26 J. Gareth Moreton Note Added: 0117239
2019-07-13 14:39 Akira1364 Note Added: 0117246
2019-07-14 16:32 Ryan Joseph Note Added: 0117258
2019-07-14 22:46 Akira1364 File Added: multi_line_strings_main_rev3.patch
2019-07-14 22:46 Akira1364 File Added: multi_line_strings_tests_rev4.patch
2019-07-14 22:46 Akira1364 Note Added: 0117261
2019-07-14 22:48 Akira1364 Note Edited: 0117261 View Revisions
2019-07-14 22:56 Akira1364 Note Edited: 0117261 View Revisions
2019-07-14 23:38 Akira1364 Note Edited: 0117261 View Revisions
2019-07-15 00:33 Akira1364 Note Edited: 0117261 View Revisions
2019-07-15 15:37 Akira1364 Note Edited: 0117261 View Revisions
2019-07-15 16:12 Akira1364 Note Edited: 0117261 View Revisions
2019-07-15 18:53 Akira1364 Note Edited: 0117261 View Revisions
2019-07-15 18:54 Akira1364 Note Edited: 0117261 View Revisions
2019-07-17 03:49 Akira1364 File Added: multi_line_strings_tests_rev5.patch
2019-07-17 03:49 Akira1364 File Added: multi_line_strings_main_rev4.patch
2019-07-17 03:49 Akira1364 Note Added: 0117278
2019-07-17 03:51 Akira1364 Note Edited: 0117278 View Revisions
2019-07-17 03:55 Akira1364 Note Edited: 0117278 View Revisions
2019-07-17 04:01 Akira1364 Note Edited: 0117278 View Revisions
2019-07-17 04:01 Akira1364 Note Edited: 0117278 View Revisions
2019-07-17 04:07 Akira1364 File Added: multi_line_strings_tests_rev6.patch
2019-07-17 04:24 Akira1364 Note Edited: 0117278 View Revisions
2019-07-17 06:32 Akira1364 Note Edited: 0117278 View Revisions
2019-07-18 05:28 Akira1364 File Added: multi_line_strings_main_rev5.patch
2019-07-18 05:28 Akira1364 Note Added: 0117295
2019-07-18 11:51 Imants Gulbis File Added: lazarus_multiline_strings.pas.patch
2019-07-18 11:51 Imants Gulbis Note Added: 0117301
2019-07-18 22:28 Akira1364 Note Added: 0117312