diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..897162c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libgtest-dev g++ + - name: Build and test + run: cd src && make && ./test + - name: Smoke compile C API + run: | + echo '#include "interpreter_c.h"' > /tmp/ir_c_smoke.c + gcc -c -x c /tmp/ir_c_smoke.c -Iinclude + g++ -std=c++17 -O2 -Wall -c src/interpreter_c.cpp -I. -o /tmp/ir_c_api.o diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..07ba006 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +image: ubuntu:latest + +before_script: + - apt-get update -qq + - apt-get install -y -qq libgtest-dev g++ + +test: + script: + - cd src && make && ./test + - echo '#include "interpreter_c.h"' > /tmp/ir_c_smoke.c + - gcc -c -x c /tmp/ir_c_smoke.c -Iinclude + - g++ -std=c++17 -O2 -Wall -c src/interpreter_c.cpp -I. -o /tmp/ir_c_api.o diff --git a/README.md b/README.md index cd72949..f20f7f9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ss << "$a = 5;" " }" "}" "$b"; -string res = ir.cmd(ss.str()); // 9 +string res = cmdResultToString(ir.cmd(ss.str())); // 9 ``` ### User functions outside the script @@ -132,20 +132,20 @@ l_myLabel1: $a = 4; ``` script = "e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one"; -res = ir.cmd(script); // 7 +res = cmdResultToString(ir.cmd(script)); // 7 script = "$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three"; -res = ir.cmd(script); // 22 +res = cmdResultToString(ir.cmd(script)); // 22 ``` ### Containers from [base lib](https://github.com/Tyill/interpreter/blob/main/include/base_library/containers.h) ``` script = "a = Vector{1,2,3}; a.push_back(4); a.push_back(5); while($v : a) print($v);"; -res = ir.cmd(script); // 1 2 3 4 5 +res = cmdResultToString(ir.cmd(script)); // 1 2 3 4 5 script = "b = Map{myKeyOne: myValueOne}; b.insert(myKeyTwo, myValueTwo); b.at(myKeyTwo)"; -res = ir.cmd(script); // myValueTwo +res = cmdResultToString(ir.cmd(script)); // myValueTwo ``` | Vector | Map | @@ -165,7 +165,7 @@ res = ir.cmd(script); // myValueTwo ``` script = "file1 = File{\"main.cpp\"}; file2 = File{\"mainCopy.txt\"}; \ if (file1.exist()) { $data = file1.read(); file2.write($data); }"; -res = ir.cmd(script); +res = cmdResultToString(ir.cmd(script)); ``` | File | Dir | @@ -179,10 +179,10 @@ res = ir.cmd(script); ``` script = "a: int = 123; type(a)"; -res = ir.cmd(script); // int +res = cmdResultToString(ir.cmd(script)); // int script = "b: str = "abc"; type(b)"; -res = ir.cmd(script); // str +res = cmdResultToString(ir.cmd(script)); // str ``` ### Example of use @@ -237,49 +237,49 @@ int main(int argc, char* argv[]) }); string script = "$a = 5; $b = 2; while ($a > 1){ $a = $a - 1; $b = summ($b, $a); if ($a < 4){ break;} } $b;"; - string res = ir.cmd(script); // 9 + string res = cmdResultToString(ir.cmd(script)); // 9 script = "$a = 5; $b = 2; $c = summ($a, ($a + ($a * ($b + $a))), summ(5)); $c;"; - res = ir.cmd(script); // 50 + res = cmdResultToString(ir.cmd(script)); // 50 script = "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a[2]"; - res = ir.cmd(script); // 3 + res = cmdResultToString(ir.cmd(script)); // 3 script = "b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b[\"myKeyTwo\"]"; - res = ir.cmd(script); // myValueTwo + res = cmdResultToString(ir.cmd(script)); // myValueTwo script = "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); while($v : a) print($v);"; - res = ir.cmd(script); // 1 2 3 + res = cmdResultToString(ir.cmd(script)); // 1 2 3 script = "a = Vector{1 + 2, 2 + 3, 3 + 4}; while($v : a) print($v);"; - res = ir.cmd(script); // 3 5 7 + res = cmdResultToString(ir.cmd(script)); // 3 5 7 script = "$b = 12; c = Map{ one : $b + 5, two : 2}; while($v : c) print($v);"; - res = ir.cmd(script); // one 17 two 2 + res = cmdResultToString(ir.cmd(script)); // one 17 two 2 script = "e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one"; - res = ir.cmd(script); // 7 + res = cmdResultToString(ir.cmd(script)); // 7 script = "$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three"; - res = ir.cmd(script); // 22 + res = cmdResultToString(ir.cmd(script)); // 22 script = "file1 = File{\"main.cpp\"}; file2 = File{\"mainCopy.txt\"}; if (file1.exist()) { $data = file1.read(); file2.write($data); }"; - res = ir.cmd(script); + res = cmdResultToString(ir.cmd(script)); script = "$a = 1; $b = 2; function myFunc{ $a += $b; }; myFunc()"; - res = ir.cmd(script); // 3 + res = cmdResultToString(ir.cmd(script)); // 3 script = "$a = 1; $b = 2; function myFunc{ $a += $b; function myFunc2{ $a += $b; }; myFunc2(); }; myFunc()"; - res = ir.cmd(script); // 5 + res = cmdResultToString(ir.cmd(script)); // 5 script = "$a = 0; function myFunc{ if ($0 > 1) $a = $0 * myFunc($0 - 1); else $a = 1; $a }; myFunc(5)"; - res = ir.cmd(script); // 120 + res = cmdResultToString(ir.cmd(script)); // 120 script = "function myFunc{ $0 += $1; }; myFunc(2, 3)"; - res = ir.cmd(script); // 5 + res = cmdResultToString(ir.cmd(script)); // 5 script = "b: str = \"abc\"; type(b)"; - res = ir.cmd(script); // str + res = cmdResultToString(ir.cmd(script)); // str return 0; } @@ -287,6 +287,13 @@ int main(int argc, char* argv[]) ### [Tests](https://github.com/Tyill/interpreter/blob/main/src/test.cpp) +### Documentation + +Extended guides: [docs/README.md](docs/README.md) + +### Benchmarks + +[docs/benchmarks.md](docs/benchmarks.md) ### License Licensed under an [MIT-2.0]-[license](LICENSE). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..81613ff --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +# Documentation + +English guides for embedding and using the Tiny interpreter library. + +| Document | Description | +|----------|-------------| +| [Getting started](getting-started.md) | Build, minimal host setup, running scripts | +| [Script language](script-language.md) | Syntax: variables, control flow, macros, containers | +| [C++ API](cpp-api.md) | `Interpreter`, `Value`, errors, host functions and operators | +| [Base library](base-library.md) | Optional modules: arithmetic, containers, filesystem, … | +| [Limitations](limitations.md) | Known quirks and edge cases | +| [Copy and containers](copy-and-containers.md) | `Interpreter` copy vs `Map` / `Vector` variable visibility | +| [Benchmarks](benchmarks.md) | Local `make bench` throughput (reference numbers) | + +**Quick links** + +- Core: [`include/interpreter.h`](../include/interpreter.h), [`src/interpreter.cpp`](../src/interpreter.cpp) +- Full sample: [`example/main.cpp`](../example/main.cpp) +- Tests: [`src/test.cpp`](../src/test.cpp) diff --git a/docs/base-library.md b/docs/base-library.md new file mode 100644 index 0000000..d0bb613 --- /dev/null +++ b/docs/base-library.md @@ -0,0 +1,130 @@ +# Base library + +Optional headers under [`include/base_library/`](../include/base_library/) extend the core interpreter with operators and functions. Each class takes `Interpreter&` in its constructor and registers handlers on that instance. + +**Rule:** construct every helper on the **same** `Interpreter` you pass scripts to. Example from [`example/main.cpp`](../example/main.cpp): + +```cpp +Interpreter ir; +InterpreterBaseLib::ArithmeticOperations ir_ao(ir); +InterpreterBaseLib::ComparisonOperations ir_co(ir); +InterpreterBaseLib::Container ir_bc(ir); +InterpreterBaseLib::Types ir_tp(ir); +InterpreterBaseLib::Filesystem ir_fs(ir); +InterpreterBaseLib::Structure ir_st(ir); +``` + +Order among helpers does not matter; all must exist before running scripts that need them. + +## arithmetic_operations.h + +Registers standard arithmetic and compound assignment operators (`+`, `-`, `*`, `/`, `+=`, …) on numeric [`Value`](../include/interpreter.h) types. + +Required for almost all numeric scripts. + +## comparison_operations.h + +Registers comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`). Results are usable in `if` / `while` conditions. + +## containers.h — `Container` + +Enables `Vector` and `Map` in scripts. + +**Construction / literals** + +```text +a = Vector; +a = Vector{ 1, 2, 3 }; +a = Vector{ 1 + 2, 2 + 3 }; + +c = Map; +c = Map{ keyOne : valueOne, keyTwo : 2 }; +``` + +Expressions inside `{ … }` are evaluated in a snapshot of the bound interpreter (see [Copy and containers](copy-and-containers.md) when using copies). + +**Vector methods** + +| Method | Description | +|--------|-------------| +| `push_back(value)` | Append | +| `pop_back()` | Remove last | +| `insert(index, value)` | Insert at index | +| `erase(index)` | Remove at index | +| `size()`, `empty()`, `clear()` | Queries / reset | +| `at(index)` or `[index]` | Access | + +**Map methods** + +| Method | Description | +|--------|-------------| +| `insert(key, value)` | Insert or replace | +| `erase(key)` | Remove key | +| `size()`, `empty()`, `clear()` | Queries / reset | +| `at(key)` or `[key]` | Access | + +**Iteration** + +```text +while ($v : a) print($v); +``` + +For `Map`, printed lines are typically `key` and value (tab-separated in host `print` output). See [Limitations](limitations.md). + +Syntax details: [Script language — Containers](script-language.md#containers). + +## structure.h — `Structure` + +Struct-like objects with dotted fields: + +```text +e = Struct{ one : 5, two : 2 }; +e.one = summ(e.one, e.two); +e.three = e.one + e.two + 3; +``` + +Fields can be added after construction. Init expressions in `{ … }` follow the same snapshot rules as containers. + +## filesystem.h — `Filesystem` + +**File** + +```text +f = File{"path.txt"}; +f.exist(); +$data = f.read(); +f.write($data); +f.remove(); +``` + +**Dir** + +```text +d = Dir{"path"}; +d.exist(); +d.remove(); +``` + +Paths in literals are usually quoted strings. Init bodies can be expressions. + +## types.h — `Types` + +Typed variable declarations and `type()`: + +```text +a: int = 123; +b: str = "abc"; +type(a); // host function returns type name +``` + +Registers `:` for declarations and the `type` function. + +## Linking + +Add `interpreter.cpp` once per binary. Base library is header-only (templates and inline registration in constructors); no extra `.cpp` files. + +## See also + +- [Getting started](getting-started.md) +- [Script language](script-language.md) +- [C++ API](cpp-api.md) diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 0000000..d64e2bf --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,55 @@ +# Benchmarks + +Informal local throughput checks for regression comparison. **Not run in CI.** + +Build and run: + +```bash +cd src && make bench && ./bench +``` + +Source: [`src/bench.cpp`](../src/bench.cpp). The host keeps `ArithmeticOperations`, `ComparisonOperations`, and `Container` alive for the whole run (same lifetime pattern as tests). + +## Scenarios + +| Label | What it measures | +|-------|------------------| +| cmd short loop | `while` + `summ` + `break` (README-style loop) | +| cmd nested expr | Nested `summ` and arithmetic | +| cmd Vector literal | `Vector{1+2, …}` and index | +| cmd Map literal | `Map{one:$b+5, …}` and lookup | +| cmd setMacro #inc | `setMacro("inc", …)` then `#inc` twice | +| cmd (varying script) | Rotating three similar scripts (no AST cache reuse) | +| parseScript+runScript (cached) | Same script text: parse once per call path, then `runScript` | +| copy | `Interpreter` copy constructor only | + +Default iteration counts: **5000** per `cmd` case (1000 for Vector/Map, 500 for varying script). Each case warms up **50** iterations before timing. + +## Reference results + +Snapshot from `g++ -std=c++17 -O2` on Linux x86_64 (single run; **your numbers will differ** by CPU and load): + +``` +interpreter bench (O2, 5000 iterations per case) + + cmd short loop 5000 iter 14.424 ms 346648 ops/s + cmd nested expr 5000 iter 9.555 ms 523299 ops/s + cmd Vector literal 1000 iter 8.718 ms 114700 ops/s + cmd Map literal 1000 iter 8.550 ms 116965 ops/s + cmd setMacro #inc 5000 iter 6.557 ms 762500 ops/s + cmd (varying script) 500 iter 1.529 ms 327061 ops/s + parseScript+runScript (cached) 5000 iter 15.092 ms 331305 ops/s + +copy: 500 Interpreter copies in 1.018 ms (490951 /s) +``` + +### How to read this + +- **ops/s** — how many times per second the scenario completed (one `cmd` or one parse+run pair). +- Use results only to compare **before vs after** a change on the **same** machine and `bench` binary. +- These micro-benchmarks do **not** compare this library to Lua, Python, or other engines. + +## See also + +- [Getting started](getting-started.md) +- [Limitations](limitations.md) diff --git a/docs/copy-and-containers.md b/docs/copy-and-containers.md new file mode 100644 index 0000000..b85a031 --- /dev/null +++ b/docs/copy-and-containers.md @@ -0,0 +1,67 @@ +# Interpreter copy and containers (Map / Vector / Struct) + +## Summary + +**On a copied interpreter, Map/Vector init expressions see the parent’s variables only if those variables were set on the same `ir` instance that `m_intr` points to.** + +In other words: when `cmd()` runs on a copy (`ir2`), expressions inside `Map{ … }` / `Vector{ … }` are evaluated from a snapshot of the **original** interpreter bound in `Container` / `Structure`, not from the copy’s `m_var`. + +## How it works + +Base library code ([`containers.h`](../include/base_library/containers.h), [`structure.h`](../include/base_library/structure.h), [`filesystem.h`](../include/base_library/filesystem.h)) stores a reference at construction: + +```cpp +Container(Interpreter& ir) : m_intr(ir) { … } +``` + +When a container literal is initialized: + +```cpp +Interpreter intrCopy = m_intr; +intrCopy.parseScript(expression_from_braces, err); +value = intrCopy.runScript().first; +``` + +- `m_intr` is always the **same** `Interpreter` passed to the `Container` constructor (in tests and `example/main.cpp`, that is the first `ir`). +- A script may run on a **copy** (`Interpreter ir2 = ir`), but `intrCopy` and `currentEntity()` inside operators still use **`m_intr`**, not `ir2`. + +`Map` / `Vector` storage (`m_mapContr`, `m_vectorContr`) lives on the `Container` object, not inside `Interpreter::Impl`. Copying an interpreter does not duplicate that storage; copies that share one `Container` share the same maps. + +## What works + +| Scenario | Result | +|----------|--------| +| `$b = 12` on **`ir`**, then `ir2 = ir`, then on **`ir2`**: `Map{ one : $b + 5 }` | OK: `m_intr` (`ir`) already has `$b = 12` → `17` | +| `$b = 12` on the **same** `ir`, then `Map{ one : $b + 5 }` on that `ir` | OK (`intrCopy` is the same interpreter) | + +## What does not work (current behavior) + +| Scenario | Result | +|----------|--------| +| `ir2 = ir`, then **only** `ir2.cmd("$b = 12")`, then `Map{ one : $b + 5 }` on `ir2` | `$b` is empty on `m_intr` (`ir`) → wrong or empty result | +| Two copies with different `c = Map{ … }` under the same name `c` | Shared `Container` storage → data can overwrite each other | + +## Diagram + +``` + ir ←── m_intr (fixed reference in Container) + ↑ + │ intrCopy = m_intr → variables read from ir + │ + ir2 ←── cmd() runs here; m_var ($b) may exist only on ir2 +``` + +## Tests + +In [`src/test.cpp`](../src/test.cpp), `interpreterCopyMoveTest`: + +- asserts **intrCopy on a single `ir`** (`$b = 12` → `Map` → `"17"`); +- does **not** claim support for “`$b` only on `ir2` after copy”. + +## See also + +- [Documentation index](README.md) +- [Limitations](limitations.md) +- [Base library — Container](base-library.md#containersh--container) + +This behavior is **documented as current**; no change to container binding is planned unless requirements change. diff --git a/docs/cpp-api.md b/docs/cpp-api.md new file mode 100644 index 0000000..b6ebbd1 --- /dev/null +++ b/docs/cpp-api.md @@ -0,0 +1,147 @@ +# C++ API + +Reference for embedding [`Interpreter`](../include/interpreter.h) from C++. Samples match [`example/main.cpp`](../example/main.cpp). + +## Lifecycle + +```cpp +Interpreter ir; // default construct +Interpreter ir2 = ir; // copy state (variables, macros, operators, …) +Interpreter ir3 = std::move(ir); // move +ir = other; // copy assign +``` + +Copy semantics for containers are special; see [Copy and containers](copy-and-containers.md). + +## Registration + +### Functions + +```cpp +using UserFunction = std::function&)>; + +bool ok = ir.addFunction("summ", [](const std::vector& args) -> Interpreter::Value { + int64_t n = 0; + for (const auto& v : args) n += Interpreter::valueAsInt64(v); + return n; +}); +``` + +Script call: `summ(1, 2, 3);` + +### Operators + +```cpp +using UserOperator = std::function; + +ir.addOperator("+=", [](Interpreter::Value& l, Interpreter::Value& r) -> Interpreter::Value { + // update l, return result + return l; +}, priority); +``` + +Higher `priority` runs earlier when multiple operators can match. Base library operators use priorities documented in [Base library](base-library.md). + +### Attributes + +```cpp +ir.addAttribute("myAttr"); +``` + +Used with reflection / entity metadata (advanced). + +## Execution + +| Method | Purpose | +|--------|---------| +| `CmdResult cmd(std::string script)` | Parse (if needed) + run; returns `{ Value, Error }` | +| `bool parseScript(std::string script, Error& outErr)` | Parse only; cache AST when script text unchanged | +| `std::pair runScript()` | Run after successful `parseScript` | + +```cpp +auto r = ir.cmd("$a = 1;"); +if (Interpreter::hasError(r.second)) { + // r.second.kind == Parse or Runtime + // r.second.position — character index for parse errors + // r.second.message +} +``` + +Helpers: + +| Helper | Role | +|--------|------| +| `cmdResultToString(const CmdResult&)` | String form of result value (empty on error) | +| `hasError(const Error&)` | `true` if `message` non-empty | +| `isParseError(const Error&)` | Parse vs runtime | + +## Variables and macros + +```cpp +ir.setVariable("$x", int64_t{42}); +Interpreter::Value v = ir.variable("$x"); +auto all = ir.allVariables(); + +ir.setMacro("inc", "$a = $a + 1;"); // stored as #inc +ir.setMacro("#twice", "2"); // also valid +// script: #inc or #twice + 3 +``` + +`setMacro` accepts names with or without a leading `#`. + +## Host control + +```cpp +ir.gotoOnLabel("l_myLabel"); // must exist in parsed script +ir.exitFromScript(); // stop current run +ir.runFunction("summ", { int64_t{1}, int64_t{2} }); +``` + +## Value type + +`Interpreter::Value` is a `std::variant` of: + +| Alternative | Use | +|-------------|-----| +| `bool` | Boolean literals `true` / `false` in scripts, conditions, `setVariable` | +| `int64_t` | Integers | +| `double` | Floating point | +| `std::string` | Text, names, mixed results | +| `ControlFlow::Break` / `Continue` | Loop control inside interpreter | + +Common static helpers: + +| Function | Notes | +|----------|--------| +| `valueToString(v)` | Display string (`bool` → `"1"` / `"0"`) | +| `valueFromLiteral("true")` / `"false"` | Parse boolean literals | +| `valueAsInt64(v)` | Coerce numeric types | +| `valueAsString(v)` | String or stringified value | +| `valueIsInteger` / `valueIsNumeric` | Type checks | +| `valueIsTruthy(v)` | Condition semantics | +| `valueFromLiteral(s)` | Parse literal text | +| `valueEquals` / `valueCompare` | Comparison | +| `makeParam("name")` | Build operator parameter lists | + +Host functions should return a `Value` appropriate to the script context (often `int64_t` or `std::string`). + +## Reflection (advanced) + +For tools that inspect or steer execution: + +- `allEntities()`, `currentEntity()`, `getEntityByIndex` +- `Entity` — `beginIndex`, `type` (`EntityType`), `name`, `value`, … +- `gotoOnEntity(index)`, `getUserFunction`, `getUserOperator` +- `getAttributeByIndex` + +Most applications only need `cmd` and host callbacks. + +## C API + +A thin C wrapper lives in [`include/interpreter_c.h`](../include/interpreter_c.h) and [`src/interpreter_c.cpp`](../src/interpreter_c.cpp). CI smoke-compiles it; there is no separate C tutorial. Use the C++ API above for new code. + +## See also + +- [Getting started](getting-started.md) +- [Script language](script-language.md) +- [Limitations](limitations.md) diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..5c3b710 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,135 @@ +# Getting started + +## What this is + +A small embeddable command interpreter: you register C++ functions and operators, then run script strings that call them. The core library is a single header plus one translation unit: + +- [`include/interpreter.h`](../include/interpreter.h) +- [`src/interpreter.cpp`](../src/interpreter.cpp) + +Optional features (math, `Vector`/`Map`, files, structs, typed variables) live under [`include/base_library/`](../include/base_library/). See [Base library](base-library.md). + +## Build and test + +On Debian/Ubuntu (same as [CI](../.github/workflows/ci.yml)): + +```bash +sudo apt-get update && sudo apt-get install -y libgtest-dev g++ +cd src && make && ./test +``` + +To compile your own program, add `interpreter.cpp` and include paths, C++17 or later: + +```bash +g++ -std=c++17 -O2 -I. your_app.cpp src/interpreter.cpp -o your_app +``` + +## Minimal host program + +Pattern from [`example/main.cpp`](../example/main.cpp): + +```cpp +#include "include/interpreter.h" +#include "include/base_library/arithmetic_operations.h" +#include "include/base_library/comparison_operations.h" + +int main() { + Interpreter ir; + + // Register base library on the same ir you will execute. + InterpreterBaseLib::ArithmeticOperations ao(ir); + InterpreterBaseLib::ComparisonOperations co(ir); + + ir.addFunction("summ", [](const std::vector& args) -> Interpreter::Value { + int64_t res = 0; + for (const auto& v : args) { + res += Interpreter::valueAsInt64(v); + } + return res; + }); + + const char* script = + "$a = 5; $b = 2; " + "while ($a > 1) { $a = $a - 1; $b = summ($b, $a); if ($a < 4) { break; } } " + "$b;"; + + const auto result = ir.cmd(script); + if (Interpreter::hasError(result.second)) { + // handle parse/runtime error + return 1; + } + // result.first holds the last expression value; string form often easiest: + std::string res = Interpreter::cmdResultToString(result); // "9" + return 0; +} +``` + +Important: + +- Host callbacks use [`Interpreter::Value`](../include/interpreter.h) (`std::variant` of `bool`, `int64_t`, `double`, `std::string`, and control-flow tokens), not raw `std::string`. See [C++ API](cpp-api.md#value-type). +- Scripts accept boolean literals **`true`** and **`false`** ([details](script-language.md#boolean-literals)). +- Construct every base-library helper (`Container`, `Filesystem`, …) on the **same** `Interpreter` instance you call `cmd()` on. + +## Running scripts + +### `cmd(script)` — usual path + +`cmd` cleans the script, parses if the text changed, then runs. Returns `CmdResult` = `{ Value, Error }`. + +```cpp +auto r = ir.cmd("$a = 1; $a + 2;"); +if (!Interpreter::hasError(r.second)) { + std::string s = Interpreter::cmdResultToString(r); // "3" +} +``` + +### `parseScript` + `runScript` — same text many times + +If you execute the **same** script string repeatedly, parse once and run many times (AST is cached when `m_prevScript` matches): + +```cpp +Interpreter::Error err; +if (ir.parseScript("$a = 0; while ($a < 10) { $a += 1; } $a;", err)) { + auto r = ir.runScript(); + // ... +} +``` + +Use `cmd` when the script text changes on each call. + +## What to register + +| Need | Register | +|------|----------| +| `+`, `-`, `*`, `/`, etc. | `ArithmeticOperations` | +| `==`, `<`, `>`, … | `ComparisonOperations` | +| `Vector`, `Map` | `Container` | +| `Struct` fields | `Structure` | +| `File`, `Dir` | `Filesystem` | +| `a: int = …`, `type(a)` | `Types` | +| Custom logic | `addFunction` / `addOperator` | + +You can omit modules you do not use. Scripts that call undefined functions or operators fail at parse or runtime. + +## Next steps + +- [Script language reference](script-language.md) +- [C++ API details](cpp-api.md) +- [Limitations](limitations.md) (return values vs `print`, copy + containers, …) +- More examples: [`example/main.cpp`](../example/main.cpp), [`src/test.cpp`](../src/test.cpp) (`readmeExamplesTest`) + +## Testing + +```bash +cd src && make && ./test +``` + +Google Test suite (34 tests) covers language features, base library, and API edge cases. When in doubt about behavior, check the matching test in `src/test.cpp`. + +## Benchmarks (local) + +```bash +cd src && make bench && ./bench +``` + +Details and reference numbers: [Benchmarks](benchmarks.md). diff --git a/docs/limitations.md b/docs/limitations.md new file mode 100644 index 0000000..8b210f7 --- /dev/null +++ b/docs/limitations.md @@ -0,0 +1,49 @@ +# Limitations and quirks + +Behaviors that are easy to misunderstand but are intentional or documented in tests. No surprises when embedding the library. + +## Copy and containers + +Copying `Interpreter` duplicates variables, macros, and operators, but **Map/Vector init** still uses the interpreter instance bound when `Container` / `Structure` were constructed. + +**Summary:** On a copy, container literals see parent variables only if those variables were set on the same `ir` that `m_intr` points to. + +Full explanation: **[Copy and containers](copy-and-containers.md)**. + +Variable isolation on copy (`$a` on `ir` vs `ir2`) works; shared container storage and `m_intr` are the subtle parts. + +## `cmd` return value vs stdout + +- `cmd` / `runScript` return the last expression’s [`Value`](cpp-api.md#value-type), not console output. +- `print` is a **host** function; typical implementations use `printf`. Tests expect loop+print scripts to return `"0"` while lines appear on stdout (`printStdoutTest`). +- To assert output, capture stdout in tests or log inside your `print` callback. + +## Container access errors + +Out-of-range Vector index or missing Map key often yields an **empty string** from `cmdResultToString`, not a `Runtime` error with a message. See `containerEdgeTest` in [`src/test.cpp`](../src/test.cpp). + +## Numeric types + +- Values are `int64_t`, `double`, or `string` at runtime ([`Value`](cpp-api.md#value-type)). +- Mixed arithmetic follows implementation rules in base library and tests; float vs int promotion may differ from C++ (e.g. `1.5 * 2` vs `1.5 / 2` — see `comparisonSupplementTest`). +- String `"5"` and integer `5` can compare equal via `valueEquals`. + +## Macros + +- `setMacro("inc", body)` and `setMacro("#inc", body)` are equivalent. +- Macro bodies are whitespace-cleaned when expanded; match normal script formatting. +- Unknown `#name` at expand time is a parse error. + +## Parse cache + +`parseScript` skips re-parse when the script text equals the previous successful parse. After changing macros or operators, run a new script string or use `cmd` with changed text so the AST refreshes. + +## C API + +[`interpreter_c.h`](../include/interpreter_c.h) wraps the C++ engine for FFI. It is smoke-tested in CI but not documented in depth here; prefer the C++ API for new integrations. + +## See also + +- [Getting started](getting-started.md) +- [Script language](script-language.md) +- [Copy and containers](copy-and-containers.md) diff --git a/docs/script-language.md b/docs/script-language.md new file mode 100644 index 0000000..fbc70d6 --- /dev/null +++ b/docs/script-language.md @@ -0,0 +1,231 @@ +# Script language + +Syntax reference for scripts passed to `Interpreter::cmd` or `parseScript`. Behavior is covered by [`src/test.cpp`](../src/test.cpp) (especially `readmeExamplesTest`). + +## Statements and expressions + +- Scripts are a sequence of statements. +- Statements end with `;`. +- Whitespace outside strings is ignored. Line comments start with `//` (to end of line). +- The value of the last expression in a script is the result of `cmd` / `runScript`. +- Parentheses `()` group expressions and raise precedence inside them. + +## Variables + +Names start with `$`: + +```text +$a = 5; +$b = "hello"; +``` + +Initializer syntax: + +```text +$a{12}; // same idea as $a = 12 with parse-time init +``` + +Non-`$` names are ordinary identifiers (containers, struct fields, labels). Names like `trueVar` are **not** the boolean literals — only the exact tokens `true` and `false` count. + +## Boolean literals + +The keywords **`true`** and **`false`** are boolean literals (no `$` prefix): + +```text +$x = true; +$y = false; +if (false) { $a = 1; } +while (true) { break; } +``` + +- Stored as `bool` in [`Value`](cpp-api.md#value-type). +- `cmdResultToString` prints them as **`1`** and **`0`** (same as numeric truthiness). +- Usable anywhere an expression is expected, including `if` / `while` conditions and comparisons (`$a == true`). +- **Reserved:** you cannot `addFunction("true", …)` or `addOperator("false", …)`; registration fails. + +## Arithmetic and comparisons + +Require [ArithmeticOperations](base-library.md#arithmetic_operationsh) and [ComparisonOperations](base-library.md#comparison_operationsh): + +```text +$a = 5; $b = 2; +$c = $a * (2 + $b); +$d = $a == $b; +``` + +## Host functions + +Registered in C++ with `addFunction`. Called from script: + +```text +$c = summ($a, $b, summ(5, 3)); +``` + +Arguments are expressions; the host receives evaluated [`Value`](cpp-api.md#value-type) values. + +## Script functions + +Define with `function` and a braced body: + +```text +$a = 1; $b = 2; +function myFunc { $a += $b; }; +myFunc(); +``` + +Parameters are positional: `$0`, `$1`, … + +```text +function add { $0 += $1; }; +add(2, 3); +``` + +Nested functions are allowed. Recursion works if the host function is visible inside the script function body. + +## Host operators + +Registered in C++ with `addOperator(name, callback, priority)`. Base library registers `=`, `.`, `[`, `:`, etc. + +```text +$c = 5; +$c += 5; +``` + +## Control flow + +| Construct | Meaning | +|-----------|---------| +| `if (condition) { body }` | Run body if condition is truthy | +| `elseif (condition) { body }` | Else branch chain | +| `else { body }` | Final else | +| `while (condition) { body }` | Loop while truthy | +| `break;` | Exit innermost loop | +| `continue;` | Next loop iteration | + +Conditions use [truthy](cpp-api.md#value-type) rules (non-zero numbers, non-empty strings, `true`, …). + +**Container iteration** + +```text +while ($v : a) print($v); +``` + +`$v` receives each element (Vector) or key/value pair context (Map) depending on container type. + +## Macros + +**Declare in script** + +```text +#macro myMac { $c = 5; $d = $c + 5; }; +$c = 1; #myMac; +``` + +**Invoke** + +```text +#myMac; +``` + +**With parameters** (`$0`, `$1`, …) + +```text +#macro myMacr { $a = $a + $0 + $0 + $1; }; +$a = 5; #myMacr(3, 4); +``` + +**From C++** + +```cpp +ir.setMacro("inc", "$a = $a + 1;"); +// script: #inc (name with or without #) +``` + +Macros are expanded before parse. Bodies are cleaned the same way as normal scripts. + +## Goto + +Labels must start with `l_` and end with `:`: + +```text +if ($a == 3) { + goto l_done; +} +l_done: $a = 0; +``` + +## Containers + +Requires [Container](base-library.md#containersh--container). See [Base library](base-library.md) for method tables. + +**Vector** + +```text +a = Vector; +a.push_back(1); +a.push_back(2); +a[2]; + +a = Vector{ 1 + 2, 2 + 3, 3 + 4 }; +``` + +**Map** + +```text +b = Map; +b.insert(myKey, myValue); +b["myKey"]; + +$b = 12; +c = Map{ one : $b + 5, two : 2 }; +c["one"]; +``` + +**Literals** + +Comma-separated elements in `{ … }`. Map entries use `key : value` segments. + +## Struct + +Requires [Structure](base-library.md#structureh--structure): + +```text +e = Struct{ one : 5, two : 2 }; +e.one = summ(e.one, e.two); +e.three = 22; +``` + +## Filesystem + +Requires [Filesystem](base-library.md#filesystemh--filesystem): + +```text +file1 = File{"main.cpp"}; +if (file1.exist()) { + $data = file1.read(); +} +``` + +## Types + +Requires [Types](base-library.md#typesh--types): + +```text +a: int = 123; +b: str = "abc"; +type(a); +``` + +## Attributes + +Optional metadata on entities (advanced). Register names with `addAttribute` in C++. + +## Print and script results + +`print` is not built in; the host defines it (see [`example/main.cpp`](../example/main.cpp)). Scripts that only call `print` in a loop often return `0` from `cmd` because the last value is from the loop, not the printed text. Output goes to the host’s `printf` (or similar). See [Limitations](limitations.md). + +## See also + +- [Getting started](getting-started.md) +- [C++ API](cpp-api.md) +- [Limitations](limitations.md) diff --git a/example/main.cpp b/example/main.cpp index 8649563..0079631 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -54,66 +54,66 @@ int main(int argc, char* argv[]) InterpreterBaseLib::Filesystem ir_fs(ir); InterpreterBaseLib::Structure ir_st(ir); - ir.addFunction("summ", [](const vector& args) ->string { - int res = 0; + ir.addFunction("summ", [](const vector& args) -> Interpreter::Value { + int64_t res = 0; for (auto& v : args) { - if (isNumber(v)) res += stoi(v); + res += Interpreter::valueAsInt64(v); } - return to_string(res); + return res; }); - ir.addFunction("print", [](const vector& args) ->string { + ir.addFunction("print", [](const vector& args) -> Interpreter::Value { for (auto& v : args) { - printf("%s ", v.c_str()); + printf("%s ", Interpreter::valueToString(v).c_str()); } printf("\n"); - return ""; + return std::string{}; }); string script = "$a = 5; $b = 2; while ($a > 1){ $a = $a - 1; $b = summ($b, $a); if ($a < 4){ break;} } $b;"; - string res = ir.cmd(script); // 9 + string res = Interpreter::cmdResultToString(ir.cmd(script)); // 9 script = "$a{5}; $b{2}; $c = summ($a, ($a + ($a * ($b + $a))), summ(5)); $c;"; - res = ir.cmd(script); // 50 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 50 script = "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a[2]"; - res = ir.cmd(script); // 3 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 3 script = "b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b[\"myKeyTwo\"]"; - res = ir.cmd(script); // myValueTwo + res = Interpreter::cmdResultToString(ir.cmd(script)); // myValueTwo script = "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); while($v : a) print($v);"; - res = ir.cmd(script); // 1 2 3 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 1 2 3 script = "a = Vector{1 + 2, 2 + 3, 3 + 4}; while($v : a) print($v);"; - res = ir.cmd(script); // 3 5 7 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 3 5 7 script = "$b = 12; c = Map{ one : $b + 5, two : 2}; while($v : c) print($v);"; - res = ir.cmd(script); // one 17 two 2 + res = Interpreter::cmdResultToString(ir.cmd(script)); // one 17 two 2 script = "e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one"; - res = ir.cmd(script); // 7 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 7 script = "$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three"; - res = ir.cmd(script); // 22 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 22 script = "file1 = File{\"main.cpp\"}; file2 = File{\"mainCopy.txt\"}; if (file1.exist()) { $data = file1.read(); file2.write($data); }"; - res = ir.cmd(script); + res = Interpreter::cmdResultToString(ir.cmd(script)); script = "$a = 1; $b = 2; function myFunc{ $a += $b; }; myFunc()"; - res = ir.cmd(script); // 3 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 3 script = "$a = 1; $b = 2; function myFunc{ $a += $b; function myFunc2{ $a += $b; }; myFunc2(); }; myFunc()"; - res = ir.cmd(script); // 5 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 5 script = "function myFunc{ if ($0 > 1) $a = $0 * myFunc($0 - 1); else $a = 1; $a }; myFunc(5)"; - res = ir.cmd(script); // 120 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 120 script = "function myFunc{ $0 += $1; }; myFunc(2, 3)"; - res = ir.cmd(script); // 5 + res = Interpreter::cmdResultToString(ir.cmd(script)); // 5 script = "b: str = 123; type(b)"; - res = ir.cmd(script); // str + res = Interpreter::cmdResultToString(ir.cmd(script)); // str return 0; } diff --git a/include/base_library/arithmetic_operations.h b/include/base_library/arithmetic_operations.h index aab1ba6..37f1887 100644 --- a/include/base_library/arithmetic_operations.h +++ b/include/base_library/arithmetic_operations.h @@ -1,99 +1,80 @@ #include "../../include/interpreter.h" -#include - namespace InterpreterBaseLib { class ArithmeticOperations { public: - bool isNumber(const std::string& s) const { - for (auto c : s) { - if (!std::isdigit(c)) { - return false; - } - } - return !s.empty(); - } - ArithmeticOperations(Interpreter& ir) { - ir.addOperator("*", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return std::to_string(stoi(leftOpd) * stoi(rightOpd)); - else - return "0"; + ir.addOperator("*", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) + return Interpreter::valueAsInt64(leftOpd) * Interpreter::valueAsInt64(rightOpd); + return int64_t{0}; }, 0); - ir.addOperator("/", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return std::to_string(stoi(leftOpd) / stoi(rightOpd)); - else - return "0"; + ir.addOperator("/", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) + return Interpreter::valueAsInt64(leftOpd) / Interpreter::valueAsInt64(rightOpd); + return int64_t{0}; }, 0); - ir.addOperator("+", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return std::to_string(stoi(leftOpd) + stoi(rightOpd)); - else - return leftOpd + rightOpd; + ir.addOperator("+", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) + return Interpreter::valueAsInt64(leftOpd) + Interpreter::valueAsInt64(rightOpd); + return Interpreter::valueAsString(leftOpd) + Interpreter::valueAsString(rightOpd); }, 1); - ir.addOperator("-", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return std::to_string(stoi(leftOpd) - stoi(rightOpd)); - else - return "0"; + ir.addOperator("-", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) + return Interpreter::valueAsInt64(leftOpd) - Interpreter::valueAsInt64(rightOpd); + return int64_t{0}; }, 1); - ir.addOperator("+=", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)){ - leftOpd = std::to_string(stoi(leftOpd) + stoi(rightOpd)); - return leftOpd; - } - else{ - leftOpd += rightOpd; - return leftOpd; - } - }, 4); + ir.addOperator("+=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) { + leftOpd = Interpreter::valueAsInt64(leftOpd) + Interpreter::valueAsInt64(rightOpd); + return leftOpd; + } + leftOpd = Interpreter::valueAsString(leftOpd) + Interpreter::valueAsString(rightOpd); + return leftOpd; + }, 4); - ir.addOperator("-=", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)){ - leftOpd = std::to_string(stoi(leftOpd) - stoi(rightOpd)); - return leftOpd; - } - else{ - leftOpd += rightOpd; - return leftOpd; - } - }, 4); - ir.addOperator("++", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd)){ - leftOpd = std::to_string(stoi(leftOpd) + 1); - return leftOpd; - } - if (isNumber(rightOpd)){ - rightOpd = std::to_string(stoi(rightOpd) + 1); - } - return rightOpd; - }, 4); + ir.addOperator("-=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd) && Interpreter::valueIsNumeric(rightOpd)) { + leftOpd = Interpreter::valueAsInt64(leftOpd) - Interpreter::valueAsInt64(rightOpd); + return leftOpd; + } + leftOpd = Interpreter::valueAsString(leftOpd) + Interpreter::valueAsString(rightOpd); + return leftOpd; + }, 4); - ir.addOperator("--", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd)){ - leftOpd = std::to_string(stoi(leftOpd) - 1); - return leftOpd; - } - if (isNumber(rightOpd)){ - rightOpd = std::to_string(stoi(rightOpd) - 1); - } - return rightOpd; - }, 4); - - ir.addOperator("=", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - leftOpd = rightOpd; - return leftOpd; - }, 100); + ir.addOperator("++", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd)) { + leftOpd = Interpreter::valueAsInt64(leftOpd) + 1; + return leftOpd; + } + if (Interpreter::valueIsNumeric(rightOpd)) { + rightOpd = Interpreter::valueAsInt64(rightOpd) + 1; + } + return rightOpd; + }, 4); - } + ir.addOperator("--", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (Interpreter::valueIsNumeric(leftOpd)) { + leftOpd = Interpreter::valueAsInt64(leftOpd) - 1; + return leftOpd; + } + if (Interpreter::valueIsNumeric(rightOpd)) { + rightOpd = Interpreter::valueAsInt64(rightOpd) - 1; + } + return rightOpd; + }, 4); + + ir.addOperator("=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + leftOpd = rightOpd; + return leftOpd; + }, 100); + } }; -} \ No newline at end of file +} diff --git a/include/base_library/comparison_operations.h b/include/base_library/comparison_operations.h index 1cc293d..fcf2cca 100644 --- a/include/base_library/comparison_operations.h +++ b/include/base_library/comparison_operations.h @@ -1,63 +1,50 @@ #include "../../include/interpreter.h" -#include - namespace InterpreterBaseLib { class ComparisonOperations { public: - bool isNumber(const std::string& s) const { - for (auto c : s) { - if (!std::isdigit(c)) { - return false; - } - } - return !s.empty(); - } - ComparisonOperations(Interpreter& ir) - { - ir.addOperator("==", [](std::string& leftOpd, std::string& rightOpd) ->std::string { - return leftOpd == rightOpd ? "1" : "0"; - }, 2); - - ir.addOperator("!=", [](std::string& leftOpd, std::string& rightOpd) ->std::string { - return leftOpd != rightOpd ? "1" : "0"; - }, 2); - - ir.addOperator(">", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return stoi(leftOpd) > stoi(rightOpd) ? "1" : "0"; - else - return leftOpd.size() > rightOpd.size() ? "1" : "0"; - }, 2); - - ir.addOperator("<", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return stoi(leftOpd) < stoi(rightOpd) ? "1" : "0"; - else - return leftOpd.size() < rightOpd.size() ? "1" : "0"; - }, 2); - - ir.addOperator(">=", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return stoi(leftOpd) >= stoi(rightOpd) ? "1" : "0"; - else - return leftOpd.size() >= rightOpd.size() ? "1" : "0"; - }, 2); - - ir.addOperator("<=", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (isNumber(leftOpd) && isNumber(rightOpd)) - return stoi(leftOpd) <= stoi(rightOpd) ? "1" : "0"; - else - return leftOpd.size() <= rightOpd.size() ? "1" : "0"; - }, 2); - - ir.addOperator("=", [](std::string& leftOpd, std::string& rightOpd) ->std::string { + { + ir.addOperator("==", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (const auto* ls = std::get_if(&leftOpd)) { + if (const auto* rs = std::get_if(&rightOpd)) { + return *ls == *rs; + } + } + return Interpreter::valueEquals(leftOpd, rightOpd); + }, 2); + + ir.addOperator("!=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + if (const auto* ls = std::get_if(&leftOpd)) { + if (const auto* rs = std::get_if(&rightOpd)) { + return *ls != *rs; + } + } + return !Interpreter::valueEquals(leftOpd, rightOpd); + }, 2); + + ir.addOperator(">", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + return Interpreter::valueCompare(leftOpd, rightOpd) > 0; + }, 2); + + ir.addOperator("<", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + return Interpreter::valueCompare(leftOpd, rightOpd) < 0; + }, 2); + + ir.addOperator(">=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + return Interpreter::valueCompare(leftOpd, rightOpd) >= 0; + }, 2); + + ir.addOperator("<=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + return Interpreter::valueCompare(leftOpd, rightOpd) <= 0; + }, 2); + + ir.addOperator("=", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { leftOpd = rightOpd; return leftOpd; - }, 100); + }, 100); } }; -} \ No newline at end of file +} diff --git a/include/base_library/containers.h b/include/base_library/containers.h index ec1a747..7eb6bc7 100644 --- a/include/base_library/containers.h +++ b/include/base_library/containers.h @@ -1,11 +1,36 @@ +#pragma once #include "../../include/interpreter.h" #include #include +#include namespace InterpreterBaseLib { +template +inline void foreachCommaInitBody(const std::string& initBody, Fn&& fn) { + const size_t ssz = initBody.size(); + size_t cpos = 0; + int bordCnt = 0; + for (size_t cp = 0; cp < ssz; ++cp) { + if (initBody[cp] == '(') { + ++bordCnt; + } + if (initBody[cp] == ')') { + --bordCnt; + } + if (((initBody[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { + size_t end = cp; + if (end == ssz - 1) { + ++end; + } + fn(initBody.substr(cpos, end - cpos)); + cpos = cp + 1; + } + } +} + class Container { public: @@ -32,77 +57,47 @@ namespace InterpreterBaseLib { m_intr(ir) { auto currOperator = ir.getUserOperator("="); - ir.addOperator("=", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - - if (rightOpd == "Vector") { - m_vectorContr[leftOpd] = std::vector(); + ir.addOperator("=", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + const std::string rightKey = Interpreter::valueAsString(rightOpd); + + if (rightKey == "Vector") { + m_vectorContr[leftKey] = std::vector(); auto entityRight = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex + 1); - std::string& initBody = entityRight.value; + const std::string initBody = Interpreter::valueAsInitBody(entityRight.value); if (!initBody.empty()) { - - size_t ssz = initBody.size(), - cpos = 0, - cp = 0; - int bordCnt = 0; - Interpreter intrCopy = m_intr; - - while (cp < ssz) { - if (initBody[cp] == '(') ++bordCnt; - if (initBody[cp] == ')') --bordCnt; - if (((initBody[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { - - if (cp == ssz - 1) ++cp; - - const std::string arg = initBody.substr(cpos, cp - cpos); - std::string err; - if (intrCopy.parseScript(arg, err)) - m_vectorContr[leftOpd].push_back(intrCopy.runScript()); - - cpos = cp + 1; + foreachCommaInitBody(initBody, [&](const std::string& arg) { + Interpreter::Error err; + if (intrCopy.parseScript(arg, err)) { + m_vectorContr[leftKey].push_back(intrCopy.runScript().first); } - ++cp; - } + }); } } - else if (rightOpd == "Map") { - m_mapContr[leftOpd] = std::map(); + else if (rightKey == "Map") { + m_mapContr[leftKey] = std::map(); auto entityRight = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex + 1); - std::string& initBody = entityRight.value; + const std::string initBody = Interpreter::valueAsInitBody(entityRight.value); if (!initBody.empty()) { - - size_t ssz = initBody.size(), - cpos = 0, - cp = 0; - int bordCnt = 0; - Interpreter intrCopy = m_intr; - - while (cp < ssz) { - if (initBody[cp] == '(') ++bordCnt; - if (initBody[cp] == ')') --bordCnt; - if (((initBody[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { - - if (cp == ssz - 1) ++cp; - - auto args = split(initBody.substr(cpos, cp - cpos), ':'); - std::string err; - if ((args.size() > 1) && intrCopy.parseScript(args[1], err)) - m_mapContr[leftOpd][args[0]] = intrCopy.runScript(); - else if (!args.empty()) - m_mapContr[leftOpd][args[0]] = ""; - - cpos = cp + 1; + foreachCommaInitBody(initBody, [&](const std::string& segment) { + auto args = split(segment, ':'); + Interpreter::Error err; + if ((args.size() > 1) && intrCopy.parseScript(args[1], err)) { + m_mapContr[leftKey][args[0]] = intrCopy.runScript().first; } - ++cp; - } - } + else if (!args.empty()) { + m_mapContr[leftKey][args[0]] = std::string{}; + } + }); + } } else if (currOperator){ return currOperator(leftOpd, rightOpd); @@ -111,86 +106,89 @@ namespace InterpreterBaseLib { }, 100); currOperator = ir.getUserOperator("."); - ir.addOperator(".", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (m_vectorContr.count(leftOpd) || m_mapContr.count(leftOpd)) { + ir.addOperator(".", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + if (m_vectorContr.count(leftKey) || m_mapContr.count(leftKey)) { return rightOpd; } else if (currOperator) { return currOperator(leftOpd, rightOpd); } - return leftOpd + '.' + rightOpd; + return Interpreter::valueAsString(leftOpd) + '.' + Interpreter::valueAsString(rightOpd); }, 0); currOperator = ir.getUserOperator("["); - ir.addOperator("[", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (m_vectorContr.count(leftOpd) || m_mapContr.count(leftOpd)) { - auto value = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex - 1).value; + ir.addOperator("[", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + if (m_vectorContr.count(leftKey) || m_mapContr.count(leftKey)) { + const std::string value = Interpreter::valueAsString(m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex - 1).value); Interpreter intrCopy = m_intr; - std::string err; + Interpreter::Error err; if (!value.empty() && intrCopy.parseScript(value, err)){ - auto key = intrCopy.runScript(); - if (m_vectorContr.count(leftOpd)){ + const std::string key = Interpreter::valueToString(intrCopy.runScript().first); + if (m_vectorContr.count(leftKey)){ auto ix = isNumber(key) ? stoi(key) : -1; - if (0 <= ix && ix < m_vectorContr[leftOpd].size()){ - return m_vectorContr[leftOpd][ix]; + if (0 <= ix && ix < (int)m_vectorContr[leftKey].size()){ + return m_vectorContr[leftKey][ix]; } - }else if (m_mapContr[leftOpd].count(key)){ - return m_mapContr[leftOpd][key]; + }else if (m_mapContr[leftKey].count(key)){ + return m_mapContr[leftKey][key]; } } else if (value.empty()){ - err = "error value.empty"; + return std::string{"error value.empty"}; } - return err; + return err.message; } else if (currOperator) { return currOperator(leftOpd, rightOpd); } - return "0"; + return int64_t{0}; }, 0); currOperator = ir.getUserOperator(":"); - ir.addOperator(":", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (m_vectorContr.count(rightOpd)) { - - int itPos = m_intr.currentEntity().value.empty() ? 0 : stoi(m_intr.currentEntity().value); - - if (itPos < (int)m_vectorContr[rightOpd].size()) { - leftOpd = m_vectorContr[rightOpd][itPos]; - return std::to_string(++itPos); + ir.addOperator(":", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string rightKey = Interpreter::valueAsString(rightOpd); + if (m_vectorContr.count(rightKey)) { + + int itPos = Interpreter::valueIsInteger(m_intr.currentEntity().value) ? (int)Interpreter::valueAsInt64(m_intr.currentEntity().value) : 0; + + if (itPos < (int)m_vectorContr[rightKey].size()) { + leftOpd = m_vectorContr[rightKey][itPos]; + return static_cast(++itPos); } - else return "0"; + else return int64_t{0}; } - else if (m_mapContr.count(rightOpd)) { + else if (m_mapContr.count(rightKey)) { - int itPos = m_intr.currentEntity().value.empty() ? 0 : stoi(m_intr.currentEntity().value); + int itPos = Interpreter::valueIsInteger(m_intr.currentEntity().value) ? (int)Interpreter::valueAsInt64(m_intr.currentEntity().value) : 0; - if (itPos < (int)m_mapContr[rightOpd].size()) { + if (itPos < (int)m_mapContr[rightKey].size()) { int cpos = 0; - for (auto& v : m_mapContr[rightOpd]) { - leftOpd = v.first + '\t' + v.second; + for (auto& v : m_mapContr[rightKey]) { + leftOpd = Interpreter::valueAsString(v.first) + '\t' + Interpreter::valueAsString(v.second); ++cpos; if (cpos > itPos) break; - } - return std::to_string(++itPos); + } + return static_cast(++itPos); } - else return "0"; + else return int64_t{0}; } else if (currOperator) { return currOperator(leftOpd, rightOpd); } - return leftOpd + ':' + rightOpd; + return Interpreter::valueAsString(leftOpd) + ':' + Interpreter::valueAsString(rightOpd); }, 0); auto currFunction = ir.getUserFunction("push_back"); - ir.addFunction("push_back", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("push_back", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string vecName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(vecName)) { for (auto& a : args) m_vectorContr[vecName].push_back(a); - ok = "1"; + ok = int64_t{1}; } else if (currFunction) { return currFunction(args); @@ -199,15 +197,15 @@ namespace InterpreterBaseLib { }); currFunction = ir.getUserFunction("pop_back"); - ir.addFunction("pop_back", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("pop_back", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string vecName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(vecName)) { if (!m_vectorContr[vecName].empty()) { m_vectorContr[vecName].pop_back(); - ok = "1"; + ok = int64_t{1}; } } else if (currFunction) { @@ -217,24 +215,24 @@ namespace InterpreterBaseLib { }); currFunction = ir.getUserFunction("insert"); - ir.addFunction("insert", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("insert", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(contrName)) { - if ((args.size() > 1) && isNumber(args[0])) { - size_t inx = size_t(stoi(args[0])); + if ((args.size() > 1) && Interpreter::valueIsInteger(args[0])) { + size_t inx = size_t(Interpreter::valueAsInt64(args[0])); if (m_vectorContr[contrName].size() > inx) { m_vectorContr[contrName].insert(m_vectorContr[contrName].begin() + inx, args[1]); - ok = "1"; + ok = int64_t{1}; } } } else if (m_mapContr.count(contrName)) { if (args.size() > 1) { - m_mapContr[contrName][args[0]] = args[1]; - ok = "1"; + m_mapContr[contrName][Interpreter::valueAsString(args[0])] = args[1]; + ok = int64_t{1}; } } else if (currFunction) { @@ -244,24 +242,25 @@ namespace InterpreterBaseLib { }); currFunction = ir.getUserFunction("erase"); - ir.addFunction("erase", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("erase", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(contrName)) { - if (!args.empty() && isNumber(args[0])) { - size_t inx = size_t(stoi(args[0])); + if (!args.empty() && Interpreter::valueIsInteger(args[0])) { + size_t inx = size_t(Interpreter::valueAsInt64(args[0])); if (m_vectorContr[contrName].size() > inx) { m_vectorContr[contrName].erase(m_vectorContr[contrName].begin() + inx); - ok = "1"; + ok = int64_t{1}; } } } else if (m_mapContr.count(contrName)) { - if (!args.empty() && m_mapContr[contrName].count(args[0])) { - m_mapContr[contrName].erase(args[0]); - ok = "1"; + const std::string key = Interpreter::valueAsString(args[0]); + if (!args.empty() && m_mapContr[contrName].count(key)) { + m_mapContr[contrName].erase(key); + ok = int64_t{1}; } } else if (currFunction) { @@ -271,48 +270,48 @@ namespace InterpreterBaseLib { }); currFunction = ir.getUserFunction("size"); - ir.addFunction("size", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("size", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); if (m_vectorContr.count(contrName)) - return std::to_string(m_vectorContr[contrName].size()); + return static_cast(m_vectorContr[contrName].size()); else if (m_mapContr.count(contrName)) - return std::to_string(m_mapContr[contrName].size()); + return static_cast(m_mapContr[contrName].size()); else if (currFunction) { return currFunction(args); } - return ""; + return std::string{}; }); currFunction = ir.getUserFunction("empty"); - ir.addFunction("empty", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("empty", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); if (m_vectorContr.count(contrName)) - return m_vectorContr[contrName].empty() ? "1" : "0"; + return m_vectorContr[contrName].empty(); else if (m_mapContr.count(contrName)) - return m_mapContr[contrName].empty() ? "1" : "0"; + return m_mapContr[contrName].empty(); else if (currFunction) { return currFunction(args); } - return ""; + return std::string{}; }); currFunction = ir.getUserFunction("clear"); - ir.addFunction("clear", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("clear", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(contrName)) { m_vectorContr[contrName].clear(); - ok = "1"; + ok = int64_t{1}; } else if (m_mapContr.count(contrName)) { m_mapContr[contrName].clear(); - ok = "1"; + ok = int64_t{1}; } else if (currFunction) { return currFunction(args); @@ -321,47 +320,48 @@ namespace InterpreterBaseLib { }); currFunction = ir.getUserFunction("at"); - ir.addFunction("at", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("at", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string out; if (m_vectorContr.count(contrName)) { - if (!args.empty() && isNumber(args[0])) { - size_t inx = size_t(stoi(args[0])); + if (!args.empty() && Interpreter::valueIsInteger(args[0])) { + size_t inx = size_t(Interpreter::valueAsInt64(args[0])); if (m_vectorContr[contrName].size() > inx) - out = m_vectorContr[contrName][inx]; + return m_vectorContr[contrName][inx]; } } else if (m_mapContr.count(contrName)) { - if (!args.empty() && m_mapContr[contrName].count(args[0])) - out = m_mapContr[contrName][args[0]]; + const std::string key = Interpreter::valueAsString(args[0]); + if (!args.empty() && m_mapContr[contrName].count(key)) + return m_mapContr[contrName][key]; } else if (currFunction) { return currFunction(args); } - return out; + return std::string{}; }); currFunction = ir.getUserFunction("set"); - ir.addFunction("set", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("set", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - std::string ok = "0"; + Interpreter::Value ok = int64_t{0}; if (m_vectorContr.count(contrName)) { - if ((args.size() > 1) && isNumber(args[0])) { - size_t inx = size_t(stoi(args[0])); + if ((args.size() > 1) && Interpreter::valueIsInteger(args[0])) { + size_t inx = size_t(Interpreter::valueAsInt64(args[0])); if (m_vectorContr[contrName].size() > inx) { m_vectorContr[contrName][inx] = args[1]; - ok = "1"; + ok = int64_t{1}; } } } else if (m_mapContr.count(contrName)) { - if ((args.size() > 1) && m_mapContr[contrName].count(args[0])) { - m_mapContr[contrName][args[0]] = args[1]; - ok = "1"; + const std::string key = Interpreter::valueAsString(args[0]); + if ((args.size() > 1) && m_mapContr[contrName].count(key)) { + m_mapContr[contrName][key] = args[1]; + ok = int64_t{1}; } } else if (currFunction) { @@ -383,7 +383,7 @@ namespace InterpreterBaseLib { protected: Interpreter& m_intr; - std::map> m_vectorContr; - std::map> m_mapContr; + std::map> m_vectorContr; + std::map> m_mapContr; }; } \ No newline at end of file diff --git a/include/base_library/filesystem.h b/include/base_library/filesystem.h index 9288119..ca8c21e 100644 --- a/include/base_library/filesystem.h +++ b/include/base_library/filesystem.h @@ -12,156 +12,191 @@ namespace InterpreterBaseLib { class Filesystem { + static Interpreter::Value evalInitBodyPath(Interpreter& ir, const std::string& initBody) { + if (initBody.empty()) { + return std::string{}; + } + std::string script = initBody; + if (script.front() != '"') { + script = "\"" + initBody + "\""; + } + Interpreter intrCopy = ir; + Interpreter::Error err; + if (intrCopy.parseScript(script, err)) { + return intrCopy.runScript().first; + } + return Interpreter::valueFromLiteral(initBody); + } + public: - + Filesystem(Interpreter& ir): m_intr(ir) - { + { auto currOperator = ir.getUserOperator("="); - ir.addOperator("=", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - - if (rightOpd == "File") { - auto initBody = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex + 1).value; + ir.addOperator("=", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + if (leftKey.empty()) { + leftKey = Interpreter::valueAsString(leftOpd); + } + const size_t opIdx = m_intr.currentEntity().beginIndex; + Interpreter::Entity rhsEntity; + std::string typeName; + for (int delta : {1, -1}) { + const size_t idx = (delta > 0) ? opIdx + 1 : (opIdx > 0 ? opIdx - 1 : 0); + const auto candidate = m_intr.getEntityByIndex(idx); + if (candidate.name == "File" || candidate.name == "Dir") { + typeName = candidate.name; + rhsEntity = candidate; + break; + } + } + if (typeName.empty()) { + typeName = Interpreter::valueAsString(rightOpd); + rhsEntity = m_intr.getEntityByIndex(opIdx + 1); + } + const std::string initBody = Interpreter::valueAsInitBody(rhsEntity.value); + + if (typeName == "File") { if (!initBody.empty()) { - Interpreter intrCopy = m_intr; - std::string err; - if (intrCopy.parseScript(initBody, err)) - m_fileHandler[leftOpd] = intrCopy.runScript(); + m_fileHandler[leftKey] = evalInitBodyPath(m_intr, initBody); + } + else { + m_fileHandler[leftKey]; } - else - m_fileHandler[leftOpd]; } - else if (rightOpd == "Dir") { - auto initBody = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex + 1).value; + else if (typeName == "Dir") { if (!initBody.empty()) { - Interpreter intrCopy = m_intr; - std::string err; - if (intrCopy.parseScript(initBody, err)) - m_dirHandler[leftOpd] = intrCopy.runScript(); + m_dirHandler[leftKey] = evalInitBodyPath(m_intr, initBody); + } + else { + m_dirHandler[leftKey]; } - else - m_dirHandler[leftOpd]; } - else if (currOperator){ + else if (currOperator) { return currOperator(leftOpd, rightOpd); } return leftOpd; }, 100); currOperator = ir.getUserOperator("."); - ir.addOperator(".", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - if (m_fileHandler.count(leftOpd) || m_dirHandler.count(leftOpd)) { + ir.addOperator(".", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::valueAsString(leftOpd); + if (m_fileHandler.count(leftKey) || m_dirHandler.count(leftKey)) { return rightOpd; } else if (currOperator) { return currOperator(leftOpd, rightOpd); } - return leftOpd + '.' + rightOpd; + return Interpreter::valueAsString(leftOpd) + '.' + Interpreter::valueAsString(rightOpd); }, 0); - + auto currFunction = ir.getUserFunction("read"); - ir.addFunction("read", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("read", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - if (m_fileHandler.count(contrName)) { - std::ifstream fs(m_fileHandler[contrName]); + if (m_fileHandler.count(contrName)) { + std::ifstream fs(Interpreter::valueAsString(m_fileHandler[contrName])); if (fs.good()) { std::stringstream strStream; strStream << fs.rdbuf(); return strStream.str(); } else { - return "0"; + return int64_t{0}; } } else if (currFunction) { return currFunction(args); } - return ""; + return std::string{}; }); currFunction = ir.getUserFunction("write"); - ir.addFunction("write", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("write", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); if (m_fileHandler.count(contrName)) { - std::ofstream fs(m_fileHandler[contrName]); + std::ofstream fs(Interpreter::valueAsString(m_fileHandler[contrName])); if (fs.good() && !args.empty()) { - fs << args[0]; - return "1"; + fs << Interpreter::valueToString(args[0]); + return int64_t{1}; } else - return "0"; + return int64_t{0}; } else if (currFunction) { return currFunction(args); } - return ""; + return std::string{}; }); currFunction = ir.getUserFunction("append"); - ir.addFunction("append", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("append", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); if (m_fileHandler.count(contrName)) { - std::ofstream fs(m_fileHandler[contrName], std::ios_base::app); + std::ofstream fs(Interpreter::valueAsString(m_fileHandler[contrName]), std::ios_base::app); if (fs.good() && !args.empty()) { - fs << args[0]; - return "1"; + fs << Interpreter::valueToString(args[0]); + return int64_t{1}; } else - return "0"; + return int64_t{0}; } else if (currFunction) { return currFunction(args); } - return "0"; + return int64_t{0}; }); currFunction = ir.getUserFunction("exist"); - ir.addFunction("exist", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("exist", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); if (m_fileHandler.count(contrName)) { - std::ifstream fs(m_fileHandler[contrName]); - return fs.good() ? "1" : "0"; + std::ifstream fs(Interpreter::valueAsString(m_fileHandler[contrName])); + return fs.good(); } else if (m_dirHandler.count(contrName)) { struct stat info; - if (stat(m_dirHandler[contrName].c_str(), &info) != 0) // cannot access - return "0"; + if (stat(Interpreter::valueAsString(m_dirHandler[contrName]).c_str(), &info) != 0) + return false; else if (info.st_mode & S_IFDIR) - return "1"; + return true; else - return "0"; + return false; } else if (currFunction) { return currFunction(args); } - return "0"; + return false; }); currFunction = ir.getUserFunction("remove"); - ir.addFunction("remove", [this, currFunction](const std::vector& args) ->std::string { + ir.addFunction("remove", [this, currFunction](const std::vector& args) -> Interpreter::Value { std::string contrName = getContrNameByFunction(m_intr.currentEntity().beginIndex); - if (m_fileHandler.count(contrName) || m_dirHandler.count(contrName)) { - return remove(m_fileHandler[contrName].c_str()) == 0 ? "1" : "0"; + if (m_fileHandler.count(contrName)) { + return remove(Interpreter::valueAsString(m_fileHandler[contrName]).c_str()) == 0; + } + if (m_dirHandler.count(contrName)) { + return remove(Interpreter::valueAsString(m_dirHandler[contrName]).c_str()) == 0; } else if (currFunction) { return currFunction(args); } - return "0"; + return false; }); } std::string getContrNameByFunction(size_t funcBeginIndex){ - + std::string out; if ((funcBeginIndex - 1 >= 0) && (m_intr.getEntityByIndex(funcBeginIndex - 1).name == ".")){ if (funcBeginIndex - 2 >= 0) @@ -172,7 +207,7 @@ namespace InterpreterBaseLib { protected: Interpreter& m_intr; - std::map m_fileHandler; - std::map m_dirHandler; + std::map m_fileHandler; + std::map m_dirHandler; }; -} \ No newline at end of file +} diff --git a/include/base_library/structure.h b/include/base_library/structure.h index 80437f0..bddf778 100644 --- a/include/base_library/structure.h +++ b/include/base_library/structure.h @@ -1,5 +1,6 @@ #include "../../include/interpreter.h" +#include "containers.h" #include #include @@ -9,15 +10,6 @@ namespace InterpreterBaseLib { class Structure { public: - bool isNumber(const std::string& s) const { - for (auto c : s) { - if (!std::isdigit(c)) { - return false; - } - } - return !s.empty(); - } - std::vector split(const std::string& str, char sep) { std::vector res; std::istringstream iss(str); @@ -30,84 +22,82 @@ namespace InterpreterBaseLib { Structure(Interpreter& ir): m_intr(ir) - { + { auto currOperator = ir.getUserOperator("="); - ir.addOperator("=", [this, currOperator](std::string& leftOpd, std::string& rightOpd) ->std::string { - - if (rightOpd == "Struct") { - m_structContr[leftOpd] = ""; + ir.addOperator("=", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + const std::string rightKey = Interpreter::valueAsString(rightOpd); + + if (rightKey == "Struct") { + m_structContr[leftKey] = std::string{}; auto entityRight = m_intr.getEntityByIndex(m_intr.currentEntity().beginIndex + 1); - std::string& initBody = entityRight.value; + const std::string initBody = Interpreter::valueAsInitBody(entityRight.value); if (!initBody.empty()) { - - size_t ssz = initBody.size(), - cpos = 0, - cp = 0; - int bordCnt = 0; - Interpreter intrCopy = m_intr; - - while (cp < ssz) { - if (initBody[cp] == '(') ++bordCnt; - if (initBody[cp] == ')') --bordCnt; - if (((initBody[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { - - if (cp == ssz - 1) ++cp; - - auto args = split(initBody.substr(cpos, cp - cpos), ':'); - std::string err; - if ((args.size() > 1) && intrCopy.parseScript(args[1], err)) - m_structContr[leftOpd + '.' + args[0]] = intrCopy.runScript(); - else if (!args.empty()) - m_structContr[leftOpd + '.' + args[0]] = ""; - - cpos = cp + 1; + foreachCommaInitBody(initBody, [&](const std::string& segment) { + auto args = split(segment, ':'); + Interpreter::Error err; + if ((args.size() > 1) && intrCopy.parseScript(args[1], err)) { + m_structContr[leftKey + '.' + args[0]] = intrCopy.runScript().first; + } + else if (!args.empty()) { + m_structContr[leftKey + '.' + args[0]] = std::string{}; } - ++cp; - } - } + }); + } } - else if (m_structContr.count(leftOpd)) { - m_structContr[leftOpd] = rightOpd; + else { + const std::string leftFull = Interpreter::valueAsString(leftOpd); + if (leftFull.find('.') != std::string::npos) { + m_structContr[leftFull] = rightOpd; + leftOpd = rightOpd; + return rightOpd; + } + } + if (m_structContr.count(leftKey)) { + m_structContr[leftKey] = rightOpd; return rightOpd; } else if (currOperator){ return currOperator(leftOpd, rightOpd); } - return leftOpd + '=' + rightOpd; + return Interpreter::valueAsString(leftOpd) + '=' + Interpreter::valueAsString(rightOpd); }, 100); currOperator = ir.getUserOperator("."); - ir.addOperator(".", [this, currOperator](std::string& leftOpd, std::string& rightOpd) -> std::string { - - if (m_structContr.count(leftOpd)) { + ir.addOperator(".", [this, currOperator](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + const std::string leftKey = Interpreter::operandName(m_intr, leftOpd); + const std::string rightKey = Interpreter::valueAsString(rightOpd); + + if (m_structContr.count(leftKey)) { if (isEqualOfNextOperator(m_intr.currentEntity().beginIndex)) { - if (!m_structContr.count(leftOpd + '.' + rightOpd)) - m_structContr[leftOpd + '.' + rightOpd] = ""; - return leftOpd + '.' + rightOpd; + const std::string fieldKey = leftKey + '.' + rightKey; + if (!m_structContr.count(fieldKey)) + m_structContr[fieldKey] = std::string{}; + return fieldKey; } else { - return m_structContr[leftOpd + '.' + rightOpd]; - } + return m_structContr[leftKey + '.' + rightKey]; + } } else if (currOperator) { return currOperator(leftOpd, rightOpd); } - return leftOpd + '.' + rightOpd; + return Interpreter::valueAsString(leftOpd) + '.' + Interpreter::valueAsString(rightOpd); }, 0); } bool isEqualOfNextOperator(size_t beginIndex){ - - return m_intr.getEntityByIndex(beginIndex + 2).name == "="; + + return m_intr.getEntityByIndex(beginIndex + 2).name == "="; } protected: Interpreter& m_intr; - std::map m_structContr; + std::map m_structContr; }; -} \ No newline at end of file +} diff --git a/include/base_library/types.h b/include/base_library/types.h index 6e94838..a491c94 100644 --- a/include/base_library/types.h +++ b/include/base_library/types.h @@ -5,26 +5,27 @@ namespace InterpreterBaseLib { class Types { public: - + Types(Interpreter& ir): m_intr(ir) - { - ir.addOperator(":", [this](std::string& leftOpd, std::string& rightOpd) ->std::string { - - m_types[leftOpd] = rightOpd; + { + ir.addOperator(":", [this](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { + m_types[Interpreter::valueAsString(leftOpd)] = rightOpd; return leftOpd; }, 0); - ir.addFunction("type", [this](const std::vector& args) ->std::string { - - if (!args.empty() && m_types.count(args[0])){ - return m_types[args[0]]; + ir.addFunction("type", [this](const std::vector& args) -> Interpreter::Value { + if (!args.empty()) { + const std::string name = Interpreter::valueAsString(args[0]); + if (m_types.count(name)) { + return m_types[name]; + } } - return ""; + return std::string{}; }); } protected: Interpreter& m_intr; - std::map m_types; + std::map m_types; }; } diff --git a/include/interpreter.h b/include/interpreter.h index 3fec6ef..34e000f 100644 --- a/include/interpreter.h +++ b/include/interpreter.h @@ -26,26 +26,42 @@ #pragma once #include +#include #include #include #include +#include +#include #include +#include +#include class Interpreter { public: - using UserFunction = std::function& args)>; - using UserOperator = std::function; - - explicit + enum class ControlFlow { Break, Continue }; + + struct Error { + enum class Kind { Parse, Runtime } kind = Kind::Parse; + size_t position = 0; + std::string message; + explicit operator bool() const { return !message.empty(); } + }; + + using Value = std::variant; + using CmdResult = std::pair; + using UserFunction = std::function& args)>; + using UserOperator = std::function; + + explicit Interpreter(); ~Interpreter(); Interpreter(const Interpreter&); - Interpreter(Interpreter&&); + Interpreter(Interpreter&&) noexcept; Interpreter& operator=(const Interpreter&); - Interpreter& operator=(Interpreter&&); + Interpreter& operator=(Interpreter&&) noexcept; /// Add function /// @param name @@ -65,40 +81,38 @@ class Interpreter { /// return true - ok bool addAttribute(const std::string& name); - /// Execute script (== parseScript + runScript) + /// Execute script (== parseScript + runScript). Re-parses when script text changes. /// @param script - /// @return result or error - std::string cmd(std::string script); + /// @return result and parse error (if any) + CmdResult cmd(std::string script); - /// Parse script - /// @param script - /// return true - ok - bool parseScript(std::string script, std::string& outErr); + /// Parse script; on failure outErr carries kind, position, and message. + bool parseScript(std::string script, Error& outErr); - /// Run script - /// return result - std::string runScript(); + /// Run parsed script; second is runtime error (empty on success). + /// Use after parseScript on the same script text (AST cached via m_prevScript). + CmdResult runScript(); /// All variables /// @return vname, value - std::map allVariables() const; + std::map allVariables() const; /// Value of variable /// @param vname /// @return value - std::string variable(const std::string& vname) const; + Value variable(const std::string& vname) const; /// Run of user function /// @param fname /// @param args /// @return result - std::string runFunction(const std::string& fname, const std::vector& args); + Value runFunction(const std::string& fname, const std::vector& args); /// Set value of variable /// @param vname /// @param value /// @return true - ok - bool setVariable(const std::string& vname, const std::string& value); + bool setVariable(const std::string& vname, const Value& value); /// Set macro /// @param mname @@ -131,8 +145,11 @@ class Interpreter { VARIABLE, VALUE, GOTO, + MACRO, }; + static constexpr size_t NoLinkIndex = static_cast(-1); + /// Internal object struct Entity { size_t beginIndex; @@ -140,7 +157,8 @@ class Interpreter { size_t bodyEndIndex; EntityType type; std::string name; - std::string value; + Value value{std::string{}}; + size_t linkIndex = NoLinkIndex; }; /// Get all entities @@ -165,7 +183,29 @@ class Interpreter { //// Reflection part //////////////////////////////////// + //// Value helpers //////////////////////////////////// + + static std::string valueToString(const Value& v); + static Value valueFromLiteral(std::string_view s); + static bool valueIsTruthy(const Value& v); + static std::string valueAsString(const Value& v); + static std::string valueAsInitBody(const Value& v); + static std::string operandName(Interpreter& ir, const Value& opd); + static bool valueIsInteger(const Value& v); + static bool valueIsNumeric(const Value& v); + static int64_t valueAsInt64(const Value& v); + static std::vector makeParam(const std::string& s); + static std::vector makeParam(std::string_view s); + static std::string paramAsString(const std::vector& params); + static Value valueFromParams(const std::vector& params, const Value& fallback); + static std::string cmdResultToString(const CmdResult& r); + static bool hasError(const Error& err); + static bool isParseError(const Error& err); + static std::string_view valueAsStringView(const Value& v); + static bool valueEquals(const Value& a, const Value& b); + static int valueCompare(const Value& a, const Value& b); + private: class Impl; - Impl* m_d = nullptr; + std::unique_ptr m_d; }; \ No newline at end of file diff --git a/include/interpreter_c.h b/include/interpreter_c.h index 0d72802..ed8d02a 100644 --- a/include/interpreter_c.h +++ b/include/interpreter_c.h @@ -36,13 +36,20 @@ #define INTERPRETER_API #endif +#include #include #if defined(__cplusplus) extern "C" { #endif /* __cplusplus */ -typedef enum BOOL{ FALSE = 0, TRUE = 1}BOOL; +#define IR_PARSE_ERR_SIZE 256 + +#ifndef BOOL +typedef enum { IR_FALSE = 0, IR_TRUE = 1 } BOOL; +#define FALSE IR_FALSE +#define TRUE IR_TRUE +#endif typedef char*(*irUserFunction)(char** args, size_t count); typedef char*(*irUserOperator)(char** ioLeftOperand, char** ioRightOperand); @@ -70,10 +77,8 @@ INTERPRETER_API BOOL irAddOperator(HIntr, char* name, irUserOperator uopr, uint3 /// @return result or error INTERPRETER_API char* irCmd(HIntr, char* script); -/// Parse script -/// @param script -/// return true - ok -INTERPRETER_API BOOL irParseScript(HIntr, char* script, char* outErr /*sz 256*/); +/// Parse script; outErr optional; outErrSize buffer size (use IR_PARSE_ERR_SIZE when outErr set); outPos optional. +INTERPRETER_API BOOL irParseScript(HIntr, char* script, char* outErr, size_t outErrSize, size_t* outPos); /// Run script /// return result @@ -103,8 +108,10 @@ INTERPRETER_API BOOL irGotoOnLabel(HIntr, char* lname); /// Exit from script INTERPRETER_API void irExitFromScript(HIntr); - - + +/// Free strings returned by irCmd, irRunScript, irVariable (not required for NULL). +INTERPRETER_API void irFree(char* p); + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/src/Makefile b/src/Makefile index ffc8b67..c066457 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,12 @@ +CXXFLAGS = -std=c++17 -O2 -Wall LDFLAGS = -lgtest -pthread -all: - g++ test.cpp interpreter.cpp $(LDFLAGS) -o test +all: test + +test: + g++ $(CXXFLAGS) test.cpp interpreter.cpp $(LDFLAGS) -o test + +bench: + g++ $(CXXFLAGS) bench.cpp interpreter.cpp -o bench + +.PHONY: all test bench diff --git a/src/bench.cpp b/src/bench.cpp new file mode 100644 index 0000000..7974f80 --- /dev/null +++ b/src/bench.cpp @@ -0,0 +1,137 @@ +// +// Interpreter Project — informal micro-benchmarks (local use). +// +#include "../include/interpreter.h" +#include "../include/base_library/arithmetic_operations.h" +#include "../include/base_library/comparison_operations.h" +#include "../include/base_library/containers.h" + +#include +#include +#include +#include +#include + +namespace { + +using Clock = std::chrono::steady_clock; + +struct BenchHost { + Interpreter ir; + InterpreterBaseLib::ArithmeticOperations ao; + InterpreterBaseLib::ComparisonOperations co; + InterpreterBaseLib::Container bc; + + BenchHost() + : ir(), + ao(ir), + co(ir), + bc(ir) { + ir.addFunction("summ", [](const std::vector& args) -> Interpreter::Value { + int64_t res = 0; + for (const auto& v : args) { + res += Interpreter::valueAsInt64(v); + } + return res; + }); + ir.setMacro("inc", "$a = $a + 1;"); + } +}; + +void runBench(const char* label, int iterations, const std::function& fn) { + constexpr int kWarmup = 50; + for (int i = 0; i < kWarmup; ++i) { + fn(); + } + + const auto t0 = Clock::now(); + for (int i = 0; i < iterations; ++i) { + fn(); + } + const auto t1 = Clock::now(); + + const double sec = std::chrono::duration(t1 - t0).count(); + const double ops = iterations / sec; + std::printf(" %-28s %8d iter %9.3f ms %10.0f ops/s\n", + label, iterations, sec * 1000.0, ops); +} + +void benchCmdChanging(Interpreter& ir, int iterations) { + static const char* scripts[] = { + "$a=0;$a+=1;$a", + "$a=0;$a+=2;$a", + "$a=0;$a+=3;$a", + }; + runBench("cmd (varying script)", iterations, [&]() { + static int idx = 0; + const auto r = ir.cmd(scripts[idx % 3]); + ++idx; + if (Interpreter::hasError(r.second)) { + return; + } + }); +} + +void benchCmdSame(Interpreter& ir, const char* script, const char* label, int iterations) { + runBench(label, iterations, [&]() { + const auto r = ir.cmd(script); + if (Interpreter::hasError(r.second)) { + return; + } + }); +} + +void benchParseRunSame(Interpreter& ir, const char* script, int iterations) { + runBench("parseScript+runScript (cached)", iterations, [&]() { + Interpreter::Error err; + if (!ir.parseScript(script, err)) { + return; + } + ir.runScript(); + }); +} + +} // namespace + +int main() { + BenchHost host; + Interpreter& ir = host.ir; + + static const char kShortLoop[] = + "$a = 5; $b = 2; while ($a > 1) { $a = $a - 1; $b = summ($b, $a); if ($a < 4) { break; } } $b;"; + static const char kNested[] = + "$a = 5; $b = 2; $c = summ($a, ($a + ($a * ($b + $a))), summ(5)); $c;"; + static const char kVector[] = + "a = Vector{1 + 2, 2 + 3, 3 + 4}; a[2];"; + static const char kMap[] = + "$b = 12; c = Map{ one : $b + 5, two : 2 }; c[\"one\"];"; + static const char kMacro[] = + "$a = 5; #inc; #inc; $a;"; + + constexpr int kIter = 5000; + + std::printf("interpreter bench (O2, %d iterations per case)\n\n", kIter); + + benchCmdSame(ir, kShortLoop, "cmd short loop", kIter); + benchCmdSame(ir, kNested, "cmd nested expr", kIter); + benchCmdSame(ir, kVector, "cmd Vector literal", kIter / 5); + benchCmdSame(ir, kMap, "cmd Map literal", kIter / 5); + benchCmdSame(ir, kMacro, "cmd setMacro #inc", kIter); + benchCmdChanging(ir, kIter / 10); + benchParseRunSame(ir, kShortLoop, kIter); + + std::printf("\ncopy: "); + const auto t0 = Clock::now(); + constexpr int kCopyIter = 500; + for (int i = 0; i < kCopyIter; ++i) { + Interpreter copy = ir; + (void)copy; + } + const auto t1 = Clock::now(); + const double copySec = std::chrono::duration(t1 - t0).count(); + std::printf("%d Interpreter copies in %.3f ms (%.0f /s)\n", + kCopyIter, copySec * 1000.0, kCopyIter / copySec); + + std::printf("\n(done — for regression comparison only, not CI)\n"); + return 0; +} diff --git a/src/interpreter.cpp b/src/interpreter.cpp index 2f3434b..73c73a7 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -25,32 +25,61 @@ #include "../include/interpreter.h" #include +#include #include #include +#include +#include #include - -using namespace std; +#include +#include +#include + +// --- ir::detail (declarations) --- + +namespace ir { +namespace detail { + +constexpr size_t kNoIndex = static_cast(-1); + +bool isDigits(std::string_view s); +std::optional parseInteger(std::string_view s); +std::optional parseNumber(std::string_view s); +bool isIdentContinue(unsigned char c); +bool matchKeywordAt(std::string_view script, size_t cpos, std::string_view kw); +Interpreter::Value parseLiteralText(std::string_view s); +Interpreter::Value parseQuotedLiteral(std::string s); +Interpreter::Value valueOfValueExpr(const Interpreter::Value& result, + const std::vector& params); +const std::string& paramKey(const std::vector& params); +size_t paramIndex(const std::vector& params); +bool isBreakValue(const Interpreter::Value& v); +bool isContinueValue(const Interpreter::Value& v); + +} // namespace detail +} // namespace ir class Interpreter::Impl { public: Impl() = default; - bool addFunction(const string& name, Interpreter::UserFunction ufunc); - bool addOperator(const string& name, Interpreter::UserOperator uopr, uint32_t priority); - bool addAttribute(const string& name); - string cmd(string script); - bool parseScript(string script, string& outErr); - string runScript(); - std::map allVariables() const; - std::string variable(const std::string& vname) const; - std::string runFunction(const std::string& fname, const std::vector& args); - bool setVariable(const std::string& vname, const std::string& value); + bool addFunction(const std::string& name, Interpreter::UserFunction ufunc); + bool addOperator(const std::string& name, Interpreter::UserOperator uopr, uint32_t priority); + bool addAttribute(const std::string& name); + Interpreter::CmdResult cmd(std::string script); + bool parseScript(std::string script, Interpreter::Error& outErr); + Interpreter::Value evalScript(); + std::pair runScript(); + std::map allVariables() const; + Interpreter::Value variable(const std::string& vname) const; + Interpreter::Value runFunction(const std::string& fname, const std::vector& args); + bool setVariable(const std::string& vname, const Interpreter::Value& value); bool setMacro(const std::string& mname, const std::string& script); bool gotoOnLabel(const std::string& lname); void exitFromScript(); - std::vector allEntities(); - Interpreter::Entity currentEntity(); - Interpreter::Entity getEntityByIndex(size_t beginIndex); - vector getAttributeByIndex(size_t beginIndex); + std::vector allEntities() const; + Interpreter::Entity currentEntity() const; + Interpreter::Entity getEntityByIndex(size_t beginIndex) const; + std::vector getAttributeByIndex(size_t beginIndex) const; bool gotoOnEntity(size_t iBegin); Interpreter::UserFunction getUserFunction(const std::string& fname); Interpreter::UserOperator getUserOperator(const std::string& oname); @@ -77,248 +106,335 @@ class Interpreter::Impl { size_t iConditionEnd; size_t iBodyEnd; size_t iOperator; - string params; - string result; + std::vector params; + Interpreter::Value result{std::string{}}; }; - struct Operatr { + struct Operator { size_t inx, priority, iLOpr, iROpr; }; - map m_ufunc; - map> m_uoper; // operator, priority - map m_var; - map m_macro; - map m_label; - set m_attribute; - map> m_exprAttribute; - map> m_soper; - map m_internFunc; - vector m_expr; - string m_err, m_prevScript; - size_t m_gotoIndex = size_t(-1); + std::map m_ufunc; + std::map> m_uoper; + std::map m_var; + std::map m_macro; + std::map m_label; + std::set m_attribute; + std::map> m_exprAttribute; + std::map> m_soper; + std::map m_internFunc; + std::vector m_expr; + Interpreter::Error m_parseErr; + std::string m_prevScript; + size_t m_gotoIndex = ir::detail::kNoIndex; size_t m_currentIndex = 0; bool m_exit = false; - string calcOperation(Keyword mainKeyword, size_t iExpr); - string calcFunction(size_t iExpr); - string calcCondition(size_t iExpr); - string calcExpression(size_t iBegin, size_t iEnd); - void calcOperatorPriority(size_t iBegin, size_t iEnd, vector& oprs); - - bool parseInstructionScript(string& script, size_t gpos); - bool parseExpressionScript(string& script, size_t gpos); - bool parseArgumentScript(string& script, size_t gpos); - bool parseMacroArgs(const string& args, string& macro); - - void cleaningScript(string& script) const; - bool checkScript(const string& script, string& err) const; - - bool startWith(const string& str, size_t pos, const string& begin) const; - bool isNumber(const string& s) const; - bool isFindKeySymbol(const string& script, size_t cpos, size_t maxpos) const; - Keyword keywordByName(const string& oprName) const; + Interpreter::Value calcOperation(Keyword mainKeyword, size_t iExpr); + Interpreter::Value calcFunction(size_t iExpr); + Interpreter::Value calcCondition(size_t iExpr); + Interpreter::Value calcExpression(size_t iBegin, size_t iEnd); + void calcOperatorPriority(size_t iBegin, size_t iEnd, std::vector& oprs); + + bool parseInstructionScript(std::string& script, size_t gpos); + bool parseExpressionScript(std::string& script, size_t gpos); + bool parseArgumentScript(std::string& script, size_t gpos); + bool parseExprPrimary(std::string& script, size_t& cpos, size_t& iExpr, size_t gpos, bool& breakLoop); + bool parseExprUnary(std::string& script, size_t& cpos, size_t& iExpr, size_t gpos); + bool parseExprOperator(std::string& script, size_t& cpos, size_t& iExpr, size_t gpos); + bool skipOneSpareSymbol(const std::string& script, size_t& cpos); + bool parseControlBody(std::string& script, size_t& cpos, size_t gpos, + const char* emptyBodyMsg, const char* invalidBodyMsg); + bool parseInstrExprOrOpCall(std::string& script, size_t& cpos, size_t gpos, size_t& iExpr); + bool parseInstrControlFlow(std::string& script, size_t& cpos, size_t gpos, size_t& iExpr, size_t& iIF); + bool parseInstrElse(std::string& script, size_t& cpos, size_t gpos, size_t& iExpr, size_t iIF); + bool parseInstrMacro(std::string& script, size_t& cpos, size_t gpos); + bool expandAllMacros(std::string& script); + bool parseInstrGotoLabel(std::string& script, size_t& cpos, size_t gpos, size_t& iExpr); + bool parseInstrFunctionDecl(std::string& script, size_t& cpos, size_t gpos); + bool parseInstrStatementExpr(std::string& script, size_t& cpos, size_t gpos, size_t& iExpr); + void emplaceSyntheticNegatePrefix(size_t& iExpr); + void mergeInternFuncs(Impl& callee) const; + void bindCallerScope(Impl& callee, const std::vector& args, + std::set& outScopeVars) const; + void writeBackScope(Impl& callee, const std::set& scopeVars); + bool parseMacroArgs(const std::string& args, std::string& macro); + + void emplaceExpr(size_t& iExpr, Keyword keyw, + std::vector params = {}, + Interpreter::Value result = std::string{}); + void emplaceExprAt(size_t iExpr, Keyword keyw, + std::vector params = {}, + Interpreter::Value result = std::string{}); + enum class ParseInitOutcome { NotHandled, Handled, BreakLoop }; + ParseInitOutcome parseNamedInitBody(Keyword entityKw, bool bindVariable, + std::string& script, size_t& cpos, size_t& iExpr, + size_t posmem, const std::string& oprName); + Interpreter::Entity makeEntity(size_t index, const Expression& exp) const; + Impl makeChildImplForParse() const; + bool failParse(size_t cpos, size_t gpos, const char* what); + static bool failCheck(std::string& err, const char* what); + + void cleaningScript(std::string& script) const; + bool checkScript(const std::string& script, std::string& err) const; + + bool startWith(std::string_view str, size_t pos, std::string_view begin) const; + bool isFindKeySymbol(const std::string& script, size_t cpos, size_t maxpos) const; + Interpreter::Value evalOperand(size_t iExpr); + Interpreter::Value variableByKey(const std::string& key) const; + Keyword keywordByName(std::string_view oprName) const; Interpreter::EntityType keywordToEntityType(Keyword keyw) const; - string getNextParam(const string& script, size_t& cpos, char symb) const; - string getOperatorAtFirst(const string& script, size_t& cpos) const; - string getFunctionAtFirst(const string& script, size_t& cpos) const; - string getMacroAtFirst(const string& script, size_t& cpos) const; - string getAttributeAtFirst(const string& script, size_t& cpos) const; - string getNextOperator(const string& script, size_t& cpos) const; - string getIntroScript(const string& script, size_t& cpos, char symbBegin, char symbEnd) const; + std::string getNextParam(const std::string& script, size_t& cpos, char symb) const; + std::string getOperatorAtFirst(const std::string& script, size_t& cpos) const; + std::string getFunctionAtFirst(const std::string& script, size_t& cpos) const; + std::string peekFunctionCall(const std::string& script, size_t cpos) const; + std::string getMacroAtFirst(const std::string& script, size_t& cpos) const; + std::string getAttributeAtFirst(const std::string& script, size_t& cpos) const; + std::string getNextOperator(const std::string& script, size_t& cpos) const; + std::string getIntroScript(const std::string& script, size_t& cpos, char symbBegin, char symbEnd) const; + bool isUnaryMinusAt(const std::string& script, size_t cpos) const; + bool isUnaryMinusContext(const std::string& script, size_t cpos) const; }; -string Interpreter::Impl::cmd(string script) { - - string err; - if (!parseScript(move(script), err)) - return err; +void Interpreter::Impl::emplaceExpr(size_t& iExpr, Keyword keyw, + std::vector params, Interpreter::Value result) { + m_expr.emplace_back(Expression{keyw, iExpr, iExpr, ir::detail::kNoIndex, + std::move(params), std::move(result)}); + ++iExpr; +} + +void Interpreter::Impl::emplaceExprAt(size_t iExpr, Keyword keyw, + std::vector params, Interpreter::Value result) { + m_expr.emplace_back(Expression{keyw, iExpr, iExpr, ir::detail::kNoIndex, + std::move(params), std::move(result)}); +} + +// --- eval --- - return runScript(); +Interpreter::CmdResult Interpreter::Impl::cmd(std::string script) { + Interpreter::Error err; + if (!parseScript(std::move(script), err)) { + return {Interpreter::Value{std::string{}}, std::move(err)}; + } + auto run = runScript(); + return {std::move(run.first), std::move(run.second)}; } -bool Interpreter::Impl::parseScript(string script, string& err) { +bool Interpreter::Impl::parseScript(std::string script, Interpreter::Error& outErr) { + outErr = {}; + m_parseErr = {}; cleaningScript(script); if (script.empty()) { - err = "Error: empty script"; + outErr.kind = Interpreter::Error::Kind::Parse; + outErr.message = "Error: empty script"; return false; } - if (script.back() != ';') script += ';'; + if (script.back() != ';') { + script += ';'; + } + + if (!expandAllMacros(script)) { + m_prevScript.clear(); + if (!m_parseErr.message.empty()) { + outErr = m_parseErr; + } + else { + outErr.kind = Interpreter::Error::Kind::Parse; + outErr.message = "Error: macro expansion failed"; + } + return false; + } if (m_prevScript != script) { m_prevScript = script; m_expr.clear(); m_label.clear(); m_soper.clear(); - m_err.clear(); - if (!checkScript(script, m_err) || !parseInstructionScript(script, 0)) { + m_parseErr = {}; + std::string checkErr; + if (!checkScript(script, checkErr) || !parseInstructionScript(script, 0)) { m_prevScript.clear(); - err = m_err; + if (!m_parseErr.message.empty()) { + outErr = m_parseErr; + } + else { + outErr.kind = Interpreter::Error::Kind::Parse; + outErr.message = checkErr; + } return false; } } return true; } -string Interpreter::Impl::runScript() { +std::pair Interpreter::Impl::runScript() { + return {evalScript(), {}}; +} + +Interpreter::Value Interpreter::Impl::evalScript() { for (auto& ex : m_expr) - ex.iOperator = size_t(-1); + ex.iOperator = ir::detail::kNoIndex; - string result; + Interpreter::Value result; m_exit = false; for (size_t i = 0; i < m_expr.size();) { result = calcOperation(m_expr[i].keyw, i); - i = max(m_expr[i].iConditionEnd, m_expr[i].iBodyEnd); + i = std::max(m_expr[i].iConditionEnd, m_expr[i].iBodyEnd); - if (m_gotoIndex != size_t(-1)) { + if (m_gotoIndex != ir::detail::kNoIndex) { for (size_t j = m_gotoIndex; j < i; ++j) - m_expr[j].iOperator = size_t(-1); + m_expr[j].iOperator = ir::detail::kNoIndex; i = m_gotoIndex; - m_gotoIndex = size_t(-1); + m_gotoIndex = ir::detail::kNoIndex; } if (m_exit) break; } return result; } -void Interpreter::Impl::cleaningScript(string& script) const { +void Interpreter::Impl::cleaningScript(std::string& script) const { - // del comments - size_t commp = script.find("//"); - while (commp != string::npos) { - size_t endStr = script.find("\n", commp); - if (endStr != string::npos){ - script.erase(commp, endStr - commp + 1); - commp = script.find("//", endStr); - }else{ - script.erase(commp); - break; + std::string out; + out.reserve(script.size()); + bool inString = false; + for (size_t i = 0; i < script.size();) { + if (!inString && i + 1 < script.size() && script[i] == '/' && script[i + 1] == '/') { + i += 2; + while (i < script.size() && script[i] != '\n') { + ++i; + } + continue; } - } - - // save string "value" - vector strVals; - size_t beginStr = script.find("\""); - while (beginStr != string::npos) { - size_t endStr = script.find("\"", beginStr + 1); - if (endStr != string::npos) { - strVals.emplace_back(script.substr(beginStr, endStr - beginStr + 1)); - beginStr = script.find("\"", endStr + 1); + if (script[i] == '"') { + if (inString && i > 0 && script[i - 1] == '\\') { + out += script[i++]; + continue; + } + inString = !inString; + out += script[i++]; + continue; } - else break; - } - - // cleaning - script.erase(remove(script.begin(), script.end(), '\n'), script.end()); - script.erase(remove(script.begin(), script.end(), '\t'), script.end()); - script.erase(remove(script.begin(), script.end(), '\v'), script.end()); - script.erase(remove(script.begin(), script.end(), '\f'), script.end()); - script.erase(remove(script.begin(), script.end(), '\r'), script.end()); - script.erase(remove(script.begin(), script.end(), ' '), script.end()); - - // restore string "value" - beginStr = script.find("\""); - size_t iVal = 0; - while ((beginStr != string::npos) && (iVal < strVals.size())) { - size_t endStr = script.find("\"", beginStr + 1); - if (endStr != string::npos) { - script.replace(beginStr, endStr - beginStr + 1, strVals[iVal]); - beginStr = script.find("\"", beginStr + strVals[iVal].size() + 1); - ++iVal; + if (inString) { + out += script[i++]; + continue; } - else break; + const char c = script[i]; + if (c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\f' || c == '\r') { + ++i; + continue; + } + out += c; + ++i; } + script = std::move(out); } -bool Interpreter::Impl::checkScript(const string& script, string& err) const { - -#define CHECK_SCENAR_RETURN(condition) \ - if (condition){ \ - if (err.empty()) err = "Error check script: " + string(#condition); \ - return false; \ - } - - CHECK_SCENAR_RETURN(std::count(script.begin(), script.end(), '{') != std::count(script.begin(), script.end(), '}')); - CHECK_SCENAR_RETURN(std::count(script.begin(), script.end(), '(') != std::count(script.begin(), script.end(), ')')); - CHECK_SCENAR_RETURN(std::count(script.begin(), script.end(), '"') % 2 != 0); - -#undef CHECK_SCENAR_RETURN +bool Interpreter::Impl::failCheck(std::string& err, const char* what) { // NOLINT(readability-make-member-function-const) + if (err.empty()) { + err = std::string("Error check script: ") + what; + } + return false; +} +bool Interpreter::Impl::checkScript(const std::string& script, std::string& err) const { + if (std::count(script.begin(), script.end(), '{') != std::count(script.begin(), script.end(), '}')) { + return failCheck(err, "{ } mismatch"); + } + if (std::count(script.begin(), script.end(), '(') != std::count(script.begin(), script.end(), ')')) { + return failCheck(err, "( ) mismatch"); + } + if (std::count(script.begin(), script.end(), '"') % 2 != 0) { + return failCheck(err, "unclosed string"); + } return true; } -bool Interpreter::Impl::addFunction(const string& name, Interpreter::UserFunction ufunc) { +bool Interpreter::Impl::addFunction(const std::string& name, Interpreter::UserFunction ufunc) { if (name.empty() || (keywordByName(name) != Keyword::INSTRUCTION) || isFindKeySymbol(name, 0, name.size())) return false; - m_ufunc[name] = move(ufunc); + m_ufunc[name] = std::move(ufunc); return true; } -bool Interpreter::Impl::addOperator(const string& name, Interpreter::UserOperator uopr, uint32_t priority) { +bool Interpreter::Impl::addOperator(const std::string& name, Interpreter::UserOperator uopr, uint32_t priority) { if (name.empty() || (keywordByName(name) != Keyword::INSTRUCTION) || isFindKeySymbol(name, 0, name.size())) return false; - m_uoper[name] = {move(uopr), priority}; + m_uoper[name] = {std::move(uopr), priority}; return true; } -bool Interpreter::Impl::addAttribute(const string& name) { +bool Interpreter::Impl::addAttribute(const std::string& name) { m_attribute.insert(name); return true; } -std::map Interpreter::Impl::allVariables() const { +std::map Interpreter::Impl::allVariables() const { return m_var; } -std::string Interpreter::Impl::variable(const std::string& vname) const { - return m_var.find(vname) != m_var.end() ? m_var.at(vname) : ""; +Interpreter::Value Interpreter::Impl::variable(const std::string& vname) const { + auto it = m_var.find(vname); + return it != m_var.end() ? it->second : Interpreter::Value{std::string{}}; } -bool Interpreter::Impl::setVariable(const std::string& vname, const std::string& value) { +bool Interpreter::Impl::setVariable(const std::string& vname, const Interpreter::Value& value) { m_var[vname] = value; return true; } -std::string Interpreter::Impl::runFunction(const std::string& fname, const std::vector& args) { - return m_ufunc.count(fname) ? m_ufunc[fname](args) : ""; +Interpreter::Value Interpreter::Impl::runFunction(const std::string& fname, const std::vector& args) { + return m_ufunc.count(fname) ? m_ufunc[fname](args) : Interpreter::Value{std::string{}}; } bool Interpreter::Impl::setMacro(const std::string& mname, const std::string& script) { - m_macro[mname] = script; + if (mname.empty()) { + return false; + } + const std::string key = (mname.front() == '#') ? mname : "#" + mname; + m_macro[key] = script; return true; } bool Interpreter::Impl::gotoOnLabel(const std::string& lname) { - bool exist = m_label.find(lname) != m_label.end(); - if (exist) - m_gotoIndex = m_label[lname]; - return exist; + const auto it = m_label.find(lname); + if (it == m_label.end()) { + return false; + } + m_gotoIndex = it->second; + return true; } void Interpreter::Impl::exitFromScript() { m_exit = true; } -std::vector Interpreter::Impl::allEntities() { + +Interpreter::Entity Interpreter::Impl::makeEntity(size_t index, const Expression& exp) const { + Interpreter::Entity entity{ + index, exp.iConditionEnd, exp.iBodyEnd, + keywordToEntityType(exp.keyw), ir::detail::paramKey(exp.params), exp.result + }; + if (exp.keyw == Keyword::ELSE || exp.keyw == Keyword::ELSE_IF) { + entity.linkIndex = ir::detail::paramIndex(exp.params); + } + return entity; +} + +std::vector Interpreter::Impl::allEntities() const { std::vector res; - for (size_t i = 0; i < m_expr.size(); ++i) { - const auto& exp = m_expr[i]; - res.emplace_back(Interpreter::Entity{ - i, exp.iConditionEnd, exp.iBodyEnd, keywordToEntityType(exp.keyw), exp.params, exp.result - }); + res.reserve(m_expr.size()); + for (size_t i = 0; i < m_expr.size(); ++i) { + res.push_back(makeEntity(i, m_expr[i])); } return res; } -Interpreter::Entity Interpreter::Impl::currentEntity() { - if (m_currentIndex >= m_expr.size()) +Interpreter::Entity Interpreter::Impl::currentEntity() const { + if (m_currentIndex >= m_expr.size()) { return Interpreter::Entity{0}; - const auto& exp = m_expr[m_currentIndex]; - return Interpreter::Entity{ - m_currentIndex, exp.iConditionEnd, exp.iBodyEnd, keywordToEntityType(exp.keyw), exp.params, exp.result - }; + } + return makeEntity(m_currentIndex, m_expr[m_currentIndex]); } -Interpreter::Entity Interpreter::Impl::getEntityByIndex(size_t beginIndex) { - if (beginIndex >= m_expr.size()) - return Interpreter::Entity{ 0 }; - const auto& exp = m_expr[beginIndex]; - return Interpreter::Entity{ - beginIndex, exp.iConditionEnd, exp.iBodyEnd, keywordToEntityType(exp.keyw), exp.params, exp.result - }; +Interpreter::Entity Interpreter::Impl::getEntityByIndex(size_t beginIndex) const { + if (beginIndex >= m_expr.size()) { + return Interpreter::Entity{0}; + } + return makeEntity(beginIndex, m_expr[beginIndex]); } -vector Interpreter::Impl::getAttributeByIndex(size_t index) { - return m_exprAttribute.count(index) ? m_exprAttribute[index] : vector(); +std::vector Interpreter::Impl::getAttributeByIndex(size_t index) const { + const auto it = m_exprAttribute.find(index); + return it != m_exprAttribute.end() ? it->second : std::vector{}; } bool Interpreter::Impl::gotoOnEntity(size_t beginIndex) { if (beginIndex < m_expr.size()) { @@ -334,15 +450,15 @@ Interpreter::UserOperator Interpreter::Impl::getUserOperator(const std::string& return m_uoper.count(oname) ? m_uoper[oname].first : nullptr; } -string Interpreter::Impl::calcOperation(Keyword mainKeyword, size_t iExpr) { +Interpreter::Value Interpreter::Impl::calcOperation(Keyword mainKeyword, size_t iExpr) { - string g_result; + Interpreter::Value g_result; switch (mainKeyword) { case Keyword::VARIABLE: - g_result = m_var[m_expr[iExpr].params]; + g_result = variableByKey(ir::detail::paramKey(m_expr[iExpr].params)); break; case Keyword::VALUE: - g_result = m_expr[iExpr].params; + g_result = ir::detail::valueOfValueExpr(m_expr[iExpr].result, m_expr[iExpr].params); break; case Keyword::EXPRESSION: g_result = m_expr[iExpr].result = calcExpression(iExpr + 1, m_expr[iExpr].iBodyEnd); @@ -357,8 +473,11 @@ string Interpreter::Impl::calcOperation(Keyword mainKeyword, size_t iExpr) { g_result = calcCondition(iExpr); break; case Keyword::GOTO: { - if (m_label.find(m_expr[iExpr].params) != m_label.end()) - m_gotoIndex = m_label[m_expr[iExpr].params]; + const std::string& gotoLabel = ir::detail::paramKey(m_expr[iExpr].params); + auto it = m_label.find(gotoLabel); + if (it != m_label.end()) { + m_gotoIndex = it->second; + } } break; default: @@ -366,18 +485,18 @@ string Interpreter::Impl::calcOperation(Keyword mainKeyword, size_t iExpr) { } return g_result; } -string Interpreter::Impl::calcFunction(size_t iExpr) { - - string g_result; +Interpreter::Value Interpreter::Impl::calcFunction(size_t iExpr) { + + Interpreter::Value g_result; size_t iBegin = iExpr + 1; size_t iEnd = m_expr[iExpr].iConditionEnd; - vector args; + std::vector args; for (size_t i = iBegin; i < iEnd;) { if ((i + 1 == m_expr[i].iBodyEnd - 1) && ((m_expr[i + 1].keyw == Keyword::VARIABLE) || (m_expr[i + 1].keyw == Keyword::VALUE))) { if (m_expr[i + 1].keyw == Keyword::VARIABLE) - m_expr[i].result = m_var[m_expr[i + 1].params]; + m_expr[i].result = variableByKey(ir::detail::paramKey(m_expr[i + 1].params)); else - m_expr[i].result = m_expr[i + 1].params; + m_expr[i].result = ir::detail::valueOfValueExpr(m_expr[i + 1].result, m_expr[i + 1].params); } else { m_expr[i].result = calcExpression(i + 1, m_expr[i].iBodyEnd); @@ -386,61 +505,40 @@ string Interpreter::Impl::calcFunction(size_t iExpr) { i = m_expr[i].iBodyEnd; } m_currentIndex = iExpr; - const string& fname = m_expr[iExpr].params; + const std::string& fname = ir::detail::paramKey(m_expr[iExpr].params); if (m_internFunc.count(fname)) { - auto& impl = m_internFunc[fname]; - for (const auto& f : m_internFunc) { - if (!impl.m_internFunc.count(f.first) || impl.m_internFunc[f.first].m_prevScript.empty()){ - impl.m_internFunc[f.first] = f.second; - } - } + auto& impl = m_internFunc[fname]; + mergeInternFuncs(impl); std::set scopeVars; - for (const auto& var : m_var) { - if (impl.m_var.count(var.first)){ - impl.m_var[var.first] = var.second; - scopeVars.insert(var.first); - } - } - for (size_t i = 0; i < args.size(); ++i) { - impl.m_var["$" + to_string(i)] = args[i]; - } - - g_result = impl.runScript(); - - for (const auto& var : impl.m_var) { - if (scopeVars.count(var.first)){ - m_var[var.first] = var.second; - } - } + bindCallerScope(impl, args, scopeVars); + g_result = impl.evalScript(); + writeBackScope(impl, scopeVars); } else { g_result = m_ufunc[fname](args); } return g_result; } -string Interpreter::Impl::calcCondition(size_t iExpr) { +Interpreter::Value Interpreter::Impl::calcCondition(size_t iExpr) { - string g_result; + Interpreter::Value g_result; size_t iBegin = iExpr + 1; size_t iCondEnd = m_expr[iExpr].iConditionEnd; size_t iBodyEnd = m_expr[iExpr].iBodyEnd; if ((m_expr[iExpr].keyw == Keyword::ELSE) || (m_expr[iExpr].keyw == Keyword::ELSE_IF)) { - size_t iIF = stoul(m_expr[iExpr].params); - if (iIF != size_t(-1)) { - string ifCondn = m_expr[iIF].result; - bool isNum = isNumber(ifCondn); - if ((isNum && (stoi(ifCondn) != 0)) || (!isNum && !ifCondn.empty())) { - return g_result; - } + const size_t iIF = ir::detail::paramIndex(m_expr[iExpr].params); + if (iIF == ir::detail::kNoIndex) { + return g_result; + } + if (Interpreter::valueIsTruthy(m_expr[iIF].result)) { + return g_result; } - else return g_result; } - string condn; + Interpreter::Value condn; if (iBegin < iCondEnd) { condn = m_expr[iExpr].result = calcExpression(iBegin, iCondEnd); } - bool isNum = isNumber(condn); - if ((m_expr[iExpr].keyw == Keyword::ELSE) || (isNum && (stoi(condn) != 0)) || (!isNum && !condn.empty())) { + if ((m_expr[iExpr].keyw == Keyword::ELSE) || Interpreter::valueIsTruthy(condn)) { bool isContinue = false, isBreak = false; for (size_t i = iCondEnd; i < iBodyEnd;) { @@ -454,10 +552,10 @@ string Interpreter::Impl::calcCondition(size_t iExpr) { case Keyword::IF: case Keyword::ELSE: case Keyword::ELSE_IF: { - string res = calcCondition(i); + Interpreter::Value res = calcCondition(i); if (m_expr[i].keyw != Keyword::WHILE) { - isBreak = res == "break"; - isContinue = res == "continue"; + isBreak = ir::detail::isBreakValue(res); + isContinue = ir::detail::isContinueValue(res); g_result = res; } i = m_expr[i].iBodyEnd; @@ -465,19 +563,23 @@ string Interpreter::Impl::calcCondition(size_t iExpr) { break; case Keyword::BREAK: { isBreak = true; - if (m_expr[iExpr].keyw != Keyword::WHILE) - g_result = "break"; + if (m_expr[iExpr].keyw != Keyword::WHILE) { + g_result = Interpreter::ControlFlow::Break; + } } break; case Keyword::CONTINUE: { isContinue = true; - if (m_expr[iExpr].keyw != Keyword::WHILE) - g_result = "continue"; + if (m_expr[iExpr].keyw != Keyword::WHILE) { + g_result = Interpreter::ControlFlow::Continue; + } } break; case Keyword::GOTO: { - if (m_label.find(m_expr[i].params) != m_label.end()) { - m_gotoIndex = m_label[m_expr[i].params]; + const std::string& gotoLabel = ir::detail::paramKey(m_expr[i].params); + auto it = m_label.find(gotoLabel); + if (it != m_label.end()) { + m_gotoIndex = it->second; } } break; @@ -486,12 +588,12 @@ string Interpreter::Impl::calcCondition(size_t iExpr) { } if (isBreak || m_exit) break; - if (m_gotoIndex != size_t(-1)) { + if (m_gotoIndex != ir::detail::kNoIndex) { if ((iCondEnd <= m_gotoIndex) && (m_gotoIndex < iBodyEnd)) { for (size_t j = m_gotoIndex; j < i; ++j) - m_expr[j].iOperator = size_t(-1); + m_expr[j].iOperator = ir::detail::kNoIndex; i = m_gotoIndex; - m_gotoIndex = size_t(-1); + m_gotoIndex = ir::detail::kNoIndex; } else break; } @@ -502,13 +604,12 @@ string Interpreter::Impl::calcCondition(size_t iExpr) { isContinue = false; for (size_t j = iBegin; j < iCondEnd; ++j) - m_expr[j].iOperator = size_t(-1); + m_expr[j].iOperator = ir::detail::kNoIndex; - const string& condn = m_expr[iExpr].result = calcExpression(iBegin, iCondEnd); - bool isNum = isNumber(condn); - if ((isNum && (stoi(condn) != 0)) || (!isNum && !condn.empty())) { + condn = m_expr[iExpr].result = calcExpression(iBegin, iCondEnd); + if (Interpreter::valueIsTruthy(condn)) { for (size_t j = iCondEnd; j < iBodyEnd; ++j) - m_expr[j].iOperator = size_t(-1); + m_expr[j].iOperator = ir::detail::kNoIndex; i = iCondEnd; } } @@ -516,72 +617,48 @@ string Interpreter::Impl::calcCondition(size_t iExpr) { } return g_result; } -string Interpreter::Impl::calcExpression(size_t iBegin, size_t iEnd) { +Interpreter::Value Interpreter::Impl::calcExpression(size_t iBegin, size_t iEnd) { if (iBegin + 1 == iEnd) { - if (m_expr[iBegin].keyw == Keyword::VARIABLE) - return m_var[m_expr[iBegin].params]; - if (m_expr[iBegin].keyw == Keyword::VALUE) - return m_expr[iBegin].params; - return calcOperation(m_expr[iBegin].keyw, iBegin); + return evalOperand(iBegin); } - bool firstRun = m_soper.find(iBegin) == m_soper.end(); - if (firstRun) - m_soper.insert({ iBegin, vector() }); - - vector& oprs = m_soper[iBegin]; - if (firstRun) { - calcOperatorPriority(iBegin, iEnd, oprs); + if (m_soper.find(iBegin) == m_soper.end()) { + m_soper.insert({ iBegin, std::vector() }); } + std::vector& oprs = m_soper[iBegin]; + oprs.clear(); + calcOperatorPriority(iBegin, iEnd, oprs); if (oprs.empty()) { return calcOperation(m_expr[iBegin].keyw, iBegin); } - string g_result; + Interpreter::Value g_result; for (auto& op : oprs) { size_t iOp = op.inx; Expression* pLeftOperd = nullptr, * pRightOperd = nullptr; - string lValue, rValue; - if (op.iLOpr != size_t(-1)) { // left operand + Interpreter::Value lValue, rValue; + if (op.iLOpr != ir::detail::kNoIndex) { pLeftOperd = &m_expr[op.iLOpr]; - if (pLeftOperd->iOperator == size_t(-1)) { - if (pLeftOperd->keyw == Keyword::VARIABLE) - lValue = m_var[m_expr[op.iLOpr].params]; - else if (pLeftOperd->keyw == Keyword::VALUE) - lValue = m_expr[op.iLOpr].params; - else - lValue = calcOperation(pLeftOperd->keyw, op.iLOpr); - } - else - lValue = m_expr[pLeftOperd->iOperator].result; + lValue = evalOperand(op.iLOpr); } - if (op.iROpr != size_t(-1)) { // right operand + if (op.iROpr != ir::detail::kNoIndex) { pRightOperd = &m_expr[op.iROpr]; - if (pRightOperd->iOperator == size_t(-1)) { - if (pRightOperd->keyw == Keyword::VARIABLE) - rValue = m_var[m_expr[op.iROpr].params]; - else if (pRightOperd->keyw == Keyword::VALUE) - rValue = m_expr[op.iROpr].params; - else - rValue = calcOperation(pRightOperd->keyw, op.iROpr); - } - else - rValue = m_expr[pRightOperd->iOperator].result; + rValue = evalOperand(op.iROpr); } m_currentIndex = iOp; - g_result = m_expr[iOp].result = m_uoper[m_expr[iOp].params].first(lValue, rValue); + g_result = m_expr[iOp].result = m_uoper[ir::detail::paramKey(m_expr[iOp].params)].first(lValue, rValue); - if (pLeftOperd && (pLeftOperd->keyw == Keyword::VARIABLE) && (pLeftOperd->iOperator == size_t(-1))) { - pLeftOperd->result = m_var[pLeftOperd->params] = lValue; + if (pLeftOperd && (pLeftOperd->keyw == Keyword::VARIABLE) && (pLeftOperd->iOperator == ir::detail::kNoIndex)) { + pLeftOperd->result = m_var[ir::detail::paramKey(pLeftOperd->params)] = lValue; } - if (pRightOperd && (pRightOperd->keyw == Keyword::VARIABLE) && (pRightOperd->iOperator == size_t(-1))) { - pRightOperd->result = m_var[pRightOperd->params] = rValue; + if (pRightOperd && (pRightOperd->keyw == Keyword::VARIABLE) && (pRightOperd->iOperator == ir::detail::kNoIndex)) { + pRightOperd->result = m_var[ir::detail::paramKey(pRightOperd->params)] = rValue; } if (pLeftOperd) { - if (pLeftOperd->iOperator != size_t(-1)) { + if (pLeftOperd->iOperator != ir::detail::kNoIndex) { size_t iLOp = pLeftOperd->iOperator; for (size_t i = iBegin; i < iEnd; ++i) { if (m_expr[i].iOperator == iLOp) @@ -591,7 +668,7 @@ string Interpreter::Impl::calcExpression(size_t iBegin, size_t iEnd) { else pLeftOperd->iOperator = iOp; } if (pRightOperd) { - if (pRightOperd->iOperator != size_t(-1)) { + if (pRightOperd->iOperator != ir::detail::kNoIndex) { size_t iROp = pRightOperd->iOperator; for (size_t i = iBegin; i < iEnd; ++i) { if (m_expr[i].iOperator == iROp) @@ -603,9 +680,9 @@ string Interpreter::Impl::calcExpression(size_t iBegin, size_t iEnd) { } return g_result; } -void Interpreter::Impl::calcOperatorPriority(size_t iBegin, size_t iEnd, vector& oprs) { +void Interpreter::Impl::calcOperatorPriority(size_t iBegin, size_t iEnd, std::vector& oprs) { - size_t iLOpr = size_t(-1); + size_t iLOpr = ir::detail::kNoIndex; for (size_t i = iBegin; i < iEnd;) { if (m_expr[i].keyw == Keyword::FUNCTION) { iLOpr = i; @@ -618,468 +695,863 @@ void Interpreter::Impl::calcOperatorPriority(size_t iBegin, size_t iEnd, vector< continue; } if (m_expr[i].keyw == Keyword::OPERATOR) { - uint32_t priority = m_uoper[m_expr[i].params].second; - size_t iROpr = (i < iEnd - 1) ? i + 1 : size_t(-1); - oprs.emplace_back({ i, priority, iLOpr, iROpr }); // inx, priority + uint32_t priority = m_uoper[ir::detail::paramKey(m_expr[i].params)].second; + size_t iROpr = (i < iEnd - 1) ? i + 1 : ir::detail::kNoIndex; + oprs.emplace_back({ i, priority, iLOpr, iROpr }); // inx, priority } iLOpr = i; ++i; } - - size_t osz = oprs.size(); + const auto osz = oprs.size(); if (osz > 1) { if (osz == 2) { - if (oprs[0].priority > oprs[1].priority) swap(oprs[0], oprs[1]); + if (oprs[0].priority > oprs[1].priority) std::swap(oprs[0], oprs[1]); } else if (osz == 3) { if (oprs[0].priority < oprs[1].priority) { - if (oprs[2].priority < oprs[0].priority) swap(oprs[0], oprs[2]); + if (oprs[2].priority < oprs[0].priority) std::swap(oprs[0], oprs[2]); } else { - if (oprs[1].priority < oprs[2].priority) swap(oprs[0], oprs[1]); - else swap(oprs[0], oprs[2]); + if (oprs[1].priority < oprs[2].priority) std::swap(oprs[0], oprs[1]); + else std::swap(oprs[0], oprs[2]); } - if (oprs[2].priority < oprs[1].priority) swap(oprs[1], oprs[2]); - } - else if (osz < 10) { // faster than std::sort on small distance - for (size_t i = 0; i < osz - 2; ++i) { - size_t iMin = i; - size_t minPriort = oprs[i].priority; - for (size_t j = i + 1; j < osz; ++j) { - if (oprs[j].priority < minPriort) { - minPriort = oprs[j].priority; - iMin = j; - } - } - if (iMin != i) swap(oprs[i], oprs[iMin]); - } - if (oprs[osz - 2].priority > oprs[osz - 1].priority) swap(oprs[osz - 2], oprs[osz - 1]); + if (oprs[2].priority < oprs[1].priority) std::swap(oprs[1], oprs[2]); } - else { - sort(oprs.begin(), oprs.end(), [](const Operatr& l, const Operatr& r) { + else{ + std::sort(oprs.begin(), oprs.end(), [](const Operator& l, const Operator& r) { return l.priority < r.priority; - }); + }); } } } -bool Interpreter::Impl::parseInstructionScript(string& script, size_t gpos) { +// --- parse --- - size_t iExpr = m_expr.size(), - cpos = 0; +bool Interpreter::Impl::failParse(size_t cpos, size_t gpos, const char* what) { + if (m_parseErr.message.empty()) { + m_parseErr.kind = Interpreter::Error::Kind::Parse; + m_parseErr.position = cpos + gpos; + m_parseErr.message = "Error script pos " + std::to_string(m_parseErr.position) + ": " + what; + } + return false; +} -#define CHECK_PARSE_RETURN(condition) \ - if (condition){ \ - if (m_err.empty()) m_err = "Error script pos " + to_string(cpos + gpos) + " src line " + to_string(__LINE__) + ": " + #condition; \ - return false; \ - } -#define SPARE_SYMBOL_CONTINUE \ - if (script[cpos] == ';' || script[cpos] == ',' || script[cpos] == ']' || script[cpos] == '[') { \ - ++cpos; \ - continue; \ - } +bool Interpreter::Impl::skipOneSpareSymbol(const std::string& script, size_t& cpos) { + if (cpos >= script.size()) { + return false; + } + const char c = script[cpos]; + if (c == ';' || c == ',' || c == ']' || c == '[') { + ++cpos; + return true; + } + return false; +} - size_t iIF = size_t(-1); - while (cpos < script.size()) { - SPARE_SYMBOL_CONTINUE - string attr = getAttributeAtFirst(script, cpos); - if (!attr.empty()) { - m_exprAttribute[iExpr].push_back(attr); - SPARE_SYMBOL_CONTINUE +bool Interpreter::Impl::parseControlBody(std::string& script, size_t& cpos, size_t gpos, + const char* emptyBodyMsg, const char* invalidBodyMsg) { + if (script[cpos] == '{') { + std::string body = getIntroScript(script, cpos, '{', '}'); + if (body.empty() || !parseInstructionScript(body, gpos + cpos - body.size() - 2)) { + return failParse(cpos, gpos, emptyBodyMsg); } - size_t cposFunc = cpos, - cposOpr = cpos; - if (!getFunctionAtFirst(script, cposFunc).empty() || !getOperatorAtFirst(script, cposOpr).empty()) { - m_expr.emplace_back({ Keyword::EXPRESSION, iExpr, iExpr, size_t(-1) }); - - string expr = getNextParam(script, cpos, ';'); - CHECK_PARSE_RETURN(expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 1)); - - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - } - else if (startWith(script, cpos, "while") || startWith(script, cpos, "if") || startWith(script, cpos, "elseif")) { - const string kname = getNextParam(script, cpos, '('); - CHECK_PARSE_RETURN(kname.empty()); - - Keyword keyw = keywordByName(kname); - m_expr.emplace_back({ keyw, iExpr, iExpr, size_t(-1) }); + } + else { + std::string body = getNextParam(script, cpos, ';') + ';'; + if ((body.size() != 1) && !parseInstructionScript(body, gpos + cpos - body.size())) { + return failParse(cpos, gpos, invalidBodyMsg); + } + } + return true; +} - if (keyw == Keyword::IF) { - iIF = iExpr; - } - else if (keyw == Keyword::ELSE_IF) { - m_expr[iExpr].params = to_string(iIF); - iIF = iExpr; - } +bool Interpreter::Impl::parseInstrExprOrOpCall(std::string& script, size_t& cpos, size_t gpos, + size_t& iExpr) { + size_t cposFunc = cpos; + size_t cposOpr = cpos; + if (getFunctionAtFirst(script, cposFunc).empty() && getOperatorAtFirst(script, cposOpr).empty()) { + return false; + } - --cpos; - string condition = getIntroScript(script, cpos, '(', ')'); - CHECK_PARSE_RETURN(condition.empty() || !parseExpressionScript(condition, gpos + cpos - condition.size() - 2)); + emplaceExprAt(iExpr, Keyword::EXPRESSION); - m_expr[iExpr].iConditionEnd = m_expr.size(); + std::string expr = getNextParam(script, cpos, ';'); + if (expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 1)) { + return failParse(cpos, gpos, "empty expression"); + } - if (script[cpos] == '{') { - string body = getIntroScript(script, cpos, '{', '}'); - CHECK_PARSE_RETURN(body.empty() || !parseInstructionScript(body, gpos + cpos - body.size() - 2)); - } - else { - string body = getNextParam(script, cpos, ';') + ';'; - CHECK_PARSE_RETURN((body.size() == 1) || !parseInstructionScript(body, gpos + cpos - body.size())); - } - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + return true; +} - if ((cpos < script.size()) && (script[cpos] == ';')) ++cpos; +bool Interpreter::Impl::parseInstrControlFlow(std::string& script, size_t& cpos, size_t gpos, + size_t& iExpr, size_t& iIF) { + if (!startWith(script, cpos, "while") && !startWith(script, cpos, "if") + && !startWith(script, cpos, "elseif")) { + if (!startWith(script, cpos, "break") && !startWith(script, cpos, "continue")) { + return false; } - else if (startWith(script, cpos, "else")) { - cpos += 4; - m_expr.emplace_back({ Keyword::ELSE, iExpr, iExpr, size_t(-1) }); - - m_expr[iExpr].params = to_string(iIF); - - if (script[cpos] == '{') { - string body = getIntroScript(script, cpos, '{', '}'); - CHECK_PARSE_RETURN(body.empty() || !parseInstructionScript(body, gpos + cpos - body.size() - 2)); - } - else { - string body = getNextParam(script, cpos, ';') + ';'; - CHECK_PARSE_RETURN((body.size() == 1) || !parseInstructionScript(body, gpos + cpos - body.size())); - } - m_expr[iExpr].iConditionEnd = iExpr + 1; - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - - if ((cpos < script.size()) && (script[cpos] == ';')) ++cpos; + const std::string kname = getNextParam(script, cpos, ';'); + if (kname.empty()) { + return failParse(cpos, gpos, "expected break or continue"); } - else if (startWith(script, cpos, "break") || startWith(script, cpos, "continue")) { - const string kname = getNextParam(script, cpos, ';'); - CHECK_PARSE_RETURN(kname.empty()); - const Keyword keyw = keywordByName(kname); + emplaceExpr(iExpr, keywordByName(kname)); + return true; + } - m_expr.emplace_back({ keyw, iExpr, iExpr, size_t(-1) }); - ++iExpr; - } - else if (startWith(script, cpos, "#macro")) { // macro declaration - cpos += 6; - const string mname = getNextParam(script, cpos, '{'); + const std::string kname = getNextParam(script, cpos, '('); + if (kname.empty()) { + return failParse(cpos, gpos, "expected keyword"); + } - cpos -= 1; - const string mvalue = getIntroScript(script, cpos, '{', '}'); - CHECK_PARSE_RETURN(mname.empty() || mvalue.empty()); + const Keyword keyw = keywordByName(kname); + emplaceExprAt(iExpr, keyw); - m_macro["#" + mname] = mvalue; + if (keyw == Keyword::IF) { + iIF = iExpr; + } + else if (keyw == Keyword::ELSE_IF) { + m_expr[iExpr].params = { static_cast(iIF) }; + iIF = iExpr; + } - if ((cpos < script.size()) && (script[cpos] == ';')) ++cpos; - } - else if (script[cpos] == '#') { // macro definition - size_t cposMName = cpos; - const string mname = getMacroAtFirst(script, cposMName); - CHECK_PARSE_RETURN(mname.empty() || (m_macro.find(mname) == m_macro.end())); + --cpos; + std::string condition = getIntroScript(script, cpos, '(', ')'); + if (condition.empty() || !parseExpressionScript(condition, gpos + cpos - condition.size() - 2)) { + return failParse(cpos, gpos, "empty condition"); + } - size_t cposArg = cposMName; - const string args = getIntroScript(script, cposArg, '(', ')'); - string macro = m_macro[mname]; - if (!args.empty()) - CHECK_PARSE_RETURN(!parseMacroArgs(args, macro)); - - if (cposArg == cposMName) - script.replace(cpos, mname.size(), macro); - else - script.replace(cpos, (mname + "(" + args + ")").size(), macro); - } - else if (startWith(script, cpos, "goto")) { - cpos += 4; - const string lname = getNextParam(script, cpos, ';'); - CHECK_PARSE_RETURN(lname.empty()); + m_expr[iExpr].iConditionEnd = m_expr.size(); - if (m_label.find(lname) == m_label.end()) - m_label.insert({ lname, size_t(-1) }); + if (!parseControlBody(script, cpos, gpos, "empty body", "invalid instruction body")) { + return true; + } - m_expr.emplace_back({ Keyword::GOTO, iExpr, iExpr, size_t(-1), lname }); - ++iExpr; - } - else if (startWith(script, cpos, "l_")) { - const string lname = getNextParam(script, cpos, ':'); - CHECK_PARSE_RETURN(lname.empty()); + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - m_label[lname] = iExpr; - } - else if (startWith(script, cpos, "function")) { - cpos += 8; - const string fname = getNextParam(script, cpos, '{'); - CHECK_PARSE_RETURN(fname.empty()); + if ((cpos < script.size()) && (script[cpos] == ';')) { + ++cpos; + } + return true; +} - cpos -= 1; - const string fbody = getIntroScript(script, cpos, '{', '}'); - CHECK_PARSE_RETURN(fbody.empty()); +bool Interpreter::Impl::parseInstrElse(std::string& script, size_t& cpos, size_t gpos, + size_t& iExpr, size_t iIF) { + if (!startWith(script, cpos, "else")) { + return false; + } - Interpreter::Impl fImpl = *this; - fImpl.m_internFunc[fname] = {}; + cpos += 4; - CHECK_PARSE_RETURN(!fImpl.parseScript(fbody, m_err)); + emplaceExprAt(iExpr, Keyword::ELSE); + m_expr[iExpr].params = { static_cast(iIF) }; - m_internFunc[fname] = fImpl; - } - else { - m_expr.emplace_back({ Keyword::EXPRESSION, iExpr, iExpr, size_t(-1) }); + if (!parseControlBody(script, cpos, gpos, "empty else body", "invalid else body")) { + return true; + } - string expr = getNextParam(script, cpos, ';'); - CHECK_PARSE_RETURN(expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 1)); + m_expr[iExpr].iConditionEnd = iExpr + 1; + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - } + if ((cpos < script.size()) && (script[cpos] == ';')) { + ++cpos; } return true; } -bool Interpreter::Impl::parseExpressionScript(string& script, size_t gpos) { - - size_t iExpr = m_expr.size(), - cpos = 0; - string oprName, fName; +bool Interpreter::Impl::expandAllMacros(std::string& script) { + size_t cpos = 0; while (cpos < script.size()) { - SPARE_SYMBOL_CONTINUE - string attr = getAttributeAtFirst(script, cpos); - if (!attr.empty()) { - m_exprAttribute[iExpr].push_back(attr); - SPARE_SYMBOL_CONTINUE - } - if (script[cpos] == '$') { - size_t posmem = cpos; - oprName = getNextOperator(script, cpos); - - size_t bodyBeginF = script.find('{', posmem); - size_t bodyBeginQ = script.find('[', posmem); - size_t bodyBegin = bodyBeginF; - char bodyBeginSym = '{', bodyEndSym = '}'; - if (bodyBeginQ < bodyBeginF){ - bodyBegin = bodyBeginQ; - bodyBeginSym = '['; - bodyEndSym = ']'; - } - if ((!oprName.empty() && (bodyBegin < cpos)) || (oprName.empty() && (bodyBegin != string::npos))) { - const string vName = script.substr(posmem, bodyBegin - posmem); - string value = getIntroScript(script, bodyBegin, bodyBeginSym, bodyEndSym); - if (!value.empty()) { - if (value[0] == '"') - value = value.substr(1); - if (!value.empty() && (value.back() == '"')) - value.pop_back(); - } - m_expr.emplace_back({ Keyword::VARIABLE, iExpr, iExpr, size_t(-1), vName, value }); ++iExpr; - if (oprName == "[" && bodyBeginSym == '['){ - m_expr.emplace_back({ Keyword::OPERATOR, iExpr, iExpr, size_t(-1), oprName }); ++iExpr; - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName, value }); ++iExpr; - } - m_var[vName] = value; + if (skipOneSpareSymbol(script, cpos)) { + continue; + } - cpos = bodyBegin; - } - else if (!oprName.empty()) { - string vName = script.substr(posmem, cpos - posmem - oprName.size()); - if (m_var.find(vName) == m_var.end()) - m_var.insert({ vName, "" }); + if (!startWith(script, cpos, "#macro")) { + ++cpos; + continue; + } - m_expr.emplace_back({ Keyword::VARIABLE, iExpr, iExpr, size_t(-1), vName }); ++iExpr; - m_expr.emplace_back({ Keyword::OPERATOR, iExpr, iExpr, size_t(-1), oprName }); ++iExpr; - } - else { - string vName = script.substr(cpos); + const size_t declStart = cpos; + cpos += 6; + const std::string mname = getNextParam(script, cpos, '{'); - if (vName.back() == ';') vName.pop_back(); + --cpos; + const std::string mvalue = getIntroScript(script, cpos, '{', '}'); + if (mname.empty() || mvalue.empty()) { + return failParse(cpos, 0, "empty macro declaration"); + } - if (m_var.find(vName) == m_var.end()) - m_var.insert({ vName, "" }); + m_macro["#" + mname] = mvalue; - m_expr.emplace_back({ Keyword::VARIABLE, iExpr, iExpr, size_t(-1), vName }); - - break; - } + size_t declEnd = cpos; + if ((declEnd < script.size()) && (script[declEnd] == ';')) { + ++declEnd; } - else if (!(fName = getFunctionAtFirst(script, cpos)).empty()) { - CHECK_PARSE_RETURN(!m_ufunc.count(fName) && !m_internFunc.count(fName)); - - m_expr.emplace_back({ Keyword::FUNCTION, iExpr, iExpr, size_t(-1), fName }); + script.erase(declStart, declEnd - declStart); + cpos = declStart; + } - size_t cposMem = cpos; - string args = getIntroScript(script, cpos, '(', ')'); - CHECK_PARSE_RETURN(args.empty() && (cposMem + 2 != cpos)); - if (!args.empty()) - CHECK_PARSE_RETURN(!parseArgumentScript(args, gpos + cpos - args.size() - 2)); + cpos = 0; + while (cpos < script.size()) { + if (skipOneSpareSymbol(script, cpos)) { + continue; + } - iExpr = m_expr[iExpr].iConditionEnd = m_expr.size(); + if (script[cpos] != '#') { + ++cpos; + continue; + } - if ((cpos < script.size()) && (script[cpos] == ';')) ++cpos; + size_t cposMName = cpos; + const std::string mname = getMacroAtFirst(script, cposMName); + if (mname.empty() || (m_macro.find(mname) == m_macro.end())) { + return failParse(cpos, 0, "unknown macro"); } - else if (script[cpos] == '(') { - string expr = getIntroScript(script, cpos, '(', ')'); - m_expr.emplace_back({ Keyword::EXPRESSION, iExpr, iExpr, size_t(-1) }); + size_t cposArg = cposMName; + const std::string args = getIntroScript(script, cposArg, '(', ')'); + std::string macro = m_macro[mname]; + if (!args.empty() && !parseMacroArgs(args, macro)) { + return failParse(cpos, 0, "invalid macro arguments"); + } + cleaningScript(macro); - CHECK_PARSE_RETURN(expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 2)); + if (cposArg == cposMName) { + script.replace(cpos, mname.size(), macro); + } + else { + script.replace(cpos, (mname + "(" + args + ")").size(), macro); + } + } + return true; +} - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); +bool Interpreter::Impl::parseInstrMacro(std::string& script, size_t& cpos, size_t gpos) { + if (startWith(script, cpos, "#macro")) { + cpos += 6; + const std::string mname = getNextParam(script, cpos, '{'); - if ((cpos < script.size()) && (script[cpos] == ';')) ++cpos; + --cpos; + const std::string mvalue = getIntroScript(script, cpos, '{', '}'); + if (mname.empty() || mvalue.empty()) { + return failParse(cpos, gpos, "empty macro declaration"); } - else if (script[cpos] == '#') { - size_t cposMName = cpos; - const string mname = getMacroAtFirst(script, cposMName); - CHECK_PARSE_RETURN(mname.empty() || (m_macro.find(mname) == m_macro.end())); - size_t cposArg = cposMName; - const string args = getIntroScript(script, cposArg, '(', ')'); - string macro = m_macro[mname]; - if (!args.empty()) - CHECK_PARSE_RETURN(!parseMacroArgs(args, macro)); + m_macro["#" + mname] = mvalue; - if (cposArg == cposMName) - script.replace(cpos, mname.size(), macro); - else - script.replace(cpos, (mname + "(" + args + ")").size(), macro); + if ((cpos < script.size()) && (script[cpos] == ';')) { + ++cpos; } - else if (!(oprName = getOperatorAtFirst(script, cpos)).empty()) { - CHECK_PARSE_RETURN(m_uoper.find(oprName) == m_uoper.end()); + return true; + } - m_expr.emplace_back({ Keyword::OPERATOR, iExpr, iExpr, size_t(-1), oprName }); ++iExpr; - } - else { // value - if (script[cpos] == '"') { - ++cpos; - const string vName = getNextParam(script, cpos, '"'); - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName }); ++iExpr; - } - else if (script[cpos] == '{') { - const string value = getIntroScript(script, cpos, '{', '}'); - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), "", value }); ++iExpr; // empty name - } - else { - size_t posmem = cpos; - oprName = getNextOperator(script, cpos); - - size_t bodyBeginF = script.find('{', posmem); - size_t bodyBeginQ = script.find('[', posmem); - size_t bodyBegin = bodyBeginF; - char bodyBeginSym = '{', bodyEndSym = '}'; - if (bodyBeginQ < bodyBeginF){ - bodyBegin = bodyBeginQ; - bodyBeginSym = '['; - bodyEndSym = ']'; - } - if ((!oprName.empty() && (bodyBegin < cpos)) || (oprName.empty() && (bodyBegin != string::npos))) { - const string vName = script.substr(posmem, bodyBegin - posmem); - const string value = getIntroScript(script, bodyBegin, bodyBeginSym, bodyEndSym); - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName, value }); ++iExpr; - if (oprName == "[" && bodyBeginSym == '['){ - m_expr.emplace_back({ Keyword::OPERATOR, iExpr, iExpr, size_t(-1), oprName }); ++iExpr; - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName, value }); ++iExpr; - } - cpos = bodyBegin; - } - else if (!oprName.empty()) { - const string vName = script.substr(posmem, cpos - posmem - oprName.size()); + if (cpos >= script.size() || script[cpos] != '#') { + return false; + } - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName }); ++iExpr; - m_expr.emplace_back({ Keyword::OPERATOR, iExpr, iExpr, size_t(-1), oprName }); ++iExpr; - } - else { - string vName = script.substr(cpos); + size_t cposMName = cpos; + const std::string mname = getMacroAtFirst(script, cposMName); + if (mname.empty() || (m_macro.find(mname) == m_macro.end())) { + return failParse(cpos, gpos, "unknown macro"); + } - if (vName.back() == ';') vName.pop_back(); + size_t cposArg = cposMName; + const std::string args = getIntroScript(script, cposArg, '(', ')'); + std::string macro = m_macro[mname]; + if (!args.empty() && !parseMacroArgs(args, macro)) { + return failParse(cpos, gpos, "invalid macro arguments"); + } + cleaningScript(macro); - m_expr.emplace_back({ Keyword::VALUE, iExpr, iExpr, size_t(-1), vName }); - break; - } - } - } + if (cposArg == cposMName) { + script.replace(cpos, mname.size(), macro); + } + else { + script.replace(cpos, (mname + "(" + args + ")").size(), macro); } return true; } -bool Interpreter::Impl::parseArgumentScript(string& script, size_t gpos) { - size_t iExpr = m_expr.size(), - cpos = 0, - cp = 0; - int bordCnt = 0; +bool Interpreter::Impl::parseInstrGotoLabel(std::string& script, size_t& cpos, size_t gpos, + size_t& iExpr) { + if (startWith(script, cpos, "goto")) { + cpos += 4; + const std::string lname = getNextParam(script, cpos, ';'); + if (lname.empty()) { + return failParse(cpos, gpos, "empty goto label"); + } - while (cp < script.size()) { - if (script[cp] == '(') ++bordCnt; - if (script[cp] == ')') --bordCnt; - if (((script[cp] == ',') || (cp == script.size() - 1)) && (bordCnt == 0)) { - m_expr.emplace_back({ Keyword::ARGUMENT, iExpr, iExpr, size_t(-1) }); + if (m_label.find(lname) == m_label.end()) { + m_label.insert({ lname, ir::detail::kNoIndex }); + } - if (cp == script.size() - 1) ++cp; + emplaceExpr(iExpr, Keyword::GOTO, Interpreter::makeParam(lname)); + return true; + } - string arg = script.substr(cpos, cp - cpos); - CHECK_PARSE_RETURN(!arg.empty() && !parseExpressionScript(arg, gpos + cpos)); + if (!startWith(script, cpos, "l_")) { + return false; + } - iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); - cpos = cp + 1; - } - ++cp; + const std::string lname = getNextParam(script, cpos, ':'); + if (lname.empty()) { + return failParse(cpos, gpos, "empty label name"); } + + m_label[lname] = iExpr; return true; } -bool Interpreter::Impl::parseMacroArgs(const string& args, string& macro) { - size_t ssz = args.size(), - cpos = 0, - cp = 0; - int bordCnt = 0; - int argIndex = 0; +Interpreter::Impl Interpreter::Impl::makeChildImplForParse() const { + Impl child; + child.m_ufunc = m_ufunc; + child.m_uoper = m_uoper; + child.m_macro = m_macro; + child.m_attribute = m_attribute; + child.m_internFunc = m_internFunc; + return child; +} - while (cp < ssz) { - if (args[cp] == '(') ++bordCnt; - if (args[cp] == ')') --bordCnt; - if (((args[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { - - if (cp == ssz - 1) ++cp; +bool Interpreter::Impl::parseInstrFunctionDecl(std::string& script, size_t& cpos, size_t gpos) { + if (!startWith(script, cpos, "function")) { + return false; + } - const string arg = args.substr(cpos, cp - cpos); - - size_t argPos = 0; - argPos = macro.find("$" + to_string(argIndex), argPos); - while (argPos != string::npos) { - - macro.replace(argPos, ("$" + to_string(argIndex)).size(), arg); + cpos += 8; + const std::string fname = getNextParam(script, cpos, '{'); + if (fname.empty()) { + return failParse(cpos, gpos, "empty function name"); + } - argPos = macro.find("$" + to_string(argIndex), argPos); - } + --cpos; + const std::string fbody = getIntroScript(script, cpos, '{', '}'); + if (fbody.empty()) { + return failParse(cpos, gpos, "empty function body"); + } - cpos = cp + 1; - ++argIndex; - } - ++cp; + Impl fImpl = makeChildImplForParse(); + fImpl.m_internFunc[fname] = {}; + + if (!fImpl.parseScript(fbody, m_parseErr)) { + return failParse(cpos, gpos, "invalid function body"); } + + m_internFunc[fname] = std::move(fImpl); return true; } -string Interpreter::Impl::getNextParam(const string& script, size_t& cpos, char symb) const { - size_t pos = script.find(symb, cpos); - string res; - if (pos != string::npos) { - res = script.substr(cpos, pos - cpos); - cpos = pos + 1; +bool Interpreter::Impl::parseInstrStatementExpr(std::string& script, size_t& cpos, size_t gpos, + size_t& iExpr) { + emplaceExprAt(iExpr, Keyword::EXPRESSION); + + std::string expr = getNextParam(script, cpos, ';'); + if (expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 1)) { + return failParse(cpos, gpos, "empty expression"); } - return res; -} -string Interpreter::Impl::getNextOperator(const string& script, size_t& cpos) const { - size_t minp = string::npos; - string opr; + + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + return true; +} + +bool Interpreter::Impl::parseInstructionScript(std::string& script, size_t gpos) { + size_t iExpr = m_expr.size(); + size_t cpos = 0; + size_t iIF = ir::detail::kNoIndex; + + while (cpos < script.size()) { + if (skipOneSpareSymbol(script, cpos)) { + continue; + } + + std::string attr = getAttributeAtFirst(script, cpos); + if (!attr.empty()) { + m_exprAttribute[iExpr].push_back(attr); + if (skipOneSpareSymbol(script, cpos)) { + continue; + } + } + + if (parseInstrExprOrOpCall(script, cpos, gpos, iExpr)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrControlFlow(script, cpos, gpos, iExpr, iIF)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrElse(script, cpos, gpos, iExpr, iIF)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrMacro(script, cpos, gpos)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrGotoLabel(script, cpos, gpos, iExpr)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrFunctionDecl(script, cpos, gpos)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (parseInstrStatementExpr(script, cpos, gpos, iExpr)) { + if (!m_parseErr.message.empty()) { + return false; + } + continue; + } + if (!m_parseErr.message.empty()) { + return false; + } + } + m_prevScript = script; + return true; +} + +void Interpreter::Impl::emplaceSyntheticNegatePrefix(size_t& iExpr) { + emplaceExpr(iExpr, Keyword::VALUE, {}, int64_t{0}); + emplaceExpr(iExpr, Keyword::OPERATOR, Interpreter::makeParam(std::string{"-"})); +} + +void Interpreter::Impl::mergeInternFuncs(Impl& callee) const { + for (const auto& f : m_internFunc) { + if (!callee.m_internFunc.count(f.first) || callee.m_internFunc[f.first].m_prevScript.empty()) { + callee.m_internFunc[f.first] = f.second; + } + } +} + +void Interpreter::Impl::bindCallerScope(Impl& callee, const std::vector& args, + std::set& outScopeVars) const { + for (const auto& var : m_var) { + if (callee.m_var.count(var.first)) { + callee.m_var[var.first] = var.second; + outScopeVars.insert(var.first); + } + } + for (size_t i = 0; i < args.size(); ++i) { + callee.m_var["$" + std::to_string(i)] = args[i]; + } +} + +void Interpreter::Impl::writeBackScope(Impl& callee, const std::set& scopeVars) { + for (const auto& var : callee.m_var) { + if (scopeVars.count(var.first)) { + m_var[var.first] = var.second; + } + } +} + +Interpreter::Impl::ParseInitOutcome Interpreter::Impl::parseNamedInitBody( + Keyword entityKw, bool bindVariable, + std::string& script, size_t& cpos, size_t& iExpr, + size_t posmem, const std::string& oprName) { + const size_t bodyBeginF = script.find('{', posmem); + const size_t bodyBeginQ = script.find('[', posmem); + size_t bodyBegin = bodyBeginF; + char bodyBeginSym = '{'; + char bodyEndSym = '}'; + if (bodyBeginQ < bodyBeginF) { + bodyBegin = bodyBeginQ; + bodyBeginSym = '['; + bodyEndSym = ']'; + } + if ((!oprName.empty() && (bodyBegin < cpos)) || + (oprName.empty() && (bodyBegin != std::string::npos))) { + const std::string vName = script.substr(posmem, bodyBegin - posmem); + std::string value = getIntroScript(script, bodyBegin, bodyBeginSym, bodyEndSym); + if (!value.empty()) { + if (value.front() == '"') { + value.erase(value.begin()); + } + if (!value.empty() && value.back() == '"') { + value.pop_back(); + } + } + if (entityKw == Keyword::VARIABLE) { + emplaceExpr(iExpr, Keyword::VARIABLE, Interpreter::makeParam(vName), Interpreter::valueFromLiteral(value)); + } + else { + emplaceExpr(iExpr, Keyword::VALUE, Interpreter::makeParam(vName), std::string{value}); + } + if (oprName == "[" && bodyBeginSym == '[') { + emplaceExpr(iExpr, Keyword::OPERATOR, Interpreter::makeParam(oprName)); + emplaceExpr(iExpr, Keyword::VALUE, Interpreter::makeParam(vName), std::string{value}); + } + if (bindVariable) { + m_var[vName] = Interpreter::valueFromLiteral(value); + } + cpos = bodyBegin; + return ParseInitOutcome::Handled; + } + if (!oprName.empty()) { + const std::string vName = script.substr(posmem, cpos - posmem - oprName.size()); + if (bindVariable && m_var.find(vName) == m_var.end()) { + m_var.insert({vName, std::string{}}); + } + emplaceExpr(iExpr, entityKw, Interpreter::makeParam(vName)); + emplaceExpr(iExpr, Keyword::OPERATOR, Interpreter::makeParam(oprName)); + return ParseInitOutcome::Handled; + } + std::string vName = script.substr(cpos); + if (!vName.empty() && vName.back() == ';') { + vName.pop_back(); + } + if (bindVariable && m_var.find(vName) == m_var.end()) { + m_var.insert({vName, std::string{}}); + } + emplaceExprAt(iExpr, entityKw, Interpreter::makeParam(vName)); + return ParseInitOutcome::BreakLoop; +} + +bool Interpreter::Impl::parseExprPrimary(std::string& script, size_t& cpos, size_t& iExpr, + size_t gpos, bool& breakLoop) { + breakLoop = false; + std::string oprName, fName; + + if (script[cpos] == '$') { + const size_t posmem = cpos; + oprName = getNextOperator(script, cpos); + const auto outcome = parseNamedInitBody( + Keyword::VARIABLE, true, script, cpos, iExpr, posmem, oprName); + if (outcome == ParseInitOutcome::BreakLoop) { + breakLoop = true; + return true; + } + if (outcome == ParseInitOutcome::Handled) { + return true; + } + return false; + } + if (!(fName = peekFunctionCall(script, cpos)).empty()) { + if (!m_ufunc.count(fName) && !m_internFunc.count(fName)) { + failParse(cpos, gpos, "unknown function"); + return false; + } + cpos += fName.size(); + + emplaceExprAt(iExpr, Keyword::FUNCTION, Interpreter::makeParam(fName)); + + const size_t cposMem = cpos; + std::string args = getIntroScript(script, cpos, '(', ')'); + if (args.empty() && (cposMem + 2 != cpos)) { + failParse(cpos, gpos, "empty function arguments"); + return false; + } + if (!args.empty() && !parseArgumentScript(args, gpos + cpos - args.size() - 2)) { + failParse(cpos, gpos, "invalid function arguments"); + return false; + } + + iExpr = m_expr[iExpr].iConditionEnd = m_expr.size(); + + if ((cpos < script.size()) && (script[cpos] == ';')) { + ++cpos; + } + return true; + } + if (script[cpos] == '(') { + std::string expr = getIntroScript(script, cpos, '(', ')'); + + emplaceExprAt(iExpr, Keyword::EXPRESSION); + + if (expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 2)) { + failParse(cpos, gpos, "empty parenthesized expression"); + return false; + } + + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + + if ((cpos < script.size()) && (script[cpos] == ';')) { + ++cpos; + } + return true; + } + if (script[cpos] == '#') { + size_t cposMName = cpos; + const std::string mname = getMacroAtFirst(script, cposMName); + if (mname.empty() || (m_macro.find(mname) == m_macro.end())) { + failParse(cpos, gpos, "unknown macro"); + return false; + } + + size_t cposArg = cposMName; + const std::string args = getIntroScript(script, cposArg, '(', ')'); + std::string macro = m_macro[mname]; + if (!args.empty() && !parseMacroArgs(args, macro)) { + failParse(cpos, gpos, "invalid macro arguments"); + return false; + } + cleaningScript(macro); + + if (cposArg == cposMName) { + script.replace(cpos, mname.size(), macro); + } + else { + script.replace(cpos, (mname + "(" + args + ")").size(), macro); + } + return true; + } + if (script[cpos] == '"') { + ++cpos; + const std::string vName = getNextParam(script, cpos, '"'); + emplaceExpr(iExpr, Keyword::VALUE, {}, ir::detail::parseQuotedLiteral(vName)); + return true; + } + if (script[cpos] == '{') { + const std::string value = getIntroScript(script, cpos, '{', '}'); + emplaceExpr(iExpr, Keyword::VALUE, Interpreter::makeParam(std::string_view{}), std::string{value}); + return true; + } + if (std::isdigit(static_cast(script[cpos]))) { + size_t end = cpos; + while (end < script.size()) { + const unsigned char c = static_cast(script[end]); + if (!std::isdigit(c) && script[end] != '.') { + break; + } + ++end; + } + emplaceExpr(iExpr, Keyword::VALUE, {}, + ir::detail::parseLiteralText(script.substr(cpos, end - cpos))); + cpos = end; + return true; + } + if (ir::detail::matchKeywordAt(script, cpos, "true")) { + emplaceExpr(iExpr, Keyword::VALUE, {}, bool{true}); + cpos += 4; + return true; + } + if (ir::detail::matchKeywordAt(script, cpos, "false")) { + emplaceExpr(iExpr, Keyword::VALUE, {}, bool{false}); + cpos += 5; + return true; + } + if (isUnaryMinusAt(script, cpos)) { + return false; + } + { + const size_t cposMem = cpos; + if (!getOperatorAtFirst(script, cpos).empty()) { + cpos = cposMem; + return false; + } + } + const size_t posmem = cpos; + oprName = getNextOperator(script, cpos); + const auto outcome = parseNamedInitBody( + Keyword::VALUE, false, script, cpos, iExpr, posmem, oprName); + if (outcome == ParseInitOutcome::BreakLoop) { + breakLoop = true; + return true; + } + if (outcome == ParseInitOutcome::Handled) { + return true; + } + return false; +} + +bool Interpreter::Impl::parseExprUnary(std::string& script, size_t& cpos, size_t& iExpr, size_t gpos) { + if (!isUnaryMinusAt(script, cpos)) { + return false; + } + + const size_t minusPos = cpos; + ++cpos; + + if (cpos < script.size() && std::isdigit(static_cast(script[cpos]))) { + size_t end = cpos; + while (end < script.size()) { + const unsigned char c = static_cast(script[end]); + if (!std::isdigit(c) && script[end] != '.') { + break; + } + ++end; + } + emplaceExpr(iExpr, Keyword::VALUE, {}, + ir::detail::parseLiteralText(script.substr(minusPos, end - minusPos))); + cpos = end; + return true; + } + + if (cpos < script.size() && script[cpos] == '$') { + emplaceSyntheticNegatePrefix(iExpr); + bool breakLoop = false; + if (!parseExprPrimary(script, cpos, iExpr, gpos, breakLoop)) { + return failParse(cpos, gpos, "expected variable after unary minus"); + } + return true; + } + + if (cpos < script.size() && script[cpos] == '(') { + emplaceSyntheticNegatePrefix(iExpr); + std::string expr = getIntroScript(script, cpos, '(', ')'); + emplaceExprAt(iExpr, Keyword::EXPRESSION); + if (expr.empty() || !parseExpressionScript(expr, gpos + cpos - expr.size() - 2)) { + return failParse(cpos, gpos, "empty parenthesized expression"); + } + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + return true; + } + + return failParse(minusPos, gpos, "invalid unary minus"); +} + +bool Interpreter::Impl::parseExprOperator(std::string& script, size_t& cpos, size_t& iExpr, size_t gpos) { + std::string oprName = getOperatorAtFirst(script, cpos); + if (oprName.empty()) { + return false; + } + if (m_uoper.find(oprName) == m_uoper.end()) { + return failParse(cpos, gpos, "unknown operator"); + } + emplaceExpr(iExpr, Keyword::OPERATOR, Interpreter::makeParam(oprName)); + return true; +} + +bool Interpreter::Impl::parseExpressionScript(std::string& script, size_t gpos) { + + size_t iExpr = m_expr.size(), + cpos = 0; + + while (cpos < script.size()) { + if (skipOneSpareSymbol(script, cpos)) { + continue; + } + std::string attr = getAttributeAtFirst(script, cpos); + if (!attr.empty()) { + m_exprAttribute[iExpr].push_back(attr); + if (skipOneSpareSymbol(script, cpos)) { + continue; + } + } + bool breakLoop = false; + if (parseExprPrimary(script, cpos, iExpr, gpos, breakLoop)) { + if (breakLoop) { + break; + } + continue; + } + if (!m_parseErr.message.empty()) { + return false; + } + if (parseExprUnary(script, cpos, iExpr, gpos)) { + continue; + } + if (!m_parseErr.message.empty()) { + return false; + } + if (parseExprOperator(script, cpos, iExpr, gpos)) { + continue; + } + if (!m_parseErr.message.empty()) { + return false; + } + } + return true; +} +bool Interpreter::Impl::parseArgumentScript(std::string& script, size_t gpos) { + + size_t iExpr = m_expr.size(), + cpos = 0, + cp = 0; + int bordCnt = 0; + + while (cp < script.size()) { + if (script[cp] == '(') ++bordCnt; + if (script[cp] == ')') --bordCnt; + if (((script[cp] == ',') || (cp == script.size() - 1)) && (bordCnt == 0)) { + emplaceExprAt(iExpr, Keyword::ARGUMENT); + + if (cp == script.size() - 1) ++cp; + + std::string arg = script.substr(cpos, cp - cpos); + if (!arg.empty() && !parseExpressionScript(arg, gpos + cpos)) { + return failParse(cpos, gpos, "invalid argument expression"); + } + + iExpr = m_expr[iExpr].iBodyEnd = m_expr.size(); + cpos = cp + 1; + } + ++cp; + } + return true; +} +bool Interpreter::Impl::parseMacroArgs(const std::string& args, std::string& macro) { + + size_t ssz = args.size(), + cpos = 0, + cp = 0; + int bordCnt = 0; + int argIndex = 0; + + while (cp < ssz) { + if (args[cp] == '(') ++bordCnt; + if (args[cp] == ')') --bordCnt; + if (((args[cp] == ',') || (cp == ssz - 1)) && (bordCnt == 0)) { + + if (cp == ssz - 1) ++cp; + + const std::string arg = args.substr(cpos, cp - cpos); + + size_t argPos = 0; + argPos = macro.find("$" + std::to_string(argIndex), argPos); + while (argPos != std::string::npos) { + + macro.replace(argPos, ("$" + std::to_string(argIndex)).size(), arg); + + argPos = macro.find("$" + std::to_string(argIndex), argPos); + } + + cpos = cp + 1; + ++argIndex; + } + ++cp; + } + return true; +} + +std::string Interpreter::Impl::getNextParam(const std::string& script, size_t& cpos, char symb) const { + size_t pos = script.find(symb, cpos); + std::string res; + if (pos != std::string::npos) { + res = script.substr(cpos, pos - cpos); + cpos = pos + 1; + } + return res; +} +std::string Interpreter::Impl::getNextOperator(const std::string& script, size_t& cpos) const { + size_t minp = std::string::npos; + std::string opr; for (const auto& op : m_uoper) { size_t pos = script.find(op.first, cpos); - if ((pos != string::npos) && ((pos <= minp) || (minp == string::npos))) { + if ((pos != std::string::npos) && ((pos <= minp) || (minp == std::string::npos))) { if (opr.empty() || (pos < minp) || (opr.size() < op.first.size())) opr = op.first; minp = pos; } } - if (minp != string::npos) { + if (minp != std::string::npos) { cpos = minp + opr.size(); } return opr; } -string Interpreter::Impl::getOperatorAtFirst(const string& script, size_t& cpos) const { - string opr; +std::string Interpreter::Impl::getOperatorAtFirst(const std::string& script, size_t& cpos) const { + std::string opr; for (const auto& op : m_uoper) { if (startWith(script, cpos, op.first)) { if (opr.empty() || (opr.size() < op.first.size())) @@ -1089,8 +1561,26 @@ string Interpreter::Impl::getOperatorAtFirst(const string& script, size_t& cpos) cpos += opr.size(); return opr; } -string Interpreter::Impl::getFunctionAtFirst(const string& script, size_t& cpos) const { - string fName; +std::string Interpreter::Impl::peekFunctionCall(const std::string& script, size_t cpos) const { + if (cpos >= script.size()) { + return {}; + } + size_t end = cpos; + while (end < script.size()) { + const unsigned char c = static_cast(script[end]); + if (!std::isalnum(c) && script[end] != '_') { + break; + } + ++end; + } + if (end == cpos || end >= script.size() || script[end] != '(') { + return {}; + } + return script.substr(cpos, end - cpos); +} + +std::string Interpreter::Impl::getFunctionAtFirst(const std::string& script, size_t& cpos) const { + std::string fName; for (const auto& f : m_ufunc) { if (startWith(script, cpos, f.first)) { if (fName.empty() || (fName.size() < f.first.size())) @@ -1108,8 +1598,8 @@ string Interpreter::Impl::getFunctionAtFirst(const string& script, size_t& cpos) cpos += fName.size(); return fName; } -string Interpreter::Impl::getMacroAtFirst(const string& script, size_t& cpos) const { - string mName; +std::string Interpreter::Impl::getMacroAtFirst(const std::string& script, size_t& cpos) const { + std::string mName; for (const auto& m : m_macro) { if (startWith(script, cpos, m.first)) { if (mName.empty() || (mName.size() < m.first.size())) @@ -1119,8 +1609,8 @@ string Interpreter::Impl::getMacroAtFirst(const string& script, size_t& cpos) co cpos += mName.size(); return mName; } -string Interpreter::Impl::getAttributeAtFirst(const string& script, size_t& cpos) const { - string mName; +std::string Interpreter::Impl::getAttributeAtFirst(const std::string& script, size_t& cpos) const { + std::string mName; for (const auto& m : m_attribute) { if (startWith(script, cpos, m)) { if (mName.empty() || (mName.size() < m.size())) @@ -1130,7 +1620,35 @@ string Interpreter::Impl::getAttributeAtFirst(const string& script, size_t& cpos cpos += mName.size(); return mName; } -string Interpreter::Impl::getIntroScript(const string& script, size_t& cpos, char symbBegin, char symbEnd) const { +bool Interpreter::Impl::isUnaryMinusContext(const std::string& script, size_t cpos) const { + if (cpos == 0) { + return true; + } + const char prev = script[cpos - 1]; + if (prev == '(' || prev == ',' || prev == ';' || prev == '[' || prev == '{') { + return true; + } + for (const auto& op : m_uoper) { + if (cpos >= op.first.size() && + script.compare(cpos - op.first.size(), op.first.size(), op.first) == 0) { + return true; + } + } + return false; +} + +bool Interpreter::Impl::isUnaryMinusAt(const std::string& script, size_t cpos) const { + if (cpos >= script.size() || script[cpos] != '-' || cpos + 1 >= script.size()) { + return false; + } + if (!isUnaryMinusContext(script, cpos)) { + return false; + } + const char next = script[cpos + 1]; + return std::isdigit(static_cast(next)) || next == '$' || next == '('; +} + +std::string Interpreter::Impl::getIntroScript(const std::string& script, size_t& cpos, char symbBegin, char symbEnd) const { size_t ssz = script.size(), cp = cpos; int bordCnt = 0; @@ -1140,7 +1658,7 @@ string Interpreter::Impl::getIntroScript(const string& script, size_t& cpos, cha if (bordCnt == 0) break; ++cp; } - string res; + std::string res; if ((bordCnt == 0) && (cp > cpos)) { res = script.substr(cpos + 1, cp - cpos - 1); cpos = cp + 1; @@ -1148,7 +1666,7 @@ string Interpreter::Impl::getIntroScript(const string& script, size_t& cpos, cha return res; } -bool Interpreter::Impl::isFindKeySymbol(const string& script, size_t cpos, size_t maxpos) const { +bool Interpreter::Impl::isFindKeySymbol(const std::string& script, size_t cpos, size_t maxpos) const { return (script.find('(', cpos) < maxpos) || (script.find(')', cpos) < maxpos) || (script.find('{', cpos) < maxpos) || @@ -1159,101 +1677,117 @@ bool Interpreter::Impl::isFindKeySymbol(const string& script, size_t cpos, size_ (script.find('"', cpos) < maxpos) || (script.find('$', cpos) < maxpos); } -bool Interpreter::Impl::startWith(const string& str, size_t pos, const string& begin) const { - return (str.find(begin, pos) - pos) == 0; +bool Interpreter::Impl::startWith(std::string_view str, size_t pos, std::string_view begin) const { + if (pos + begin.size() > str.size()) { + return false; + } + return str.compare(pos, begin.size(), begin) == 0; } -bool Interpreter::Impl::isNumber(const string& s) const { - for (auto c : s) { - if (!std::isdigit(c)) { - return false; - } + +Interpreter::Value Interpreter::Impl::variableByKey(const std::string& key) const { + auto it = m_var.find(key); + return it != m_var.end() ? it->second : Interpreter::Value{std::string{}}; +} + +Interpreter::Value Interpreter::Impl::evalOperand(size_t iExpr) { + Expression& exp = m_expr[iExpr]; + if (exp.iOperator != ir::detail::kNoIndex) { + return m_expr[exp.iOperator].result; + } + switch (exp.keyw) { + case Keyword::VARIABLE: + return variableByKey(ir::detail::paramKey(exp.params)); + case Keyword::VALUE: + return ir::detail::valueOfValueExpr(exp.result, exp.params); + default: + return calcOperation(exp.keyw, iExpr); } - return !s.empty(); } -Interpreter::Impl::Keyword Interpreter::Impl::keywordByName(const string& oprName) const { - Keyword nextOpr = Keyword::INSTRUCTION; - if (oprName == "if") nextOpr = Keyword::IF; - else if (oprName == "else") nextOpr = Keyword::ELSE; - else if (oprName == "elseif") nextOpr = Keyword::ELSE_IF; - else if (oprName == "while") nextOpr = Keyword::WHILE; - else if (oprName == "break") nextOpr = Keyword::BREAK; - else if (oprName == "goto") nextOpr = Keyword::GOTO; - else if (oprName == "#macro") nextOpr = Keyword::MACRO; - else if (oprName == "continue") nextOpr = Keyword::CONTINUE; - return nextOpr; +Interpreter::Impl::Keyword Interpreter::Impl::keywordByName(std::string_view oprName) const { + if (oprName == "if") return Keyword::IF; + if (oprName == "else") return Keyword::ELSE; + if (oprName == "elseif") return Keyword::ELSE_IF; + if (oprName == "while") return Keyword::WHILE; + if (oprName == "break") return Keyword::BREAK; + if (oprName == "goto") return Keyword::GOTO; + if (oprName == "#macro") return Keyword::MACRO; + if (oprName == "continue") return Keyword::CONTINUE; + if (oprName == "true" || oprName == "false") return Keyword::VALUE; + return Keyword::INSTRUCTION; } Interpreter::EntityType Interpreter::Impl::keywordToEntityType(Keyword keyw) const { - switch (keyw){ - case Interpreter::Impl::Keyword::EXPRESSION: return Interpreter::EntityType::EXPRESSION; - case Interpreter::Impl::Keyword::OPERATOR: return Interpreter::EntityType::OPERATOR; - case Interpreter::Impl::Keyword::WHILE: return Interpreter::EntityType::WHILE; - case Interpreter::Impl::Keyword::IF: return Interpreter::EntityType::IF; - case Interpreter::Impl::Keyword::ELSE: return Interpreter::EntityType::ELSE; - case Interpreter::Impl::Keyword::ELSE_IF: return Interpreter::EntityType::ELSE_IF; - case Interpreter::Impl::Keyword::BREAK: return Interpreter::EntityType::BREAK; - case Interpreter::Impl::Keyword::CONTINUE: return Interpreter::EntityType::CONTINUE; - case Interpreter::Impl::Keyword::FUNCTION: return Interpreter::EntityType::FUNCTION; - case Interpreter::Impl::Keyword::ARGUMENT: return Interpreter::EntityType::ARGUMENT; - case Interpreter::Impl::Keyword::VARIABLE: return Interpreter::EntityType::VARIABLE; - case Interpreter::Impl::Keyword::VALUE: return Interpreter::EntityType::VALUE; - case Interpreter::Impl::Keyword::GOTO: return Interpreter::EntityType::GOTO; - default: return Interpreter::EntityType::EXPRESSION; - } -} - -Interpreter::Interpreter() { - m_d = new Interpreter::Impl(); -} -Interpreter::~Interpreter() { - if (m_d) delete m_d; -} -Interpreter::Interpreter(const Interpreter& other) { - m_d = new Interpreter::Impl(); - if (other.m_d) - *m_d = *other.m_d; -} -Interpreter::Interpreter(Interpreter&& other) { - std::swap(m_d, other.m_d); + static constexpr Interpreter::EntityType kMap[] = { + Interpreter::EntityType::EXPRESSION, // INSTRUCTION + Interpreter::EntityType::EXPRESSION, + Interpreter::EntityType::OPERATOR, + Interpreter::EntityType::WHILE, + Interpreter::EntityType::IF, + Interpreter::EntityType::ELSE, + Interpreter::EntityType::ELSE_IF, + Interpreter::EntityType::BREAK, + Interpreter::EntityType::CONTINUE, + Interpreter::EntityType::FUNCTION, + Interpreter::EntityType::ARGUMENT, + Interpreter::EntityType::MACRO, + Interpreter::EntityType::VARIABLE, + Interpreter::EntityType::VALUE, + Interpreter::EntityType::GOTO, + }; + const size_t i = static_cast(keyw); + return i < sizeof(kMap) / sizeof(kMap[0]) ? kMap[i] : Interpreter::EntityType::EXPRESSION; } -Interpreter& Interpreter::operator=(const Interpreter& other) { - if ((this != &other) && m_d && other.m_d) + +Interpreter::Interpreter() : m_d(std::make_unique()) {} +Interpreter::~Interpreter() = default; +Interpreter::Interpreter(const Interpreter& other) + : m_d(std::make_unique()) { + if (other.m_d) { *m_d = *other.m_d; - return *this; + } } -Interpreter& Interpreter::operator=(Interpreter&& other) { +Interpreter::Interpreter(Interpreter&& other) noexcept = default; +Interpreter& Interpreter::operator=(const Interpreter& other) { if (this != &other) { - std::swap(m_d, other.m_d); + if (other.m_d) { + if (!m_d) { + m_d = std::make_unique(); + } + *m_d = *other.m_d; + } } return *this; } -string Interpreter::cmd(string script) { - return m_d ? m_d->cmd(move(script)) : ""; +Interpreter& Interpreter::operator=(Interpreter&& other) noexcept = default; +Interpreter::CmdResult Interpreter::cmd(std::string script) { + return m_d ? m_d->cmd(std::move(script)) + : Interpreter::CmdResult{Interpreter::Value{std::string{}}, {}}; } -bool Interpreter::parseScript(std::string script, string& outErr) { - return m_d ? m_d->parseScript(move(script), outErr) : false; +bool Interpreter::parseScript(std::string script, Error& outErr) { + return m_d ? m_d->parseScript(std::move(script), outErr) : false; } -std::string Interpreter::runScript() { - return m_d ? m_d->runScript() : ""; +std::pair Interpreter::runScript() { + return m_d ? m_d->runScript() + : std::pair{Value{std::string{}}, {}}; } -bool Interpreter::addFunction(const string& name, UserFunction ufunc) { +bool Interpreter::addFunction(const std::string& name, UserFunction ufunc) { return m_d ? m_d->addFunction(name, ufunc) : false; } -bool Interpreter::addOperator(const string& name, UserOperator uoper, uint32_t priority) { +bool Interpreter::addOperator(const std::string& name, UserOperator uoper, uint32_t priority) { return m_d ? m_d->addOperator(name, uoper, priority) : false; } bool Interpreter::addAttribute(const std::string& name) { return m_d ? m_d->addAttribute(name) : false; } -std::map Interpreter::allVariables() const { - return m_d ? m_d->allVariables() : std::map(); +std::map Interpreter::allVariables() const { + return m_d ? m_d->allVariables() : std::map(); } -std::string Interpreter::variable(const std::string& vname) const { - return m_d ? m_d->variable(vname) : ""; +Interpreter::Value Interpreter::variable(const std::string& vname) const { + return m_d ? m_d->variable(vname) : Interpreter::Value{std::string{}}; } -std::string Interpreter::runFunction(const std::string& fname, const std::vector& args) { - return m_d ? m_d->runFunction(fname, args) : ""; +Interpreter::Value Interpreter::runFunction(const std::string& fname, const std::vector& args) { + return m_d ? m_d->runFunction(fname, args) : Interpreter::Value{std::string{}}; } -bool Interpreter::setVariable(const std::string& vname, const std::string& value) { +bool Interpreter::setVariable(const std::string& vname, const Interpreter::Value& value) { return m_d ? m_d->setVariable(vname, value) : false; } bool Interpreter::setMacro(const std::string& mname, const std::string& script) { @@ -1286,3 +1820,355 @@ Interpreter::UserFunction Interpreter::getUserFunction(const std::string& fname) Interpreter::UserOperator Interpreter::getUserOperator(const std::string& oname) { return m_d ? m_d->getUserOperator(oname) : nullptr; } + +// --- ir::detail (definitions) --- + +namespace ir { +namespace detail { + +bool isDigits(std::string_view s) { + if (s.empty()) { + return false; + } + for (unsigned char c : s) { + if (!std::isdigit(c)) { + return false; + } + } + return true; +} + +std::optional parseInteger(std::string_view s) { + if (!isDigits(s)) { + return std::nullopt; + } + int64_t v = 0; + for (char c : s) { + v = v * 10 + (c - '0'); + } + return v; +} + +std::optional parseNumber(std::string_view s) { + if (s.empty()) { + return std::nullopt; + } + size_t i = 0; + bool neg = false; + if (s[0] == '-') { + neg = true; + i = 1; + } + if (i >= s.size()) { + return std::nullopt; + } + const size_t dot = s.find('.', i); + if (dot != std::string_view::npos) { + const auto intPart = parseInteger(s.substr(i, dot - i)); + const auto fracPart = parseInteger(s.substr(dot + 1)); + if (!intPart || !fracPart) { + return std::nullopt; + } + double whole = static_cast(*intPart); + double frac = static_cast(*fracPart); + size_t fracLen = s.size() - dot - 1; + for (size_t p = 0; p < fracLen; ++p) { + frac /= 10.0; + } + double v = whole + frac; + return neg ? Interpreter::Value{-v} : Interpreter::Value{v}; + } + if (auto n = parseInteger(s.substr(i))) { + return neg ? Interpreter::Value{-*n} : Interpreter::Value{*n}; + } + return std::nullopt; +} + +bool isIdentContinue(unsigned char c) { + return std::isalnum(c) || c == '_'; +} + +bool matchKeywordAt(std::string_view script, size_t cpos, std::string_view kw) { + if (cpos + kw.size() > script.size()) { + return false; + } + if (script.substr(cpos, kw.size()) != kw) { + return false; + } + if (cpos + kw.size() < script.size() && + isIdentContinue(static_cast(script[cpos + kw.size()]))) { + return false; + } + return true; +} + +Interpreter::Value parseLiteralText(std::string_view s) { + return Interpreter::valueFromLiteral(s); +} + +Interpreter::Value parseQuotedLiteral(std::string s) { + return s; +} + +Interpreter::Value valueOfValueExpr(const Interpreter::Value& result, + const std::vector& params) { + if (!params.empty()) { + if (const auto* name = std::get_if(¶ms[0])) { + if (!name->empty()) { + return *name; + } + } + } + return result; +} + +const std::string& paramKey(const std::vector& params) { + static const std::string kEmpty; + if (params.empty()) { + return kEmpty; + } + if (const auto* s = std::get_if(¶ms[0])) { + return *s; + } + return kEmpty; +} + +size_t paramIndex(const std::vector& params) { + if (params.empty()) { + return kNoIndex; + } + if (const auto* i = std::get_if(¶ms[0])) { + return static_cast(*i); + } + if (const auto* s = std::get_if(¶ms[0])) { + if (auto n = parseInteger(*s)) { + return static_cast(*n); + } + } + return kNoIndex; +} + +bool isBreakValue(const Interpreter::Value& v) { + const auto* cf = std::get_if(&v); + return cf && *cf == Interpreter::ControlFlow::Break; +} + +bool isContinueValue(const Interpreter::Value& v) { + const auto* cf = std::get_if(&v); + return cf && *cf == Interpreter::ControlFlow::Continue; +} + +} // namespace detail +} // namespace ir + +// --- value helpers --- + +std::string Interpreter::valueToString(const Interpreter::Value& v) { + return std::visit([](auto&& arg) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg ? "1" : "0"; + } + else if constexpr (std::is_same_v) { + return std::to_string(arg); + } + else if constexpr (std::is_same_v) { + std::ostringstream os; + os << std::fixed << std::setprecision(10) << arg; + std::string s = os.str(); + while (s.size() > 1 && s.back() == '0') { + s.pop_back(); + } + if (s.size() > 1 && s.back() == '.') { + s.pop_back(); + } + return s; + } + else if constexpr (std::is_same_v) { + return arg == Interpreter::ControlFlow::Break ? "break" : "continue"; + } + else { + return arg; + } + }, v); +} + +Interpreter::Value Interpreter::valueFromLiteral(std::string_view s) { + if (s.empty()) { + return std::string{}; + } + if (s == "true") { + return true; + } + if (s == "false") { + return false; + } + if (auto n = ir::detail::parseNumber(s)) { + return *n; + } + return std::string(s); +} + +bool Interpreter::valueIsTruthy(const Interpreter::Value& v) { + return std::visit([](auto&& arg) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg; + } + else if constexpr (std::is_same_v) { + return arg != 0; + } + else if constexpr (std::is_same_v) { + return arg != 0.0; + } + else if constexpr (std::is_same_v) { + return true; + } + else { + return !arg.empty(); + } + }, v); +} + +std::string Interpreter::valueAsString(const Interpreter::Value& v) { + if (const auto* s = std::get_if(&v)) { + return *s; + } + return Interpreter::valueToString(v); +} + +std::string Interpreter::valueAsInitBody(const Interpreter::Value& v) { + if (const auto* s = std::get_if(&v)) { + return *s; + } + return {}; +} + +std::string Interpreter::operandName(Interpreter& ir, const Interpreter::Value& opd) { + const size_t idx = ir.currentEntity().beginIndex; + if (idx > 0) { + const std::string name = ir.getEntityByIndex(idx - 1).name; + if (!name.empty()) { + return name; + } + } + return Interpreter::valueAsString(opd); +} + +bool Interpreter::valueIsInteger(const Interpreter::Value& v) { + return std::holds_alternative(v); +} + +bool Interpreter::valueIsNumeric(const Interpreter::Value& v) { + return std::holds_alternative(v) || std::holds_alternative(v); +} + +int64_t Interpreter::valueAsInt64(const Interpreter::Value& v) { + if (const auto* i = std::get_if(&v)) { + return *i; + } + if (const auto* b = std::get_if(&v)) { + return *b ? 1 : 0; + } + if (const auto* d = std::get_if(&v)) { + return static_cast(*d); + } + return 0; +} + +std::vector Interpreter::makeParam(const std::string& s) { + return Interpreter::makeParam(std::string_view{s}); +} + +std::vector Interpreter::makeParam(std::string_view s) { + if (s.empty()) { + return {}; + } + return {std::string(s)}; +} + +std::string Interpreter::paramAsString(const std::vector& params) { + return params.empty() ? std::string{} : Interpreter::valueAsString(params[0]); +} + +Interpreter::Value Interpreter::valueFromParams(const std::vector& params, + const Interpreter::Value& fallback) { + if (params.empty()) { + return fallback; + } + if (const auto* i = std::get_if(¶ms[0])) { + return *i; + } + return fallback; +} + +std::string Interpreter::cmdResultToString(const Interpreter::CmdResult& r) { + if (r.second) { + return r.second.message; + } + return Interpreter::valueToString(r.first); +} + +bool Interpreter::hasError(const Interpreter::Error& err) { + return static_cast(err); +} + +bool Interpreter::isParseError(const Interpreter::Error& err) { + return err && err.kind == Interpreter::Error::Kind::Parse; +} + +std::string_view Interpreter::valueAsStringView(const Interpreter::Value& v) { + if (const auto* s = std::get_if(&v)) { + return *s; + } + return {}; +} + +namespace InterpreterCompareDetail { + +std::optional valueAsDouble(const Interpreter::Value& v) { + if (const auto* i = std::get_if(&v)) { + return static_cast(*i); + } + if (const auto* d = std::get_if(&v)) { + return *d; + } + return std::nullopt; +} + +} // namespace InterpreterCompareDetail + +bool Interpreter::valueEquals(const Interpreter::Value& a, const Interpreter::Value& b) { + if (a.index() != b.index()) { + return Interpreter::valueAsString(a) == Interpreter::valueAsString(b); + } + return std::visit([&b](const auto& lhs) -> bool { + using T = std::decay_t; + return lhs == std::get(b); + }, a); +} + +int Interpreter::valueCompare(const Interpreter::Value& a, const Interpreter::Value& b) { + if (Interpreter::valueIsNumeric(a) && Interpreter::valueIsNumeric(b)) { + const auto da = InterpreterCompareDetail::valueAsDouble(a); + const auto db = InterpreterCompareDetail::valueAsDouble(b); + if (da && db) { + if (*da < *db) { + return -1; + } + if (*da > *db) { + return 1; + } + return 0; + } + } + const std::string sa = Interpreter::valueAsString(a); + const std::string sb = Interpreter::valueAsString(b); + if (sa.size() < sb.size()) { + return -1; + } + if (sa.size() > sb.size()) { + return 1; + } + return 0; +} diff --git a/src/interpreter_c.cpp b/src/interpreter_c.cpp index 1ab416b..31645c3 100644 --- a/src/interpreter_c.cpp +++ b/src/interpreter_c.cpp @@ -11,19 +11,24 @@ HIntr irCreateIntr(){ BOOL irAddFunction(HIntr h, char* name, irUserFunction ufunc){ if (!h || !name || !ufunc) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); + + ir->addFunction(name, [ufunc](const std::vector& args)->Interpreter::Value{ - ir->addFunction(name, [ufunc](const std::vector& args)->std::string{ - char** cargs = new char*[args.size()]; for(size_t i = 0; i < args.size(); ++i){ - cargs[i] = (char*)args[i].c_str(); + const auto s = Interpreter::valueToString(args[i]); + cargs[i] = new char[s.size() + 1]{}; + strcpy(cargs[i], s.c_str()); } auto res = ufunc(cargs, args.size()); - free(cargs); + for(size_t i = 0; i < args.size(); ++i){ + delete[] cargs[i]; + } + delete[] cargs; - return res; + return Interpreter::valueFromLiteral(res ? res : ""); }); return TRUE; } @@ -31,36 +36,39 @@ BOOL irAddFunction(HIntr h, char* name, irUserFunction ufunc){ BOOL irAddOperator(HIntr h, char* name, irUserOperator uopr, uint32_t priority){ if (!h || !name || !uopr) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - ir->addOperator(name, [uopr](std::string& left, std::string& right)->std::string{ - - char* cleft = new char[left.size() + 1]{}; - strcpy(cleft, left.c_str()); + ir->addOperator(name, [uopr](Interpreter::Value& left, Interpreter::Value& right)->Interpreter::Value{ - char* cright = new char[right.size() + 1]{}; - strcpy(cright, right.c_str()); + auto leftStr = Interpreter::valueToString(left); + auto rightStr = Interpreter::valueToString(right); + + char* cleft = new char[leftStr.size() + 1]{}; + strcpy(cleft, leftStr.c_str()); + + char* cright = new char[rightStr.size() + 1]{}; + strcpy(cright, rightStr.c_str()); auto res = uopr(&cleft, &cright); - left = cleft; - right = cright; + left = Interpreter::valueFromLiteral(cleft); + right = Interpreter::valueFromLiteral(cright); - free(cleft); - free(cright); + delete[] cleft; + delete[] cright; - return res; + return Interpreter::valueFromLiteral(res ? res : ""); }, priority); return TRUE; } - + char* irCmd(HIntr h, char* script){ if (!h || !script) return NULL; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - auto res = ir->cmd(script); + const auto res = Interpreter::cmdResultToString(ir->cmd(script)); char* cres = new char[res.size() + 1]{}; strcpy(cres, res.c_str()); @@ -68,26 +76,30 @@ char* irCmd(HIntr h, char* script){ return cres; } -BOOL irParseScript(HIntr h, char* script, char* outErr /*sz 256*/){ +BOOL irParseScript(HIntr h, char* script, char* outErr, size_t outErrSize, size_t* outPos) { if (!h || !script) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - std::string err; - bool res = ir->parseScript(script, err); + Interpreter::Error err; + const bool ok = ir->parseScript(script, err); - if (outErr){ - strncpy(outErr, err.c_str(), 255); + if (outErr && outErrSize > 0) { + strncpy(outErr, err.message.c_str(), outErrSize - 1); + outErr[outErrSize - 1] = '\0'; } - return res ? TRUE : FALSE; + if (outPos) { + *outPos = err.position; + } + return ok ? TRUE : FALSE; } char* irRunScript(HIntr h){ if (!h) return NULL; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - auto res = ir->runScript(); + const auto res = Interpreter::valueToString(ir->runScript().first); char* cres = new char[res.size() + 1]{}; strcpy(cres, res.c_str()); @@ -98,9 +110,9 @@ char* irRunScript(HIntr h){ char* irVariable(HIntr h, char* vname){ if (!h || !vname) return NULL; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - auto res = ir->variable(vname); + const auto res = Interpreter::valueToString(ir->variable(vname)); char* cres = new char[res.size() + 1]{}; strcpy(cres, res.c_str()); @@ -111,9 +123,9 @@ char* irVariable(HIntr h, char* vname){ BOOL irSetVariable(HIntr h, char* vname, char* value){ if (!h || !vname || !value) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); - auto res = ir->setVariable(vname, value); + auto res = ir->setVariable(vname, Interpreter::valueFromLiteral(value)); return res ? TRUE : FALSE; } @@ -121,7 +133,7 @@ BOOL irSetVariable(HIntr h, char* vname, char* value){ BOOL irSetMacro(HIntr h, char* mname, char* script){ if (!h || !mname || !script) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); auto res = ir->setMacro(mname, script); @@ -131,17 +143,21 @@ BOOL irSetMacro(HIntr h, char* mname, char* script){ BOOL irGotoOnLabel(HIntr h, char* lname){ if (!h || !lname) return FALSE; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); auto res = ir->gotoOnLabel(lname); return res ? TRUE : FALSE; } - + void irExitFromScript(HIntr h){ if (!h) return; - auto ir = reinterpret_cast(h); + auto ir = reinterpret_cast(h); ir->exitFromScript(); -} \ No newline at end of file +} + +void irFree(char* p) { + delete[] p; +} diff --git a/src/test.cpp b/src/test.cpp index cf9fa22..77f7927 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -4,24 +4,6 @@ // // This code is licensed under the MIT License. // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// #include #include "../include/interpreter.h" #include "../include/base_library/arithmetic_operations.h" @@ -29,65 +11,71 @@ #include "../include/base_library/containers.h" #include "../include/base_library/structure.h" #include "../include/base_library/types.h" +#include "../include/base_library/filesystem.h" +#include #include +#include using namespace std; -bool isNumber(const string& s) { - for (auto c : s) { - if (!std::isdigit(c)) { - return false; - } - } - return !s.empty(); +bool cmdIs(Interpreter& ir, const string& script, const string& expected) { + return Interpreter::cmdResultToString(ir.cmd(script)) == expected; } class InprTest : public ::testing::Test { public: - InprTest(): + InprTest(): ao_ir(ir), co_ir(ir), ts_ir(ir), bc_ir(ir), + fs_ir(ir), st_ir(ir){ - - ir.addOperator("->", [](string& leftOpd, string& rightOpd) ->string { + + ir.addOperator("->", [](Interpreter::Value& leftOpd, Interpreter::Value& rightOpd) -> Interpreter::Value { rightOpd = leftOpd; return rightOpd; }, 5); - ir.addFunction("summ", [](const vector& args) ->string { - int res = 0; + ir.addFunction("summ", [](const vector& args) -> Interpreter::Value { + int64_t res = 0; for (auto& v : args) { - if (isNumber(v)) res += stoi(v); + res += Interpreter::valueAsInt64(v); } - return to_string(res); + return res; + }); + ir.addFunction("print", [](const vector& args) -> Interpreter::Value { + for (auto& v : args) { + printf("%s ", Interpreter::valueToString(v).c_str()); + } + printf("\n"); + return std::string{}; }); Interpreter* pIr = &ir; - ir.addFunction("setB", [pIr](const vector& args) ->string { - int res = 0; + ir.addFunction("setB", [pIr](const vector& args) -> Interpreter::Value { + int64_t res = 0; for (auto& v : args) { - if (isNumber(v)) res += stoi(v); + if (Interpreter::valueIsInteger(v)) res += Interpreter::valueAsInt64(v); } - pIr->setVariable("$b", to_string(res)); - return to_string(res); + pIr->setVariable("$b", res); + return res; }); - ir.addFunction("range", [pIr](const vector& args) ->string { - int maxv = 0; - if (!args.empty() && isNumber(args[0])) - maxv = stoi(args[0]); + ir.addFunction("range", [pIr](const vector& args) -> Interpreter::Value { + int64_t maxv = 0; + if (!args.empty() && Interpreter::valueIsInteger(args[0])) + maxv = Interpreter::valueAsInt64(args[0]); auto entity = pIr->currentEntity(); - int cval = 0; - if (isNumber(entity.value)) - cval = stoi(entity.value); - - return cval < maxv ? to_string(cval + 1) : "0"; + int64_t cval = 0; + if (Interpreter::valueIsInteger(entity.value)) + cval = Interpreter::valueAsInt64(entity.value); + + return cval < maxv ? static_cast(cval + 1) : int64_t{0}; }); - ir.addFunction("getAttr", [pIr](const vector& args) ->string { + ir.addFunction("getAttr", [pIr](const vector&) -> Interpreter::Value { auto attrs = pIr->getAttributeByIndex(pIr->currentEntity().beginIndex - 1); std::ostringstream out; - std::copy(attrs.begin(), attrs.end(), std::ostream_iterator(out, ",")); + std::copy(attrs.begin(), attrs.end(), std::ostream_iterator(out, ",")); auto str = out.str(); return str.substr(0, str.size()-1); }); @@ -95,94 +83,442 @@ class InprTest : public ::testing::Test { ir.addAttribute("attr2"); ir.addAttribute("attr3"); } - ~InprTest() { + ~InprTest() { } Interpreter ir; InterpreterBaseLib::ArithmeticOperations ao_ir; InterpreterBaseLib::ComparisonOperations co_ir; InterpreterBaseLib::Types ts_ir; InterpreterBaseLib::Container bc_ir; + InterpreterBaseLib::Filesystem fs_ir; InterpreterBaseLib::Structure st_ir; }; -TEST_F(InprTest, operatorTest){ - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; $a + $b") == "7"); - EXPECT_TRUE(ir.cmd("$a = 0; $a += 5; $b = 2; $a + $b") == "7"); - EXPECT_TRUE(ir.cmd("$a = 4; ++$a; $b = 2; $a + $b") == "7"); - EXPECT_TRUE(ir.cmd("$a = 4; $a++; $b = 2; $a + $b") == "7"); - EXPECT_TRUE(ir.cmd("$a = 0; $b = 0; $c = ($a == $b); $c") == "1"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; $a->$b; $b") == "5"); - EXPECT_TRUE(ir.cmd("$b = \"summ = \"; $a = $b + \"$a + $b\"; $a") == "summ = $a + $b"); - EXPECT_TRUE(ir.cmd("$b = \"$a + $b\"; $a = \"summ = \" + $b; $a") == "summ = $a + $b"); -} -TEST_F(InprTest, conditionTest){ - EXPECT_TRUE(ir.cmd("$a = 5; if ($a == 5){ $b = 2; } $a + $b") == "7"); - EXPECT_TRUE(ir.cmd("$a = 5; if ($a == 3){ $b = 2;} else { $a = 3;}; $a") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; if ($a == 3){ $b = 2;} elseif($a == 5){ $a = 3;}; $a") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; if ($a == 3){ $b = 2;} elseif($a != 4){ $a = 3;}; $a") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; if ($a == 1 + $b){ $b = 2; } elseif($a != 4){ $a = 3;}; $a") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; } $b") == "5"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ while($b < $a){ $b += 1; break;} $a -= 1;} $b") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; if ($a == 1){ break;} } $b") == "4"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; if ($a == 1){ if ($a == 1){ break;}} } $b") == "4"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= 1; if ($a == 2){continue;} $b += 1;} $b") == "4"); - EXPECT_TRUE(ir.cmd("$a = 5; if($a == 3){ $b = 3;} elseif($a == 5){ $b = 5;} elseif($a == 5){ $b = 4;} $b") == "5"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= 1; if ($a == 2) continue; $b += 1;} $b") == "4"); - EXPECT_TRUE(ir.cmd("$a = 5; if($a == 3) $b = 3; elseif($a == 5) $b = 5; elseif($a == 5) $b = 4; $b") == "5"); -} -TEST_F(InprTest, functionTest){ - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; summ($a, $b)") == "7"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; 1 + summ($a, $b)") == "8"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; 1 + summ($a, $b) - 1") == "7"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; 1 + summ($a, $b, 1) - 1") == "8"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; 1 + summ() + $b") == "3"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; setB($a - 1)") == "4"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; 1 + summ(1, summ($a)) + $b") == "9"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; summ(summ($a, summ($b)), summ($b, summ($a)))") == "14"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; summ(summ($a + summ($b + 1, 1) + 3, summ($b)))") == "14"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 0; while($a > 0){ $a -= summ(1); if ($a == 2){continue;} $b += summ(1);} $b") == "4"); -} -TEST_F(InprTest, macrosTest){ - EXPECT_TRUE(ir.cmd("$a = 5; #macro myMacr{$a = $a + 2;} #myMacr; #myMacr; #myMacr;") == "11"); - EXPECT_TRUE(ir.cmd("$a = 5; #macro myMacr{ $a = $a + $0 + $0 + $1; } #myMacr(3,4);") == "15"); - EXPECT_TRUE(ir.cmd("#macro RANGE{while(range($0))}; $a = 0; #RANGE(100) $a += 1; $a;") == "100"); -} -TEST_F(InprTest, gotoTest){ - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; goto l_jmp; $a = summ($a, $b); l_jmp: $a;") == "5"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; while($a > 0){ $a -= 1; if ($a == 2){ goto l_jmp;}} l_jmp: $a; ") == "2"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; goto l_cyc; l_jmp: goto l_exit; l_cyc: while($a > 0){ $a -= 1; if ($a == 2){ goto l_jmp;};}; l_exit: $a;") == "2"); - EXPECT_TRUE(ir.cmd("$a = 5; $b = 2; while($a > 0){goto l_jmp1; l_jmp: $a = 10; goto l_exit; l_jmp1: $a -= 1; if ($a == 2){ goto l_jmp;}} l_exit: $a; ") == "10"); -} -TEST_F(InprTest, reflectionTest){ - EXPECT_TRUE(ir.cmd("$a = 0; while(range(100)) $a += 1; $a;") == "100"); -} -TEST_F(InprTest, containerTest){ - EXPECT_TRUE(ir.cmd("a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a.size()") == "3"); - EXPECT_TRUE(ir.cmd("a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a[1 + 1]") == "3"); - EXPECT_TRUE(ir.cmd("b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b.at(myKeyTwo)") == "myValueTwo"); - EXPECT_TRUE(ir.cmd("b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b[\"myKeyTwo\"]") == "myValueTwo"); -} -TEST_F(InprTest, structureTest){ - EXPECT_TRUE(ir.cmd("e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one") == "7"); - EXPECT_TRUE(ir.cmd("$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = $b; e.three") == "12"); - EXPECT_TRUE(ir.cmd("$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three") == "22"); -} -TEST_F(InprTest, internFuncTest){ - EXPECT_TRUE(ir.cmd("$a = 1; $b = 2; function myFunc{ $a += $b; }; myFunc()") == "3"); - EXPECT_TRUE(ir.cmd("$a = 1; $b = 2; function myFunc{ $a += $b; function myFunc2{ $a += $b; }; myFunc2(); }; myFunc()") == "5"); - EXPECT_TRUE(ir.cmd("function myFunc{ if ($0 > 1) $a = $0 * myFunc($0 - 1); else $a = 1; $a }; myFunc(5)") == "120"); - EXPECT_TRUE(ir.cmd("function myFunc{ $0 += $1; }; myFunc(2, 3)") == "5"); -} -TEST_F(InprTest, typesTest){ - EXPECT_TRUE(ir.cmd("$a: int = 123; type($a)") == "int"); - EXPECT_TRUE(ir.cmd("$b: str = \"abc\"; type($b)") == "str"); -} -TEST_F(InprTest, attributesTest){ - EXPECT_TRUE(ir.cmd("[attr1,attr2,attr3] getAttr()") == "attr1,attr2,attr3"); +TEST_F(InprTest, operatorTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; $a + $b", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; $a - $b", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 0; $a += 5; $b = 2; $a + $b", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 4; ++$a; $b = 2; $a + $b", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 4; $a++; $b = 2; $a + $b", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 0; $b = 0; $c = ($a == $b); $c", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; $a->$b; $b", "5")); + EXPECT_TRUE(cmdIs(ir, "$b = \"summ = \"; $a = $b + \"$a + $b\"; $a", "summ = $a + $b")); + EXPECT_TRUE(cmdIs(ir, "$b = \"$a + $b\"; $a = \"summ = \" + $b; $a", "summ = $a + $b")); +} +TEST_F(InprTest, conditionTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; if ($a == 5){ $b = 2; } $a + $b", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if ($a == 3){ $b = 2;} else { $a = 3;}; $a", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if ($a == 3){ $b = 2;} elseif($a == 5){ $a = 3;}; $a", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if ($a == 3){ $b = 2;} elseif($a != 4){ $a = 3;}; $a", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; if ($a == 1 + $b){ $b = 2; } elseif($a != 4){ $a = 3;}; $a", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; } $b", "5")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ while($b < $a){ $b += 1; break;} $a -= 1;} $b", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; if ($a == 1){ break;} } $b", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= 1; $b += 1; if ($a == 1){ if ($a == 1){ break;}} } $b", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= 1; if ($a == 2){continue;} $b += 1;} $b", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if($a == 3){ $b = 3;} elseif($a == 5){ $b = 5;} elseif($a == 5){ $b = 4;} $b", "5")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= 1; if ($a == 2) continue; $b += 1;} $b", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if($a == 3) $b = 3; elseif($a == 5) $b = 5; elseif($a == 5) $b = 4; $b", "5")); +} +TEST_F(InprTest, functionTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; summ($a, $b)", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ($a, $b)", "8")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ($a, $b) - 1", "7")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ($a, $b, 1) - 1", "8")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ() + $b", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; setB($a - 1)", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ(1, summ($a)) + $b", "9")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; summ(summ($a, summ($b)), summ($b, summ($a)))", "14")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; summ(summ($a + summ($b + 1, 1) + 3, summ($b)))", "14")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 0; while($a > 0){ $a -= summ(1); if ($a == 2){continue;} $b += summ(1);} $b", "4")); +} +TEST_F(InprTest, macrosTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; #macro myMacr{$a = $a + 2;} #myMacr; #myMacr; #myMacr;", "11")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; #macro myMacr{ $a = $a + $0 + $0 + $1; } #myMacr(3,4);", "15")); + EXPECT_TRUE(cmdIs(ir, "#macro RANGE{while(range($0))}; $a = 0; #RANGE(100) $a += 1; $a;", "100")); +} +TEST_F(InprTest, gotoTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; goto l_jmp; $a = summ($a, $b); l_jmp: $a;", "5")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; while($a > 0){ $a -= 1; if ($a == 2){ goto l_jmp;}} l_jmp: $a; ", "2")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; goto l_cyc; l_jmp: goto l_exit; l_cyc: while($a > 0){ $a -= 1; if ($a == 2){ goto l_jmp;};}; l_exit: $a;", "2")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; while($a > 0){goto l_jmp1; l_jmp: $a = 10; goto l_exit; l_jmp1: $a -= 1; if ($a == 2){ goto l_jmp;}} l_exit: $a; ", "10")); +} +TEST_F(InprTest, reflectionTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 0; while(range(100)) $a += 1; $a;", "100")); +} +TEST_F(InprTest, containerTest){ + EXPECT_TRUE(cmdIs(ir, "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a.size()", "3")); + EXPECT_TRUE(cmdIs(ir, "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a[1 + 1]", "3")); + EXPECT_TRUE(cmdIs(ir, "b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b.at(myKeyTwo)", "myValueTwo")); + EXPECT_TRUE(cmdIs(ir, "b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b[\"myKeyTwo\"]", "myValueTwo")); +} +TEST_F(InprTest, structureTest){ + EXPECT_TRUE(cmdIs(ir, "e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one", "7")); + EXPECT_TRUE(cmdIs(ir, "$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = $b; e.three", "12")); + EXPECT_TRUE(cmdIs(ir, "$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three", "22")); +} +TEST_F(InprTest, internFuncTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 1; $b = 2; function myFunc{ $a += $b; }; myFunc()", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 1; $b = 2; function myFunc{ $a += $b; function myFunc2{ $a += $b; }; myFunc2(); }; myFunc()", "5")); + EXPECT_TRUE(cmdIs(ir, "function myFunc{ if ($0 > 1) $a = $0 * myFunc($0 - 1); else $a = 1; $a }; myFunc(5)", "120")); + EXPECT_TRUE(cmdIs(ir, "function myFunc{ $0 += $1; }; myFunc(2, 3)", "5")); +} +TEST_F(InprTest, typesTest){ + EXPECT_TRUE(cmdIs(ir, "$a: int = 123; type($a)", "int")); + EXPECT_TRUE(cmdIs(ir, "$b: str = \"abc\"; type($b)", "str")); +} +TEST_F(InprTest, attributesTest){ + EXPECT_TRUE(cmdIs(ir, "[attr1,attr2,attr3] getAttr()", "attr1,attr2,attr3")); +} +TEST_F(InprTest, literalTest){ + EXPECT_TRUE(cmdIs(ir, "$a = -42; $a;", "-42")); + EXPECT_TRUE(cmdIs(ir, "$a = 3.14; $a;", "3.14")); +} +TEST_F(InprTest, parseTimeValueTest){ + EXPECT_TRUE(cmdIs(ir, "\"42\" + 1", "421")); + EXPECT_TRUE(cmdIs(ir, "-5 + 10", "5")); + EXPECT_TRUE(cmdIs(ir, "-3.14 + 3.14", "0")); + EXPECT_TRUE(cmdIs(ir, "1.5 + 2", "3")); + EXPECT_TRUE(cmdIs(ir, "$x = true; $x", "1")); + EXPECT_TRUE(cmdIs(ir, "$y = false; $y", "0")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; $c = ($a == $b); $c", "0")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $c = ($a == 5); $c", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; if (false) { $a = 1; } $a", "5")); + EXPECT_FALSE(ir.addFunction("true", [](const vector&) -> Interpreter::Value { + return int64_t{0}; + })); +} +TEST_F(InprTest, literalCmdRepeatTest) { + const string script = "1 + 2;"; + const string expected = "3"; + EXPECT_EQ(Interpreter::cmdResultToString(ir.cmd(script)), expected); + EXPECT_EQ(Interpreter::cmdResultToString(ir.cmd(script)), expected); + EXPECT_EQ(Interpreter::cmdResultToString(ir.cmd(script)), expected); + + const string floatScript = "-5 + 10;"; + EXPECT_EQ(Interpreter::cmdResultToString(ir.cmd(floatScript)), "5"); + EXPECT_EQ(Interpreter::cmdResultToString(ir.cmd(floatScript)), "5"); + + Interpreter::Error err; + ASSERT_TRUE(ir.parseScript("-3.14 + 3.14;", err)); + EXPECT_FALSE(Interpreter::hasError(err)); + EXPECT_EQ(Interpreter::valueToString(ir.runScript().first), "0"); + EXPECT_EQ(Interpreter::valueToString(ir.runScript().first), "0"); +} +TEST_F(InprTest, cleaningTest){ + EXPECT_TRUE(cmdIs(ir, "$a = \"a b\"; $a;", "a b")); + EXPECT_TRUE(cmdIs(ir, "1+2;", "3")); + EXPECT_TRUE(cmdIs(ir, "1 + 2;", "3")); +} +TEST_F(InprTest, unaryMinusTest){ + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = -$a; $b;", "-5")); + EXPECT_TRUE(cmdIs(ir, "$a = -(1 + 2); $a;", "-3")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $b = 2; 1 + summ($a, $b) - 1", "7")); +} +TEST_F(InprTest, parseErrorTest){ + auto r = ir.cmd("if ("); + EXPECT_TRUE(r.second); + EXPECT_FALSE(r.second.message.empty()); + EXPECT_TRUE(Interpreter::isParseError(r.second)); + + r = ir.cmd("unknownFunc();"); + EXPECT_TRUE(r.second); + EXPECT_FALSE(r.second.message.empty()); + + r = ir.cmd("$a = 1; $a;"); + EXPECT_FALSE(r.second); +} +TEST_F(InprTest, parseScriptErrorTest) { + Interpreter::Error err; + EXPECT_FALSE(ir.parseScript("if (", err)); + EXPECT_TRUE(Interpreter::hasError(err)); + EXPECT_TRUE(Interpreter::isParseError(err)); + + EXPECT_FALSE(ir.parseScript("$a = 1; unknownFunc();", err)); + EXPECT_TRUE(Interpreter::isParseError(err)); + EXPECT_GT(err.position, 0u); + + EXPECT_TRUE(ir.parseScript("$a = 5;", err)); + EXPECT_FALSE(Interpreter::hasError(err)); + const auto r1 = ir.runScript(); + EXPECT_FALSE(Interpreter::hasError(r1.second)); + EXPECT_EQ(Interpreter::valueToString(r1.first), "5"); + + EXPECT_TRUE(ir.parseScript("$a = 5;", err)); + EXPECT_FALSE(Interpreter::hasError(err)); + const auto r2 = ir.runScript(); + EXPECT_EQ(Interpreter::valueToString(r1.first), Interpreter::valueToString(r2.first)); +} +TEST_F(InprTest, filesystemTest){ + char path[] = "/tmp/ir_fs_test_XXXXXX"; + const int fd = mkstemp(path); + ASSERT_NE(fd, -1); + close(fd); + remove(path); + + const string p = path; + EXPECT_TRUE(cmdIs(ir, "f = File{\"" + p + "\"}; f.write(\"hello\"); f.exist();", "1")); + EXPECT_TRUE(cmdIs(ir, "f = File{\"" + p + "\"}; f.read();", "hello")); + EXPECT_TRUE(cmdIs(ir, "f = File{\"" + p + "\"}; f.write(\"hel\"); f.append(\"lo\"); f.read();", "hello")); + EXPECT_TRUE(cmdIs(ir, "f = File{\"" + p + "\"}; f.remove(); f.exist();", "0")); + EXPECT_TRUE(cmdIs(ir, "d = Dir{\"/tmp\"}; d.exist();", "1")); +} + +TEST_F(InprTest, valueSemanticsTest) { + EXPECT_TRUE(cmdIs(ir, "$x = \"5\"; $x + 2", "52")); + EXPECT_TRUE(cmdIs(ir, "$a = 0; if (true) { $a = 1; } $a", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; while (false) { $a = 1; } $a", "5")); + EXPECT_FALSE(ir.addOperator("false", [](Interpreter::Value&, Interpreter::Value&) -> Interpreter::Value { + return int64_t{0}; + }, 1)); + ir.setVariable("$t", true); + EXPECT_TRUE(cmdIs(ir, "$t", "1")); +} + +TEST_F(InprTest, comparisonAndArithmeticTest) { + EXPECT_TRUE(cmdIs(ir, "$a = 3; $a < 5", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $a != 5", "0")); + EXPECT_TRUE(cmdIs(ir, "$a = 3.14; $a > 3", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 6; $a / 2", "3")); + EXPECT_TRUE(cmdIs(ir, "$a = 2; $a * 3", "6")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; --$a; $a", "4")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $a -= 2; $a", "3")); + EXPECT_TRUE(cmdIs(ir, "\"5\" == \"5\"", "1")); + // Cross-type string/int compare: both coerce to comparable values. + EXPECT_TRUE(cmdIs(ir, "\"5\" == 5", "1")); +} + +TEST_F(InprTest, containerExtendedTest) { + EXPECT_TRUE(cmdIs(ir, "a = Vector{1, 2, 3}; a.size()", "3")); + EXPECT_TRUE(cmdIs(ir, "a = Vector{1, 2, 3}; $s = 0; while ($v : a) $s += $v; $s", "6")); + EXPECT_TRUE(cmdIs(ir, "m = Map{ k : 10 }; m[\"k\"]", "10")); + EXPECT_TRUE(cmdIs(ir, "$b = 12; c = Map{ one : $b + 5, two : 2}; $s = 0; while ($v : c) $s += 1; $s", "2")); + EXPECT_TRUE(cmdIs(ir, "a = Vector; a.push_back(1); a.pop_back(); a.empty()", "1")); +} + +TEST_F(InprTest, interpreterApiTest) { + EXPECT_EQ(Interpreter::valueAsInt64(ir.runFunction("summ", {int64_t{2}, int64_t{3}})), 5); + EXPECT_TRUE(cmdIs(ir, "$a = 42;", "42")); + EXPECT_EQ(Interpreter::valueAsInt64(ir.variable("$a")), 42); + EXPECT_TRUE(cmdIs(ir, "1+2;", "3")); + EXPECT_TRUE(cmdIs(ir, "10;", "10")); + + Interpreter::Error err; + EXPECT_FALSE(ir.parseScript("", err)); + EXPECT_TRUE(Interpreter::hasError(err)); + EXPECT_FALSE(ir.parseScript("$a = \"", err)); + EXPECT_TRUE(Interpreter::hasError(err)); +} + +TEST_F(InprTest, readmeExamplesTest) { + EXPECT_TRUE(cmdIs(ir, + "$a = 5; $b = 2; while($a > 1){ $a -= 1; $b = summ($b, $a); if($a < 4){ break;} } $b;", + "9")); + + struct Case { const char* script; const char* expected; }; + const Case cases[] = { + {"$a = 5; $b = 2; while ($a > 1){ $a = $a - 1; $b = summ($b, $a); if ($a < 4){ break;} } $b;", + "9"}, + {"$a = 5; $b = 2; $c = summ($a, ($a + ($a * ($b + $a))), summ(5)); $c;", "50"}, + {"a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); a[2]", "3"}, + {"b = Map; b.insert(myKeyOne, myValueOne); b.insert(myKeyTwo, myValueTwo); b[\"myKeyTwo\"]", + "myValueTwo"}, + // README comments say "1 2 3" etc.; last while-iter value is 0. + {"a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); while($v : a) print($v);", "0"}, + {"a = Vector{1 + 2, 2 + 3, 3 + 4}; while($v : a) print($v);", "0"}, + {"$b = 12; c = Map{ one : $b + 5, two : 2}; while($v : c) print($v);", "0"}, + {"e = Struct{ one : 5, two : 2}; e.one = summ(e.one, e.two); e.one", "7"}, + {"$b = 12; e = Struct{ one : $b + 5, two : 2}; e.three = e.one + e.two + 3; e.three", "22"}, + {"$a = 1; $b = 2; function myFunc{ $a += $b; }; myFunc()", "3"}, + {"$a = 1; $b = 2; function myFunc{ $a += $b; function myFunc2{ $a += $b; }; myFunc2(); }; myFunc()", + "5"}, + {"$a = 0; function myFunc{ if ($0 > 1) $a = $0 * myFunc($0 - 1); else $a = 1; $a }; myFunc(5)", + "120"}, + {"function myFunc{ $0 += $1; }; myFunc(2, 3)", "5"}, + {"b: str = \"abc\"; type(b)", "str"}, + }; + for (const auto& c : cases) { + EXPECT_TRUE(cmdIs(ir, c.script, c.expected)) << c.script; + } + + char srcPath[] = "/tmp/ir_readme_src_XXXXXX"; + char dstPath[] = "/tmp/ir_readme_dst_XXXXXX"; + const int srcFd = mkstemp(srcPath); + const int dstFd = mkstemp(dstPath); + ASSERT_NE(srcFd, -1); + ASSERT_NE(dstFd, -1); + close(srcFd); + close(dstFd); + remove(dstPath); + + const string src = srcPath; + const string dst = dstPath; + EXPECT_TRUE(cmdIs(ir, "file1 = File{\"" + src + "\"}; file1.write(\"readme\"); file1.exist();", "1")); + const string copyScript = + "file1 = File{\"" + src + "\"}; file2 = File{\"" + dst + "\"}; " + "if (file1.exist()) { $data = file1.read(); file2.write($data); }"; + const auto copyResult = ir.cmd(copyScript); + EXPECT_FALSE(Interpreter::hasError(copyResult.second)); + EXPECT_TRUE(cmdIs(ir, "f = File{\"" + dst + "\"}; f.read();", "readme")); + remove(srcPath); + remove(dstPath); +} + +TEST(valueHelpersTest, staticValueApi) { + EXPECT_TRUE(std::holds_alternative(Interpreter::valueFromLiteral("true"))); + EXPECT_TRUE(std::holds_alternative(Interpreter::valueFromLiteral("false"))); + EXPECT_TRUE(std::holds_alternative(Interpreter::valueFromLiteral("42"))); + EXPECT_TRUE(std::holds_alternative(Interpreter::valueFromLiteral("3.14"))); + EXPECT_TRUE(std::holds_alternative(Interpreter::valueFromLiteral(""))); + + EXPECT_FALSE(Interpreter::valueIsTruthy(false)); + EXPECT_FALSE(Interpreter::valueIsTruthy(int64_t{0})); + EXPECT_FALSE(Interpreter::valueIsTruthy(0.0)); + EXPECT_FALSE(Interpreter::valueIsTruthy(std::string{})); + EXPECT_TRUE(Interpreter::valueIsTruthy(true)); + EXPECT_TRUE(Interpreter::valueIsTruthy(int64_t{1})); + EXPECT_TRUE(Interpreter::valueIsTruthy(std::string{"x"})); + EXPECT_TRUE(Interpreter::valueIsTruthy(Interpreter::ControlFlow::Break)); + + EXPECT_TRUE(Interpreter::valueEquals(int64_t{5}, int64_t{5})); + EXPECT_TRUE(Interpreter::valueEquals(std::string{"5"}, int64_t{5})); + + EXPECT_GT(Interpreter::valueCompare(3.14, int64_t{3}), 0); + EXPECT_LT(Interpreter::valueCompare(int64_t{3}, int64_t{5}), 0); + + EXPECT_EQ(Interpreter::valueToString(true), "1"); + EXPECT_EQ(Interpreter::valueToString(Interpreter::ControlFlow::Break), "break"); +} + +TEST_F(InprTest, comparisonSupplementTest) { + EXPECT_TRUE(cmdIs(ir, "$a = 3; $a <= 3", "1")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; $a >= 6", "0")); + EXPECT_TRUE(cmdIs(ir, "$a = 3.14; $a == 3.14", "1")); + // Float literals: * promotes like +; / uses integer division on numeric values. + EXPECT_TRUE(cmdIs(ir, "1.5 * 2", "2")); + EXPECT_TRUE(cmdIs(ir, "1.5 / 2", "0")); +} + +TEST_F(InprTest, variableInitTest) { + EXPECT_TRUE(cmdIs(ir, "$a{10}; $a", "10")); + EXPECT_TRUE(cmdIs(ir, + "$a{5}; $b{2}; $c = summ($a, ($a + ($a * ($b + $a))), summ(5)); $c;", + "50")); +} + +TEST_F(InprTest, interpreterStateTest) { + EXPECT_TRUE(cmdIs(ir, "$a = 7;", "7")); + EXPECT_TRUE(cmdIs(ir, "$a + 1;", "8")); + + ir.cmd("$a = 1; $b = 2;"); + const auto vars = ir.allVariables(); + EXPECT_GE(vars.size(), 2u); + EXPECT_EQ(Interpreter::valueAsInt64(vars.at("$a")), 1); + + ASSERT_TRUE(ir.setMacro("#two", "2")); + EXPECT_TRUE(cmdIs(ir, "#two + 3;", "5")); + + ir.setVariable("$d", 3.14); + ir.setVariable("$s", std::string{"hi"}); + EXPECT_TRUE(std::holds_alternative(ir.variable("$d"))); + EXPECT_EQ(Interpreter::valueAsString(ir.variable("$s")), "hi"); + EXPECT_TRUE(cmdIs(ir, "$d", "3.14")); + EXPECT_TRUE(cmdIs(ir, "$s", "hi")); +} + +TEST_F(InprTest, containerEdgeTest) { + // OOB / missing keys: error string in result, not CmdResult.second. + EXPECT_TRUE(cmdIs(ir, "a = Vector{1}; a[99]", "")); + EXPECT_TRUE(cmdIs(ir, "m = Map{ k : 1 }; m[\"missing\"]", "")); + EXPECT_TRUE(cmdIs(ir, "m = Map{}; m.at(x)", "")); + EXPECT_TRUE(cmdIs(ir, "m = Map{ k : 10 }; m.at(k)", "10")); + EXPECT_TRUE(cmdIs(ir, "a = Vector; a.push_back(1); a.clear(); a.empty()", "1")); +} + +TEST_F(InprTest, hostReturnTypesTest) { + ir.addFunction("retBool", [](const vector&) -> Interpreter::Value { + return true; + }); + ir.addFunction("retDouble", [](const vector&) -> Interpreter::Value { + return 3.14; + }); + ir.addFunction("retStr", [](const vector&) -> Interpreter::Value { + return std::string{"ok"}; + }); + + EXPECT_TRUE(cmdIs(ir, "retBool()", "1")); + EXPECT_TRUE(cmdIs(ir, "retDouble()", "3.14")); + EXPECT_TRUE(cmdIs(ir, "retStr()", "ok")); + EXPECT_TRUE(cmdIs(ir, "summ(1, retBool())", "2")); + EXPECT_TRUE(cmdIs(ir, "retStr() + \"!\"", "ok!")); +} + +TEST_F(InprTest, keywordBoundaryTest) { + EXPECT_TRUE(cmdIs(ir, "m = Map{ trueVar : 5 }; m[\"trueVar\"]", "5")); + EXPECT_TRUE(cmdIs(ir, "e = Struct{ falseFlag : 2 }; e.falseFlag", "2")); + // trueVar without $ is not the bool literal; comparison yields false (0). + EXPECT_TRUE(cmdIs(ir, "if (trueVar == 5) { 1; }", "0")); +} + +TEST_F(InprTest, miscApiTest) { + Interpreter::Error err; + ASSERT_TRUE(ir.parseScript("$a = \"\";", err)); + EXPECT_FALSE(Interpreter::hasError(err)); + ir.runScript(); + EXPECT_EQ(Interpreter::valueAsString(ir.variable("$a")), ""); + + ASSERT_TRUE(ir.setMacro("#inc", "$a = $a + 1;")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; #inc; $a", "6")); + ASSERT_TRUE(ir.setMacro("inc", "$a = $a + 1;")); + EXPECT_TRUE(cmdIs(ir, "$a = 5; #inc; $a", "6")); +} + +TEST_F(InprTest, printStdoutTest) { + auto runPrint = [this](const char* script) { + testing::internal::CaptureStdout(); + const auto r = ir.cmd(script); + const std::string captured = testing::internal::GetCapturedStdout(); + EXPECT_FALSE(Interpreter::hasError(r.second)) << Interpreter::cmdResultToString(r); + EXPECT_EQ(Interpreter::cmdResultToString(r), "0"); + return captured; + }; + + const std::string out = runPrint( + "a = Vector; a.push_back(1); a.push_back(2); a.push_back(3); while($v : a) print($v);"); + EXPECT_NE(out.find("1"), std::string::npos); + EXPECT_NE(out.find("2"), std::string::npos); + EXPECT_NE(out.find("3"), std::string::npos); + + const std::string out2 = runPrint( + "a = Vector{1 + 2, 2 + 3, 3 + 4}; while($v : a) print($v);"); + EXPECT_NE(out2.find("3"), std::string::npos); + EXPECT_NE(out2.find("5"), std::string::npos); + EXPECT_NE(out2.find("7"), std::string::npos); + + const std::string out3 = runPrint( + "$b = 12; c = Map{ one : $b + 5, two : 2}; while($v : c) print($v);"); + EXPECT_NE(out3.find("one"), std::string::npos); + EXPECT_NE(out3.find("17"), std::string::npos); + EXPECT_NE(out3.find("two"), std::string::npos); + EXPECT_NE(out3.find("2"), std::string::npos); +} + +TEST_F(InprTest, interpreterCopyMoveTest) { + EXPECT_TRUE(cmdIs(ir, "$a = 1;", "1")); + Interpreter ir2 = ir; + EXPECT_TRUE(cmdIs(ir2, "$a = 2;", "2")); + EXPECT_TRUE(cmdIs(ir, "$a", "1")); + EXPECT_TRUE(cmdIs(ir2, "$a", "2")); + + EXPECT_TRUE(cmdIs(ir, "$b = 12;", "12")); + EXPECT_TRUE(cmdIs(ir, "c = Map{ one : $b + 5 }; c[\"one\"]", "17")); + + Interpreter ir3 = std::move(ir); + EXPECT_TRUE(cmdIs(ir3, "1 + 2;", "3")); } int main(int argc, char* argv[]){ - + ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +}