diff options
Diffstat (limited to 'src/yue.cpp')
-rw-r--r-- | src/yue.cpp | 164 |
1 files changed, 160 insertions, 4 deletions
diff --git a/src/yue.cpp b/src/yue.cpp index 5b4dccc..9ae4c3e 100644 --- a/src/yue.cpp +++ b/src/yue.cpp | |||
@@ -19,10 +19,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||
19 | #include <sstream> | 19 | #include <sstream> |
20 | #include <string_view> | 20 | #include <string_view> |
21 | #include <tuple> | 21 | #include <tuple> |
22 | #include <chrono> | ||
23 | #include <thread> | ||
22 | using namespace std::string_view_literals; | 24 | using namespace std::string_view_literals; |
23 | using namespace std::string_literals; | 25 | using namespace std::string_literals; |
26 | using namespace std::chrono_literals; | ||
24 | #include "ghc/fs_std.hpp" | 27 | #include "ghc/fs_std.hpp" |
25 | #include "linenoise.hpp" | 28 | #include "linenoise.hpp" |
29 | #include "efsw/efsw.hpp" | ||
26 | 30 | ||
27 | #if not(defined YUE_NO_MACRO && defined YUE_COMPILER_ONLY) | 31 | #if not(defined YUE_NO_MACRO && defined YUE_COMPILER_ONLY) |
28 | #define _DEFER(code, line) std::shared_ptr<void> _defer_##line(nullptr, [&](auto) { code; }) | 32 | #define _DEFER(code, line) std::shared_ptr<void> _defer_##line(nullptr, [&](auto) { code; }) |
@@ -88,8 +92,8 @@ void pushOptions(lua_State* L, int lineOffset) { | |||
88 | #ifndef YUE_COMPILER_ONLY | 92 | #ifndef YUE_COMPILER_ONLY |
89 | static const char luaminifyCodes[] = | 93 | static const char luaminifyCodes[] = |
90 | #include "LuaMinify.h" | 94 | #include "LuaMinify.h" |
91 | 95 | // | |
92 | static void pushLuaminify(lua_State * L) { | 96 | static void pushLuaminify(lua_State * L) { |
93 | if (luaL_loadbuffer(L, luaminifyCodes, sizeof(luaminifyCodes) / sizeof(luaminifyCodes[0]) - 1, "=(luaminify)") != 0) { | 97 | if (luaL_loadbuffer(L, luaminifyCodes, sizeof(luaminifyCodes) / sizeof(luaminifyCodes[0]) - 1, "=(luaminify)") != 0) { |
94 | std::string err = "failed to load luaminify module.\n"s + lua_tostring(L, -1); | 98 | std::string err = "failed to load luaminify module.\n"s + lua_tostring(L, -1); |
95 | luaL_error(L, err.c_str()); | 99 | luaL_error(L, err.c_str()); |
@@ -100,6 +104,126 @@ static const char luaminifyCodes[] = | |||
100 | } | 104 | } |
101 | #endif // YUE_COMPILER_ONLY | 105 | #endif // YUE_COMPILER_ONLY |
102 | 106 | ||
107 | fs::path getTargetFile(const fs::path& srcFile) { | ||
108 | auto ext = srcFile.extension().string(); | ||
109 | for (auto& ch : ext) ch = std::tolower(ch); | ||
110 | if (!ext.empty() && ext.substr(1) == yue::extension) { | ||
111 | auto targetFile = srcFile; | ||
112 | targetFile.replace_extension("lua"s); | ||
113 | if (fs::exists(targetFile)) { | ||
114 | return targetFile; | ||
115 | } | ||
116 | } | ||
117 | return fs::path(); | ||
118 | } | ||
119 | |||
120 | fs::path getTargetFileDirty(const fs::path& srcFile) { | ||
121 | if (!fs::exists(srcFile)) return fs::path(); | ||
122 | auto ext = srcFile.extension().string(); | ||
123 | for (auto& ch : ext) ch = std::tolower(ch); | ||
124 | if (!fs::is_directory(srcFile) && !ext.empty() && ext.substr(1) == yue::extension) { | ||
125 | auto targetFile = srcFile; | ||
126 | targetFile.replace_extension("lua"s); | ||
127 | if (fs::exists(targetFile)) { | ||
128 | auto time = fs::last_write_time(targetFile); | ||
129 | auto targetTime = fs::last_write_time(srcFile); | ||
130 | if (time < targetTime) { | ||
131 | return targetFile; | ||
132 | } | ||
133 | } else { | ||
134 | return targetFile; | ||
135 | } | ||
136 | } | ||
137 | return fs::path(); | ||
138 | } | ||
139 | |||
140 | static std::string compileFile(const fs::path& srcFile, yue::YueConfig conf, const std::string& workPath) { | ||
141 | auto targetFile = getTargetFileDirty(srcFile); | ||
142 | if (targetFile.empty()) return std::string(); | ||
143 | std::ifstream input(srcFile, std::ios::in); | ||
144 | if (input) { | ||
145 | std::string s( | ||
146 | (std::istreambuf_iterator<char>(input)), | ||
147 | std::istreambuf_iterator<char>()); | ||
148 | auto modulePath = srcFile.lexically_relative(workPath); | ||
149 | conf.module = modulePath.string(); | ||
150 | if (!workPath.empty()) { | ||
151 | auto it = conf.options.find("path"); | ||
152 | if (it != conf.options.end()) { | ||
153 | it->second += ';'; | ||
154 | it->second += (fs::path(workPath) / "?.lua"sv).string(); | ||
155 | } else { | ||
156 | conf.options["path"] = (fs::path(workPath) / "?.lua"sv).string(); | ||
157 | } | ||
158 | } | ||
159 | auto result = yue::YueCompiler{YUE_ARGS}.compile(s, conf); | ||
160 | if (result.error.empty()) { | ||
161 | std::string targetExtension("lua"sv); | ||
162 | if (result.options) { | ||
163 | auto it = result.options->find("target_extension"s); | ||
164 | if (it != result.options->end()) { | ||
165 | targetExtension = it->second; | ||
166 | } | ||
167 | } | ||
168 | if (targetFile.has_parent_path()) { | ||
169 | fs::create_directories(targetFile.parent_path()); | ||
170 | } | ||
171 | if (result.codes.empty()) { | ||
172 | return "Built "s + modulePath.string() + '\n'; | ||
173 | } | ||
174 | std::ofstream output(targetFile, std::ios::trunc | std::ios::out); | ||
175 | if (output) { | ||
176 | const auto& codes = result.codes; | ||
177 | if (conf.reserveLineNumber) { | ||
178 | auto head = "-- [yue]: "s + modulePath.string() + '\n'; | ||
179 | output.write(head.c_str(), head.size()); | ||
180 | } | ||
181 | output.write(codes.c_str(), codes.size()); | ||
182 | return "Built "s + modulePath.string() + '\n'; | ||
183 | } else { | ||
184 | return "Failed to write file: "s + targetFile.string() + '\n'; | ||
185 | } | ||
186 | } else { | ||
187 | return "Failed to compile: "s + modulePath.string() + '\n' + result.error + '\n'; | ||
188 | } | ||
189 | } else { | ||
190 | return "Failed to read file: "s + srcFile.string() + '\n'; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | class UpdateListener : public efsw::FileWatchListener { | ||
195 | public: | ||
196 | void handleFileAction(efsw::WatchID, const std::string& dir, const std::string& filename, efsw::Action action, std::string oldFilename) override { | ||
197 | switch(action) { | ||
198 | case efsw::Actions::Add: | ||
199 | if (auto res = compileFile(fs::path(dir) / filename, config, workPath); !res.empty()) { | ||
200 | std::cout << res; | ||
201 | } | ||
202 | break; | ||
203 | case efsw::Actions::Delete: { | ||
204 | auto srcFile = fs::path(dir) / filename; | ||
205 | auto targetFile = getTargetFile(srcFile); | ||
206 | if (!targetFile.empty()) { | ||
207 | fs::remove(targetFile); | ||
208 | std::cout << "Deleted " << targetFile.lexically_relative(workPath).string() << '\n'; | ||
209 | } | ||
210 | break; | ||
211 | } | ||
212 | case efsw::Actions::Modified: | ||
213 | if (auto res = compileFile(fs::path(dir) / filename, config, workPath); !res.empty()) { | ||
214 | std::cout << res; | ||
215 | } | ||
216 | break; | ||
217 | case efsw::Actions::Moved: | ||
218 | break; | ||
219 | default: | ||
220 | break; | ||
221 | } | ||
222 | } | ||
223 | yue::YueConfig config; | ||
224 | std::string workPath; | ||
225 | }; | ||
226 | |||
103 | int main(int narg, const char** args) { | 227 | int main(int narg, const char** args) { |
104 | const char* help = | 228 | const char* help = |
105 | "Usage: yue [options|files|directories] ...\n\n" | 229 | "Usage: yue [options|files|directories] ...\n\n" |
@@ -275,6 +399,7 @@ int main(int narg, const char** args) { | |||
275 | bool writeToFile = true; | 399 | bool writeToFile = true; |
276 | bool dumpCompileTime = false; | 400 | bool dumpCompileTime = false; |
277 | bool lintGlobal = false; | 401 | bool lintGlobal = false; |
402 | bool watchFiles = false; | ||
278 | std::string targetPath; | 403 | std::string targetPath; |
279 | std::string resultFile; | 404 | std::string resultFile; |
280 | std::string workPath; | 405 | std::string workPath; |
@@ -412,6 +537,8 @@ int main(int narg, const char** args) { | |||
412 | std::cout << help; | 537 | std::cout << help; |
413 | return 1; | 538 | return 1; |
414 | } | 539 | } |
540 | } else if (arg == "-w"sv) { | ||
541 | watchFiles = true; | ||
415 | } else if (arg.size() > 2 && arg.substr(0, 2) == "--"sv && arg.substr(2, 1) != "-"sv) { | 542 | } else if (arg.size() > 2 && arg.substr(0, 2) == "--"sv && arg.substr(2, 1) != "-"sv) { |
416 | auto argStr = arg.substr(2); | 543 | auto argStr = arg.substr(2); |
417 | yue::Utils::trim(argStr); | 544 | yue::Utils::trim(argStr); |
@@ -437,13 +564,16 @@ int main(int narg, const char** args) { | |||
437 | } | 564 | } |
438 | } | 565 | } |
439 | } | 566 | } |
567 | } else if (watchFiles) { | ||
568 | std::cout << "Error: -w can not be used with file\n"sv; | ||
569 | return 1; | ||
440 | } else { | 570 | } else { |
441 | workPath = fs::path(arg).parent_path().string(); | 571 | workPath = fs::path(arg).parent_path().string(); |
442 | files.emplace_back(arg, arg); | 572 | files.emplace_back(arg, arg); |
443 | } | 573 | } |
444 | } | 574 | } |
445 | } | 575 | } |
446 | if (files.empty()) { | 576 | if (!watchFiles && files.empty()) { |
447 | std::cout << help; | 577 | std::cout << help; |
448 | return 0; | 578 | return 0; |
449 | } | 579 | } |
@@ -451,6 +581,32 @@ int main(int narg, const char** args) { | |||
451 | std::cout << "Error: -o can not be used with multiple input files\n"sv; | 581 | std::cout << "Error: -o can not be used with multiple input files\n"sv; |
452 | std::cout << help; | 582 | std::cout << help; |
453 | } | 583 | } |
584 | if (watchFiles) { | ||
585 | auto fullWorkPath = fs::absolute(fs::path(workPath)).string(); | ||
586 | std::list<std::future<std::string>> results; | ||
587 | for (const auto& file : files) { | ||
588 | auto task = std::async(std::launch::async, [=]() { | ||
589 | return compileFile(fs::absolute(file.first), config, fullWorkPath); | ||
590 | }); | ||
591 | results.push_back(std::move(task)); | ||
592 | } | ||
593 | for (auto& result : results) { | ||
594 | std::string msg = result.get(); | ||
595 | if (!msg.empty()) { | ||
596 | std::cout << msg; | ||
597 | } | ||
598 | } | ||
599 | efsw::FileWatcher fileWatcher{}; | ||
600 | UpdateListener listener{}; | ||
601 | listener.config = config; | ||
602 | listener.workPath = fullWorkPath; | ||
603 | fileWatcher.addWatch(workPath, &listener, true); | ||
604 | fileWatcher.watch(); | ||
605 | while (true) { | ||
606 | std::this_thread::sleep_for(10000ms); | ||
607 | } | ||
608 | return 0; | ||
609 | } | ||
454 | std::list<std::future<std::tuple<int, std::string, std::string>>> results; | 610 | std::list<std::future<std::tuple<int, std::string, std::string>>> results; |
455 | for (const auto& file : files) { | 611 | for (const auto& file : files) { |
456 | auto task = std::async(std::launch::async, [=]() { | 612 | auto task = std::async(std::launch::async, [=]() { |
@@ -518,7 +674,7 @@ int main(int narg, const char** args) { | |||
518 | } | 674 | } |
519 | targetFile.replace_extension('.' + targetExtension); | 675 | targetFile.replace_extension('.' + targetExtension); |
520 | } | 676 | } |
521 | if (!targetPath.empty()) { | 677 | if (targetFile.has_parent_path()) { |
522 | fs::create_directories(targetFile.parent_path()); | 678 | fs::create_directories(targetFile.parent_path()); |
523 | } | 679 | } |
524 | if (result.codes.empty()) { | 680 | if (result.codes.empty()) { |