diff --git a/.gitignore b/.gitignore index 29beb8d..5e5b306 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ *.app shell pseudo +pseudoc pseudo-lsp run_tests diff --git a/Makefile b/Makefile index 15b3eb5..6c497c2 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,36 @@ $(BUILD_COV_DIR)/%.o: src/%.cpp $(HEADERS) | $(BUILD_COV_DIR) run : $(TARGET) ./shell +# Runtime library for compiled programs (interpreter core + rt_* shims) +# Prefer the system ar: GNU binutils ar produces archives Apple's ld rejects. +AR := $(shell test -x /usr/bin/ar && echo /usr/bin/ar || echo ar) +RT_LIB = $(BUILD_DIR)/libpseudort.a +RT_OBJS = $(filter-out $(BUILD_DIR)/shell.o,$(OBJS)) $(BUILD_DIR)/runtime.o + +$(RT_LIB): $(RT_OBJS) + $(AR) rcs $@ $(RT_OBJS) + +runtime: $(BUILD_DIR) $(RT_LIB) + +# LLVM AOT compiler (optional; requires LLVM, e.g. `brew install llvm`) +LLVM_CONFIG ?= $(shell command -v llvm-config 2>/dev/null || echo /opt/homebrew/opt/llvm/bin/llvm-config) +LLVM_INCLUDEDIR = $(shell $(LLVM_CONFIG) --includedir) +LLVM_LIBDIR = $(shell $(LLVM_CONFIG) --libdir) +LLVM_COMPILE_FLAGS = -I$(LLVM_INCLUDEDIR) -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS +PSEUDOC_TARGET = pseudoc +PSEUDOC_OBJS = $(filter-out $(BUILD_DIR)/shell.o,$(OBJS)) $(BUILD_DIR)/compiler.o $(BUILD_DIR)/pseudoc.o + +$(BUILD_DIR)/compiler.o: src/compiler.cpp $(HEADERS) | $(BUILD_DIR) + $(CC) -c $(CPPFLAGS) $(LLVM_COMPILE_FLAGS) src/compiler.cpp -o $@ + +$(BUILD_DIR)/pseudoc.o: src/pseudoc.cpp $(HEADERS) | $(BUILD_DIR) + $(CC) -c $(CPPFLAGS) $(LLVM_COMPILE_FLAGS) src/pseudoc.cpp -o $@ + +$(PSEUDOC_TARGET): $(BUILD_DIR) $(PSEUDOC_OBJS) $(RT_LIB) + $(CC) $(CPPFLAGS) $(PSEUDOC_OBJS) -L$(LLVM_LIBDIR) -lLLVM -Wl,-rpath,$(LLVM_LIBDIR) -o $(PSEUDOC_TARGET) + +compiler: $(PSEUDOC_TARGET) runtime + # Google Test rules GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h \ @@ -66,7 +96,10 @@ test: $(TEST_TARGET) coverage: test gcov -r -o $(BUILD_COV_DIR) $(filter-out src/shell.cpp, $(SRCS)) -.PHONY: clean all test coverage lsp +test-compiler: $(TARGET) + bash test/compiler_tests.sh + +.PHONY: clean all test coverage lsp test-compiler runtime compiler lsp: $(LSP_TARGET) clean: diff --git a/README.md b/README.md index 9ff23c9..2b416a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,38 @@ # PseudoCodeInterpreter -Transform pseudo-code into an executable programming language. +Transform pseudo-code into an executable programming language. Programs can be +run with the interpreter (`pseudo`) or compiled to native executables with the +LLVM-based compiler (`pseudoc`). + +## Compiling to Native Code + +Requires LLVM (e.g. `brew install llvm`). Build and use the compiler: + +```sh +make # interpreter (used as reference by the test suite) +make compiler # builds pseudoc and build/libpseudort.a +./pseudoc program.ps -o program +./program +``` + +Options: + +- `-o `: output executable path (defaults to the source basename). +- `--emit-llvm`: print the generated LLVM IR instead of producing a binary. +- `--runtime-lib `: explicit path to `libpseudort.a` (also settable via + `$PSEUDO_RT_LIB`; by default it is found next to the `pseudoc` binary). + +The compiler lowers the AST to LLVM IR (optimized at `-O2`), where each +language operation calls into a runtime built from the interpreter's own value +and symbol-table code, so compiled behavior matches the interpreter. Recursive +pure-numeric algorithms get the same by-argument memoization the interpreter +applies. Compiled output is verified against the interpreter by +`make test-compiler` (differential tests in `test/compiler/`). + +Current limitations: + +- A loop used as a function's implicit return value evaluates to `NONE` + instead of the interpreter's collected per-iteration array. ## Data Types diff --git a/ci/clang-tidy-check.sh b/ci/clang-tidy-check.sh index b911c12..c5957f8 100755 --- a/ci/clang-tidy-check.sh +++ b/ci/clang-tidy-check.sh @@ -16,9 +16,21 @@ else fi fi +# src/compiler.cpp and src/pseudoc.cpp need LLVM headers; skip them when no +# LLVM development install is available. +llvm_config="$(command -v llvm-config || echo /opt/homebrew/opt/llvm/bin/llvm-config)" +llvm_flags=() +llvm_filter='cat' +if "${llvm_config}" --includedir >/dev/null 2>&1; then + llvm_flags=("-I$(${llvm_config} --includedir)") +else + llvm_filter="grep -Ev '^src/(compiler|pseudoc)\\.cpp$'" +fi + mapfile -t files < <( printf '%s\n' "${changed_files}" \ | grep -E '^(src|test)/.*\.cpp$' \ + | eval "${llvm_filter}" \ || true ) @@ -31,4 +43,5 @@ clang-tidy --warnings-as-errors='*' "${files[@]}" -- \ -std=c++17 \ -Isrc \ -Igoogletest/googletest/include \ + "${llvm_flags[@]}" \ -pthread diff --git a/docs/superpowers/plans/2026-06-10-llvm-compiler.md b/docs/superpowers/plans/2026-06-10-llvm-compiler.md new file mode 100644 index 0000000..a4c2b32 --- /dev/null +++ b/docs/superpowers/plans/2026-06-10-llvm-compiler.md @@ -0,0 +1,146 @@ +# LLVM AOT Compiler Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** A `pseudoc` binary that compiles `.ps` programs to native executables via LLVM, with stdout behavior identical to the interpreter. + +**Architecture:** Codegen walks the existing AST and emits LLVM IR where every operation is a call into an `extern "C"` runtime (`rt_*`) layered on the existing `Value`/`SymbolTable` classes; the emitted object file is linked against `build/libpseudort.a`. See `docs/superpowers/specs/2026-06-10-llvm-compiler-design.md`. + +**Tech Stack:** C++17 project code, LLVM 21 C++ API (Homebrew, `llvm-config`), GNU make, bash differential tests. + +--- + +### Task 1: Differential test harness (failing first) + +**Files:** +- Create: `test/compiler_tests.sh` (executable) +- Create: `test/compiler/basics.ps`, `test/compiler/control_flow.ps`, `test/compiler/functions.ps`, `test/compiler/collections.ps`, `test/compiler/errors.ps` +- Modify: `Makefile` (add `test-compiler` target) + +- [x] **Step 1:** Write `test/compiler_tests.sh`: for each `.ps` in `test/compiler/` plus `test/test_fib.ps`, `test/test_repeat.ps`, `test/test_array_methods.ps`, `test/test_string_index.ps`, run `./pseudo f.ps > expected`, `./pseudoc f.ps -o tmpbin && ./tmpbin > actual`, diff. Exit nonzero on any mismatch. Also assert `./pseudoc test/test_struct.ps` fails with a struct compile error. +- [x] **Step 2:** Write the five new `.ps` fixtures covering: int/float/string arithmetic and comparisons, `and/or/not`, unary minus, `^` and `%`; if/else-if chains, for with step (incl. negative), while/repeat with break/continue, nested loops; recursion (fib), functions using caller scope, too-few-args error; arrays (literal, index assign, push/pop/insert/remove/resize/size/back), hash tables (set/get/contains/keys/size), string indexing and concatenation; index-out-of-range error. +- [x] **Step 3:** Run `bash test/compiler_tests.sh` — must FAIL (`pseudoc` missing). Commit. + +### Task 2: Minimal intrusive changes to interpreter core + +**Files:** +- Modify: `src/value.h` (Value gains `enable_shared_from_this`; add `PrecomputedNode`) +- Modify: `src/node.h` (add `NODE_PRECOMPUTED` constant) +- Modify: `src/interpreter.cpp` (`Interpreter::visit` dispatch case returning the precomputed value) +- Create: `src/imports.h` +- Modify: `src/pseudo.cpp` (move `ImportState`/`expand_imports` out of anonymous namespace, include `imports.h`) + +- [x] **Step 1:** `class Value : public std::enable_shared_from_this`; add + +```cpp +class PrecomputedNode : public Node { +public: + explicit PrecomputedNode(std::shared_ptr _value) : value(_value) {} + std::string get_node() override { return "PRECOMPUTED"; } + std::string get_type() override { return NODE_PRECOMPUTED; } + std::shared_ptr get_value() { return value; } +protected: + std::shared_ptr value; +}; +``` + +- [x] **Step 2:** `imports.h` declares `struct ImportState` and `bool expand_imports(const std::string&, const std::string&, ImportState&, std::string&, std::string&);` matching the current pseudo.cpp definitions. +- [x] **Step 3:** `make clean && make && make test` — all existing gtests PASS. Run an example to sanity-check. Commit. + +### Task 3: Runtime library `libpseudort.a` + +**Files:** +- Create: `src/runtime.h`, `src/runtime.cpp` +- Modify: `Makefile` (`runtime` target producing `build/libpseudort.a` from all interpreter objects except `shell.o`, plus `runtime.o`) + +- [x] **Step 1:** `runtime.h` — extern "C" ABI (all value params/returns are raw `Value*` kept alive by arena frames): + +```cpp +extern "C" { +void rt_init(); +void rt_shutdown(); +// constructors +Value* rt_make_int(int64_t v); +Value* rt_make_float(double v); +Value* rt_make_string(const char* s); +Value* rt_make_none(); +Value* rt_array_new(); +void rt_array_push(Value* arr, Value* v); +// variables (current scope chain) +Value* rt_get_var(const char* name); +Value* rt_set_var(const char* name, Value* v); // returns v +// operators: op is a single char code matching token types +Value* rt_bin_op(int op, Value* a, Value* b); +Value* rt_unary_op(int op, Value* a); +// control helpers +int64_t rt_cond_eq1(Value* v); // stoll(get_num()) == 1 (if) +int64_t rt_as_int(Value* v); // as_int() (while/repeat) +int64_t rt_for_cond(Value* i, Value* end, Value* step); +Value* rt_for_step_check(Value* step); // errors on step == 0 +// indexing / members +Value* rt_index(Value* obj, Value* idx); +Value* rt_index_assign(Value* obj, Value* idx, Value* v); +Value* rt_member_access(Value* obj, const char* name); +// calls +Value* rt_define_algo(const char* name, Value* (*fn)(), const char* const* args, int64_t nargs); +Value* rt_call(Value* callee, Value** argv, int64_t argc); +// frames +int64_t rt_frame_mark(); +void rt_frame_release(int64_t mark); +Value* rt_keep(Value* v, int64_t mark); // re-register v in frame `mark`-1 (survives release) +} +``` + +- [x] **Step 2:** Implement in runtime.cpp: global `std::vector>> frames` and `std::vector> scopes` (global scope created by `rt_init`); `CompiledAlgoValue : Value` holding `Value*(*fn)()` and arg names; `rt_call` dispatch (compiled → arity check with interpreter's exact colored error strings, new scope + frame, bind args, invoke; otherwise wrap args in `PrecomputedNode` and call `callee->execute(nodes, current_scope)`). Binary/unary ops call the existing `operator+` etc. on `shared_from_this()` handles. Any `VALUE_ERROR` encountered → print `get_num()` + `"\n"`, `exit(1)`. +- [x] **Step 3:** `make runtime` builds `build/libpseudort.a`. Commit. + +### Task 4: Compiler skeleton + driver (milestone: arithmetic & print) + +**Files:** +- Create: `src/compiler.h`, `src/compiler.cpp` (class `Compiler`: `bool compile(const NodeList& ast, llvm::Module&)`, error list with positions) +- Create: `src/pseudoc.cpp` (driver: args, expand_imports, lex/parse with run()-style error printing, codegen, verify, O2 PassBuilder, TargetMachine object emission, link via `c++`) +- Modify: `Makefile` (`compiler` target via `llvm-config`, default `/opt/homebrew/opt/llvm/bin/llvm-config` fallback; link `-lLLVM` from `--libdir` with rpath) + +- [x] **Step 1:** Codegen for: ValueNode literals, VarAccess/VarAssign, BinOp/UnaryOp (`rt_bin_op` with op codes), AlgorithmCall (callee codegen + `rt_call`), statement sequencing in `main` (`rt_init` first, `ret i32 0` last). Unsupported node types → recorded compile error. +- [x] **Step 2:** Driver `--emit-llvm` and `-o`; runtime archive lookup (`--runtime-lib`, `$PSEUDO_RT_LIB`, `/build/libpseudort.a`). +- [x] **Step 3:** `make compiler`; `echo 'print("hi", 1 + 2 * 3)' > /tmp/t.ps && ./pseudoc /tmp/t.ps -o /tmp/t && /tmp/t` matches `./pseudo /tmp/t.ps`. Commit. + +### Task 5: Control flow + +**Files:** +- Modify: `src/compiler.cpp` + +- [x] **Step 1:** `if` (yields Int 0 when untaken, propagates last-statement value), `for` (assign → step default/check → end; latch keeps previous `i` slot per visit_for; per-iteration `rt_frame_mark`/`rt_frame_release`), `while`, `repeat`, `break`/`continue` (branch + release to loop mark), nested loops via a loop-context stack. +- [x] **Step 2:** Differential run of `test/compiler/control_flow.ps`, `test/test_repeat.ps` — PASS. Commit. + +### Task 6: Functions and return + +**Files:** +- Modify: `src/compiler.cpp` + +- [x] **Step 1:** AlgorithmDef → new LLVM function compiled with its own builder state (body statements; implicit result = last statement value; `ReturnNode` → `rt_keep` + `ret`); definition site emits `rt_define_algo` with arg-name global strings. Same handling when defs appear inside other bodies. +- [x] **Step 2:** Differential run of `test/compiler/functions.ps`, `test/test_fib.ps` — PASS. Commit. + +### Task 7: Arrays, hash tables, strings, member calls + +**Files:** +- Modify: `src/compiler.cpp` + +- [x] **Step 1:** ArrayNode literal, ArrayAccess (`rt_index`), ArrayAssign (`rt_index_assign`, including hash-table set; member assignment on non-instances reports the interpreter's error string at runtime), MemberAccess (`rt_member_access` → BoundMethodValue path; method calls flow through existing `rt_call` fallback). +- [x] **Step 2:** Differential run of `test/compiler/collections.ps`, `test/test_array_methods.ps`, `test/test_string_index.ps` — PASS. Commit. + +### Task 8: Struct rejection, error fixtures, full suite + +**Files:** +- Modify: `src/compiler.cpp` (NODE_STRUCTDEF → "compile error: Struct is not supported by the compiler yet" with location; same for instance member assignment when detectable) +- Modify: `Makefile` (`test-compiler` runs harness) + +- [x] **Step 1:** `bash test/compiler_tests.sh` — ALL PASS (including struct rejection and runtime error fixtures). +- [x] **Step 2:** `make test` still green; `ci/clang-format-check.sh` clean (format new files). +- [x] **Step 3:** Update README (compile section). Commit. + +## Self-review notes + +- Spec coverage: every spec section maps to a task (tests→1, intrusive changes→2, runtime→3, driver/build→4, codegen semantics→4–7, struct rejection→8, README/CI→8). +- Types consistent: ABI defined once in Task 3 and used in 4–7. +- `rt_keep`'s frame semantics are decided in Task 3 (`re-register in caller frame`), used by Task 6 return handling. diff --git a/docs/superpowers/specs/2026-06-10-llvm-compiler-design.md b/docs/superpowers/specs/2026-06-10-llvm-compiler-design.md new file mode 100644 index 0000000..0a1f14b --- /dev/null +++ b/docs/superpowers/specs/2026-06-10-llvm-compiler-design.md @@ -0,0 +1,165 @@ +# LLVM AOT Compiler for PseudoCode — Design + +Date: 2026-06-10 +Status: Approved for implementation (autonomous session; decisions documented below) + +## Goal + +Add a compile mode to the project: a `pseudoc` binary that turns a `.ps` source +file into a native executable using LLVM, with output behavior identical to the +interpreter (`pseudo file.ps`). + +## Approaches considered + +1. **Boxed-value AOT with runtime library (chosen).** Compile the AST to LLVM + IR where each language operation is a call into an `extern "C"` runtime + (`rt_*`) built on top of the existing `Value`, `SymbolTable`, and + `BoundMethodValue` code. Link the generated object file against + `libpseudort.a` to produce a standalone executable. + - Pros: exact semantic parity (reuses the interpreter's own operators, + builtins, array/hash methods), tractable codegen, full dynamic typing. + - Cons: values remain boxed, so speedups come from removing AST-walking + overhead, not from unboxed arithmetic (future work). +2. **Typed-subset native codegen.** Infer int/float types and emit raw + arithmetic. Fast, but supports only a fraction of the language and + duplicates semantics; high divergence risk. +3. **Textual IR emission + clang.** No LLVM library dependency, but fragile, + unverifiable IR and a worse foundation. Rejected. + +## Architecture + +``` +pseudoc (new binary) + ├── reuses: lexer, parser, import expansion (refactored out of run()) + ├── src/compiler.{h,cpp}: Compiler — AST → llvm::Module (LLVM C++ API) + └── links output .o with build/libpseudort.a via the system C++ driver + +libpseudort.a (new static library) + ├── existing objects: pseudo.o, interpreter.o, symboltable.o, node.o, ... + └── src/runtime.{h,cpp}: extern "C" rt_* shims + CompiledAlgoValue +``` + +### Value representation + +All values are `Value*` (LLVM opaque `ptr`). `Value` gains +`std::enable_shared_from_this` so the runtime can recover the owning +`shared_ptr` from a raw pointer. Lifetimes are managed by an **arena frame +stack** in the runtime: every `rt_*` function that produces a value registers +its `shared_ptr` in the current frame. Frames are pushed/popped: + +- per function call (by `rt_call`), +- per loop iteration (emitted by codegen), so long-running loops don't grow + memory unboundedly. Values that survive (stored in variables, arrays, etc.) + are kept alive by the `SymbolTable`/container `shared_ptr`s. +- `rt_frame_mark()` / `rt_frame_release(mark)` let codegen unwind frames on + `break` / `return` edges. Function results are re-registered in the caller's + frame. + +### Codegen rules (mirror interpreter semantics exactly) + +- Each top-level statement of the program body compiles into `main` in order; + `Algorithm` definitions compile to LLVM functions `ptr @"ps.algo.N."()` + plus a runtime registration (`rt_define_algo`) at the definition site, so + call-before-definition errors behave the same. +- Variables: always through `rt_get_var` / `rt_set_var` (dynamic scoping via + the symbol-table parent chain, exactly like `AlgoValue::execute`). +- `if`: branch on `stoll(cond->get_num()) == 1` (`rt_cond_eq1`); the statement + yields Int 0 when no branch is taken. +- `for`: replicate `visit_for` — evaluate assign, then step (default Int 1), + then end; `step == 0` → "Infinite for loop" error; the latch recomputes + `var <- i + step` from the value read at the previous latch (body + reassignments of the loop variable are overwritten, matching the + interpreter). Condition is float-aware `<=` / `>=` via `rt_for_cond`. +- `while`: condition `as_int() == 1`; `repeat ... until`: body first, loop + while `as_int() == 0`. +- `break`/`continue`/`return` compile to branches (with frame releases); + `return` outside any algorithm behaves like the interpreter (top level: + statement result, ignored). +- Calls: evaluate callee and args to `Value*`, then `rt_call(callee, argv, + argc)`: + - `CompiledAlgoValue` → arity check ("Too few/Too many arguments" with the + same color codes), push scope + frame, bind args, call function pointer. + - anything else (builtins, `BoundMethodValue` for array/string/hash methods) + → wrap evaluated args in `PrecomputedNode`s and reuse the existing + `execute(NodeList, SymbolTable*)` paths unchanged. +- Member access / array index / array assign: `rt_member_access`, `rt_index`, + `rt_index_assign` mirroring `visit_member_access`, `visit_array_access`, + `visit_array_assign` (string 1-indexing, hash get/set, array element + assignment). +- Struct definitions: in-struct methods compile to private algorithm functions + and runtime `CompiledAlgoValue`s, then `rt_define_struct` registers a + `StructValue` with member and method maps. Later `Algorithm Struct::method` + definitions compile as global algorithms and call `rt_struct_add_method` at + the definition site, matching the interpreter's attachment timing. Bound + instance method calls execute with a parent scope containing `self`. +- Array literals: `rt_array_new` + `rt_array_push`. +- Errors: any `rt_*` function that produces or receives a `VALUE_ERROR` prints + `err->get_num()` (same text the interpreter prints at top level) and exits + with status 1. There is no error-value plumbing in generated IR. + +### Documented v1 divergences from the interpreter + +- A loop used as a function's implicit return value evaluates to `NONE`; + the interpreter (with `collect_loop_results`) returns an array of + per-iteration values. Rarely used; revisit if needed. +- `break`/`continue` outside any loop are compile-time errors (the + interpreter propagates them as control values, which can even break an + outer loop across a function call — pathological behavior we do not + reproduce). +- Compiled recursive pure-numeric algorithms get the interpreter's + by-argument memoization via `rt_define_algo(..., memoizable)`, using the + shared `is_memoizable_numeric_algo` analysis (src/analysis.h). + +### Intrusive changes to existing code (kept minimal) + +1. `Value` inherits `std::enable_shared_from_this` (value.h). +2. New `PrecomputedNode` (value.h, since it stores a `shared_ptr`) and + one dispatch case in `Interpreter::visit`. +3. `expand_imports` (and its `ImportState`) refactored from an anonymous + namespace in pseudo.cpp into a small header so the compiler driver can use + it. + +### Driver & CLI + +``` +pseudoc [-o ] [--emit-llvm] [--runtime-lib ] +``` + +- Default output: source basename without extension (`fib.ps` → `fib`). +- `--emit-llvm` prints the textual IR to stdout and stops. +- Pipeline: read → expand imports → lex → parse (reuse run()'s error + reporting) → codegen → `llvm::verifyModule` → O2 via `PassBuilder` → object + file (TargetMachine) → link `c++ obj libpseudort.a -o out`. +- Runtime archive located via `--runtime-lib`, `$PSEUDO_RT_LIB`, or relative to + the `pseudoc` binary (`/build/libpseudort.a`). + +### Build integration + +Makefile gets optional targets (not part of default `make`, since LLVM is an +optional dependency): + +- `make runtime` → `build/libpseudort.a` (existing objects + runtime.o). +- `make compiler` → `pseudoc`, compiled/linked using `llvm-config` + (`/opt/homebrew/opt/llvm/bin/llvm-config` fallback). Fails with a clear + message if LLVM is missing. +- `make test-compiler` → end-to-end differential tests. + +CI is unchanged (compiler build is optional); new sources follow +clang-format/clang-tidy rules. + +## Testing + +Differential end-to-end testing is the core strategy: +`test/compiler_tests.sh` compiles each supported `.ps` test/benchmark file +with `pseudoc` and asserts its stdout equals `./pseudo file.ps` stdout. +Coverage: arithmetic/comparison/logic on ints/floats/strings, arrays + +methods, hash tables, string indexing, if/for/while/repeat with +break/continue, recursion (fib), nested functions, argument-count errors, +runtime errors (index out of range), and struct construction/method calls. +Existing `make test` (gtest) must keep passing. + +## Performance expectation + +v1 removes parse/AST-walk overhead but keeps boxed values and symbol-table +access; expect modest wins over the interpreter. Unboxing/SROA of numeric +locals is explicitly future work. diff --git a/src/analysis.h b/src/analysis.h new file mode 100644 index 0000000..018f294 --- /dev/null +++ b/src/analysis.h @@ -0,0 +1,21 @@ +/// -------------------- +/// Algorithm analysis +/// -------------------- + +#ifndef ANALYSIS_H +#define ANALYSIS_H + +#include +#include +#include + +#include "node.h" + +// True for recursive algorithms whose bodies only use numeric literals, their +// own arguments, numeric operators, ifs, and self-calls: their results can be +// memoized by argument values (used by both the interpreter and the compiled +// runtime). +bool is_memoizable_numeric_algo(const std::shared_ptr& node, const std::string& algo_name, + const std::vector& args); + +#endif diff --git a/src/compiler.cpp b/src/compiler.cpp new file mode 100644 index 0000000..ada0a8c --- /dev/null +++ b/src/compiler.cpp @@ -0,0 +1,743 @@ +/// -------------------- +/// LLVM AOT compiler +/// -------------------- +/// +/// Lowers the AST to LLVM IR. Every language operation becomes a call into +/// the runtime (src/runtime.h); control flow becomes real basic blocks. The +/// emitted code mirrors Interpreter::visit_* semantics; see +/// docs/superpowers/specs/2026-06-10-llvm-compiler-design.md. + +#include "compiler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "analysis.h" +#include "node.h" +#include "runtime.h" +#include "token.h" + +namespace { + +class CodeGen { + public: + CodeGen(llvm::Module& _module, std::vector& _errors) + : module(_module), ctx(_module.getContext()), builder(ctx), errors(_errors) { + ptr_ty = builder.getPtrTy(); + i64_ty = builder.getInt64Ty(); + f64_ty = builder.getDoubleTy(); + } + + bool run(const NodeList& ast) { + llvm::Function* main_fn = + llvm::Function::Create(llvm::FunctionType::get(builder.getInt32Ty(), false), + llvm::Function::ExternalLinkage, "main", module); + llvm::BasicBlock* entry = llvm::BasicBlock::Create(ctx, "entry", main_fn); + builder.SetInsertPoint(entry); + builder.CreateCall(get_rt("rt_init", builder.getVoidTy(), {})); + + for (const auto& node : ast) { + if (gen(node) == nullptr && block_terminated()) { + break; + } + } + if (!block_terminated()) { + builder.CreateRet(builder.getInt32(0)); + } + return errors.empty(); + } + + private: + struct LoopContext { + llvm::BasicBlock* latch; + llvm::BasicBlock* exit; + llvm::Value* iter_mark; + // `while` re-pushes its iteration frame in the header, so `continue` + // must release first; `for`/`repeat` latches release it themselves. + bool release_on_continue; + }; + + llvm::Module& module; + llvm::LLVMContext& ctx; + llvm::IRBuilder<> builder; + std::vector& errors; + + llvm::PointerType* ptr_ty{nullptr}; + llvm::Type* i64_ty{nullptr}; + llvm::Type* f64_ty{nullptr}; + + std::vector loops; + std::map string_constants; + int algo_counter{0}; + + /// ---- helpers ---- + + llvm::FunctionCallee get_rt(const std::string& name, llvm::Type* ret, + std::vector args) { + return module.getOrInsertFunction(name, llvm::FunctionType::get(ret, args, false)); + } + + bool block_terminated() { return builder.GetInsertBlock()->getTerminator() != nullptr; } + + llvm::Value* cstring(const std::string& text) { + auto found = string_constants.find(text); + if (found != string_constants.end()) { + return found->second; + } + llvm::Value* global = builder.CreateGlobalString(text, "str"); + string_constants[text] = global; + return global; + } + + llvm::AllocaInst* entry_alloca(llvm::Type* type) { + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::IRBuilder<> tmp(&fn->getEntryBlock(), fn->getEntryBlock().begin()); + return tmp.CreateAlloca(type); + } + + llvm::Value* string_array(const std::vector& strings, + const std::string& global_name) { + if (strings.empty()) { + return llvm::ConstantPointerNull::get(ptr_ty); + } + + llvm::ArrayType* strings_ty = llvm::ArrayType::get(ptr_ty, strings.size()); + std::vector string_ptrs; + string_ptrs.reserve(strings.size()); + for (const auto& value : strings) { + string_ptrs.push_back(llvm::cast(cstring(value))); + } + return new llvm::GlobalVariable(module, strings_ty, true, llvm::GlobalValue::PrivateLinkage, + llvm::ConstantArray::get(strings_ty, string_ptrs), + global_name); + } + + llvm::Value* value_array(const std::vector& values) { + if (values.empty()) { + return llvm::ConstantPointerNull::get(ptr_ty); + } + + llvm::ArrayType* values_ty = llvm::ArrayType::get(ptr_ty, values.size()); + llvm::AllocaInst* slots = entry_alloca(values_ty); + for (size_t i = 0; i < values.size(); ++i) { + builder.CreateStore(values[i], + builder.CreateConstInBoundsGEP2_64(values_ty, slots, 0, i)); + } + return slots; + } + + void error(const std::string& message, const std::shared_ptr& node) { + std::string suffix; + std::shared_ptr tok = node ? node->get_tok() : nullptr; + if (tok) { + suffix = " (line " + std::to_string(tok->get_pos().line + 1) + ")"; + } + errors.push_back(message + suffix); + } + + llvm::Value* make_none() { return builder.CreateCall(get_rt("rt_make_none", ptr_ty, {})); } + + llvm::Value* make_int(int64_t v) { + return builder.CreateCall(get_rt("rt_make_int", ptr_ty, {i64_ty}), {builder.getInt64(v)}); + } + + llvm::Value* frame_mark() { return builder.CreateCall(get_rt("rt_frame_mark", i64_ty, {})); } + + void frame_push() { builder.CreateCall(get_rt("rt_frame_push", builder.getVoidTy(), {})); } + + void frame_release(llvm::Value* mark) { + builder.CreateCall(get_rt("rt_frame_release", builder.getVoidTy(), {i64_ty}), {mark}); + } + + /// ---- dispatch ---- + + // Emits code for one node. Returns the node's value (an LLVM `ptr` to a + // runtime Value) or nullptr when the node terminated the current block + // (break/continue/return) or reported a compile error. + llvm::Value* gen(const std::shared_ptr& node) { + std::string type = node->get_type(); + if (type == NODE_VALUE) return gen_literal(node); + if (type == NODE_VARACCESS) return gen_var_access(node); + if (type == NODE_VARASSIGN) return gen_var_assign(node); + if (type == NODE_BINOP) return gen_bin_op(node); + if (type == NODE_UNARYOP) return gen_unary_op(node); + if (type == NODE_IF) return gen_if(node); + if (type == NODE_FOR) return gen_for(node); + if (type == NODE_WHILE) return gen_while(node); + if (type == NODE_REPEAT) return gen_repeat(node); + if (type == NODE_ALGODEF) return gen_algo_def(node); + if (type == NODE_ALGOCALL) return gen_algo_call(node); + if (type == NODE_ARRAY) return gen_array(node); + if (type == NODE_ARRACCESS) return gen_array_access(node); + if (type == NODE_ARRASSIGN) return gen_array_assign(node); + if (type == NODE_MEMACCESS) return gen_member_access(node); + if (type == NODE_RETURN) return gen_return(node); + if (type == NODE_BREAK) return gen_loop_jump(node, true); + if (type == NODE_CONTINUE) return gen_loop_jump(node, false); + if (type == NODE_STRUCTDEF) return gen_struct_def(node); + error("compile error: unsupported statement " + type, node); + return nullptr; + } + + // Emits a statement sequence; returns the last statement's value, or + // nullptr when the sequence terminated the block (or errored). + llvm::Value* gen_statements(const NodeList& nodes) { + llvm::Value* last = nullptr; + for (const auto& node : nodes) { + last = gen(node); + if (last == nullptr) { + return nullptr; + } + } + return last; + } + + /// ---- expressions ---- + + llvm::Value* gen_literal(const std::shared_ptr& node) { + std::shared_ptr tok = node->get_tok(); + if (tok->get_type() == TOKEN_INT) { + return make_int(std::stoll(tok->get_value())); + } + if (tok->get_type() == TOKEN_FLOAT) { + return builder.CreateCall(get_rt("rt_make_float", ptr_ty, {f64_ty}), + {llvm::ConstantFP::get(f64_ty, std::stod(tok->get_value()))}); + } + if (tok->get_type() == TOKEN_STRING) { + return builder.CreateCall(get_rt("rt_make_string", ptr_ty, {ptr_ty}), + {cstring(tok->get_value())}); + } + error("compile error: unsupported literal " + tok->get_type(), node); + return nullptr; + } + + llvm::Value* gen_var_access(const std::shared_ptr& node) { + return builder.CreateCall(get_rt("rt_get_var", ptr_ty, {ptr_ty}), + {cstring(node->get_name())}); + } + + llvm::Value* gen_var_assign(const std::shared_ptr& node) { + llvm::Value* value = gen(node->get_child()[0]); + if (value == nullptr) { + return nullptr; + } + return builder.CreateCall(get_rt("rt_set_var", ptr_ty, {ptr_ty, ptr_ty}), + {cstring(node->get_name()), value}); + } + + llvm::Value* as_int(llvm::Value* value) { + return builder.CreateCall(get_rt("rt_as_int", i64_ty, {ptr_ty}), {value}); + } + + llvm::Value* gen_bin_op(const std::shared_ptr& node) { + std::shared_ptr op = node->get_tok(); + if (op->get_type() == TOKEN_KEYWORD && + (op->get_value() == "and" || op->get_value() == "or")) { + return gen_short_circuit(node, op->get_value() == "and"); + } + + static const std::map OP_CODES{ + {TOKEN_ADD, RT_OP_ADD}, {TOKEN_SUB, RT_OP_SUB}, {TOKEN_MUL, RT_OP_MUL}, + {TOKEN_DIV, RT_OP_DIV}, {TOKEN_MOD, RT_OP_MOD}, {TOKEN_POW, RT_OP_POW}, + {TOKEN_EQUAL, RT_OP_EQUAL}, {TOKEN_NEQ, RT_OP_NEQ}, {TOKEN_LESS, RT_OP_LESS}, + {TOKEN_GREATER, RT_OP_GREATER}, {TOKEN_LEQ, RT_OP_LEQ}, {TOKEN_GEQ, RT_OP_GEQ}, + }; + auto code = OP_CODES.find(op->get_type()); + if (code == OP_CODES.end()) { + error("compile error: unsupported operator " + op->get_type(), node); + return nullptr; + } + + NodeList child = node->get_child(); + llvm::Value* lhs = gen(child[0]); + if (lhs == nullptr) return nullptr; + llvm::Value* rhs = gen(child[1]); + if (rhs == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_bin_op", ptr_ty, {i64_ty, ptr_ty, ptr_ty}), + {builder.getInt64(code->second), lhs, rhs}); + } + + // Mirrors visit_bin_op's short-circuit `and` / `or`: the result is always + // an Int 0/1 derived from as_int(). + llvm::Value* gen_short_circuit(const std::shared_ptr& node, bool is_and) { + NodeList child = node->get_child(); + llvm::Value* lhs = gen(child[0]); + if (lhs == nullptr) return nullptr; + + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::BasicBlock* rhs_bb = llvm::BasicBlock::Create(ctx, "sc.rhs", fn); + llvm::BasicBlock* short_bb = llvm::BasicBlock::Create(ctx, "sc.short", fn); + llvm::BasicBlock* merge_bb = llvm::BasicBlock::Create(ctx, "sc.merge", fn); + + llvm::Value* lhs_true = builder.CreateICmpNE(as_int(lhs), builder.getInt64(0)); + if (is_and) { + builder.CreateCondBr(lhs_true, rhs_bb, short_bb); + } else { + builder.CreateCondBr(lhs_true, short_bb, rhs_bb); + } + + builder.SetInsertPoint(short_bb); + llvm::Value* short_value = make_int(is_and ? 0 : 1); + builder.CreateBr(merge_bb); + + builder.SetInsertPoint(rhs_bb); + llvm::Value* rhs = gen(child[1]); + if (rhs == nullptr) return nullptr; + llvm::Value* rhs_value = builder.CreateCall(get_rt("rt_bool", ptr_ty, {ptr_ty}), {rhs}); + llvm::BasicBlock* rhs_end = builder.GetInsertBlock(); + builder.CreateBr(merge_bb); + + builder.SetInsertPoint(merge_bb); + llvm::PHINode* phi = builder.CreatePHI(ptr_ty, 2); + phi->addIncoming(short_value, short_bb); + phi->addIncoming(rhs_value, rhs_end); + return phi; + } + + llvm::Value* gen_unary_op(const std::shared_ptr& node) { + std::shared_ptr op = node->get_tok(); + int64_t code; + if (op->get_type() == TOKEN_ADD) { + code = RT_OP_UPLUS; + } else if (op->get_type() == TOKEN_SUB) { + code = RT_OP_UNEG; + } else if (op->get_type() == TOKEN_KEYWORD && op->get_value() == "not") { + code = RT_OP_UNOT; + } else { + error("compile error: unsupported unary operator " + op->get_type(), node); + return nullptr; + } + llvm::Value* operand = gen(node->get_child()[0]); + if (operand == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_unary_op", ptr_ty, {i64_ty, ptr_ty}), + {builder.getInt64(code), operand}); + } + + /// ---- control flow ---- + + llvm::Value* gen_if(const std::shared_ptr& node) { + IfNode* if_node = dynamic_cast(node.get()); + llvm::Value* cond = gen(if_node->get_condition()); + if (cond == nullptr) return nullptr; + llvm::Value* taken = builder.CreateICmpNE( + builder.CreateCall(get_rt("rt_cond_eq1", i64_ty, {ptr_ty}), {cond}), + builder.getInt64(0)); + + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::BasicBlock* then_bb = llvm::BasicBlock::Create(ctx, "if.then", fn); + llvm::BasicBlock* else_bb = llvm::BasicBlock::Create(ctx, "if.else", fn); + llvm::BasicBlock* merge_bb = llvm::BasicBlock::Create(ctx, "if.merge", fn); + llvm::AllocaInst* slot = entry_alloca(ptr_ty); + builder.CreateCondBr(taken, then_bb, else_bb); + + bool merge_reachable = false; + builder.SetInsertPoint(then_bb); + llvm::Value* then_value = gen_statements(if_node->get_expr()); + if (!errors.empty()) return nullptr; + if (then_value != nullptr) { + builder.CreateStore(then_value, slot); + } + if (!block_terminated()) { + builder.CreateBr(merge_bb); + merge_reachable = true; + } + + builder.SetInsertPoint(else_bb); + llvm::Value* else_value = + if_node->get_else().empty() ? make_int(0) : gen_statements(if_node->get_else()); + if (!errors.empty()) return nullptr; + if (else_value != nullptr) { + builder.CreateStore(else_value, slot); + } + if (!block_terminated()) { + builder.CreateBr(merge_bb); + merge_reachable = true; + } + + if (!merge_reachable) { + merge_bb->eraseFromParent(); + return nullptr; + } + builder.SetInsertPoint(merge_bb); + return builder.CreateLoad(ptr_ty, slot); + } + + llvm::Value* gen_while(const std::shared_ptr& node) { + NodeList child = node->get_child(); + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::BasicBlock* header = llvm::BasicBlock::Create(ctx, "while.cond", fn); + llvm::BasicBlock* body_bb = llvm::BasicBlock::Create(ctx, "while.body", fn); + llvm::BasicBlock* exit_bb = llvm::BasicBlock::Create(ctx, "while.exit", fn); + + llvm::Value* mark = frame_mark(); + builder.CreateBr(header); + + // One arena frame per iteration covers the condition and the body. + builder.SetInsertPoint(header); + frame_push(); + llvm::Value* cond = gen(child[0]); + if (cond == nullptr) return nullptr; + llvm::Value* enter = builder.CreateICmpEQ(as_int(cond), builder.getInt64(1)); + builder.CreateCondBr(enter, body_bb, exit_bb); + + builder.SetInsertPoint(body_bb); + loops.push_back({header, exit_bb, mark, true}); + bool terminated = gen_body(NodeList(child.begin() + 1, child.end())); + loops.pop_back(); + if (!errors.empty()) return nullptr; + if (!terminated) { + frame_release(mark); + builder.CreateBr(header); + } + + builder.SetInsertPoint(exit_bb); + frame_release(mark); + return make_none(); + } + + llvm::Value* gen_repeat(const std::shared_ptr& node) { + NodeList child = node->get_child(); + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::BasicBlock* body_bb = llvm::BasicBlock::Create(ctx, "repeat.body", fn); + llvm::BasicBlock* latch = llvm::BasicBlock::Create(ctx, "repeat.cond", fn); + llvm::BasicBlock* exit_bb = llvm::BasicBlock::Create(ctx, "repeat.exit", fn); + + llvm::Value* mark = frame_mark(); + builder.CreateBr(body_bb); + + builder.SetInsertPoint(body_bb); + frame_push(); + loops.push_back({latch, exit_bb, mark, false}); + bool terminated = gen_body(NodeList(child.begin() + 1, child.end())); + loops.pop_back(); + if (!errors.empty()) return nullptr; + if (!terminated) { + builder.CreateBr(latch); + } + + builder.SetInsertPoint(latch); + llvm::Value* cond = gen(child[0]); + if (cond == nullptr) return nullptr; + llvm::Value* again = builder.CreateICmpEQ(as_int(cond), builder.getInt64(0)); + frame_release(mark); + builder.CreateCondBr(again, body_bb, exit_bb); + + builder.SetInsertPoint(exit_bb); + frame_release(mark); + return make_none(); + } + + llvm::Value* gen_for(const std::shared_ptr& node) { + NodeList child = node->get_child(); + // Evaluation order matches visit_for: assign, step (default 1), end. + llvm::Value* init = gen(child[0]); + if (init == nullptr) return nullptr; + llvm::Value* step = child[2] != nullptr ? gen(child[2]) : make_int(1); + if (step == nullptr) return nullptr; + llvm::Value* end = gen(child[1]); + if (end == nullptr) return nullptr; + builder.CreateCall(get_rt("rt_for_step_check", builder.getVoidTy(), {ptr_ty}), {step}); + + std::string var_name = child[0]->get_name(); + llvm::AllocaInst* slot = entry_alloca(ptr_ty); + builder.CreateStore(init, slot); + + // Dedicated frame keeping the latest loop-variable value alive even if + // the body rebinds the variable (the interpreter holds it in a local). + llvm::Value* var_frame = frame_mark(); + frame_push(); + llvm::Value* iter_mark = frame_mark(); + + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::BasicBlock* header = llvm::BasicBlock::Create(ctx, "for.cond", fn); + llvm::BasicBlock* body_bb = llvm::BasicBlock::Create(ctx, "for.body", fn); + llvm::BasicBlock* latch = llvm::BasicBlock::Create(ctx, "for.latch", fn); + llvm::BasicBlock* exit_bb = llvm::BasicBlock::Create(ctx, "for.exit", fn); + builder.CreateBr(header); + + builder.SetInsertPoint(header); + llvm::Value* keep_going = builder.CreateICmpNE( + builder.CreateCall(get_rt("rt_for_cond", i64_ty, {ptr_ty, ptr_ty, ptr_ty}), + {builder.CreateLoad(ptr_ty, slot), end, step}), + builder.getInt64(0)); + builder.CreateCondBr(keep_going, body_bb, exit_bb); + + builder.SetInsertPoint(body_bb); + frame_push(); + loops.push_back({latch, exit_bb, iter_mark, false}); + bool terminated = gen_body(NodeList(child.begin() + 3, child.end())); + loops.pop_back(); + if (!errors.empty()) return nullptr; + if (!terminated) { + builder.CreateBr(latch); + } + + // Latch mirrors visit_for: the next value comes from the value read at + // the previous latch, not from body reassignments of the variable. + builder.SetInsertPoint(latch); + llvm::Value* next = builder.CreateCall( + get_rt("rt_bin_op", ptr_ty, {i64_ty, ptr_ty, ptr_ty}), + {builder.getInt64(RT_OP_ADD), builder.CreateLoad(ptr_ty, slot), step}); + builder.CreateCall(get_rt("rt_set_var", ptr_ty, {ptr_ty, ptr_ty}), + {cstring(var_name), next}); + builder.CreateCall(get_rt("rt_loop_keep", builder.getVoidTy(), {i64_ty, ptr_ty}), + {var_frame, next}); + builder.CreateStore(next, slot); + frame_release(iter_mark); + builder.CreateBr(header); + + builder.SetInsertPoint(exit_bb); + frame_release(var_frame); + return make_none(); + } + + // Emits a loop body (one or more statements). Returns true when the body + // terminated the current block (break/continue/return on every path). + bool gen_body(const NodeList& body) { + for (const auto& stmt : body) { + if (gen(stmt) == nullptr) { + return block_terminated() || !errors.empty(); + } + } + return false; + } + + llvm::Value* gen_loop_jump(const std::shared_ptr& node, bool is_break) { + if (loops.empty()) { + error(std::string("compile error: ") + (is_break ? "break" : "continue") + + " outside of a loop is not supported by the compiler", + node); + return nullptr; + } + const LoopContext& loop = loops.back(); + if (is_break) { + frame_release(loop.iter_mark); + builder.CreateBr(loop.exit); + } else { + if (loop.release_on_continue) { + frame_release(loop.iter_mark); + } + builder.CreateBr(loop.latch); + } + return nullptr; + } + + llvm::Value* gen_return(const std::shared_ptr& node) { + llvm::Value* value = gen(node->get_child()[0]); + if (value == nullptr) return nullptr; + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + if (fn->getName() == "main") { + // Top-level `return` does not stop execution in the interpreter. + return value; + } + builder.CreateRet(value); + return nullptr; + } + + /// ---- functions and calls ---- + + llvm::Function* emit_algo_function(const std::shared_ptr& node, const std::string& name) { + AlgorithmDefNode* def = dynamic_cast(node.get()); + llvm::Function* fn = llvm::Function::Create( + llvm::FunctionType::get(ptr_ty, false), llvm::Function::PrivateLinkage, + "ps.algo." + std::to_string(algo_counter++) + "." + name, module); + + llvm::IRBuilderBase::InsertPoint saved = builder.saveIP(); + std::vector saved_loops; + saved_loops.swap(loops); + + llvm::BasicBlock* entry = llvm::BasicBlock::Create(ctx, "entry", fn); + builder.SetInsertPoint(entry); + llvm::Value* last = gen_statements(def->get_body()); + if (!block_terminated() && errors.empty()) { + builder.CreateRet(last != nullptr ? last : make_none()); + } + + loops.swap(saved_loops); + builder.restoreIP(saved); + if (!errors.empty()) { + return nullptr; + } + return fn; + } + + llvm::Value* gen_algo_value(const std::shared_ptr& node, const std::string& name, + bool define_global) { + std::vector arg_names; + for (const auto& tok : node->get_toks()) { + arg_names.push_back(tok->get_value()); + } + + llvm::Function* fn = emit_algo_function(node, name); + if (fn == nullptr) { + return nullptr; + } + + bool memoizable = is_memoizable_numeric_algo(node, name, arg_names); + std::string rt_name = define_global ? "rt_define_algo" : "rt_make_algo"; + return builder.CreateCall(get_rt(rt_name, ptr_ty, {ptr_ty, ptr_ty, ptr_ty, i64_ty, i64_ty}), + {cstring(name), fn, string_array(arg_names, "args"), + builder.getInt64(static_cast(arg_names.size())), + builder.getInt64(memoizable ? 1 : 0)}); + } + + llvm::Value* gen_algo_def(const std::shared_ptr& node) { + std::string name = node->get_name(); + size_t scope = name.find("::"); + if (scope == std::string::npos) { + // Registration happens at the definition site, like visit_algo_def. + return gen_algo_value(node, name, true); + } + + std::string struct_name = name.substr(0, scope); + std::string method_name = name.substr(scope + 2); + llvm::Value* method = gen_algo_value(node, name, true); + if (method == nullptr) { + return nullptr; + } + builder.CreateCall(get_rt("rt_struct_add_method", ptr_ty, {ptr_ty, ptr_ty, ptr_ty}), + {cstring(struct_name), cstring(method_name), method}); + return method; + } + + llvm::Value* gen_algo_call(const std::shared_ptr& node) { + AlgorithmCallNode* call = dynamic_cast(node.get()); + std::shared_ptr callee_node = call->get_call(); + llvm::Value* callee; + if (callee_node->get_type() == NODE_VARACCESS) { + callee = gen_var_access(callee_node); + } else { + callee = gen(callee_node); + } + if (callee == nullptr) return nullptr; + + const NodeList& args = call->get_args(); + llvm::Value* argv; + if (args.empty()) { + argv = llvm::ConstantPointerNull::get(ptr_ty); + } else { + llvm::ArrayType* argv_ty = llvm::ArrayType::get(ptr_ty, args.size()); + llvm::Function* fn = builder.GetInsertBlock()->getParent(); + llvm::IRBuilder<> tmp(&fn->getEntryBlock(), fn->getEntryBlock().begin()); + llvm::AllocaInst* slots = tmp.CreateAlloca(argv_ty); + for (size_t i = 0; i < args.size(); ++i) { + llvm::Value* arg = gen(args[i]); + if (arg == nullptr) return nullptr; + builder.CreateStore(arg, builder.CreateConstInBoundsGEP2_64(argv_ty, slots, 0, i)); + } + argv = slots; + } + return builder.CreateCall( + get_rt("rt_call", ptr_ty, {ptr_ty, ptr_ty, i64_ty}), + {callee, argv, builder.getInt64(static_cast(args.size()))}); + } + + llvm::Value* gen_struct_def(const std::shared_ptr& node) { + std::vector member_names; + for (const auto& tok : node->get_toks()) { + member_names.push_back(tok->get_value()); + } + + std::vector method_names; + std::vector methods; + for (const auto& method_node : node->get_child()) { + std::string method_name = method_node->get_name(); + llvm::Value* method = gen_algo_value(method_node, method_name, false); + if (method == nullptr) { + return nullptr; + } + method_names.push_back(method_name); + methods.push_back(method); + } + + return builder.CreateCall( + get_rt("rt_define_struct", ptr_ty, {ptr_ty, ptr_ty, i64_ty, ptr_ty, ptr_ty, i64_ty}), + {cstring(node->get_name()), string_array(member_names, "members"), + builder.getInt64(static_cast(member_names.size())), + string_array(method_names, "method.names"), value_array(methods), + builder.getInt64(static_cast(methods.size()))}); + } + + /// ---- arrays, hash tables, members ---- + + llvm::Value* gen_array(const std::shared_ptr& node) { + llvm::Value* array = builder.CreateCall(get_rt("rt_array_new", ptr_ty, {})); + for (const auto& element : node->get_child()) { + llvm::Value* value = gen(element); + if (value == nullptr) return nullptr; + builder.CreateCall(get_rt("rt_array_push", builder.getVoidTy(), {ptr_ty, ptr_ty}), + {array, value}); + } + return array; + } + + llvm::Value* gen_array_access(const std::shared_ptr& node) { + NodeList child = node->get_child(); + llvm::Value* container = gen(child[0]); + if (container == nullptr) return nullptr; + llvm::Value* index = gen(child[1]); + if (index == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_index", ptr_ty, {ptr_ty, ptr_ty}), {container, index}); + } + + llvm::Value* gen_array_assign(const std::shared_ptr& node) { + NodeList child = node->get_child(); + if (child[0]->get_type() == NODE_MEMACCESS) { + NodeList member_child = child[0]->get_child(); + llvm::Value* object = gen(member_child[0]); + if (object == nullptr) return nullptr; + llvm::Value* value = gen(child[1]); + if (value == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_member_assign", ptr_ty, {ptr_ty, ptr_ty, ptr_ty}), + {object, cstring(member_child[1]->get_name()), value}); + } + if (child[0]->get_type() != NODE_ARRACCESS) { + error( + "compile error: assignment target must be an array element or " + "object member", + node); + return nullptr; + } + NodeList access_child = child[0]->get_child(); + llvm::Value* container = gen(access_child[0]); + if (container == nullptr) return nullptr; + llvm::Value* index = gen(access_child[1]); + if (index == nullptr) return nullptr; + llvm::Value* value = gen(child[1]); + if (value == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_index_assign", ptr_ty, {ptr_ty, ptr_ty, ptr_ty}), + {container, index, value}); + } + + llvm::Value* gen_member_access(const std::shared_ptr& node) { + NodeList child = node->get_child(); + llvm::Value* object = gen(child[0]); + if (object == nullptr) return nullptr; + return builder.CreateCall(get_rt("rt_member_access", ptr_ty, {ptr_ty, ptr_ty}), + {object, cstring(child[1]->get_name())}); + } +}; + +} // namespace + +bool Compiler::compile(const NodeList& ast, llvm::Module& module, + std::vector& errors) { + CodeGen codegen(module, errors); + return codegen.run(ast); +} diff --git a/src/compiler.h b/src/compiler.h new file mode 100644 index 0000000..829a9ff --- /dev/null +++ b/src/compiler.h @@ -0,0 +1,26 @@ +/// -------------------- +/// LLVM AOT compiler +/// -------------------- + +#ifndef COMPILER_H +#define COMPILER_H + +#include +#include + +#include "node.h" + +namespace llvm { +class Module; +} // namespace llvm + +class Compiler { + public: + // Lowers the parsed program into `module` (a `main` function plus one + // function per Algorithm). Returns false and fills `errors` when the + // program uses features the compiler does not support. + static bool compile(const NodeList& ast, llvm::Module& module, + std::vector& errors); +}; + +#endif diff --git a/src/imports.h b/src/imports.h new file mode 100644 index 0000000..32e1bbf --- /dev/null +++ b/src/imports.h @@ -0,0 +1,22 @@ +/// -------------------- +/// Import expansion +/// -------------------- + +#ifndef IMPORTS_H +#define IMPORTS_H + +#include +#include + +struct ImportState { + std::set loaded; + std::set loading; +}; + +// Recursively replaces `import` lines in `text` with the imported file +// contents. Returns false and fills `error` on unresolved, unreadable, or +// circular imports. +bool expand_imports(const std::string& file_name, const std::string& text, ImportState& state, + std::string& expanded, std::string& error); + +#endif diff --git a/src/interpreter.cpp b/src/interpreter.cpp index 47e39bb..26a00e0 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -3,13 +3,24 @@ /// -------------------- #include "interpreter.h" -#include "node.h" -#include "value.h" + +#include +#include +#include #include +#include #include -#include +#include #include +#include #include +#include +#include + +#include "jit.h" +#include "node.h" +#include "token.h" +#include "value.h" namespace { constexpr int JIT_HOT_THRESHOLD = 8; @@ -22,7 +33,7 @@ bool is_jit_root(const std::shared_ptr& node) { bool has_assignment_node(const std::shared_ptr& node) { if (!node) return false; - const std::string type = node->get_type(); + std::string type = node->get_type(); if (type == NODE_VARASSIGN || type == NODE_ARRASSIGN) { return true; } @@ -42,74 +53,78 @@ bool has_assignment_node(const std::shared_ptr& node) { } return false; } -} // namespace +} // namespace std::shared_ptr Interpreter::visit(std::shared_ptr node) { if (std::optional> jit_result = try_visit_jit(node)) { return *jit_result; } - if(node->get_type() == NODE_VALUE) { + if (node->get_type() == NODE_VALUE) { return visit_number(node); } - if(node->get_type() == NODE_VARACCESS) { + if (node->get_type() == NODE_VARACCESS) { return visit_var_access(node); } - if(node->get_type() == NODE_VARASSIGN) { + if (node->get_type() == NODE_VARASSIGN) { return visit_var_assign(node); } - if(node->get_type() == NODE_BINOP) { + if (node->get_type() == NODE_BINOP) { return visit_bin_op(node); } - if(node->get_type() == NODE_UNARYOP) { + if (node->get_type() == NODE_UNARYOP) { return visit_unary_op(node); } - if(node->get_type() == NODE_IF) { + if (node->get_type() == NODE_IF) { return visit_if(node); } - if(node->get_type() == NODE_FOR) { + if (node->get_type() == NODE_FOR) { return visit_for(node); } - if(node->get_type() == NODE_WHILE) { + if (node->get_type() == NODE_WHILE) { return visit_while(node); } - if(node->get_type() == NODE_REPEAT) { + if (node->get_type() == NODE_REPEAT) { return visit_repeat(node); } - if(node->get_type() == NODE_ALGODEF) { + if (node->get_type() == NODE_ALGODEF) { return visit_algo_def(node); } - if(node->get_type() == NODE_STRUCTDEF) { + if (node->get_type() == NODE_STRUCTDEF) { return visit_struct_def(node); } - if(node->get_type() == NODE_ALGOCALL) { + if (node->get_type() == NODE_ALGOCALL) { return visit_algo_call(node); } - if(node->get_type() == NODE_ARRAY) { + if (node->get_type() == NODE_ARRAY) { return visit_array(node); } - if(node->get_type() == NODE_ARRACCESS) { + if (node->get_type() == NODE_ARRACCESS) { return visit_array_access(node); } - if(node->get_type() == NODE_ARRASSIGN) { + if (node->get_type() == NODE_ARRASSIGN) { return visit_array_assign(node); } - if(node->get_type() == NODE_MEMACCESS) { + if (node->get_type() == NODE_MEMACCESS) { return visit_member_access(node); } - if(node->get_type() == NODE_RETURN) { + if (node->get_type() == NODE_RETURN) { return visit_return(node); } - if(node->get_type() == NODE_BREAK) { + if (node->get_type() == NODE_BREAK) { return std::make_shared(VALUE_BREAK); } - if(node->get_type() == NODE_CONTINUE) { + if (node->get_type() == NODE_CONTINUE) { return std::make_shared(VALUE_CONTINUE); } + if (node->get_type() == NODE_PRECOMPUTED) { + return dynamic_cast(node.get())->get_value(); + } return std::make_shared(VALUE_ERROR, "Fail to get result\n"); } -std::optional> Interpreter::try_visit_jit(const std::shared_ptr& node) { +std::optional> Interpreter::try_visit_jit( + const std::shared_ptr& node) { static std::unordered_map jit_cache; if (!is_jit_root(node)) { @@ -140,12 +155,15 @@ std::optional> Interpreter::try_visit_jit(const std::shar } std::shared_ptr Interpreter::visit_number(std::shared_ptr node) { - if(node->get_tok()->get_type() == TOKEN_INT) - return std::make_shared>(VALUE_INT, std::stoll(node->get_tok()->get_value())); - else if(node->get_tok()->get_type() == TOKEN_FLOAT) - return std::make_shared>(VALUE_FLOAT, std::stod(node->get_tok()->get_value())); - else if(node->get_tok()->get_type() == TOKEN_STRING) - return std::make_shared>(VALUE_STRING, node->get_tok()->get_value()); + if (node->get_tok()->get_type() == TOKEN_INT) + return std::make_shared>(VALUE_INT, + std::stoll(node->get_tok()->get_value())); + else if (node->get_tok()->get_type() == TOKEN_FLOAT) + return std::make_shared>(VALUE_FLOAT, + std::stod(node->get_tok()->get_value())); + else if (node->get_tok()->get_type() == TOKEN_STRING) + return std::make_shared>(VALUE_STRING, + node->get_tok()->get_value()); else return std::make_shared(VALUE_ERROR, "Not a value type\n"); } @@ -167,15 +185,15 @@ std::shared_ptr Interpreter::visit_var_assign(std::shared_ptr node) if (suffix->get_type() == VALUE_ERROR) { return suffix; } - if (suffix->get_type() == VALUE_STRING && current->append_string(suffix->as_string())) { + if (suffix->get_type() == VALUE_STRING && + current->append_string(suffix->as_string())) { return current; } } } } std::shared_ptr value = visit(child[0]); - if(value->get_type() == VALUE_ERROR) - return value; + if (value->get_type() == VALUE_ERROR) return value; symbol_table.set(var_name, value); return symbol_table.get(var_name); } @@ -184,42 +202,35 @@ std::shared_ptr Interpreter::visit_bin_op(std::shared_ptr node) { NodeList child = node->get_child(); std::shared_ptr a, b; a = visit(child[0]); - if(a->get_type() == VALUE_ERROR) - return a; - if(node->get_tok()->get_type() == TOKEN_KEYWORD && node->get_tok()->get_value() == "and") { - if(a->as_int() == 0) - return std::make_shared>(VALUE_INT, 0); + if (a->get_type() == VALUE_ERROR) return a; + if (node->get_tok()->get_type() == TOKEN_KEYWORD && node->get_tok()->get_value() == "and") { + if (a->as_int() == 0) return std::make_shared>(VALUE_INT, 0); b = visit(child[1]); - if(b->get_type() == VALUE_ERROR) - return b; + if (b->get_type() == VALUE_ERROR) return b; return std::make_shared>(VALUE_INT, b->as_int() != 0); } - if(node->get_tok()->get_type() == TOKEN_KEYWORD && node->get_tok()->get_value() == "or") { - if(a->as_int() != 0) - return std::make_shared>(VALUE_INT, 1); + if (node->get_tok()->get_type() == TOKEN_KEYWORD && node->get_tok()->get_value() == "or") { + if (a->as_int() != 0) return std::make_shared>(VALUE_INT, 1); b = visit(child[1]); - if(b->get_type() == VALUE_ERROR) - return b; + if (b->get_type() == VALUE_ERROR) return b; return std::make_shared>(VALUE_INT, b->as_int() != 0); } b = visit(child[1]); - if(b->get_type() == VALUE_ERROR) - return b; + if (b->get_type() == VALUE_ERROR) return b; return bin_op(a, b, node->get_tok()); } std::shared_ptr Interpreter::visit_unary_op(std::shared_ptr node) { NodeList child = node->get_child(); std::shared_ptr a = visit(child[0]); - if(a->get_type() == VALUE_ERROR) - return a; + if (a->get_type() == VALUE_ERROR) return a; return unary_op(a, node->get_tok()); } std::shared_ptr Interpreter::visit_array(std::shared_ptr node) { NodeList child = node->get_child(); ValueList array_value; - for(int i{0}; i < child.size(); ++i) { + for (int i{0}; i < child.size(); ++i) { array_value.push_back(visit(child[i])); } return std::make_shared(array_value); @@ -228,27 +239,27 @@ std::shared_ptr Interpreter::visit_array(std::shared_ptr node) { std::shared_ptr& Interpreter::visit_array_access(std::shared_ptr node) { NodeList child{node->get_child()}; std::shared_ptr arr{visit(child[0])}, index{visit(child[1])}; - if(arr->get_type() == VALUE_STRING) { + if (arr->get_type() == VALUE_STRING) { std::string str = arr->as_string(); int p = index->as_int(); if (1 <= p && p <= static_cast(str.size())) { - error = std::make_shared>( - VALUE_STRING, std::string(1, str[p - 1])); + error = + std::make_shared>(VALUE_STRING, std::string(1, str[p - 1])); } else { error = std::make_shared( VALUE_ERROR, "Index out of range, size: " + std::to_string(str.size()) + - ", position: " + std::to_string(p)); + ", position: " + std::to_string(p)); } return error; } - if(arr->get_type() == VALUE_HASH_TABLE) { + if (arr->get_type() == VALUE_HASH_TABLE) { algo_call_temp = arr; error = dynamic_cast(arr.get())->get(index); return error; } - if(arr->get_type() != VALUE_ARRAY) { - error = std::make_shared(VALUE_ERROR, "Access can only apply on array, find " + - arr->get_type() + "\n"); + if (arr->get_type() != VALUE_ARRAY) { + error = std::make_shared( + VALUE_ERROR, "Access can only apply on array, find " + arr->get_type() + "\n"); return error; } algo_call_temp = arr; @@ -257,11 +268,11 @@ std::shared_ptr& Interpreter::visit_array_access(std::shared_ptr no std::shared_ptr Interpreter::visit_array_assign(std::shared_ptr node) { NodeList child{node->get_child()}; - if(child[0]->get_type() == NODE_MEMACCESS) { + if (child[0]->get_type() == NODE_MEMACCESS) { // Handle member assignment: obj.member <- val - // visit_member_access currently returns a value (possibly a BoundMethodValue or just a value). - // But for assignment we need to set the member. - // We need to unpack NODE_MEMACCESS to get obj and member name. + // visit_member_access currently returns a value (possibly a BoundMethodValue or just a + // value). But for assignment we need to set the member. We need to unpack NODE_MEMACCESS to + // get obj and member name. std::shared_ptr obj_node = child[0]->get_child()[0]; std::shared_ptr member_node = child[0]->get_child()[1]; @@ -273,10 +284,12 @@ std::shared_ptr Interpreter::visit_array_assign(std::shared_ptr nod inst->set_member(member_node->get_name(), val); return val; } else { - return std::make_shared(VALUE_ERROR, "Assignment to member only supported for Struct Instances\n"); + return std::make_shared( + VALUE_ERROR, "Assignment to member only supported for Struct Instances\n"); } - } else if(child[0]->get_type() != NODE_ARRACCESS) { - return std::make_shared(VALUE_ERROR, "Access can only apply on array or object member\n"); + } else if (child[0]->get_type() != NODE_ARRACCESS) { + return std::make_shared(VALUE_ERROR, + "Access can only apply on array or object member\n"); } NodeList access_child{child[0]->get_child()}; std::shared_ptr obj{visit(access_child[0])}; @@ -287,17 +300,17 @@ std::shared_ptr Interpreter::visit_array_assign(std::shared_ptr nod if (value->get_type() == VALUE_ERROR) return value; return dynamic_cast(obj.get())->set(key, value); } - std::shared_ptr &arr{visit_array_access(child[0])}, value{visit(child[1])}; + std::shared_ptr&arr{visit_array_access(child[0])}, value{visit(child[1])}; return arr = value; } std::shared_ptr Interpreter::visit_member_access(std::shared_ptr node) { NodeList child{node->get_child()}; std::shared_ptr obj{visit(child[0])}; - std::shared_ptr &member{child[1]}; + std::shared_ptr& member{child[1]}; - if(obj->get_type() == VALUE_ARRAY || obj->get_type() == VALUE_STRING || - obj->get_type() == VALUE_HASH_TABLE) { + if (obj->get_type() == VALUE_ARRAY || obj->get_type() == VALUE_STRING || + obj->get_type() == VALUE_HASH_TABLE) { std::string member_name = member->get_name(); return std::make_shared(obj, member_name); } else if (obj->get_type() == VALUE_INSTANCE) { @@ -305,33 +318,33 @@ std::shared_ptr Interpreter::visit_member_access(std::shared_ptr no InstanceValue* inst = dynamic_cast(obj.get()); return inst->get_member(member_name, obj); } - error = std::make_shared(VALUE_ERROR, obj->get_num() + " has no member " + member->get_name() + "\n"); + error = std::make_shared( + VALUE_ERROR, obj->get_num() + " has no member " + member->get_name() + "\n"); return error; -} +} std::shared_ptr Interpreter::visit_if(std::shared_ptr node) { IfNode* if_node = dynamic_cast(node.get()); std::shared_ptr cond = visit(if_node->get_condition()); - if(cond->get_type() == VALUE_ERROR) - return cond; - if(std::stoll(cond->get_num()) == 1) { + if (cond->get_type() == VALUE_ERROR) return cond; + if (std::stoll(cond->get_num()) == 1) { std::shared_ptr ret; - for(auto expr : if_node->get_expr()) { + for (auto expr : if_node->get_expr()) { ret = visit(expr); - if(ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN || - ret->get_type() == VALUE_BREAK || ret->get_type() == VALUE_CONTINUE) { - return ret; - } + if (ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN || + ret->get_type() == VALUE_BREAK || ret->get_type() == VALUE_CONTINUE) { + return ret; + } } return ret; - } else if(!if_node->get_else().empty()) { + } else if (!if_node->get_else().empty()) { std::shared_ptr ret; - for(auto expr : if_node->get_else()) { + for (auto expr : if_node->get_else()) { ret = visit(expr); - if(ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN || - ret->get_type() == VALUE_BREAK || ret->get_type() == VALUE_CONTINUE) { - return ret; - } + if (ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN || + ret->get_type() == VALUE_BREAK || ret->get_type() == VALUE_CONTINUE) { + return ret; + } } return ret; } @@ -343,22 +356,22 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { std::shared_ptr i = visit(child[0]); if (i->get_type() == VALUE_ERROR) return i; std::shared_ptr step; - if(child[2] != nullptr) { + if (child[2] != nullptr) { step = visit(child[2]); if (step->get_type() == VALUE_ERROR) return step; } else { step = std::make_shared>(VALUE_INT, 1); } std::shared_ptr end_value = visit(child[1]); - if(end_value->get_type() == VALUE_ERROR) return end_value; + if (end_value->get_type() == VALUE_ERROR) return end_value; std::function, std::shared_ptr)> condition; - if(step->as_double() > 0) { + if (step->as_double() > 0) { condition = [](std::shared_ptr i, std::shared_ptr end) -> bool { if (i->get_type() == VALUE_FLOAT || end->get_type() == VALUE_FLOAT) return i->as_double() <= end->as_double(); return i->as_int() <= end->as_int(); }; - } else if(step->as_double() < 0) { + } else if (step->as_double() < 0) { condition = [](std::shared_ptr i, std::shared_ptr end) -> bool { if (i->get_type() == VALUE_FLOAT || end->get_type() == VALUE_FLOAT) return i->as_double() >= end->as_double(); @@ -384,7 +397,8 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { fast_assign_name = child[3]->get_name(); fast_assign_program = ExpressionJit::compile(assign_child[0]); if (!fast_assign_program && assign_child[0]->get_type() == NODE_ALGOCALL) { - AlgorithmCallNode* call_node = dynamic_cast(assign_child[0].get()); + AlgorithmCallNode* call_node = + dynamic_cast(assign_child[0].get()); if (call_node->get_call()->get_type() == NODE_VARACCESS) { std::shared_ptr callee = symbol_table.get(call_node->get_name()); AlgoValue* algo = dynamic_cast(callee.get()); @@ -399,7 +413,8 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { if (fast_call_body_program) { bool args_compiled = true; for (const auto& arg : call_node->get_args()) { - std::optional arg_program = ExpressionJit::compile(arg); + std::optional arg_program = + ExpressionJit::compile(arg); if (!arg_program) { args_compiled = false; break; @@ -436,18 +451,16 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { } ValueList ret; - while(condition(i, end_value)) { + while (condition(i, end_value)) { if (fast_assign_program) { std::optional> val = fast_assign_program->execute(symbol_table); if (!val) { fast_assign_program.reset(); std::shared_ptr fallback = visit(child[3]); - if(fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) + if (fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) return fallback; - if(fallback->get_type() == VALUE_BREAK) - goto end_for_loop; - if(fallback->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if (fallback->get_type() == VALUE_BREAK) goto end_for_loop; + if (fallback->get_type() == VALUE_CONTINUE) goto next_for_iteration; } else { if ((*val)->get_type() == VALUE_ERROR || (*val)->get_type() == VALUE_RETURN) return *val; @@ -461,8 +474,7 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { std::optional> arg = fast_call_arg_programs[arg_index].execute(symbol_table); if (!arg || (*arg)->get_type() == VALUE_ERROR) { - if (arg && (*arg)->get_type() == VALUE_ERROR) - return *arg; + if (arg && (*arg)->get_type() == VALUE_ERROR) return *arg; failed = true; break; } @@ -477,7 +489,8 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { fast_call_body_program->execute(symbol_table); if (!val) { failed = true; - } else if ((*val)->get_type() == VALUE_ERROR || (*val)->get_type() == VALUE_RETURN) { + } else if ((*val)->get_type() == VALUE_ERROR || + (*val)->get_type() == VALUE_RETURN) { return *val; } else if ((*val)->get_type() == VALUE_BREAK) { goto end_for_loop; @@ -488,8 +501,8 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { } } - for (int arg_index = static_cast(had_saved_values.size()) - 1; - arg_index >= 0; --arg_index) { + for (int arg_index = static_cast(had_saved_values.size()) - 1; arg_index >= 0; + --arg_index) { if (had_saved_values[arg_index]) { symbol_table.set(fast_call_arg_names[arg_index], saved_values[arg_index]); } else { @@ -500,59 +513,48 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { if (failed) { fast_call_body_program.reset(); std::shared_ptr fallback = visit(child[3]); - if(fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) + if (fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) return fallback; - if(fallback->get_type() == VALUE_BREAK) - goto end_for_loop; - if(fallback->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if (fallback->get_type() == VALUE_BREAK) goto end_for_loop; + if (fallback->get_type() == VALUE_CONTINUE) goto next_for_iteration; } } else if (fast_array && fast_array_index_program && fast_array_value_program) { - std::optional> index = fast_array_index_program->execute(symbol_table); - std::optional> val = fast_array_value_program->execute(symbol_table); + std::optional> index = + fast_array_index_program->execute(symbol_table); + std::optional> val = + fast_array_value_program->execute(symbol_table); if (!index || !val) { fast_array = nullptr; std::shared_ptr fallback = visit(child[3]); - if(fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) + if (fallback->get_type() == VALUE_ERROR || fallback->get_type() == VALUE_RETURN) return fallback; - if(fallback->get_type() == VALUE_BREAK) - goto end_for_loop; - if(fallback->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if (fallback->get_type() == VALUE_BREAK) goto end_for_loop; + if (fallback->get_type() == VALUE_CONTINUE) goto next_for_iteration; } else { - if ((*index)->get_type() == VALUE_ERROR) - return *index; + if ((*index)->get_type() == VALUE_ERROR) return *index; if ((*val)->get_type() == VALUE_ERROR || (*val)->get_type() == VALUE_RETURN) return *val; - if ((*val)->get_type() == VALUE_BREAK) - goto end_for_loop; - if ((*val)->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if ((*val)->get_type() == VALUE_BREAK) goto end_for_loop; + if ((*val)->get_type() == VALUE_CONTINUE) goto next_for_iteration; fast_array->operator[]((*index)->as_int()) = *val; } - } else if(child.size() == 4) { + } else if (child.size() == 4) { std::shared_ptr val = visit(child[3]); - if(val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) - return val; - if(val->get_type() == VALUE_BREAK) - goto end_for_loop; - if(val->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if (val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) return val; + if (val->get_type() == VALUE_BREAK) goto end_for_loop; + if (val->get_type() == VALUE_CONTINUE) goto next_for_iteration; if (collect_loop_results) { ret.push_back(val); } } else { - for(int index{3}; index < child.size(); ++index) { + for (int index{3}; index < child.size(); ++index) { std::shared_ptr val{visit(child[index])}; - if(val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) - return val; - if(val->get_type() == VALUE_BREAK) - goto end_for_loop; - if(val->get_type() == VALUE_CONTINUE) - goto next_for_iteration; + if (val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) return val; + if (val->get_type() == VALUE_BREAK) goto end_for_loop; + if (val->get_type() == VALUE_CONTINUE) goto next_for_iteration; } } -next_for_iteration: + next_for_iteration: symbol_table.set(child[0]->get_name(), i + step); i = symbol_table.get(child[0]->get_name()); } @@ -561,46 +563,39 @@ std::shared_ptr Interpreter::visit_for(std::shared_ptr node) { static std::shared_ptr none = std::make_shared(); return none; } - if(child.size() != 4) - ret.push_back(std::make_shared()); + if (child.size() != 4) ret.push_back(std::make_shared()); return std::make_shared(ret); } std::shared_ptr Interpreter::visit_while(std::shared_ptr node) { NodeList child = node->get_child(); ValueList ret; - while(visit(child[0])->as_int() == 1) { - if(child.size() == 2) { + while (visit(child[0])->as_int() == 1) { + if (child.size() == 2) { std::shared_ptr val = visit(child[1]); - if(val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) - return val; - if(val->get_type() == VALUE_BREAK) - break; - if(val->get_type() == VALUE_CONTINUE) - continue; + if (val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) return val; + if (val->get_type() == VALUE_BREAK) break; + if (val->get_type() == VALUE_CONTINUE) continue; if (collect_loop_results) { ret.push_back(val); } } else { bool should_continue = false; bool should_break = false; - for(int index{1}; index < child.size(); ++index) { + for (int index{1}; index < child.size(); ++index) { std::shared_ptr ret{visit(child[index])}; - if(ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN) - return ret; - if(ret->get_type() == VALUE_BREAK) { + if (ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN) return ret; + if (ret->get_type() == VALUE_BREAK) { should_break = true; break; } - if(ret->get_type() == VALUE_CONTINUE) { + if (ret->get_type() == VALUE_CONTINUE) { should_continue = true; break; } } - if(should_break) - break; - if(should_continue) - continue; + if (should_break) break; + if (should_continue) continue; } } if (!collect_loop_results) { @@ -614,39 +609,33 @@ std::shared_ptr Interpreter::visit_repeat(std::shared_ptr node) { NodeList child = node->get_child(); ValueList ret; do { - if(child.size() == 2) { + if (child.size() == 2) { std::shared_ptr val = visit(child[1]); - if(val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) - return val; - if(val->get_type() == VALUE_BREAK) - break; - if(val->get_type() == VALUE_CONTINUE) - continue; + if (val->get_type() == VALUE_ERROR || val->get_type() == VALUE_RETURN) return val; + if (val->get_type() == VALUE_BREAK) break; + if (val->get_type() == VALUE_CONTINUE) continue; if (collect_loop_results) { ret.push_back(val); } } else { bool should_continue = false; bool should_break = false; - for(int index{1}; index < child.size(); ++index) { + for (int index{1}; index < child.size(); ++index) { std::shared_ptr ret{visit(child[index])}; - if(ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN) - return ret; - if(ret->get_type() == VALUE_BREAK) { + if (ret->get_type() == VALUE_ERROR || ret->get_type() == VALUE_RETURN) return ret; + if (ret->get_type() == VALUE_BREAK) { should_break = true; break; } - if(ret->get_type() == VALUE_CONTINUE) { + if (ret->get_type() == VALUE_CONTINUE) { should_continue = true; break; } } - if(should_break) - break; - if(should_continue) - continue; + if (should_break) break; + if (should_continue) continue; } - } while(visit(child[0])->as_int() == 0); + } while (visit(child[0])->as_int() == 0); if (!collect_loop_results) { static std::shared_ptr none = std::make_shared(); return none; @@ -657,12 +646,12 @@ std::shared_ptr Interpreter::visit_repeat(std::shared_ptr node) { std::shared_ptr Interpreter::visit_algo_def(std::shared_ptr node) { std::string algo_name = node->get_name(); std::shared_ptr value = std::make_shared(algo_name, node); - + size_t pos = algo_name.find("::"); if (pos != std::string::npos) { std::string struct_name = algo_name.substr(0, pos); std::string method_name = algo_name.substr(pos + 2); - + std::shared_ptr struct_val = symbol_table.get(struct_name); if (struct_val->get_type() == VALUE_STRUCT) { StructValue* s = dynamic_cast(struct_val.get()); @@ -684,18 +673,19 @@ std::shared_ptr Interpreter::visit_algo_call(std::shared_ptr node) return *array_result; } - AlgorithmCallNode *algo_call_node = dynamic_cast(node.get()); + AlgorithmCallNode* algo_call_node = dynamic_cast(node.get()); std::shared_ptr algo_node = algo_call_node->get_call(); std::shared_ptr algo; - if(algo_node->get_type() == NODE_VARACCESS) + if (algo_node->get_type() == NODE_VARACCESS) algo = symbol_table.get(algo_call_node->get_name()); else algo = visit(algo_node); return algo->execute(algo_call_node->get_args(), &symbol_table); } -std::optional> Interpreter::try_visit_array_method_call(const std::shared_ptr& node) { - AlgorithmCallNode *algo_call_node = dynamic_cast(node.get()); +std::optional> Interpreter::try_visit_array_method_call( + const std::shared_ptr& node) { + AlgorithmCallNode* algo_call_node = dynamic_cast(node.get()); if (!algo_call_node) { return std::nullopt; } @@ -706,13 +696,11 @@ std::optional> Interpreter::try_visit_array_method_call(c } NodeList member_child = call_node->get_child(); - const std::string method_name = member_child[1]->get_name(); - const bool supported_method = - method_name == "push" || method_name == "push_back" || - method_name == "pop" || method_name == "pop_back" || - method_name == "resize" || method_name == "size" || - method_name == "back" || method_name == "insert" || - method_name == "remove"; + std::string method_name = member_child[1]->get_name(); + bool supported_method = + method_name == "push" || method_name == "push_back" || method_name == "pop" || + method_name == "pop_back" || method_name == "resize" || method_name == "size" || + method_name == "back" || method_name == "insert" || method_name == "remove"; if (!supported_method || member_child[0]->get_type() != NODE_VARACCESS) { return std::nullopt; } @@ -721,7 +709,7 @@ std::optional> Interpreter::try_visit_array_method_call(c if (obj->get_type() != VALUE_ARRAY) { return std::nullopt; } - ArrayValue *arr_obj = dynamic_cast(obj.get()); + ArrayValue* arr_obj = dynamic_cast(obj.get()); const NodeList& args = algo_call_node->get_args(); auto visit_arg = [this](const std::shared_ptr& arg) -> std::shared_ptr { @@ -735,8 +723,8 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "push" || method_name == "push_back") { if (args.size() != 1) { - return std::make_shared( - VALUE_ERROR, "Expect one argument for " + method_name + "\n"); + return std::make_shared(VALUE_ERROR, + "Expect one argument for " + method_name + "\n"); } std::shared_ptr arg = visit_arg(args[0]); if (arg->get_type() == VALUE_ERROR) { @@ -748,8 +736,8 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "pop" || method_name == "pop_back") { if (!args.empty()) { - return std::make_shared( - VALUE_ERROR, "Expect zero argument for " + method_name + "\n"); + return std::make_shared(VALUE_ERROR, + "Expect zero argument for " + method_name + "\n"); } if (arr_obj->empty()) { std::cout << "Cannot " << method_name << " from an empty array\n"; @@ -760,8 +748,7 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "resize") { if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for resize\n"); + return std::make_shared(VALUE_ERROR, "Expect one argument for resize\n"); } std::shared_ptr new_size_val = visit_arg(args[0]); if (new_size_val->get_type() == VALUE_ERROR) { @@ -788,8 +775,7 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "insert") { if (args.size() != 2) { - return std::make_shared(VALUE_ERROR, - "Expect two arguments for insert\n"); + return std::make_shared(VALUE_ERROR, "Expect two arguments for insert\n"); } std::shared_ptr index = visit_arg(args[0]); if (index->get_type() == VALUE_ERROR) { @@ -804,8 +790,7 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "remove") { if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for remove\n"); + return std::make_shared(VALUE_ERROR, "Expect one argument for remove\n"); } std::shared_ptr index = visit_arg(args[0]); if (index->get_type() == VALUE_ERROR) { @@ -816,20 +801,18 @@ std::optional> Interpreter::try_visit_array_method_call(c if (method_name == "size") { if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for size\n"); + return std::make_shared(VALUE_ERROR, "Expect zero argument for size\n"); } return arr_obj->size(); } if (method_name == "back") { if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero arguments for back\n"); + return std::make_shared(VALUE_ERROR, "Expect zero arguments for back\n"); } if (arr_obj->empty()) { - return std::make_shared( - VALUE_ERROR, "Cannot call back on an empty array\n"); + return std::make_shared(VALUE_ERROR, + "Cannot call back on an empty array\n"); } return arr_obj->back(); } @@ -837,50 +820,50 @@ std::optional> Interpreter::try_visit_array_method_call(c return std::nullopt; } -std::shared_ptr Interpreter::bin_op( - std::shared_ptr a, std::shared_ptr b, std::shared_ptr op -) { - if(op->get_type() == TOKEN_ADD) +std::shared_ptr Interpreter::bin_op(std::shared_ptr a, std::shared_ptr b, + std::shared_ptr op) { + if (op->get_type() == TOKEN_ADD) return a + b; - else if(op->get_type() == TOKEN_SUB) + else if (op->get_type() == TOKEN_SUB) return a - b; - else if(op->get_type() == TOKEN_MUL) + else if (op->get_type() == TOKEN_MUL) return a * b; - else if(op->get_type() == TOKEN_DIV) + else if (op->get_type() == TOKEN_DIV) return a / b; - else if(op->get_type() == TOKEN_MOD) + else if (op->get_type() == TOKEN_MOD) return a % b; - else if(op->get_type() == TOKEN_POW) - return pow(a, b); - else if(op->get_type() == TOKEN_EQUAL) + else if (op->get_type() == TOKEN_POW) + return pow(a, b); // NOLINT(misc-include-cleaner): value.h overload + else if (op->get_type() == TOKEN_EQUAL) return a == b; - else if(op->get_type() == TOKEN_NEQ) + else if (op->get_type() == TOKEN_NEQ) return a != b; - else if(op->get_type() == TOKEN_LESS) + else if (op->get_type() == TOKEN_LESS) return a < b; - else if(op->get_type() == TOKEN_GREATER) + else if (op->get_type() == TOKEN_GREATER) return a > b; - else if(op->get_type() == TOKEN_LEQ) + else if (op->get_type() == TOKEN_LEQ) return a <= b; - else if(op->get_type() == TOKEN_GEQ) + else if (op->get_type() == TOKEN_GEQ) return a >= b; - else if(op->get_type() == TOKEN_KEYWORD && op->get_value() == "and") + else if (op->get_type() == TOKEN_KEYWORD && op->get_value() == "and") return a && b; - else if(op->get_type() == TOKEN_KEYWORD && op->get_value() == "or") + else if (op->get_type() == TOKEN_KEYWORD && op->get_value() == "or") return a || b; - + return std::make_shared(VALUE_ERROR, "Not a binary op\n"); } std::shared_ptr Interpreter::unary_op(std::shared_ptr a, std::shared_ptr op) { - if(op->get_type() == TOKEN_ADD) + if (op->get_type() == TOKEN_ADD) return a; - else if(op->get_type() == TOKEN_SUB) + else if (op->get_type() == TOKEN_SUB) return -a; - else if(op->get_type() == TOKEN_KEYWORD && op->get_value() == "not") + else if (op->get_type() == TOKEN_KEYWORD && op->get_value() == "not") return !a; return std::make_shared(VALUE_ERROR, "Not an unary op\n"); -}std::shared_ptr Interpreter::visit_struct_def(std::shared_ptr node) { +} +std::shared_ptr Interpreter::visit_struct_def(std::shared_ptr node) { auto struct_node = dynamic_cast(node.get()); std::string name = struct_node->get_name(); std::vector members; @@ -890,7 +873,8 @@ std::shared_ptr Interpreter::unary_op(std::shared_ptr a, std::shar std::map> methods; for (const auto& method_node : struct_node->get_child()) { - std::string method_name = method_node->get_name(); // This is the simple name "constructor", "add", etc. + std::string method_name = + method_node->get_name(); // This is the simple name "constructor", "add", etc. // We need to visit it to create an AlgoValue // But visit_algo_def puts it in symbol table. We don't want that for struct methods, // we want them inside the struct. diff --git a/src/node.h b/src/node.h index 11888d0..c717192 100644 --- a/src/node.h +++ b/src/node.h @@ -7,10 +7,11 @@ #include #include +#include #include #include #include -#include + #include "token.h" const std::string NODE_VALUE{"VALUE"}; @@ -33,294 +34,314 @@ const std::string NODE_STRUCTDEF("STRUCTDEF"); const std::string NODE_RETURN("RETURN"); const std::string NODE_BREAK("BREAK"); const std::string NODE_CONTINUE("CONTINUE"); +const std::string NODE_PRECOMPUTED("PRECOMPUTED"); const std::string TAB{" "}; class Node { -public: + public: Node() : node_id(next_node_id.fetch_add(1, std::memory_order_relaxed)) {} virtual std::string get_node() = 0; - virtual ~Node() {}; - virtual std::vector> get_child() { return std::vector>(0);} - virtual std::string get_type() {return "NONE";} - virtual std::shared_ptr get_tok() { return nullptr;} - virtual TokenList get_toks() { return TokenList(0);} - virtual std::string get_name() { return "";} - std::size_t get_id() const { return node_id;} -private: + virtual ~Node() {} + virtual std::vector> get_child() { + return std::vector>(0); + } + virtual std::string get_type() { return "NONE"; } + virtual std::shared_ptr get_tok() { return nullptr; } + virtual TokenList get_toks() { return TokenList(0); } + virtual std::string get_name() { return ""; } + std::size_t get_id() const { return node_id; } + + private: inline static std::atomic_size_t next_node_id{1}; std::size_t node_id; }; using NodeList = std::vector>; -class ErrorNode: public Node { -public: - ErrorNode(std::shared_ptr _tok) - : tok(_tok) {} +class ErrorNode : public Node { + public: + ErrorNode(std::shared_ptr _tok) : tok(_tok) {} std::string get_node() override; - std::shared_ptr get_tok() override { return tok;} - std::string get_type() override {return NODE_ERROR;} -protected: + std::shared_ptr get_tok() override { return tok; } + std::string get_type() override { return NODE_ERROR; } + + protected: std::shared_ptr tok; }; -class ValueNode: public Node { -public: - ValueNode(std::shared_ptr _tok) - : tok(_tok) {} +class ValueNode : public Node { + public: + ValueNode(std::shared_ptr _tok) : tok(_tok) {} std::string get_node() override; - std::string get_type() override {return NODE_VALUE;} - std::shared_ptr get_tok() override { return tok;} -protected: + std::string get_type() override { return NODE_VALUE; } + std::shared_ptr get_tok() override { return tok; } + + protected: std::shared_ptr tok; }; -class BinOpNode: public Node { -public: +class BinOpNode : public Node { + public: BinOpNode(std::shared_ptr left, std::shared_ptr right, std::shared_ptr tok) : left_node(left), right_node(right), op_tok(tok) {} std::string get_node() override; NodeList get_child() override; - std::string get_type() override {return NODE_BINOP;} - std::shared_ptr get_tok() override { return op_tok;} -protected: + std::string get_type() override { return NODE_BINOP; } + std::shared_ptr get_tok() override { return op_tok; } + + protected: std::shared_ptr left_node, right_node; std::shared_ptr op_tok; }; -class UnaryOpNode: public Node { -public: +class UnaryOpNode : public Node { + public: UnaryOpNode(std::shared_ptr _node, std::shared_ptr tok) : node(_node), op_tok(tok) {} std::string get_node() override; NodeList get_child() override; - std::string get_type() override { return NODE_UNARYOP;} - std::shared_ptr get_tok() override { return op_tok;} -protected: + std::string get_type() override { return NODE_UNARYOP; } + std::shared_ptr get_tok() override { return op_tok; } + + protected: std::shared_ptr node; std::shared_ptr op_tok; }; -class VarAssignNode: public Node { -public: - VarAssignNode(std::string _name, std::shared_ptr _node) - : name(_name), node(_node) {} +class VarAssignNode : public Node { + public: + VarAssignNode(std::string _name, std::shared_ptr _node) : name(_name), node(_node) {} std::string get_node() override; - NodeList get_child() override { return NodeList{node};} - std::string get_type() override { return NODE_VARASSIGN;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return name;} -protected: + NodeList get_child() override { return NodeList{node}; } + std::string get_type() override { return NODE_VARASSIGN; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return name; } + + protected: std::string name; std::shared_ptr node; }; -class VarAccessNode: public Node { -public: - VarAccessNode(std::shared_ptr _tok) - : tok(_tok) {} +class VarAccessNode : public Node { + public: + VarAccessNode(std::shared_ptr _tok) : tok(_tok) {} std::string get_node() override; - NodeList get_child() override { return NodeList(0);} - std::string get_type() override{ return NODE_VARACCESS;} - std::shared_ptr get_tok() override { return tok;} - std::string get_name() override {return tok->get_value();} -protected: + NodeList get_child() override { return NodeList(0); } + std::string get_type() override { return NODE_VARACCESS; } + std::shared_ptr get_tok() override { return tok; } + std::string get_name() override { return tok->get_value(); } + + protected: std::shared_ptr tok; }; -class IfNode: public Node { -public: +class IfNode : public Node { + public: IfNode(std::shared_ptr condition, NodeList expr, NodeList _else_node) : condition_node(condition), expr_node(expr), else_node(_else_node) {} std::string get_node() override; NodeList get_child() override { throw std::runtime_error("should not call get_child on if node"); } - const std::shared_ptr& get_condition() { return condition_node;} - const NodeList& get_expr() { return expr_node;} - const NodeList& get_else() { return else_node;} - std::string get_type() override{ return NODE_IF;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: - std::shared_ptr condition_node; + const std::shared_ptr& get_condition() { return condition_node; } + const NodeList& get_expr() { return expr_node; } + const NodeList& get_else() { return else_node; } + std::string get_type() override { return NODE_IF; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: + std::shared_ptr condition_node; NodeList expr_node, else_node; }; -class ForNode: public Node { -public: - ForNode( - std::shared_ptr _var_assign, std::shared_ptr _end_value, - std::shared_ptr _step_value, NodeList _body_node - ) : var_assign(_var_assign), end_value(_end_value), step_value(_step_value), body_node(_body_node) {} +class ForNode : public Node { + public: + ForNode(std::shared_ptr _var_assign, std::shared_ptr _end_value, + std::shared_ptr _step_value, NodeList _body_node) + : var_assign(_var_assign), + end_value(_end_value), + step_value(_step_value), + body_node(_body_node) {} std::string get_node() override; NodeList get_child() override { NodeList child{var_assign, end_value, step_value}; - for(auto node : body_node) child.push_back(node); + for (auto node : body_node) child.push_back(node); return child; } - std::string get_type() override { return NODE_FOR;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: + std::string get_type() override { return NODE_FOR; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: std::shared_ptr var_assign, end_value, step_value; NodeList body_node; }; -class WhileNode: public Node { -public: +class WhileNode : public Node { + public: WhileNode(std::shared_ptr _condition, NodeList _body_node) : condition(_condition), body_node(_body_node) {} std::string get_node() override; NodeList get_child() override { NodeList child{condition}; - for(auto node : body_node) child.push_back(node); + for (auto node : body_node) child.push_back(node); return child; } - std::string get_type() override { return NODE_WHILE;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: + std::string get_type() override { return NODE_WHILE; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: std::shared_ptr condition; NodeList body_node; }; -class RepeatNode: public Node { -public: +class RepeatNode : public Node { + public: RepeatNode(NodeList _body_node, std::shared_ptr _condition) : condition(_condition), body_node(_body_node) {} std::string get_node() override; NodeList get_child() override { NodeList child{condition}; - for(auto node : body_node) child.push_back(node); + for (auto node : body_node) child.push_back(node); return child; } - std::string get_type() override { return NODE_REPEAT;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: + std::string get_type() override { return NODE_REPEAT; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: std::shared_ptr condition; NodeList body_node; }; -class AlgorithmDefNode: public Node { -public: - AlgorithmDefNode(std::shared_ptr _algo_name, const TokenList &_args_name, NodeList _body_node = {}) +class AlgorithmDefNode : public Node { + public: + AlgorithmDefNode(std::shared_ptr _algo_name, const TokenList& _args_name, + NodeList _body_node = {}) : algo_name(_algo_name), args_name(_args_name), body_node(_body_node) {} std::string get_node() override; - NodeList get_child() override { return body_node;} - const NodeList& get_body() const { return body_node;} - std::string get_type() override { return NODE_ALGODEF;} - std::shared_ptr get_tok() override { return algo_name;} - TokenList get_toks() override { return args_name;} - std::string get_name() override { return algo_name->get_value();} -protected: + NodeList get_child() override { return body_node; } + const NodeList& get_body() const { return body_node; } + std::string get_type() override { return NODE_ALGODEF; } + std::shared_ptr get_tok() override { return algo_name; } + TokenList get_toks() override { return args_name; } + std::string get_name() override { return algo_name->get_value(); } + + protected: std::shared_ptr algo_name; TokenList args_name; NodeList body_node; }; -class AlgorithmCallNode: public Node { -public: - AlgorithmCallNode(std::shared_ptr _call_node, const NodeList &_args) +class AlgorithmCallNode : public Node { + public: + AlgorithmCallNode(std::shared_ptr _call_node, const NodeList& _args) : call_node(_call_node), args(_args) {} std::string get_node() override; - NodeList get_child() override { return args;} - const NodeList& get_args() const { return args;} - std::string get_type() override { return NODE_ALGOCALL;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return call_node->get_name();} - std::shared_ptr get_call() { return call_node;} -protected: + NodeList get_child() override { return args; } + const NodeList& get_args() const { return args; } + std::string get_type() override { return NODE_ALGOCALL; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return call_node->get_name(); } + std::shared_ptr get_call() { return call_node; } + + protected: std::shared_ptr call_node; NodeList args; }; -class ArrayNode: public Node { -public: - ArrayNode(const NodeList &_elements_node) - : elements_node(_elements_node) {} +class ArrayNode : public Node { + public: + ArrayNode(const NodeList& _elements_node) : elements_node(_elements_node) {} std::string get_node() override; - NodeList get_child() override { return elements_node;} - std::string get_type() override { return NODE_ARRAY;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: + NodeList get_child() override { return elements_node; } + std::string get_type() override { return NODE_ARRAY; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: NodeList elements_node; }; -class ArrayAccessNode: public Node { -public: +class ArrayAccessNode : public Node { + public: ArrayAccessNode(std::shared_ptr _arr, std::shared_ptr _index) : arr(_arr), index(_index) {} std::string get_node() override; - NodeList get_child() override { return NodeList{arr, index};} - std::string get_type() override { return NODE_ARRACCESS;} - std::shared_ptr get_tok() override { return nullptr;} -protected: + NodeList get_child() override { return NodeList{arr, index}; } + std::string get_type() override { return NODE_ARRACCESS; } + std::shared_ptr get_tok() override { return nullptr; } + + protected: std::shared_ptr arr, index; }; -class ArrayAssignNode: public Node { -public: +class ArrayAssignNode : public Node { + public: ArrayAssignNode(std::shared_ptr _arr, std::shared_ptr _value) : arr(_arr), value(_value) {} std::string get_node() override; - NodeList get_child() override { return NodeList{arr, value};} - std::string get_type() override { return NODE_ARRASSIGN;} - std::shared_ptr get_tok() override { return nullptr;} -protected: + NodeList get_child() override { return NodeList{arr, value}; } + std::string get_type() override { return NODE_ARRASSIGN; } + std::shared_ptr get_tok() override { return nullptr; } + + protected: std::shared_ptr arr, value; }; -class MemberAccessNode: public Node { -public: +class MemberAccessNode : public Node { + public: MemberAccessNode(std::shared_ptr _obj, std::shared_ptr _member) : obj(_obj), member(_member) {} std::string get_node() override; - NodeList get_child() override { return NodeList{obj, member};} - std::string get_type() override { return NODE_MEMACCESS;} - std::shared_ptr get_tok() override { return nullptr;} -protected: + NodeList get_child() override { return NodeList{obj, member}; } + std::string get_type() override { return NODE_MEMACCESS; } + std::shared_ptr get_tok() override { return nullptr; } + + protected: std::shared_ptr obj, member; }; -class StructDefNode: public Node { -public: - StructDefNode(std::shared_ptr _struct_name, const TokenList &_members, const NodeList &_methods) +class StructDefNode : public Node { + public: + StructDefNode(std::shared_ptr _struct_name, const TokenList& _members, + const NodeList& _methods) : struct_name(_struct_name), members(_members), methods(_methods) {} std::string get_node() override; - NodeList get_child() override { return methods;} - std::string get_type() override { return NODE_STRUCTDEF;} - std::shared_ptr get_tok() override { return struct_name;} - TokenList get_toks() override { return members;} - std::string get_name() override { return struct_name->get_value();} -protected: + NodeList get_child() override { return methods; } + std::string get_type() override { return NODE_STRUCTDEF; } + std::shared_ptr get_tok() override { return struct_name; } + TokenList get_toks() override { return members; } + std::string get_name() override { return struct_name->get_value(); } + + protected: std::shared_ptr struct_name; TokenList members; NodeList methods; }; -class ReturnNode: public Node { -public: - ReturnNode(std::shared_ptr _node) - : node(_node) {} +class ReturnNode : public Node { + public: + ReturnNode(std::shared_ptr _node) : node(_node) {} std::string get_node() override { return "RETURN " + node->get_node(); } - NodeList get_child() override { return NodeList{node};} - std::string get_type() override { return NODE_RETURN;} - std::shared_ptr get_tok() override { return nullptr;} - std::string get_name() override { return "";} -protected: + NodeList get_child() override { return NodeList{node}; } + std::string get_type() override { return NODE_RETURN; } + std::shared_ptr get_tok() override { return nullptr; } + std::string get_name() override { return ""; } + + protected: std::shared_ptr node; }; -class ControlNode: public Node { -public: - ControlNode(const std::string& _type) - : control_type(_type) {} +class ControlNode : public Node { + public: + ControlNode(const std::string& _type) : control_type(_type) {} std::string get_node() override { return control_type; } std::string get_type() override { return control_type; } -protected: + + protected: std::string control_type; }; diff --git a/src/pseudo.cpp b/src/pseudo.cpp index dbf7ab0..80ed2f9 100644 --- a/src/pseudo.cpp +++ b/src/pseudo.cpp @@ -1,30 +1,45 @@ #include "pseudo.h" -#include "color.h" -#include "interpreter.h" -#include "node.h" -#include "value.h" -#include "error.h" + #include #include +#include +#include +#include #include #include #include +#include #include #include #include +#include #include +#include #include #include #include +#include #include +#include "analysis.h" +#include "color.h" +#include "error.h" +#include "imports.h" +#include "interpreter.h" +#include "jit.h" +#include "lexer.h" +#include "node.h" +#include "parser.h" +#include "token.h" +#include "value.h" + /// -------------------- /// Value /// -------------------- -std::ostream &operator<<(std::ostream &out, Value &number) { - out << number.get_num(); - return out; +std::ostream& operator<<(std::ostream& out, Value& number) { + out << number.get_num(); + return out; } int64_t Value::as_int() { return std::stoll(get_num()); } @@ -33,250 +48,249 @@ double Value::as_double() { return std::stod(get_num()); } std::string Value::as_string() { return get_num(); } -template std::string TypedValue::get_num() { - std::stringstream ss; - std::string ret; - if (type == VALUE_FLOAT || type == VALUE_INT) { - ss << value; - std::getline(ss, ret); - } else if (type == VALUE_STRING || type == VALUE_ERROR) { - ret = value; - } - return ret; +template +std::string TypedValue::get_num() { + std::stringstream ss; + std::string ret; + if (type == VALUE_FLOAT || type == VALUE_INT) { + ss << value; + std::getline(ss, ret); + } else if (type == VALUE_STRING || type == VALUE_ERROR) { + ret = value; + } + return ret; } -template int64_t TypedValue::as_int() { - if constexpr (std::is_same_v) { - return value; - } else if constexpr (std::is_same_v) { - return static_cast(value); - } else { - return std::stoll(value); - } +template +int64_t TypedValue::as_int() { + if constexpr (std::is_same_v) { + return value; + } else if constexpr (std::is_same_v) { + return static_cast(value); + } else { + return std::stoll(value); + } } -template double TypedValue::as_double() { - if constexpr (std::is_same_v) { - return value; - } else if constexpr (std::is_same_v) { - return static_cast(value); - } else { - return std::stod(value); - } +template +double TypedValue::as_double() { + if constexpr (std::is_same_v) { + return value; + } else if constexpr (std::is_same_v) { + return static_cast(value); + } else { + return std::stod(value); + } } -template std::string TypedValue::as_string() { - if constexpr (std::is_same_v) { - return value; - } else { - return get_num(); - } +template +std::string TypedValue::as_string() { + if constexpr (std::is_same_v) { + return value; + } else { + return get_num(); + } } -template bool TypedValue::append_string(const std::string& suffix) { - if constexpr (std::is_same_v) { - value += suffix; - return true; - } - return false; -} - -template std::string TypedValue::repr() { - std::stringstream ss; - std::string ret; - if (type == VALUE_FLOAT || type == VALUE_INT) { - ret = get_num(); - } else if (type == VALUE_STRING) { - ss << value; - - char ch{char(ss.get())}; - ret += '\"'; - while (!ss.eof()) { - if (REVERSE_ESCAPE_CHAR.count(ch)) { - ret += '\\'; - ret += REVERSE_ESCAPE_CHAR.at(ch); - } else { - ret += ch; - } - ch = ss.get(); - } - ret += '\"'; - } - return ret; +template +bool TypedValue::append_string(const std::string& suffix) { + if constexpr (std::is_same_v) { + value += suffix; + return true; + } + return false; } -std::string ArrayValue::get_num() { - std::stringstream ss; - ss << "{"; - if (!value.empty()) - ss << value[0]->repr(); - for (int i{1}; i < value.size(); ++i) { - ss << ", " << value[i]->repr(); - } - ss << "}"; - std::string ret; - std::getline(ss, ret); - return ret; +template +std::string TypedValue::repr() { + std::stringstream ss; + std::string ret; + if (type == VALUE_FLOAT || type == VALUE_INT) { + ret = get_num(); + } else if (type == VALUE_STRING) { + ss << value; + + char ch{char(ss.get())}; + ret += '\"'; + while (!ss.eof()) { + if (REVERSE_ESCAPE_CHAR.count(ch)) { + ret += '\\'; + ret += REVERSE_ESCAPE_CHAR.at(ch); + } else { + ret += ch; + } + ch = ss.get(); + } + ret += '\"'; + } + return ret; } -void ArrayValue::push_back(std::shared_ptr new_value) { - value.push_back(new_value); +std::string ArrayValue::get_num() { + std::stringstream ss; + ss << "{"; + if (!value.empty()) ss << value[0]->repr(); + for (int i{1}; i < value.size(); ++i) { + ss << ", " << value[i]->repr(); + } + ss << "}"; + std::string ret; + std::getline(ss, ret); + return ret; } +void ArrayValue::push_back(std::shared_ptr new_value) { value.push_back(new_value); } + std::shared_ptr ArrayValue::insert(int p, std::shared_ptr new_value) { - if (p < 1 || p > static_cast(value.size()) + 1) { - return std::make_shared( - VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + - ", position: " + std::to_string(p)); - } - value.insert(value.begin() + (p - 1), new_value); - return new_value; + if (p < 1 || p > static_cast(value.size()) + 1) { + return std::make_shared( + VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + + ", position: " + std::to_string(p)); + } + value.insert(value.begin() + (p - 1), new_value); + return new_value; } std::shared_ptr ArrayValue::remove(int p) { - if (p < 1 || p > static_cast(value.size())) { - return std::make_shared( - VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + - ", position: " + std::to_string(p)); - } - std::shared_ptr ret = value[p - 1]; - value.erase(value.begin() + (p - 1)); - return ret; + if (p < 1 || p > static_cast(value.size())) { + return std::make_shared( + VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + + ", position: " + std::to_string(p)); + } + std::shared_ptr ret = value[p - 1]; + value.erase(value.begin() + (p - 1)); + return ret; } std::shared_ptr ArrayValue::pop_back() { - if (value.empty()) - return std::make_shared(VALUE_ERROR, "Pop an empty array"); - std::shared_ptr ret = value.back(); - value.pop_back(); - return ret; + if (value.empty()) return std::make_shared(VALUE_ERROR, "Pop an empty array"); + std::shared_ptr ret = value.back(); + value.pop_back(); + return ret; } -std::shared_ptr &ArrayValue::operator[](int p) { - if (1 <= p && p <= value.size()) - return value[p - 1]; - error = std::make_shared( - VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + - ", position: " + std::to_string(p)); - return error; +std::shared_ptr& ArrayValue::operator[](int p) { + if (1 <= p && p <= value.size()) return value[p - 1]; + error = std::make_shared( + VALUE_ERROR, "Index out of range, size: " + std::to_string(value.size()) + + ", position: " + std::to_string(p)); + return error; } std::string HashTableValue::key_id(std::shared_ptr key) const { - if (key->get_type() == VALUE_ARRAY || key->get_type() == VALUE_INSTANCE || - key->get_type() == VALUE_STRUCT || key->get_type() == VALUE_HASH_TABLE || - key->get_type() == VALUE_ALGO) { - return key->get_type() + ":ptr:" + - std::to_string(reinterpret_cast(key.get())); - } - return key->get_type() + ":" + key->repr(); + if (key->get_type() == VALUE_ARRAY || key->get_type() == VALUE_INSTANCE || + key->get_type() == VALUE_STRUCT || key->get_type() == VALUE_HASH_TABLE || + key->get_type() == VALUE_ALGO) { + return key->get_type() + + ":ptr:" + std::to_string(reinterpret_cast(key.get())); + } + return key->get_type() + ":" + key->repr(); } std::string HashTableValue::get_num() { - std::stringstream ss; - ss << "{"; - if (!entries.empty()) { - ss << entries[0].key->repr() << ": " << entries[0].value->repr(); - } - for (size_t i = 1; i < entries.size(); ++i) { - ss << ", " << entries[i].key->repr() << ": " << entries[i].value->repr(); - } - ss << "}"; - return ss.str(); + std::stringstream ss; + ss << "{"; + if (!entries.empty()) { + ss << entries[0].key->repr() << ": " << entries[0].value->repr(); + } + for (size_t i = 1; i < entries.size(); ++i) { + ss << ", " << entries[i].key->repr() << ": " << entries[i].value->repr(); + } + ss << "}"; + return ss.str(); } std::shared_ptr HashTableValue::get(std::shared_ptr key) { - auto found = index.find(key_id(key)); - if (found == index.end()) { - return std::make_shared(); - } - return entries[found->second].value; + auto found = index.find(key_id(key)); + if (found == index.end()) { + return std::make_shared(); + } + return entries[found->second].value; } std::shared_ptr HashTableValue::set(std::shared_ptr key, std::shared_ptr value) { - std::string id = key_id(key); - auto found = index.find(id); - if (found != index.end()) { - entries[found->second].value = value; + std::string id = key_id(key); + auto found = index.find(id); + if (found != index.end()) { + entries[found->second].value = value; + return value; + } + index[id] = entries.size(); + entries.push_back({key, value}); return value; - } - index[id] = entries.size(); - entries.push_back({key, value}); - return value; } std::shared_ptr HashTableValue::remove(std::shared_ptr key) { - std::string id = key_id(key); - auto found = index.find(id); - if (found == index.end()) { - return std::make_shared(); - } + std::string id = key_id(key); + auto found = index.find(id); + if (found == index.end()) { + return std::make_shared(); + } - size_t removed = found->second; - std::shared_ptr value = entries[removed].value; - index.erase(found); - if (removed != entries.size() - 1) { - entries[removed] = entries.back(); - index[key_id(entries[removed].key)] = removed; - } - entries.pop_back(); - return value; + size_t removed = found->second; + std::shared_ptr value = entries[removed].value; + index.erase(found); + if (removed != entries.size() - 1) { + entries[removed] = entries.back(); + index[key_id(entries[removed].key)] = removed; + } + entries.pop_back(); + return value; } bool HashTableValue::contains(std::shared_ptr key) const { - return index.count(key_id(key)) != 0; + return index.count(key_id(key)) != 0; } std::shared_ptr HashTableValue::size() const { - return std::make_shared>(VALUE_INT, entries.size()); + return std::make_shared>(VALUE_INT, entries.size()); } std::shared_ptr HashTableValue::keys() const { - ValueList keys; - keys.reserve(entries.size()); - for (const Entry& entry : entries) { - keys.push_back(entry.key); - } - return std::make_shared(keys); + ValueList keys; + keys.reserve(entries.size()); + for (const Entry& entry : entries) { + keys.push_back(entry.key); + } + return std::make_shared(keys); } std::shared_ptr HashTableValue::values() const { - ValueList values; - values.reserve(entries.size()); - for (const Entry& entry : entries) { - values.push_back(entry.value); - } - return std::make_shared(values); + ValueList values; + values.reserve(entries.size()); + for (const Entry& entry : entries) { + values.push_back(entry.value); + } + return std::make_shared(values); } void HashTableValue::clear() { - entries.clear(); - index.clear(); + entries.clear(); + index.clear(); } -std::shared_ptr BaseAlgoValue::set_args(const NodeList &args, SymbolTable &sym, - Interpreter &interpreter) { - if (args.size() < arg_names.size()) { - return std::make_shared( - VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too few arguments" RESET); - } else if (args.size() > arg_names.size()) { - return std::make_shared(VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + - "Too many arguments" RESET); - } - - for (int i = 0; i < args.size(); ++i) { - std::shared_ptr v = interpreter.visit(args[i]); - if (v->get_type() == VALUE_ERROR) - return v; - sym.set(arg_names[i], v); - } - static std::shared_ptr none = std::make_shared(); - return none; +std::shared_ptr BaseAlgoValue::set_args(const NodeList& args, SymbolTable& sym, + Interpreter& interpreter) { + if (args.size() < arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too few arguments" RESET); + } else if (args.size() > arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too many arguments" RESET); + } + + for (int i = 0; i < args.size(); ++i) { + std::shared_ptr v = interpreter.visit(args[i]); + if (v->get_type() == VALUE_ERROR) return v; + sym.set(arg_names[i], v); + } + static std::shared_ptr none = std::make_shared(); + return none; } class ScopeCleaner { -public: + public: ScopeCleaner(SymbolTable& _sym) : sym(_sym) {} ~ScopeCleaner() { if (!sym.has_instances()) { @@ -289,915 +303,809 @@ class ScopeCleaner { std::shared_ptr self_ptr = val; std::shared_ptr dtor = inst->get_member("destructor", self_ptr); if (dtor->get_type() == VALUE_ALGO) { - dtor->execute({}, sym.get_parent()); + dtor->execute({}, sym.get_parent()); } } } } } -private: + + private: SymbolTable& sym; }; namespace { -bool is_pure_numeric_node(const std::shared_ptr& node, - const std::string& algo_name, +bool is_pure_numeric_node(const std::shared_ptr& node, const std::string& algo_name, const std::unordered_set& arg_names) { - if (!node) - return false; + if (!node) return false; - const std::string type = node->get_type(); - if (type == NODE_VALUE) { - const std::string token_type = node->get_tok()->get_type(); - return token_type == TOKEN_INT || token_type == TOKEN_FLOAT; - } - if (type == NODE_VARACCESS) { - return arg_names.count(node->get_name()) != 0; - } - if (type == NODE_BINOP) { - NodeList child = node->get_child(); - return child.size() == 2 && - is_pure_numeric_node(child[0], algo_name, arg_names) && - is_pure_numeric_node(child[1], algo_name, arg_names); - } - if (type == NODE_UNARYOP) { - NodeList child = node->get_child(); - return child.size() == 1 && - is_pure_numeric_node(child[0], algo_name, arg_names); - } - if (type == NODE_RETURN) { - NodeList child = node->get_child(); - return child.size() == 1 && - is_pure_numeric_node(child[0], algo_name, arg_names); - } - if (type == NODE_IF) { - IfNode* if_node = dynamic_cast(node.get()); - if (!is_pure_numeric_node(if_node->get_condition(), algo_name, arg_names)) - return false; - for (const auto& expr : if_node->get_expr()) { - if (!is_pure_numeric_node(expr, algo_name, arg_names)) - return false; + std::string type = node->get_type(); + if (type == NODE_VALUE) { + std::string token_type = node->get_tok()->get_type(); + return token_type == TOKEN_INT || token_type == TOKEN_FLOAT; } - for (const auto& expr : if_node->get_else()) { - if (!is_pure_numeric_node(expr, algo_name, arg_names)) - return false; + if (type == NODE_VARACCESS) { + return arg_names.count(node->get_name()) != 0; } - return true; - } - if (type == NODE_ALGOCALL) { - AlgorithmCallNode* call_node = dynamic_cast(node.get()); - if (call_node->get_call()->get_type() != NODE_VARACCESS || - call_node->get_name() != algo_name) { - return false; - } - for (const auto& arg : call_node->get_args()) { - if (!is_pure_numeric_node(arg, algo_name, arg_names)) - return false; + if (type == NODE_BINOP) { + NodeList child = node->get_child(); + return child.size() == 2 && is_pure_numeric_node(child[0], algo_name, arg_names) && + is_pure_numeric_node(child[1], algo_name, arg_names); + } + if (type == NODE_UNARYOP) { + NodeList child = node->get_child(); + return child.size() == 1 && is_pure_numeric_node(child[0], algo_name, arg_names); + } + if (type == NODE_RETURN) { + NodeList child = node->get_child(); + return child.size() == 1 && is_pure_numeric_node(child[0], algo_name, arg_names); + } + if (type == NODE_IF) { + IfNode* if_node = dynamic_cast(node.get()); + if (!is_pure_numeric_node(if_node->get_condition(), algo_name, arg_names)) return false; + for (const auto& expr : if_node->get_expr()) { + if (!is_pure_numeric_node(expr, algo_name, arg_names)) return false; + } + for (const auto& expr : if_node->get_else()) { + if (!is_pure_numeric_node(expr, algo_name, arg_names)) return false; + } + return true; + } + if (type == NODE_ALGOCALL) { + AlgorithmCallNode* call_node = dynamic_cast(node.get()); + if (call_node->get_call()->get_type() != NODE_VARACCESS || + call_node->get_name() != algo_name) { + return false; + } + for (const auto& arg : call_node->get_args()) { + if (!is_pure_numeric_node(arg, algo_name, arg_names)) return false; + } + return true; } - return true; - } - return false; + return false; } bool has_self_call(const std::shared_ptr& node, const std::string& algo_name) { - if (!node) - return false; + if (!node) return false; - if (node->get_type() == NODE_ALGOCALL) { - AlgorithmCallNode* call_node = dynamic_cast(node.get()); - if (call_node->get_call()->get_type() == NODE_VARACCESS && - call_node->get_name() == algo_name) { - return true; - } - for (const auto& arg : call_node->get_args()) { - if (has_self_call(arg, algo_name)) - return true; + if (node->get_type() == NODE_ALGOCALL) { + AlgorithmCallNode* call_node = dynamic_cast(node.get()); + if (call_node->get_call()->get_type() == NODE_VARACCESS && + call_node->get_name() == algo_name) { + return true; + } + for (const auto& arg : call_node->get_args()) { + if (has_self_call(arg, algo_name)) return true; + } + return false; } - return false; - } - - if (node->get_type() == NODE_IF) { - IfNode* if_node = dynamic_cast(node.get()); - if (has_self_call(if_node->get_condition(), algo_name)) - return true; - for (const auto& expr : if_node->get_expr()) { - if (has_self_call(expr, algo_name)) - return true; + + if (node->get_type() == NODE_IF) { + IfNode* if_node = dynamic_cast(node.get()); + if (has_self_call(if_node->get_condition(), algo_name)) return true; + for (const auto& expr : if_node->get_expr()) { + if (has_self_call(expr, algo_name)) return true; + } + for (const auto& expr : if_node->get_else()) { + if (has_self_call(expr, algo_name)) return true; + } + return false; } - for (const auto& expr : if_node->get_else()) { - if (has_self_call(expr, algo_name)) - return true; + + for (const auto& child : node->get_child()) { + if (has_self_call(child, algo_name)) return true; } return false; - } - - for (const auto& child : node->get_child()) { - if (has_self_call(child, algo_name)) - return true; - } - return false; } -bool is_memoizable_numeric_algo(const std::shared_ptr& node, - const std::string& algo_name, - const std::vector& args) { - AlgorithmDefNode* algo_node = dynamic_cast(node.get()); - if (!algo_node || args.empty()) - return false; +} // namespace - bool recursive = false; - std::unordered_set arg_set(args.begin(), args.end()); - for (const auto& expr : algo_node->get_body()) { - if (!is_pure_numeric_node(expr, algo_name, arg_set)) - return false; - recursive = recursive || has_self_call(expr, algo_name); - } - return recursive; +bool is_memoizable_numeric_algo(const std::shared_ptr& node, const std::string& algo_name, + const std::vector& args) { + AlgorithmDefNode* algo_node = dynamic_cast(node.get()); + if (!algo_node || args.empty()) return false; + + bool recursive = false; + std::unordered_set arg_set(args.begin(), args.end()); + for (const auto& expr : algo_node->get_body()) { + if (!is_pure_numeric_node(expr, algo_name, arg_set)) return false; + recursive = recursive || has_self_call(expr, algo_name); + } + return recursive; } +namespace { + std::shared_ptr single_return_numeric_expr(const std::shared_ptr& node, const std::string& algo_name, const std::vector& args) { - AlgorithmDefNode* algo_node = dynamic_cast(node.get()); - if (!algo_node || algo_node->get_body().size() != 1) - return nullptr; + AlgorithmDefNode* algo_node = dynamic_cast(node.get()); + if (!algo_node || algo_node->get_body().size() != 1) return nullptr; - std::shared_ptr ret = algo_node->get_body()[0]; - if (ret->get_type() != NODE_RETURN || has_self_call(ret, algo_name)) - return nullptr; + std::shared_ptr ret = algo_node->get_body()[0]; + if (ret->get_type() != NODE_RETURN || has_self_call(ret, algo_name)) return nullptr; - NodeList child = ret->get_child(); - if (child.size() != 1) - return nullptr; + NodeList child = ret->get_child(); + if (child.size() != 1) return nullptr; - std::unordered_set arg_set(args.begin(), args.end()); - if (!is_pure_numeric_node(child[0], algo_name, arg_set)) - return nullptr; - return child[0]; + std::unordered_set arg_set(args.begin(), args.end()); + if (!is_pure_numeric_node(child[0], algo_name, arg_set)) return nullptr; + return child[0]; } std::string numeric_cache_key(const ValueList& values) { - std::string key; - for (const auto& value : values) { - if (value->get_type() != VALUE_INT && value->get_type() != VALUE_FLOAT) - return ""; - key += value->get_type(); - key += ':'; - key += value->get_num(); - key += '|'; - } - return key; -} - -} // namespace - -std::shared_ptr AlgoValue::execute(const NodeList& args, SymbolTable *parent) { - static std::unordered_map memoizable_by_node; - static std::unordered_map>> memoized_results; - static std::unordered_map single_return_jit; - static std::unordered_set single_return_jit_disabled; - - SymbolTable sym(parent); - ScopeCleaner cleaner(sym); - Interpreter interpreter(sym); - const std::size_t node_id = value->get_id(); - auto memoizable_found = memoizable_by_node.find(node_id); - if (memoizable_found == memoizable_by_node.end()) { - memoizable_found = memoizable_by_node.emplace( - node_id, is_memoizable_numeric_algo(value, algo_name, arg_names)).first; - } - - if (memoizable_found->second) { - if (args.size() < arg_names.size()) { - return std::make_shared( - VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too few arguments" RESET); - } else if (args.size() > arg_names.size()) { - return std::make_shared(VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + - "Too many arguments" RESET); + std::string key; + for (const auto& value : values) { + if (value->get_type() != VALUE_INT && value->get_type() != VALUE_FLOAT) return ""; + key += value->get_type(); + key += ':'; + key += value->get_num(); + key += '|'; } + return key; +} - ValueList evaluated_args; - evaluated_args.reserve(args.size()); - for (int i = 0; i < args.size(); ++i) { - std::shared_ptr arg = interpreter.visit(args[i]); - if (arg->get_type() == VALUE_ERROR) - return arg; - evaluated_args.push_back(arg); - } - - const std::string cache_key = numeric_cache_key(evaluated_args); - if (!cache_key.empty()) { - auto& cache = memoized_results[node_id]; - auto cached = cache.find(cache_key); - if (cached != cache.end()) { - return cached->second; - } - - for (int i = 0; i < evaluated_args.size(); ++i) { - sym.set(arg_names[i], evaluated_args[i]); - } - - AlgorithmDefNode* algo_node = dynamic_cast(value.get()); - const NodeList& algo_body = algo_node->get_body(); - std::shared_ptr ret = std::make_shared(); - for (int i = 0; i < algo_body.size(); ++i) { - ret = interpreter.visit(algo_body[i]); - if (ret->get_type() == VALUE_RETURN) { - ret = dynamic_cast(ret.get())->get_value(); - break; +} // namespace + +std::shared_ptr AlgoValue::execute(const NodeList& args, SymbolTable* parent) { + static std::unordered_map memoizable_by_node; + static std::unordered_map>> + memoized_results; + static std::unordered_map single_return_jit; + static std::unordered_set single_return_jit_disabled; + + SymbolTable sym(parent); + ScopeCleaner cleaner(sym); + Interpreter interpreter(sym); + std::size_t node_id = value->get_id(); + auto memoizable_found = memoizable_by_node.find(node_id); + if (memoizable_found == memoizable_by_node.end()) { + memoizable_found = + memoizable_by_node + .emplace(node_id, is_memoizable_numeric_algo(value, algo_name, arg_names)) + .first; + } + + if (memoizable_found->second) { + if (args.size() < arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too few arguments" RESET); + } else if (args.size() > arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too many arguments" RESET); } - if (ret->get_type() == VALUE_ERROR) { - return ret; + + ValueList evaluated_args; + evaluated_args.reserve(args.size()); + for (int i = 0; i < args.size(); ++i) { + std::shared_ptr arg = interpreter.visit(args[i]); + if (arg->get_type() == VALUE_ERROR) return arg; + evaluated_args.push_back(arg); } - } - if (ret->get_type() == VALUE_INT || ret->get_type() == VALUE_FLOAT) { - cache[cache_key] = ret; - } - return ret; - } - } - std::shared_ptr ret{set_args(args, sym, interpreter)}; - if (ret->get_type() == VALUE_ERROR) - return ret; + std::string cache_key = numeric_cache_key(evaluated_args); + if (!cache_key.empty()) { + auto& cache = memoized_results[node_id]; + auto cached = cache.find(cache_key); + if (cached != cache.end()) { + return cached->second; + } + + for (int i = 0; i < evaluated_args.size(); ++i) { + sym.set(arg_names[i], evaluated_args[i]); + } - if (!single_return_jit_disabled.count(node_id)) { - auto compiled = single_return_jit.find(node_id); - if (compiled == single_return_jit.end()) { - std::shared_ptr return_expr = - single_return_numeric_expr(value, algo_name, arg_names); - if (return_expr) { - std::optional program = ExpressionJit::compile(return_expr); - if (program) { - compiled = single_return_jit.emplace(node_id, std::move(*program)).first; - } else { - single_return_jit_disabled.insert(node_id); + AlgorithmDefNode* algo_node = dynamic_cast(value.get()); + const NodeList& algo_body = algo_node->get_body(); + std::shared_ptr ret = std::make_shared(); + for (int i = 0; i < algo_body.size(); ++i) { + ret = interpreter.visit(algo_body[i]); + if (ret->get_type() == VALUE_RETURN) { + ret = dynamic_cast(ret.get())->get_value(); + break; + } + if (ret->get_type() == VALUE_ERROR) { + return ret; + } + } + if (ret->get_type() == VALUE_INT || ret->get_type() == VALUE_FLOAT) { + cache[cache_key] = ret; + } + return ret; } - } else { - single_return_jit_disabled.insert(node_id); - } } - if (compiled != single_return_jit.end()) { - std::optional> jit_result = compiled->second.execute(sym); - if (jit_result) { - return *jit_result; - } + + std::shared_ptr ret{set_args(args, sym, interpreter)}; + if (ret->get_type() == VALUE_ERROR) return ret; + + if (!single_return_jit_disabled.count(node_id)) { + auto compiled = single_return_jit.find(node_id); + if (compiled == single_return_jit.end()) { + std::shared_ptr return_expr = + single_return_numeric_expr(value, algo_name, arg_names); + if (return_expr) { + std::optional program = ExpressionJit::compile(return_expr); + if (program) { + compiled = single_return_jit.emplace(node_id, std::move(*program)).first; + } else { + single_return_jit_disabled.insert(node_id); + } + } else { + single_return_jit_disabled.insert(node_id); + } + } + if (compiled != single_return_jit.end()) { + std::optional> jit_result = compiled->second.execute(sym); + if (jit_result) { + return *jit_result; + } + } } - } - AlgorithmDefNode* algo_node = dynamic_cast(value.get()); - const NodeList& algo_body = algo_node->get_body(); + AlgorithmDefNode* algo_node = dynamic_cast(value.get()); + const NodeList& algo_body = algo_node->get_body(); - for (int i = 0; i < algo_body.size(); ++i) { - ret = interpreter.visit(algo_body[i]); - if (ret->get_type() == VALUE_RETURN) { - return dynamic_cast(ret.get())->get_value(); + for (int i = 0; i < algo_body.size(); ++i) { + ret = interpreter.visit(algo_body[i]); + if (ret->get_type() == VALUE_RETURN) { + return dynamic_cast(ret.get())->get_value(); + } } - } - return ret; + return ret; } -std::shared_ptr BuiltinAlgoValue::execute(const NodeList& args, - SymbolTable *parent) { - SymbolTable sym(parent); - ScopeCleaner cleaner(sym); - Interpreter interpreter(sym); - if (algo_name == "print") { - std::string output; - for (int i = 0; i < args.size(); ++i) { - std::shared_ptr arg = interpreter.visit(args[i]); - if (arg->get_type() == VALUE_ERROR) - return arg; - if (i > 0) - output += " "; - output += arg->get_num(); - } - return execute_print(output); - } - - std::shared_ptr ret{set_args(args, sym, interpreter)}; - if (ret->get_type() == VALUE_ERROR) - return ret; - if (algo_name == "read") { - return execute_read(); - } else if (algo_name == "read_line") { - return execute_read_line(); - } else if (algo_name == "open") { - return std::make_shared(VALUE_ERROR, "Not found!"); - } else if (algo_name == "clear") { - return execute_clear(); - } else if (algo_name == "quit") { - exit(0); - } else if (algo_name == "int") { - return execute_int(sym.get(arg_names[0])->get_num()); - } else if (algo_name == "float") { - return execute_float(sym.get(arg_names[0])->get_num()); - } else if (algo_name == "string") { - return execute_string(sym.get(arg_names[0])->get_num()); - } else if (algo_name == "HashTable") { - return std::make_shared(); - } - return ret; -} - -std::shared_ptr BoundMethodValue::execute(const NodeList& args, - SymbolTable *parent) { - if (obj->get_type() == VALUE_ARRAY) { - ArrayValue *arr_obj = dynamic_cast(obj.get()); +std::shared_ptr BuiltinAlgoValue::execute(const NodeList& args, SymbolTable* parent) { SymbolTable sym(parent); + ScopeCleaner cleaner(sym); Interpreter interpreter(sym); - - if (method_name == "push" || method_name == "push_back") { - if (args.size() != 1) { - return std::make_shared( - VALUE_ERROR, "Expect one argument for " + method_name + "\n"); - } - std::shared_ptr arg = interpreter.visit(args[0]); - if (arg->get_type() == VALUE_ERROR) - return arg; - arr_obj->push_back(arg); - return arr_obj->back(); - } else if (method_name == "pop" || method_name == "pop_back") { - if (!args.empty()) { - return std::make_shared( - VALUE_ERROR, "Expect zero argument for " + method_name + "\n"); - } - if (arr_obj->size()->get_num() == "0") { - std::cout << "Cannot " << method_name << " from an empty array\n"; - return std::make_shared(); - } - return arr_obj->pop_back(); - } else if (method_name == "resize") { - if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for resize\n"); - } - std::shared_ptr new_size_val = interpreter.visit(args[0]); - if (new_size_val->get_type() == VALUE_ERROR) - return new_size_val; - if (new_size_val->get_type() != VALUE_INT) { - std::cout << "Argument for resize must be an integer\n"; - return std::make_shared(); - } - long long new_size; - try { - new_size = new_size_val->as_int(); - } catch (const std::out_of_range &oor) { - std::cout << "Resize argument out of range\n"; - return std::make_shared(); - } - if (new_size < 0) { - std::cout << "Resize argument cannot be negative\n"; - return std::make_shared(); - } - arr_obj->resize(static_cast(new_size)); - return obj; - } else if (method_name == "insert") { - if (args.size() != 2) { - return std::make_shared(VALUE_ERROR, - "Expect two arguments for insert\n"); - } - std::shared_ptr index = interpreter.visit(args[0]); - if (index->get_type() == VALUE_ERROR) - return index; - std::shared_ptr value = interpreter.visit(args[1]); - if (value->get_type() == VALUE_ERROR) - return value; - return arr_obj->insert(index->as_int(), value); - } else if (method_name == "remove") { - if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for remove\n"); - } - std::shared_ptr index = interpreter.visit(args[0]); - if (index->get_type() == VALUE_ERROR) - return index; - return arr_obj->remove(index->as_int()); - } else if (method_name == "size") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for size\n"); - } - return arr_obj->size(); - } else if (method_name == "back") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero arguments for back\n"); - } - if (arr_obj->size()->get_num() == "0") { - return std::make_shared( - VALUE_ERROR, "Cannot call back on an empty array\n"); - } - return arr_obj->back(); + if (algo_name == "print") { + std::string output; + for (int i = 0; i < args.size(); ++i) { + std::shared_ptr arg = interpreter.visit(args[i]); + if (arg->get_type() == VALUE_ERROR) return arg; + if (i > 0) output += " "; + output += arg->get_num(); + } + return execute_print(output); } - } else if (obj->get_type() == VALUE_STRING) { - if (method_name == "size") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for size\n"); - } - return std::make_shared>( - VALUE_INT, static_cast(obj->as_string().size())); + + std::shared_ptr ret{set_args(args, sym, interpreter)}; + if (ret->get_type() == VALUE_ERROR) return ret; + if (algo_name == "read") { + return execute_read(); + } else if (algo_name == "read_line") { + return execute_read_line(); + } else if (algo_name == "open") { + return std::make_shared(VALUE_ERROR, "Not found!"); + } else if (algo_name == "clear") { + return execute_clear(); + } else if (algo_name == "quit") { + exit(0); + } else if (algo_name == "int") { + return execute_int(sym.get(arg_names[0])->get_num()); + } else if (algo_name == "float") { + return execute_float(sym.get(arg_names[0])->get_num()); + } else if (algo_name == "string") { + return execute_string(sym.get(arg_names[0])->get_num()); + } else if (algo_name == "HashTable") { + return std::make_shared(); } - } else if (obj->get_type() == VALUE_HASH_TABLE) { - HashTableValue *table_obj = dynamic_cast(obj.get()); - SymbolTable sym(parent); - Interpreter interpreter(sym); + return ret; +} - if (method_name == "set") { - if (args.size() != 2) { - return std::make_shared(VALUE_ERROR, - "Expect two arguments for set\n"); - } - std::shared_ptr key = interpreter.visit(args[0]); - if (key->get_type() == VALUE_ERROR) - return key; - std::shared_ptr value = interpreter.visit(args[1]); - if (value->get_type() == VALUE_ERROR) - return value; - return table_obj->set(key, value); - } else if (method_name == "get") { - if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for get\n"); - } - std::shared_ptr key = interpreter.visit(args[0]); - if (key->get_type() == VALUE_ERROR) - return key; - return table_obj->get(key); - } else if (method_name == "contains") { - if (args.size() != 1) { - return std::make_shared( - VALUE_ERROR, "Expect one argument for contains\n"); - } - std::shared_ptr key = interpreter.visit(args[0]); - if (key->get_type() == VALUE_ERROR) - return key; - return std::make_shared>(VALUE_INT, - table_obj->contains(key)); - } else if (method_name == "remove") { - if (args.size() != 1) { - return std::make_shared(VALUE_ERROR, - "Expect one argument for remove\n"); - } - std::shared_ptr key = interpreter.visit(args[0]); - if (key->get_type() == VALUE_ERROR) - return key; - return table_obj->remove(key); - } else if (method_name == "size") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for size\n"); - } - return table_obj->size(); - } else if (method_name == "is_empty") { - if (!args.empty()) { - return std::make_shared( - VALUE_ERROR, "Expect zero argument for is_empty\n"); - } - return std::make_shared>( - VALUE_INT, table_obj->size()->as_int() == 0); - } else if (method_name == "keys") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for keys\n"); - } - return table_obj->keys(); - } else if (method_name == "values") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for values\n"); - } - return table_obj->values(); - } else if (method_name == "clear") { - if (!args.empty()) { - return std::make_shared(VALUE_ERROR, - "Expect zero argument for clear\n"); - } - table_obj->clear(); - return obj; - } - } else if (obj->get_type() == VALUE_INSTANCE) { - InstanceValue *inst_obj = dynamic_cast(obj.get()); - // Find method - if (inst_obj->struct_def->methods.count(method_name)) { - std::shared_ptr method = - inst_obj->struct_def->methods[method_name]; - // method is likely AlgoValue. - // We need to execute it with 'self' in scope. - // We can use the existing AlgoValue::execute, but we need to inject - // 'self'. Since AlgoValue::execute creates a new symbol table, we can't - // inject it easily from outside *before* it starts unless we modify it or - // subclass it. However, we can create a new AlgoValue or wrapper that - // injects 'self'. - - // Actually, let's copy the logic of AlgoValue::execute here but add self. - AlgoValue *algo_val = dynamic_cast(method.get()); - if (!algo_val) - return std::make_shared(VALUE_ERROR, - "Method is not an algorithm"); +std::shared_ptr BoundMethodValue::execute(const NodeList& args, SymbolTable* parent) { + if (obj->get_type() == VALUE_ARRAY) { + ArrayValue* arr_obj = dynamic_cast(obj.get()); + SymbolTable sym(parent); + Interpreter interpreter(sym); - SymbolTable sym(parent); - ScopeCleaner cleaner(sym); - Interpreter interpreter(sym); + if (method_name == "push" || method_name == "push_back") { + if (args.size() != 1) { + return std::make_shared( + VALUE_ERROR, "Expect one argument for " + method_name + "\n"); + } + std::shared_ptr arg = interpreter.visit(args[0]); + if (arg->get_type() == VALUE_ERROR) return arg; + arr_obj->push_back(arg); + return arr_obj->back(); + } else if (method_name == "pop" || method_name == "pop_back") { + if (!args.empty()) { + return std::make_shared( + VALUE_ERROR, "Expect zero argument for " + method_name + "\n"); + } + if (arr_obj->size()->get_num() == "0") { + std::cout << "Cannot " << method_name << " from an empty array\n"; + return std::make_shared(); + } + return arr_obj->pop_back(); + } else if (method_name == "resize") { + if (args.size() != 1) { + return std::make_shared(VALUE_ERROR, + "Expect one argument for resize\n"); + } + std::shared_ptr new_size_val = interpreter.visit(args[0]); + if (new_size_val->get_type() == VALUE_ERROR) return new_size_val; + if (new_size_val->get_type() != VALUE_INT) { + std::cout << "Argument for resize must be an integer\n"; + return std::make_shared(); + } + long long new_size; + try { + new_size = new_size_val->as_int(); + } catch (const std::out_of_range& oor) { + std::cout << "Resize argument out of range\n"; + return std::make_shared(); + } + if (new_size < 0) { + std::cout << "Resize argument cannot be negative\n"; + return std::make_shared(); + } + arr_obj->resize(static_cast(new_size)); + return obj; + } else if (method_name == "insert") { + if (args.size() != 2) { + return std::make_shared(VALUE_ERROR, + "Expect two arguments for insert\n"); + } + std::shared_ptr index = interpreter.visit(args[0]); + if (index->get_type() == VALUE_ERROR) return index; + std::shared_ptr value = interpreter.visit(args[1]); + if (value->get_type() == VALUE_ERROR) return value; + return arr_obj->insert(index->as_int(), value); + } else if (method_name == "remove") { + if (args.size() != 1) { + return std::make_shared(VALUE_ERROR, + "Expect one argument for remove\n"); + } + std::shared_ptr index = interpreter.visit(args[0]); + if (index->get_type() == VALUE_ERROR) return index; + return arr_obj->remove(index->as_int()); + } else if (method_name == "size") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, "Expect zero argument for size\n"); + } + return arr_obj->size(); + } else if (method_name == "back") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, + "Expect zero arguments for back\n"); + } + if (arr_obj->size()->get_num() == "0") { + return std::make_shared(VALUE_ERROR, + "Cannot call back on an empty array\n"); + } + return arr_obj->back(); + } + } else if (obj->get_type() == VALUE_STRING) { + if (method_name == "size") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, "Expect zero argument for size\n"); + } + return std::make_shared>( + VALUE_INT, static_cast(obj->as_string().size())); + } + } else if (obj->get_type() == VALUE_HASH_TABLE) { + HashTableValue* table_obj = dynamic_cast(obj.get()); + SymbolTable sym(parent); + Interpreter interpreter(sym); + + if (method_name == "set") { + if (args.size() != 2) { + return std::make_shared(VALUE_ERROR, "Expect two arguments for set\n"); + } + std::shared_ptr key = interpreter.visit(args[0]); + if (key->get_type() == VALUE_ERROR) return key; + std::shared_ptr value = interpreter.visit(args[1]); + if (value->get_type() == VALUE_ERROR) return value; + return table_obj->set(key, value); + } else if (method_name == "get") { + if (args.size() != 1) { + return std::make_shared(VALUE_ERROR, "Expect one argument for get\n"); + } + std::shared_ptr key = interpreter.visit(args[0]); + if (key->get_type() == VALUE_ERROR) return key; + return table_obj->get(key); + } else if (method_name == "contains") { + if (args.size() != 1) { + return std::make_shared(VALUE_ERROR, + "Expect one argument for contains\n"); + } + std::shared_ptr key = interpreter.visit(args[0]); + if (key->get_type() == VALUE_ERROR) return key; + return std::make_shared>(VALUE_INT, table_obj->contains(key)); + } else if (method_name == "remove") { + if (args.size() != 1) { + return std::make_shared(VALUE_ERROR, + "Expect one argument for remove\n"); + } + std::shared_ptr key = interpreter.visit(args[0]); + if (key->get_type() == VALUE_ERROR) return key; + return table_obj->remove(key); + } else if (method_name == "size") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, "Expect zero argument for size\n"); + } + return table_obj->size(); + } else if (method_name == "is_empty") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, + "Expect zero argument for is_empty\n"); + } + return std::make_shared>(VALUE_INT, + table_obj->size()->as_int() == 0); + } else if (method_name == "keys") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, "Expect zero argument for keys\n"); + } + return table_obj->keys(); + } else if (method_name == "values") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, + "Expect zero argument for values\n"); + } + return table_obj->values(); + } else if (method_name == "clear") { + if (!args.empty()) { + return std::make_shared(VALUE_ERROR, + "Expect zero argument for clear\n"); + } + table_obj->clear(); + return obj; + } + } else if (obj->get_type() == VALUE_INSTANCE) { + InstanceValue* inst_obj = dynamic_cast(obj.get()); + // Find method + if (inst_obj->struct_def->methods.count(method_name)) { + std::shared_ptr method = inst_obj->struct_def->methods[method_name]; + AlgoValue* algo_val = dynamic_cast(method.get()); + if (!algo_val) { + if (method->get_type() != VALUE_ALGO) { + return std::make_shared(VALUE_ERROR, "Method is not an algorithm"); + } + // Compiled struct methods use their own execute path, with + // `self` provided by this parent binding scope. + SymbolTable sym(parent); + sym.set("self", obj); + return method->execute(args, &sym); + } - // Set self - sym.set("self", obj); + SymbolTable sym(parent); + ScopeCleaner cleaner(sym); + Interpreter interpreter(sym); - std::shared_ptr ret{algo_val->set_args(args, sym, interpreter)}; - if (ret->get_type() == VALUE_ERROR) - return ret; + // Set self + sym.set("self", obj); - // Execute body - AlgorithmDefNode* algo_node = dynamic_cast(algo_val->get_node_ptr().get()); - const NodeList& algo_body = algo_node->get_body(); + std::shared_ptr ret{algo_val->set_args(args, sym, interpreter)}; + if (ret->get_type() == VALUE_ERROR) return ret; - std::shared_ptr res = ret; - for (int i = 0; i < algo_body.size(); ++i) { - res = interpreter.visit(algo_body[i]); - if (res->get_type() == VALUE_ERROR) - return res; - if (res->get_type() == VALUE_RETURN) - return dynamic_cast(res.get())->get_value(); - } - return res; + // Execute body + AlgorithmDefNode* algo_node = + dynamic_cast(algo_val->get_node_ptr().get()); + const NodeList& algo_body = algo_node->get_body(); + + std::shared_ptr res = ret; + for (int i = 0; i < algo_body.size(); ++i) { + res = interpreter.visit(algo_body[i]); + if (res->get_type() == VALUE_ERROR) return res; + if (res->get_type() == VALUE_RETURN) + return dynamic_cast(res.get())->get_value(); + } + return res; + } } - } - return std::make_shared(VALUE_ERROR, - "Unknown member or invalid object for " + - method_name + "\n"); + return std::make_shared( + VALUE_ERROR, "Unknown member or invalid object for " + method_name + "\n"); } -std::shared_ptr BuiltinAlgoValue::execute_print(const std::string &str) { - std::cout << str << "\n"; - return std::make_shared(); +std::shared_ptr BuiltinAlgoValue::execute_print(const std::string& str) { + std::cout << str << "\n"; + return std::make_shared(); } std::shared_ptr BuiltinAlgoValue::execute_read() { - std::string ret; - std::cin >> ret; - std::cin.ignore(); - return std::make_shared>(VALUE_STRING, ret); + std::string ret; + std::cin >> ret; + std::cin.ignore(); + return std::make_shared>(VALUE_STRING, ret); } std::shared_ptr BuiltinAlgoValue::execute_read_line() { - std::string ret; - std::getline(std::cin, ret); - return std::make_shared>(VALUE_STRING, ret); + std::string ret; + std::getline(std::cin, ret); + return std::make_shared>(VALUE_STRING, ret); } std::shared_ptr BuiltinAlgoValue::execute_clear() { - std::system("clear"); - return std::make_shared(); -} - -std::shared_ptr BuiltinAlgoValue::execute_int(const std::string &str) { - if (str[0] != '-' && !std::isdigit(str[0])) { - return std::make_shared(VALUE_ERROR, "Cannot convert \"" + str + - "\" to an int"); - } - for (int i{1}; i < str.size(); ++i) - if (!std::isdigit(str[0])) - return std::make_shared( - VALUE_ERROR, "Cannot convert \"" + str + "\" to an int"); - return std::make_shared>(VALUE_INT, std::stoll(str)); -} - -std::shared_ptr BuiltinAlgoValue::execute_float(const std::string &str) { - int point{0}; - if (str[0] != '-' && !std::isdigit(str[0])) { - return std::make_shared(VALUE_ERROR, "Cannot convert \"" + str + - "\" to an int"); - } - for (int i{1}; i < str.size(); ++i) { - if (!std::isdigit(str[0]) && (str[0] != '.' || point == 1)) { - return std::make_shared( - VALUE_ERROR, "Cannot convert \"" + str + "\" to an int"); - } - if (str[0] == '.') - point++; - } - return std::make_shared>(VALUE_FLOAT, std::stod(str)); -} - -std::shared_ptr -BuiltinAlgoValue::execute_string(const std::string &str) { - return std::make_shared>(VALUE_STRING, str); -} - -std::shared_ptr operator+(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_FLOAT, a->as_double() + b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() + b->as_int()); - else if (a->get_type() == VALUE_STRING && b->get_type() == VALUE_STRING) - return std::make_shared>( - VALUE_STRING, a->as_string() + b->as_string()); - else - return std::make_shared( - VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + - "Runtime ERROR: ADD operation can only apply on " - "number or two string\n" RESET); -} - -std::shared_ptr operator-(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_FLOAT, a->as_double() - b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() - b->as_int()); - else - return std::make_shared( - VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + - "Runtime ERROR: SUB operation can only apply on number\n" RESET); -} - -std::shared_ptr operator*(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_FLOAT, a->as_double() * b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() * b->as_int()); - else if (a->get_type() == VALUE_STRING && b->get_type() == VALUE_INT) { - std::string ret, str_a{a->as_string()}; - int64_t times{b->as_int()}; - for (int i{0}; i < times; ++i) - ret += str_a; - return std::make_shared>(VALUE_STRING, ret); - } else - return std::make_shared( - VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + - "Runtime ERROR: MUL operation can only apply on " - "number or string and int\n" RESET); -} - -std::shared_ptr operator/(std::shared_ptr a, - std::shared_ptr b) { - if (b->as_double() == 0.0) - return std::make_shared(VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + - "Runtime ERROR: DIV by 0\n" RESET); - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_FLOAT, a->as_double() / b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() / b->as_int()); - else - return std::make_shared( - VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + - "Runtime ERROR: DIV operation can only apply on number\n" RESET); + std::system("clear"); + return std::make_shared(); } -std::shared_ptr operator%(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() != VALUE_INT || b->get_type() != VALUE_INT) - return std::make_shared( - VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + - "Cannot apply \"%\" operation on float\n" RESET); - return std::make_shared>( - VALUE_INT, a->as_int() % b->as_int()); -} - -std::shared_ptr operator==(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_INSTANCE || b->get_type() == VALUE_INSTANCE || - a->get_type() == VALUE_ARRAY || b->get_type() == VALUE_ARRAY || - a->get_type() == VALUE_HASH_TABLE || b->get_type() == VALUE_HASH_TABLE || - a->get_type() == VALUE_STRUCT || b->get_type() == VALUE_STRUCT) - return std::make_shared>(VALUE_INT, a.get() == b.get()); - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() == b->as_double()); - else - return std::make_shared>(VALUE_INT, - a->as_string() == b->as_string()); -} - -std::shared_ptr operator!=(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_INSTANCE || b->get_type() == VALUE_INSTANCE || - a->get_type() == VALUE_ARRAY || b->get_type() == VALUE_ARRAY || - a->get_type() == VALUE_HASH_TABLE || b->get_type() == VALUE_HASH_TABLE || - a->get_type() == VALUE_STRUCT || b->get_type() == VALUE_STRUCT) - return std::make_shared>(VALUE_INT, a.get() != b.get()); - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() != b->as_double()); - else - return std::make_shared>(VALUE_INT, - a->as_string() != b->as_string()); -} - -std::shared_ptr operator<(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() < b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() < b->as_int()); - else - return std::make_shared>(VALUE_INT, - a->as_string() < b->as_string()); -} - -std::shared_ptr operator>(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() > b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() > b->as_int()); - else - return std::make_shared>(VALUE_INT, - a->as_string() > b->as_string()); -} - -std::shared_ptr operator<=(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() <= b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() <= b->as_int()); - else - return std::make_shared>(VALUE_INT, - a->as_string() <= b->as_string()); -} - -std::shared_ptr operator>=(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, a->as_double() >= b->as_double()); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, a->as_int() >= b->as_int()); - else - return std::make_shared>(VALUE_INT, - a->as_string() >= b->as_string()); -} - -std::shared_ptr operator&&(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, - a->as_double() != 0 && b->as_double() != 0); - else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) - return std::make_shared>( - VALUE_INT, - a->as_int() != 0 && b->as_int() != 0); - else - return std::make_shared>( - VALUE_INT, a->as_int() && b->as_int()); -} - -std::shared_ptr operator||(std::shared_ptr a, - std::shared_ptr b) { - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_INT, - a->as_double() != 0 || b->as_double() != 0); - else - return std::make_shared>( - VALUE_INT, a->as_int() || b->as_int()); +std::shared_ptr BuiltinAlgoValue::execute_int(const std::string& str) { + if (str[0] != '-' && !std::isdigit(str[0])) { + return std::make_shared(VALUE_ERROR, + "Cannot convert \"" + str + "\" to an int"); + } + for (int i{1}; i < str.size(); ++i) + if (!std::isdigit(str[0])) + return std::make_shared(VALUE_ERROR, + "Cannot convert \"" + str + "\" to an int"); + return std::make_shared>(VALUE_INT, std::stoll(str)); +} + +std::shared_ptr BuiltinAlgoValue::execute_float(const std::string& str) { + int point{0}; + if (str[0] != '-' && !std::isdigit(str[0])) { + return std::make_shared(VALUE_ERROR, + "Cannot convert \"" + str + "\" to an int"); + } + for (int i{1}; i < str.size(); ++i) { + if (!std::isdigit(str[0]) && (str[0] != '.' || point == 1)) { + return std::make_shared(VALUE_ERROR, + "Cannot convert \"" + str + "\" to an int"); + } + if (str[0] == '.') point++; + } + return std::make_shared>(VALUE_FLOAT, std::stod(str)); +} + +std::shared_ptr BuiltinAlgoValue::execute_string(const std::string& str) { + return std::make_shared>(VALUE_STRING, str); +} + +std::shared_ptr operator+(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, a->as_double() + b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() + b->as_int()); + else if (a->get_type() == VALUE_STRING && b->get_type() == VALUE_STRING) + return std::make_shared>(VALUE_STRING, + a->as_string() + b->as_string()); + else + return std::make_shared(VALUE_ERROR, + Color(0xFF, 0x39, 0x6E).get() + + "Runtime ERROR: ADD operation can only apply on " + "number or two string\n" RESET); +} + +std::shared_ptr operator-(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, a->as_double() - b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() - b->as_int()); + else + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + + "Runtime ERROR: SUB operation can only apply on number\n" RESET); +} + +std::shared_ptr operator*(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, a->as_double() * b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() * b->as_int()); + else if (a->get_type() == VALUE_STRING && b->get_type() == VALUE_INT) { + std::string ret, str_a{a->as_string()}; + int64_t times{b->as_int()}; + for (int i{0}; i < times; ++i) ret += str_a; + return std::make_shared>(VALUE_STRING, ret); + } else + return std::make_shared(VALUE_ERROR, + Color(0xFF, 0x39, 0x6E).get() + + "Runtime ERROR: MUL operation can only apply on " + "number or string and int\n" RESET); +} + +std::shared_ptr operator/(std::shared_ptr a, std::shared_ptr b) { + if (b->as_double() == 0.0) + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Runtime ERROR: DIV by 0\n" RESET); + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, a->as_double() / b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() / b->as_int()); + else + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + + "Runtime ERROR: DIV operation can only apply on number\n" RESET); +} + +std::shared_ptr operator%(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() != VALUE_INT || b->get_type() != VALUE_INT) + return std::make_shared( + VALUE_ERROR, + Color(0xFF, 0x39, 0x6E).get() + "Cannot apply \"%\" operation on float\n" RESET); + return std::make_shared>(VALUE_INT, a->as_int() % b->as_int()); +} + +std::shared_ptr operator==(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_INSTANCE || b->get_type() == VALUE_INSTANCE || + a->get_type() == VALUE_ARRAY || b->get_type() == VALUE_ARRAY || + a->get_type() == VALUE_HASH_TABLE || b->get_type() == VALUE_HASH_TABLE || + a->get_type() == VALUE_STRUCT || b->get_type() == VALUE_STRUCT) + return std::make_shared>(VALUE_INT, a.get() == b.get()); + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() == b->as_double()); + else + return std::make_shared>(VALUE_INT, a->as_string() == b->as_string()); +} + +std::shared_ptr operator!=(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_INSTANCE || b->get_type() == VALUE_INSTANCE || + a->get_type() == VALUE_ARRAY || b->get_type() == VALUE_ARRAY || + a->get_type() == VALUE_HASH_TABLE || b->get_type() == VALUE_HASH_TABLE || + a->get_type() == VALUE_STRUCT || b->get_type() == VALUE_STRUCT) + return std::make_shared>(VALUE_INT, a.get() != b.get()); + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() != b->as_double()); + else + return std::make_shared>(VALUE_INT, a->as_string() != b->as_string()); +} + +std::shared_ptr operator<(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() < b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() < b->as_int()); + else + return std::make_shared>(VALUE_INT, a->as_string() < b->as_string()); +} + +std::shared_ptr operator>(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() > b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() > b->as_int()); + else + return std::make_shared>(VALUE_INT, a->as_string() > b->as_string()); +} + +std::shared_ptr operator<=(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() <= b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() <= b->as_int()); + else + return std::make_shared>(VALUE_INT, a->as_string() <= b->as_string()); +} + +std::shared_ptr operator>=(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, a->as_double() >= b->as_double()); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, a->as_int() >= b->as_int()); + else + return std::make_shared>(VALUE_INT, a->as_string() >= b->as_string()); +} + +std::shared_ptr operator&&(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, + a->as_double() != 0 && b->as_double() != 0); + else if (a->get_type() == VALUE_INT && b->get_type() == VALUE_INT) + return std::make_shared>(VALUE_INT, + a->as_int() != 0 && b->as_int() != 0); + else + return std::make_shared>(VALUE_INT, a->as_int() && b->as_int()); +} + +std::shared_ptr operator||(std::shared_ptr a, std::shared_ptr b) { + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_INT, + a->as_double() != 0 || b->as_double() != 0); + else + return std::make_shared>(VALUE_INT, a->as_int() || b->as_int()); } std::shared_ptr operator-(std::shared_ptr a) { - if (a->get_type() == VALUE_FLOAT) - return std::make_shared>(VALUE_FLOAT, - 0 - a->as_double()); - else - return std::make_shared>(VALUE_INT, - 0 - a->as_int()); + if (a->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, 0 - a->as_double()); + else + return std::make_shared>(VALUE_INT, 0 - a->as_int()); } std::shared_ptr operator!(std::shared_ptr a) { - if (a->get_type() == VALUE_FLOAT) - return std::make_shared>(VALUE_FLOAT, - a->as_double() == 0); - else - return std::make_shared>(VALUE_INT, - a->as_int() == 0); + if (a->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, a->as_double() == 0); + else + return std::make_shared>(VALUE_INT, a->as_int() == 0); } std::shared_ptr pow(std::shared_ptr a, std::shared_ptr b) { - if (a->as_double() == 0.0 && b->as_double() == 0.0) - return std::make_shared( - VALUE_ERROR, - Color(0xFF, 0x39, 0x6E).get() + "Runtime ERROR: 0 to the 0\n" RESET); - if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) - return std::make_shared>( - VALUE_FLOAT, - std::pow(a->as_double(), b->as_double())); - else - return std::make_shared>( - VALUE_INT, - std::pow(a->as_int(), b->as_int())); -} - -std::shared_ptr InstanceValue::get_member(const std::string &name, std::shared_ptr self) { - if (members.count(name)) { - return members[name]; - } - // Check for methods in struct definition - if (struct_def->methods.count(name)) { - if (self.get() == nullptr) { - // Fallback if self not provided, but this shouldn't happen for method calls - // Create a copy? Or error? - // For now, create a copy as before, but warn? - return std::make_shared( - std::make_shared(*this), name); - } - return std::make_shared(self, name); - } - return std::make_shared(VALUE_ERROR, "Member not found: " + name); -} - -void InstanceValue::set_member(const std::string &name, - std::shared_ptr val) { - // If it's declared in struct def members, we can set it. - bool found = false; - for (const auto &mem : struct_def->members) { - if (mem == name) { - found = true; - break; - } - } - if (found) { - members[name] = val; - } else { - members[name] = val; // Just set it for now. - } -} -std::shared_ptr StructValue::execute(const NodeList& args, - SymbolTable *parent) { - // Constructor call - std::shared_ptr instance = - std::make_shared(std::make_shared(*this)); - - // Initialize members to NONE - for (const auto &member : members) { - instance->set_member(member, std::make_shared(VALUE_NONE)); - } - - // Call constructor if exists - if (methods.count("constructor")) { - std::shared_ptr ctor = methods["constructor"]; - // We need to bind the constructor to the instance - std::shared_ptr bound_ctor = - std::make_shared(instance, "constructor"); - // But BoundMethodValue execute logic for custom objects is not implemented - // in pseudo.cpp yet (only ArrayValue). We need to implement it. Actually, - // let's reuse AlgoValue::execute but inject 'self'. - - // Wait, BoundMethodValue holds the object and the method name. - // Its execute() needs to look up the method (which we have in 'ctor') and - // call it with 'self' = obj. - - // Actually, if we use BoundMethodValue, we need to implement execute for - // generic objects. Alternatively, we can manually call ctor->execute(args, - // parent) but we need to inject 'self'. AlgoValue::execute creates a new - // symbol table. We need to add 'self' to it. But AlgoValue::execute - // interface doesn't allow injecting symbols easily before execution. - // However, AlgoValue::execute does: - // SymbolTable sym(parent); - // set_args(args, sym, interpreter); - // ... - - // We can manually do what AlgoValue::execute does. - // Or we can modify AlgoValue to support binding? - // Or implement BoundMethodValue::execute properly. - } - - // For now, let's rely on BoundMethodValue which we will implement/update in - // pseudo.cpp. - if (methods.count("constructor")) { - std::shared_ptr bound_ctor = - std::make_shared(instance, "constructor"); - std::shared_ptr ret = bound_ctor->execute(args, parent); - if (ret->get_type() == VALUE_ERROR) - return ret; - } - - return instance; + if (a->as_double() == 0.0 && b->as_double() == 0.0) + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Runtime ERROR: 0 to the 0\n" RESET); + if (a->get_type() == VALUE_FLOAT || b->get_type() == VALUE_FLOAT) + return std::make_shared>(VALUE_FLOAT, + std::pow(a->as_double(), b->as_double())); + else + return std::make_shared>(VALUE_INT, std::pow(a->as_int(), b->as_int())); +} + +std::shared_ptr InstanceValue::get_member(const std::string& name, + std::shared_ptr self) { + if (members.count(name)) { + return members[name]; + } + // Check for methods in struct definition + if (struct_def->methods.count(name)) { + if (self.get() == nullptr) { + // Fallback if self not provided, but this shouldn't happen for method calls + // Create a copy? Or error? + // For now, create a copy as before, but warn? + return std::make_shared(std::make_shared(*this), name); + } + return std::make_shared(self, name); + } + return std::make_shared(VALUE_ERROR, "Member not found: " + name); +} + +void InstanceValue::set_member(const std::string& name, std::shared_ptr val) { + // If it's declared in struct def members, we can set it. + bool found = false; + for (const auto& mem : struct_def->members) { + if (mem == name) { + found = true; + break; + } + } + if (found) { + members[name] = val; + } else { + members[name] = val; // Just set it for now. + } +} +std::shared_ptr StructValue::execute(const NodeList& args, SymbolTable* parent) { + // Constructor call + std::shared_ptr instance = + std::make_shared(std::make_shared(*this)); + + // Initialize members to NONE + for (const auto& member : members) { + instance->set_member(member, std::make_shared(VALUE_NONE)); + } + + // Call constructor if exists + if (methods.count("constructor")) { + std::shared_ptr ctor = methods["constructor"]; + // We need to bind the constructor to the instance + std::shared_ptr bound_ctor = + std::make_shared(instance, "constructor"); + // But BoundMethodValue execute logic for custom objects is not implemented + // in pseudo.cpp yet (only ArrayValue). We need to implement it. Actually, + // let's reuse AlgoValue::execute but inject 'self'. + + // Wait, BoundMethodValue holds the object and the method name. + // Its execute() needs to look up the method (which we have in 'ctor') and + // call it with 'self' = obj. + + // Actually, if we use BoundMethodValue, we need to implement execute for + // generic objects. Alternatively, we can manually call ctor->execute(args, + // parent) but we need to inject 'self'. AlgoValue::execute creates a new + // symbol table. We need to add 'self' to it. But AlgoValue::execute + // interface doesn't allow injecting symbols easily before execution. + // However, AlgoValue::execute does: + // SymbolTable sym(parent); + // set_args(args, sym, interpreter); + // ... + + // We can manually do what AlgoValue::execute does. + // Or we can modify AlgoValue to support binding? + // Or implement BoundMethodValue::execute properly. + } + + // For now, let's rely on BoundMethodValue which we will implement/update in + // pseudo.cpp. + if (methods.count("constructor")) { + std::shared_ptr bound_ctor = + std::make_shared(instance, "constructor"); + std::shared_ptr ret = bound_ctor->execute(args, parent); + if (ret->get_type() == VALUE_ERROR) return ret; + } + + return instance; } /// -------------------- @@ -1206,227 +1114,213 @@ std::shared_ptr StructValue::execute(const NodeList& args, namespace { -struct ImportState { - std::set loaded; - std::set loading; -}; +std::string trim(const std::string& str) { + size_t begin = 0; + while (begin < str.size() && std::isspace(static_cast(str[begin]))) { + begin++; + } -std::string trim(const std::string &str) { - size_t begin = 0; - while (begin < str.size() && - std::isspace(static_cast(str[begin]))) { - begin++; - } - - size_t end = str.size(); - while (end > begin && - std::isspace(static_cast(str[end - 1]))) { - end--; - } - return str.substr(begin, end - begin); -} - -bool parse_import_line(const std::string &line, std::string &target) { - std::string trimmed = trim(line); - const std::string keyword = "import"; - if (trimmed.rfind(keyword, 0) != 0) { - return false; - } - if (trimmed.size() > keyword.size() && - !std::isspace(static_cast(trimmed[keyword.size()]))) { - return false; - } + size_t end = str.size(); + while (end > begin && std::isspace(static_cast(str[end - 1]))) { + end--; + } + return str.substr(begin, end - begin); +} - target = trim(trimmed.substr(keyword.size())); - if (target.empty()) { - return false; - } +bool parse_import_line(const std::string& line, std::string& target) { + std::string trimmed = trim(line); + std::string keyword = "import"; + if (trimmed.rfind(keyword, 0) != 0) { + return false; + } + if (trimmed.size() > keyword.size() && + !std::isspace(static_cast(trimmed[keyword.size()]))) { + return false; + } - if (target.front() == '"' && target.back() == '"' && target.size() >= 2) { - target = target.substr(1, target.size() - 2); - } - return !target.empty(); + target = trim(trimmed.substr(keyword.size())); + if (target.empty()) { + return false; + } + + if (target.front() == '"' && target.back() == '"' && target.size() >= 2) { + target = target.substr(1, target.size() - 2); + } + return !target.empty(); } -std::vector -candidate_import_paths(const std::string &target, - const std::filesystem::path &base_dir) { - namespace fs = std::filesystem; - fs::path target_path(target); - std::vector candidates; +std::vector candidate_import_paths(const std::string& target, + const std::filesystem::path& base_dir) { + namespace fs = std::filesystem; + fs::path target_path(target); + std::vector candidates; + + if (target_path.is_absolute()) { + candidates.push_back(target_path); + } else { + if (!target_path.has_extension()) { + candidates.push_back(fs::current_path() / "lib" / (target + ".ps")); + } + candidates.push_back(base_dir / target_path); + candidates.push_back(fs::current_path() / target_path); + } - if (target_path.is_absolute()) { - candidates.push_back(target_path); - } else { if (!target_path.has_extension()) { - candidates.push_back(fs::current_path() / "lib" / (target + ".ps")); + candidates.push_back(base_dir / (target + ".ps")); + candidates.push_back(fs::current_path() / (target + ".ps")); } - candidates.push_back(base_dir / target_path); - candidates.push_back(fs::current_path() / target_path); - } - if (!target_path.has_extension()) { - candidates.push_back(base_dir / (target + ".ps")); - candidates.push_back(fs::current_path() / (target + ".ps")); - } + return candidates; +} - return candidates; +bool read_file(const std::filesystem::path& path, std::string& text) { + std::ifstream input(path); + if (!input) { + return false; + } + std::stringstream buffer; + buffer << input.rdbuf(); + text = buffer.str(); + if (!text.empty() && text.back() != '\n') { + text += '\n'; + } + return true; } -bool read_file(const std::filesystem::path &path, std::string &text) { - std::ifstream input(path); - if (!input) { +bool resolve_import(const std::string& target, const std::filesystem::path& base_dir, + std::filesystem::path& resolved) { + namespace fs = std::filesystem; + for (const auto& candidate : candidate_import_paths(target, base_dir)) { + std::error_code ec; + if (fs::exists(candidate, ec) && fs::is_regular_file(candidate, ec)) { + resolved = fs::weakly_canonical(candidate, ec); + if (ec) { + resolved = fs::absolute(candidate, ec); + } + return true; + } + } return false; - } - std::stringstream buffer; - buffer << input.rdbuf(); - text = buffer.str(); - if (!text.empty() && text.back() != '\n') { - text += '\n'; - } - return true; -} - -bool resolve_import(const std::string &target, - const std::filesystem::path &base_dir, - std::filesystem::path &resolved) { - namespace fs = std::filesystem; - for (const auto &candidate : candidate_import_paths(target, base_dir)) { - std::error_code ec; - if (fs::exists(candidate, ec) && fs::is_regular_file(candidate, ec)) { - resolved = fs::weakly_canonical(candidate, ec); - if (ec) { - resolved = fs::absolute(candidate, ec); - } - return true; - } - } - return false; -} - -bool expand_imports(const std::string &file_name, const std::string &text, - ImportState &state, std::string &expanded, - std::string &error) { - namespace fs = std::filesystem; - fs::path current_path(file_name); - fs::path base_dir = current_path.has_parent_path() - ? fs::absolute(current_path).parent_path() - : fs::current_path(); - - std::stringstream input(text); - std::string line; - while (std::getline(input, line)) { - std::string target; - if (!parse_import_line(line, target)) { - expanded += line + "\n"; - continue; - } - - fs::path import_path; - if (!resolve_import(target, base_dir, import_path)) { - error = "Import ERROR: cannot resolve \"" + target + "\" from " + - base_dir.string(); - return false; - } - - std::string import_key = import_path.string(); - if (state.loaded.count(import_key)) { - continue; - } - if (state.loading.count(import_key)) { - error = "Import ERROR: circular import involving " + import_key; - return false; - } - - std::string import_text; - if (!read_file(import_path, import_text)) { - error = "Import ERROR: cannot read " + import_key; - return false; - } - - state.loading.insert(import_key); - std::string import_expanded; - if (!expand_imports(import_key, import_text, state, import_expanded, - error)) { - return false; - } - state.loading.erase(import_key); - state.loaded.insert(import_key); - expanded += import_expanded; - expanded += "\n"; - } - return true; -} - -} // namespace - -std::string run(std::string file_name, std::string text, - SymbolTable &global_symbol_table) { - ImportState import_state; - std::string expanded_text; - std::string import_error; - if (!expand_imports(file_name, text, import_state, expanded_text, - import_error)) { - std::cout << import_error << "\n"; - return "ABORT"; - } - - Lexer lexer(file_name, expanded_text); - TokenList tokens = lexer.make_tokens(); - if (tokens.empty()) - return ""; - if (tokens[0]->get_type() == TOKEN_ERROR) { - // std::cout << "Tokens: " << tokens << "\n"; - // Detailed Lexer Error? - // Lexer returns TOKEN_ERROR in list. - // But lexer usually returns a list with one error token if it fails? - // Or a list where some tokens are errors. - // Check lexer.cpp. - // Lexer::make_tokens() returns a list. If error, it pushes ErrorToken. - // Let's iterate and find errors. - for (auto tok : tokens) { - if (tok->get_type() == TOKEN_ERROR) { - std::cout << "Error: " << tok->get_value() << ", line " << tok->get_pos().line + 1 << ", column: " << tok->get_pos().column << "\n"; - std::cout << error_marker(expanded_text, tok->get_pos(), tok->get_pos()); - return "ABORT"; +} + +} // namespace + +bool expand_imports(const std::string& file_name, const std::string& text, ImportState& state, + std::string& expanded, std::string& error) { + namespace fs = std::filesystem; + fs::path current_path(file_name); + fs::path base_dir = current_path.has_parent_path() ? fs::absolute(current_path).parent_path() + : fs::current_path(); + + std::stringstream input(text); + std::string line; + while (std::getline(input, line)) { + std::string target; + if (!parse_import_line(line, target)) { + expanded += line + "\n"; + continue; + } + + fs::path import_path; + if (!resolve_import(target, base_dir, import_path)) { + error = "Import ERROR: cannot resolve \"" + target + "\" from " + base_dir.string(); + return false; + } + + std::string import_key = import_path.string(); + if (state.loaded.count(import_key)) { + continue; + } + if (state.loading.count(import_key)) { + error = "Import ERROR: circular import involving " + import_key; + return false; + } + + std::string import_text; + if (!read_file(import_path, import_text)) { + error = "Import ERROR: cannot read " + import_key; + return false; + } + + state.loading.insert(import_key); + std::string import_expanded; + if (!expand_imports(import_key, import_text, state, import_expanded, error)) { + return false; } + state.loading.erase(import_key); + state.loaded.insert(import_key); + expanded += import_expanded; + expanded += "\n"; } - } - - Parser parser(tokens); - NodeList ast = parser.parse(); - - for (auto node : ast) { - if(node->get_type() == NODE_ERROR) { - // std::cout << "Nodes: " << node->get_node() << "\n"; - std::shared_ptr err_tok = node->get_tok(); - if(err_tok) { - std::cout << "Error: " << err_tok->get_value() << ", line " << err_tok->get_pos().line + 1 << ", column: " << err_tok->get_pos().column << "\n"; - std::cout << error_marker(expanded_text, err_tok->get_pos(), err_tok->get_pos()); - } else { - std::cout << "Error: " << node->get_node() << "\n"; - } - return "ABORT"; - } - } - - Interpreter interpreter(global_symbol_table, file_name == "stdin"); - ArrayValue *ret{new ArrayValue(ValueList(0))}; - for (auto node : ast) { - ret->push_back(interpreter.visit(node)); - if (ret->back()->get_type() == VALUE_ERROR) { - std::cout << ret->back()->get_num() << "\n"; - return "ABORT"; - } - } - - while (ret->get_type() == VALUE_ARRAY && - ret->back()->get_type() == VALUE_ARRAY) { - ret = dynamic_cast(ret->back().get()); - } - - if (file_name == "stdin" && ret->operator[](0)->get_type() != VALUE_NONE) { - std::cout << ret->get_num() << "\n"; - } - return ""; + return true; +} + +std::string run(std::string file_name, std::string text, SymbolTable& global_symbol_table) { + ImportState import_state; + std::string expanded_text; + std::string import_error; + if (!expand_imports(file_name, text, import_state, expanded_text, import_error)) { + std::cout << import_error << "\n"; + return "ABORT"; + } + + Lexer lexer(file_name, expanded_text); + TokenList tokens = lexer.make_tokens(); + if (tokens.empty()) return ""; + if (tokens[0]->get_type() == TOKEN_ERROR) { + // std::cout << "Tokens: " << tokens << "\n"; + // Detailed Lexer Error? + // Lexer returns TOKEN_ERROR in list. + // But lexer usually returns a list with one error token if it fails? + // Or a list where some tokens are errors. + // Check lexer.cpp. + // Lexer::make_tokens() returns a list. If error, it pushes ErrorToken. + // Let's iterate and find errors. + for (auto tok : tokens) { + if (tok->get_type() == TOKEN_ERROR) { + std::cout << "Error: " << tok->get_value() << ", line " << tok->get_pos().line + 1 + << ", column: " << tok->get_pos().column << "\n"; + std::cout << error_marker(expanded_text, tok->get_pos(), tok->get_pos()); + return "ABORT"; + } + } + } + + Parser parser(tokens); + NodeList ast = parser.parse(); + + for (auto node : ast) { + if (node->get_type() == NODE_ERROR) { + // std::cout << "Nodes: " << node->get_node() << "\n"; + std::shared_ptr err_tok = node->get_tok(); + if (err_tok) { + std::cout << "Error: " << err_tok->get_value() << ", line " + << err_tok->get_pos().line + 1 + << ", column: " << err_tok->get_pos().column << "\n"; + std::cout << error_marker(expanded_text, err_tok->get_pos(), err_tok->get_pos()); + } else { + std::cout << "Error: " << node->get_node() << "\n"; + } + return "ABORT"; + } + } + + Interpreter interpreter(global_symbol_table, file_name == "stdin"); + ArrayValue* ret{new ArrayValue(ValueList(0))}; + for (auto node : ast) { + ret->push_back(interpreter.visit(node)); + if (ret->back()->get_type() == VALUE_ERROR) { + std::cout << ret->back()->get_num() << "\n"; + return "ABORT"; + } + } + + while (ret->get_type() == VALUE_ARRAY && ret->back()->get_type() == VALUE_ARRAY) { + ret = dynamic_cast(ret->back().get()); + } + + if (file_name == "stdin" && ret->operator[](0)->get_type() != VALUE_NONE) { + std::cout << ret->get_num() << "\n"; + } + return ""; } diff --git a/src/pseudoc.cpp b/src/pseudoc.cpp new file mode 100644 index 0000000..48ca45d --- /dev/null +++ b/src/pseudoc.cpp @@ -0,0 +1,262 @@ +/// -------------------- +/// pseudoc driver +/// -------------------- +/// +/// Compiles a .ps source file to a native executable: +/// pseudoc [-o ] [--emit-llvm] [--runtime-lib ] + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compiler.h" +#include "error.h" +#include "imports.h" +#include "lexer.h" +#include "node.h" +#include "parser.h" +#include "token.h" + +namespace { + +namespace fs = std::filesystem; + +void usage() { + std::cout << "usage: pseudoc [-o ] [--emit-llvm] " + "[--runtime-lib ]\n"; +} + +std::string find_runtime_lib(const std::string& flag_value, const char* argv0) { + std::vector candidates; + if (!flag_value.empty()) { + candidates.emplace_back(flag_value); + } + if (const char* env = std::getenv("PSEUDO_RT_LIB")) { + candidates.emplace_back(env); + } + std::error_code ec; + fs::path exe = fs::weakly_canonical(fs::path(argv0), ec); + if (!ec && exe.has_parent_path()) { + candidates.push_back(exe.parent_path() / "build" / "libpseudort.a"); + } + candidates.push_back(fs::current_path() / "build" / "libpseudort.a"); + + for (const auto& candidate : candidates) { + if (fs::exists(candidate, ec) && fs::is_regular_file(candidate, ec)) { + return candidate.string(); + } + } + return ""; +} + +std::string shell_quote(const std::string& text) { + std::string quoted = "'"; + for (char c : text) { + if (c == '\'') { + quoted += "'\\''"; + } else { + quoted += c; + } + } + quoted += "'"; + return quoted; +} + +} // namespace + +int main(int argc, char** argv) { + std::string input_path, output_path, runtime_lib_flag; + bool emit_llvm = false; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "-o" && i + 1 < argc) { + output_path = argv[++i]; + } else if (arg == "--emit-llvm") { + emit_llvm = true; + } else if (arg == "--runtime-lib" && i + 1 < argc) { + runtime_lib_flag = argv[++i]; + } else if (arg == "-h" || arg == "--help") { + usage(); + return 0; + } else if (!arg.empty() && arg[0] == '-') { + std::cout << "unknown option: " << arg << "\n"; + usage(); + return 1; + } else if (input_path.empty()) { + input_path = arg; + } else { + usage(); + return 1; + } + } + if (input_path.empty()) { + usage(); + return 1; + } + if (output_path.empty()) { + output_path = fs::path(input_path).stem().string(); + if (output_path.empty()) { + output_path = "a.out"; + } + } + + std::ifstream input(input_path); + if (!input) { + std::cout << "cannot open " << input_path << "\n"; + return 1; + } + std::string code, line; + while (std::getline(input, line)) { + code += line + "\n"; + } + + ImportState import_state; + std::string expanded_text, import_error; + if (!expand_imports(input_path, code, import_state, expanded_text, import_error)) { + std::cout << import_error << "\n"; + return 1; + } + + Lexer lexer(input_path, expanded_text); + TokenList tokens = lexer.make_tokens(); + for (const auto& tok : tokens) { + if (tok->get_type() == TOKEN_ERROR) { + std::cout << "Error: " << tok->get_value() << ", line " << tok->get_pos().line + 1 + << ", column: " << tok->get_pos().column << "\n"; + std::cout << error_marker(expanded_text, tok->get_pos(), tok->get_pos()); + return 1; + } + } + + Parser parser(tokens); + NodeList ast = parser.parse(); + for (const auto& node : ast) { + if (node->get_type() == NODE_ERROR) { + std::shared_ptr err_tok = node->get_tok(); + if (err_tok) { + std::cout << "Error: " << err_tok->get_value() << ", line " + << err_tok->get_pos().line + 1 + << ", column: " << err_tok->get_pos().column << "\n"; + std::cout << error_marker(expanded_text, err_tok->get_pos(), err_tok->get_pos()); + } else { + std::cout << "Error: " << node->get_node() << "\n"; + } + return 1; + } + } + + llvm::LLVMContext context; + llvm::Module module(input_path, context); + std::vector compile_errors; + if (!Compiler::compile(ast, module, compile_errors)) { + for (const auto& message : compile_errors) { + std::cout << message << "\n"; + } + return 1; + } + + if (emit_llvm) { + module.print(llvm::outs(), nullptr); + return 0; + } + + if (llvm::verifyModule(module, &llvm::errs())) { + std::cout << "internal error: generated module is invalid\n"; + return 1; + } + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + + std::string triple_name = llvm::sys::getDefaultTargetTriple(); + std::string lookup_error; + const llvm::Target* target = llvm::TargetRegistry::lookupTarget(triple_name, lookup_error); + if (target == nullptr) { + std::cout << "target lookup failed: " << lookup_error << "\n"; + return 1; + } + llvm::TargetOptions target_options; + llvm::TargetMachine* machine = target->createTargetMachine( + llvm::Triple(triple_name), "generic", "", target_options, llvm::Reloc::PIC_); + module.setDataLayout(machine->createDataLayout()); + module.setTargetTriple(llvm::Triple(triple_name)); + + // Standard O2 pipeline. + llvm::LoopAnalysisManager lam; + llvm::FunctionAnalysisManager fam; + llvm::CGSCCAnalysisManager cgam; + llvm::ModuleAnalysisManager mam; + llvm::PassBuilder pass_builder(machine); + pass_builder.registerModuleAnalyses(mam); + pass_builder.registerCGSCCAnalyses(cgam); + pass_builder.registerFunctionAnalyses(fam); + pass_builder.registerLoopAnalyses(lam); + pass_builder.crossRegisterProxies(lam, fam, cgam, mam); + llvm::ModulePassManager mpm = + pass_builder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O2); + mpm.run(module, mam); + + fs::path object_path = + fs::temp_directory_path() / ("pseudoc-" + std::to_string(::getpid()) + ".o"); + { + std::error_code ec; + llvm::raw_fd_ostream object_stream(object_path.string(), ec, llvm::sys::fs::OF_None); + if (ec) { + std::cout << "cannot write " << object_path.string() << ": " << ec.message() << "\n"; + return 1; + } + llvm::legacy::PassManager emit_pm; + if (machine->addPassesToEmitFile(emit_pm, object_stream, nullptr, + llvm::CodeGenFileType::ObjectFile)) { + std::cout << "target cannot emit object files\n"; + return 1; + } + emit_pm.run(module); + } + + std::string runtime_lib = find_runtime_lib(runtime_lib_flag, argv[0]); + if (runtime_lib.empty()) { + std::cout << "cannot find libpseudort.a (build it with `make runtime`, " + "or pass --runtime-lib)\n"; + fs::remove(object_path); + return 1; + } + + std::string linker = "c++"; + if (const char* env = std::getenv("PSEUDO_LD")) { + linker = env; + } + std::string command = linker + " " + shell_quote(object_path.string()) + " " + + shell_quote(runtime_lib) + " -o " + shell_quote(output_path); + int status = std::system(command.c_str()); + fs::remove(object_path); + if (status != 0) { + std::cout << "link failed: " << command << "\n"; + return 1; + } + return 0; +} diff --git a/src/runtime.cpp b/src/runtime.cpp new file mode 100644 index 0000000..cf3363d --- /dev/null +++ b/src/runtime.cpp @@ -0,0 +1,409 @@ +/// -------------------- +/// Compiled-program runtime +/// -------------------- + +#include "runtime.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "color.h" +#include "interpreter.h" +#include "node.h" +#include "symboltable.h" +#include "value.h" + +namespace { + +// Arena frames keep every runtime-created value alive until its frame is +// released; survivors are owned by symbol tables and containers. +std::vector>> frames; +// Scope stack: scopes.back() is the current symbol table; entry 0 holds the +// globals. Parents follow the caller chain, like the interpreter. +std::vector> scopes; + +[[noreturn]] void rt_fail(const std::shared_ptr& err) { + std::cout << err->get_num() << "\n"; + exit(1); +} + +Value* track(std::shared_ptr value) { + if (value->get_type() == VALUE_ERROR) { + rt_fail(value); + } + frames.back().push_back(value); + return value.get(); +} + +std::shared_ptr ref(Value* v) { return v->shared_from_this(); } + +SymbolTable& current_scope() { return *scopes.back(); } + +class CompiledAlgoValue : public Value { + public: + CompiledAlgoValue(const std::string& _algo_name, Value* (*_fn)(), + std::vector _arg_names, bool _memoizable) + : Value(VALUE_ALGO), + fn(_fn), + algo_name(_algo_name), + arg_names(std::move(_arg_names)), + memoizable(_memoizable) {} + + std::string get_num() override { return algo_name; } + std::string repr() override { return algo_name; } + + std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) override; + + Value* (*fn)(); + std::string algo_name; + std::vector arg_names; + bool memoizable; + std::unordered_map> memo; +}; + +// Mirrors numeric_cache_key in pseudo.cpp; empty when any argument is not a +// plain number. +std::string numeric_args_key(const ValueList& values) { + std::string key; + for (const auto& value : values) { + if (value->get_type() != VALUE_INT && value->get_type() != VALUE_FLOAT) return ""; + key += value->get_type(); + key += ':'; + key += value->get_num(); + key += '|'; + } + return key; +} + +std::shared_ptr make_compiled_algo(const char* name, Value* (*fn)(), + const char* const* arg_names, int64_t nargs, + int64_t memoizable) { + std::vector names; + names.reserve(nargs); + for (int64_t i = 0; i < nargs; ++i) { + names.emplace_back(arg_names[i]); + } + return std::make_shared(name, fn, std::move(names), memoizable != 0); +} + +void run_scope_destructors(SymbolTable& scope) { + if (!scope.has_instances()) { + return; + } + for (const auto& [name, val] : scope.get_symbols()) { + (void)name; + if (val->get_type() != VALUE_INSTANCE || val.use_count() != 1) { + continue; + } + InstanceValue* inst = dynamic_cast(val.get()); + std::shared_ptr self_ptr = val; + std::shared_ptr dtor = inst->get_member("destructor", self_ptr); + if (dtor->get_type() == VALUE_ALGO) { + std::shared_ptr ret = dtor->execute({}, scope.get_parent()); + if (ret->get_type() == VALUE_ERROR) { + rt_fail(ret); + } + } + } +} + +std::shared_ptr call_compiled(CompiledAlgoValue* algo, const ValueList& args, + SymbolTable* parent = nullptr) { + if (args.size() < algo->arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too few arguments" RESET); + } + if (args.size() > algo->arg_names.size()) { + return std::make_shared( + VALUE_ERROR, Color(0xFF, 0x39, 0x6E).get() + "Too many arguments" RESET); + } + + std::string memo_key; + if (algo->memoizable) { + memo_key = numeric_args_key(args); + if (!memo_key.empty()) { + auto cached = algo->memo.find(memo_key); + if (cached != algo->memo.end()) { + return cached->second; + } + } + } + + int64_t mark = rt_frame_mark(); + rt_frame_push(); + scopes.push_back(std::make_unique(parent != nullptr ? parent : ¤t_scope())); + for (size_t i = 0; i < args.size(); ++i) { + scopes.back()->set(algo->arg_names[i], args[i]); + } + Value* result = algo->fn(); + std::shared_ptr kept = ref(result); + rt_frame_release(mark); + run_scope_destructors(*scopes.back()); + scopes.pop_back(); + if (!memo_key.empty() && (kept->get_type() == VALUE_INT || kept->get_type() == VALUE_FLOAT)) { + algo->memo[memo_key] = kept; + } + return kept; +} + +std::shared_ptr CompiledAlgoValue::execute(const NodeList& args, SymbolTable* parent) { + SymbolTable sym(parent); + Interpreter interpreter(sym); + ValueList evaluated; + evaluated.reserve(args.size()); + for (const auto& arg : args) { + std::shared_ptr v = interpreter.visit(arg); + if (v->get_type() == VALUE_ERROR) { + return v; + } + evaluated.push_back(v); + } + return call_compiled(this, evaluated, &sym); +} + +} // namespace + +extern "C" { + +void rt_init() { + frames.emplace_back(); + scopes.push_back(std::make_unique()); +} + +Value* rt_make_int(int64_t v) { return track(std::make_shared>(VALUE_INT, v)); } + +Value* rt_make_float(double v) { + return track(std::make_shared>(VALUE_FLOAT, v)); +} + +Value* rt_make_string(const char* s) { + return track(std::make_shared>(VALUE_STRING, std::string(s))); +} + +Value* rt_make_none() { return track(std::make_shared()); } + +Value* rt_array_new() { return track(std::make_shared(ValueList(0))); } + +void rt_array_push(Value* arr, Value* v) { dynamic_cast(arr)->push_back(ref(v)); } + +Value* rt_get_var(const char* name) { return track(current_scope().get(name)); } + +Value* rt_set_var(const char* name, Value* v) { + current_scope().set(name, ref(v)); + return track(current_scope().get(name)); +} + +Value* rt_bin_op(int64_t op, Value* a, Value* b) { + std::shared_ptr lhs = ref(a), rhs = ref(b); + switch (op) { + case RT_OP_ADD: + return track(lhs + rhs); + case RT_OP_SUB: + return track(lhs - rhs); + case RT_OP_MUL: + return track(lhs * rhs); + case RT_OP_DIV: + return track(lhs / rhs); + case RT_OP_MOD: + return track(lhs % rhs); + case RT_OP_POW: + return track(pow(lhs, rhs)); // NOLINT(misc-include-cleaner): value.h overload + case RT_OP_EQUAL: + return track(lhs == rhs); + case RT_OP_NEQ: + return track(lhs != rhs); + case RT_OP_LESS: + return track(lhs < rhs); + case RT_OP_GREATER: + return track(lhs > rhs); + case RT_OP_LEQ: + return track(lhs <= rhs); + case RT_OP_GEQ: + return track(lhs >= rhs); + default: + rt_fail(std::make_shared(VALUE_ERROR, "Not a binary op\n")); + } +} + +Value* rt_unary_op(int64_t op, Value* a) { + std::shared_ptr operand = ref(a); + switch (op) { + case RT_OP_UPLUS: + return track(operand); + case RT_OP_UNEG: + return track(-operand); + case RT_OP_UNOT: + return track(!operand); + default: + rt_fail(std::make_shared(VALUE_ERROR, "Not an unary op\n")); + } +} + +Value* rt_bool(Value* v) { return rt_make_int(v->as_int() != 0); } + +int64_t rt_cond_eq1(Value* v) { return std::stoll(v->get_num()) == 1; } + +int64_t rt_as_int(Value* v) { return v->as_int(); } + +int64_t rt_for_cond(Value* i, Value* end, Value* step) { + if (step->as_double() > 0) { + if (i->get_type() == VALUE_FLOAT || end->get_type() == VALUE_FLOAT) + return i->as_double() <= end->as_double(); + return i->as_int() <= end->as_int(); + } + if (i->get_type() == VALUE_FLOAT || end->get_type() == VALUE_FLOAT) + return i->as_double() >= end->as_double(); + return i->as_int() >= end->as_int(); +} + +void rt_for_step_check(Value* step) { + if (step->as_double() == 0) { + rt_fail(std::make_shared(VALUE_ERROR, "Infinite for loop\n")); + } +} + +Value* rt_index(Value* obj, Value* idx) { + std::shared_ptr container = ref(obj), index = ref(idx); + if (container->get_type() == VALUE_STRING) { + std::string str = container->as_string(); + int p = index->as_int(); + if (1 <= p && p <= static_cast(str.size())) { + return track(std::make_shared>(VALUE_STRING, + std::string(1, str[p - 1]))); + } + return track(std::make_shared( + VALUE_ERROR, "Index out of range, size: " + std::to_string(str.size()) + + ", position: " + std::to_string(p))); + } + if (container->get_type() == VALUE_HASH_TABLE) { + return track(dynamic_cast(container.get())->get(index)); + } + if (container->get_type() != VALUE_ARRAY) { + return track(std::make_shared( + VALUE_ERROR, "Access can only apply on array, find " + container->get_type() + "\n")); + } + return track(dynamic_cast(container.get())->operator[](index->as_int())); +} + +Value* rt_index_assign(Value* obj, Value* idx, Value* v) { + std::shared_ptr container = ref(obj), index = ref(idx), value = ref(v); + if (container->get_type() == VALUE_HASH_TABLE) { + return track(dynamic_cast(container.get())->set(index, value)); + } + if (container->get_type() != VALUE_ARRAY) { + return track(std::make_shared( + VALUE_ERROR, "Access can only apply on array or object member\n")); + } + // Matches visit_array_assign: an out-of-range index assigns into the + // array's error slot and is effectively ignored. + dynamic_cast(container.get())->operator[](index->as_int()) = value; + return track(value); +} + +Value* rt_member_access(Value* obj, const char* name) { + std::shared_ptr object = ref(obj); + if (object->get_type() == VALUE_ARRAY || object->get_type() == VALUE_STRING || + object->get_type() == VALUE_HASH_TABLE) { + return track(std::make_shared(object, name)); + } + if (object->get_type() == VALUE_INSTANCE) { + return track(dynamic_cast(object.get())->get_member(name, object)); + } + return track(std::make_shared( + VALUE_ERROR, object->get_num() + " has no member " + std::string(name) + "\n")); +} + +Value* rt_member_assign(Value* obj, const char* name, Value* v) { + std::shared_ptr object = ref(obj), value = ref(v); + if (object->get_type() == VALUE_INSTANCE) { + dynamic_cast(object.get())->set_member(name, value); + return track(value); + } + return track(std::make_shared( + VALUE_ERROR, "Assignment to member only supported for Struct Instances\n")); +} + +Value* rt_make_algo(const char* name, Value* (*fn)(), const char* const* arg_names, int64_t nargs, + int64_t memoizable) { + return track(make_compiled_algo(name, fn, arg_names, nargs, memoizable)); +} + +Value* rt_define_algo(const char* name, Value* (*fn)(), const char* const* arg_names, int64_t nargs, + int64_t memoizable) { + std::shared_ptr algo = make_compiled_algo(name, fn, arg_names, nargs, memoizable); + current_scope().set(name, algo); + return track(algo); +} + +Value* rt_define_struct(const char* name, const char* const* member_names, int64_t nmembers, + const char* const* method_names, Value** methods, int64_t nmethods) { + std::vector members; + members.reserve(nmembers); + for (int64_t i = 0; i < nmembers; ++i) { + members.emplace_back(member_names[i]); + } + + std::map> method_map; + for (int64_t i = 0; i < nmethods; ++i) { + method_map[method_names[i]] = ref(methods[i]); + } + + std::shared_ptr struct_value = std::make_shared(name, members, method_map); + current_scope().set(name, struct_value); + return track(struct_value); +} + +Value* rt_struct_add_method(const char* struct_name, const char* method_name, Value* method) { + std::shared_ptr struct_value = current_scope().get(struct_name); + if (struct_value->get_type() == VALUE_STRUCT) { + dynamic_cast(struct_value.get())->methods[method_name] = ref(method); + } + return track(ref(method)); +} + +Value* rt_call(Value* callee, Value** argv, int64_t argc) { + ValueList args; + args.reserve(argc); + for (int64_t i = 0; i < argc; ++i) { + args.push_back(ref(argv[i])); + } + + if (auto* compiled = dynamic_cast(callee)) { + return track(call_compiled(compiled, args)); + } + + // Builtins, bound methods, and other interpreter-executed callees: hand + // over the already-evaluated arguments as precomputed AST nodes. + NodeList arg_nodes; + arg_nodes.reserve(argc); + for (const auto& arg : args) { + arg_nodes.push_back(std::make_shared(arg)); + } + return track(callee->execute(arg_nodes, ¤t_scope())); +} + +int64_t rt_frame_mark() { return static_cast(frames.size()); } + +void rt_frame_push() { frames.emplace_back(); } + +void rt_frame_release(int64_t mark) { + while (static_cast(frames.size()) > mark) { + frames.pop_back(); + } +} + +void rt_loop_keep(int64_t frame_index, Value* v) { + std::shared_ptr kept = ref(v); + frames[frame_index].clear(); + frames[frame_index].push_back(kept); +} + +} // extern "C" diff --git a/src/runtime.h b/src/runtime.h new file mode 100644 index 0000000..b2abacc --- /dev/null +++ b/src/runtime.h @@ -0,0 +1,95 @@ +/// -------------------- +/// Compiled-program runtime +/// -------------------- +/// +/// extern "C" entry points called by code generated by the LLVM compiler +/// (src/compiler.cpp). All Value* parameters and results are raw pointers +/// whose ownership is tracked by an arena frame stack inside the runtime; +/// anything that must outlive a frame (variables, container elements) is +/// kept alive by the SymbolTable / container shared_ptrs. +/// +/// Any runtime error (a VALUE_ERROR result) prints the same message the +/// interpreter prints at top level and exits with status 1. + +#ifndef RUNTIME_H +#define RUNTIME_H + +#include + +class Value; + +// Operator codes shared between the compiler and the runtime. +enum RtBinOpCode : int64_t { + RT_OP_ADD = 0, + RT_OP_SUB, + RT_OP_MUL, + RT_OP_DIV, + RT_OP_MOD, + RT_OP_POW, + RT_OP_EQUAL, + RT_OP_NEQ, + RT_OP_LESS, + RT_OP_GREATER, + RT_OP_LEQ, + RT_OP_GEQ, +}; + +enum RtUnaryOpCode : int64_t { + RT_OP_UPLUS = 0, + RT_OP_UNEG, + RT_OP_UNOT, +}; + +extern "C" { + +void rt_init(); + +Value* rt_make_int(int64_t v); +Value* rt_make_float(double v); +Value* rt_make_string(const char* s); +Value* rt_make_none(); +Value* rt_array_new(); +void rt_array_push(Value* arr, Value* v); + +Value* rt_get_var(const char* name); +Value* rt_set_var(const char* name, Value* v); + +Value* rt_bin_op(int64_t op, Value* a, Value* b); +Value* rt_unary_op(int64_t op, Value* a); +// Int 0/1 value from as_int() != 0 (used by short-circuit and/or). +Value* rt_bool(Value* v); + +// stoll(get_num()) == 1, the interpreter's `if` condition. +int64_t rt_cond_eq1(Value* v); +// as_int(), the interpreter's `while` / `repeat` condition. +int64_t rt_as_int(Value* v); +// Float-aware inclusive bound check matching Interpreter::visit_for. +int64_t rt_for_cond(Value* i, Value* end, Value* step); +// Errors out (like the interpreter) when step == 0. +void rt_for_step_check(Value* step); + +Value* rt_index(Value* obj, Value* idx); +Value* rt_index_assign(Value* obj, Value* idx, Value* v); +Value* rt_member_access(Value* obj, const char* name); +Value* rt_member_assign(Value* obj, const char* name, Value* v); + +// `memoizable` enables the same by-argument result caching the interpreter +// applies to recursive pure-numeric algorithms. +Value* rt_make_algo(const char* name, Value* (*fn)(), const char* const* arg_names, int64_t nargs, + int64_t memoizable); +Value* rt_define_algo(const char* name, Value* (*fn)(), const char* const* arg_names, int64_t nargs, + int64_t memoizable); +Value* rt_define_struct(const char* name, const char* const* member_names, int64_t nmembers, + const char* const* method_names, Value** methods, int64_t nmethods); +Value* rt_struct_add_method(const char* struct_name, const char* method_name, Value* method); +Value* rt_call(Value* callee, Value** argv, int64_t argc); + +int64_t rt_frame_mark(); +void rt_frame_push(); +void rt_frame_release(int64_t mark); +// Replaces the contents of frame `frame_index` with just `v`: keeps the +// current loop variable alive for one iteration without per-iteration growth. +void rt_loop_keep(int64_t frame_index, Value* v); +} + +#endif diff --git a/src/value.h b/src/value.h index 4a786a6..fab534a 100644 --- a/src/value.h +++ b/src/value.h @@ -5,12 +5,13 @@ #ifndef VALUE_H #define VALUE_H -#include -#include -#include +#include #include +#include +#include #include -#include +#include + #include "node.h" const std::string VALUE_NONE{"NONE"}; @@ -27,41 +28,37 @@ const std::string VALUE_RETURN{"Return"}; const std::string VALUE_BREAK{"Break"}; const std::string VALUE_CONTINUE{"Continue"}; -const std::map REVERSE_ESCAPE_CHAR { - {'\n', 'n'}, {'\r', 'r'}, - {'\b', 'b'}, {'\"', '\"'}, - {'\'', '\''}, {'\\', '\\'}, - {'\t', 't'} -}; +const std::map REVERSE_ESCAPE_CHAR{ + {'\n', 'n'}, {'\r', 'r'}, {'\b', 'b'}, {'\"', '\"'}, {'\'', '\''}, {'\\', '\\'}, {'\t', 't'}}; class SymbolTable; class Interpreter; -class Value { -public: - Value(const std::string& _type = VALUE_NONE) - : type(_type) {} - virtual std::string get_num() { return type;} - virtual std::string repr() { return type;} - virtual std::string get_type(){ return type;} +class Value : public std::enable_shared_from_this { + public: + Value(const std::string& _type = VALUE_NONE) : type(_type) {} + virtual std::string get_num() { return type; } + virtual std::string repr() { return type; } + virtual std::string get_type() { return type; } virtual int64_t as_int(); virtual double as_double(); virtual std::string as_string(); virtual bool append_string(const std::string&) { return false; } - virtual std::shared_ptr execute(const NodeList& args = {}, SymbolTable *parent = nullptr) - { return std::make_shared();}; - friend std::ostream& operator<<(std::ostream &out, Value &token); - -protected: + virtual std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) { + return std::make_shared(); + }; + friend std::ostream& operator<<(std::ostream& out, Value& token); + + protected: std::string type; }; using ValueList = std::vector>; -template -class TypedValue: public Value { -public: - TypedValue(const std::string& _type, const T &_value) - : Value(_type), value(_value) {} +template +class TypedValue : public Value { + public: + TypedValue(const std::string& _type, const T& _value) : Value(_type), value(_value) {} std::string get_num() override; std::string repr() override; int64_t as_int() override; @@ -69,50 +66,53 @@ class TypedValue: public Value { std::string as_string() override; bool append_string(const std::string&) override; -protected: + protected: T value; }; using ErrorValue = TypedValue; -class BaseAlgoValue: public Value { -public: - BaseAlgoValue(const std::string &_algo_name, std::shared_ptr _value) +class BaseAlgoValue : public Value { + public: + BaseAlgoValue(const std::string& _algo_name, std::shared_ptr _value) : Value(VALUE_ALGO), value(_value), algo_name(_algo_name) { for (const auto& tok : value->get_toks()) { arg_names.push_back(tok->get_value()); } } - std::string get_num() override { return algo_name;} + std::string get_num() override { return algo_name; } virtual std::shared_ptr set_args(const NodeList&, SymbolTable&, Interpreter&); - std::string repr() override { return get_num();} + std::string repr() override { return get_num(); } -protected: + protected: std::string algo_name; std::shared_ptr value; std::vector arg_names; }; -class AlgoValue: public BaseAlgoValue { -public: - AlgoValue(const std::string &_algo_name, std::shared_ptr _value) +class AlgoValue : public BaseAlgoValue { + public: + AlgoValue(const std::string& _algo_name, std::shared_ptr _value) : BaseAlgoValue(_algo_name, _value) {} - std::string get_num() override { return algo_name;} - std::string repr() override { return get_num();} - std::shared_ptr execute(const NodeList& args = {}, SymbolTable *parent = nullptr) override; + std::string get_num() override { return algo_name; } + std::string repr() override { return get_num(); } + std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) override; friend class BoundMethodValue; // Expose value for friends/derived or public use if needed for method binding std::shared_ptr get_node_ptr() { return value; } const std::vector& get_arg_names() const { return arg_names; } -protected: + + protected: }; -class BuiltinAlgoValue: public BaseAlgoValue { -public: - BuiltinAlgoValue(const std::string &_algo_name, std::shared_ptr _value) +class BuiltinAlgoValue : public BaseAlgoValue { + public: + BuiltinAlgoValue(const std::string& _algo_name, std::shared_ptr _value) : BaseAlgoValue(_algo_name, _value) {} - std::string get_num() override { return algo_name;} - std::shared_ptr execute(const NodeList& args = {}, SymbolTable *parent = nullptr) override; + std::string get_num() override { return algo_name; } + std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) override; std::shared_ptr execute_print(const std::string&); std::shared_ptr execute_read(); std::shared_ptr execute_read_line(); @@ -120,36 +120,40 @@ class BuiltinAlgoValue: public BaseAlgoValue { std::shared_ptr execute_int(const std::string&); std::shared_ptr execute_float(const std::string&); std::shared_ptr execute_string(const std::string&); - std::string repr() override { return get_num();} -protected: + std::string repr() override { return get_num(); } + + protected: }; class BoundMethodValue : public Value { -public: + public: BoundMethodValue(std::shared_ptr _obj, std::string _method_name) : Value(VALUE_ALGO), obj(_obj), method_name(_method_name) {} - std::shared_ptr execute(const NodeList& args = {}, SymbolTable *parent = nullptr) override; + std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) override; std::string get_num() override { return method_name; } std::string repr() override { return ""; } -protected: + + protected: std::shared_ptr obj; std::string method_name; }; -class ArrayValue: public Value { -public: - ArrayValue(ValueList _value) - : Value(VALUE_ARRAY), value(_value) {} +class ArrayValue : public Value { + public: + ArrayValue(ValueList _value) : Value(VALUE_ARRAY), value(_value) {} std::string get_num() override; std::shared_ptr& operator[](int p); void push_back(std::shared_ptr); std::shared_ptr insert(int p, std::shared_ptr); std::shared_ptr remove(int p); std::shared_ptr pop_back(); - bool empty() const { return value.empty();} - std::shared_ptr& size() { return sz = std::make_shared>(VALUE_INT, value.size());}; - std::shared_ptr& back() { return value.back();}; - std::string repr() override { return get_num();} + bool empty() const { return value.empty(); } + std::shared_ptr& size() { + return sz = std::make_shared>(VALUE_INT, value.size()); + }; + std::shared_ptr& back() { return value.back(); }; + std::string repr() override { return get_num(); } void resize(int new_size) { if (new_size < 0) { @@ -161,20 +165,20 @@ class ArrayValue: public Value { size_t old_size = value.size(); value.resize(new_size); for (size_t i = old_size; i < static_cast(new_size); ++i) { - value[i] = std::make_shared(); // Fill with default Value + value[i] = std::make_shared(); // Fill with default Value } } } std::shared_ptr sz, error; -protected: + + protected: ValueList value; }; -class HashTableValue: public Value { -public: - HashTableValue() - : Value(VALUE_HASH_TABLE) {} +class HashTableValue : public Value { + public: + HashTableValue() : Value(VALUE_HASH_TABLE) {} std::string get_num() override; std::string repr() override { return get_num(); } std::shared_ptr get(std::shared_ptr key); @@ -186,7 +190,7 @@ class HashTableValue: public Value { std::shared_ptr values() const; void clear(); -protected: + protected: struct Entry { std::shared_ptr key; std::shared_ptr value; @@ -219,8 +223,9 @@ std::shared_ptr operator-(std::shared_ptr); std::shared_ptr operator!(std::shared_ptr); class StructValue : public Value { -public: - StructValue(const std::string& _name, const std::vector& _members, const std::map>& _methods) + public: + StructValue(const std::string& _name, const std::vector& _members, + const std::map>& _methods) : Value(VALUE_STRUCT), name(_name), members(_members), methods(_methods) {} std::string get_num() override { return name; } @@ -229,18 +234,20 @@ class StructValue : public Value { std::vector members; std::map> methods; std::string name; - std::shared_ptr execute(const NodeList& args = {}, SymbolTable *parent = nullptr) override; + std::shared_ptr execute(const NodeList& args = {}, + SymbolTable* parent = nullptr) override; }; class InstanceValue : public Value { -public: + public: InstanceValue(std::shared_ptr _struct_def) : Value(VALUE_INSTANCE), struct_def(_struct_def) {} std::string get_num() override { return struct_def->name + " Instance"; } std::string repr() override { return "name + ">"; } - std::shared_ptr get_member(const std::string& name, std::shared_ptr self = nullptr); + std::shared_ptr get_member(const std::string& name, + std::shared_ptr self = nullptr); void set_member(const std::string& name, std::shared_ptr val); std::shared_ptr struct_def; @@ -248,20 +255,32 @@ class InstanceValue : public Value { }; class ReturnValue : public Value { -public: - ReturnValue(std::shared_ptr _value) - : Value(VALUE_RETURN), value(_value) {} + public: + ReturnValue(std::shared_ptr _value) : Value(VALUE_RETURN), value(_value) {} std::string get_num() override { return value->get_num(); } std::string repr() override { return value->repr(); } std::shared_ptr get_value() { return value; } -protected: + + protected: std::shared_ptr value; }; class ControlValue : public Value { -public: - ControlValue(const std::string& _type) - : Value(_type) {} + public: + ControlValue(const std::string& _type) : Value(_type) {} +}; + +// Wraps an already-evaluated value as an AST node so compiled code can reuse +// interpreter execution paths that expect argument nodes (builtins, methods). +class PrecomputedNode : public Node { + public: + explicit PrecomputedNode(std::shared_ptr _value) : value(_value) {} + std::string get_node() override { return "PRECOMPUTED"; } + std::string get_type() override { return NODE_PRECOMPUTED; } + std::shared_ptr get_value() { return value; } + + protected: + std::shared_ptr value; }; template class TypedValue; diff --git a/test/compiler/basics.ps b/test/compiler/basics.ps new file mode 100644 index 0000000..582ea99 Binary files /dev/null and b/test/compiler/basics.ps differ diff --git a/test/compiler/collections.ps b/test/compiler/collections.ps new file mode 100644 index 0000000..341f18e Binary files /dev/null and b/test/compiler/collections.ps differ diff --git a/test/compiler/control_flow.ps b/test/compiler/control_flow.ps new file mode 100644 index 0000000..387ff92 Binary files /dev/null and b/test/compiler/control_flow.ps differ diff --git a/test/compiler/errors.ps b/test/compiler/errors.ps new file mode 100644 index 0000000..0728c64 Binary files /dev/null and b/test/compiler/errors.ps differ diff --git a/test/compiler/functions.ps b/test/compiler/functions.ps new file mode 100644 index 0000000..3c2147c Binary files /dev/null and b/test/compiler/functions.ps differ diff --git a/test/compiler_tests.sh b/test/compiler_tests.sh new file mode 100755 index 0000000..64dbd67 --- /dev/null +++ b/test/compiler_tests.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Differential tests: compiled binaries must produce the same stdout as the +# interpreter. Run from the repository root after `make` and `make compiler`. +set -u + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PSEUDO="$ROOT/pseudo" +PSEUDOC="$ROOT/pseudoc" +WORKDIR="$(mktemp -d)" +trap 'rm -rf "$WORKDIR"' EXIT + +if [[ ! -x "$PSEUDO" ]]; then + echo "missing $PSEUDO (run make first)" >&2 + exit 1 +fi +if [[ ! -x "$PSEUDOC" ]]; then + echo "missing $PSEUDOC (run make compiler first)" >&2 + exit 1 +fi + +FILES=( + "$ROOT"/test/compiler/*.ps + "$ROOT/test/test_fib.ps" + "$ROOT/test/test_repeat.ps" + "$ROOT/test/test_array_methods.ps" + "$ROOT/test/test_string_index.ps" + "$ROOT/test/test_struct.ps" +) + +failures=0 +for src in "${FILES[@]}"; do + name="$(basename "$src" .ps)" + expected="$WORKDIR/$name.expected" + actual="$WORKDIR/$name.actual" + bin="$WORKDIR/$name.bin" + + "$PSEUDO" "$src" > "$expected" 2>&1 + if ! "$PSEUDOC" "$src" -o "$bin" > "$WORKDIR/$name.compile" 2>&1; then + echo "FAIL $name: compilation failed" + sed 's/^/ /' "$WORKDIR/$name.compile" + failures=$((failures + 1)) + continue + fi + "$bin" > "$actual" 2>&1 + if ! diff -u "$expected" "$actual" > "$WORKDIR/$name.diff"; then + echo "FAIL $name: output mismatch" + sed 's/^/ /' "$WORKDIR/$name.diff" + failures=$((failures + 1)) + else + echo "PASS $name" + fi +done + +if [[ $failures -ne 0 ]]; then + echo "$failures test(s) failed" + exit 1 +fi +echo "all compiler tests passed"