1 //===-- lib/Parser/prescan.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "prescan.h"
10 #include "preprocessor.h"
11 #include "token-sequence.h"
12 #include "flang/Common/idioms.h"
13 #include "flang/Parser/characters.h"
14 #include "flang/Parser/message.h"
15 #include "flang/Parser/source.h"
16 #include "llvm/Support/raw_ostream.h"
17 #include <cstddef>
18 #include <cstring>
19 #include <utility>
20 #include <vector>
21 
22 namespace Fortran::parser {
23 
24 using common::LanguageFeature;
25 
26 static constexpr int maxPrescannerNesting{100};
27 
28 Prescanner::Prescanner(Messages &messages, CookedSource &cooked,
29     Preprocessor &preprocessor, common::LanguageFeatureControl lfc)
30     : messages_{messages}, cooked_{cooked}, preprocessor_{preprocessor},
31       features_{lfc}, encoding_{cooked.allSources().encoding()} {}
32 
33 Prescanner::Prescanner(const Prescanner &that)
34     : messages_{that.messages_}, cooked_{that.cooked_},
35       preprocessor_{that.preprocessor_}, features_{that.features_},
36       inFixedForm_{that.inFixedForm_},
37       fixedFormColumnLimit_{that.fixedFormColumnLimit_},
38       encoding_{that.encoding_}, prescannerNesting_{that.prescannerNesting_ +
39                                      1},
40       skipLeadingAmpersand_{that.skipLeadingAmpersand_},
41       compilerDirectiveBloomFilter_{that.compilerDirectiveBloomFilter_},
42       compilerDirectiveSentinels_{that.compilerDirectiveSentinels_} {}
43 
44 static inline constexpr bool IsFixedFormCommentChar(char ch) {
45   return ch == '!' || ch == '*' || ch == 'C' || ch == 'c';
46 }
47 
48 static void NormalizeCompilerDirectiveCommentMarker(TokenSequence &dir) {
49   char *p{dir.GetMutableCharData()};
50   char *limit{p + dir.SizeInChars()};
51   for (; p < limit; ++p) {
52     if (*p != ' ') {
53       CHECK(IsFixedFormCommentChar(*p));
54       *p = '!';
55       return;
56     }
57   }
58   DIE("compiler directive all blank");
59 }
60 
61 void Prescanner::Prescan(ProvenanceRange range) {
62   AllSources &allSources{cooked_.allSources()};
63   startProvenance_ = range.start();
64   std::size_t offset{0};
65   const SourceFile *source{allSources.GetSourceFile(startProvenance_, &offset)};
66   CHECK(source);
67   start_ = source->content().data() + offset;
68   limit_ = start_ + range.size();
69   nextLine_ = start_;
70   const bool beganInFixedForm{inFixedForm_};
71   if (prescannerNesting_ > maxPrescannerNesting) {
72     Say(GetProvenance(start_),
73         "too many nested INCLUDE/#include files, possibly circular"_err_en_US);
74     return;
75   }
76   while (nextLine_ < limit_) {
77     Statement();
78   }
79   if (inFixedForm_ != beganInFixedForm) {
80     std::string dir{"!dir$ "};
81     if (beganInFixedForm) {
82       dir += "fixed";
83     } else {
84       dir += "free";
85     }
86     dir += '\n';
87     TokenSequence tokens{dir, allSources.AddCompilerInsertion(dir).start()};
88     tokens.Emit(cooked_);
89   }
90 }
91 
92 void Prescanner::Statement() {
93   TokenSequence tokens;
94   LineClassification line{ClassifyLine(nextLine_)};
95   switch (line.kind) {
96   case LineClassification::Kind::Comment:
97     nextLine_ += line.payloadOffset; // advance to '!' or newline
98     NextLine();
99     return;
100   case LineClassification::Kind::IncludeLine:
101     FortranInclude(nextLine_ + line.payloadOffset);
102     NextLine();
103     return;
104   case LineClassification::Kind::ConditionalCompilationDirective:
105   case LineClassification::Kind::IncludeDirective:
106   case LineClassification::Kind::DefinitionDirective:
107   case LineClassification::Kind::PreprocessorDirective:
108     preprocessor_.Directive(TokenizePreprocessorDirective(), this);
109     return;
110   case LineClassification::Kind::CompilerDirective:
111     directiveSentinel_ = line.sentinel;
112     CHECK(InCompilerDirective());
113     BeginSourceLineAndAdvance();
114     if (inFixedForm_) {
115       CHECK(IsFixedFormCommentChar(*at_));
116     } else {
117       while (*at_ == ' ' || *at_ == '\t') {
118         ++at_, ++column_;
119       }
120       CHECK(*at_ == '!');
121     }
122     if (directiveSentinel_[0] == '$' && directiveSentinel_[1] == '\0') {
123       // OpenMP conditional compilation line.  Remove the sentinel and then
124       // treat the line as if it were normal source.
125       at_ += 2, column_ += 2;
126       if (inFixedForm_) {
127         LabelField(tokens);
128       } else {
129         SkipSpaces();
130       }
131     } else {
132       // Compiler directive.  Emit normalized sentinel.
133       EmitChar(tokens, '!');
134       ++at_, ++column_;
135       for (const char *sp{directiveSentinel_}; *sp != '\0';
136            ++sp, ++at_, ++column_) {
137         EmitChar(tokens, *sp);
138       }
139       if (*at_ == ' ') {
140         EmitChar(tokens, ' ');
141         ++at_, ++column_;
142       }
143       tokens.CloseToken();
144     }
145     break;
146   case LineClassification::Kind::Source:
147     BeginSourceLineAndAdvance();
148     if (inFixedForm_) {
149       LabelField(tokens);
150     } else if (skipLeadingAmpersand_) {
151       skipLeadingAmpersand_ = false;
152       const char *p{SkipWhiteSpace(at_)};
153       if (p < limit_ && *p == '&') {
154         column_ += ++p - at_;
155         at_ = p;
156       }
157     } else {
158       SkipSpaces();
159     }
160     break;
161   }
162 
163   while (NextToken(tokens)) {
164   }
165 
166   Provenance newlineProvenance{GetCurrentProvenance()};
167   if (std::optional<TokenSequence> preprocessed{
168           preprocessor_.MacroReplacement(tokens, *this)}) {
169     // Reprocess the preprocessed line.  Append a newline temporarily.
170     preprocessed->PutNextTokenChar('\n', newlineProvenance);
171     preprocessed->CloseToken();
172     const char *ppd{preprocessed->ToCharBlock().begin()};
173     LineClassification ppl{ClassifyLine(ppd)};
174     preprocessed->RemoveLastToken(); // remove the newline
175     switch (ppl.kind) {
176     case LineClassification::Kind::Comment:
177       break;
178     case LineClassification::Kind::IncludeLine:
179       FortranInclude(ppd + ppl.payloadOffset);
180       break;
181     case LineClassification::Kind::ConditionalCompilationDirective:
182     case LineClassification::Kind::IncludeDirective:
183     case LineClassification::Kind::DefinitionDirective:
184     case LineClassification::Kind::PreprocessorDirective:
185       Say(preprocessed->GetProvenanceRange(),
186           "Preprocessed line resembles a preprocessor directive"_en_US);
187       preprocessed->ToLowerCase().Emit(cooked_);
188       break;
189     case LineClassification::Kind::CompilerDirective:
190       if (preprocessed->HasRedundantBlanks()) {
191         preprocessed->RemoveRedundantBlanks();
192       }
193       NormalizeCompilerDirectiveCommentMarker(*preprocessed);
194       preprocessed->ToLowerCase();
195       SourceFormChange(preprocessed->ToString());
196       preprocessed->ClipComment(true /* skip first ! */).Emit(cooked_);
197       break;
198     case LineClassification::Kind::Source:
199       if (inFixedForm_) {
200         if (preprocessed->HasBlanks(/*after column*/ 6)) {
201           preprocessed->RemoveBlanks(/*after column*/ 6);
202         }
203       } else {
204         if (preprocessed->HasRedundantBlanks()) {
205           preprocessed->RemoveRedundantBlanks();
206         }
207       }
208       preprocessed->ToLowerCase().ClipComment().Emit(cooked_);
209       break;
210     }
211   } else {
212     tokens.ToLowerCase();
213     if (line.kind == LineClassification::Kind::CompilerDirective) {
214       SourceFormChange(tokens.ToString());
215     }
216     tokens.Emit(cooked_);
217   }
218   if (omitNewline_) {
219     omitNewline_ = false;
220   } else {
221     cooked_.Put('\n', newlineProvenance);
222   }
223   directiveSentinel_ = nullptr;
224 }
225 
226 TokenSequence Prescanner::TokenizePreprocessorDirective() {
227   CHECK(nextLine_ < limit_ && !inPreprocessorDirective_);
228   inPreprocessorDirective_ = true;
229   BeginSourceLineAndAdvance();
230   TokenSequence tokens;
231   while (NextToken(tokens)) {
232   }
233   inPreprocessorDirective_ = false;
234   return tokens;
235 }
236 
237 void Prescanner::NextLine() {
238   void *vstart{static_cast<void *>(const_cast<char *>(nextLine_))};
239   void *v{std::memchr(vstart, '\n', limit_ - nextLine_)};
240   if (!v) {
241     nextLine_ = limit_;
242   } else {
243     const char *nl{const_cast<const char *>(static_cast<char *>(v))};
244     nextLine_ = nl + 1;
245   }
246 }
247 
248 void Prescanner::LabelField(TokenSequence &token, int outCol) {
249   for (; *at_ != '\n' && column_ <= 6; ++at_) {
250     if (*at_ == '\t') {
251       ++at_;
252       column_ = 7;
253       break;
254     }
255     if (*at_ != ' ' &&
256         !(*at_ == '0' && column_ == 6)) { // '0' in column 6 becomes space
257       EmitChar(token, *at_);
258       ++outCol;
259     }
260     ++column_;
261   }
262   if (outCol > 1) {
263     token.CloseToken();
264   }
265   if (outCol < 7) {
266     if (outCol == 1) {
267       token.Put("      ", 6, sixSpaceProvenance_.start());
268     } else {
269       for (; outCol < 7; ++outCol) {
270         token.PutNextTokenChar(' ', spaceProvenance_);
271       }
272       token.CloseToken();
273     }
274   }
275 }
276 
277 void Prescanner::SkipToEndOfLine() {
278   while (*at_ != '\n') {
279     ++at_, ++column_;
280   }
281 }
282 
283 bool Prescanner::MustSkipToEndOfLine() const {
284   if (inFixedForm_ && column_ > fixedFormColumnLimit_ && !tabInCurrentLine_) {
285     return true; // skip over ignored columns in right margin (73:80)
286   } else if (*at_ == '!' && !inCharLiteral_) {
287     return true; // inline comment goes to end of source line
288   } else {
289     return false;
290   }
291 }
292 
293 void Prescanner::NextChar() {
294   CHECK(*at_ != '\n');
295   ++at_, ++column_;
296   while (at_[0] == '\xef' && at_[1] == '\xbb' && at_[2] == '\xbf') {
297     // UTF-8 byte order mark - treat this file as UTF-8
298     at_ += 3;
299     encoding_ = Encoding::UTF_8;
300   }
301   if (inPreprocessorDirective_) {
302     SkipCComments();
303   } else {
304     bool mightNeedSpace{false};
305     if (MustSkipToEndOfLine()) {
306       SkipToEndOfLine();
307     } else {
308       mightNeedSpace = *at_ == '\n';
309     }
310     for (; Continuation(mightNeedSpace); mightNeedSpace = false) {
311       if (MustSkipToEndOfLine()) {
312         SkipToEndOfLine();
313       }
314     }
315     if (*at_ == '\t') {
316       tabInCurrentLine_ = true;
317     }
318   }
319 }
320 
321 void Prescanner::SkipCComments() {
322   while (true) {
323     if (IsCComment(at_)) {
324       if (const char *after{SkipCComment(at_)}) {
325         column_ += after - at_;
326         // May have skipped over one or more newlines; relocate the start of
327         // the next line.
328         nextLine_ = at_ = after;
329         NextLine();
330       } else {
331         // Don't emit any messages about unclosed C-style comments, because
332         // the sequence /* can appear legally in a FORMAT statement.  There's
333         // no ambiguity, since the sequence */ cannot appear legally.
334         break;
335       }
336     } else if (inPreprocessorDirective_ && at_[0] == '\\' && at_ + 2 < limit_ &&
337         at_[1] == '\n' && nextLine_ < limit_) {
338       BeginSourceLineAndAdvance();
339     } else {
340       break;
341     }
342   }
343 }
344 
345 void Prescanner::SkipSpaces() {
346   while (*at_ == ' ' || *at_ == '\t') {
347     NextChar();
348   }
349   insertASpace_ = false;
350 }
351 
352 const char *Prescanner::SkipWhiteSpace(const char *p) {
353   while (*p == ' ' || *p == '\t') {
354     ++p;
355   }
356   return p;
357 }
358 
359 const char *Prescanner::SkipWhiteSpaceAndCComments(const char *p) const {
360   while (true) {
361     if (*p == ' ' || *p == '\t') {
362       ++p;
363     } else if (IsCComment(p)) {
364       if (const char *after{SkipCComment(p)}) {
365         p = after;
366       } else {
367         break;
368       }
369     } else {
370       break;
371     }
372   }
373   return p;
374 }
375 
376 const char *Prescanner::SkipCComment(const char *p) const {
377   char star{' '}, slash{' '};
378   p += 2;
379   while (star != '*' || slash != '/') {
380     if (p >= limit_) {
381       return nullptr; // signifies an unterminated comment
382     }
383     star = slash;
384     slash = *p++;
385   }
386   return p;
387 }
388 
389 bool Prescanner::NextToken(TokenSequence &tokens) {
390   CHECK(at_ >= start_ && at_ < limit_);
391   if (InFixedFormSource()) {
392     SkipSpaces();
393   } else {
394     if (*at_ == '/' && IsCComment(at_)) {
395       // Recognize and skip over classic C style /*comments*/ when
396       // outside a character literal.
397       if (features_.ShouldWarn(LanguageFeature::ClassicCComments)) {
398         Say(GetProvenance(at_), "nonstandard usage: C-style comment"_en_US);
399       }
400       SkipCComments();
401     }
402     if (*at_ == ' ' || *at_ == '\t') {
403       // Compress free-form white space into a single space character.
404       const auto theSpace{at_};
405       char previous{at_ <= start_ ? ' ' : at_[-1]};
406       NextChar();
407       SkipSpaces();
408       if (*at_ == '\n') {
409         // Discard white space at the end of a line.
410       } else if (!inPreprocessorDirective_ &&
411           (previous == '(' || *at_ == '(' || *at_ == ')')) {
412         // Discard white space before/after '(' and before ')', unless in a
413         // preprocessor directive.  This helps yield space-free contiguous
414         // names for generic interfaces like OPERATOR( + ) and
415         // READ ( UNFORMATTED ), without misinterpreting #define f (notAnArg).
416         // This has the effect of silently ignoring the illegal spaces in
417         // the array constructor ( /1,2/ ) but that seems benign; it's
418         // hard to avoid that while still removing spaces from OPERATOR( / )
419         // and OPERATOR( // ).
420       } else {
421         // Preserve the squashed white space as a single space character.
422         tokens.PutNextTokenChar(' ', GetProvenance(theSpace));
423         tokens.CloseToken();
424         return true;
425       }
426     }
427   }
428   if (insertASpace_) {
429     tokens.PutNextTokenChar(' ', spaceProvenance_);
430     insertASpace_ = false;
431   }
432   if (*at_ == '\n') {
433     return false;
434   }
435   const char *start{at_};
436   if (*at_ == '\'' || *at_ == '"') {
437     QuotedCharacterLiteral(tokens, start);
438     preventHollerith_ = false;
439   } else if (IsDecimalDigit(*at_)) {
440     int n{0}, digits{0};
441     static constexpr int maxHollerith{256 /*lines*/ * (132 - 6 /*columns*/)};
442     do {
443       if (n < maxHollerith) {
444         n = 10 * n + DecimalDigitValue(*at_);
445       }
446       EmitCharAndAdvance(tokens, *at_);
447       ++digits;
448       if (InFixedFormSource()) {
449         SkipSpaces();
450       }
451     } while (IsDecimalDigit(*at_));
452     if ((*at_ == 'h' || *at_ == 'H') && n > 0 && n < maxHollerith &&
453         !preventHollerith_) {
454       Hollerith(tokens, n, start);
455     } else if (*at_ == '.') {
456       while (IsDecimalDigit(EmitCharAndAdvance(tokens, *at_))) {
457       }
458       ExponentAndKind(tokens);
459     } else if (ExponentAndKind(tokens)) {
460     } else if (digits == 1 && n == 0 && (*at_ == 'x' || *at_ == 'X') &&
461         inPreprocessorDirective_) {
462       do {
463         EmitCharAndAdvance(tokens, *at_);
464       } while (IsHexadecimalDigit(*at_));
465     } else if (IsLetter(*at_)) {
466       // Handles FORMAT(3I9HHOLLERITH) by skipping over the first I so that
467       // we don't misrecognize I9HOLLERITH as an identifier in the next case.
468       EmitCharAndAdvance(tokens, *at_);
469     } else if (at_[0] == '_' && (at_[1] == '\'' || at_[1] == '"')) {
470       EmitCharAndAdvance(tokens, *at_);
471       QuotedCharacterLiteral(tokens, start);
472     }
473     preventHollerith_ = false;
474   } else if (*at_ == '.') {
475     char nch{EmitCharAndAdvance(tokens, '.')};
476     if (!inPreprocessorDirective_ && IsDecimalDigit(nch)) {
477       while (IsDecimalDigit(EmitCharAndAdvance(tokens, *at_))) {
478       }
479       ExponentAndKind(tokens);
480     } else if (nch == '.' && EmitCharAndAdvance(tokens, '.') == '.') {
481       EmitCharAndAdvance(tokens, '.'); // variadic macro definition ellipsis
482     }
483     preventHollerith_ = false;
484   } else if (IsLegalInIdentifier(*at_)) {
485     do {
486     } while (IsLegalInIdentifier(EmitCharAndAdvance(tokens, *at_)));
487     if (*at_ == '\'' || *at_ == '"') {
488       QuotedCharacterLiteral(tokens, start);
489       preventHollerith_ = false;
490     } else {
491       // Subtle: Don't misrecognize labeled DO statement label as Hollerith
492       // when the loop control variable starts with 'H'.
493       preventHollerith_ = true;
494     }
495   } else if (*at_ == '*') {
496     if (EmitCharAndAdvance(tokens, '*') == '*') {
497       EmitCharAndAdvance(tokens, '*');
498     } else {
499       // Subtle ambiguity:
500       //  CHARACTER*2H     declares H because *2 is a kind specifier
501       //  DATAC/N*2H  /    is repeated Hollerith
502       preventHollerith_ = !slashInCurrentLine_;
503     }
504   } else {
505     char ch{*at_};
506     if (ch == '(' || ch == '[') {
507       ++delimiterNesting_;
508     } else if ((ch == ')' || ch == ']') && delimiterNesting_ > 0) {
509       --delimiterNesting_;
510     }
511     char nch{EmitCharAndAdvance(tokens, ch)};
512     preventHollerith_ = false;
513     if ((nch == '=' &&
514             (ch == '<' || ch == '>' || ch == '/' || ch == '=' || ch == '!')) ||
515         (ch == nch &&
516             (ch == '/' || ch == ':' || ch == '*' || ch == '#' || ch == '&' ||
517                 ch == '|' || ch == '<' || ch == '>')) ||
518         (ch == '=' && nch == '>')) {
519       // token comprises two characters
520       EmitCharAndAdvance(tokens, nch);
521     } else if (ch == '/') {
522       slashInCurrentLine_ = true;
523     }
524   }
525   tokens.CloseToken();
526   return true;
527 }
528 
529 bool Prescanner::ExponentAndKind(TokenSequence &tokens) {
530   char ed{ToLowerCaseLetter(*at_)};
531   if (ed != 'e' && ed != 'd') {
532     return false;
533   }
534   EmitCharAndAdvance(tokens, ed);
535   if (*at_ == '+' || *at_ == '-') {
536     EmitCharAndAdvance(tokens, *at_);
537   }
538   while (IsDecimalDigit(*at_)) {
539     EmitCharAndAdvance(tokens, *at_);
540   }
541   if (*at_ == '_') {
542     while (IsLegalInIdentifier(EmitCharAndAdvance(tokens, *at_))) {
543     }
544   }
545   return true;
546 }
547 
548 void Prescanner::QuotedCharacterLiteral(
549     TokenSequence &tokens, const char *start) {
550   char quote{*at_};
551   const char *end{at_ + 1};
552   inCharLiteral_ = true;
553   const auto emit{[&](char ch) { EmitChar(tokens, ch); }};
554   const auto insert{[&](char ch) { EmitInsertedChar(tokens, ch); }};
555   bool isEscaped{false};
556   bool escapesEnabled{features_.IsEnabled(LanguageFeature::BackslashEscapes)};
557   while (true) {
558     if (*at_ == '\\') {
559       if (escapesEnabled) {
560         isEscaped = !isEscaped;
561       } else {
562         // The parser always processes escape sequences, so don't confuse it
563         // when escapes are disabled.
564         insert('\\');
565       }
566     } else {
567       isEscaped = false;
568     }
569     EmitQuotedChar(static_cast<unsigned char>(*at_), emit, insert, false,
570         Encoding::LATIN_1);
571     while (PadOutCharacterLiteral(tokens)) {
572     }
573     if (*at_ == '\n') {
574       if (!inPreprocessorDirective_) {
575         Say(GetProvenanceRange(start, end),
576             "Incomplete character literal"_err_en_US);
577       }
578       break;
579     }
580     end = at_ + 1;
581     NextChar();
582     if (*at_ == quote && !isEscaped) {
583       // A doubled unescaped quote mark becomes a single instance of that
584       // quote character in the literal (later).  There can be spaces between
585       // the quotes in fixed form source.
586       EmitChar(tokens, quote);
587       inCharLiteral_ = false; // for cases like print *, '...'!comment
588       NextChar();
589       if (InFixedFormSource()) {
590         SkipSpaces();
591       }
592       if (*at_ != quote) {
593         break;
594       }
595       inCharLiteral_ = true;
596     }
597   }
598   inCharLiteral_ = false;
599 }
600 
601 void Prescanner::Hollerith(
602     TokenSequence &tokens, int count, const char *start) {
603   inCharLiteral_ = true;
604   CHECK(*at_ == 'h' || *at_ == 'H');
605   EmitChar(tokens, 'H');
606   while (count-- > 0) {
607     if (PadOutCharacterLiteral(tokens)) {
608     } else if (*at_ == '\n') {
609       Say(GetProvenanceRange(start, at_),
610           "Possible truncated Hollerith literal"_en_US);
611       break;
612     } else {
613       NextChar();
614       // Each multi-byte character encoding counts as a single character.
615       // No escape sequences are recognized.
616       // Hollerith is always emitted to the cooked character
617       // stream in UTF-8.
618       DecodedCharacter decoded{DecodeCharacter(
619           encoding_, at_, static_cast<std::size_t>(limit_ - at_), false)};
620       if (decoded.bytes > 0) {
621         EncodedCharacter utf8{
622             EncodeCharacter<Encoding::UTF_8>(decoded.codepoint)};
623         for (int j{0}; j < utf8.bytes; ++j) {
624           EmitChar(tokens, utf8.buffer[j]);
625         }
626         at_ += decoded.bytes - 1;
627       } else {
628         Say(GetProvenanceRange(start, at_),
629             "Bad character in Hollerith literal"_err_en_US);
630         break;
631       }
632     }
633   }
634   if (*at_ != '\n') {
635     NextChar();
636   }
637   inCharLiteral_ = false;
638 }
639 
640 // In fixed form, source card images must be processed as if they were at
641 // least 72 columns wide, at least in character literal contexts.
642 bool Prescanner::PadOutCharacterLiteral(TokenSequence &tokens) {
643   while (inFixedForm_ && !tabInCurrentLine_ && at_[1] == '\n') {
644     if (column_ < fixedFormColumnLimit_) {
645       tokens.PutNextTokenChar(' ', spaceProvenance_);
646       ++column_;
647       return true;
648     }
649     if (!FixedFormContinuation(false /*no need to insert space*/) ||
650         tabInCurrentLine_) {
651       return false;
652     }
653     CHECK(column_ == 7);
654     --at_; // point to column 6 of continuation line
655     column_ = 6;
656   }
657   return false;
658 }
659 
660 bool Prescanner::IsFixedFormCommentLine(const char *start) const {
661   const char *p{start};
662   if (IsFixedFormCommentChar(*p) || *p == '%' || // VAX %list, %eject, &c.
663       ((*p == 'D' || *p == 'd') &&
664           !features_.IsEnabled(LanguageFeature::OldDebugLines))) {
665     return true;
666   }
667   bool anyTabs{false};
668   while (true) {
669     if (*p == ' ') {
670       ++p;
671     } else if (*p == '\t') {
672       anyTabs = true;
673       ++p;
674     } else if (*p == '0' && !anyTabs && p == start + 5) {
675       ++p; // 0 in column 6 must treated as a space
676     } else {
677       break;
678     }
679   }
680   if (!anyTabs && p >= start + fixedFormColumnLimit_) {
681     return true;
682   }
683   if (*p == '!' && !inCharLiteral_ && (anyTabs || p != start + 5)) {
684     return true;
685   }
686   return *p == '\n';
687 }
688 
689 const char *Prescanner::IsFreeFormComment(const char *p) const {
690   p = SkipWhiteSpaceAndCComments(p);
691   if (*p == '!' || *p == '\n') {
692     return p;
693   } else {
694     return nullptr;
695   }
696 }
697 
698 std::optional<std::size_t> Prescanner::IsIncludeLine(const char *start) const {
699   const char *p{SkipWhiteSpace(start)};
700   for (char ch : "include"s) {
701     if (ToLowerCaseLetter(*p++) != ch) {
702       return std::nullopt;
703     }
704   }
705   p = SkipWhiteSpace(p);
706   if (*p == '"' || *p == '\'') {
707     return {p - start};
708   }
709   return std::nullopt;
710 }
711 
712 void Prescanner::FortranInclude(const char *firstQuote) {
713   const char *p{firstQuote};
714   while (*p != '"' && *p != '\'') {
715     ++p;
716   }
717   char quote{*p};
718   std::string path;
719   for (++p; *p != '\n'; ++p) {
720     if (*p == quote) {
721       if (p[1] != quote) {
722         break;
723       }
724       ++p;
725     }
726     path += *p;
727   }
728   if (*p != quote) {
729     Say(GetProvenanceRange(firstQuote, p),
730         "malformed path name string"_err_en_US);
731     return;
732   }
733   p = SkipWhiteSpace(p + 1);
734   if (*p != '\n' && *p != '!') {
735     const char *garbage{p};
736     for (; *p != '\n' && *p != '!'; ++p) {
737     }
738     Say(GetProvenanceRange(garbage, p),
739         "excess characters after path name"_en_US);
740   }
741   std::string buf;
742   llvm::raw_string_ostream error{buf};
743   Provenance provenance{GetProvenance(nextLine_)};
744   AllSources &allSources{cooked_.allSources()};
745   const SourceFile *currentFile{allSources.GetSourceFile(provenance)};
746   if (currentFile) {
747     allSources.PushSearchPathDirectory(DirectoryName(currentFile->path()));
748   }
749   const SourceFile *included{allSources.Open(path, error)};
750   if (currentFile) {
751     allSources.PopSearchPathDirectory();
752   }
753   if (!included) {
754     Say(provenance, "INCLUDE: %s"_err_en_US, error.str());
755   } else if (included->bytes() > 0) {
756     ProvenanceRange includeLineRange{
757         provenance, static_cast<std::size_t>(p - nextLine_)};
758     ProvenanceRange fileRange{
759         allSources.AddIncludedFile(*included, includeLineRange)};
760     Prescanner{*this}.set_encoding(included->encoding()).Prescan(fileRange);
761   }
762 }
763 
764 const char *Prescanner::IsPreprocessorDirectiveLine(const char *start) const {
765   const char *p{start};
766   for (; *p == ' '; ++p) {
767   }
768   if (*p == '#') {
769     if (inFixedForm_ && p == start + 5) {
770       return nullptr;
771     }
772   } else {
773     p = SkipWhiteSpace(p);
774     if (*p != '#') {
775       return nullptr;
776     }
777   }
778   return SkipWhiteSpace(p + 1);
779 }
780 
781 bool Prescanner::IsNextLinePreprocessorDirective() const {
782   return IsPreprocessorDirectiveLine(nextLine_) != nullptr;
783 }
784 
785 bool Prescanner::SkipCommentLine(bool afterAmpersand) {
786   if (nextLine_ >= limit_) {
787     if (afterAmpersand && prescannerNesting_ > 0) {
788       // A continuation marker at the end of the last line in an
789       // include file inhibits the newline for that line.
790       SkipToEndOfLine();
791       omitNewline_ = true;
792     }
793     return false;
794   }
795   auto lineClass{ClassifyLine(nextLine_)};
796   if (lineClass.kind == LineClassification::Kind::Comment) {
797     NextLine();
798     return true;
799   } else if (inPreprocessorDirective_) {
800     return false;
801   } else if (lineClass.kind ==
802           LineClassification::Kind::ConditionalCompilationDirective ||
803       lineClass.kind == LineClassification::Kind::PreprocessorDirective) {
804     // Allow conditional compilation directives (e.g., #ifdef) to affect
805     // continuation lines.
806     // Allow other preprocessor directives, too, except #include
807     // (when it does not follow '&'), #define, and #undef (because
808     // they cannot be allowed to affect preceding text on a
809     // continued line).
810     preprocessor_.Directive(TokenizePreprocessorDirective(), this);
811     return true;
812   } else if (afterAmpersand &&
813       (lineClass.kind == LineClassification::Kind::IncludeDirective ||
814           lineClass.kind == LineClassification::Kind::IncludeLine)) {
815     SkipToEndOfLine();
816     omitNewline_ = true;
817     skipLeadingAmpersand_ = true;
818     return false;
819   } else {
820     return false;
821   }
822 }
823 
824 const char *Prescanner::FixedFormContinuationLine(bool mightNeedSpace) {
825   if (nextLine_ >= limit_) {
826     return nullptr;
827   }
828   tabInCurrentLine_ = false;
829   char col1{*nextLine_};
830   if (InCompilerDirective()) {
831     // Must be a continued compiler directive.
832     if (!IsFixedFormCommentChar(col1)) {
833       return nullptr;
834     }
835     int j{1};
836     for (; j < 5; ++j) {
837       char ch{directiveSentinel_[j - 1]};
838       if (ch == '\0') {
839         break;
840       }
841       if (ch != ToLowerCaseLetter(nextLine_[j])) {
842         return nullptr;
843       }
844     }
845     for (; j < 5; ++j) {
846       if (nextLine_[j] != ' ') {
847         return nullptr;
848       }
849     }
850     char col6{nextLine_[5]};
851     if (col6 != '\n' && col6 != '\t' && col6 != ' ' && col6 != '0') {
852       if (nextLine_[6] != ' ' && mightNeedSpace) {
853         insertASpace_ = true;
854       }
855       return nextLine_ + 6;
856     }
857     return nullptr;
858   } else {
859     // Normal case: not in a compiler directive.
860     if (col1 == '&' &&
861         features_.IsEnabled(
862             LanguageFeature::FixedFormContinuationWithColumn1Ampersand)) {
863       // Extension: '&' as continuation marker
864       if (features_.ShouldWarn(
865               LanguageFeature::FixedFormContinuationWithColumn1Ampersand)) {
866         Say(GetProvenance(nextLine_), "nonstandard usage"_en_US);
867       }
868       return nextLine_ + 1;
869     }
870     if (col1 == '\t' && nextLine_[1] >= '1' && nextLine_[1] <= '9') {
871       tabInCurrentLine_ = true;
872       return nextLine_ + 2; // VAX extension
873     }
874     if (col1 == ' ' && nextLine_[1] == ' ' && nextLine_[2] == ' ' &&
875         nextLine_[3] == ' ' && nextLine_[4] == ' ') {
876       char col6{nextLine_[5]};
877       if (col6 != '\n' && col6 != '\t' && col6 != ' ' && col6 != '0') {
878         return nextLine_ + 6;
879       }
880     }
881     if (delimiterNesting_ > 0) {
882       if (!IsFixedFormCommentChar(col1)) {
883         return nextLine_;
884       }
885     }
886   }
887   return nullptr; // not a continuation line
888 }
889 
890 const char *Prescanner::FreeFormContinuationLine(bool ampersand) {
891   const char *p{nextLine_};
892   if (p >= limit_) {
893     return nullptr;
894   }
895   p = SkipWhiteSpace(p);
896   if (InCompilerDirective()) {
897     if (*p++ != '!') {
898       return nullptr;
899     }
900     for (const char *s{directiveSentinel_}; *s != '\0'; ++p, ++s) {
901       if (*s != ToLowerCaseLetter(*p)) {
902         return nullptr;
903       }
904     }
905     p = SkipWhiteSpace(p);
906     if (*p == '&') {
907       if (!ampersand) {
908         insertASpace_ = true;
909       }
910       return p + 1;
911     } else if (ampersand) {
912       return p;
913     } else {
914       return nullptr;
915     }
916   } else {
917     if (*p == '&') {
918       return p + 1;
919     } else if (*p == '!' || *p == '\n' || *p == '#') {
920       return nullptr;
921     } else if (ampersand || delimiterNesting_ > 0) {
922       if (p > nextLine_) {
923         --p;
924       } else {
925         insertASpace_ = true;
926       }
927       return p;
928     } else {
929       return nullptr;
930     }
931   }
932 }
933 
934 bool Prescanner::FixedFormContinuation(bool mightNeedSpace) {
935   // N.B. We accept '&' as a continuation indicator in fixed form, too,
936   // but not in a character literal.
937   if (*at_ == '&' && inCharLiteral_) {
938     return false;
939   }
940   do {
941     if (const char *cont{FixedFormContinuationLine(mightNeedSpace)}) {
942       BeginSourceLine(cont);
943       column_ = 7;
944       NextLine();
945       return true;
946     }
947   } while (SkipCommentLine(false /* not after ampersand */));
948   return false;
949 }
950 
951 bool Prescanner::FreeFormContinuation() {
952   const char *p{at_};
953   bool ampersand{*p == '&'};
954   if (ampersand) {
955     p = SkipWhiteSpace(p + 1);
956   }
957   if (*p != '\n') {
958     if (inCharLiteral_) {
959       return false;
960     } else if (*p != '!' &&
961         features_.ShouldWarn(LanguageFeature::CruftAfterAmpersand)) {
962       Say(GetProvenance(p), "missing ! before comment after &"_en_US);
963     }
964   }
965   do {
966     if (const char *cont{FreeFormContinuationLine(ampersand)}) {
967       BeginSourceLine(cont);
968       NextLine();
969       return true;
970     }
971   } while (SkipCommentLine(ampersand));
972   return false;
973 }
974 
975 bool Prescanner::Continuation(bool mightNeedFixedFormSpace) {
976   if (*at_ == '\n' || *at_ == '&') {
977     if (inFixedForm_) {
978       return FixedFormContinuation(mightNeedFixedFormSpace);
979     } else {
980       return FreeFormContinuation();
981     }
982   } else {
983     return false;
984   }
985 }
986 
987 std::optional<Prescanner::LineClassification>
988 Prescanner::IsFixedFormCompilerDirectiveLine(const char *start) const {
989   const char *p{start};
990   char col1{*p++};
991   if (!IsFixedFormCommentChar(col1)) {
992     return std::nullopt;
993   }
994   char sentinel[5], *sp{sentinel};
995   int column{2};
996   for (; column < 6; ++column, ++p) {
997     if (*p != ' ') {
998       if (*p == '\n' || *p == '\t') {
999         break;
1000       }
1001       if (sp == sentinel + 1 && sentinel[0] == '$' && IsDecimalDigit(*p)) {
1002         // OpenMP conditional compilation line: leave the label alone
1003         break;
1004       }
1005       *sp++ = ToLowerCaseLetter(*p);
1006     }
1007   }
1008   if (column == 6) {
1009     if (*p == ' ' || *p == '\t' || *p == '0') {
1010       ++p;
1011     } else {
1012       // This is a Continuation line, not an initial directive line.
1013       return std::nullopt;
1014     }
1015   }
1016   if (sp == sentinel) {
1017     return std::nullopt;
1018   }
1019   *sp = '\0';
1020   if (const char *ss{IsCompilerDirectiveSentinel(sentinel)}) {
1021     std::size_t payloadOffset = p - start;
1022     return {LineClassification{
1023         LineClassification::Kind::CompilerDirective, payloadOffset, ss}};
1024   }
1025   return std::nullopt;
1026 }
1027 
1028 std::optional<Prescanner::LineClassification>
1029 Prescanner::IsFreeFormCompilerDirectiveLine(const char *start) const {
1030   char sentinel[8];
1031   const char *p{SkipWhiteSpace(start)};
1032   if (*p++ != '!') {
1033     return std::nullopt;
1034   }
1035   for (std::size_t j{0}; j + 1 < sizeof sentinel; ++p, ++j) {
1036     if (*p == '\n') {
1037       break;
1038     }
1039     if (*p == ' ' || *p == '\t' || *p == '&') {
1040       if (j == 0) {
1041         break;
1042       }
1043       sentinel[j] = '\0';
1044       p = SkipWhiteSpace(p + 1);
1045       if (*p == '!') {
1046         break;
1047       }
1048       if (const char *sp{IsCompilerDirectiveSentinel(sentinel)}) {
1049         std::size_t offset = p - start;
1050         return {LineClassification{
1051             LineClassification::Kind::CompilerDirective, offset, sp}};
1052       }
1053       break;
1054     }
1055     sentinel[j] = ToLowerCaseLetter(*p);
1056   }
1057   return std::nullopt;
1058 }
1059 
1060 Prescanner &Prescanner::AddCompilerDirectiveSentinel(const std::string &dir) {
1061   std::uint64_t packed{0};
1062   for (char ch : dir) {
1063     packed = (packed << 8) | (ToLowerCaseLetter(ch) & 0xff);
1064   }
1065   compilerDirectiveBloomFilter_.set(packed % prime1);
1066   compilerDirectiveBloomFilter_.set(packed % prime2);
1067   compilerDirectiveSentinels_.insert(dir);
1068   return *this;
1069 }
1070 
1071 const char *Prescanner::IsCompilerDirectiveSentinel(
1072     const char *sentinel) const {
1073   std::uint64_t packed{0};
1074   std::size_t n{0};
1075   for (; sentinel[n] != '\0'; ++n) {
1076     packed = (packed << 8) | (sentinel[n] & 0xff);
1077   }
1078   if (n == 0 || !compilerDirectiveBloomFilter_.test(packed % prime1) ||
1079       !compilerDirectiveBloomFilter_.test(packed % prime2)) {
1080     return nullptr;
1081   }
1082   const auto iter{compilerDirectiveSentinels_.find(std::string(sentinel, n))};
1083   return iter == compilerDirectiveSentinels_.end() ? nullptr : iter->c_str();
1084 }
1085 
1086 Prescanner::LineClassification Prescanner::ClassifyLine(
1087     const char *start) const {
1088   if (inFixedForm_) {
1089     if (std::optional<LineClassification> lc{
1090             IsFixedFormCompilerDirectiveLine(start)}) {
1091       return std::move(*lc);
1092     }
1093     if (IsFixedFormCommentLine(start)) {
1094       return {LineClassification::Kind::Comment};
1095     }
1096   } else {
1097     if (std::optional<LineClassification> lc{
1098             IsFreeFormCompilerDirectiveLine(start)}) {
1099       return std::move(*lc);
1100     }
1101     if (const char *bang{IsFreeFormComment(start)}) {
1102       return {LineClassification::Kind::Comment,
1103           static_cast<std::size_t>(bang - start)};
1104     }
1105   }
1106   if (std::optional<std::size_t> quoteOffset{IsIncludeLine(start)}) {
1107     return {LineClassification::Kind::IncludeLine, *quoteOffset};
1108   }
1109   if (const char *dir{IsPreprocessorDirectiveLine(start)}) {
1110     if (std::memcmp(dir, "if", 2) == 0 || std::memcmp(dir, "elif", 4) == 0 ||
1111         std::memcmp(dir, "else", 4) == 0 || std::memcmp(dir, "endif", 5) == 0) {
1112       return {LineClassification::Kind::ConditionalCompilationDirective};
1113     } else if (std::memcmp(dir, "include", 7) == 0) {
1114       return {LineClassification::Kind::IncludeDirective};
1115     } else if (std::memcmp(dir, "define", 6) == 0 ||
1116         std::memcmp(dir, "undef", 5) == 0) {
1117       return {LineClassification::Kind::DefinitionDirective};
1118     } else {
1119       return {LineClassification::Kind::PreprocessorDirective};
1120     }
1121   }
1122   return {LineClassification::Kind::Source};
1123 }
1124 
1125 void Prescanner::SourceFormChange(std::string &&dir) {
1126   if (dir == "!dir$ free") {
1127     inFixedForm_ = false;
1128   } else if (dir == "!dir$ fixed") {
1129     inFixedForm_ = true;
1130   }
1131 }
1132 } // namespace Fortran::parser
1133