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