diff options
author | CarolineConcatto <51754594+CarolineConcatto@users.noreply.github.com> | 2020-02-25 15:11:52 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-25 07:11:52 -0800 |
commit | 64ab3302d5a130c00b66a6957b2e7f0c9b9c537d (patch) | |
tree | 7658afe96fac3d6dabd9c6aa2b471294c3ddfa7c /flang/lib/Parser/preprocessor.cpp | |
parent | 456a61d188e9cdf43bd44e28e11708773d838798 (diff) | |
download | llvm-64ab3302d5a130c00b66a6957b2e7f0c9b9c537d.zip llvm-64ab3302d5a130c00b66a6957b2e7f0c9b9c537d.tar.gz llvm-64ab3302d5a130c00b66a6957b2e7f0c9b9c537d.tar.bz2 |
[flang] [LLVMify F18] Compiler module folders should have capitalised names (flang-compiler/f18#980)
This patch renames the modules in f18 to use a capital letter in the
module name
Signed-off-by: Caroline Concatto <caroline.concatto@arm.com>
Original-commit: flang-compiler/f18@d2eb7a1c443d1539ef12b6f027074a0eb15b1ea0
Reviewed-on: https://github.com/flang-compiler/f18/pull/980
Diffstat (limited to 'flang/lib/Parser/preprocessor.cpp')
-rw-r--r-- | flang/lib/Parser/preprocessor.cpp | 1015 |
1 files changed, 1015 insertions, 0 deletions
diff --git a/flang/lib/Parser/preprocessor.cpp b/flang/lib/Parser/preprocessor.cpp new file mode 100644 index 0000000..f825f56 --- /dev/null +++ b/flang/lib/Parser/preprocessor.cpp @@ -0,0 +1,1015 @@ +//===-- lib/Parser/preprocessor.cpp ---------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "preprocessor.h" +#include "prescan.h" +#include "flang/Common/idioms.h" +#include "flang/Parser/characters.h" +#include "flang/Parser/message.h" +#include <algorithm> +#include <cinttypes> +#include <cstddef> +#include <ctime> +#include <map> +#include <memory> +#include <optional> +#include <set> +#include <sstream> +#include <utility> + +namespace Fortran::parser { + +Definition::Definition( + const TokenSequence &repl, std::size_t firstToken, std::size_t tokens) + : replacement_{Tokenize({}, repl, firstToken, tokens)} {} + +Definition::Definition(const std::vector<std::string> &argNames, + const TokenSequence &repl, std::size_t firstToken, std::size_t tokens, + bool isVariadic) + : isFunctionLike_{true}, + argumentCount_(argNames.size()), isVariadic_{isVariadic}, + replacement_{Tokenize(argNames, repl, firstToken, tokens)} {} + +Definition::Definition(const std::string &predefined, AllSources &sources) + : isPredefined_{true}, replacement_{predefined, + sources.AddCompilerInsertion(predefined).start()} { +} + +bool Definition::set_isDisabled(bool disable) { + bool was{isDisabled_}; + isDisabled_ = disable; + return was; +} + +static bool IsLegalIdentifierStart(const CharBlock &cpl) { + return cpl.size() > 0 && IsLegalIdentifierStart(cpl[0]); +} + +TokenSequence Definition::Tokenize(const std::vector<std::string> &argNames, + const TokenSequence &token, std::size_t firstToken, std::size_t tokens) { + std::map<std::string, std::string> args; + char argIndex{'A'}; + for (const std::string &arg : argNames) { + CHECK(args.find(arg) == args.end()); + args[arg] = "~"s + argIndex++; + } + TokenSequence result; + for (std::size_t j{0}; j < tokens; ++j) { + CharBlock tok{token.TokenAt(firstToken + j)}; + if (IsLegalIdentifierStart(tok)) { + auto it{args.find(tok.ToString())}; + if (it != args.end()) { + result.Put(it->second, token.GetTokenProvenance(j)); + continue; + } + } + result.Put(token, firstToken + j, 1); + } + return result; +} + +static std::size_t AfterLastNonBlank(const TokenSequence &tokens) { + for (std::size_t j{tokens.SizeInTokens()}; j > 0; --j) { + if (!tokens.TokenAt(j - 1).IsBlank()) { + return j; + } + } + return 0; +} + +static TokenSequence Stringify( + const TokenSequence &tokens, AllSources &allSources) { + TokenSequence result; + Provenance quoteProvenance{allSources.CompilerInsertionProvenance('"')}; + result.PutNextTokenChar('"', quoteProvenance); + for (std::size_t j{0}; j < tokens.SizeInTokens(); ++j) { + const CharBlock &token{tokens.TokenAt(j)}; + std::size_t bytes{token.size()}; + for (std::size_t k{0}; k < bytes; ++k) { + char ch{token[k]}; + Provenance from{tokens.GetTokenProvenance(j, k)}; + if (ch == '"' || ch == '\\') { + result.PutNextTokenChar(ch, from); + } + result.PutNextTokenChar(ch, from); + } + } + result.PutNextTokenChar('"', quoteProvenance); + result.CloseToken(); + return result; +} + +TokenSequence Definition::Apply( + const std::vector<TokenSequence> &args, AllSources &allSources) { + TokenSequence result; + bool pasting{false}; + bool skipping{false}; + int parenthesesNesting{0}; + std::size_t tokens{replacement_.SizeInTokens()}; + for (std::size_t j{0}; j < tokens; ++j) { + const CharBlock &token{replacement_.TokenAt(j)}; + std::size_t bytes{token.size()}; + if (skipping) { + if (bytes == 1) { + if (token[0] == '(') { + ++parenthesesNesting; + } else if (token[0] == ')') { + skipping = --parenthesesNesting > 0; + } + } + continue; + } + if (bytes == 2 && token[0] == '~') { + std::size_t index = token[1] - 'A'; + if (index >= args.size()) { + continue; + } + std::size_t afterLastNonBlank{AfterLastNonBlank(result)}; + if (afterLastNonBlank > 0 && + result.TokenAt(afterLastNonBlank - 1).ToString() == "#") { + // stringifying + while (result.SizeInTokens() >= afterLastNonBlank) { + result.pop_back(); + } + result.Put(Stringify(args[index], allSources)); + } else { + std::size_t argTokens{args[index].SizeInTokens()}; + for (std::size_t k{0}; k < argTokens; ++k) { + if (!pasting || !args[index].TokenAt(k).IsBlank()) { + result.Put(args[index], k); + pasting = false; + } + } + } + } else if (bytes == 2 && token[0] == '#' && token[1] == '#') { + // Token pasting operator in body (not expanded argument); discard any + // immediately preceding white space, then reopen the last token. + while (!result.empty() && + result.TokenAt(result.SizeInTokens() - 1).IsBlank()) { + result.pop_back(); + } + if (!result.empty()) { + result.ReopenLastToken(); + pasting = true; + } + } else if (pasting && token.IsBlank()) { + // Delete whitespace immediately following ## in the body. + } else if (bytes == 11 && isVariadic_ && + token.ToString() == "__VA_ARGS__") { + Provenance commaProvenance{allSources.CompilerInsertionProvenance(',')}; + for (std::size_t k{argumentCount_}; k < args.size(); ++k) { + if (k > argumentCount_) { + result.Put(","s, commaProvenance); + } + result.Put(args[k]); + } + } else if (bytes == 10 && isVariadic_ && token.ToString() == "__VA_OPT__" && + j + 2 < tokens && replacement_.TokenAt(j + 1).ToString() == "(" && + parenthesesNesting == 0) { + parenthesesNesting = 1; + skipping = args.size() == argumentCount_; + ++j; + } else { + if (bytes == 1 && parenthesesNesting > 0 && token[0] == '(') { + ++parenthesesNesting; + } else if (bytes == 1 && parenthesesNesting > 0 && token[0] == ')') { + if (--parenthesesNesting == 0) { + skipping = false; + continue; + } + } + result.Put(replacement_, j); + } + } + return result; +} + +static std::string FormatTime(const std::time_t &now, const char *format) { + char buffer[16]; + return {buffer, + std::strftime(buffer, sizeof buffer, format, std::localtime(&now))}; +} + +Preprocessor::Preprocessor(AllSources &allSources) : allSources_{allSources} { + // Capture current local date & time once now to avoid having the values + // of __DATE__ or __TIME__ change during compilation. + std::time_t now; + std::time(&now); + definitions_.emplace(SaveTokenAsName("__DATE__"s), // e.g., "Jun 16 1904" + Definition{FormatTime(now, "\"%h %e %Y\""), allSources}); + definitions_.emplace(SaveTokenAsName("__TIME__"s), // e.g., "23:59:60" + Definition{FormatTime(now, "\"%T\""), allSources}); + // The values of these predefined macros depend on their invocation sites. + definitions_.emplace( + SaveTokenAsName("__FILE__"s), Definition{"__FILE__"s, allSources}); + definitions_.emplace( + SaveTokenAsName("__LINE__"s), Definition{"__LINE__"s, allSources}); +} + +void Preprocessor::Define(std::string macro, std::string value) { + definitions_.emplace(SaveTokenAsName(macro), Definition{value, allSources_}); +} + +void Preprocessor::Undefine(std::string macro) { definitions_.erase(macro); } + +std::optional<TokenSequence> Preprocessor::MacroReplacement( + const TokenSequence &input, const Prescanner &prescanner) { + // Do quick scan for any use of a defined name. + std::size_t tokens{input.SizeInTokens()}; + std::size_t j; + for (j = 0; j < tokens; ++j) { + CharBlock token{input.TokenAt(j)}; + if (!token.empty() && IsLegalIdentifierStart(token[0]) && + IsNameDefined(token)) { + break; + } + } + if (j == tokens) { + return std::nullopt; // input contains nothing that would be replaced + } + TokenSequence result{input, 0, j}; + for (; j < tokens; ++j) { + const CharBlock &token{input.TokenAt(j)}; + if (token.IsBlank() || !IsLegalIdentifierStart(token[0])) { + result.Put(input, j); + continue; + } + auto it{definitions_.find(token)}; + if (it == definitions_.end()) { + result.Put(input, j); + continue; + } + Definition &def{it->second}; + if (def.isDisabled()) { + result.Put(input, j); + continue; + } + if (!def.isFunctionLike()) { + if (def.isPredefined()) { + std::string name{def.replacement().TokenAt(0).ToString()}; + std::string repl; + if (name == "__FILE__") { + repl = "\""s + + allSources_.GetPath(prescanner.GetCurrentProvenance()) + '"'; + } else if (name == "__LINE__") { + std::stringstream ss; + ss << allSources_.GetLineNumber(prescanner.GetCurrentProvenance()); + repl = ss.str(); + } + if (!repl.empty()) { + ProvenanceRange insert{allSources_.AddCompilerInsertion(repl)}; + ProvenanceRange call{allSources_.AddMacroCall( + insert, input.GetTokenProvenanceRange(j), repl)}; + result.Put(repl, call.start()); + continue; + } + } + def.set_isDisabled(true); + TokenSequence replaced{ReplaceMacros(def.replacement(), prescanner)}; + def.set_isDisabled(false); + if (!replaced.empty()) { + ProvenanceRange from{def.replacement().GetProvenanceRange()}; + ProvenanceRange use{input.GetTokenProvenanceRange(j)}; + ProvenanceRange newRange{ + allSources_.AddMacroCall(from, use, replaced.ToString())}; + result.Put(replaced, newRange); + } + continue; + } + // Possible function-like macro call. Skip spaces and newlines to see + // whether '(' is next. + std::size_t k{j}; + bool leftParen{false}; + while (++k < tokens) { + const CharBlock &lookAhead{input.TokenAt(k)}; + if (!lookAhead.IsBlank() && lookAhead[0] != '\n') { + leftParen = lookAhead[0] == '(' && lookAhead.size() == 1; + break; + } + } + if (!leftParen) { + result.Put(input, j); + continue; + } + std::vector<std::size_t> argStart{++k}; + for (int nesting{0}; k < tokens; ++k) { + CharBlock token{input.TokenAt(k)}; + if (token.size() == 1) { + char ch{token[0]}; + if (ch == '(') { + ++nesting; + } else if (ch == ')') { + if (nesting == 0) { + break; + } + --nesting; + } else if (ch == ',' && nesting == 0) { + argStart.push_back(k + 1); + } + } + } + if (argStart.size() == 1 && k == argStart[0] && def.argumentCount() == 0) { + // Subtle: () is zero arguments, not one empty argument, + // unless one argument was expected. + argStart.clear(); + } + if (k >= tokens || argStart.size() < def.argumentCount() || + (argStart.size() > def.argumentCount() && !def.isVariadic())) { + result.Put(input, j); + continue; + } + std::vector<TokenSequence> args; + for (std::size_t n{0}; n < argStart.size(); ++n) { + std::size_t at{argStart[n]}; + std::size_t count{ + (n + 1 == argStart.size() ? k : argStart[n + 1] - 1) - at}; + args.emplace_back(TokenSequence(input, at, count)); + } + def.set_isDisabled(true); + TokenSequence replaced{ + ReplaceMacros(def.Apply(args, allSources_), prescanner)}; + def.set_isDisabled(false); + if (!replaced.empty()) { + ProvenanceRange from{def.replacement().GetProvenanceRange()}; + ProvenanceRange use{input.GetIntervalProvenanceRange(j, k - j)}; + ProvenanceRange newRange{ + allSources_.AddMacroCall(from, use, replaced.ToString())}; + result.Put(replaced, newRange); + } + j = k; // advance to the terminal ')' + } + return result; +} + +TokenSequence Preprocessor::ReplaceMacros( + const TokenSequence &tokens, const Prescanner &prescanner) { + if (std::optional<TokenSequence> repl{MacroReplacement(tokens, prescanner)}) { + return std::move(*repl); + } + return tokens; +} + +void Preprocessor::Directive(const TokenSequence &dir, Prescanner *prescanner) { + std::size_t tokens{dir.SizeInTokens()}; + std::size_t j{dir.SkipBlanks(0)}; + if (j == tokens) { + return; + } + if (dir.TokenAt(j).ToString() != "#") { + prescanner->Say(dir.GetTokenProvenanceRange(j), "missing '#'"_err_en_US); + return; + } + j = dir.SkipBlanks(j + 1); + while (tokens > 0 && dir.TokenAt(tokens - 1).IsBlank()) { + --tokens; + } + if (j == tokens) { + return; + } + if (IsDecimalDigit(dir.TokenAt(j)[0]) || dir.TokenAt(j)[0] == '"') { + return; // treat like #line, ignore it + } + std::size_t dirOffset{j}; + std::string dirName{ToLowerCaseLetters(dir.TokenAt(dirOffset).ToString())}; + j = dir.SkipBlanks(j + 1); + CharBlock nameToken; + if (j < tokens && IsLegalIdentifierStart(dir.TokenAt(j)[0])) { + nameToken = dir.TokenAt(j); + } + if (dirName == "line") { + // #line is ignored + } else if (dirName == "define") { + if (nameToken.empty()) { + prescanner->Say(dir.GetTokenProvenanceRange(j < tokens ? j : tokens - 1), + "#define: missing or invalid name"_err_en_US); + return; + } + nameToken = SaveTokenAsName(nameToken); + definitions_.erase(nameToken); + if (++j < tokens && dir.TokenAt(j).size() == 1 && + dir.TokenAt(j)[0] == '(') { + j = dir.SkipBlanks(j + 1); + std::vector<std::string> argName; + bool isVariadic{false}; + if (dir.TokenAt(j).ToString() != ")") { + while (true) { + std::string an{dir.TokenAt(j).ToString()}; + if (an == "...") { + isVariadic = true; + } else { + if (an.empty() || !IsLegalIdentifierStart(an[0])) { + prescanner->Say(dir.GetTokenProvenanceRange(j), + "#define: missing or invalid argument name"_err_en_US); + return; + } + argName.push_back(an); + } + j = dir.SkipBlanks(j + 1); + if (j == tokens) { + prescanner->Say(dir.GetTokenProvenanceRange(tokens - 1), + "#define: malformed argument list"_err_en_US); + return; + } + std::string punc{dir.TokenAt(j).ToString()}; + if (punc == ")") { + break; + } + if (isVariadic || punc != ",") { + prescanner->Say(dir.GetTokenProvenanceRange(j), + "#define: malformed argument list"_err_en_US); + return; + } + j = dir.SkipBlanks(j + 1); + if (j == tokens) { + prescanner->Say(dir.GetTokenProvenanceRange(tokens - 1), + "#define: malformed argument list"_err_en_US); + return; + } + } + if (std::set<std::string>(argName.begin(), argName.end()).size() != + argName.size()) { + prescanner->Say(dir.GetTokenProvenance(dirOffset), + "#define: argument names are not distinct"_err_en_US); + return; + } + } + j = dir.SkipBlanks(j + 1); + definitions_.emplace(std::make_pair( + nameToken, Definition{argName, dir, j, tokens - j, isVariadic})); + } else { + j = dir.SkipBlanks(j + 1); + definitions_.emplace( + std::make_pair(nameToken, Definition{dir, j, tokens - j})); + } + } else if (dirName == "undef") { + if (nameToken.empty()) { + prescanner->Say( + dir.GetIntervalProvenanceRange(dirOffset, tokens - dirOffset), + "# missing or invalid name"_err_en_US); + } else { + j = dir.SkipBlanks(j + 1); + if (j != tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#undef: excess tokens at end of directive"_err_en_US); + } else { + definitions_.erase(nameToken); + } + } + } else if (dirName == "ifdef" || dirName == "ifndef") { + bool doThen{false}; + if (nameToken.empty()) { + prescanner->Say( + dir.GetIntervalProvenanceRange(dirOffset, tokens - dirOffset), + "#%s: missing name"_err_en_US, dirName); + } else { + j = dir.SkipBlanks(j + 1); + if (j != tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#%s: excess tokens at end of directive"_en_US, dirName); + } + doThen = IsNameDefined(nameToken) == (dirName == "ifdef"); + } + if (doThen) { + ifStack_.push(CanDeadElseAppear::Yes); + } else { + SkipDisabledConditionalCode(dirName, IsElseActive::Yes, prescanner, + dir.GetTokenProvenance(dirOffset)); + } + } else if (dirName == "if") { + if (IsIfPredicateTrue(dir, j, tokens - j, prescanner)) { + ifStack_.push(CanDeadElseAppear::Yes); + } else { + SkipDisabledConditionalCode(dirName, IsElseActive::Yes, prescanner, + dir.GetTokenProvenanceRange(dirOffset)); + } + } else if (dirName == "else") { + if (j != tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#else: excess tokens at end of directive"_err_en_US); + } else if (ifStack_.empty()) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#else: not nested within #if, #ifdef, or #ifndef"_err_en_US); + } else if (ifStack_.top() != CanDeadElseAppear::Yes) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#else: already appeared within this #if, #ifdef, or #ifndef"_err_en_US); + } else { + ifStack_.pop(); + SkipDisabledConditionalCode("else", IsElseActive::No, prescanner, + dir.GetTokenProvenanceRange(dirOffset)); + } + } else if (dirName == "elif") { + if (ifStack_.empty()) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#elif: not nested within #if, #ifdef, or #ifndef"_err_en_US); + } else if (ifStack_.top() != CanDeadElseAppear::Yes) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#elif: #else previously appeared within this #if, #ifdef, or #ifndef"_err_en_US); + } else { + ifStack_.pop(); + SkipDisabledConditionalCode("elif", IsElseActive::No, prescanner, + dir.GetTokenProvenanceRange(dirOffset)); + } + } else if (dirName == "endif") { + if (j != tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#endif: excess tokens at end of directive"_err_en_US); + } else if (ifStack_.empty()) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#endif: no #if, #ifdef, or #ifndef"_err_en_US); + } else { + ifStack_.pop(); + } + } else if (dirName == "error") { + prescanner->Say( + dir.GetIntervalProvenanceRange(dirOffset, tokens - dirOffset), + "%s"_err_en_US, dir.ToString()); + } else if (dirName == "warning" || dirName == "comment" || + dirName == "note") { + prescanner->Say( + dir.GetIntervalProvenanceRange(dirOffset, tokens - dirOffset), + "%s"_en_US, dir.ToString()); + } else if (dirName == "include") { + if (j == tokens) { + prescanner->Say( + dir.GetIntervalProvenanceRange(dirOffset, tokens - dirOffset), + "#include: missing name of file to include"_err_en_US); + return; + } + std::string include; + if (dir.TokenAt(j).ToString() == "<") { + std::size_t k{j + 1}; + if (k >= tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#include: file name missing"_err_en_US); + return; + } + while (k < tokens && dir.TokenAt(k) != ">") { + ++k; + } + if (k >= tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(j, tokens - j), + "#include: expected '>' at end of included file"_en_US); + } else if (k + 1 < tokens) { + prescanner->Say(dir.GetIntervalProvenanceRange(k + 1, tokens - k - 1), + "#include: extra stuff ignored after '>'"_en_US); + } + TokenSequence braced{dir, j + 1, k - j - 1}; + include = ReplaceMacros(braced, *prescanner).ToString(); + } else if (j + 1 == tokens && + (include = dir.TokenAt(j).ToString()).substr(0, 1) == "\"" && + include.substr(include.size() - 1, 1) == "\"") { + include = include.substr(1, include.size() - 2); + } else { + prescanner->Say(dir.GetTokenProvenanceRange(j < tokens ? j : tokens - 1), + "#include: expected name of file to include"_err_en_US); + return; + } + if (include.empty()) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#include: empty include file name"_err_en_US); + return; + } + std::stringstream error; + const SourceFile *included{allSources_.Open(include, &error)}; + if (!included) { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#include: %s"_err_en_US, error.str()); + } else if (included->bytes() > 0) { + ProvenanceRange fileRange{ + allSources_.AddIncludedFile(*included, dir.GetProvenanceRange())}; + Prescanner{*prescanner} + .set_encoding(included->encoding()) + .Prescan(fileRange); + } + } else { + prescanner->Say(dir.GetTokenProvenanceRange(dirOffset), + "#%s: unknown or unimplemented directive"_err_en_US, dirName); + } +} + +CharBlock Preprocessor::SaveTokenAsName(const CharBlock &t) { + names_.push_back(t.ToString()); + return {names_.back().data(), names_.back().size()}; +} + +bool Preprocessor::IsNameDefined(const CharBlock &token) { + return definitions_.find(token) != definitions_.end(); +} + +static std::string GetDirectiveName( + const TokenSequence &line, std::size_t *rest) { + std::size_t tokens{line.SizeInTokens()}; + std::size_t j{line.SkipBlanks(0)}; + if (j == tokens || line.TokenAt(j).ToString() != "#") { + *rest = tokens; + return ""; + } + j = line.SkipBlanks(j + 1); + if (j == tokens) { + *rest = tokens; + return ""; + } + *rest = line.SkipBlanks(j + 1); + return ToLowerCaseLetters(line.TokenAt(j).ToString()); +} + +void Preprocessor::SkipDisabledConditionalCode(const std::string &dirName, + IsElseActive isElseActive, Prescanner *prescanner, + ProvenanceRange provenanceRange) { + int nesting{0}; + while (!prescanner->IsAtEnd()) { + if (!prescanner->IsNextLinePreprocessorDirective()) { + prescanner->NextLine(); + continue; + } + TokenSequence line{prescanner->TokenizePreprocessorDirective()}; + std::size_t rest{0}; + std::string dn{GetDirectiveName(line, &rest)}; + if (dn == "ifdef" || dn == "ifndef" || dn == "if") { + ++nesting; + } else if (dn == "endif") { + if (nesting-- == 0) { + return; + } + } else if (isElseActive == IsElseActive::Yes && nesting == 0) { + if (dn == "else") { + ifStack_.push(CanDeadElseAppear::No); + return; + } + if (dn == "elif" && + IsIfPredicateTrue( + line, rest, line.SizeInTokens() - rest, prescanner)) { + ifStack_.push(CanDeadElseAppear::Yes); + return; + } + } + } + prescanner->Say(provenanceRange, "#%s: missing #endif"_err_en_US, dirName); +} + +// Precedence level codes used here to accommodate mixed Fortran and C: +// 15: parentheses and constants, logical !, bitwise ~ +// 14: unary + and - +// 13: ** +// 12: *, /, % (modulus) +// 11: + and - +// 10: << and >> +// 9: bitwise & +// 8: bitwise ^ +// 7: bitwise | +// 6: relations (.EQ., ==, &c.) +// 5: .NOT. +// 4: .AND., && +// 3: .OR., || +// 2: .EQV. and .NEQV. / .XOR. +// 1: ? : +// 0: , +static std::int64_t ExpressionValue(const TokenSequence &token, + int minimumPrecedence, std::size_t *atToken, + std::optional<Message> *error) { + enum Operator { + PARENS, + CONST, + NOTZERO, // ! + COMPLEMENT, // ~ + UPLUS, + UMINUS, + POWER, + TIMES, + DIVIDE, + MODULUS, + ADD, + SUBTRACT, + LEFTSHIFT, + RIGHTSHIFT, + BITAND, + BITXOR, + BITOR, + LT, + LE, + EQ, + NE, + GE, + GT, + NOT, + AND, + OR, + EQV, + NEQV, + SELECT, + COMMA + }; + static const int precedence[]{ + 15, 15, 15, 15, // (), 6, !, ~ + 14, 14, // unary +, - + 13, 12, 12, 12, 11, 11, 10, 10, // **, *, /, %, +, -, <<, >> + 9, 8, 7, // &, ^, | + 6, 6, 6, 6, 6, 6, // relations .LT. to .GT. + 5, 4, 3, 2, 2, // .NOT., .AND., .OR., .EQV., .NEQV. + 1, 0 // ?: and , + }; + static const int operandPrecedence[]{0, -1, 15, 15, 15, 15, 13, 12, 12, 12, + 11, 11, 11, 11, 9, 8, 7, 7, 7, 7, 7, 7, 7, 6, 4, 3, 3, 3, 1, 0}; + + static std::map<std::string, enum Operator> opNameMap; + if (opNameMap.empty()) { + opNameMap["("] = PARENS; + opNameMap["!"] = NOTZERO; + opNameMap["~"] = COMPLEMENT; + opNameMap["**"] = POWER; + opNameMap["*"] = TIMES; + opNameMap["/"] = DIVIDE; + opNameMap["%"] = MODULUS; + opNameMap["+"] = ADD; + opNameMap["-"] = SUBTRACT; + opNameMap["<<"] = LEFTSHIFT; + opNameMap[">>"] = RIGHTSHIFT; + opNameMap["&"] = BITAND; + opNameMap["^"] = BITXOR; + opNameMap["|"] = BITOR; + opNameMap[".lt."] = opNameMap["<"] = LT; + opNameMap[".le."] = opNameMap["<="] = LE; + opNameMap[".eq."] = opNameMap["=="] = EQ; + opNameMap[".ne."] = opNameMap["/="] = opNameMap["!="] = NE; + opNameMap[".ge."] = opNameMap[">="] = GE; + opNameMap[".gt."] = opNameMap[">"] = GT; + opNameMap[".not."] = NOT; + opNameMap[".and."] = opNameMap[".a."] = opNameMap["&&"] = AND; + opNameMap[".or."] = opNameMap[".o."] = opNameMap["||"] = OR; + opNameMap[".eqv."] = EQV; + opNameMap[".neqv."] = opNameMap[".xor."] = opNameMap[".x."] = NEQV; + opNameMap["?"] = SELECT; + opNameMap[","] = COMMA; + } + + std::size_t tokens{token.SizeInTokens()}; + CHECK(tokens > 0); + if (*atToken >= tokens) { + *error = + Message{token.GetProvenanceRange(), "incomplete expression"_err_en_US}; + return 0; + } + + // Parse and evaluate a primary or a unary operator and its operand. + std::size_t opAt{*atToken}; + std::string t{token.TokenAt(opAt).ToString()}; + enum Operator op; + std::int64_t left{0}; + if (t == "(") { + op = PARENS; + } else if (IsDecimalDigit(t[0])) { + op = CONST; + std::size_t consumed{0}; + left = std::stoll(t, &consumed, 0 /*base to be detected*/); + if (consumed < t.size()) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "Uninterpretable numeric constant '%s'"_err_en_US, t}; + return 0; + } + } else if (IsLegalIdentifierStart(t[0])) { + // undefined macro name -> zero + // TODO: BOZ constants? + op = CONST; + } else if (t == "+") { + op = UPLUS; + } else if (t == "-") { + op = UMINUS; + } else if (t == "." && *atToken + 2 < tokens && + ToLowerCaseLetters(token.TokenAt(*atToken + 1).ToString()) == "not" && + token.TokenAt(*atToken + 2).ToString() == ".") { + op = NOT; + *atToken += 2; + } else { + auto it{opNameMap.find(t)}; + if (it != opNameMap.end()) { + op = it->second; + } else { + *error = Message{token.GetTokenProvenanceRange(opAt), + "operand expected in expression"_err_en_US}; + return 0; + } + } + if (precedence[op] < minimumPrecedence) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "operator precedence error"_err_en_US}; + return 0; + } + ++*atToken; + if (op != CONST) { + left = ExpressionValue(token, operandPrecedence[op], atToken, error); + if (*error) { + return 0; + } + switch (op) { + case PARENS: + if (*atToken < tokens && token.TokenAt(*atToken).ToString() == ")") { + ++*atToken; + break; + } + if (*atToken >= tokens) { + *error = Message{token.GetProvenanceRange(), + "')' missing from expression"_err_en_US}; + } else { + *error = Message{ + token.GetTokenProvenanceRange(*atToken), "expected ')'"_err_en_US}; + } + return 0; + case NOTZERO: left = !left; break; + case COMPLEMENT: left = ~left; break; + case UPLUS: break; + case UMINUS: left = -left; break; + case NOT: left = -!left; break; + default: CRASH_NO_CASE; + } + } + + // Parse and evaluate binary operators and their second operands, if present. + while (*atToken < tokens) { + int advance{1}; + t = token.TokenAt(*atToken).ToString(); + if (t == "." && *atToken + 2 < tokens && + token.TokenAt(*atToken + 2).ToString() == ".") { + t += ToLowerCaseLetters(token.TokenAt(*atToken + 1).ToString()) + '.'; + advance = 3; + } + auto it{opNameMap.find(t)}; + if (it == opNameMap.end()) { + break; + } + op = it->second; + if (op < POWER || precedence[op] < minimumPrecedence) { + break; + } + opAt = *atToken; + *atToken += advance; + + std::int64_t right{ + ExpressionValue(token, operandPrecedence[op], atToken, error)}; + if (*error) { + return 0; + } + + switch (op) { + case POWER: + if (left == 0) { + if (right < 0) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "0 ** negative power"_err_en_US}; + } + } else if (left != 1 && right != 1) { + if (right <= 0) { + left = !right; + } else { + std::int64_t power{1}; + for (; right > 0; --right) { + if ((power * left) / left != power) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "overflow in exponentation"_err_en_US}; + left = 1; + } + power *= left; + } + left = power; + } + } + break; + case TIMES: + if (left != 0 && right != 0 && ((left * right) / left) != right) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "overflow in multiplication"_err_en_US}; + } + left = left * right; + break; + case DIVIDE: + if (right == 0) { + *error = Message{ + token.GetTokenProvenanceRange(opAt), "division by zero"_err_en_US}; + left = 0; + } else { + left = left / right; + } + break; + case MODULUS: + if (right == 0) { + *error = Message{ + token.GetTokenProvenanceRange(opAt), "modulus by zero"_err_en_US}; + left = 0; + } else { + left = left % right; + } + break; + case ADD: + if ((left < 0) == (right < 0) && (left < 0) != (left + right < 0)) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "overflow in addition"_err_en_US}; + } + left = left + right; + break; + case SUBTRACT: + if ((left < 0) != (right < 0) && (left < 0) == (left - right < 0)) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "overflow in subtraction"_err_en_US}; + } + left = left - right; + break; + case LEFTSHIFT: + if (right < 0 || right > 64) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "bad left shift count"_err_en_US}; + } + left = right >= 64 ? 0 : left << right; + break; + case RIGHTSHIFT: + if (right < 0 || right > 64) { + *error = Message{token.GetTokenProvenanceRange(opAt), + "bad right shift count"_err_en_US}; + } + left = right >= 64 ? 0 : left >> right; + break; + case BITAND: + case AND: left = left & right; break; + case BITXOR: left = left ^ right; break; + case BITOR: + case OR: left = left | right; break; + case LT: left = -(left < right); break; + case LE: left = -(left <= right); break; + case EQ: left = -(left == right); break; + case NE: left = -(left != right); break; + case GE: left = -(left >= right); break; + case GT: left = -(left > right); break; + case EQV: left = -(!left == !right); break; + case NEQV: left = -(!left != !right); break; + case SELECT: + if (*atToken >= tokens || token.TokenAt(*atToken).ToString() != ":") { + *error = Message{token.GetTokenProvenanceRange(opAt), + "':' required in selection expression"_err_en_US}; + return 0; + } else { + ++*atToken; + std::int64_t third{ + ExpressionValue(token, operandPrecedence[op], atToken, error)}; + left = left != 0 ? right : third; + } + break; + case COMMA: left = right; break; + default: CRASH_NO_CASE; + } + } + return left; +} + +bool Preprocessor::IsIfPredicateTrue(const TokenSequence &expr, + std::size_t first, std::size_t exprTokens, Prescanner *prescanner) { + TokenSequence expr1{expr, first, exprTokens}; + if (expr1.HasBlanks()) { + expr1.RemoveBlanks(); + } + TokenSequence expr2; + for (std::size_t j{0}; j < expr1.SizeInTokens(); ++j) { + if (ToLowerCaseLetters(expr1.TokenAt(j).ToString()) == "defined") { + CharBlock name; + if (j + 3 < expr1.SizeInTokens() && + expr1.TokenAt(j + 1).ToString() == "(" && + expr1.TokenAt(j + 3).ToString() == ")") { + name = expr1.TokenAt(j + 2); + j += 3; + } else if (j + 1 < expr1.SizeInTokens() && + IsLegalIdentifierStart(expr1.TokenAt(j + 1))) { + name = expr1.TokenAt(++j); + } + if (!name.empty()) { + char truth{IsNameDefined(name) ? '1' : '0'}; + expr2.Put(&truth, 1, allSources_.CompilerInsertionProvenance(truth)); + continue; + } + } + expr2.Put(expr1, j); + } + TokenSequence expr3{ReplaceMacros(expr2, *prescanner)}; + if (expr3.HasBlanks()) { + expr3.RemoveBlanks(); + } + if (expr3.empty()) { + prescanner->Say(expr.GetProvenanceRange(), "empty expression"_err_en_US); + return false; + } + std::size_t atToken{0}; + std::optional<Message> error; + bool result{ExpressionValue(expr3, 0, &atToken, &error) != 0}; + if (error) { + prescanner->Say(std::move(*error)); + } else if (atToken < expr3.SizeInTokens() && + expr3.TokenAt(atToken).ToString() != "!") { + prescanner->Say(expr3.GetIntervalProvenanceRange( + atToken, expr3.SizeInTokens() - atToken), + atToken == 0 ? "could not parse any expression"_err_en_US + : "excess characters after expression"_err_en_US); + } + return result; +} +} |