Repository navigation
Expand file tree
/
Copy pathcppsh.cpp
More file actions
316 lines (283 loc) · 14.4 KB
/
Copy pathcppsh.cpp
File metadata and controls
316 lines (283 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <vector>
#include <string>
#include <chrono>
#include <thread>
#include <filesystem>
#ifndef _WIN32
#include <unistd.h>
#include <sys/wait.h>
#include <csignal>
#ifdef __linux__
#include <sys/inotify.h>
#elif defined(__APPLE__)
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <fcntl.h>
#endif
#else
// Windows fallbacks
#include <process.h>
#define fork() -1
#define kill(pid, sig)
#define waitpid(pid, status, options)
#define execl(path, arg, ...)
#define STDIN_FILENO 0
inline int isatty(int) { return 1; }
#endif
#include "cppsh_core.hpp"
int main(int argc, char* argv[]) {
try {
std::vector<std::string> args(argv + 1, argv + argc);
cppsh::CliOptions opt = cppsh::parse_cli_args(args);
cppsh::EnvConfig env_cfg = cppsh::get_env_config();
if (opt.repl) {
if (!isatty(STDIN_FILENO)) {
// TTY Awareness: Piped input mode (e.g., cat script | cppsh)
std::string content, line;
while (std::getline(std::cin, line)) {
content += line + "\n";
}
opt.repl = false;
// Execute piped content directly
int st_mode = cppsh::check_stacktrace_support(env_cfg.cxx);
std::string compiled_source = cppsh::wrap_script(content, "stdin.cppsh", true, st_mode);
std::string tmp_file = "/tmp/cppsh_stdin.cpp";
std::ofstream out(tmp_file); out << compiled_source; out.close();
std::string compile_cmd = env_cfg.cxx + " -std=c++23 -O0 " + tmp_file + " -o /tmp/cppsh_stdin.out";
if (!env_cfg.ldflags.empty()) compile_cmd += " " + env_cfg.ldflags;
if (st_mode == 1) compile_cmd += " -lstdc++_libbacktrace";
else if (st_mode == 2) compile_cmd += " -lstdc++_exp";
if (std::system(compile_cmd.c_str()) != 0) return 1;
return std::system("/tmp/cppsh_stdin.out");
} else {
std::cout << "=== Welcome to cppsh (C++23 REPL) ===\n"
<< "Type `cmd` to run a shell command.\n"
<< "Type exit or quit to leave.\n";
cppsh::ReplState state;
std::string compiler = env_cfg.cxx;
int st_mode = cppsh::check_stacktrace_support(compiler);
std::string line;
while (true) {
if (!cppsh::repl_readline("cppsh> ", line, state.input_history)) break;
std::string t = cppsh::trim(line);
if (t == "exit" || t == "quit") break;
if (t.empty()) continue;
std::string compiled_source = cppsh::process_repl_input(state, t, st_mode);
std::string tmp_file = "/tmp/cppsh_repl.cpp";
std::ofstream out(tmp_file); out << compiled_source; out.close();
std::string compile_cmd = compiler + " -std=c++23 -O0 " + tmp_file + " -o /tmp/cppsh_repl.out";
if (!env_cfg.ldflags.empty()) compile_cmd += " " + env_cfg.ldflags;
if (st_mode == 1) compile_cmd += " -lstdc++_libbacktrace";
else if (st_mode == 2) compile_cmd += " -lstdc++_exp";
if (std::system(compile_cmd.c_str()) != 0) {
std::cerr << "cppsh: Compilation error. State preserved." << std::endl;
if (!state.last_was_expr && !state.history.empty()) state.history.pop_back();
} else {
int repl_exit = std::system("/tmp/cppsh_repl.out");
if (repl_exit != 0) {
std::cerr << "[cppsh] Script exited with code " << repl_exit << std::endl;
}
}
}
return 0;
}
}
std::string script_path = opt.script_path;
std::string content = cppsh::read_file(script_path);
cppsh::ScriptMetadata meta = cppsh::extract_metadata(content);
std::string compiler = meta.compiler.empty() ? env_cfg.cxx : meta.compiler;
std::string std_ver = meta.std_version.empty() ? "c++23" : meta.std_version;
std::string debugger = meta.debugger.empty() ? env_cfg.debugger : meta.debugger;
int st_mode = cppsh::check_stacktrace_support(compiler);
if (opt.watch) {
std::cout << "[cppsh] Watching " << script_path << " for changes (Hot-Reloading)..." << std::endl;
std::string compiled_source = cppsh::wrap_script(content, script_path, true, st_mode);
std::string tmp_file = "/tmp/cppsh_script.cpp";
std::ofstream out(tmp_file); out << compiled_source; out.close();
std::string compile_cmd = compiler + " -std=" + std_ver + " -O0 " + tmp_file + " -o /tmp/cppsh_script.out";
if (!env_cfg.ldflags.empty()) compile_cmd += " " + env_cfg.ldflags;
if (st_mode == 1) compile_cmd += " -lstdc++_libbacktrace";
else if (st_mode == 2) compile_cmd += " -lstdc++_exp";
if (std::system(compile_cmd.c_str()) != 0) {
std::cerr << "cppsh: Initial compilation failed!" << std::endl;
return 1;
}
pid_t active_pid = fork();
if (active_pid == 0) {
execl("/tmp/cppsh_script.out", "/tmp/cppsh_script.out", nullptr);
std::exit(1);
}
#ifdef __linux__
int fd = inotify_init();
if (fd < 0) throw std::runtime_error("inotify_init failed");
int wd = inotify_add_watch(fd, script_path.c_str(), IN_MODIFY);
char buffer[1024];
#elif defined(__APPLE__)
int kq = kqueue();
if (kq < 0) throw std::runtime_error("kqueue failed");
int file_fd = open(script_path.c_str(), O_RDONLY);
struct kevent change;
EV_SET(&change, file_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, nullptr);
kevent(kq, &change, 1, nullptr, 0, nullptr);
struct kevent event;
#else
auto last_time = std::filesystem::last_write_time(script_path);
#endif
while (true) {
#ifdef __linux__
int length = read(fd, buffer, sizeof(buffer));
if (length < 0) break;
#elif defined(__APPLE__)
int n = kevent(kq, nullptr, 0, &event, 1, nullptr);
if (n < 0) break;
#else
std::this_thread::sleep_for(std::chrono::milliseconds(500));
auto current_time = std::filesystem::last_write_time(script_path);
if (current_time == last_time) continue;
last_time = current_time;
#endif
std::cout << "\n[cppsh] File change detected. Starting shadow build..." << std::endl;
std::string new_content = cppsh::read_file(script_path);
std::string new_compiled = cppsh::wrap_script(new_content, script_path, true, st_mode);
std::string new_tmp = "/tmp/cppsh_script_watch.cpp";
std::ofstream nout(new_tmp); nout << new_compiled; nout.close();
std::string shadow_compile = compiler + " -std=" + std_ver + " -O0 " + new_tmp + " -o /tmp/cppsh_script_watch.out";
if (!env_cfg.ldflags.empty()) shadow_compile += " " + env_cfg.ldflags;
if (st_mode == 1) shadow_compile += " -lstdc++_libbacktrace";
else if (st_mode == 2) shadow_compile += " -lstdc++_exp";
if (std::system(shadow_compile.c_str()) == 0) {
std::cout << "[cppsh] Shadow build successful. Restarting process..." << std::endl;
if (active_pid > 0) {
kill(active_pid, SIGTERM);
waitpid(active_pid, nullptr, 0);
}
std::filesystem::rename("/tmp/cppsh_script_watch.out", "/tmp/cppsh_script.out");
active_pid = fork();
if (active_pid == 0) {
execl("/tmp/cppsh_script.out", "/tmp/cppsh_script.out", nullptr);
std::exit(1);
}
} else {
std::cerr << "[cppsh] Shadow build failed! Keeping active process running." << std::endl;
}
}
return 0;
}
if (opt.export_cpp) {
std::string export_path = cppsh::get_export_output_path(script_path);
std::string clean_source = cppsh::wrap_script(content, script_path, false, st_mode);
std::ofstream out(export_path);
if (!out.is_open()) {
std::cerr << "cppsh: Failed to open export path: " << export_path << std::endl;
return 1;
}
out << clean_source;
out.close();
std::cout << "[cppsh] Successfully exported proper C++ source to: " << export_path << "\n"
<< " You can now open this file in your IDE (CLion, VS Code, etc.).\n";
return 0;
}
if (!opt.build && !opt.export_cpp && !opt.debug && !opt.watch) {
std::string cached_binary = cppsh::get_cached_binary_path(content, env_cfg.cxxflags + "|||" + env_cfg.ldflags + "|||" + compiler + "|||" + std_ver);
if (std::filesystem::exists(cached_binary)) {
// Execution Skip: Skip compilation if cached binary is fresh
std::cout << "[cppsh] Near-instant execution via SHA-1/FNV-1a binary cache: " << cached_binary << std::endl;
std::string exec_cmd = cached_binary;
for (const auto& arg : opt.script_args) {
exec_cmd += " \""; exec_cmd += arg; exec_cmd += "\"";
}
return std::system(exec_cmd.c_str());
}
}
std::string cache_dir = cppsh::get_cache_dir();
std::filesystem::create_directories(cache_dir);
std::string prelude_path = cache_dir + "/prelude.hpp";
// Always rewrite the prelude so stale cached copies with wrong contents are replaced.
{
std::ofstream pout(prelude_path);
pout << "#include <vector>\n#include <string>\n#include <cstdint>\n"
"#include <iostream>\n#include <stdexcept>\n#include <format>\n"
"#include <memory>\n#include <array>\n#include <sys/wait.h>\n"
"#include <csignal>\n"
"#if __has_include(<stacktrace>)\n#include <stacktrace>\n#endif\n"
"#if __has_include(<print>)\n#include <print>\n#endif\n"
"using str = std::string;\n"
"template <typename T> using vec = std::vector<T>;\n"
"using u64 = uint64_t;\n";
pout.close();
}
std::string pch_file = cache_dir + "/prelude.hpp.gch";
// Remove stale PCH so it is regenerated fresh with the updated prelude
if (std::filesystem::exists(pch_file)) {
std::filesystem::remove(pch_file);
}
if (!opt.debug) {
std::string pch_cmd = compiler + " -std=" + std_ver + " -O3 -x c++-header " + prelude_path + " -o " + pch_file;
// PCH is a best-effort optimisation; failure is non-fatal
[[maybe_unused]] int pch_rc = std::system(pch_cmd.c_str());
}
// Wrap the script (passing the original script path for #line directives)
std::string compiled_source = cppsh::wrap_script(content, script_path, true, st_mode);
// Write out the transpiled source
std::string tmp_file = "/tmp/cppsh_script.cpp";
std::ofstream out(tmp_file);
out << compiled_source;
out.close();
// Compile and execute
std::string compile_cmd = compiler + " -std=" + std_ver;
if (!env_cfg.cxxflags.empty()) compile_cmd += " " + env_cfg.cxxflags;
if (!opt.debug && std::filesystem::exists(pch_file)) {
compile_cmd += " -include " + prelude_path;
}
std::string output_binary = "/tmp/cppsh_script.out";
if (opt.build) {
output_binary = cppsh::get_build_output_path(script_path);
compile_cmd += " -O3";
} else if (opt.debug || debugger == "gdb" || debugger == "lldb") {
compile_cmd += " -g -O0";
} else {
compile_cmd += " -O0";
}
compile_cmd += " " + tmp_file + " -o " + output_binary;
if (!env_cfg.ldflags.empty()) compile_cmd += " " + env_cfg.ldflags;
if (st_mode == 1) compile_cmd += " -lstdc++_libbacktrace";
else if (st_mode == 2) compile_cmd += " -lstdc++_exp";
// Static linking support for standalone deployment binaries
if (compiler.find("g++") != std::string::npos || compiler.find("gcc") != std::string::npos) {
if (opt.build) {
compile_cmd += " -static-libstdc++ -static-libgcc";
}
}
if (std::system(compile_cmd.c_str()) != 0) {
std::cerr << "cppsh: Compilation failed!" << std::endl;
return 1;
}
if (!opt.build && !opt.export_cpp && !opt.debug && !opt.watch) {
std::string cached_binary = cppsh::get_cached_binary_path(content, env_cfg.cxxflags + "|||" + env_cfg.ldflags + "|||" + compiler + "|||" + std_ver);
std::filesystem::copy_file(output_binary, cached_binary, std::filesystem::copy_options::overwrite_existing);
}
if (opt.build) {
std::cout << "[cppsh] Successfully built optimized deployment binary: " << output_binary << std::endl;
return 0;
}
if (opt.debug) {
std::string dbg_cmd = debugger + " " + output_binary;
return std::system(dbg_cmd.c_str());
}
// Forwarding arguments
std::string exec_cmd = output_binary;
for (const auto& arg : opt.script_args) {
exec_cmd += " \"";
exec_cmd += arg;
exec_cmd += "\"";
}
return std::system(exec_cmd.c_str());
} catch (const std::exception& e) {
std::cerr << "cppsh Error: " << e.what() << std::endl;
return 1;
}
}