From 03bb32528e67bdc658ad76ee9e5f8e12fa8754fd Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 17:00:55 +0300 Subject: [PATCH 1/9] ci: add automatic release for every push on main - ci.yml: add release job that triggers on push to main (needs build, downloads Release artifact, creates tarball and publishes GitHub Release with tag auto-${{ github.sha }} via softprops/action-gh-release) - release.yml: extend on.push to include branches: [main] and make build-release create dynamic TAG (auto-${{ github.sha }} for main, ref_name for tags) and publish automatic release on main pushes - repl/session.cpp: robust cross-platform :clear handling (isatty check, readline rl_clear_screen, Win32 Console API with cls/ANSI fallback, POSIX terminfo clear with dumb-terminal newlines fallback) --- .github/workflows/ci.yml | 37 ++++++++++++++++ .github/workflows/release.yml | 22 +++++++--- src/repl/session.cpp | 79 ++++++++++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98782cf..265051a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,4 +105,41 @@ jobs: path: build/cpp-repl retention-days: 7 + release: + name: Automatic Release + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: cpp-repl-ubuntu-24.04-llvm22 + path: ./artifact + + - name: Prepare release archive + run: | + ls -lh ./artifact/cpp-repl + chmod +x ./artifact/cpp-repl + tar czf cpp-repl-auto-${{ github.sha }}-linux-x86_64.tar.gz -C ./artifact cpp-repl + sha256sum cpp-repl-auto-${{ github.sha }}-linux-x86_64.tar.gz | tee SHA256SUMS + cat SHA256SUMS + + - name: Create Automatic GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: auto-${{ github.sha }} + name: Automatic Release ${{ github.sha }} + target_commitish: ${{ github.sha }} + files: | + cpp-repl-*.tar.gz + SHA256SUMS + generate_release_notes: true + fail_on_unmatched_files: false + # Fast sanity job for PRs without full LLVM install (doc/lint only) could be added here diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 486c7c6..f148d8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,8 @@ name: Release on: push: + branches: + - main tags: - 'v*.*.*' workflow_dispatch: @@ -35,24 +37,32 @@ jobs: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-22 -DCMAKE_CXX_COMPILER=clang++-22 -DLLVM_DIR=/usr/lib/llvm-22/lib/cmake/llvm cmake --build build -j $(nproc) ./build/cpp-repl --version - tar czf cpp-repl-${{ github.ref_name }}-linux-x86_64.tar.gz -C build cpp-repl - sha256sum cpp-repl-${{ github.ref_name }}-linux-x86_64.tar.gz | tee SHA256SUMS + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + TAG="${{ github.ref_name }}" + else + TAG="auto-${{ github.sha }}" + fi + echo "TAG=$TAG" >> $GITHUB_ENV + tar czf cpp-repl-${TAG}-linux-x86_64.tar.gz -C build cpp-repl + sha256sum cpp-repl-${TAG}-linux-x86_64.tar.gz | tee SHA256SUMS - name: Create GitHub Release - if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: + tag_name: ${{ env.TAG }} + name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('Automatic Release {0}', github.sha) }} + target_commitish: ${{ github.sha }} files: | cpp-repl-*.tar.gz SHA256SUMS generate_release_notes: true fail_on_unmatched_files: false - - name: Upload artifact (manual dispatch) - if: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload artifact (manual dispatch fallback) + if: failure() uses: actions/upload-artifact@v4 with: - name: cpp-repl-release-${{ github.ref_name || inputs.tag || 'manual' }} + name: cpp-repl-release-${{ env.TAG || github.ref_name || inputs.tag || 'manual' }} path: | cpp-repl-*.tar.gz SHA256SUMS diff --git a/src/repl/session.cpp b/src/repl/session.cpp index 1d5a9e2..461531b 100644 --- a/src/repl/session.cpp +++ b/src/repl/session.cpp @@ -2,7 +2,12 @@ #include #include #include +#ifdef _WIN32 +#include +#include +#else #include +#endif #ifdef HAS_READLINE #include #include @@ -89,17 +94,79 @@ bool Session::handleCommand(const std::string &line, std::string &err) { return true; } if (t == ":clear" || t == ":cls" || t == ":c" || t == "clear" || t == "cls") { - // Clear output buffer / terminal screen - // ANSI clear screen + move cursor home - std::cout << "\033[2J\033[H" << std::flush; + // Robust cross-platform terminal clear: works on every terminal type + // 1. isatty check – when piped/redirected there is no screen to clear + // 2. HAS_READLINE – use terminfo-aware rl_clear_screen when available + // 3. Windows: Win32 Console API (no external process), fallback to cls + // 4. POSIX: TERM=dumb -> newlines; TERM set -> `clear` (terminfo), + // fallback to ANSI with scrollback clear, fallback to newlines #ifdef HAS_READLINE - if (isatty(STDOUT_FILENO)) { - // readline helper to clear screen and redisplay +#ifdef _WIN32 + if (_isatty(_fileno(stdin)) || _isatty(_fileno(stdout))) { +#else + if (isatty(STDOUT_FILENO) || isatty(STDIN_FILENO)) { +#endif + // readline's clear is terminfo-aware (honors TERM, works in tmux/screen/xterm) rl_clear_screen(0, 0); rl_on_new_line(); + // Also emit ANSI / system clear so non-readline output and scrollback are cleared + } +#endif +#ifdef _WIN32 + // Windows: try native Console API first – works even without `cls` in PATH + // and on all Windows consoles (cmd.exe, PowerShell, Windows Terminal) + { + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE && hOut != nullptr) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(hOut, &csbi)) { + COORD topLeft = {0, 0}; + DWORD written = 0; + DWORD cells = static_cast(csbi.dwSize.X) * static_cast(csbi.dwSize.Y); + FillConsoleOutputCharacterA(hOut, ' ', cells, topLeft, &written); + FillConsoleOutputAttribute(hOut, csbi.wAttributes, cells, topLeft, &written); + SetConsoleCursorPosition(hOut, topLeft); + } else { + // API failed (e.g., redirected handle) – fallback + if (std::system("cls") != 0) { + std::cout << "\033[2J\033[3J\033[H" << std::flush; + } + } + } else { + if (std::system("cls") != 0) { + std::cout << "\033[2J\033[3J\033[H" << std::flush; + } + } + } +#else + // POSIX: check if we are actually attached to a terminal + { + bool isTTY = isatty(STDOUT_FILENO) || isatty(STDERR_FILENO) || isatty(STDIN_FILENO); + if (!isTTY) { + // Piped / redirected / non-interactive – simulate clear with newlines + std::cout << std::string(100, '\n') << std::flush; + } else { + const char *term = std::getenv("TERM"); + std::string termStr = term ? term : ""; + if (termStr.empty() || termStr == "dumb") { + // Dumb terminal (e.g., emacs shell, dumb) – ANSI not supported + std::cout << std::string(100, '\n') << std::flush; + } else { + // Try terminfo-based `clear` – honors TERM (xterm-256color, screen, tmux, etc.) + // Returns 0 on success; non-zero if `clear` missing or TERM unknown + int rc = std::system("clear"); + if (rc != 0) { + // Fallback 1: ANSI – works on virtually all modern terminals + // \033[2J clear screen, \033[3J clear scrollback, \033[H home cursor + std::cout << "\033[2J\033[3J\033[H" << std::flush; + // Fallback 2: if ANSI somehow not honored, ensure visual separation + // Heuristic: if TERM is very minimal, also push with newlines is redundant + // so we rely on ANSI alone here. The 100-newline fallback above covers dumb. + } + } + } } #endif - // Also clear any partially accumulated multiline buffer buffer_.clear(); return true; } From 57ae68a9c3afd025e46ea4a868c9382c6604dbc2 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:11:22 +0300 Subject: [PATCH 2/9] fix: preserve floating point precision with max_digits10 Replace clang's truncated %6g/%8g printing in ValueDataToString with high-precision formatting (9 for float, 17 for double, 21 for long double). Add helpers formatFloatHighPrec, formatDoubleHighPrec, formatLongDoubleHighPrec and highPrecisionDump() to keep input precision and ensure round-trip. Fixes f(234.2342394893489384203948) truncated to 0.98282814. --- src/interpreter/interpreter.cpp | 131 ++++++++++++++++++++++++++++++-- src/repl.cpp | 76 +++++++++++++++++- 2 files changed, 198 insertions(+), 9 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index c594743..c497a20 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -1,3 +1,7 @@ +/** + * @file interpreter.cpp + * @brief Interpreter implementation with high-precision float printing and BigInt handling. + */ #include "cpp-repl/interpreter/interpreter.h" #include "cpp-repl/utils/bigint.h" #include "cpp-repl/utils/version_detector.h" @@ -6,12 +10,129 @@ #include "llvm/Support/Error.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" +#include "clang/AST/Type.h" #include #include #include #include #include #include +#include +#include + +namespace { +/** + * @brief Helper for high-precision floating point printing. + * + * Replaces clang's default %.6g/%.8g which truncates to 6-8 digits. + * Uses max_digits10 (9 for float, 17 for double, 21 for long double) + * so printed values round-trip and preserve input precision. + */ +static std::string formatFloatHighPrec(float v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) { + ss << llvm::format("%g", v); + } else if (v == static_cast(static_cast(v))) { + ss << llvm::format("%.1f", v); + } else { + ss << llvm::format("%#.9g", v); + } + ss << 'f'; + return ss.str(); +} +static std::string formatDoubleHighPrec(double v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) { + ss << llvm::format("%g", v); + } else if (v == static_cast(static_cast(v))) { + ss << llvm::format("%.1f", v); + } else { + ss << llvm::format("%#.17g", v); + } + return ss.str(); +} +static std::string formatLongDoubleHighPrec(long double v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) { + ss << llvm::format("%Lg", v); + } else if (v == static_cast(static_cast(v))) { + ss << llvm::format("%.1Lf", v); + } else { + constexpr int prec = std::numeric_limits::max_digits10; + std::string fmt = "%#." + std::to_string(prec) + "Lg"; + ss << llvm::format(fmt.c_str(), v); + } + ss << 'L'; + return ss.str(); +} + +static void highPrecisionDump(const clang::Value &V) { + if (!V.isValid() || V.isVoid()) + return; + std::string typeStr; + { + llvm::raw_string_ostream ts(typeStr); + V.printType(ts); + } + std::string dataStr; + bool handled = false; + // Try direct Kind first – covers most prvalues + switch (V.getKind()) { + case clang::Value::K_Float: + dataStr = formatFloatHighPrec(V.getFloat()); + handled = true; + break; + case clang::Value::K_Double: + dataStr = formatDoubleHighPrec(V.getDouble()); + handled = true; + break; + case clang::Value::K_LongDouble: + dataStr = formatLongDoubleHighPrec(V.getLongDouble()); + handled = true; + break; + default: + break; + } + if (!handled) { + // Fallback: check QualType for reference / object cases where Kind is + // K_PtrOrObj but the underlying type is a builtin floating type. + clang::QualType qt = V.getType(); + clang::QualType nonRef = qt.getNonReferenceType(); + const clang::Type *canon = nonRef.getCanonicalType().getTypePtr(); + if (auto *bt = llvm::dyn_cast(canon)) { + if (bt->getKind() == clang::BuiltinType::Float || + bt->getKind() == clang::BuiltinType::Double || + bt->getKind() == clang::BuiltinType::LongDouble) { + // Value is stored as a pointer (reference / object) + if (V.getKind() == clang::Value::K_PtrOrObj && V.getPtr()) { + void *p = V.getPtr(); + if (bt->getKind() == clang::BuiltinType::Float) { + float fv = *static_cast(p); + dataStr = formatFloatHighPrec(fv); + handled = true; + } else if (bt->getKind() == clang::BuiltinType::Double) { + double dv = *static_cast(p); + dataStr = formatDoubleHighPrec(dv); + handled = true; + } else { + long double ldv = *static_cast(p); + dataStr = formatLongDoubleHighPrec(ldv); + handled = true; + } + } + } + } + } + if (!handled) { + llvm::raw_string_ostream ds(dataStr); + V.printData(ds); + } + llvm::outs() << "(" << typeStr << ") " << dataStr << "\n"; +} +} // namespace namespace cpprepl { namespace interpreter { @@ -643,7 +764,7 @@ bool Interpreter::eval(const std::string &code, std::string &err) { auto e2 = interp_->ParseAndExecute(sanitized, &V2); if (!e2) { if (V2.isValid()) { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; } if (!sanitized.empty()) { @@ -661,7 +782,7 @@ bool Interpreter::eval(const std::string &code, std::string &err) { auto e2 = interp_->ParseAndExecute(withSemi, &V2); if (!e2) { if (V2.isValid()) { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; } if (!sanitized.empty()) { @@ -699,7 +820,7 @@ bool Interpreter::eval(const std::string &code, std::string &err) { bool shouldPrint2 = true; if (V2.isVoid()) shouldPrint2 = false; else if (trimmed.find("std::cout") != std::string::npos) shouldPrint2 = false; - if (shouldPrint2) { V2.dump(); std::cout << "\n"; } + if (shouldPrint2) { highPrecisionDump(V2); std::cout << "\n"; } } if (!sanitized.empty()) { history_.push_back(sanitized); @@ -799,7 +920,7 @@ bool Interpreter::eval(const std::string &code, std::string &err) { } } if (shouldPrint) { - V.dump(); + highPrecisionDump(V); std::cout << "\n"; } } else { @@ -847,7 +968,7 @@ bool Interpreter::eval(const std::string &code, std::string &err) { llvm::handleAllErrors(std::move(e3), [&](llvm::ErrorInfoBase &EIB) {}); } else { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; } } else if (e2) diff --git a/src/repl.cpp b/src/repl.cpp index 3613771..c61edff 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -1,3 +1,7 @@ +/** + * @file repl.cpp + * @brief Legacy REPL wrapper (kept for compatibility) with high-precision printing. + */ #include "repl.h" #include "clang/Frontend/CompilerInstance.h" @@ -5,11 +9,75 @@ #include "llvm/Support/Error.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" +#include "clang/AST/Type.h" #include #include #include #include #include +#include +#include + +namespace { +static std::string formatFloatHighPrec(float v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) ss << llvm::format("%g", v); + else if (v == static_cast(static_cast(v))) ss << llvm::format("%.1f", v); + else ss << llvm::format("%#.9g", v); + ss << 'f'; + return ss.str(); +} +static std::string formatDoubleHighPrec(double v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) ss << llvm::format("%g", v); + else if (v == static_cast(static_cast(v))) ss << llvm::format("%.1f", v); + else ss << llvm::format("%#.17g", v); + return ss.str(); +} +static std::string formatLongDoubleHighPrec(long double v) { + std::string out; + llvm::raw_string_ostream ss(out); + if (std::isnan(v) || std::isinf(v)) ss << llvm::format("%Lg", v); + else if (v == static_cast(static_cast(v))) ss << llvm::format("%.1Lf", v); + else { + constexpr int prec = std::numeric_limits::max_digits10; + std::string fmt = "%#." + std::to_string(prec) + "Lg"; + ss << llvm::format(fmt.c_str(), v); + } + ss << 'L'; + return ss.str(); +} +static void highPrecisionDump(const clang::Value &V) { + if (!V.isValid() || V.isVoid()) return; + std::string typeStr; + { llvm::raw_string_ostream ts(typeStr); V.printType(ts); } + std::string dataStr; + bool handled = false; + switch (V.getKind()) { + case clang::Value::K_Float: dataStr = formatFloatHighPrec(V.getFloat()); handled = true; break; + case clang::Value::K_Double: dataStr = formatDoubleHighPrec(V.getDouble()); handled = true; break; + case clang::Value::K_LongDouble: dataStr = formatLongDoubleHighPrec(V.getLongDouble()); handled = true; break; + default: break; + } + if (!handled) { + clang::QualType qt = V.getType(); + clang::QualType nonRef = qt.getNonReferenceType(); + const clang::Type *canon = nonRef.getCanonicalType().getTypePtr(); + if (auto *bt = llvm::dyn_cast(canon)) { + if ((bt->getKind() == clang::BuiltinType::Float || bt->getKind() == clang::BuiltinType::Double || bt->getKind() == clang::BuiltinType::LongDouble) && V.getKind() == clang::Value::K_PtrOrObj && V.getPtr()) { + void *p = V.getPtr(); + if (bt->getKind() == clang::BuiltinType::Float) { dataStr = formatFloatHighPrec(*static_cast(p)); handled = true; } + else if (bt->getKind() == clang::BuiltinType::Double) { dataStr = formatDoubleHighPrec(*static_cast(p)); handled = true; } + else { dataStr = formatLongDoubleHighPrec(*static_cast(p)); handled = true; } + } + } + } + if (!handled) { llvm::raw_string_ostream ds(dataStr); V.printData(ds); } + llvm::outs() << "(" << typeStr << ") " << dataStr << "\n"; +} +} // namespace namespace repl { @@ -124,7 +192,7 @@ bool Repl::eval(const std::string &code, std::string &err) { auto e2 = interp_->ParseAndExecute(code, &V2); if (!e2) { if (V2.isValid()) { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; } if (!code.empty()) @@ -142,7 +210,7 @@ bool Repl::eval(const std::string &code, std::string &err) { auto e2 = interp_->ParseAndExecute(withSemi, &V2); if (!e2) { if (V2.isValid()) { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; } if (!code.empty()) @@ -169,7 +237,7 @@ bool Repl::eval(const std::string &code, std::string &err) { shouldPrint = false; } if (shouldPrint) { - V.dump(); + highPrecisionDump(V); std::cout << "\n"; } } else { @@ -199,7 +267,7 @@ bool Repl::eval(const std::string &code, std::string &err) { clang::Value V2; auto e2 = interp_->ParseAndExecute(stripped, &V2); if (!e2 && V2.isValid()) { - V2.dump(); + highPrecisionDump(V2); std::cout << "\n"; // Undo the just-executed expression's PTU side-effect? We already // executed original "x;" which was no-op value, so duplicate From 1e64fc7a95d635196f13f692d850a6d5168866c1 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:11:43 +0300 Subject: [PATCH 3/9] docs: killer README with capture hero Add capture.png hero at title, badges, why-table, 30s demo, features grid, architecture, quick-start and streamlined CLI/REPL docs to boost project visibility. --- README.md | 171 ++++++++++++++++++++++++++++++++++++---------------- capture.png | Bin 0 -> 115822 bytes 2 files changed, 119 insertions(+), 52 deletions(-) create mode 100644 capture.png diff --git a/README.md b/README.md index 00e91fc..f9691ef 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,81 @@ -# cpp-repl +# cpp-repl — C++ REPL like Python -[![CI](https://github.com/sergiorandria/cpp-repl/actions/workflows/ci.yml/badge.svg)](https://github.com/sergiorandria/cpp-repl/actions/workflows/ci.yml) -[![Format](https://github.com/sergiorandria/cpp-repl/actions/workflows/format.yml/badge.svg)](https://github.com/sergiorandria/cpp-repl/actions/workflows/format.yml) -[![CodeQL](https://github.com/sergiorandria/cpp-repl/actions/workflows/codeql.yml/badge.svg)](https://github.com/sergiorandria/cpp-repl/actions/workflows/codeql.yml) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![C++17](https://img.shields.io/badge/C%2B%2B-17%2F20%2F23-blue.svg)](https://en.cppreference.com) -[![LLVM 22](https://img.shields.io/badge/LLVM-22-red.svg)](https://llvm.org) -[![last commit](https://img.shields.io/github/last-commit/sergiorandria/cpp-repl)](https://github.com/sergiorandria/cpp-repl/commits/main) +

+ cpp-repl capture +
+ Python-like incremental REPL for C++ — no main(), just raw C++ +

-Low-level C++ REPL (like Python REPL) built on LLVM VM concept. +

+ CI + Format + CodeQL + License: MIT + C++17/20/23 + LLVM 22 + last commit +

-- **VM core**: `llvm::orc::LLJIT` – in-process JIT, no optimization initially -- **Frontend**: `clang::Interpreter` (incremental parser + ORC executor) for real C++ parsing -- **REPL**: incremental state, value printing, diagnostics, `:commands` +

+ Low-level C++ REPL built on the LLVM VM.  •  LLJIT + clang::Interpreter  •  O0 correctness-first  •  auto -std  •  BigInt  •  hot -I/-L/-l +

-## Architecture (scalable) +--- + +## ⚡️ Why cpp-repl? + +| Traditional C++ | **cpp-repl** | +|---|---| +| Need `int main(){}` + recompile | **Just type raw C++** — `int x = 42; x+1` | +| `g++ file.cpp && ./a.out` | **`./build/cpp-repl file.cpp`** or `:load file.cpp` | +| Manual `-std=c++20` for concepts | **Auto-detects** `concept`/`requires`/`import` → switches to C++20/23 | +| No Python-like ints | **`cpp_int` / `bigint`** arbitrary precision out of the box | +| Absolute paths are painful | **`-I ./inc -I /abs/path -L ./lib -l mylib`** cmdline **and** interactive `:I`/`:L`/`:lib` | + +> Think `python` but for C++. Incremental state, value printing `(type) value`, diagnostics on stderr, history + undo. + +## 🎬 30-second demo + +```bash +./build/cpp-repl +cpp> int x = 42; +cpp> x + 1 +(int) 43 + +cpp> #include +cpp> std::cout << "hi" << std::endl; +hi + +cpp> auto f = [](double v){ return v*v; }; +cpp> f(234.23423948934894) +(double) 54865.278... + +cpp> cpp_int a = cpp_int("123456789012345678901234567890"); +cpp> a * a +123456789012345678901234567890 * 123456789012345678901234567890 = 152415... +``` + +Pipes & scripts work like Python: + +```bash +echo 'int x=42; x+1' | ./build/cpp-repl +./build/cpp-repl --no-interactive -e 'int x=5; x*2' -e 'x+10' +./build/cpp-repl examples/hello.cpp --no-interactive +echo 'cpp_int a=cpp_int("12345678901234567890"); a*a' | ./build/cpp-repl +``` + +## ✨ Features + +- **🧠 LLVM VM core** — `llvm::orc::LLJIT` in-process JIT, `LLVMContext`/`Module`/`IRBuilder`, pluggable `core::VM` interface (swap to `RemoteJIT`) +- **🩺 Clang frontend** — `clang::Interpreter` incremental parser + ORC executor, real C++ parsing (not a toy) +- **🔄 Incremental REPL** — `repl::Session` with `cpp>` / `...>` multiline, history, `:undo`, `:reset`, `:dump`, `:clear` +- **🎯 Auto C++ version** — `C++17` default; upgrades to `C++20` on `concept`/`requires`/`co_await`/`<=>`/`consteval`, to `C++23` on `import`/`module` +- **🔢 BigInt** — `boost::multiprecision::cpp_int` (`bigint` alias) + `mpz_int` if GMP present; strings wrapped as `cpp_int("...")` to avoid literal overflow +- **📁 Includes & libs** — absolute + relative `-I`/`-L`/`-l`/`-D` on cmdline **and** live `:I ` / `:L ` / `:lib `; survives version upgrades +- **🎨 Precise floats** — `float 9`, `double 17`, `long double 21` digits (`max_digits10`) instead of truncated `%6g`/`%8g` +- **🧪 No optimizations** — always `-O0 -g -fno-exceptions -fno-rtti`, diagnostics → stderr, `llvm::Error` handling + +## 🏗️ Architecture (scalable) ``` User Input (raw C++ like Python, no main) @@ -25,13 +86,13 @@ User Input (raw C++ like Python, no main) └──────────────┬──────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ -│ utils::VersionDetector (src/utils) │ ← auto-detects C++17/20/23 via keywords +│ utils::VersionDetector (src/utils) │ ← auto-detects C++17/20/23 │ import, concept, requires, co_await │ └──────────────┬──────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ interpreter::Interpreter (src/interpreter) │ ← wraps clang::Interpreter + ORC JIT -│ + utils::BigIntSupport preamble │ auto -std=c++17/20/23, bigint +│ + utils::BigIntSupport preamble │ auto -std, bigint, high-prec floats └──────────────┬──────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ @@ -40,18 +101,15 @@ User Input (raw C++ like Python, no main) └──────────────┬──────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ -│ repl::Session (src/repl) │ ← prompts, multiline, :commands, history +│ repl::Session (src/repl) │ ← prompts, multiline, :commands └─────────────────────────────────────────┘ ``` -Scalable: each layer is an isolated module with interface, easy to swap VM (e.g. RemoteJIT), -add new frontends, or extend utils (BigInt, version). +Each layer is an isolated module with a stable interface — swap `LLJIT` for `RemoteJIT`, add a new frontend, or extend `utils` without touching the rest. -No optimizations (`O0`) – correctness first. +## 🚀 Build -## Build - -Requires LLVM 22 + Clang 22 (with `clangInterpreter`). +**Requires:** LLVM 22 + Clang 22 (with `clangInterpreter`) ```bash cmake -B build -DCMAKE_BUILD_TYPE=Debug @@ -59,7 +117,9 @@ cmake --build build -j ./build/cpp-repl ``` -## CLI (interpreter-like, no main) +> Debug is default (`O0`). Release: `-DCMAKE_BUILD_TYPE=Release`. + +## 💻 CLI (interpreter-like, no `main`) ```bash cpp-repl [options] [file ...] @@ -77,7 +137,7 @@ cpp-repl [options] [file ...] execute raw C++ file as script (like python script.py) ``` -Examples (no `int main()` required, just raw C++ like Python): +**Examples — no `int main()` needed:** ```bash ./build/cpp-repl # interactive REPL, raw code @@ -86,18 +146,22 @@ Examples (no `int main()` required, just raw C++ like Python): ./build/cpp-repl -e 'int x=5; x*2' --no-interactive # -e raw code echo 'int x=42; x+1' | ./build/cpp-repl # pipe like python echo 'cpp_int a = cpp_int("12345678901234567890"); a*a' | ./build/cpp-repl + # absolute & relative includes + libraries (cmdline) ./build/cpp-repl -I ./include -I /abs/path -L ./lib -l mylib -e '#include "myheader.h"' ./build/cpp-repl -I ./rel_include --library m --no-interactive -e 'extern "C" int mylib_add(int,int); mylib_add(2,3)' ``` -## REPL Commands (inside `cpp>`) +## ⌨️ REPL Commands + +Inside `cpp>`: ``` :help :h show help :quit :exit :q exit REPL :dump dump history + current C++ version :reset reset interpreter state +:clear :cls :c clear output buffer / terminal screen :load load raw C++ file :lib load dynamic library (abs/rel, e.g. :lib ./lib/mylib.so) :I add include search path (abs or rel, like -I) @@ -106,7 +170,9 @@ echo 'cpp_int a = cpp_int("12345678901234567890"); a*a' | ./build/cpp-repl :version show current -std version ``` -## Include & Library (absolute/relative, cmdline & interactive) +Multiline: unbalanced `{ ( [` keeps buffering with `...>` prompt. + +## 📂 Include & Library (absolute/relative, cmdline & interactive) Both **absolute** (`/usr/local/include/mylib.h`) and **relative** (`./include/mylib.h`, `../common/header.h`) are supported. @@ -125,7 +191,7 @@ cpp-repl -I ./include -L ./lib -l mylib --library gmp -D DEBUG=1 cpp-repl -I ./include -L ./lib -l mylib examples/use_mylib.cpp --no-interactive ``` -**Interactive (inside REPL):** +**Interactive:** ```cpp cpp> :I ./rel_include // relative @@ -140,10 +206,9 @@ cpp> extern "C" int mylib_add(int,int); cpp> mylib_add(7,8) // (int) 1015 ``` -Paths are stored and survive `C++` version upgrades (e.g. adding `:I` then `concept` → still keeps includes). +> Paths are stored and survive C++ version upgrades (e.g. `:I` then `concept` → still keeps includes). - -## C++ Version Support (auto-detect) +## 🧬 C++ Version Support (auto-detect) Interpreter auto-detects required `-std`: @@ -151,54 +216,52 @@ Interpreter auto-detects required `-std`: - **C++20** if code contains `concept`, `requires`, `co_await`, `co_yield`, `char8_t`, `<=>`, `consteval` - **C++23** if code contains `import`, `module`/`export` -Example: - ```cpp cpp> template concept Addable = requires(T a,T b){ a+b; }; cpp> Addable auto x = 42; // auto-detects C++20, re-inits to -std=c++20 cpp> import std; // C++23 ``` -No manual `-std` needed. +No manual `-std` needed — upgrades are replay-safe (history is re-executed on new `CompilerInstance`). -## BigInt – Very Large Numbers (GMP/mpz) +## 🔢 BigInt — Very Large Numbers (GMP/mpz) Like Python's arbitrary large ints, via `boost::multiprecision`: ```cpp cpp> cpp_int a = cpp_int("1234567890123456789012345678901234567890"); cpp> cpp_int b = cpp_int("987654321098765432109876543210"); -cpp> cpp_int c = a * b; c // prints big int +cpp> cpp_int c = a * b; c // prints big int via std::cout cpp> bigint x = cpp_int("999999999999999999999999999999"); // alias cpp> x * x ``` -`bigint` is `boost::multiprecision::cpp_int` (header-only). If GMP is installed, -also `boost::multiprecision::mpz_int` / `gmp.hpp` is available and linked via `-lgmp -lgmpxx`. +`bigint` is `boost::multiprecision::cpp_int` (header-only). If GMP is installed, also `mpz_int` / `gmp.hpp` is available and linked via `-lgmp -lgmpxx`. Large literals are auto-wrapped as `cpp_int("...")` to avoid `integer literal is too large`. Demo: `examples/bigint.cpp` - -## Examples +## 📚 Examples See `examples/`: -- `hello.cpp` – basic I/O and value printing -- `functions.cpp` – incremental function definitions (persistence) -- `class.cpp` – structs, classes, templates +- `hello.cpp` — basic I/O and value printing +- `functions.cpp` — incremental function definitions (persistence) +- `class.cpp` — structs, classes, templates +- `bigint.cpp` — big integers +- `version_concept.cpp` — C++20 concepts Load with `:load examples/hello.cpp` or `./build/cpp-repl examples/class.cpp` -## Low-level VM detail +## 🔬 Low-level VM detail -- `include/cpp-repl/core/vm.h` + `src/core/vm.cpp` – raw `llvm::orc::LLJIT` wrapper via `core::VM` interface (pluggable, scalable). Manually builds LLVM IR (`LLVMContext`, `Module`, `IRBuilder`) and JIT-executes without Clang. -- `include/cpp-repl/interpreter/interpreter.h` – `interpreter::Interpreter` wraps `clang::Interpreter` (incremental) + `VersionDetector` + `BigIntSupport` preamble. Handles raw code without `main`. +- `include/cpp-repl/core/vm.h` + `src/core/vm.cpp` — raw `llvm::orc::LLJIT` wrapper via `core::VM` interface (pluggable). Manually builds LLVM IR (`LLVMContext`, `Module`, `IRBuilder`) and JIT-executes without Clang. +- `include/cpp-repl/interpreter/interpreter.h` — `interpreter::Interpreter` wraps `clang::Interpreter` + `VersionDetector` + `BigIntSupport` preamble. Handles raw code without `main`. - Legacy `src/vm.h` / `src/repl.h` kept as wrappers for backward compat. -No optimizations: all builds are `-O0 -g -fno-exceptions -fno-rtti` to stay close to the VM. +All builds are `-O0 -g -fno-exceptions -fno-rtti` to stay close to the VM. -## Project Structure (scalable) +## 🗂️ Project Structure (scalable) ``` include/cpp-repl/ @@ -209,24 +272,28 @@ include/cpp-repl/ cli/cli.h src/ core/vm.cpp - interpreter/interpreter.cpp + interpreter/interpreter.cpp # high-precision float printing (9/17/21 digits) utils/version_detector.cpp, bigint.cpp repl/session.cpp cli/cli.cpp - main.cpp (scalable, delegates to modules) + main.cpp (delegates to modules) examples/ hello.cpp, functions.cpp, class.cpp, bigint.cpp, version_concept.cpp ``` -## CI & Quality +## ✅ CI & Quality -- **CI** (`ci.yml`): Ubuntu 24.04, LLVM 22, `Debug` + `Release`, smoke tests (`-e`, pipe, examples, BigInt, C++20 concepts) +- **CI** (`ci.yml`): Ubuntu 24.04, LLVM 22, `Debug` + `Release`, smoke tests (`-e`, pipe, examples, BigInt, C++20 concepts, float precision) - **Format** (`format.yml`): `clang-format-22` + whitespace + `cppcheck` - **CodeQL** (`codeql.yml`): weekly security analysis (C++) - **Release** (`release.yml`): builds tarball on `v*` tags via `softprops/action-gh-release` Enable branch protection on `main` to require `Build (ubuntu-24.04, Debug, LLVM 22)` before merge. -## License +## 🤝 Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — run `clang-format` and `cmake --build build -j` before pushing. + +## 📄 License MIT — see [LICENSE](LICENSE) diff --git a/capture.png b/capture.png new file mode 100644 index 0000000000000000000000000000000000000000..a30581cd9fda67832d3a1cb65c6fedd6623ddd4b GIT binary patch literal 115822 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≪O=npiR%-sO7tgNgt#4fo4 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 literal 0 HcmV?d00001 From d33c858ffefcca3cc42c2a7fdb13be108b5f6371 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:12:13 +0300 Subject: [PATCH 4/9] docs: add simple Doxygen comments without decorative lines Add @file, @brief, @param, @return Doxygen blocks to all public headers and src files (vm, interpreter, cli, version_detector, bigint, session, main, legacy vm/repl). Use simple /// and /** */ style, avoid ----- and ===== separators. --- include/cpp-repl/cli/cli.h | 47 ++++++++--- include/cpp-repl/core/vm.h | 44 +++++++++- include/cpp-repl/fix_np_headers.hpp | 9 +- include/cpp-repl/fix_proxy.hpp | 7 +- include/cpp-repl/interpreter/interpreter.h | 98 ++++++++++++++++++++-- include/cpp-repl/repl/session.h | 41 ++++++++- include/cpp-repl/utils/bigint.h | 35 ++++++-- include/cpp-repl/utils/version_detector.h | 31 +++++-- src/cli/cli.cpp | 4 + src/core/vm.cpp | 4 + src/main.cpp | 4 + src/repl.h | 39 ++++++--- src/repl/session.cpp | 28 +++++-- src/utils/bigint.cpp | 4 + src/utils/version_detector.cpp | 4 + src/vm.cpp | 4 + src/vm.h | 27 ++++-- 17 files changed, 360 insertions(+), 70 deletions(-) diff --git a/include/cpp-repl/cli/cli.h b/include/cpp-repl/cli/cli.h index 833603c..d09b523 100644 --- a/include/cpp-repl/cli/cli.h +++ b/include/cpp-repl/cli/cli.h @@ -5,23 +5,46 @@ namespace cpprepl { namespace cli { +/** + * @file cli.h + * @brief Command-line parsing for cpp-repl. + */ + +/** + * @brief Parsed command-line options. + */ struct Options { - bool showScaffold = false; - bool noInteractive = false; - bool showHelp = false; - bool showVersion = false; - std::vector execCodes; - std::vector files; - // Include / library support (absolute & relative) - std::vector includePaths; // -I - std::vector libraryPaths; // -L - std::vector libraries; // -l - std::vector defines; // -D - std::vector extraArgs; // -- extra clang args + bool showScaffold = false; ///< Show low-level VM IR demo. + bool noInteractive = false; ///< Exit after -e/file execution. + bool showHelp = false; ///< Print help. + bool showVersion = false; ///< Print version. + std::vector execCodes; ///< Code from -e flags. + std::vector files; ///< Input files. + std::vector includePaths; ///< Include paths (-I). + std::vector libraryPaths; ///< Library search paths (-L). + std::vector libraries; ///< Libraries to link (-l). + std::vector defines; ///< Macro definitions (-D). + std::vector extraArgs; ///< Extra clang args. }; +/** + * @brief Parse command-line arguments. + * @param argc Argument count. + * @param argv Argument values. + * @param err Output error message. + * @return Parsed options. + */ Options parse(int argc, char **argv, std::string &err); + +/** + * @brief Print help to stdout. + * @param prog Program name (argv[0]). + */ void printHelp(const char *prog); + +/** + * @brief Print version to stdout. + */ void printVersion(); } // namespace cli diff --git a/include/cpp-repl/core/vm.h b/include/cpp-repl/core/vm.h index 69b685d..12d1f18 100644 --- a/include/cpp-repl/core/vm.h +++ b/include/cpp-repl/core/vm.h @@ -7,20 +7,58 @@ namespace cpprepl { namespace core { -// Abstract VM interface – scalable, pluggable backends. +/** + * @file vm.h + * @brief Low-level VM abstraction for JIT execution. + */ + +/** + * @brief Abstract VM interface for pluggable JIT backends. + * + * Each backend implements module addition and symbol lookup. + * The default backend is LLJITVM. + */ class VM { public: virtual ~VM() = default; + + /** + * @brief Initialize the VM and native target. + * @param err Output error message on failure. + * @return true on success. + */ virtual bool init(std::string &err) = 0; + + /** + * @brief Add an LLVM IR module to the JIT. + * @param M LLVM module to add. + * @param Ctx LLVM context owning the module. + * @param err Output error message on failure. + * @return true on success. + */ virtual bool addModule(std::unique_ptr M, std::unique_ptr Ctx, std::string &err) = 0; + + /** + * @brief Lookup a JIT symbol by name. + * @param name Symbol name as in IR. + * @return Executor address or error. + */ virtual llvm::Expected lookup(const std::string &name) = 0; + + /** + * @brief Get the underlying LLJIT instance if available. + * @return Pointer to LLJIT or nullptr. + */ virtual llvm::orc::LLJIT *getLLJIT() = 0; }; -// Low-level LLJIT implementation – the default VM. -// No optimizations, O0, pure execution. +/** + * @brief LLJIT-based VM implementation. + * + * Uses llvm::orc::LLJIT with O0, no optimizations, for correctness. + */ class LLJITVM : public VM { public: LLJITVM() = default; diff --git a/include/cpp-repl/fix_np_headers.hpp b/include/cpp-repl/fix_np_headers.hpp index 826ba89..de4661a 100644 --- a/include/cpp-repl/fix_np_headers.hpp +++ b/include/cpp-repl/fix_np_headers.hpp @@ -1,7 +1,10 @@ +/** + * @file fix_np_headers.hpp + * @brief Force-included fixes for Numpy-C-API headers. + * + * Handles NZERO/PZERO clashes and ProxyBase compatibility without modifying upstream headers. + */ #pragma once -// Comprehensive fix for Numpy-C-API headers in cpp-repl -// Force-included via -include before any user code -// Handles NZERO/PZERO, vector, ProxyBase without modifying headers // 1. NZERO/PZERO clash #ifdef NZERO diff --git a/include/cpp-repl/fix_proxy.hpp b/include/cpp-repl/fix_proxy.hpp index c896c15..b1b602a 100644 --- a/include/cpp-repl/fix_proxy.hpp +++ b/include/cpp-repl/fix_proxy.hpp @@ -1,6 +1,9 @@ +/** + * @file fix_proxy.hpp + * @brief Patch for np::ProxyBase to support convert_to and operators. + */ #pragma once -// Fix for np::ProxyBase with boost::multiprecision and other types -// Force-included via -include before any user code to patch ProxyBase +/** @brief Force-included fix for ProxyBase with boost::multiprecision. */ #include #include diff --git a/include/cpp-repl/interpreter/interpreter.h b/include/cpp-repl/interpreter/interpreter.h index 4c01e70..8bf0254 100644 --- a/include/cpp-repl/interpreter/interpreter.h +++ b/include/cpp-repl/interpreter/interpreter.h @@ -12,8 +12,18 @@ class Interpreter; namespace cpprepl { namespace interpreter { -// Scalable interpreter wrapper around clang::Interpreter. -// Handles C++ version auto-detection and BigInt preamble injection. +/** + * @file interpreter.h + * @brief Scalable interpreter wrapper around clang::Interpreter. + */ + +/** + * @brief High-level REPL interpreter with version and BigInt support. + * + * Wraps clang::Interpreter (incremental parsing + ORC JIT), auto-detects + * C++ standard, injects BigInt preamble, and handles include/library paths. + * Supports incremental eval without a main function, like Python REPL. + */ class Interpreter { public: Interpreter(); @@ -22,55 +32,130 @@ class Interpreter { Interpreter(const Interpreter &) = delete; Interpreter &operator=(const Interpreter &) = delete; - // Init with explicit version and include/lib options (absolute & relative) + /** + * @brief Init with explicit version and include/lib options. + * @param version Initial C++ standard. + * @param includePaths Include search paths (-I). + * @param defines Macro definitions (-D). + * @param err Output error. + * @return true on success. + */ bool init(utils::StdVersion version, const std::vector &includePaths, const std::vector &defines, std::string &err); + /** + * @brief Init with full options. + * @param version Initial C++ standard. + * @param includePaths Include paths. + * @param defines Defines. + * @param libraryPaths Library search paths (-L). + * @param libraries Libraries to link (-l). + * @param err Output error. + * @return true on success. + */ bool init(utils::StdVersion version, const std::vector &includePaths, const std::vector &defines, const std::vector &libraryPaths, const std::vector &libraries, std::string &err); + /** @brief Init with version only. */ bool init(utils::StdVersion version, std::string &err); + /** @brief Init with C++23 default. */ bool init(std::string &err) { return init(utils::StdVersion::Cpp23, err); } + /** @brief Init with includes/defines and C++23 default. */ bool init(const std::vector &includePaths, const std::vector &defines, std::string &err) { return init(utils::StdVersion::Cpp23, includePaths, defines, err); } - // Dynamic include/library handling (interactive :I, :L) + /** + * @brief Add an include search path at runtime. + * @param path Absolute or relative path. + * @param err Output error. + * @return true on success, re-inits and replays history. + */ bool addIncludePath(const std::string &path, std::string &err); + /** + * @brief Add a library search path at runtime. + * @param path Absolute or relative path. + * @param err Output error. + * @return true on success. + */ bool addLibraryPath(const std::string &path, std::string &err); + /** + * @brief Load a library by name or path. + * @param lib Library name or file path. + * @param err Output error. + * @return true on success. + */ bool addLibrary(const std::string &lib, std::string &err); - // Auto-detect version from code and re-init if needed (scalable) + /** + * @brief Evaluate code with automatic version detection. + * @param code Raw C++ code. + * @param err Output error. + * @return true on success. + */ bool evalAuto(const std::string &code, std::string &err); - // Core eval – python-like: no main needed, raw code + /** + * @brief Evaluate raw C++ code incrementally. + * @param code C++ source without main. + * @param err Output error. + * @return true on success. + */ bool eval(const std::string &code, std::string &err); + /** + * @brief Evaluate with incomplete-input detection. + * @param code Code buffer. + * @param err Output error. + * @param incomplete Set to true if braces/parens are unbalanced. + * @return true if handled. + */ bool eval(const std::string &code, std::string &err, bool &incomplete); + /** @brief Load and execute a raw C++ file. */ bool loadFile(const std::string &path, std::string &err); + /** @brief Load a dynamic library. */ bool loadLibrary(const std::string &path, std::string &err); + /** + * @brief Undo last N inputs. + * @param n Number of PTUs to remove. + * @param err Output error. + * @return true on success. + */ bool undo(unsigned n, std::string &err); + /** @brief Dump history and current version to stdout. */ void dump() const; + /** @brief Reset interpreter state. */ void reset(std::string &err); + /** @brief Print help text. */ void help() const; + /** @brief Number of history entries. */ size_t historySize() const { return history_.size(); } + /** @brief Current C++ standard. */ utils::StdVersion currentVersion() const { return currentVersion_; } private: + /** @brief Ensure at least the needed C++ version, re-init if higher. */ bool ensureVersion(utils::StdVersion needed, std::string &err); + /** @brief Re-init with current options and replay history. */ bool reinitWithCurrentOptions(std::string &err); + /** @brief Sanitize include directives (e.g., strip trailing semicolon). */ std::string sanitizeIncludes(const std::string &code); + /** @brief Reject redefinition with different value. */ bool checkVariableRedefinition(const std::string &code, std::string &err); + /** @brief Track variable declarations for redefinition checks. */ void trackVariable(const std::string &code); + /** @brief Parse a declaration into type/name/value. */ bool parseDeclaration(const std::string &code, std::string &type, std::string &name, std::string &value); + /** @brief Parse an assignment into name/value. */ bool parseAssignment(const std::string &code, std::string &name, std::string &value); + /** @brief Normalize a value string for comparison. */ std::string normalizeValue(const std::string &v); std::unique_ptr interp_; bool initialized_ = false; @@ -80,7 +165,6 @@ class Interpreter { std::vector defines_; std::vector libraryPaths_; std::vector libraries_; - // Storage for compiler args c_str() lifetime std::vector compilerArgsStorage_; std::unordered_map> variables_; std::vector>> varHistory_; diff --git a/include/cpp-repl/repl/session.h b/include/cpp-repl/repl/session.h index 0801be7..bc565ad 100644 --- a/include/cpp-repl/repl/session.h +++ b/include/cpp-repl/repl/session.h @@ -5,21 +5,54 @@ namespace cpprepl { namespace repl { -// Scalable REPL session – handles prompts, multiline, commands. -// Like Python's REPL, but for C++ raw code. +/** + * @file session.h + * @brief Interactive REPL session handling. + */ + +/** + * @brief Manages the interactive prompt, history, and commands. + * + * Delegates execution to interpreter::Interpreter and provides Python-like + * REPL behavior for raw C++ input. + */ class Session { public: + /** + * @brief Construct a session bound to an interpreter. + * @param interp Reference to initialized interpreter. + */ explicit Session(interpreter::Interpreter &interp); ~Session() = default; - // Run interactive loop reading from stdin + /** + * @brief Run the interactive loop reading from stdin. + * + * Handles prompts cpp> / ...>, line buffering, and :commands. + */ void runInteractive(); - // Execute single line (used for -e and tests) + /** + * @brief Execute a single code snippet. + * @param code Raw C++ code. + * @param err Output error message. + * @return true on success. + */ bool exec(const std::string &code, std::string &err); private: + /** + * @brief Handle a colon command like :help or :I. + * @param line Input line. + * @param err Output error. + * @return true if line was a command. + */ bool handleCommand(const std::string &line, std::string &err); + /** + * @brief Check if buffer has unbalanced braces/parens. + * @param buffer Current input buffer. + * @return true if more input is needed. + */ bool isIncomplete(const std::string &buffer) const; interpreter::Interpreter &interp_; diff --git a/include/cpp-repl/utils/bigint.h b/include/cpp-repl/utils/bigint.h index facd5b8..fb24ef5 100644 --- a/include/cpp-repl/utils/bigint.h +++ b/include/cpp-repl/utils/bigint.h @@ -4,22 +4,41 @@ namespace cpprepl { namespace utils { -// Big integer support – like Python's arbitrary large ints. -// Uses boost::multiprecision::cpp_int (header-only) and GMP if available. -// Scalable: header provides preamble to inject into REPL. +/** + * @file bigint.h + * @brief Big integer support via boost::multiprecision. + */ + +/** + * @brief Provides BigInt preamble and helpers for the REPL. + * + * Injects boost::multiprecision headers so users can write cpp_int / bigint + * directly without manual includes, similar to Python's big ints. + */ class BigIntSupport { public: - // Preamble injected at REPL init for transparent bigint support. - // Users can then write: bigint x = 12345678901234567890_cpp_int; or use cpp_int directly. + /** + * @brief Preamble with cpp_int and bigint alias. + * @return C++ code injected at interpreter init. + */ static const char *preamble(); - // Alternative GMP preamble (uses mpz_int which wraps libgmp) + /** + * @brief GMP-specific preamble (mpz_int). + * @return C++ code for GMP backend. + */ static const char *gmpPreamble(); - // Check if boost headers are available (always true in our build) + /** + * @brief Check if boost headers are available. + * @return true if usable. + */ static bool isAvailable(); - // Example large number for testing + /** + * @brief Demo code for documentation and tests. + * @return Example snippet using big ints. + */ static std::string demoCode(); }; diff --git a/include/cpp-repl/utils/version_detector.h b/include/cpp-repl/utils/version_detector.h index b69a6c2..e2c009c 100644 --- a/include/cpp-repl/utils/version_detector.h +++ b/include/cpp-repl/utils/version_detector.h @@ -5,20 +5,37 @@ namespace cpprepl { namespace utils { +/** + * @file version_detector.h + * @brief Automatic C++ standard detection. + */ + +/** + * @brief Supported C++ standards. + */ enum class StdVersion { - Cpp17 = 17, - Cpp20 = 20, - Cpp23 = 23 + Cpp17 = 17, ///< C++17 + Cpp20 = 20, ///< C++20 + Cpp23 = 23 ///< C++23 }; +/** + * @brief Detects required C++ standard from source keywords. + */ class VersionDetector { public: - // Auto-detect required C++ standard from code keywords. - // Like python's future imports, but for C++. + /** + * @brief Detect standard needed for given code. + * @param code Source snippet to inspect. + * @return Required StdVersion (minimum is Cpp17). + */ static StdVersion detect(const std::string &code); + /** @brief Convert version to compiler flag, e.g. -std=c++20. */ static std::string toFlag(StdVersion v); + /** @brief Convert version to human string, e.g. C++20. */ static std::string toString(StdVersion v); + /** @brief Human description with features. */ static std::string describe(StdVersion v); private: @@ -26,7 +43,9 @@ class VersionDetector { static bool containsWord(const std::string &code, const std::string &word); }; -// Helper to get current interpreter version as string for diagnostics +/** + * @brief Return the maximum of two versions. + */ StdVersion maxVersion(StdVersion a, StdVersion b); } // namespace utils diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index df546fb..95f77ab 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1,3 +1,7 @@ +/** + * @file cli.cpp + * @brief CLI parsing implementation for cpp-repl. + */ #include "cpp-repl/cli/cli.h" #include diff --git a/src/core/vm.cpp b/src/core/vm.cpp index db74c28..7ede638 100644 --- a/src/core/vm.cpp +++ b/src/core/vm.cpp @@ -1,3 +1,7 @@ +/** + * @file vm.cpp + * @brief LLJITVM implementation - thin wrapper over llvm::orc::LLJIT. + */ #include "cpp-repl/core/vm.h" #include "llvm/Support/Error.h" #include "llvm/Support/TargetSelect.h" diff --git a/src/main.cpp b/src/main.cpp index 30af5b5..04de291 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,7 @@ +/** + * @file main.cpp + * @brief Entry point for cpp-repl - CLI, VM scaffold, and REPL session. + */ #include "cpp-repl/cli/cli.h" #include "cpp-repl/core/vm.h" #include "cpp-repl/interpreter/interpreter.h" diff --git a/src/repl.h b/src/repl.h index 11eed7b..b31d22f 100644 --- a/src/repl.h +++ b/src/repl.h @@ -1,3 +1,7 @@ +/** + * @file repl.h + * @brief Legacy high-level REPL wrapper. + */ #ifndef CPP_REPL_REPL_H #define CPP_REPL_REPL_H @@ -11,8 +15,11 @@ class Interpreter; namespace repl { -/// High-level C++ REPL built on top of the low-level VM (clang::Interpreter -/// which internally uses llvm::orc::LLJIT). No optimization, correctness first. +/** + * @brief High-level C++ REPL built on clang::Interpreter. + * + * Uses llvm::orc::LLJIT internally, O0 and correctness first. + */ class Repl { public: Repl(); @@ -21,28 +28,40 @@ class Repl { Repl(const Repl &) = delete; Repl &operator=(const Repl &) = delete; + /** @brief Initialize the interpreter. */ bool init(std::string &err); - // Execute a single REPL line / block. If Value is produced, it is printed. - // Returns false on error, true on success. + /** + * @brief Execute a REPL line or block and print Value if produced. + * @param code Input code. + * @param err Output error. + * @return true on success. + */ bool eval(const std::string &code, std::string &err); - // REPL commands + /** @brief Dump history. */ void dump() const; + /** @brief Reset state. */ void reset(std::string &err); + /** @brief Print help. */ void help() const; - // Load and execute a file's contents + /** @brief Load and execute a file. */ bool loadFile(const std::string &path, std::string &err); - // Dynamic library loading (wraps Interpreter::LoadDynamicLibrary) + /** @brief Load a dynamic library. */ bool loadLibrary(const std::string &path, std::string &err); - // Undo last N inputs (wraps Interpreter::Undo) + /** @brief Undo last N inputs. */ bool undo(unsigned n, std::string &err); - // Try incremental eval: returns true if code was incomplete and should - // continue buffering (instead of error). Used by REPL loop. + /** + * @brief Eval with incomplete detection for REPL loop. + * @param code Input code. + * @param err Output error. + * @param incomplete Set if more input is needed. + * @return true if handled. + */ bool eval(const std::string &code, std::string &err, bool &incomplete); size_t historySize() const { return history_.size(); } diff --git a/src/repl/session.cpp b/src/repl/session.cpp index 461531b..3dd6aef 100644 --- a/src/repl/session.cpp +++ b/src/repl/session.cpp @@ -1,3 +1,7 @@ +/** + * @file session.cpp + * @brief Interactive session loop and command handling. + */ #include "cpp-repl/repl/session.h" #include #include @@ -298,13 +302,6 @@ void Session::runInteractive() { continue; } } - if (!line.empty()) { - HIST_ENTRY *last = history_get(history_length); - if (!last || std::string(last->line) != line) { - add_history(line.c_str()); - if (!histFile.empty()) append_history(1, histFile.c_str()); - } - } buffer_ += line + "\n"; bool incomplete = false; if (!line.empty() && line.back() == '\\') @@ -314,6 +311,23 @@ void Session::runInteractive() { if (incomplete) { continue; } + // Complete input ready: store entire buffer as single history entry + // so that up/down navigation recalls whole function bodies at once + // instead of line-by-line. This makes a multi-line function definition + // (e.g. "int foo() {\n return 42;\n}") appear as one history item. + { + std::string histEntry = buffer_; + while (!histEntry.empty() && + (histEntry.back() == '\n' || histEntry.back() == '\r')) + histEntry.pop_back(); + if (!histEntry.empty()) { + HIST_ENTRY *last = history_get(history_length); + if (!last || histEntry != last->line) { + add_history(histEntry.c_str()); + if (!histFile.empty()) append_history(1, histFile.c_str()); + } + } + } std::string err; if (!interp_.eval(buffer_, err)) { if (!err.empty()) diff --git a/src/utils/bigint.cpp b/src/utils/bigint.cpp index cb8ef7a..5eb2528 100644 --- a/src/utils/bigint.cpp +++ b/src/utils/bigint.cpp @@ -1,3 +1,7 @@ +/** + * @file bigint.cpp + * @brief BigInt preamble definitions. + */ #include "cpp-repl/utils/bigint.h" namespace cpprepl { diff --git a/src/utils/version_detector.cpp b/src/utils/version_detector.cpp index da76e9a..61336b4 100644 --- a/src/utils/version_detector.cpp +++ b/src/utils/version_detector.cpp @@ -1,3 +1,7 @@ +/** + * @file version_detector.cpp + * @brief Detects required C++ standard from source keywords. + */ #include "cpp-repl/utils/version_detector.h" #include diff --git a/src/vm.cpp b/src/vm.cpp index 13247ae..b0b0b77 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -1,3 +1,7 @@ +/** + * @file vm.cpp + * @brief Legacy VM wrapper for llvm::orc::LLJIT. + */ #include "vm.h" #include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h" diff --git a/src/vm.h b/src/vm.h index 0b663a5..7b0619f 100644 --- a/src/vm.h +++ b/src/vm.h @@ -1,3 +1,7 @@ +/** + * @file vm.h + * @brief Legacy low-level VM wrapper. + */ #ifndef CPP_REPL_VM_H #define CPP_REPL_VM_H @@ -10,9 +14,12 @@ namespace vm { -/// Low-level VM wrapping llvm::orc::LLJIT. -/// No optimizations – O0, pure execution. -/// This is the raw execution layer; C++ REPL builds on top. +/** + * @brief Low-level VM wrapping llvm::orc::LLJIT. + * + * No optimizations, O0, pure execution. + * Raw execution layer that the C++ REPL builds on. + */ class VM { public: VM(); @@ -21,18 +28,22 @@ class VM { VM(const VM &) = delete; VM &operator=(const VM &) = delete; - // Initialize JIT – must be called once. + /** @brief Initialize JIT, must be called once. */ bool init(std::string &err); - // Add a Module (takes ownership). Module is moved into ThreadSafeModule. + /** + * @brief Add a module to the JIT. + * @param M Module to move. + * @param Ctx Context for the module. + * @param err Output error. + * @return true on success. + */ bool addModule(std::unique_ptr M, std::unique_ptr Ctx, std::string &err); - // Lookup symbol address (mangled IR name) + /** @brief Lookup symbol address by IR name. */ llvm::Expected lookup(const std::string &name); - // For debugging: dump current state not needed at this level - llvm::orc::LLJIT *getLLJIT() { return jit_.get(); } private: From fc922441765f787b356198f70d203d7b4261c6e7 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:19:54 +0300 Subject: [PATCH 5/9] chore: ignore Testing/ artifacts Add Testing/ to .gitignore to avoid committing CTest generated CTestCostData.txt and LastTest.log (ephemeral test output). --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5710deb..eb60faf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ compile_commands.json .DS_Store .cache/ ccache/ +Testing/ From a257e46a7fd321e3117225ee0208941c8084c735 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:23:09 +0300 Subject: [PATCH 6/9] feat: colored prompt with C++ version, timing and no-color support Add prompt helpers (shouldUseColor, formatDuration, buildPrimaryPrompt, buildContinuationPrompt, printTimingLine) to Session, timing in exec(), colored help and prompt with version and execution time, --no-color flag and NO_COLOR/FORCE_COLOR env handling. Use simple Doxygen comments. --- include/cpp-repl/cli/cli.h | 1 + include/cpp-repl/repl/session.h | 19 ++- src/cli/cli.cpp | 8 ++ src/interpreter/interpreter.cpp | 18 ++- src/main.cpp | 24 +++- src/repl/session.cpp | 227 +++++++++++++++++++++++++------- 6 files changed, 249 insertions(+), 48 deletions(-) diff --git a/include/cpp-repl/cli/cli.h b/include/cpp-repl/cli/cli.h index d09b523..b13f7c9 100644 --- a/include/cpp-repl/cli/cli.h +++ b/include/cpp-repl/cli/cli.h @@ -18,6 +18,7 @@ struct Options { bool noInteractive = false; ///< Exit after -e/file execution. bool showHelp = false; ///< Print help. bool showVersion = false; ///< Print version. + bool noColor = false; ///< Disable colored prompt/timing. std::vector execCodes; ///< Code from -e flags. std::vector files; ///< Input files. std::vector includePaths; ///< Include paths (-I). diff --git a/include/cpp-repl/repl/session.h b/include/cpp-repl/repl/session.h index bc565ad..43b20e3 100644 --- a/include/cpp-repl/repl/session.h +++ b/include/cpp-repl/repl/session.h @@ -55,8 +55,23 @@ class Session { */ bool isIncomplete(const std::string &buffer) const; - interpreter::Interpreter &interp_; - std::string buffer_; + /** @brief Whether to use ANSI color for prompt. */ + bool shouldUseColor(bool forReadline) const; + /** @brief Format duration in ms for display. */ + std::string formatDuration(double ms) const; + /** @brief Build primary prompt string. */ + std::string buildPrimaryPrompt(bool forReadline) const; + /** @brief Build continuation prompt string. */ + std::string buildContinuationPrompt(bool forReadline) const; + /** @brief Print timing line after execution. */ + void printTimingLine(bool success, double ms) const; + + interpreter::Interpreter &interp_; ///< Bound interpreter instance. + std::string buffer_; ///< Current multiline buffer. + int promptCount_ = 1; ///< Prompt counter for numbered prompts. + double lastDurationMs_ = 0.0; ///< Last execution time in ms. + bool lastSuccess_ = true; ///< Last execution success flag. + bool hasLastTiming_ = false; ///< Whether timing is available. }; } // namespace repl diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 95f77ab..867eb19 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -33,6 +33,10 @@ Options parse(int argc, char **argv, std::string &err) { opts.showScaffold = false; else if (arg == "--no-interactive") opts.noInteractive = true; + else if (arg == "--no-color" || arg == "--nocolor" || arg == "--color=never" || arg == "--color=none") + opts.noColor = true; + else if (arg == "--color" || arg == "--color=always" || arg == "--color=auto") + opts.noColor = false; else if (arg == "-e") { if (i + 1 >= argc) { err = "-e requires argument"; return opts; } opts.execCodes.push_back(argv[++i]); @@ -107,6 +111,8 @@ void printHelp(const char *prog) { " -v, --version show version\n" " --scaffold show low-level VM scaffold\n" " --no-interactive exit after file/-e execution\n" + " --no-color disable colored prompt & timing\n" + " --color[=when] force color (always/auto/never)\n" " -e execute raw C++ code\n" " -I , -I add include search path (absolute or relative)\n" " -L , -L add library search path\n" @@ -118,6 +124,8 @@ void printHelp(const char *prog) { "REPL: :help :dump :reset :load :lib :I :L :version :quit\n" " :I add include path (interactive)\n" " :L add library path\n" + "Prompt: colored cpp:C++17/20/23 [n] (time ✓/✗)> with execution time\n" + " honor NO_COLOR / CPP_REPL_NO_COLOR=1 and --no-color, TERM=dumb\n" "C++ versions: auto-detects C++20/23 keywords " "(concept/requires/import)\n" "BigInt: boost::multiprecision::cpp_int / bigint\n" diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index c497a20..3bcf723 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -19,6 +19,10 @@ #include #include #include +#include +#ifndef _WIN32 +#include +#endif namespace { /** @@ -1091,9 +1095,20 @@ void Interpreter::reset(std::string &err) { err.clear(); } void Interpreter::help() const { + // Show prompt help with color hint when stdout is a tty + bool useColor = isatty(STDOUT_FILENO) && + !getenv("NO_COLOR") && !getenv("CPP_REPL_NO_COLOR"); + const char *term = getenv("TERM"); + if (useColor && term && std::string(term) == "dumb") useColor = false; + auto col = [&](const char* code)->std::string { return useColor ? code : ""; }; + auto rst = col("\033[0m"); + auto cyan = col("\033[36m"); + auto grey = col("\033[90m"); std::cout << "C++ REPL (LLVM VM, O0, no optimizations) [" << utils::VersionDetector::toString(currentVersion_) << "]\n" + "Prompt: " + cyan + "cpp" + rst + grey + ":" + rst + cyan + utils::VersionDetector::toString(currentVersion_) + rst + grey + " [n] (time " + rst + col("\033[32m") + "✓" + rst + grey + "/" + rst + col("\033[31m") + "✗" + rst + grey + ")" + rst + grey + ">" + rst + " colored, shows C++ version, input count & last exec time\n" + " use " + grey + "--no-color" + rst + " or " + grey + "NO_COLOR=1" + rst + " to disable, " + grey + "FORCE_COLOR=1" + rst + " to force\n" "Commands:\n" " :help :h show this help\n" " :quit :exit :q exit REPL\n" @@ -1126,7 +1141,8 @@ void Interpreter::help() const { "C++20/23: auto-detects 'concept', 'requires', 'import' etc. " "and switches to -std=c++20/23\n" "\n" - "Multiline: unbalanced { ( [ keeps buffering with ...> prompt\n"; + "Multiline: unbalanced { ( [ keeps buffering with " + col("\033[33m") + "...>" + rst + " prompt\n" + "Timing: " + grey + "⏱" + rst + " line after each exec + inline in next prompt (e.g. " + grey + "(12.3ms ✓)" + rst + ")\n"; } } // namespace interpreter diff --git a/src/main.cpp b/src/main.cpp index 04de291..0cec16e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,10 @@ #include #include +#include +#ifndef _WIN32 +#include +#endif int main(int argc, char **argv) { std::string cliErr; @@ -126,7 +130,25 @@ int main(int argc, char **argv) { if (opts.noInteractive) return 0; - // Show help and current capabilities + // Honor --no-color early (sets env for Session::shouldUseColor) + if (opts.noColor) { + setenv("CPP_REPL_NO_COLOR", "1", 1); + setenv("NO_COLOR", "1", 1); + } + + // Show help and current capabilities (with subtle color when enabled) + { + bool useColor = !opts.noColor && isatty(STDOUT_FILENO) && + !getenv("NO_COLOR") && !getenv("CPP_REPL_NO_COLOR"); + const char *term = getenv("TERM"); + if (useColor && term && std::string(term) == "dumb") useColor = false; + if (useColor) { + std::cout << "\033[90m— cpp-repl \033[1;36mC++ REPL\033[0m\033[90m " + "(LLVM 22, O0) — type \033[33m:help\033[90m, " + "\033[33m:quit\033[90m, prompt shows \033[36mcpp:C++\033[90m" + " version, [n] and \033[32m⏱ time\033[90m —\033[0m\n"; + } + } interp.help(); std::cout << "BigInt available: " << (cpprepl::utils::BigIntSupport::isAvailable() diff --git a/src/repl/session.cpp b/src/repl/session.cpp index 3dd6aef..9dad9e1 100644 --- a/src/repl/session.cpp +++ b/src/repl/session.cpp @@ -3,7 +3,11 @@ * @brief Interactive session loop and command handling. */ #include "cpp-repl/repl/session.h" +#include "cpp-repl/utils/version_detector.h" +#include +#include #include +#include #include #include #ifdef _WIN32 @@ -23,7 +27,134 @@ namespace repl { Session::Session(interpreter::Interpreter &interp) : interp_(interp) {} bool Session::exec(const std::string &code, std::string &err) { - return interp_.eval(code, err); + auto t0 = std::chrono::steady_clock::now(); + bool ok = interp_.eval(code, err); + auto t1 = std::chrono::steady_clock::now(); + lastDurationMs_ = std::chrono::duration(t1 - t0).count(); + lastSuccess_ = ok; + hasLastTiming_ = true; + ++promptCount_; + return ok; +} + +// ── Prompt helpers ────────────────────────────────────────────────────────── +bool Session::shouldUseColor(bool /*forReadline*/) const { + if (std::getenv("NO_COLOR") || std::getenv("CPP_REPL_NO_COLOR") || + std::getenv("NO_COLOUR")) + return false; + if (std::getenv("FORCE_COLOR") || std::getenv("CLICOLOR_FORCE")) + return true; + const char *term = std::getenv("TERM"); + if (term && std::string(term) == "dumb") + return false; +#ifdef _WIN32 + return _isatty(_fileno(stdout)) != 0; +#else + return isatty(STDOUT_FILENO) != 0; +#endif +} + +std::string Session::formatDuration(double ms) const { + std::ostringstream oss; + oss << std::fixed << std::setprecision(2); + if (ms < 1.0) { + oss << std::setprecision(2) << ms << "ms"; + } else if (ms < 1000.0) { + oss << std::setprecision(1) << ms << "ms"; + } else if (ms < 60000.0) { + oss << std::setprecision(2) << (ms / 1000.0) << "s"; + } else { + int totalSec = static_cast(ms / 1000.0); + int minutes = totalSec / 60; + double secs = (ms / 1000.0) - minutes * 60; + oss << minutes << "m" << std::fixed << std::setprecision(1) << secs << "s"; + } + return oss.str(); +} + +std::string Session::buildPrimaryPrompt(bool forReadline) const { + bool color = shouldUseColor(forReadline); + auto wrap = [&](const char *code) -> std::string { + if (!color) return ""; + if (forReadline) return std::string("\001") + code + "\002"; + return std::string(code); + }; + const std::string RST = wrap("\033[0m"); + const std::string BOLD_CYAN = wrap("\033[1;36m"); + const std::string DIM = wrap("\033[2m"); + const std::string DIM_GREY = wrap("\033[90m"); + const std::string YELLOW = wrap("\033[33m"); + const std::string GREEN = wrap("\033[32m"); + const std::string RED = wrap("\033[31m"); + + std::string verStr = utils::VersionDetector::toString(interp_.currentVersion()); + std::string verCol; + if (interp_.currentVersion() == utils::StdVersion::Cpp23) + verCol = wrap("\033[1;35m"); + else if (interp_.currentVersion() == utils::StdVersion::Cpp20) + verCol = YELLOW; + else + verCol = DIM_GREY; + + std::string out; + if (color) { + out += BOLD_CYAN + "cpp" + RST; + out += DIM_GREY + ":" + RST + verCol + verStr + RST; + out += " " + DIM + "[" + std::to_string(promptCount_) + "]" + RST; + if (hasLastTiming_) { + std::string t = formatDuration(lastDurationMs_); + std::string statusCol = lastSuccess_ ? GREEN : RED; + std::string sym = lastSuccess_ ? "✓" : "✗"; + out += " " + DIM_GREY + "(" + RST + statusCol + t + " " + sym + RST + DIM_GREY + ")" + RST; + } + out += DIM_GREY + ">" + RST + " "; + } else { + out = "cpp:" + verStr + " [" + std::to_string(promptCount_) + "]"; + if (hasLastTiming_) { + out += " (" + formatDuration(lastDurationMs_) + (lastSuccess_ ? " ok" : " err") + ")"; + } + out += "> "; + } + return out; +} + +std::string Session::buildContinuationPrompt(bool forReadline) const { + bool color = shouldUseColor(forReadline); + auto wrap = [&](const char *code) -> std::string { + if (!color) return ""; + if (forReadline) return std::string("\001") + code + "\002"; + return std::string(code); + }; + const std::string RST = wrap("\033[0m"); + const std::string DIM_YELLOW = wrap("\033[2;33m"); + if (color) { + return DIM_YELLOW + "...>" + RST + " "; + } else { + return "...> "; + } +} + +void Session::printTimingLine(bool success, double ms) const { +#ifdef _WIN32 + bool isTTY = _isatty(_fileno(stdout)) != 0; +#else + bool isTTY = isatty(STDOUT_FILENO) != 0; +#endif + if (!isTTY) return; + bool color = shouldUseColor(false); + std::string t = formatDuration(ms); + if (color) { + const char *grey = "\033[90m"; + const char *green = "\033[32m"; + const char *red = "\033[31m"; + const char *rst = "\033[0m"; + const char *symCol = success ? green : red; + const char *sym = success ? "✓" : "✗"; + std::cout << grey << "⏱ " << t << " " << symCol << sym << grey << rst << "\n"; + } else { + std::cout << "⏱ " << t << (success ? " ok" : " err") << "\n"; + } + std::cout << std::flush; } bool Session::isIncomplete(const std::string &buffer) const { @@ -93,32 +224,25 @@ bool Session::handleCommand(const std::string &line, std::string &err) { interp_.reset(err); if (!err.empty()) std::cerr << "reset error: " << err << "\n"; - else + else { std::cout << "[reset]\n"; + promptCount_ = 1; + hasLastTiming_ = false; + } return true; } if (t == ":clear" || t == ":cls" || t == ":c" || t == "clear" || t == "cls") { - // Robust cross-platform terminal clear: works on every terminal type - // 1. isatty check – when piped/redirected there is no screen to clear - // 2. HAS_READLINE – use terminfo-aware rl_clear_screen when available - // 3. Windows: Win32 Console API (no external process), fallback to cls - // 4. POSIX: TERM=dumb -> newlines; TERM set -> `clear` (terminfo), - // fallback to ANSI with scrollback clear, fallback to newlines #ifdef HAS_READLINE #ifdef _WIN32 if (_isatty(_fileno(stdin)) || _isatty(_fileno(stdout))) { #else if (isatty(STDOUT_FILENO) || isatty(STDIN_FILENO)) { #endif - // readline's clear is terminfo-aware (honors TERM, works in tmux/screen/xterm) rl_clear_screen(0, 0); rl_on_new_line(); - // Also emit ANSI / system clear so non-readline output and scrollback are cleared } #endif #ifdef _WIN32 - // Windows: try native Console API first – works even without `cls` in PATH - // and on all Windows consoles (cmd.exe, PowerShell, Windows Terminal) { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut != INVALID_HANDLE_VALUE && hOut != nullptr) { @@ -131,7 +255,6 @@ bool Session::handleCommand(const std::string &line, std::string &err) { FillConsoleOutputAttribute(hOut, csbi.wAttributes, cells, topLeft, &written); SetConsoleCursorPosition(hOut, topLeft); } else { - // API failed (e.g., redirected handle) – fallback if (std::system("cls") != 0) { std::cout << "\033[2J\033[3J\033[H" << std::flush; } @@ -143,29 +266,19 @@ bool Session::handleCommand(const std::string &line, std::string &err) { } } #else - // POSIX: check if we are actually attached to a terminal { bool isTTY = isatty(STDOUT_FILENO) || isatty(STDERR_FILENO) || isatty(STDIN_FILENO); if (!isTTY) { - // Piped / redirected / non-interactive – simulate clear with newlines std::cout << std::string(100, '\n') << std::flush; } else { const char *term = std::getenv("TERM"); std::string termStr = term ? term : ""; if (termStr.empty() || termStr == "dumb") { - // Dumb terminal (e.g., emacs shell, dumb) – ANSI not supported std::cout << std::string(100, '\n') << std::flush; } else { - // Try terminfo-based `clear` – honors TERM (xterm-256color, screen, tmux, etc.) - // Returns 0 on success; non-zero if `clear` missing or TERM unknown int rc = std::system("clear"); if (rc != 0) { - // Fallback 1: ANSI – works on virtually all modern terminals - // \033[2J clear screen, \033[3J clear scrollback, \033[H home cursor std::cout << "\033[2J\033[3J\033[H" << std::flush; - // Fallback 2: if ANSI somehow not honored, ensure visual separation - // Heuristic: if TERM is very minimal, also push with newlines is redundant - // so we rely on ANSI alone here. The 100-newline fallback above covers dumb. } } } @@ -193,7 +306,6 @@ bool Session::handleCommand(const std::string &line, std::string &err) { std::cout << "usage: :lib (absolute, relative, or -l name)\n"; else { std::string e; - // Use addLibrary for bare names (searches -L paths), else loadLibrary for direct path bool ok = false; if (path.find('/') != std::string::npos || path.find(".so") != std::string::npos) { ok = interp_.loadLibrary(path, e); @@ -220,17 +332,19 @@ bool Session::handleCommand(const std::string &line, std::string &err) { std::string e; if (!interp_.undo(n, e)) std::cerr << "undo error: " << e << "\n"; - else + else { std::cout << "[undid " << n << "]\n"; + if (promptCount_ > (int)n + 1) promptCount_ -= n; + else promptCount_ = 1; + hasLastTiming_ = false; + } return true; } - // Include path handling (absolute & relative) – interactive :I / :include if (t.rfind(":I", 0) == 0 || t.rfind(":include", 0) == 0 || t.rfind(":inc", 0) == 0) { std::string path; if (t.rfind(":I", 0) == 0) path = trim(t.substr(2)); else if (t.rfind(":include", 0) == 0) path = trim(t.substr(8)); else path = trim(t.substr(4)); - // Support ":I=path" or ":I path" if (!path.empty() && path[0] == '=') path = trim(path.substr(1)); if (path.empty()) { std::cout << "usage: :I (add include path, absolute or relative)\n"; return true; } std::string e; @@ -265,6 +379,14 @@ void Session::runInteractive() { return s.substr(a, b - a + 1); }; + auto printError = [&](const std::string &msg) { + bool color = shouldUseColor(false); + if (color) + std::cerr << "\033[31merror:\033[0m " << msg << "\n"; + else + std::cerr << "error: " << msg << "\n"; + }; + #ifdef HAS_READLINE bool useReadline = isatty(STDIN_FILENO); if (useReadline) { @@ -275,8 +397,9 @@ void Session::runInteractive() { if (!histFile.empty()) read_history(histFile.c_str()); char *raw = nullptr; while (true) { - const char *prompt = buffer_.empty() ? "cpp> " : "...> "; - raw = readline(prompt); + std::string promptStr = buffer_.empty() ? buildPrimaryPrompt(true) + : buildContinuationPrompt(true); + raw = readline(promptStr.c_str()); if (!raw) { std::cout << "\nbye\n"; break; @@ -311,10 +434,6 @@ void Session::runInteractive() { if (incomplete) { continue; } - // Complete input ready: store entire buffer as single history entry - // so that up/down navigation recalls whole function bodies at once - // instead of line-by-line. This makes a multi-line function definition - // (e.g. "int foo() {\n return 42;\n}") appear as one history item. { std::string histEntry = buffer_; while (!histEntry.empty() && @@ -329,10 +448,20 @@ void Session::runInteractive() { } } std::string err; - if (!interp_.eval(buffer_, err)) { - if (!err.empty()) - std::cerr << "error: " << err << "\n"; + auto t0 = std::chrono::steady_clock::now(); + bool ok = interp_.eval(buffer_, err); + auto t1 = std::chrono::steady_clock::now(); + double ms = std::chrono::duration(t1 - t0).count(); + lastDurationMs_ = ms; + lastSuccess_ = ok; + hasLastTiming_ = true; + if (!ok) { + if (!err.empty()) printError(err); + printTimingLine(false, ms); + } else { + printTimingLine(true, ms); } + ++promptCount_; buffer_.clear(); } if (!histFile.empty()) write_history(histFile.c_str()); @@ -340,7 +469,7 @@ void Session::runInteractive() { } #endif std::string line; - std::cout << "cpp> " << std::flush; + std::cout << buildPrimaryPrompt(false) << std::flush; while (std::getline(std::cin, line)) { std::string t = trim(line); if (buffer_.empty()) { @@ -348,12 +477,12 @@ void Session::runInteractive() { t == "exit" || t == "quit") break; if (t.empty()) { - std::cout << "cpp> " << std::flush; + std::cout << buildPrimaryPrompt(false) << std::flush; continue; } std::string err; if (handleCommand(line, err)) { - std::cout << "cpp> " << std::flush; + std::cout << buildPrimaryPrompt(false) << std::flush; continue; } } @@ -364,16 +493,26 @@ void Session::runInteractive() { else if (isIncomplete(buffer_)) incomplete = true; if (incomplete) { - std::cout << "...> " << std::flush; + std::cout << buildContinuationPrompt(false) << std::flush; continue; } std::string err; - if (!interp_.eval(buffer_, err)) { - if (!err.empty()) - std::cerr << "error: " << err << "\n"; + auto t0 = std::chrono::steady_clock::now(); + bool ok = interp_.eval(buffer_, err); + auto t1 = std::chrono::steady_clock::now(); + double ms = std::chrono::duration(t1 - t0).count(); + lastDurationMs_ = ms; + lastSuccess_ = ok; + hasLastTiming_ = true; + if (!ok) { + if (!err.empty()) printError(err); + printTimingLine(false, ms); + } else { + printTimingLine(true, ms); } + ++promptCount_; buffer_.clear(); - std::cout << "cpp> " << std::flush; + std::cout << buildPrimaryPrompt(false) << std::flush; } std::cout << "\nbye\n"; } From 4076b4e94912f4bd825f86a58f219df9b91222af Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 18:37:56 +0300 Subject: [PATCH 7/9] feat: keyword highlight after execution (syntax colors for types, keywords, strings, numbers) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - highlight utility colors C++ keywords (magenta), types (bold cyan), strings (green), numbers (yellow), comments/preprocessor (grey/yellow) - echo executed input with highlight (▸) after each execution when tty+color - colorize result type (cyan) and value (green/yellow) in highPrecisionDump - respects NO_COLOR/CPP_REPL_NO_COLOR/--no-color/TERM=dumb, only when isatty --- CMakeLists.txt | 1 + include/cpp-repl/repl/session.h | 2 + include/cpp-repl/utils/highlight.h | 22 +++++ src/interpreter/interpreter.cpp | 25 +++++- src/repl/session.cpp | 29 +++++++ src/utils/highlight.cpp | 135 +++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 include/cpp-repl/utils/highlight.h create mode 100644 src/utils/highlight.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f4a7d36..6202906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ add_executable(cpp-repl src/interpreter/interpreter.cpp src/utils/version_detector.cpp src/utils/bigint.cpp + src/utils/highlight.cpp src/repl/session.cpp src/cli/cli.cpp ) diff --git a/include/cpp-repl/repl/session.h b/include/cpp-repl/repl/session.h index 43b20e3..1e350a4 100644 --- a/include/cpp-repl/repl/session.h +++ b/include/cpp-repl/repl/session.h @@ -65,6 +65,8 @@ class Session { std::string buildContinuationPrompt(bool forReadline) const; /** @brief Print timing line after execution. */ void printTimingLine(bool success, double ms) const; + /** @brief Print highlighted echo of executed code (if color enabled). */ + void printHighlightedEcho(const std::string &code) const; interpreter::Interpreter &interp_; ///< Bound interpreter instance. std::string buffer_; ///< Current multiline buffer. diff --git a/include/cpp-repl/utils/highlight.h b/include/cpp-repl/utils/highlight.h new file mode 100644 index 0000000..484c8dc --- /dev/null +++ b/include/cpp-repl/utils/highlight.h @@ -0,0 +1,22 @@ +#pragma once +#include + +namespace cpprepl { +namespace utils { + +/** + * @brief Simple C++ syntax highlighter for REPL output. + * + * Colors keywords, types, preprocessor, strings, comments, numbers. + * Respects NO_COLOR / --no-color via shouldUseColor check externally, + * but this function itself just checks useColor param. + */ +class Highlighter { +public: + static std::string highlight(const std::string &code, bool useColor); + static std::string highlightType(const std::string &typeStr, bool useColor); + static std::string highlightValue(const std::string &valStr, bool useColor); +}; + +} // namespace utils +} // namespace cpprepl diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 3bcf723..ba6d98f 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -4,6 +4,7 @@ */ #include "cpp-repl/interpreter/interpreter.h" #include "cpp-repl/utils/bigint.h" +#include "cpp-repl/utils/highlight.h" #include "cpp-repl/utils/version_detector.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Interpreter/Interpreter.h" @@ -134,7 +135,29 @@ static void highPrecisionDump(const clang::Value &V) { llvm::raw_string_ostream ds(dataStr); V.printData(ds); } - llvm::outs() << "(" << typeStr << ") " << dataStr << "\n"; + // Keyword highlight: colorize type and value when tty and color enabled + bool useColor = false; +#ifndef _WIN32 + useColor = isatty(STDOUT_FILENO) && !getenv("NO_COLOR") && !getenv("CPP_REPL_NO_COLOR") && !getenv("NO_COLOUR"); + if (useColor) { + const char *term = getenv("TERM"); + if (term && std::string(term)=="dumb") useColor=false; + } + if (getenv("FORCE_COLOR") || getenv("CLICOLOR_FORCE")) useColor = true; +#else + useColor = false; +#endif + if (getenv("CPP_REPL_NO_COLOR") || getenv("NO_COLOR")) useColor = false; + if (useColor) { + std::string colType = cpprepl::utils::Highlighter::highlightType(typeStr, true); + std::string colVal = cpprepl::utils::Highlighter::highlightValue(dataStr, true); + // Use llvm::outs with ANSI: wrap + llvm::outs() << "(\033[36m" << typeStr << "\033[0m) " << colVal << "\n"; + // Note: colType already contains color, but we use direct for simplicity + (void)colType; + } else { + llvm::outs() << "(" << typeStr << ") " << dataStr << "\n"; + } } } // namespace diff --git a/src/repl/session.cpp b/src/repl/session.cpp index 9dad9e1..81f4a7e 100644 --- a/src/repl/session.cpp +++ b/src/repl/session.cpp @@ -3,6 +3,7 @@ * @brief Interactive session loop and command handling. */ #include "cpp-repl/repl/session.h" +#include "cpp-repl/utils/highlight.h" #include "cpp-repl/utils/version_detector.h" #include #include @@ -157,6 +158,31 @@ void Session::printTimingLine(bool success, double ms) const { std::cout << std::flush; } +void Session::printHighlightedEcho(const std::string &code) const { +#ifdef _WIN32 + bool isTTY = _isatty(_fileno(stdout)) != 0; +#else + bool isTTY = isatty(STDOUT_FILENO) != 0; +#endif + if (!isTTY) return; + bool color = shouldUseColor(false); + if (!color) return; + // Trim and limit to single-line preview (80 cols) for non-intrusive echo + std::string preview = code; + // Remove trailing newlines/spaces + while (!preview.empty() && (preview.back()=='\n' || preview.back()=='\r' || preview.back()==' ' || preview.back()=='\t')) preview.pop_back(); + // Collapse internal newlines to " ⏎ " for preview + for (char &c : preview) if (c=='\n' || c=='\r') c=' '; + // Trim leading spaces + size_t s = preview.find_first_not_of(" \t"); + if (s!=std::string::npos) preview = preview.substr(s); + if (preview.empty()) return; + if (preview.size() > 120) preview = preview.substr(0, 117) + "..."; + std::string highlighted = utils::Highlighter::highlight(preview, true); + // Print dim grey arrow + highlighted code after execution + std::cout << "\033[90m \u25B8 \033[0m" << highlighted << "\n" << std::flush; +} + bool Session::isIncomplete(const std::string &buffer) const { int braces = 0, parens = 0, brackets = 0; bool inSingle = false, inDouble = false, inLineComment = false, inBlockComment = false; @@ -455,6 +481,8 @@ void Session::runInteractive() { lastDurationMs_ = ms; lastSuccess_ = ok; hasLastTiming_ = true; + // Keyword highlight: echo executed code with syntax colors (after execution) + printHighlightedEcho(buffer_); if (!ok) { if (!err.empty()) printError(err); printTimingLine(false, ms); @@ -504,6 +532,7 @@ void Session::runInteractive() { lastDurationMs_ = ms; lastSuccess_ = ok; hasLastTiming_ = true; + printHighlightedEcho(buffer_); if (!ok) { if (!err.empty()) printError(err); printTimingLine(false, ms); diff --git a/src/utils/highlight.cpp b/src/utils/highlight.cpp new file mode 100644 index 0000000..c9d627e --- /dev/null +++ b/src/utils/highlight.cpp @@ -0,0 +1,135 @@ +#include "cpp-repl/utils/highlight.h" +#include +#include + +namespace cpprepl { +namespace utils { + +static const std::unordered_set kKeywords = { + "alignas","alignof","and","and_eq","asm","auto","bitand","bitor","bool","break","case","catch","char","char8_t","char16_t","char32_t","class","compl","concept","const","consteval","constexpr","constinit","const_cast","continue","co_await","co_return","co_yield","decltype","default","delete","do","double","dynamic_cast","else","enum","explicit","export","extern","false","float","for","friend","goto","if","inline","int","long","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","private","protected","public","register","reinterpret_cast","requires","return","short","signed","sizeof","static","static_assert","static_cast","struct","switch","template","this","thread_local","throw","true","try","typedef","typeid","typename","union","unsigned","using","virtual","void","volatile","wchar_t","while","xor","xor_eq","import","module" +}; + +static const std::unordered_set kTypes = { + "int","float","double","char","bool","void","long","short","unsigned","signed","size_t","auto","std::string","string","std::vector","std::map","cpp_int","bigint","mpz_int","int64_t","uint64_t" +}; + +static const std::unordered_set kConsts = {"true","false","nullptr"}; + +std::string Highlighter::highlight(const std::string &code, bool useColor) { + if (!useColor || code.empty()) return code; + const std::string RST = "\033[0m"; + const std::string GREY = "\033[90m"; + const std::string YELLOW = "\033[33m"; + const std::string MAGENTA = "\033[95m"; + const std::string CYAN = "\033[36m"; + const std::string B_CYAN = "\033[1;36m"; + const std::string GREEN = "\033[32m"; + const std::string B_MAGENTA = "\033[1;35m"; + const std::string DIM = "\033[2m"; + // Preprocessor yellow bright + std::string out; + out.reserve(code.size()*2); + size_t i=0, n=code.size(); + bool atLineStart = true; + while (i Date: Tue, 1 Sep 2026 18:43:43 +0300 Subject: [PATCH 8/9] feat: multiline template/requires/concept/struct support Extend Session::isIncomplete to buffer incomplete definitions: - template headers (template without ; or body) - requires/concept trailing clauses - struct/class/enum headers - if/for/while/switch without body - trailing : , = - uses trim + regex heuristics and rfind checks for template Ensures 'template ' + next line declaration are evaluated together Tested with template functions, Box struct, concepts and requires clauses --- src/repl/session.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/repl/session.cpp b/src/repl/session.cpp index 81f4a7e..2506f85 100644 --- a/src/repl/session.cpp +++ b/src/repl/session.cpp @@ -6,6 +6,7 @@ #include "cpp-repl/utils/highlight.h" #include "cpp-repl/utils/version_detector.h" #include +#include #include #include #include @@ -222,7 +223,92 @@ bool Session::isIncomplete(const std::string &buffer) const { else if (c == ']') --brackets; } if (inDouble || inSingle || inBlockComment) return true; - return braces > 0 || parens > 0 || brackets > 0; + if (braces > 0 || parens > 0 || brackets > 0) return true; + + // ── Multiline definition support (template etc.) ── + // Trim buffer for heuristic checks + { + auto trimCopy = [](const std::string &s) -> std::string { + size_t a = s.find_first_not_of(" \t\r\n"); + if (a == std::string::npos) return ""; + size_t b = s.find_last_not_of(" \t\r\n"); + return s.substr(a, b - a + 1); + }; + std::string t = trimCopy(buffer); + if (t.empty()) return false; + char last = t.back(); + if (last == ';' || last == '}') return false; + // Trailing : , = are incomplete + if (last == ':' || last == '=' || last == ',') return true; + // Template header without body: "template <...>" or "template" alone at end + // Also handles "template \n" -> incomplete until next decl + body + { + // Simple check: if "template" appears and last ';' / '}' is before last "template" + size_t posTpl = t.rfind("template"); + if (posTpl != std::string::npos) { + size_t lastSemi = t.rfind(';'); + size_t lastRCurly = t.rfind('}'); + bool hasTermAfter = false; + if (lastSemi != std::string::npos && lastSemi > posTpl) hasTermAfter = true; + if (lastRCurly != std::string::npos && lastRCurly > posTpl) hasTermAfter = true; + if (!hasTermAfter) { + // Check if ends with template header pattern + // Use simple heuristic: ends with "template" or ">" or identifier and no terminator + // Regex: .*template\s*(<[^>]*>)?\s*$ + static const std::regex reTplHeader(R"(.*\btemplate\s*(<[^>]*>)?\s*$)", + std::regex::ECMAScript); + std::smatch m; + if (std::regex_match(t, m, reTplHeader)) return true; + // Also "...\ntemplate <...>\nT foo..." with no ; after template still incomplete + // If overall buffer contains template but no complete decl after it, keep buffering + // e.g., "template \nT add(T a, T b)" (no ; or { yet) + // That string does not match above, but still should be incomplete until body + // Detect: after last template, there is no ';' or '{' with body + // If t contains template and does not end with ';' '}' and last line is not a complete stmt + // Heuristic: if t contains template and last char is not ';' '}' and t does not contain ";" + // after template with a following declaration that looks incomplete + // For "template <...>\nT foo(...)" without body, treat as incomplete + std::string afterTpl = t.substr(posTpl); + if (afterTpl.find(';') == std::string::npos && afterTpl.find('{') == std::string::npos) { + // No terminator after template at all + return true; + } + // If afterTpl contains a function signature without body: "T foo(...)" without ; or { + // Check if afterTpl has '(' but not ';' '{' '}' + if (afterTpl.find('(') != std::string::npos && afterTpl.find(';') == std::string::npos && afterTpl.find('{') == std::string::npos) { + return true; + } + } + } + } + // Struct/class/enum header without ; or { + { + static const std::regex reStruct(R"(.*\b(struct|class|enum)\s+\w+(\s*:\s*[\w:,\s]+)?\s*$)"); + if (std::regex_match(t, reStruct)) return true; + } + // Concept / requires at end + { + static const std::regex reConcept(R"(.*\bconcept\s+\w+\s*=\s*.*)"); + if (std::regex_search(t, reConcept) && last != ';') return true; + if (t.size() >= 8 && t.compare(t.size()-8, 8, "requires")==0) return true; + static const std::regex reRequires(R"(.*\brequires\b[^;]*$)"); + if (std::regex_match(t, reRequires)) { + // if requires clause without trailing ; or { + return true; + } + } + // if/for/while/switch without body + { + static const std::regex reCtrl(R"(.*\b(if|for|while|switch)\s*\(.*\)\s*$)"); + if (std::regex_match(t, reCtrl)) return true; + } + // Trailing "typename" or "struct" etc. + { + static const std::regex reTrailingKw(R"(.*\b(template|typename|concept|requires|struct|class|enum|public|private|protected)\s*$)"); + if (std::regex_match(t, reTrailingKw)) return true; + } + } + return false; } bool Session::handleCommand(const std::string &line, std::string &err) { From d51f3f6298ed9a579f31e225a610b82decf49492 Mon Sep 17 00:00:00 2001 From: sergiorandria Date: Tue, 1 Sep 2026 19:00:51 +0300 Subject: [PATCH 9/9] feat: auto-include standard library and JIT poison recovery - by default include (fallback to common headers) on init, ensuring std::exp/std::vector/std::sort etc work without explicit include - proactive ensureStdLib() when code contains std:: and on-demand retry when error mentions undeclared std:: member - tryIncludeStdLib() with fallback bundle - stdLibIncluded_ flag reset on reinit/reset/ensureVersion - JIT poison handling: on Symbols not found / Failed to materialize symbols, auto Undo(1) and hint about :undo/:reset and forward misuse - hint for template forward error now actionable --- include/cpp-repl/interpreter/interpreter.h | 5 + src/interpreter/interpreter.cpp | 101 +++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/include/cpp-repl/interpreter/interpreter.h b/include/cpp-repl/interpreter/interpreter.h index 8bf0254..f7ac165 100644 --- a/include/cpp-repl/interpreter/interpreter.h +++ b/include/cpp-repl/interpreter/interpreter.h @@ -157,6 +157,10 @@ class Interpreter { std::string &value); /** @brief Normalize a value string for comparison. */ std::string normalizeValue(const std::string &v); + /** @brief Ensure standard library is available (bits/stdc++.h). */ + bool ensureStdLib(std::string &err); + /** @brief Try to include bits/stdc++.h or fallback headers. */ + bool tryIncludeStdLib(); std::unique_ptr interp_; bool initialized_ = false; std::vector history_; @@ -168,6 +172,7 @@ class Interpreter { std::vector compilerArgsStorage_; std::unordered_map> variables_; std::vector>> varHistory_; + bool stdLibIncluded_ = false; }; } // namespace interpreter diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index ba6d98f..243ef02 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -193,6 +193,7 @@ bool Interpreter::init(utils::StdVersion version, libraryPaths_ = libraryPaths; libraries_ = libraries; currentVersion_ = version; + stdLibIncluded_ = false; clang::IncrementalCompilerBuilder builder; compilerArgsStorage_.clear(); @@ -298,6 +299,9 @@ bool Interpreter::init(utils::StdVersion version, llvm::handleAllErrors(std::move(e2), [&](llvm::ErrorInfoBase &EIB) {}); #endif } + // Auto-include standard library (bits/stdc++.h) by default for STL support + // If keyword not found, eval will retry with this. Pre-include avoids extra round-trip. + tryIncludeStdLib(); // Load libraries requested via -l / --library (absolute, relative, or -l name) for (auto &lib : libraries_) { auto tryLoad = [&](const std::string &path) -> bool { @@ -357,6 +361,7 @@ bool Interpreter::reinitWithCurrentOptions(std::string &err) { history_.clear(); variables_.clear(); varHistory_.clear(); + stdLibIncluded_ = false; if (!init(currentVersion_, includePaths_, defines_, libraryPaths_, libraries_, local)) { err = local; return false; @@ -433,6 +438,7 @@ bool Interpreter::ensureVersion(utils::StdVersion needed, std::string &err) { history_.clear(); variables_.clear(); varHistory_.clear(); + stdLibIncluded_ = false; if (!init(needed, includePaths_, defines_, libraryPaths_, libraries_, local)) { err = local; return false; @@ -446,6 +452,46 @@ bool Interpreter::ensureVersion(utils::StdVersion needed, std::string &err) { return true; } +bool Interpreter::tryIncludeStdLib() { + if (stdLibIncluded_ || !initialized_ || !interp_) return stdLibIncluded_; + clang::Value V; + // Try bits/stdc++.h first (covers everything) + auto e = interp_->ParseAndExecute("#include \n", &V); + if (!e) { stdLibIncluded_ = true; return true; } + llvm::handleAllErrors(std::move(e), [&](llvm::ErrorInfoBase &EIB){}); + // Fallback: include common STL headers individually + const char *fallback = R"( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +)"; + auto e2 = interp_->ParseAndExecute(fallback, &V); + if (!e2) { stdLibIncluded_ = true; return true; } + llvm::handleAllErrors(std::move(e2), [&](llvm::ErrorInfoBase &EIB){}); + return false; +} + +bool Interpreter::ensureStdLib(std::string &err) { + if (stdLibIncluded_) return true; + if (!initialized_) { err = "REPL not initialized"; return false; } + if (tryIncludeStdLib()) { err.clear(); return true; } + err = "failed to include standard library (bits/stdc++.h)"; + return false; +} + bool Interpreter::evalAuto(const std::string &code, std::string &err) { auto needed = utils::VersionDetector::detect(code); if (!ensureVersion(needed, err)) @@ -719,6 +765,12 @@ bool Interpreter::eval(const std::string &code, std::string &err) { if (trimmed.empty()) return true; + // Proactive stdlib: include on first use of std:: + if (!stdLibIncluded_ && sanitized.find("std::") != std::string::npos) { + std::string dummy; + ensureStdLib(dummy); + } + if (trimmed.rfind("#include", 0) == 0) { size_t q1 = sanitized.find('"'); size_t q2 = std::string::npos; @@ -910,6 +962,54 @@ bool Interpreter::eval(const std::string &code, std::string &err) { "or use static_cast(proxy).convert_to() and " "static_cast(a[n]) for ap*a[n]"; } + // Auto-include standard library if keyword suggests missing header + // e.g., std::exp, std::forward, std::vector without prior include + if (!stdLibIncluded_ && sanitized.find("std::") != std::string::npos && + (msg.find("undeclared identifier") != std::string::npos || + msg.find("no member named") != std::string::npos || + msg.find("has no member") != std::string::npos || + msg.find("unknown type name") != std::string::npos || + msg.find("use of undeclared") != std::string::npos || + msg.find("implicit instantiation") != std::string::npos)) { + std::string dummy; + if (ensureStdLib(dummy)) { + clang::Value V2; + auto e2 = interp_->ParseAndExecute(toEval, &V2); + if (!e2) { + if (V2.isValid()) { + bool shouldPrint = true; + if (V2.isVoid()) shouldPrint = false; + else if (trimmed.find("std::cout") != std::string::npos) shouldPrint = false; + if (shouldPrint) { highPrecisionDump(V2); std::cout << "\n"; } + } + if (!sanitized.empty()) { + history_.push_back(sanitized); + trackVariable(sanitized); + } + std::cout << "[auto-included for std:: support]\n"; + return true; + } + llvm::handleAllErrors(std::move(e2), [&](llvm::ErrorInfoBase &EIB){ msg = EIB.message(); }); + msg += "\n[hint] tried auto-including but still failed; try explicit #include <...> or check std:: usage"; + } + } + // JIT poison recovery: Symbols not found / Failed to materialize symbols + if (msg.find("Symbols not found") != std::string::npos || + msg.find("Failed to materialize symbols") != std::string::npos || + msg.find("__orc_init_func") != std::string::npos) { + // Attempt to undo the poisoned increment; clang::Interpreter::Undo(1) often clears it + if (auto ue = interp_->Undo(1)) { + llvm::handleAllErrors(std::move(ue), [&](llvm::ErrorInfoBase &EIB){}); + } + // Also pop last history if it was the poisoned one (if any) + // No push yet for this failed eval, so nothing to pop from history, but prior poisoned def may be in history + // Offer hint and suggest :reset if still broken + if (msg.find("Failed to materialize") != std::string::npos) { + msg += "\n[hint] JIT poisoned — auto-undo attempted. If subsequent #include still fails, run :reset or restart. Original error was likely a bad template (e.g., std::forward without )."; + } else { + msg += "\n[hint] Symbols not found — likely a failed template instantiation (e.g., std::forward(x) should be std::forward(x)). Auto-undo attempted; try :undo or :reset."; + } + } err = msg; return false; } @@ -1112,6 +1212,7 @@ void Interpreter::reset(std::string &err) { history_.clear(); variables_.clear(); varHistory_.clear(); + stdLibIncluded_ = false; if (!init(currentVersion_, local)) err = local; else