blob: 63c1681ceb0b1644efd009b19f55a0947a2549d8 [file] [log] [blame]
Sam McCallf12cd992020-06-26 01:49:53 +02001//===--- ConfigCompile.cpp - Translating Fragments into Config ------------===//
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// Fragments are applied to Configs in two steps:
10//
11// 1. (When the fragment is first loaded)
12// FragmentCompiler::compile() traverses the Fragment and creates
13// function objects that know how to apply the configuration.
14// 2. (Every time a config is required)
15// CompiledFragment() executes these functions to populate the Config.
16//
17// Work could be split between these steps in different ways. We try to
18// do as much work as possible in the first step. For example, regexes are
19// compiled in stage 1 and captured by the apply function. This is because:
20//
21// - it's more efficient, as the work done in stage 1 must only be done once
22// - problems can be reported in stage 1, in stage 2 we must silently recover
23//
24//===----------------------------------------------------------------------===//
25
26#include "Config.h"
27#include "ConfigFragment.h"
28#include "support/Logger.h"
29#include "support/Trace.h"
30#include "llvm/ADT/StringRef.h"
31#include "llvm/Support/Regex.h"
32#include "llvm/Support/SMLoc.h"
33#include "llvm/Support/SourceMgr.h"
34
35namespace clang {
36namespace clangd {
37namespace config {
38namespace {
39
40struct CompiledFragmentImpl {
41 // The independent conditions to check before using settings from this config.
42 // The following fragment has *two* conditions:
43 // If: { Platform: [mac, linux], PathMatch: foo/.* }
44 // All of them must be satisfied: the platform and path conditions are ANDed.
45 // The OR logic for the platform condition is implemented inside the function.
46 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions;
47 // Mutations that this fragment will apply to the configuration.
48 // These are invoked only if the conditions are satisfied.
49 std::vector<llvm::unique_function<void(Config &) const>> Apply;
50
51 bool operator()(const Params &P, Config &C) const {
52 for (const auto &C : Conditions) {
53 if (!C(P)) {
54 dlog("Config fragment {0}: condition not met", this);
55 return false;
56 }
57 }
58 dlog("Config fragment {0}: applying {1} rules", this, Apply.size());
59 for (const auto &A : Apply)
60 A(C);
61 return true;
62 }
63};
64
65// Wrapper around condition compile() functions to reduce arg-passing.
66struct FragmentCompiler {
67 CompiledFragmentImpl &Out;
68 DiagnosticCallback Diagnostic;
69 llvm::SourceMgr *SourceMgr;
70
71 llvm::Optional<llvm::Regex> compileRegex(const Located<std::string> &Text) {
72 std::string Anchored = "^(" + *Text + ")$";
73 llvm::Regex Result(Anchored);
74 std::string RegexError;
75 if (!Result.isValid(RegexError)) {
76 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range);
77 return llvm::None;
78 }
79 return Result;
80 }
81
82 void compile(Fragment &&F) {
83 compile(std::move(F.If));
84 compile(std::move(F.CompileFlags));
85 }
86
87 void compile(Fragment::IfBlock &&F) {
88 if (F.HasUnrecognizedCondition)
89 Out.Conditions.push_back([&](const Params &) { return false; });
90
91 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>();
92 for (auto &Entry : F.PathMatch) {
93 if (auto RE = compileRegex(Entry))
94 PathMatch->push_back(std::move(*RE));
95 }
96 if (!PathMatch->empty()) {
97 Out.Conditions.push_back(
98 [PathMatch(std::move(PathMatch))](const Params &P) {
99 if (P.Path.empty())
100 return false;
101 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) {
102 return RE.match(P.Path);
103 });
104 });
105 }
106 }
107
108 void compile(Fragment::CompileFlagsBlock &&F) {
109 if (!F.Add.empty()) {
110 std::vector<std::string> Add;
111 for (auto &A : F.Add)
112 Add.push_back(std::move(*A));
113 Out.Apply.push_back([Add(std::move(Add))](Config &C) {
114 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) {
115 Args.insert(Args.end(), Add.begin(), Add.end());
116 });
117 });
118 }
119 }
120
121 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error;
122 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message,
123 llvm::SMRange Range) {
124 if (Range.isValid() && SourceMgr != nullptr)
125 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range));
126 else
127 Diagnostic(llvm::SMDiagnostic("", Kind, Message));
128 }
129};
130
131} // namespace
132
133CompiledFragment Fragment::compile(DiagnosticCallback D) && {
134 llvm::StringRef ConfigFile = "<unknown>";
135 std::pair<unsigned, unsigned> LineCol = {0, 0};
136 if (auto *SM = Source.Manager.get()) {
137 unsigned BufID = SM->getMainFileID();
138 LineCol = SM->getLineAndColumn(Source.Location, BufID);
139 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
140 }
141 trace::Span Tracer("ConfigCompile");
142 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile);
143 auto Result = std::make_shared<CompiledFragmentImpl>();
144 vlog("Config fragment: compiling {0}:{1} -> {2}", ConfigFile, LineCol.first,
145 Result.get());
146
147 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this));
148 // Return as cheaply-copyable wrapper.
149 return [Result(std::move(Result))](const Params &P, Config &C) {
150 return (*Result)(P, C);
151 };
152}
153
154} // namespace config
155} // namespace clangd
156} // namespace clang