aboutsummaryrefslogtreecommitdiff
path: root/src/yue.cpp
diff options
context:
space:
mode:
authorLi Jin <dragon-fly@qq.com>2021-02-17 11:22:07 +0800
committerLi Jin <dragon-fly@qq.com>2021-02-17 11:22:07 +0800
commit7066392d1c974065181d95d93274136dcd625d43 (patch)
treecf51eafc2c52cbc12246a306bca172d799193d30 /src/yue.cpp
parent90cd12ad9ef465f3e435e1bd034dcfbe4e19d016 (diff)
downloadyuescript-7066392d1c974065181d95d93274136dcd625d43.tar.gz
yuescript-7066392d1c974065181d95d93274136dcd625d43.tar.bz2
yuescript-7066392d1c974065181d95d93274136dcd625d43.zip
stop reusing variables, rename project.
Diffstat (limited to 'src/yue.cpp')
-rw-r--r--src/yue.cpp570
1 files changed, 570 insertions, 0 deletions
diff --git a/src/yue.cpp b/src/yue.cpp
new file mode 100644
index 0000000..b5f91ff
--- /dev/null
+++ b/src/yue.cpp
@@ -0,0 +1,570 @@
1/* Copyright (c) 2021 Jin Li, http://www.luvfight.me
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
8
9#include "yuescript/yue_compiler.h"
10#include "yuescript/yue_parser.h"
11
12#include <iostream>
13#include <iomanip>
14#include <cstdlib>
15#include <limits>
16#include <fstream>
17#include <chrono>
18#include <future>
19#include <sstream>
20#include <tuple>
21#include <string_view>
22#include <memory>
23using namespace std::string_view_literals;
24#include "ghc/fs_std.hpp"
25#include "linenoise.hpp"
26
27#if not (defined YUE_NO_MACRO && defined YUE_COMPILER_ONLY)
28#define _DEFER(code,line) std::shared_ptr<void> _defer_##line(nullptr, [&](auto){code;})
29#define DEFER(code) _DEFER(code,__LINE__)
30extern "C" {
31#include "lua.h"
32#include "lauxlib.h"
33#include "lualib.h"
34int luaopen_yue(lua_State* L);
35} // extern "C"
36
37static void openlibs(void* state) {
38 lua_State* L = static_cast<lua_State*>(state);
39 luaL_openlibs(L);
40 luaopen_yue(L);
41}
42
43void pushYue(lua_State* L, std::string_view name) {
44 lua_getglobal(L, "package"); // package
45 lua_getfield(L, -1, "loaded"); // package loaded
46 lua_getfield(L, -1, "yue"); // package loaded yue
47 lua_pushlstring(L, &name.front(), name.size()); // package loaded yue name
48 lua_gettable(L, -2); // loaded[name], package loaded yue item
49 lua_insert(L, -4); // item package loaded yue
50 lua_pop(L, 3); // item
51}
52
53void pushOptions(lua_State* L, int lineOffset) {
54 lua_newtable(L);
55 lua_pushliteral(L, "lint_global");
56 lua_pushboolean(L, 0);
57 lua_rawset(L, -3);
58 lua_pushliteral(L, "implicit_return_root");
59 lua_pushboolean(L, 1);
60 lua_rawset(L, -3);
61 lua_pushliteral(L, "reserve_line_number");
62 lua_pushboolean(L, 1);
63 lua_rawset(L, -3);
64 lua_pushliteral(L, "space_over_tab");
65 lua_pushboolean(L, 0);
66 lua_rawset(L, -3);
67 lua_pushliteral(L, "same_module");
68 lua_pushboolean(L, 1);
69 lua_rawset(L, -3);
70 lua_pushliteral(L, "line_offset");
71 lua_pushinteger(L, lineOffset);
72 lua_rawset(L, -3);
73}
74#endif // not (defined YUE_NO_MACRO && defined YUE_COMPILER_ONLY)
75
76#ifndef YUE_NO_MACRO
77#define YUE_ARGS nullptr,openlibs
78#else
79#define YUE_ARGS
80#endif // YUE_NO_MACRO
81
82#ifndef YUE_COMPILER_ONLY
83static const char luaminifyCodes[] =
84#include "LuaMinify.h"
85
86static void pushLuaminify(lua_State* L) {
87 if (luaL_loadbuffer(L, luaminifyCodes, sizeof(luaminifyCodes) / sizeof(luaminifyCodes[0]) - 1, "=(luaminify)") != 0) {
88 std::string err = std::string("failed to load luaminify module.\n") + lua_tostring(L, -1);
89 luaL_error(L, err.c_str());
90 } else if (lua_pcall(L, 0, 1, 0) != 0) {
91 std::string err = std::string("failed to init luaminify module.\n") + lua_tostring(L, -1);
92 luaL_error(L, err.c_str());
93 }
94}
95#endif // YUE_COMPILER_ONLY
96
97int main(int narg, const char** args) {
98 const char* help =
99"Usage: yue [options|files|directories] ...\n\n"
100" -h Print this message\n"
101#ifndef YUE_COMPILER_ONLY
102" -e str Execute a file or raw codes\n"
103" -m Generate minified codes\n"
104#endif // YUE_COMPILER_ONLY
105" -t path Specify where to place compiled files\n"
106" -o file Write output to file\n"
107" -s Use spaces in generated codes instead of tabs\n"
108" -p Write output to standard out\n"
109" -b Dump compile time (doesn't write output)\n"
110" -l Write line numbers from source codes\n"
111" -v Print version\n"
112#ifndef YUE_COMPILER_ONLY
113" -- Read from standard in, print to standard out\n"
114" (Must be first and only argument)\n\n"
115" Execute without options to enter REPL, type symbol '$'\n"
116" in a single line to start/stop multi-line mode\n"
117#endif // YUE_COMPILER_ONLY
118;
119#ifndef YUE_COMPILER_ONLY
120 if (narg == 1) {
121 lua_State* L = luaL_newstate();
122 openlibs(L);
123 DEFER(lua_close(L));
124 pushYue(L, "insert_loader"sv);
125 if (lua_pcall(L, 0, 0, 0) != 0) {
126 std::cout << lua_tostring(L, -1) << '\n';
127 return 1;
128 }
129 int count = 0;
130 linenoise::SetMultiLine(false);
131 linenoise::SetCompletionCallback([](const char* editBuffer, std::vector<std::string>& completions) {
132 std::string buf = editBuffer;
133 std::string tmp = buf;
134 yue::Utils::trim(tmp);
135 if (tmp.empty()) return;
136 std::string pre;
137 auto pos = buf.find_first_not_of(" \t\n");
138 if (pos != std::string::npos) {
139 pre = buf.substr(0, pos);
140 }
141 switch (tmp[0]) {
142 case 'b':
143 completions.push_back(pre + "break");
144 break;
145 case 'c':
146 completions.push_back(pre + "class ");
147 completions.push_back(pre + "continue");
148 break;
149 case 'e':
150 completions.push_back(pre + "else");
151 completions.push_back(pre + "export ");
152 break;
153 case 'i':
154 completions.push_back(pre + "import \"");
155 break;
156 case 'g':
157 completions.push_back(pre + "global ");
158 break;
159 case 'l':
160 completions.push_back(pre + "local ");
161 break;
162 case 'm':
163 completions.push_back(pre + "macro expr ");
164 completions.push_back(pre + "macro block ");
165 completions.push_back(pre + "macro lua ");
166 break;
167 case 's':
168 completions.push_back(pre + "switch ");
169 break;
170 case 'u':
171 completions.push_back(pre + "unless ");
172 break;
173 case 'w':
174 completions.push_back(pre + "with ");
175 completions.push_back(pre + "when ");
176 break;
177 }
178 });
179 std::cout << "Yuescript "sv << yue::version << '\n';
180 while (true) {
181 count++;
182 std::string codes;
183 bool quit = linenoise::Readline("> ", codes);
184 if (quit) return 0;
185 linenoise::AddHistory(codes.c_str());
186 yue::Utils::trim(codes);
187 if (codes == "$"sv) {
188 codes.clear();
189 for (std::string line; !(quit = linenoise::Readline("", line));) {
190 auto temp = line;
191 yue::Utils::trim(temp);
192 if (temp == "$"sv) {
193 break;
194 }
195 codes += '\n';
196 codes += line;
197 linenoise::AddHistory(line.c_str());
198 yue::Utils::trim(codes);
199 }
200 if (quit) return 0;
201 }
202 codes.insert(0, "global *\n"sv);
203 int top = lua_gettop(L);
204 DEFER(lua_settop(L, top));
205 pushYue(L, "loadstring"sv);
206 lua_pushlstring(L, codes.c_str(), codes.size());
207 lua_pushstring(L, (std::string("=(repl ") + std::to_string(count) + ')').c_str());
208 pushOptions(L, -1);
209 const std::string_view Err = "\033[35m"sv, Val = "\033[33m"sv, Stop = "\033[0m\n"sv;
210 if (lua_pcall(L, 3, 2, 0) != 0) {
211 std::cout << Err << lua_tostring(L, -1) << Stop;
212 continue;
213 }
214 if (lua_isnil(L, -2) != 0) {
215 std::string err = lua_tostring(L, -1);
216 auto modName = std::string("(repl "sv) + std::to_string(count) + "):";
217 if (err.substr(0, modName.size()) == modName) {
218 err = err.substr(modName.size());
219 }
220 auto pos = err.find(':');
221 if (pos != std::string::npos) {
222 int lineNum = std::stoi(err.substr(0, pos));
223 err = std::to_string(lineNum - 1) + err.substr(pos);
224 }
225 std::cout << Err << err << Stop;
226 continue;
227 }
228 lua_pop(L, 1);
229 pushYue(L, "pcall"sv);
230 lua_insert(L, -2);
231 int last = lua_gettop(L) - 2;
232 if (lua_pcall(L, 1, LUA_MULTRET, 0) != 0) {
233 std::cout << Err << lua_tostring(L, -1) << Stop;
234 continue;
235 }
236 int cur = lua_gettop(L);
237 int retCount = cur - last;
238 bool success = lua_toboolean(L, -retCount) != 0;
239 if (success) {
240 if (retCount > 1) {
241 for (int i = 1; i < retCount; ++i) {
242 std::cout << Val << luaL_tolstring(L, -retCount + i, nullptr) << Stop;
243 lua_pop(L, 1);
244 }
245 }
246 } else {
247 std::cout << Err << lua_tostring(L, -1) << Stop;
248 }
249 }
250 std::cout << '\n';
251 return 0;
252 }
253 bool minify = false;
254#endif // YUE_COMPILER_ONLY
255 yue::YueConfig config;
256 config.implicitReturnRoot = true;
257 config.lintGlobalVariable = false;
258 config.reserveLineNumber = false;
259 config.useSpaceOverTab = false;
260 bool writeToFile = true;
261 bool dumpCompileTime = false;
262 std::string targetPath;
263 std::string resultFile;
264 std::list<std::pair<std::string,std::string>> files;
265 for (int i = 1; i < narg; ++i) {
266 std::string arg = args[i];
267 if (arg == "--"sv) {
268 if (i != 1) {
269 std::cout << help;
270 return 1;
271 }
272 char ch;
273 std::string codes;
274 while ((ch = std::cin.get()) != EOF) {
275 codes += ch;
276 }
277 yue::YueConfig conf;
278 conf.implicitReturnRoot = true;
279 conf.lintGlobalVariable = false;
280 conf.reserveLineNumber = false;
281 conf.useSpaceOverTab = true;
282 auto result = yue::YueCompiler{YUE_ARGS}.compile(codes, conf);
283 if (result.error.empty()) {
284 std::cout << result.codes;
285 return 0;
286 } else {
287 std::ostringstream buf;
288 std::cout << result.error << '\n';
289 return 1;
290 }
291#ifndef YUE_COMPILER_ONLY
292 } else if (arg == "-e"sv) {
293 ++i;
294 if (i < narg) {
295 lua_State* L = luaL_newstate();
296 openlibs(L);
297 DEFER(lua_close(L));
298 pushYue(L, "insert_loader"sv);
299 if (lua_pcall(L, 0, 0, 0) != 0) {
300 std::cout << lua_tostring(L, -1) << '\n';
301 return 1;
302 }
303 std::string evalStr = args[i];
304 std::ifstream input(evalStr, std::ios::in);
305 if (input) {
306 auto ext = fs::path(evalStr).extension().string();
307 for (auto& ch : ext) ch = std::tolower(ch);
308 if (ext == ".lua") {
309 lua_getglobal(L, "load");
310 } else {
311 pushYue(L, "loadstring"sv);
312 }
313 std::string s(
314 (std::istreambuf_iterator<char>(input)),
315 std::istreambuf_iterator<char>());
316 lua_pushlstring(L, s.c_str(), s.size());
317 lua_pushlstring(L, evalStr.c_str(), evalStr.size());
318 } else {
319 pushYue(L, "loadstring"sv);
320 lua_pushlstring(L, evalStr.c_str(), evalStr.size());
321 lua_pushliteral(L, "=(eval str)");
322 }
323 if (lua_pcall(L, 2, 2, 0) != 0) {
324 std::cout << lua_tostring(L, -1) << '\n';
325 return 1;
326 }
327 if (lua_isnil(L, -2) != 0) {
328 std::cout << lua_tostring(L, -1) << '\n';
329 return 1;
330 }
331 lua_pop(L, 1);
332 pushYue(L, "pcall"sv);
333 lua_insert(L, -2);
334 int argCount = 0;
335 i++;
336 while (i < narg) {
337 argCount++;
338 lua_pushstring(L, args[i]);
339 i++;
340 }
341 if (lua_pcall(L, 1 + argCount, 2, 0) != 0) {
342 std::cout << lua_tostring(L, -1) << '\n';
343 return 1;
344 }
345 bool success = lua_toboolean(L, -2) != 0;
346 if (!success) {
347 std::cout << lua_tostring(L, -1) << '\n';
348 return 1;
349 }
350 return 0;
351 } else {
352 std::cout << help;
353 return 1;
354 }
355 } else if (arg == "-m"sv) {
356 minify = true;
357#endif // YUE_COMPILER_ONLY
358 } else if (arg == "-s"sv) {
359 config.useSpaceOverTab = true;
360 } else if (arg == "-l"sv) {
361 config.reserveLineNumber = true;
362 } else if (arg == "-p"sv) {
363 writeToFile = false;
364 } else if (arg == "-t"sv) {
365 ++i;
366 if (i < narg) {
367 targetPath = args[i];
368 } else {
369 std::cout << help;
370 return 1;
371 }
372 } else if (arg == "-b"sv) {
373 dumpCompileTime = true;
374 } else if (arg == "-h"sv) {
375 std::cout << help;
376 return 0;
377 } else if (arg == "-v"sv) {
378 std::cout << "Yuescript version: "sv << yue::version << '\n';
379 return 0;
380 } else if (arg == "-o"sv) {
381 ++i;
382 if (i < narg) {
383 resultFile = args[i];
384 } else {
385 std::cout << help;
386 return 1;
387 }
388 } else if (arg.substr(0, 1) == "-"sv && arg.find('=') != std::string::npos) {
389 auto argStr = arg.substr(1);
390 size_t idx = argStr.find('=');
391 auto key = argStr.substr(0, idx);
392 auto value = argStr.substr(idx + 1);
393 config.options[key] = value;
394 } else {
395 if (fs::is_directory(arg)) {
396 for (auto item : fs::recursive_directory_iterator(arg)) {
397 if (!item.is_directory()) {
398 auto ext = item.path().extension().string();
399 for (char& ch : ext) ch = std::tolower(ch);
400 if (!ext.empty() && ext.substr(1) == yue::extension) {
401 files.emplace_back(item.path().string(), item.path().lexically_relative(arg).string());
402 }
403 }
404 }
405 } else {
406 files.emplace_back(arg, arg);
407 }
408 }
409 }
410 if (files.empty()) {
411 std::cout << help;
412 return 0;
413 }
414 if (!resultFile.empty() && files.size() > 1) {
415 std::cout << "Error: -o can not be used with multiple input files.\n"sv;
416 std::cout << help;
417 }
418 std::list<std::future<std::tuple<int,std::string,std::string>>> results;
419 for (const auto& file : files) {
420 auto task = std::async(std::launch::async, [=]() {
421 std::ifstream input(file.first, std::ios::in);
422 if (input) {
423 std::string s(
424 (std::istreambuf_iterator<char>(input)),
425 std::istreambuf_iterator<char>());
426 if (dumpCompileTime) {
427 auto start = std::chrono::high_resolution_clock::now();
428 auto result = yue::YueCompiler{YUE_ARGS}.compile(s, config);
429 auto end = std::chrono::high_resolution_clock::now();
430 if (!result.codes.empty()) {
431 std::chrono::duration<double> diff = end - start;
432 start = std::chrono::high_resolution_clock::now();
433 yue::YueParser{}.parse<yue::File_t>(s);
434 end = std::chrono::high_resolution_clock::now();
435 std::chrono::duration<double> parseDiff = end - start;
436 std::ostringstream buf;
437 buf << file.first << " \n"sv;
438 buf << "Parse time: "sv << std::setprecision(5) << parseDiff.count() * 1000 << " ms\n";
439 buf << "Compile time: "sv << std::setprecision(5) << (diff.count() - parseDiff.count()) * 1000 << " ms\n\n";
440 return std::tuple{0, file.first, buf.str()};
441 } else {
442 std::ostringstream buf;
443 buf << "Fail to compile: "sv << file.first << ".\n"sv;
444 buf << result.error << '\n';
445 return std::tuple{1, file.first, buf.str()};
446 }
447 }
448 auto result = yue::YueCompiler{YUE_ARGS}.compile(s, config);
449 if (result.error.empty()) {
450 if (!writeToFile) {
451 return std::tuple{0, file.first, result.codes + '\n'};
452 } else {
453 std::string targetExtension("lua"sv);
454 if (result.options) {
455 auto it = result.options->find("target_extension");
456 if (it != result.options->end()) {
457 targetExtension = it->second;
458 }
459 }
460 fs::path targetFile;
461 if (!resultFile.empty()) {
462 targetFile = resultFile;
463 } else {
464 if (!targetPath.empty()) {
465 targetFile = fs::path(targetPath) / file.second;
466 } else {
467 targetFile = file.first;
468 }
469 targetFile.replace_extension('.' + targetExtension);
470 }
471 if (!targetPath.empty()) {
472 fs::create_directories(targetFile.parent_path());
473 }
474 std::ofstream output(targetFile, std::ios::trunc | std::ios::out);
475 if (output) {
476 const auto& codes = result.codes;
477 if (config.reserveLineNumber) {
478 auto head = std::string("-- [yue]: "sv) + file.first + '\n';
479 output.write(head.c_str(), head.size());
480 }
481 output.write(codes.c_str(), codes.size());
482 return std::tuple{0, targetFile.string(), std::string("Built "sv) + file.first + '\n'};
483 } else {
484 return std::tuple{1, std::string(), std::string("Fail to write file: "sv) + targetFile.string() + '\n'};
485 }
486 }
487 } else {
488 std::ostringstream buf;
489 buf << "Fail to compile: "sv << file.first << ".\n";
490 buf << result.error << '\n';
491 return std::tuple{1, std::string(), buf.str()};
492 }
493 } else {
494 return std::tuple{1, std::string(), std::string("Fail to read file: "sv) + file.first + ".\n"};
495 }
496 });
497 results.push_back(std::move(task));
498 }
499 int ret = 0;
500#ifndef YUE_COMPILER_ONLY
501 lua_State* L = nullptr;
502 DEFER({
503 if (L) lua_close(L);
504 });
505 if (minify) {
506 L = luaL_newstate();
507 luaL_openlibs(L);
508 pushLuaminify(L);
509 }
510#endif // YUE_COMPILER_ONLY
511 std::list<std::string> errs;
512 for (auto& result : results) {
513 int val = 0;
514 std::string file;
515 std::string msg;
516 std::tie(val, file, msg) = result.get();
517 if (val != 0) {
518 ret = val;
519 errs.push_back(msg);
520 } else {
521#ifndef YUE_COMPILER_ONLY
522 if (minify) {
523 std::ifstream input(file, std::ios::in);
524 if (input) {
525 std::string s;
526 if (writeToFile) {
527 s = std::string(
528 (std::istreambuf_iterator<char>(input)),
529 std::istreambuf_iterator<char>());
530 } else {
531 s = msg;
532 }
533 input.close();
534 int top = lua_gettop(L);
535 DEFER(lua_settop(L, top));
536 lua_pushvalue(L, -1);
537 lua_pushlstring(L, s.c_str(), s.size());
538 if (lua_pcall(L, 1, 1, 0) != 0) {
539 ret = 2;
540 std::string err = lua_tostring(L, -1);
541 errs.push_back(std::string("Fail to minify: "sv) + file + '\n' + err + '\n');
542 } else {
543 size_t size = 0;
544 const char* minifiedCodes = lua_tolstring(L, -1, &size);
545 if (writeToFile) {
546 std::ofstream output(file, std::ios::trunc | std::ios::out);
547 output.write(minifiedCodes, size);
548 output.close();
549 std::cout << "Minified built "sv << file << '\n';
550 } else {
551 std::cout << minifiedCodes << '\n';
552 }
553 }
554 } else {
555 ret = 2;
556 errs.push_back(std::string("Fail to minify: "sv) + file + '\n');
557 }
558 } else {
559 std::cout << msg;
560 }
561#else
562 std::cout << msg;
563#endif // YUE_COMPILER_ONLY
564 }
565 }
566 for (const auto& err : errs) {
567 std::cout << err;
568 }
569 return ret;
570}