1 // SciTE - Scintilla based Text Editor
2 /** @file LexillaLibrary.cxx
3 ** Interface to loadable lexers.
4 ** Maintains a list of lexer library paths and CreateLexer functions.
5 ** If list changes then load all the lexer libraries and find the functions.
6 ** When asked to create a lexer, call each function until one succeeds.
7 **/
8 // Copyright 2019 by Neil Hodgson <neilh@scintilla.org>
9 // The License.txt file describes the conditions under which this software may be distributed.
10
11 #include <cstring>
12
13 #include <string>
14 #include <string_view>
15 #include <vector>
16 #include <set>
17
18 #if !_WIN32
19 #include <dlfcn.h>
20 #else
21 #include <windows.h>
22 #endif
23
24 #include "ILexer.h"
25
26 #include "LexillaLibrary.h"
27
28 namespace {
29
30 #if _WIN32
31 #define EXT_LEXER_DECL __stdcall
32 typedef FARPROC Function;
33 typedef HMODULE Module;
34 constexpr const char *extensionSO = ".dll";
35 constexpr const char *pathSeparator = "\\";
36 constexpr const char *defaultName = "lexilla";
37 #else
38 #define EXT_LEXER_DECL
39 typedef void *Function;
40 typedef void *Module;
41 #if defined(__APPLE__)
42 constexpr const char *extensionSO = ".dylib";
43 #else
44 constexpr const char *extensionSO = ".so";
45 #endif
46 constexpr const char *pathSeparator = "/";
47 constexpr const char *defaultName = "liblexilla";
48 #endif
49
50 typedef Scintilla::ILexer5 *(EXT_LEXER_DECL *CreateLexerFn)(const char *name);
51 using GetLibraryPropertyNamesFn = const char *(EXT_LEXER_DECL *)();
52 using SetLibraryPropertyFn = void(EXT_LEXER_DECL *)(const char *key, const char *value);
53
54 /// Generic function to convert from a Function(void* or FARPROC) to a function pointer.
55 /// This avoids undefined and conditionally defined behaviour.
56 template<typename T>
FunctionPointer(Function function)57 T FunctionPointer(Function function) noexcept {
58 static_assert(sizeof(T) == sizeof(function));
59 T fp;
60 memcpy(&fp, &function, sizeof(T));
61 return fp;
62 }
63
64 #if _WIN32
65
WideStringFromUTF8(std::string_view sv)66 std::wstring WideStringFromUTF8(std::string_view sv) {
67 const int sLength = static_cast<int>(sv.length());
68 const int cchWide = ::MultiByteToWideChar(CP_UTF8, 0, sv.data(), sLength, nullptr, 0);
69 std::wstring sWide(cchWide, 0);
70 ::MultiByteToWideChar(CP_UTF8, 0, sv.data(), sLength, &sWide[0], cchWide);
71 return sWide;
72 }
73
74 #endif
75
76 std::string directoryLoadDefault;
77 std::string lastLoaded;
78 std::vector<CreateLexerFn> fnCLs;
79 std::vector<GetLibraryPropertyNamesFn> fnGLPNs;
80 std::vector<std::string> libraryProperties;
81 std::vector<SetLibraryPropertyFn> fnSLPs;
82
FindSymbol(Module m,const char * symbol)83 Function FindSymbol(Module m, const char *symbol) noexcept {
84 #if _WIN32
85 return ::GetProcAddress(m, symbol);
86 #else
87 return dlsym(m, symbol);
88 #endif
89 }
90
91 LexillaCreatePointer pCreateLexerDefault = nullptr;
92
NameContainsDot(std::string_view path)93 bool NameContainsDot(std::string_view path) noexcept {
94 for (std::string_view::const_reverse_iterator it = path.crbegin();
95 it != path.crend(); ++it) {
96 if (*it == '.')
97 return true;
98 if (*it == '/' || *it == '\\')
99 return false;
100 }
101 return false;
102 }
103
104 }
105
LexillaSetDefault(LexillaCreatePointer pCreate)106 void LexillaSetDefault(LexillaCreatePointer pCreate) {
107 pCreateLexerDefault = pCreate;
108 }
109
LexillaSetDefaultDirectory(std::string_view directory)110 void LexillaSetDefaultDirectory(std::string_view directory) {
111 directoryLoadDefault = directory;
112 }
113
LexillaLoad(std::string_view sharedLibraryPaths)114 bool LexillaLoad(std::string_view sharedLibraryPaths) {
115 if (sharedLibraryPaths == lastLoaded) {
116 return !fnCLs.empty();
117 }
118
119 std::string_view paths = sharedLibraryPaths;
120
121 fnCLs.clear();
122 fnGLPNs.clear();
123 fnSLPs.clear();
124 while (!paths.empty()) {
125 const size_t separator = paths.find_first_of(';');
126 std::string path(paths.substr(0, separator));
127 if (separator == std::string::npos) {
128 paths.remove_prefix(paths.size());
129 } else {
130 paths.remove_prefix(separator + 1);
131 }
132 if (path == ".") {
133 if (directoryLoadDefault.empty()) {
134 path = "";
135 } else {
136 path = directoryLoadDefault;
137 path += pathSeparator;
138 }
139 path += defaultName;
140 }
141 if (!NameContainsDot(path)) {
142 // No '.' in name so add extension
143 path.append(extensionSO);
144 }
145 #if _WIN32
146 // Convert from UTF-8 to wide characters
147 std::wstring wsPath = WideStringFromUTF8(path);
148 Module lexillaDL = ::LoadLibraryW(wsPath.c_str());
149 #else
150 Module lexillaDL = dlopen(path.c_str(), RTLD_LAZY);
151 #endif
152 if (lexillaDL) {
153 CreateLexerFn fnCL = FunctionPointer<CreateLexerFn>(
154 FindSymbol(lexillaDL, "CreateLexer"));
155 if (fnCL) {
156 fnCLs.push_back(fnCL);
157 }
158 GetLibraryPropertyNamesFn fnGLPN = FunctionPointer<GetLibraryPropertyNamesFn>(
159 FindSymbol(lexillaDL, "GetLibraryPropertyNames"));
160 if (fnGLPN) {
161 fnGLPNs.push_back(fnGLPN);
162 }
163 SetLibraryPropertyFn fnSLP = FunctionPointer<SetLibraryPropertyFn>(
164 FindSymbol(lexillaDL, "SetLibraryProperty"));
165 if (fnSLP) {
166 fnSLPs.push_back(fnSLP);
167 }
168 }
169 }
170 lastLoaded = sharedLibraryPaths;
171
172 std::set<std::string> nameSet;
173 for (GetLibraryPropertyNamesFn fnGLPN : fnGLPNs) {
174 const char *cpNames = fnGLPN();
175 if (cpNames) {
176 std::string_view names = cpNames;
177 while (!names.empty()) {
178 const size_t separator = names.find_first_of('\n');
179 std::string name(names.substr(0, separator));
180 nameSet.insert(name);
181 if (separator == std::string::npos) {
182 names.remove_prefix(names.size());
183 } else {
184 names.remove_prefix(separator + 1);
185 }
186 }
187 }
188 }
189 libraryProperties = std::vector<std::string>(nameSet.begin(), nameSet.end());
190
191 return !fnCLs.empty();
192 }
193
LexillaCreateLexer(std::string_view languageName)194 Scintilla::ILexer5 *LexillaCreateLexer(std::string_view languageName) {
195 std::string sLanguageName(languageName); // Ensure NUL-termination
196 for (CreateLexerFn fnCL : fnCLs) {
197 Scintilla::ILexer5 *pLexer = fnCL(sLanguageName.c_str());
198 if (pLexer) {
199 return pLexer;
200 }
201 }
202 if (pCreateLexerDefault) {
203 return pCreateLexerDefault(sLanguageName.c_str());
204 }
205 return nullptr;
206 }
207
LexillaLibraryProperties()208 std::vector<std::string> LexillaLibraryProperties() {
209 return libraryProperties;
210 }
211
LexillaSetProperty(const char * key,const char * value)212 void LexillaSetProperty(const char *key, const char *value) {
213 for (SetLibraryPropertyFn fnSLP : fnSLPs) {
214 fnSLP(key, value);
215 }
216 }
217