aboutsummaryrefslogtreecommitdiff
path: root/src/yue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yue.cpp')
-rw-r--r--src/yue.cpp164
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>
22using namespace std::string_view_literals; 24using namespace std::string_view_literals;
23using namespace std::string_literals; 25using namespace std::string_literals;
26using 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
89static const char luaminifyCodes[] = 93static const char luaminifyCodes[] =
90#include "LuaMinify.h" 94#include "LuaMinify.h"
91 95//
92 static void pushLuaminify(lua_State * L) { 96static 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
107fs::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
120fs::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
140static 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
194class UpdateListener : public efsw::FileWatchListener {
195public:
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
103int main(int narg, const char** args) { 227int 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()) {